d3-react-experiments

Mixing d3 & React



by Christophe Rosset / @topheman

Back to basics

React basics

Any modification to state / props / context
will trigger a re-render to VirtualDOM

Changes will be flushed to the DOM

No DOM direct access

el.appendChild(childNode)

el.innerHTML = 'foo'

React basics

Event delegation

No DOM direct access

onClick={handler}

onclick="javascript:alert('toto')"

el.addEventListener('click', handler, false)

D3 basics


const valueline = d3.line()
  .x(d => x(d.date))
  .y(d => y(d.price));

const svg = d3.select('body').append('svg');
svg.append('path')
  .data([data])
  .attr('d', valueline);
          

Some simple example from bl.ocks.org

Not made for each other at first glance

  • Immutability vs mutability
  • VirtualDOM vs Direct DOM access

React reconciliation

Mutating React's DOM

Our charts's interface


const chartProps = {
  data,
  maxX,
  maxY,
  minX,
  minY,
  width,
  height,
  margin
}

<SomeChart {...chartProps} />
          

1) Vanilla d3 based


import { select } from 'd3-selection';

// ...

render() {
  return (
    <svg ref={(node) => this.rootNode = select(node)}></svg>
  );
}
          
  • Pure vanilla d3 code embedded in React component
  • Direct access to embeded DOM node

DEMO2 (transition)

Vanilla d3 based

  • Prevent re-render (of d3's root DOM node)
  • Use React's lifecycle hooks
    • componentDidMount
    • componentWillReceiveProps
    • shouldComponentUpdate
    • componentWillUpdate
    • componentDidUpdate
  • Keep apply updates from state changes

SEE BLOG POST

2) React faux DOM


render() {

  const rootNode = ReactFauxDOM.createElement('svg');
  // ... some d3-like code
  return rootNode.toReact();

}
          

Trick d3 with a DOM like structure that renders to React

Think of it like jsdom
with a .toReact() method
that renders into React elements

DEMO

React faux DOM

  • Keep the same d3 code
  • Generated svg (universal ready)
  • Animations ... :(

3) Pure JSX

  • Computation managed by d3
  • Render managed by React

DrawCircles = ({ data, ...props }) => {

  const d3computedData = d3(data, props); // fake

  return (
    <svg height={d3computedData.height}
         width={d3computedData.width}>
      {d3computedData.circles.map((circleData, key) => <circle
        key={key}
        cx={circleData.x}
        cy={circleData.y}
        r={circleData.radius}
        fill={circleData.color} />)}
    </svg>
  )

  // or ...

  return (
    <svg height={d3computedData.height}
         width={d3computedData.width}>
      {d3computedData.circles.map((circleData, key) => <MyCircle
        key={key}
        {...circleData} />)}
    </svg>
  );
}

          

Pure JSX

  • Make your own *
  • Use an existing library (like Victory)

* You might end up creating the same kind of components as existing libraries ...

Compose Pure JSX

DEMO

Compose Pure JSX

  • Easier to read (declarative syntax)
  • No lifecycle hooks needed
  • Can be stateless (functional component)

Compose Pure JSX
What about complex charts ?

DEMO

Compose Pure JSX
What about complex charts ?

  • OK for advanced charts
  • Not so good for complex ones (better use embedded vanilla d3)

SEE BLOG POST

Approaches addressed

  • Embed vanilla d3 code inside React component
  • Provide a DOM like structure to d3 that renders to react (react-faux-dom)
  • Pure JSX components

Questions ?

Resources

d3-react-experiments

Mixing d3 & React



by Christophe Rosset / @topheman