TL;DR: cleaned, got rid of classical OOP symptoms hemorrhaging through the place, year filtering done in one spot = simplistic parameterized filtering. One inflexible design decision kept intentionally. (Read more if ...)
<general rant mode on>
I got very sick of all the __bind
__bind __bind dung and got rid of it entirely; JavaScript is a language with closure support and prototypical inheritance and the abundance of __bind() always points at developers who are desperately trying to cling to 'classical OOP' and ditto inheritance schemes: that stuff is for C++, Java, etc. but using JS that way is both driving you bonkers (because 'this' will never be what you expect it to be when you stick to that way of thinking) and severely limiting yourself, because you'll have FUBARred all the useful native bahaviour alongside.
I know, I have been there myself and done all that.
However, a certain library and a certain Mike made me <insert sarcasm/> hate his guts because his code and my drive to understand what he did implicitly forced me to get back to square one and learn JavaScript like I've always learned languages: the hard way, i.e. by reading up on the reference manual and in the case of JavaScript munching over a series of articles about closures and 'this' peculiarities in JavaScript, and only after I got all that, get back and try to get something done.
I'm grateful that I was forced to retrace my steps like that and do it the old-fashioned way once more; I've learned a lot and as a result got unstuck on quite a few bits that had me completely fazed before. JavaScript was my second language that I learned like everybody else seems to learn their languages: copy-pasta-monkey-banging-ooh-shit-banana-is-getting-away, and I can tell you: it's all instant gratification, but when you're even a little stuck, you're dead in the water. Crocodile food.
Have a look at the gist Bubblechart.js code and breathe a sigh of relief as the whole __bind(yack yack yack) and this.this and this.that crowd, which was crapping their mark all over the bloody place, is ... GONE.
Makes for much more readable code; yes, to be 'readable', one needs to understand what a closure is, but that's requirement #1 for grokking JavaScript anyway. Me? Been stupid, done that, got the scars to prove it.
Now the code is much more similar to the way d3.js itself is coded; it's not a coding style, it's making good use of the JavaScript language (functions being first class citizens, for example). See how I altered the Bubblechart: no more 'new', just call and get a chart object, next you're using closures,etc. under the hood to get at all the chart goodness when you call a public member function.
Second rant: pretty please, stick to one name for a (member) variable. (Bonus: always clean up incoming data before processing. It saves a LOT of hassle. see how I go and convert incoming elements to numerics, etc., before barging on)
Cases in point:
- CSV has column 'main_trade-group' (ooch!), so first order of the day is moving that bugger from el['main_trade-group'] to el.main_trade_group. See the new bubblechart code.
- Second, tooltips looked at objects with el.tbalance and el.importsto, while the bulk of the code worked with the 'original' node elements which have el.total_balance and el.imports_to. Now everybody looks at total_balance and imports_to. One name serves everywhere, saving another multiline object copy.
I'm betting good money on that the first rant is for someone else as my bet is you copied the basic bubblechart code from someplace and took off from there. Anyway, have a look and see if you like it, or not.
The second one is basic developer discipline 101. (And I loath the word discipline, but here it applies. Please, do yourself a favor and be strict in what you code.)
</
general
rant mode off>
Ah. got that off my chest.
So, the new code has the 'filter-by-year' built in. It wasn't a matter of 'which single line needs to change' as the basic approach was too inflexible to allow for easy filtering. The current code works, but be warned: it is 'final' in that further enhancement of the d3.nest-originated structure, used this way, is still inflexible: exploding the nodes on click to show individual countries would add another layer of custom code to make it work and your head will start to buzz. I did it this way as it's most similar to the starting point in terms of code and flexibility, and there should be some 'considering how to do this, really' on the road ahead.
Advise: analyze what the current codebase does; notice the ugliness of the code when using two levels of d3.nest()ing here and consider how you'ld do the filtering and create-nodes-from-consolidated-data process bits if you only have one nest level at any one time, ever.
<spoiler alert>
One way of coding the 'data cube visualization' here would be to recreate a suitable d3.nest+rollup dataset, which would be specifically created for the active filter+expansion set. It doesn't mean adding more nest.key levels, but ditching the entire d3.nest result and doing it from scratch. That is a major difference from the original, as it means the 'raw data' must be preserved alongside - the original code didn't do this; the current code has this already set up, so it should be relatively easy to take the d3.nest-related stuff down and both reduce it to a single level and reinvoke it with the correct keys and filters on click events. The current code takes the second d3.nest level to filter, but that is not flexible if you want to approach the data from arbitrary dimensions.
If I would have to code it, I'd do my filtering (zero or more criteria) first, then do a d3.nest+rollup on the remainder. This process would rinse&repeat at each user click on a filter or zoom (expand/consolidate) command, e.g. when year filtering.
For very advanced systems, one can go and precalculate such 'cross-sections', but that's several bridges too far now, I guess.
</spoiler alert>
I brought back the 'All' button to help show the filter behaviour when a non-year filter value is passed into the filter code: when it's not part of the list of available years, simply all data is rolled up and displayed.
Also added: next to nodes, now also some 'links' are generated to help the force layout to keep same-group blobs together. It's one way of doing this, certainly not the only way, but in a force layout it works. Sort of: it's no _guarantee_ that same-group nodes are kept together, but it's a major influence in making it so. linkStrength is the one you want to tweak if the 'clustering together' isn't to your liking.
Note that node.sort doesn't really help a force layout; it can subtly influence it but the controlability is nil. node sorting is more something for a pack layout.
Removed: the 'move to center' logic. That's a copy of the 'gravity' which is already built into the force layout itself; as the center is at x=width/2 and y=height/2 it's only a rewrite of what's already in there. Negative gravity might be useful somewhere, but not here. gravity=-.001 --> +0.2 or thereabouts. Like cooking: adjust to taste.
'Charge' is used to keep nodes apart; it's not exactly collision detection/avoidance, but can be used as something similar to that; I tweaked your charge formula: divisor 7 --> divisor 5. This is to compensate for the gravity and above all the new 'links' influences, which pull nodes together, if you don't 'negative charge' them.
Last bit: the create_chart()+start() vs. redraw() logic has been refactored; chique wording for merging two chunks of code which were 90% identical. ;-)
When you fetch the repo, the commits show the consecutive stages of me going from 'original' to this end result; looking at with a good diff viewer might help to see how this came to be. (I use Scooter Software's Beyond Compare; a happy user of 10+ years of use there; it is very neat as it copes very well downplaying whitespace diffs in visualization)
Fixing the 'inflexible design' bit re filter chaining and expanding to show countries ~ treating data as a true data cube, is left as an exercise for the reader ;-) - this was done to aid learning: if I deliver this 'ready to go', all you're left with is a lacklustre 'Aha Erlebnis' at best.
On Wed, Jul 18, 2012 at 5:19 PM, Majella
<majella...@inbox.com> wrote:
Sorry should have been more specific. At the moment only the first set of data in the array are returned as per code:
year: rows[0].year
..so the filter (see code for filter in gist) is working perfectly for the year 2006 which is the one returned within this first set of data , but how do I access all rows in the nested array?
On Wednesday, 18 July 2012 12:42:52 UTC+1, Majella wrote:
Ger - Thank you for the help and it worked perfectly. Stuck with the filtering now though - previous code just not working?