Nicholas Felton’s 2013 Annual Report contained two distinct histogram variants. This post covers how I created the first variant, which looks like this:

A pretty typical histogram. However, the styling aspects are what I found most interesting:

• Truncated measure labels with a scale factor in the upper-right corner
• Slanted measure transition lines that join the entire series of measurements into a single line

I’ll cover how I achieved both of these below.

## Truncated Measure Labels

This was pretty straightforward, as there’s a math function that can be used directly to determine how many significant digits you need to represent a number: `Math.log10`. The `log10` function calculates the base 10 logarithm of a number, which can be used to calculate the number of digits within it. For example,

`Math.log10(623) = 2.7944880466591697`

The scale factor is calculated by using the whole part of the result (using `Math.floor`) as the exponent in a call to `Math.pow`.

 ``````1 2 3 4 5 6 7 8 `````` ``````// We know we're showing 12 months so divide into 24 intervals labelXOffset = (width - margins.left - margins.right) / 24; // Calculate the scale factor used to get the most-significant digit const yMin = d3.min(data, yAccessor); const scale = Math.pow(10, Math.floor(Math.log10(yMin))); const formatter = d3.format(".0f"); ``````

The minimum value was used to ensure that the labels weren’t unnecessarily “flattened” by a scale that was off by a factor of 10 (or more), and D3’s formatting functions were used to discard the fractional part of each measure.

Inside the SVG, the measure labels were added using `text` elements:

 ``````1 2 3 4 5 6 7 `````` ``````{#each data as datum} {formatter(yAccessor(datum) / scale)} {/each} ``````

 ``````1 2 3 4 5 6 7 8 `````` `````` TOTAL MONTHLY MEASUREMENT ×{formatter(scale)} ``````

## Measure Transitions

I implemented two separate line styles while building this visualization. The first used the built-in D3 line generation tools, and the second used a custom line generator.

### First Attempt Using D3 Curves

At first I tried to use the `line` function to generate a segmented line that represented the measures in the histogram. There is a `curve` function that can be used to specify how points in a line should be joined, and in particular `curveStepAfter` seemed like it might do the job.

💣 However, in order for this to work I needed to ensure that there was one extra entry in the data, or D3 wouldn’t create the final flat value segment. I simply copied the last point and set the date to one month in the future.

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 `````` ``````// Massage the data so that there are enough points to complete the path // Copy the final entry in the array const myClonedData = R.clone(data); // Now set the date to one month after myClonedData.month = "2021-01-01"; const myData = [...R.clone(data), myClonedData]; // First attempt - using 'curveStepAfter' const yLine = d3 .line() .x((d) => xScale(xAccessor(d))) .y((d) => yScale(yAccessor(d))) .curve(d3.curveStepAfter)(myData); ``````

This resulted in the following:

This was actually a pretty good starting point, and was what I used until I had time to revisit the visualization.

### Second Attempt Using Custom Line Generator

Once I had a little more time to revisit my first attempt, I decided to implement the slanted segment connectors that were used in the original.

I had to replace the use of the `line` built-in `x` and `y` functions with a custom function that took the spacing between measured values into consideration. My goal was to allocate 5% of each measure line at the start and end to be used for the transition. As a result, each line segment was drawn by creating a horizontal line for the measure itself, and then creating a line from the end of that measure to the start of the next. This logic is contained in the following function:

 `````` 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 `````` ``````function generateFeltonLine(data, xScale, xAccessor, yScale, yAccessor) { // this is only correct because of 0-based arrays // and # segments = # points - 1 const segments = data.length; // Calculate displayed segment width and connector width const segmentWidth = xScale(xAccessor(data)) - xScale(xAccessor(data)); const connectorWidth = segmentWidth * 0.05; // 5% on each side // start with the first point, as it (and the last point) // are special cases let result = [ [ xScale(xAccessor(data)), yScale(yAccessor(data)) ] ]; // now all of the interior points for (let i = 1; i < data.length - 1; i++) { result.push([ xScale(xAccessor(data[i])) - connectorWidth, yScale(yAccessor(data[i - 1])), ]); result.push([ xScale(xAccessor(data[i])) + connectorWidth, yScale(yAccessor(data[i])), ]); } // add the final point result.push([ xScale(xAccessor(data[data.length - 1])), yScale(yAccessor(data[data.length - 1])), ]); return result; } ``````

Once that was done I could create the measure line as follows:

 ``````1 2 3 4 5 `````` ``````// Second attempt - Felton-style connectors const yLine = d3 .line()( generateFeltonLine(myData, xScale, xAccessor, yScale, yAccessor) ); ``````

Resulting in a much-improved (in my opinion) final version of the visualization: