Adding auto-aligned X and Y-axis labels to dc.js row charts

Posted on Tags , ,

I’ve recently been playing around with dc.js, crossfilter and d3 and have been thoroughly enjoying myself in the process. After testing the waters, I dove in head first and I’m currently building some neat interactive dashboards for slicing and dicing multidimensional data as a fun project in my spare time.

One of the slight quirks in dc.js is that the dc.rowChart chart type does not inherit from the dc.coordinateGridMin mixin and as a result, does not expose any easy way of programmatically binding X- and Y-axis labels to the chart. After playing around for a bit, I was able to come up with the following approach to easily bind axis labels to the row chart while ensuring that they remain centered about the X- and Y-axes without the need for any hacky manual offsets in the code.

Before we get started, here is a demo of an example dc.js rowChart with custom x-axis andĀ  y-axis labelsĀ that are automatically aligned to the corresponding axes using the approach laid out in this article.

So let’s get started. First, I’ll list out the helper functions for binding the labels to each axis.

It’s a bit hard to work through all of the code in one go, so let’s tackle it bit by bit. First, we have the following lines of code:

Here, we are defining the addXLabel method and initially creating a d3-selection of the chart SVG element and then appending a text element to it. We define the text-anchor attribute to be middle to ensure that the text is always horizontally centered about the anchor point. This makes alignment to the d3 axes considerably easier. Initially, we assign an x and y positioning in the SVG canvas that are approximate best guesses for where they need to go. We are going to refine these locations further on in the code. Next, we have the following two lines of code:

The first line is very important, and a common pattern involved in text alignment problems in d3/dc.js land. node() is a d3 method that returns the DOM node associated with a d3 selection. We then use the getBbox() SVG method to query this DOM node and obtain the width, height and x and y positions of the element in the parent SVG element. The second line just reads in the top, right, bottom, and left margins for the row chart using the margins() method in dc.js.

This last bit of code is where the final, correct positioning for the text element is calculated and assigned. For the x-axis label, we calculate the width of the actual x-axis (chart width – left margin – right margin) and then set the x attribute of the text element to the mid-point of the x-axis object, taking into account the extra offset due to the left margin of the chart object.

The second half of the code for the y-axis label is very similar to this. The only difference is that we apply a transform of rotate(-90) to the text element in order to have it rotated and parallel with the y-axis. Rotation transforms also change the orientation of the x and y-axis for the text element, since they are defined in relation to the local co-ordinate frame of the text element (which is now rotated by 90 degrees). As a result, the x attribute now represents the vertical positioning for the y-label and the y attribute represents the horizontal positioning. Furthermore, due to the rotation of the co-ordinate system, the x values representing the vertical dimension of the chart are all negative with the zero value representing the top of the chart.

Once we have these utility functions to bind the axis labels to the chart, we need to figure out how to correctly invoke these in order to display the labels. The trick is to add these methods to the dc.js chart object using on.("postRender") which essentially instructs the chart object to invoke the provided callback function (which takes the current chart object as its single input) when the initial rendering of the chart is completed. We need to bind our axis label method to the postRender callback function because we are relying on using D3 selections of chart objects in the DOM to add and align labels and these will only be present in the DOM at a later point in time from when the chart is initialized (once something like dc.renderAll() is invoked).

You could certainly invoke the addXLabel and addYLabel methods separately by invoking them after the call to rendering this chart is executed. However, the .on("postRender") approach offers the advantage of providing a more modular and reusable piece of code for axis labeling of dc.rowChart objects without requiring a separate block of code in a different section of the file.

You can access the full working copy of this demo at my Github repository.

Feel free to comment below if you found this useful or if you have your own way of tackling this that takes a different approach.

PS – A big shout out to all the devs of dc.js for the outstanding documentation. I only wish that every open source library had a similar level of documentation supported with working examples.