Tag Archives: React

D3 React Components with Victory – Reusability / Composability

react-logo-1000-transparentlogo_d3-svg

On the topheman/d3-react-experiments project, at the end of last month, I released a few examples of React components exposing charts based either on vanilla d3 code or react-faux-dom.

In this new release, I took a full JSX approach, using some components from the Victory library. I’ve already used some of them in the past (simple pie charts / histograms), but this time, I made a more advanced chart:

  • dual Y axis (two Y axis with independent scale) / multi lines
  • user interractions:
    • mouse:
      • hover legends or lines will highlight relevent lines and axis
      • hover the chart will show a tooltip with some contextual infos
    • touch: toggle touch legends or lines will highlight relevent lines and axis
  • fully responsive
  • data comes from the npm registry API:
    • a simple configuration object will setup all the charts

Test the Demo!

Declarative

When you come back to some d3 code after a while, it can be hard to dive back in (even if it’s yours). This is where the declarative approach of React/JSX makes sense. It is easier to read and understand what’s happening, since you only have to deal with components that you feed with some props.

The code might be easier to reason about, but on the other hand, you’ll have to change your vanilla d3 approach to a component based one (and some times directly deal with svg via JSX).

Reusability / Composability

When I started doing datavisualisation in React, I felt like each chart component library I tested was too weak / not composable enough – that all you could do eventually was a single pie chart, bar chart or line chart. I almost started my own library but didn’t go through when I realised that all I would have done was making exactly the same components as libraries like Victory …

In fact, you can compose those components with each others to make more advanced charts. That way, you benefit from already available reusable components. You can even break those advanced components in multiple reusable ones on your end.

Limits

In that case, there is a limit to reusability / composability. Like any framework which gives you high level abstractions, there will come a point when your needs will become very specific and won’t fit in. If you want to make some hard core visualisations like some of which you can see on bl.ocks.org, you’ll be better coding them in vanilla d3 encapsulated in a React component.

Conclusion

  • You shouldn’t need to re-invent the wheel. React datavisualisation libraries like Victory provide components like VictoryAxis, VictoryBar, VictoryLine … They will cover your basic use cases, out of the box.
  • Those components can be composed as HOC to make advanced charts. We need people to create examples.
  • Those libraries may not be suited to make complex charts. In that case, some good ole vanilla d3, embeded in a React component will be a better solution.

Tophe

Test the Demo!

Resources:

Plain d3 code and React working together

react-logo-1000-transparentlogo_d3-svg

A few months ago, I released the v1 of topheman/d3-react-experiments. At that time, my goal was to experiment some of the existing d3 libraries that you could directly use as React components. I managed to setup a few examples with Victory and d3act.

For the v2, I took the approach of a pure d3 user that may or may not know React but would want to keep full control over the creation of his chart, using the d3 API – mixed with React lifecycle hooks.

Test the Demo!

D3 / StaticMultiLineChart

That way, I created the StaticMultiLineChart component which embeds plain d3 code (I nearly made a full copy/paste of this bl.ocks.org example) and it works just out of the box with React, without needing to know much about React lifecycle hooks.

As you can see, we only need a componentWillUpdate() lifecycle hook, to cleanup the svg node, so that drawLineChart() will work properly on each update (since DOMNodes are not reused and it appends some on each update, we would end up with multiple charts).

This is a very naive approach, but it shows how, with little knowing about React lifcecyle hooks and JSX, you can still do d3 … Transitions may not be possible to implement that way (see TransitionMultiLineChart).

View source code on Github

import React from 'react';

import ColorHash from 'color-hash';

import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import { select } from 'd3-selection';
import { axisBottom, axisLeft } from 'd3-axis';

const colorHash = new ColorHash();

export default class StaticMultiLineChart extends React.Component {

  static propTypes = {
    margin: React.PropTypes.object,
    width: React.PropTypes.number,
    height: React.PropTypes.number,
    data: React.PropTypes.object.isRequired,
    minX: React.PropTypes.number,
    maxX: React.PropTypes.number,
    minY: React.PropTypes.number,
    maxY: React.PropTypes.number
  }

  static defaultProps = {
    margin: {
      top: 20,
      right: 20,
      bottom: 30,
      left: 50
    },
    width: 700,
    height: 400
  }

  constructor() {
    super();
  }

  /**
   * This example is a reuse of some plain code from an example on https://bl.ocks.org/d3noob/4db972df5d7efc7d611255d1cc6f3c4f
   * Since the render method contains .append() invocations, I remove any child of the root node at each render
   *
   * See the other examples for smarter approaches
   */
  componentWillUpdate() {
    // each update, flush the nodes of the chart - this isn't the best way - see the other example for better practice
    while (this.rootNode.firstChild) {
      this.rootNode.removeChild(this.rootNode.firstChild);
    }
  }

