The 2013 Felton Histogram - Variant 1
Tags: showdev, vscode, svelte, dataviz
Published: 2021-03-22 17:59:56

---
title: The 2013 Felton Histogram - Variant 1
published: true
date: 2021-03-09 20:51:43 UTC
tags: showdev,vscode,svelte,dataviz
canonical_url: https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/
---
Nicholas Felton’s [2013 Annual Report](http://feltron.com/FAR13.html) contained two distinct histogram variants. This post covers how I created the first variant, which looks like this:

![Histogram (Variant 1) detail from 2013 Annual Report](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/original-felton-histogram-1.png) <figcaption>Histogram (Variant 1) detail from 2013 Annual Report <a href="http://feltron.com/FAR13.html">(Nicholas Felton)</a></figcaption>

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`.

```typescript
// 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:

```html
{#each data as datum}
  <text
    class="label"
    x={xScale(xAccessor(datum)) + labelXOffset}
    y={yScale(yAccessor(datum)) - labelYOffset}
    >{formatter(yAccessor(datum) / scale)}</text>
{/each}
```

The title and scale indicator was added using additional text elements:

```html
<text class="title" x={margins.left} y={margins.top + textHeight}>
  TOTAL MONTHLY MEASUREMENT
</text>
<text
  class="scale-label"
  x={width - margins.right}
  y={margins.top + textHeight}>&times;{formatter(scale)}
</text>
```

## 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`](https://github.com/d3/d3-shape#line) function to generate a segmented line that represented the measures in the histogram. There is a [`curve`](https://github.com/d3/d3-shape#line_curve) function that can be used to specify how points in a line should be joined, and in particular [`curveStepAfter`](https://github.com/d3/d3-shape#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.

```typescript
// 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[11]);
// 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:

![First attempt using D3 line and curveStepAfter](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-first-attempt_hu55b1a074477979be86ca962ca86a249e_59152_800x0_resize_box_2.png) <figcaption>First attempt using D3 line and curveStepAfter</figcaption>

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:

```typescript
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[1])) - xScale(xAccessor(data[0]));
  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[0])), yScale(yAccessor(data[0])) ]
  ];

  // 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:

```typescript
// 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:

![Final histogram version - variant 1](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/felton-histogram-1-final.png) <figcaption>Final histogram version - variant 1</figcaption>

The code I used to write this post is [available on GitHub](https://github.com/JeffreyPalmer/svelte-d3-examples).

Please feel free to contact me if you have any questions or comments.