A while ago I saw this amazing article in Wired. The basic idea was to start with a week’s worth of NYC 311 call data, collect the data based on the category of the 311 complaint type, and then show what complaints were made by the hour of the day in which the calls were placed. I found the results (above) to be visually stunning. Don’t you?
I had used the awesome Highcharts library before, so I decided to see if I could create the same effect of having my data “float” above the x-axis, just as in the image above. I quickly found that while very flexible, Highcharts did not offer the chart style I wanted out of the box. Hmm. Cool! We get to hack Highcharts! :) This is how I got it done. (EDIT: Since I completed this project, Highcharts has added this type of chart to their portfolio–TIL it’s called a “steamgraph.”)
This example is going to start with the standard Highcharts demo of a “stacked area” chart, which can be found here. This tutorial assumes you have some basic familiarity with Highcharts, and are comfortable with the code required to produce this “stacked area” demo. If you want to follow along, click the link above, then click the button marked “Edit in Jsfiddle.”
Before we start our hacking, we need to make some cosmetic changes to the demo (these settings can be found under
marker.enabled: false) and eliminate the border lines on the data (delete
lineColor), leaving just the color fill. Next, we change the
areaspline. This is necessary for the curved line effect we are going for. Finally, we get rid of the labels on the y-axis (delete other
yAxis settings and set
visible: false), since they will no longer be meaningful for our visualization.
So our starting point should now look like this (view on jsfiddle):
Our goal is to raise the colored areas on the chart to make them centered on the y-axis. Each point on the x-axis has to be raised by a different amount. How can we do that? My solution was to use a variation on the standard algorithm for centering text in a fixed-width column: that is, to pad each line of text with a number of leading spaces equal to (column.width - text.length) / 2.
Translating that approach to our problem, we are going to add a new data set to the chart. I call this set
floatingOffset will consist of seven data points, but these data points will be calculated specifically to “lift” or “float” the colored areas of the chart so they are centered on the y-axis.
The first thing we need to calculate is our “column width” or, in our case, the highest point of the data drawn on the chart. For this example, we’ll do the calculations by hand to illustrate the process.
The data sets from jsfiddle at the time of this writing are as follows. Use the data you see in jsfiddle for your own exercise.
[502, 635, 809, 947, 1402, 3634, 5268]
[106, 107, 111, 133, 221, 767, 1766]
[163, 203, 276, 408, 547, 729, 628]
[18, 31, 54, 156, 339, 818, 1201]
[2, 2, 2, 6, 13, 30, 46]
We can see from the chart above that the highest point is the final data point in each group, so we can calculate our max height from the last item in each array as:
5268 + 1766 + 628 + 1201 + 46 = 8909
We’re going to round this working number up to 10,000 for two reasons. One, to make our math easier. ;) And two, to provide a little extra padding, which is needed to keep our chart from actually touching the x-axis at its widest point (the result if we use the exact height calculated).
Next, we need to calculate the height of the plotted data at each data point. Just as we did when calculating the highest point, we are simply going to take the sum total of our data arrays at each point. In math-y lingo, we’re going to transpose our collection of arrays, and then sum each of the transposed arrays. Using the data arrays listed above (remember, your numbers may be different), we get the following:
[791, 978, 1252, 1650, 2522, 5978, 8909] (full disclosure: I used a spreadsheet)
Now that we (finally!) have the necessary data, we can calculate the
floatingOffset series by subtracting each of the above numbers from 10,000:
[9209, 9022, 8748, 8350, 7478, 4022, 1091] (still using my spreadsheet . . .)
and then dividing the results each by 2 (and rounding off):
[4605, 4511, 4374, 4175, 3739, 2011, 546]
This is our
floatingOffset data set. Let’s add it to the chart and see what we get (view in jsfiddle):
Okay, that’s a good start, but there are issues. We don’t really want to see “floatingOffset” in the legend, and we definitely don’t want to see the
floatingOffset area colored pink on the chart.
The first issue is solved easily enough by adding a new attribute to our
floatingOffset data set:
With that handled, I came up with two ways to keep
floatingOffset from actually appearing on the chart. The first was to simply set the
fillColor attribute for the series to a fully transparent color that would make it invisible on the chart (e.g.,
The above method felt kind of hacky to me, so I ended up implementing something that felt more idiomatic. First, I set the
visible attribute of the
floatingOffset series to
false. However, because the default behavior of Highcharts is to collapse any hidden data series, I also had to set the chart’s
ignoreHiddenSeries attribute to
These two changes give us the final effect we desire (view in jsfiddle):
And that’s it! Thanks for reading! :)
You can see the live project that generated my opening illustration here, and the source code for the project on GitHub.