Why React in place of D3?

So, I’m migrating my web app to full client-side rendering using React recently. The reason is that I mixed both server-side rendering and client-side render too much. At first, all the pages use server rendering (something like jinja2 template). However, as a consequence of the increase in interaction with user on the web app, I started to add more and more js code, which leads to the logic duplication in both backend and frontend. I decided to move all the rendering to React.js and that makes my life much easier dealing with all the DOM manipulation and two-way binding.

The only thing I need to deal with is the diagram that I implemented using D3.js before. I have been researching on the internet for a good solution and I was very closed to follow one of those tutorials, which suggests hooking D3.js rendering in React’s componentDidMount event (actually, most of the tutorials suggest that). Suddenly, one of my frontend friend recommended me throwing away D3.js for all those tasks. He said that React.js is very good at DOM manipulation stuff, why I have to mixed D3 inside that to lose all the flexibility of two-way binding, virtual DOM update,… Yeah, that sounds logical and I decided to give it a try, threw away all my old code and started in a fresh new React way. Of course, I didn’t D3.js completely, I still use it because of its supporting functions for calculating the diagram position and coordination.

Implement the Tree diagram in React.js

Okay, the first thing I need to do is to convert this old piece of code from D3 to React. The requirement is to draw a family tree like this. In contrast to my imagination, rendering the tree diagram using React is an amazingly effortless task.

tree diagram

D3 Code

  • Render the nodes and links (not exactly, but similar to this).
// Calculate the tree diagram, nodes list position using d3
...

// Render the nodes
var nodeGroups = rootGroup.selectAll("g.node")
  .data(nodesList, function(d) { return d.id || (d.id = ++id); });

var nodeEnter = nodeGroups.enter().append("svg:g")
      .attr("class", "node")
      .attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; });
nodeEnter.append("svg:circle")
  .attr("r", 10)
  .style("fill", function(d) { return d._children ? "lightsteelblue" : "#fff"; });
nodeEnter.append("svg:text")
    .text(function(d) { return d.info["fullName"]; })
    .attr("y", -19)
    .attr("dy", ".35em")
    .attr("text-anchor", "middle")
    .style("fill-opacity", 1);
nodeEnter.append("svg:image")
    .attr("xlink:href", function(d){
      return d.info.picture;
    })
    .attr("x", -20)
    .attr("y", -68)
    .attr("height", "40px")
    .attr("width", "40px");

// Render the links
var linksGroup = rootGroup.selectAll("path.link")
      .data(linksList, function(d) { return d.target.id; });
linksGroup.enter().insert("svg:path", "g")
    .attr("class", "link")
    .attr("d", diagonal);

I skip some parts in the code to calculate the nodes list position for demonstration. You can check the D3 tree documentation here or refer to the full implementation on my Github

The D3 code is much similar to jQuery style, which makes me feel difficult to imagine the DOM structure of the graph. It also requires me to explicitly update the tree graph everytime the data changes (the user click on the button to expand or collapse the tree).

React Code

What we need to render that graph are the person nodes and the links. Each person node will be render as a group (<g>) of other elements (the person image, person name and the circle button to expand or collapse the branch). Let’s take a look at the React style code.

class TreeView extends Component {

  render() {
    /* get the pedigree tree data */
    const { treeData, containerWidth, containerHeight } = this.props;

    /* still use d3.js to calculate the tree layout and position of nodes, links */
    const treeLayout = d3.layout.tree().size([containerWidth, containerHeight]);
    const nodesList = treeLayout.nodes(root).reverse();
    const linksList = treeLayout.links(nodesList);
    const diagonal = d3.svg.diagonal().projection((d) => [d.x, d.y]);

    /* render the nodes */
    const nodes = nodesList.map(node => {
      return (
        <g key={node.id} className="node"
           transform={`translate(${node.x}, ${node.y})`}>
          <circle r="10" style={'fill': node._children ? 'lightsteelblue' : '#fff'} />
          <text y="-19" dy=".35em" textAnchor="middle"
                style={'fillOpacity': 1}>{node.fullName}</text>
          <image href={node.picture} x="-20" y="-68"
                 width="40px" height="40px"></image>
        </g>
      );
    });

    /* render the links */
    const links = linksList.map(link => {
      return (
        <path key={`${link.source.id}-${link.target.id}`} className="link"
              d={diagonal(link)} />
      );
    });

    return (
      <div className="tree-container">
        <svg height="1000" width={containerWidth}>
          <g>
            {links}
            {nodes}
          </g>
        </svg>
      </div>
    );
  }

}

As you can see from the code above, we still utilize D3 but only to calculate the tree diagram, the position of all nodes and links (yeah, why need to reinvent the wheel, just use what’s already there). It’s much better and easier to understand, especially the svg tag like <g>, <circle>, <path>,… We can also benefit from React two-way binding and DOM manipulation. There is no need to explicitly call the render or update function. React does that automagically with it’s excellent virtual DOM.

Responsive Diagram

The best thing when using React is it’s two-way binding and auto DOM update when data changes. Thanks to react-dimensions, I can easily make my graph become a responsive one with just one line changed in my code. All I need to do is to wrap my component inside the Dimension higher-order component

const Dimensions = require('react-dimensions');

//
...

module.exports = Dimensions()(TreeView);

Code: Github repo

Graph Animation

Animation in React is a bit tricky. However, with the help of react-motion and it’s <TransitionMotion />, we can easily simulate d3’s enter and exit event. Simply implement the willEnter and willLeave function of the TransitionMotion, you can achieve nearly the same result as can do in d3.

Full demo code

The full code can be found on my Github, under the clojure-pedigree repo. It uses Baobab to store data and baobab-react for interacting with React

Demo

tree diagram