Yet Another Insignificant Learning Notes
Thursday, 10 May 2018
Converting svg to base64
Wednesday, 7 February 2018
Python dynamic function call example
class dataWriter(object): |
Tuesday, 23 January 2018
On d3's Data Join and Why Sometimes Method Chaining is Troublesome
Don't get me wrong. D3 is a very powerful library, and data join is a very powerful feature. However, I have problem with its tendency to advertise method chaining in the official examples.
If you are using d3, the general updating pattern is probably the first thing you’ve learned.
Let's take the force-directed graph for example. The code below is directly copied from here.
var link = svg.append("g") |
And you probably have noticed the method chaining. Everything up to this point seems quite natural. let’s read it together line by line.
So, we append a group element to the svg, add a class attribute, select all the line elements(which gives us an empty collection at this point), binding it to our data graph.links. Then calling enter() gives us the the unbound data (If you don’t know how that happens, go to this official introduction), to which we append a line for each.
How neat, you would think! Yep, it is neat, for now.
But wait! One of the the merit of data binding is to modify the data and have our elements changing accordingly. So what say if we want to have an update function, which we call each time the data changes? Luckily there’s another example we can referring to. The parts relating to modifying link elements are copied below.
var link = g.append("g").selectAll("line"); |
Chances are you’ve never gone beyond the vanilla version of updating in the first part, and these three lines would throw you off the balance. So we know link.data do the data binding, but what does it really return? Digging a little bit into the reference and we know the answer:
It returns the overlapping part of the data and the existing elements - the update collection, as it is called. It also has two collections appending to it, calling enter() gives you the unbound data collection, and calling exit() gives you the unbound elements.
So we removes the unbound elements, adding new elements to new data. But what is merge doing here? The parameter ‘link’ is our update collection, and the caller on left side is still the newly bound elements, so we are adding the newly bound to the existing bound, which gives us all the elements at this point. Re-assigning it to link, next time when restart is called, we can again bind our new data to this collection.
Then you realize there’s no reason at all, to name the data binding return as ‘link’! We could name it as something else like linkUpdate, which is far clearer.
linkUpdate = link.data(links, function(d) { return d.source.id + "-" + d.target.id; }); |
Right, so now let’s say our existing code is exactly like the first code, and we try to extend it to accommodate updating. Which parts should you put into the update function? So we read through the code, and saying oh, so up till the selectAll line will give me the initial data collection, so I should first cut this out. Then next line is data binding, then call enter gives me the new collection, wait, i need to delete the unbound elements, so I should first stop at data binding and make an assignment, with which later I can call exit()... Cover up the second code and try it yourself. Realize how cumbersome this process is?
var link = svg.append("g") |
var link = svg.append("g") |
But what if we start with something like above? For one we just need to move sentences around and adding new ones, instead of reading through the whole chain trying to break it up.
But why is it better, you might ask? And why the previous one gives us problem?
You see, when we are using method chain, we usually hold the idea that we will always get the same object back each time we call a new function on it.
So for example in Jquery, you will be very comfortable with the following.
myEl.text('helloworld').css("color", "red").addClass("myclass") |
Navigation is also not that hard to understand.
myEl.next().css("color", "red") |
But be careful, in the second example we have context switching. After we call next, we are no long referring to the same object as before. But it is a very simple one so you mind bends without effort.
But in the previous no-break chain, how many times have we switched context? Five times! Big chances you might need to reuse some of the intermediate steps in the future. And everytime you are reading it, your mind is doing an extra hard round of twister. Why?
For one, the data() call for data binding and enter() call for getting to unbound collection are hardly very natural for human mind, as least not as self-explanatory as functions like next(). That, plus the example’s naming convention, aka calling these different contexts the same name, you will have headache 3 months later trying to read through your code ( which is exactly what happened to me yesterday).
So do yourself a favour, think twice when you are using method chain, and breaking them up if you notice you are switching context too many times.
Sunday, 21 January 2018
A useful pattern: disallow an action for a certain time
Thursday, 24 August 2017
Blur Brush
Monday, 14 August 2017
Angular + d3(v4) pie chart
Wednesday, 19 April 2017
Async collection in a nutshell
Why do I need Async Collections?
Me: So I want to do async operations on a batch of items...
Async: Say no more, bro.
So, aync collection library have 12 distinct groups of functions, each including what I call a base form and its derivatives. The 12 base functions are:
concat, detect,each, every, filter, groupBy, map, sortBy, reduce, transform, some, reject.
A little bit too much? Men's memory block is 4-7, right? Don't worry. Let's group them.
Let's do it in the form of a little questionare.
1. So you have a batch of items on which you want to run async functions. Is the function a truth test?
A. Truth test. (Go to 2) B. NO, OF COURSE NOT YOU ANIMAL. (Go to 5)
2. Do you just want a yes or no as result, or you want to have those objects as results?
A. Gimme boolean.(Go to 3) B. Gimme Objects.(Go to 4)
3. So you want a simple yes or no. You wanna to know if every one of the items have passed the test? Or just if any of them have passed the test?
A. Every. (Use every.) B. Any. (Use some)
4. You want group of items as results. Do you want the first one that has passed the test, or all? Or maybe all that failed the test?
A. First one passed.(Use detect). B. All passed.(Use filter). C.All failed. (Use reject.)
5. So you dont want truth test. But maybe you want a retained state between operation on each object?
A. NO, Seriously why would I want that.(Go to 6). B. YES. (Go to 9)
6.So you want normal stuff. Ok, do you need a grouped result of all operations or you are ok if they are seperately handled?
A. Separate, please. (Use each.) B. No. I need all results to be handled together. (Go to 7)
7. So you want results of all items together. Does the sequence matter to you?
A. YES(Go to 8). B. NO.(Use concat)
8. So you want results in right sequence. Do you have a criteria to group them, sort them, or original sequence of item is fine?
A. Group. (Use groupBy). B.Sort. (Use sortBy) C. Original sequence.(Use map)
9. So you want a retained state between operations. You can do it in series, or do it in parallel but with a key information. Which one?
A. Series. (Use reduce). B.With key. (Use transform)
To sum up, let's use a chart.
I also made a small test program to tell the difference between each, concat and map.
And here's the result.
Here's a list of all the 'affixes'.
- series : The async operation is run in series. One at a time.
- limit : Runs a maximum number of async operations at a time. User can define this number through argument 'limit'.
- of : This is only used with 'each'. Let you supply key as an arguement of the async ooperations.
- values: Used with 'map' function only. This function is used for objects and the async operation can have a key argument
- right :Only used with reduce. Run series of items in reverse order.
Limit | Series | |
---|---|---|
concat | ✓ | |
detect | ✓ | ✓ |
every | ✓ | ✓ |
filter | ✓ | ✓ |
groupBy | ✓ | ✓ |
reduce | ||
reject | ✓ | ✓ |
some | ✓ | ✓ |
each | ✓ | ✓ |
sortBy | ||
map | ✓ | ✓ |
transform |