Alternative patterns

There are several alternatives to the pattern we're using.

D3 only for data-bound elements

Restricts D3 to rendering only the data-bound elements. For example, here's React creating all of the non-data-bound elements of a chart.

class MyChart extends React.Component {
  componentDidMount() {
    // ...
  }

  render() {
    return (
      <div>
        <h1>My chart</h1>
        <svg>
          <g className="chart group"></g>
        </svg>
      </div>
    );
  }
};

... now d3 renders the data-bound stuff:

componentDidMount() {
  d3.select('g.chart.group').selectAll('.datapoints')
    .data([ /* ... */ ]);
  // etc.
}

React for everything but the math

Let React render all DOM elements, but let D3 help calculate the properties of those elements.

class BarChart extends React.Component {
  render() {
    const { data } = this.props;
    const scale = d3.scaleLinear()
      .range([0, 100])
      .domain(d3.extent(data, d => d.value));

    const bars = data.map(d => {
      const styles = { width: scale(d.value) + '%' };
      return (
        <div styles={styles} />
      );
    });

    return (
      <div className="bar-chart">
        {bars}
      </div>
    )
  }
}

Why we still do it our way 💪

  1. D3 is good at dataviz. Let it do it's thing.
  2. Enforcing a hard border means our dataviz components don't necessarily rely on React. We can use them anywhere there is JavaScript.
  3. Forcing dataviz components to work as their own components makes us write our code in a more declarative way. Improves legibility and editability.
  4. Separating dataviz component lets us work on them in isolation. Greatly improves workflow and design.✨