  drawLineChart() {
    // ... the d3 code manipulating the DOM Node this.rootNode goes here
  }

  render() {
    // only start drawing (accessing the DOM) after the first render, once we get hold on the ref of the node
    if (this.rootNode) {
      this.drawLineChart();
    }
    else {
      // setTimeout necessary for the very first draw, to ensure drawing using a DOMNode and prevent the following error:
      // "Uncaught TypeError: Cannot read property 'ownerDocument' of null"
      setTimeout(() => this.drawLineChart(), 0);
    }

    return (
       <svg ref={(node) => this.rootNode = node}></svg>
    );
  }

}

D3 / TransitionMultiLineChart

To apply d3 transitions, you won’t be able to flush all the DOMNodes on each updates, you’ll need to keep a reference to each of them inside the component to let d3 mutate them (this isn’t specific to React).

We will use componentDidMount() hook to call our init() method which will create the nodes for the axis and line groups and store them on the component instance. Thanks to componentDidMount() behavior, it will only be called once and ensure we have access to the DOM created by React.

All our d3 code updating the DOM is in update() – split in updateSize() and updateData(). This update() method is called in componentDidUpdate(), which fires when props or state have changed (such as data, width, height …).

Since componentDidUpdate() is not called at first render, we call some setState() in componentDidMount() to ensure that our chart updates after the very first render.

To avoid some unnecessary DOMNodes attributes changing, we use componentWillReceiveProps() hook to check whether if it’s worth it to call updateSize() inside update() on the next tick by tagging this.shouldUpdateSize.

In fact, the difference between this code and plain d3 is only a little management of the lifecycle hooks of React to ensure you have access to you DOMNode or know when your data changed …

View source code on Github

import React from 'react';

import ColorHash from 'color-hash';

import { scaleLinear } from 'd3-scale';
import { line } from 'd3-shape';
import { select } from 'd3-selection';
import { axisLeft, axisBottom } from 'd3-axis';
import 'd3-transition';

const colorHash = new ColorHash();

export default class TransitionMultiLineChart extends React.Component {

  static propTypes = {
    margin: React.PropTypes.object,
    width: React.PropTypes.number,
    height: React.PropTypes.number,
    data: React.PropTypes.object.isRequired,
    minX: React.PropTypes.number,
    maxX: React.PropTypes.number,
    minY: React.PropTypes.number,
    maxY: React.PropTypes.number
  }

  static defaultProps = {
    margin: {
      top: 20,
      right: 20,
      bottom: 30,
      left: 50
    },
    width: 700,
    height: 400
  }

  constructor() {
    super();
    this.shouldUpdateSize = true;
    // minimal state to manage React lifecycle
    this.state = {
      initialized: false
    };
  }

  /**
   * From React doc https://facebook.github.io/react/docs/component-specs.html#mounting-componentdidmount :
   *
   * "Invoked once, only on the client (not on the server), immediately after the initial rendering occurs.
   * At this point in the lifecycle, you can access any refs to your children (e.g., to access the underlying DOM representation).
   * The componentDidMount() method of child components is invoked before that of parent components."
   *
   * this.init is called here because:
   * - we need the ref to the svg node
   * - it won't we called again
   */
  componentDidMount() {
    console.log('componentDidMount');
    this.init();
    // the code bellow is to trigger componentDidUpdate (which is not called at first render)
    setTimeout(() => {
      this.setState({
        initialized: true
      });
    });
  }

  /**
   * From React doc : https://facebook.github.io/react/docs/component-specs.html#updating-componentwillreceiveprops
   *
   * "Invoked when a component is receiving new props. This method is not called for the initial render.
   * Use this as an opportunity to react to a prop transition before render() is called
   * by updating the state using this.setState().
   * The old props can be accessed via this.props. Calling this.setState() within this function will not trigger an additional render."
   *
   * I use this hook to check whether or not this.updateSize should be called on the next update
   * Doing the same thing about this.updateData would involve deep checking the whole data passed.
   */
  componentWillReceiveProps({ margin, width, height, minX, maxX, maxY }) {
    console.log('componentWillReceiveProps');
    if (margin !== this.props.margin || width !== this.props.width || height !== this.props.height ||
      minX !== this.props.minX || maxX !== this.props.maxX || maxY !== this.props.maxY) {
      console.log('change size');
      this.shouldUpdateSize = true;
    }
  }

  /**
   * From React doc https://facebook.github.io/react/docs/component-specs.html#updating-componentdidupdate :
   *
   * "Invoked immediately after the component's updates are flushed to the DOM.
   * This method is not called for the initial render.
   * Use this as an opportunity to operate on the DOM when the component has been updated."
   *
   * this.update is called here because:
   * - it's not called for initial render - componentDidMount ensures to have our svg element init
   * - it's called after each update of the component - we get the new props
   */
  componentDidUpdate() {
    console.log('componentDidUpdate');
    this.update();
  }

