The 2013 Felton Histogram - Variant 2
Tags: showdev, vscode, svelte, dataviz
Published: 2021-03-30 16:42:15

---
title: The 2013 Felton Histogram - Variant 2
published: true
date: 2021-03-15 20:05:32 UTC
tags: showdev,vscode,svelte,dataviz
canonical_url: https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/
---

Nicholas Felton’s [2013 Annual Report](http://feltron.com/FAR13.html) contained two distinct histogram variants. This post covers how I created the second variant, which looks like this:

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

This is similar to the first variant that I covered in [my last post](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-1/), with the following changes:

- Two measures are plotted on the same x-axis, with their y-axes facing in opposite directions
- Irregular stippling is used to differentiate the lower measure
- Measure series labels are added, while value labels are removed

## Plotting with Multiple Axes

This histogram variant adds a second measure that has its y-axis pointing downward, in opposition to the y-axis of the top measure. Since the y-axis in SVG increases from top to bottom, the range of the top scale will be from the y midpoint to the top margin (from x-axis _upwards_), and the range of the bottom scale will be from the y midpoint to the height minus the bottom margin (from x-axis _downwards_).

```typescript
$: xScale = d3
  .scaleTime()
  .domain([dateParser("2020-01-01"), dateParser("2021-01-01")])
  .range([margins.left, width - margins.right]);

// The top scale goes from midpoint to top margin
// Also add 10% of the max top value to the end of the domain
// to ensure there's headroom on the chart
$: topScale = d3
  .scaleLinear()
  .domain([0, d3.max(data, topAccessor) * 1.1])
  .range([
    (height - margins.bottom - margins.top) / 2 + margins.top,
    margins.top,
  ])
  .nice();

// The bottom scale goes from midpoint to bottom margin
// Also add 10% of the max bottom value to the end of the domain
// to ensure there's footroom (?) on the chart
$: bottomScale = d3
  .scaleLinear()
  .domain([0, d3.max(data, bottomAccessor) * 1.1])
  .range([
    (height - margins.bottom - margins.top) / 2 + margins.top,
    height - margins.bottom,
  ])
  .nice();
```

With these scales, the same line drawing techniques from the last post can be used to create the two measure lines.

First, the line paths were created using the `generateFeltonLine` method:

```typescript
$: topLine = d3.line()(
  generateFeltonLine(
    augmentedData,
    xScale,
    xAccessor,
    topScale,
    topAccessor
  )
);

$: bottomLine = d3.line()(
  generateFeltonLine(
    augmentedData,
    xScale,
    xAccessor,
    bottomScale,
    bottomAccessor
  )
);
```

Then they were placed within the SVG along with some CSS classes for styling:

```html
<svg viewBox="0 0 {width} {height}" ...>
<path class="top-line" d={topLine} />
<path class="bottom-line" d={bottomLine} />
...
</svg>
```

Note: Much of the boilerplate in the code has been omitted to keep things short. These omissions are represented by `...` in the source snippets. See [the GitHub repository](https://github.com/JeffreyPalmer/svelte-d3-examples) for the working implementation.

## Generating Irregular Stippling

The original histogram used a form of stippling to differentiate the bottom measure from the top measure. My goal was to find an algorithm to generate realistic-looking stippling, generate a polygon that represents the area to be shaded, and then use the polygon as a mask to determine which points should be shown.

### Aesthetically pleasing random point distribution

It turns out that generating stippling that looks good is actually somewhat difficult. Luckily I was able to lean on the community to find an existing implementation of [Bridson’s Algorithm](https://observablehq.com/@mbostock/poisson-disc-distribution), which can be used to approximate human stippling. Thanks (again) [Mike](https://bost.ocks.org/mike/) [Bostock](https://observablehq.com/@mbostock)!

I added a version of this generator function to my code and was able to use it to generate a set of points like so:

```typescript
import { poissonDiscSampler } from "../utils.ts";

// Use the generator to generate all of the points
// so that they can be iterated over by svelte
function generatePoissonPoints(width, height, radius) {
  const sampler = poissonDiscSampler(width, height, radius);
  let points = [];
  let a: [float, float] = sampler();
  while (a) {
    points.push(a);
    a = sampler();
  }
  return points;
}

// generate points covering the entire bottom of the SVG
// they'll be filtered later
$: points = generatePoissonPoints(
  width,
  (height - margins.top - margins.bottom) / 2,
  10
);
```

(The actual radius value of `10` that I used was the result of some visual trial and error.)

If you were to plot these points without any filtering, you get this:

![Stippling without polygon containment checks](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_157717_800x0_resize_box_2.png) <figcaption>Stippling without polygon containment checks</figcaption>

The stippling looks great! Now we just need to get rid of everything outside of the measure area.

### Creating the bounding polygon

Unsurprisingly, D3 has the ability to perform polygon containment checks out of the box via [polygonContains](https://github.com/d3/d3-polygon#polygonContains). Since I had already created a function to generate the set of points that constitute the measure line, creating a polygon from those points was straightforward.

```typescript
function generateClosedFeltonPolygon(data, xScale, xAccessor,
yScale, yAccessor) {
  const lineSegments = generateFeltonLine(data, xScale, xAccessor,
    yScale, yAccessor);
  return [
    // First point - on the origin
    [xScale(xAccessor(data[0])), yScale(0)],
    // Now the generated points for the measure lines
    ...lineSegments,
    // Bring the line back to the x-axis
    [xScale(xAccessor(data[data.length - 1])), yScale(0)],
    // Now close the polygon by going back to the origin
    [xScale(xAccessor(data[0])), yScale(0)],
  ];
}

$: closedPoly = generateClosedFeltonPolygon(
  augmentedData,
  xScale,
  xAccessor,
  bottomScale,
  bottomAccessor
);
```

Once the polygon was created, it could be used to filter the point array.

```html
<svg ...>
  ...
  {#each points as point}
    {#if d3.polygonContains(closedPoly, [point[0], height / 2 + point[1]])}
      <circle
        class="stipple"
        cx={point[0]}
        cy={height / 2 + point[1]}
        r={2.5}
      />
    {/if}
  {/each}
</svg>
```

At this point, the filtered histogram looked like this:

![Stippling after implementing polygon containment checks](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-polygon-bound_hu4838ea1efdfa98868426e0299ef5ad62_99057_800x0_resize_box_2.png) <figcaption>Stippling after implementing polygon containment checks</figcaption>

## Adding Labels

The final step was to add measure labels above and below the x-axis:

```html
<text
  class="top-label"
  text-anchor="end"
  x={width - margins.right}
  y={(height - margins.top - margins.bottom) / 2 +
     margins.top - textHeight / 2}>Top</text
>
<text
  class="bottom-label"
  text-anchor="end"
  x={width - margins.right}
  y={(height - margins.top - margins.bottom) / 2 +
     margins.top + textHeight}>Bottom</text
>
```

Unfortunately the lower label was somewhat obscured by the stippling:

![Measure labels - detail](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-no-text-filter.png) <figcaption>Measure labels - detail</figcaption>

I experimented with CSS-based text shadows in an attempt to create an outline that would mask the nearby points, but that didn’t work as well as I hoped it would. I did a little more research and eventually came across [an article describing the `feMorphology` filters available in SVGs](https://tympanus.net/codrops/2019/01/22/svg-filter-effects-outline-text-with-femorphology/). (It’s still amazing to me what’s built into SVG!)

These filters can be used to define a series of image transformations, which in this case were used to implement a very clean text outline (see the linked article for a detailed description of how this actually works).

```html
<svg ...>
  <filter id="outline">
    <feMorphology
      in="SourceAlpha"
      result="DILATED"
      operator="dilate"
      radius="5"
    />
    <feFlood
      flood-color="rgba(55, 65, 81, 1)"
      flood-opacity="1"
      result="BACKGROUND"
    />
    <feComposite
      in="BACKGROUND"
      in2="DILATED"
      operator="in"
      result="OUTLINE"
    />
    <feMerge>
      <feMergeNode in="OUTLINE" />
      <feMergeNode in="SourceGraphic" />
    </feMerge>
  </filter>

  <text
    class="top-label"
    filter="url(#outline)"
    text-anchor="end"
    x={width - margins.right}
    y={(height - margins.top - margins.bottom) / 2 +
       margins.top - textHeight / 2}>Top</text
  >
  <text
    class="bottom-label"
    filter="url(#outline)"
    text-anchor="end"
    x={width - margins.right}
    y={(height - margins.top - margins.bottom) / 2 +
       margins.top + textHeight}>Bottom</text
  >
</svg>
```

Once the SVG `text` elements were altered to include a reference to the filter via `filter="url(#outline)"`, the label text in the stippled area became significantly more legible:

![SVG text outline via feMorphology filters - detail](https://jpalmer.dev/2021/03/the-2013-felton-histogram-variant-2/felton-histogram-2-text-filtered-detail.png) <figcaption>SVG text outline via feMorphology filters - detail</figcaption>

## Final Result

At this point, the final version of the histogram was complete:

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

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

If you have any feedback, please let me know on [Twitter](https://twitter.com/jeffpalmer) or feel free to [contact me here.](https://jpalmer.dev/contact)