Skip to main content
100%

Streamgraph

✓ Published0🌍 Public
GGerHobbelt
Last edited Sep 9, 2012
Created on Aug 26, 2012

This streamgraph visualizes two synthetic datasets generated with a custom layer-stacking function, rendered as smooth, colored areas in SVG with D3 v2. Each stream can be toggled and faded by clicking or hovering on its tag in the legenda above the chart, where tag positions are computed using getBBox() to fit the graph width. The visualization supports smooth transitions between datasets and includes a button to switch between the two stacked layouts, with in-stream tags positioned heuristically to label each series. Styling uses a Google Font and interactive highlights, while the code comments note known browser quirks around font loading and transition interruptions.

AI-generated description

A little blurb to settle the nerves �� <a href="http://bl.ocks.org/3480186" title="view this gist via bl.ocks.org">?</a> <a href="http://bl.ocks.org/GerHobbelt" title="view a list of all my gists at bl.ocks.org">8</a>

This demo expands on the standard streamgraph demo in several ways:

  • generates a (bogus) set of tags, one for each series shown in the streamgraph.
  • these 'tags' are printed at the top of the graph as a legenda: the layout logic exploys SVG node.getBBox() to determine the exact pixel cost (width/height) of each tag and distributes them evenly across the horizontal expanse.
  • mixes the above with [Google] WebFonts. <font color="red">That got me a nasty surprise, which I fixed with a timer-delayed action and since Safari's behaviour raised a few hairs I arrived at the FOUT pages ('flash/flicker of unstyled content') and good cause to further enhance my timer-based hack; I added the timeout after looking at Paul Irish's blog page. Unless someone can prove me wrong, I think I got the bugger nailed. (Nasty polling way to check for font loaded and rendered, though...)</font>
  • hover and click event in legenda animate = fade the streams, and vice versa.

Positioning tags in the streams

And then there's the important bit of course: one way to find the 'proper spot' in each stream to print the tag, as big as possible. As can be seen in the code (bottom section of index.hmtl) this was a quest; I didn't commit all the iterations into git, but the 'improvement #x' comments are indicative of what came after I finally got the basic approach nailed - which only worked for a large enough data set as every 'tag' had to span multiple [x] data points to allow this simplistic algorithm to deliver something near usable.

The improvements now allow for small datasets as they are sliced/interpolated at a 1px step to produce (interpolated) data points to make the tag placement algorithm work as desired.

How I got here

The general idea was to scan each stream polygon from left to right and see where we might find a horizontal-enough section that could fit the tag, in its smallest form or maybe bigger. Then continue with the scan in order to find the 'best' location by picking the top 1 from the candidate set.

This started out as code which located the first ('left-most') [x] datapoint which was 'high enough' (.y value!) to make a chance of containing the tag text bbox. Then the code would interpolate the left-most x position for which the .y was exactly the minimum required, and from there the code went to scan to the right to find when we'd hit a right edge, either a downwards upper edge (.y0 + .y) or a upwards going lower edge (.y0). This still lacked the 'growing' code which is needed to find out how big we can scale the text vertically, but somewhere in there I screwed up and got eaten by bugs.

So I ditched that part of the code and went for the 'lazy method' as you see now, which was intended as a speed optimization for that original approach: if you'ld have enough data points, the interpolation tacics wouldn't be required as with sufficient data points, every pixel would be covered by an explicit datapoint anyhow. After a bit of testing and headscratching due to yet another off-by-one mistake of mine I got where I am today (seek out the if(0) in the code to see the visual debugging of red point indicating text x,y and green box indicating the detected largest rectangle fitting the given stream).

Then a bit of pondering led to the decision to keep the 'lazy method' as the only method (so that TODO comment at the bottom is now practically bogus) and just 'tweak' small datasets into becomes virtually large datasets by altering the stepsize in the scan loop to a non-integer, minimal step size. And the rest is history...

Similar vizzes