  extractSize() {
    const { margin, width: widthIncludingMargins, height: heightIncludingMargins } = this.props;
    const width = widthIncludingMargins - margin.left - margin.right;
    const height = heightIncludingMargins - margin.top - margin.bottom;
    return {
      width,
      height,
      margin
    };
  }

  /**
   * Create svg nodes in order to reuse them
   */
  init() {
    console.log('init');
    this.lineGroup = this.rootNode.append('g');
    this.axisLeftGroup = this.lineGroup.append('g');
    this.axisBottomGroup = this.lineGroup.append('g');
  }

  updateSize() {
    console.log('updateSize');
    const { width, height, margin } = this.extractSize();
    const { minX, maxX, maxY } = this.props;

    // resize/re-align root nodes
    this.rootNode
      .attr('width', width + margin.left + margin.right)
      .attr('height', height + margin.top + margin.bottom);
    this.lineGroup
      .attr('transform',
        'translate(' + margin.left + ',' + margin.top + ')');

    // set domain for axis
    const xScale = scaleLinear().range([0, width]);
    const yScale = scaleLinear().range([height, 0]);

    // Scale the range of the data
    xScale.domain([minX, maxX]);
    yScale.domain([0, maxY]);

    // Update the X Axis
    this.axisBottomGroup.transition()
      .attr('transform', 'translate(0,' + height + ')')
      .call(axisBottom(xScale).ticks(width > 500 ? Math.floor(width / 80) : 4)); // prevent from having too much ticks on small screens

    // Update the Y Axis
    this.axisLeftGroup.transition()
      .call(axisLeft(yScale));

    // this.line is not called directy since it's used as a callback and is re-assigned. It is wrapped inside this.lineReference
    this.line = line() // .interpolate("monotone")
      .x(d => xScale(d.x))
      .y(d => yScale(d.y));
  }

  updateData() {
    console.log('updateData');
    const { data } = this.props;

    const drawLine = this.line;

    // prepare data to [ [{x, y, color}, {x, y, color}], [{x, y, color}, {x, y, color}] ... ]
    const processedData = [];
    Object.keys(data).forEach(countryName => {
      processedData.push(data[countryName].map((infos) => ({ color: colorHash.hex(countryName), ...infos})));
    });

    // generate line paths
    const lines = this.lineGroup.selectAll('.line').data(processedData);

    // [Update] transition from previous paths to new paths
    this.lineGroup.selectAll('.line')
      .transition()
      .style('stroke', d => d[0] ? d[0].color : null)
      .attr('d', drawLine);

    // [Enter] any new data
    lines.enter()
      .append('path')
      .attr('class', 'line')
      .style('stroke-width', '2px')
      .style('fill', 'none')
      .style('stroke', d => d[0] ? d[0].color : null)
      .attr('d', drawLine);

    // [Exit]
    lines.exit()
      .remove();
  }

  update() {
    console.log('update');
    // only call this.updateSize() if some props involving size have changed (check is done on componentWillReceiveProps)
    if (this.shouldUpdateSize === true) {
      this.updateSize();
      this.shouldUpdateSize = false;
    }
    this.updateData();
  }

  render() {
    console.log('render');
    return (
       <svg ref={(node) => this.rootNode = select(node)}></svg>
    );
  }

}

Conclusion

I just wanted to expose how I managed to make d3 work with React and how, even if you’re a pure d3 user, you should still be able to take advantage of the whole possibilities of d3.

Though, with its component approach and its JSX declaration syntax, React has a great potential for reusable chart components (that’s where it becomes very interesting).

The next step on topheman/d3-react-experiments will be to try to make reusable charts based on JSX (leave the computation part to d3 and make the rendering in React, without letting d3 mutate the DOM).

This may seem like rebuilding the wheel, there are already libraries that do that kind of thing – I’ve even tested some of them on that project – but I can’t seem to find any clear winner …

Are you making data visualisations on React ? What libraries are you using ? Do you use home-made components ?…

Tophe

Learn functional programming with Redux

A few weeks ago, I implemented redux on my react project now renamed topheman/react-es6-redux.

What is Redux ?

Redux is a library made by Dan Abramov that evolves the ideas of Flux, avoiding its complexity (and lots of boilerplate). You can use it anywhere (client/server) with any library. Its goal is to solve the problem of state management in applications.

To do that, you have a single store that holds the state of your whole app as an object.

This store dispatches actions (make this kind of call from anywhere inside your app).

