## Overview

In his influential data graphics text [The Visual Display of Quantitative Information](https://www.edwardtufte.com/tufte/books_vdqi), Edward Tufte describes a chart form he calls the "dot-dash-plot" (p.133), which is similar to a scatter plot but also uses each of the chart axes to display a marginal distribution. This is a powerful extension which lets readers quickly interpret the concentration of values along each one-dimensional axis more readily than with the two-dimensional central plot alone.

There are many ways to implement this with D3.js, but one particularly concise and powerful approach is to write the distribution as a secondary chart function _which is also an axis_. The rendering function can both conform to the [reusable charts pattern](https://bost.ocks.org/mike/chart/) and also host additional proxy methods which fulfill the API of a [d3-axis](https://github.com/d3/d3-axis) instance.

This project is written in a heavily annotated style called [literate programming](https://en.wikipedia.org/wiki/Literate_programming). The code blocks from this Markdown document are being executed as JavaScript by [lit-web](https://github.com/vijithassar/lit-web).

## Implementation

### Wrapper

First, an anonymous function to guard the rest of the code in this script within a closure.

```javascript
(() => {
```

### Configuration

Start with a bunch of static configuration values assigned to variables which we can then reference semantically throughout the rest of the script. In particular, it is helpful to add the size of the distribution to the [margin convention](https://observablehq.com/@d3/margin-convention).

```javascript
  const height = 480
  const width = 960
  const grid = 10
  const radius = 2
  const distributionSize = 10
  const margin = {
    top: grid * 3,
    right: grid * 3,
    bottom: grid * 3 + distributionSize,
    left: grid * 3 + distributionSize
  }
```

### Scales

The same pair of scales can be used both for plotting the central chart and for the accompanying secondary distributions.

```javascript
  const x = d3.scaleLinear()
      .domain([0, 1])
      .range([0, width - margin.left - margin.right])
  const y = d3.scaleLinear()
      .domain([0, 1])
      .range([height - margin.top - margin.bottom, 0])
```

### Data

Generate some data points to plot.

```javascript
  const xValue = d3.randomNormal(0.45, 0.12)
  const yValue = d3.randomNormal(0.6, 0.1)
  const data = Array.from({length: 500})
    .map(() => {
      return {
        x: xValue(),
        y: yValue()
      }
    })
```

### Distribution Axis

This factory returns a distribution plotting function which also _internally manages_ a D3 axis.

In the interest of simplicity, the function signature of the factory as implemented here requires the caller to specify either horizontal or vertical alignment. That helps keep the code in this demonstration concise, but if we wanted to make it a little more verbose, this could also be configured as a set of four drop-in replacements for `d3.axisTop`, `d3.axisBottom`, `d3.axisLeft`, and `d3.axisRight`.

The hybrid axis also obviously needs access to the complete data set in order to plot it.

```javascript
const distributionAxis = (dimension, data) => {

  let scale
  const axis = dimension === 'x' ? d3.axisBottom() : d3.axisLeft()

```

We'll refer to the return value of the factory as the `hybrid` because from the purposes of the calling code it is simultaneously operating as both a chart and an axis.

```javascript
  const hybrid = selection => {

    // plot the distribution
    const yOffset = dimension === 'x' ? distributionSize * -1 : 0
    const scale = axis.scale()
    selection.append('g')
      .classed('distribution', true)
      .attr('transform', `translate(0,${yOffset})`)
      .selectAll('.mark')
      .data(data)
      .enter()
      .append('g')
      .classed('mark', true)
      .append('rect')
      .attr('x', d => dimension === 'x' ? scale(d.x) : 0)
      .attr('y', d => dimension === 'y' ? scale(d.y) : 0)
      .attr('height', dimension === 'x' ? distributionSize : 1)
      .attr('width', dimension === 'y' ? distributionSize : 1)

    // render the axis
    selection.append('g')
      .classed('axis', true)
      .call(axis)

  }
```

### Axis Methods

Starting with an instance of `d3.axisTop` as the example to mock against, we now proxy all the axis configuration methods onto the hybrid function. Whenever any axis method is called on the hybrid object, it instead calls the corresponding method from its internal D3 axis.

The proxy methods examine the return value of the equivalent axis method in order to determine their own return behavior. If the memory pointer reveals that the axis method is just trying to return itself to facilitate [fluent chaining](https://en.wikipedia.org/wiki/Fluent_interface), the proxy method on the hybrid object will do the same, returning the hybrid. On the other hand, if there's a difference between the return value and the axis object, that indicates that the method is being used as a getter, which means method chaining is not a concern and the result of the getter should be returned.

You could, of course, further instrument this step with all sorts of other nonsense!

```javascript
  Object.keys({ ...d3.axisTop() }).forEach(key => {
    hybrid[key] = (...args) => {
      const result = axis[key](...args)
      return result === axis ? hybrid : result
    }
  })
```

Return the hybrid and close out the factory function.

```javascript
  return hybrid
}
```

### D3 Method

What the hell, let's staple the factory to the `d3` object. This doesn't accomplish anything technically other than a sort of namespacing, but maybe it's worth doing nonetheless just to syntactically drive home the point that this could be considered an extension of the core chart logic and is equivalent to the built-in axis functions.

```javascript
  d3.distributionAxis = distributionAxis
```

### Chart

Now let's see it in action! This next chunk of code just renders a typical [scatter plot](https://observablehq.com/@d3/scatterplot) with the `distributionAxis` in place of the usual calls to `d3.axisLeft` and `d3.axisBottom`.

```javascript
  const dotdashplot = selection => {
    const wrapper = selection
      .append('g')
      .classed('wrapper', true)
      .attr('transform', `translate(${margin.left},${margin.top})`)
    const points = wrapper
      .append('g')
      .classed('points', true)
    const point = points
      .selectAll('circle')
      .data(data)
      .enter()
      .append('circle')
      .attr('cx', d => x(d.x))
      .attr('cy', d => y(d.y))
      .attr('r', radius)
      .classed('point', true)
    const axes = wrapper
      .append('g')
      .classed('axes', true)

    // render axes with the distributionAxis
    // instead of d3.axisLeft and d3.axisBottom
    axes
      .append('g')
      .attr('class', 'distribution-axis y-axis')
      .call(d3.distributionAxis('y', data).scale(y))
    const yOffset = y.range()[0] - y.range()[1]
    axes
      .append('g')
      .attr('class', 'distribution-axis x-axis')
      .attr('transform', `translate(0,${yOffset})`)
      .call(d3.distributionAxis('x', data).scale(x))
  }
```

### Execute

Render the chart – or _all three_ charts, I should say.

```javascript
  d3.select('main')
    .html('')
    .append('svg')
    .attr('height', height)
    .attr('width', width)
    .call(dotdashplot)
```

### Fin

Close the anonymous function opened at the very beginning.

```javascript
})()
```