Those actions will pass through “reducers” which will process them and return a new state: (previousState, action) => newState (they are called “pure function” because no matter what, given the same arguments, they should always return the same result – no side effects).

Since all the app state exists in one place, you can combineReducers (split them so that they’ll each handle their part of the state).

This was a very short description of what is redux – more infos on redux.js.org.

How about functional programming ?

This paradigm has been around for a long time (it was there before Object Oriented programming) and if you’ve never heard of it, you’ve been using it for sure. I have been using both but I must say that I did learned design patterns in OOP but I never took any real interest in functional programming until recently, mostly because I was used to OOP.

What’s great with Redux is that if you digg just a little, you’ll learn a lot about:

  • immutability
  • functional programming
  • ES6+

If you’re doing UI in JavaScript, you should embrace those. Don’t be afraid, we’re already developping in ES6, you might find very interesting the approach of functional programming and immutability …

Resources:

Make your own React production version with webpack

react-webpack

If you’ve been developing with React, you must have seen that you are provided with very verbose errors/warnings that can come in handy.

Quote from the download page of React:

We provide two versions of React: an uncompressed version for development and a minified version for production. The development version includes extra warnings about common mistakes, whereas the production version includes extra performance optimizations and strips all error messages.

When you’re making your production bundle, you should not include all the extra code used in development (which makes extra checks not useful in production and at the end makes your bundle heavy).

The way to do that is to use the Webpack.DefinePlugin or envify, if you’re using browserify.

By doing the following, you’ll be injecting a variable process.env.NODE_ENV set to "production" at build time which is used inside React as we’ll see.

webpack.config.prod.js

module.exports = {
  //...
  plugins:[
    new webpack.DefinePlugin({
      'process.env':{
        'NODE_ENV': JSON.stringify('production')
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress:{
        warnings: true
      }
    })
  ]
  //...
}

That way, you’ll be including the same kind of code which is in react.min.js inside your bundle. If you take a look at the source code of React inside ./node_modules/react/lib, you’ll see a lot of places with a ternary like process.env.NODE_ENV !== 'production'. All those development related features will be dropped at the minification step.

Resources:

Continue reading

Upgraded to react v0.14

logo-reactjs

The genesis of the project:

On october 7th, 2015, Facebook released the v0.14 of React. I took it as an opportunity to get back at React and upgrade those projects to the latest version, to check it out.

The easy part

Upgrading React was in fact the easy part … Since my app was react-warnings free, as said on the upgrade-guide, I only had to:

  • switch to react-dom (and react-dom/server for the server-side). As of now the render engine is a different module (React already did the render on browser and server, but now that there is react-native, it really makes sense to split)
  • switch to react-addons-* modules and drop React.addons which were deprecated (more modularity)

The tricky part

I was using react-router@0.13.2 and by upgrading to react@0.14.0, I ran into invalid peerDependencies problem (react-router@0.13.x couldn’t work with a version of React over 0.13).

So I also upgraded react-router to the v1.0.0-rc3, which has a new API … In fact, the refactor went pretty smoothly for the client-side part (the upgrade guide is well documented).

But for the server-side, I hit an issue: on previous versions, you could attach pretty much any attribute to the object passed from the router to your components. Now if you do that, it won’t get all the way down to your components. The workaround I finally found is to attach those data to the params attribute of the object passed by the router and retrieve them in the props.params of the component … There might be a better way (the createElement API …)

UPDATE: Since then, react-router has released the v0.13.4 which is compatible with react@0.13.x, but I’m glad I did the upgrade, so now I have a client & a server-side project that work with the latest versions of React & react-router!

Sugar

I refactored some components with the syntax introduced in react@0.14 for stateless functional components using ES6 fat arrow (no this, class or anything alike).

As I was at it, I enhanced the build routines:

  • I added banners on html/js/css containing description/version/git revision (something I’m doing by default now on my projects) – for those who are building their project on heroku, check this commit 😉
  • I fixed react-hot-reload for the client-side project (this is a great workflow)
  • I fixed livereloading for the server-side project (when you make changes on a component in development, not only your browser has to reload but your also has your node server, since there is server-side rendering and they both need to have the same version)

Conclusion

This sprint was a great exercise to get back into React – both client and server-side. I also got to play with webpack and gulp (yeah, I enjoy setting up build routines 🙂 ). But moreover, coding in ES6 is great … I enjoy it, I did it on my previous project and please, don’t get left behind, don’t fear webpack/Babel, those are great tools that you could easily setup via a yeoman generator or a boilerplate from git …

I’ll keep using React, I do like this library. My next steps will be to add some animation/transition and setting up redux (or a flux-like implementation).

Tophe

All the code is available on github, each tag has its own release providing a changelog with a list of items pointing to the commits of the version, so if you want to take a peak, please do.

Resources: