Loading...

How to Building Animated Components, or How React Makes D3 Better

View: 556    Dowload: 0   Comment: 0   Post by: hanhga   Category: Javascript   Fields: Other

Many of the best data visualizations you’ve seen online use D3. It is a great library, and with the recent v4 update, it became more robust than ever.

Add React, and you can make D3 even better.

Just like jQuery, D3 is powerful but low level. The bigger your visualization, the harder your code becomes to work with, the more time you spend fixing bugs and pulling your hair out.

React can fix that.

You can read my book React+d3js ES6 for a deep insight, or keep reading for an overview of how to best integrate React and D3. In a practical example, we’ll see how to build declarative transition-based animations.

A version of this article also exists as a D3 meetup talk on YouTube.

Is React Worth It?

OK, React is big. It adds a ton of code to your payload, and it increases your dependency footprint. It’s yet another library that you have to keep updated.

Setting up Webpack and Babel is easy these days: just run create-react-app. It gives you JSX compilation, modern JavaScript features, linting, hot loading, and code minification for production builds. It’s great.

Despite the size and tooling complexity, React is worth it, especially if you’re serious about your visualization. If you’re building a one-off that you’ll never have to maintain, debug, or expand, stick to pure D3. If you’re building something real, I encourage you to add React to the mix.

To me, the main benefit is that React ~~forces~~ strongly encourages you to componentize your code. The other benefits are either symptoms of componentization, or made possible by it.

The main benefits of using React with your D3 code are:

  • componentization
  • easier testing & debugging
  • smart DOM redraws
  • hot loading

Componentization encourages you to build your code as a series of logical units – components. With JSX, you can use them like they were HTML elements: <Histogram /><Piechart /><MyFancyThingThatIMade />. We’ll dive deeper into that in the next section.

Building your visualization as a series of components makes it easier to test and debug. You can focus on logical units one at a time. If a component works here, it will work over there as well. If it passes tests and looks nice, it will pass tests and look nice no matter how often you render it, no matter where you put it, and no matter who calls it. 

Benefits of Componentization

Components this, components that. Blah blah blah. Why should you care? Your dataviz code already works. You build it, you ship it, you make people happy.

But does the code make you happy? With components, it can. Components make your life easier because they make your code:

  • declarative
  • reusable
  • understandable
  • organized

It’s okay if that sounds like buzzword soup. Let me show you.

For instance, declarative code is the kind of code where you say what you want, not how you want it. Ever written HTML or CSS? You know how to write declarative code! Congratz!

React uses JSX to make your JavaScript look like HTML. But don’t worry, it all compiles to pure JavaScript behind the scenes.

Try to guess what this code does:

render() {
  // ...
  return (
      <g transform={translate}>
          <Histogram data={this.props.data}
                     value={(d) => d.base_salary}
                     x={0}
                     y={0}
                     width={400}
                     height={200}
                     title="All" />
          <Histogram data={engineerData}
                     value={(d) => d.base_salary}
                     x={450}
                     y={0}
                     width={400}
                     height={200}
                     title="Engineer" />
          <Histogram data={programmerData}
                     value={(d) => d.base_salary}
                     x={0}
                     y={220}
                     width={400}
                     height={200}
                     title="Programmer"/>
          <Histogram data={developerData}
                     value={(d) => d.base_salary}
                     x={450}
                     y={220}
                     width={400}
                     height={200}
                     title="Developer" />
      </g>
  )
}

If you guessed “Renders four histograms”, you were right. Hooray.

After you create a Histogram component, you can use it like it was a normal piece of HTML. A histogram shows up anywhere you put <Histogram /> with the right parameters.

In this case, the parameters are x and y coordinates, width and height sizing, the title, some data, and avalue accessor. They can be anything your component needs.

Parameters look like HTML attributes, but can take any JavaScript object, even functions. It’s like HTML on steroids.

With some boilerplate and the right dataset, that code above gives you a picture like this. A comparison of salary distributions for different types of people who write software.

Look at the code again. Notice how reusable components are? It’s like <Histogram /> was a function that created a histogram. Behind the scenes it does compile into a function call – (new Histogram()).render(), or something similar. Histogram becomes a class, and you call an instance’s render function every time you use<Histogram />.

React components should follow the principles of good functional programming. No side effects, statelessness, idempotency, comparability. Unless you really, really want to break the rules.

Unlike JavaScript functions where following these principles requires deliberate effort, React makes it hard not to code that way. That’s a win when you work in a team.

Declerativeness and reusability make your code understandable by default. If you’ve ever used HTML, you can read what that code does. You might not understand the details, but if you know some HTML and JavaScript, you know how to read JSX.

Complex components are made out of simpler components, which are made out of even simpler components, which are eventually made out of pure HTML elements. This keeps your code organized.

When you come back in six months, you can look at your code and think, “Ah yes, four histograms. To tweak this, I should open the Histogram component and poke around”

React takes the principles I’ve always loved about fancy-pants functional programming and makes them practical. I love that.

Let me show you an example – an animated alphabet.

A Practical Example

This is a shortened version of a more in-depth article on React and D3 and transitions that I posted on my blog a few months ago. We’re going to gloss over some details in this version to keep it short. You can dive into the full codebase in the github repository.

The code is based on React 15 and D3 4.0.0. Some of the syntax I use, like class properties, is not in stable ES6 yet, but should work if you use create-react-app for your tooling setup.

To make an animated alphabet, we need two components:

  • Alphabet, which creates random lists of letters every 1.5 seconds, then maps through them to render Lettercomponents
  • Letter, which renders an SVG text element, and takes care of its own enter/update/exit transitions

We’re going to use React to render SVG elements, and we’ll use D3 for transitions, intervals, and some maths.

The Alphabet Component

The Alphabet component holds the current list of letters in state and renders a collection of Letter components in a loop.

We start with a skeleton like this:

// src/components/Alphabet/index.jsx
import React, { Component } from 'react';
import ReactTransitionGroup from 'react-addons-transition-group';
import * as d3 from 'd3';

require('./style.css');

import Letter from './Letter';

class Alphabet extends Component {
    static letters = "abcdefghijklmnopqrstuvwxyz".split('');
    state = {alphabet: []}

    componentWillMount() {
        // starts an interval to update alphabet
    }

    render() {
        // spits out svg elements
    }
}

export default Alphabet;

We import our dependencies, add some styling, and define the Alphabet component. It holds a list of available letters in a static letters property and an empty alphabet in component state. We’ll need acomponentWillMount and a render method as well.

The best place to create a new alphabet every 1.5 seconds is in componentWillMount:

// src/components/Alphabet/index.jsx
    componentWillMount() {
        d3.interval(() => this.setState({
           alphabet: d3.shuffle(Alphabet.letters)
                       .slice(0, Math.floor(Math.random() * Alphabet.letters.length))
                       .sort()
        }), 1500);
    }

We use d3.interval( //.., 1500) to call a function every 1.5 seconds. On each period, we shuffle the available letters, slice out a random amount, sort them, and update component state with setState().

This ensures our alphabet is both random and in alphabetical order. setState() triggers a re-render.

Our declarative magic starts in the render method.

// src/components/Alphabet/index.jsx
    render() {
        let transform = `translate(${this.props.x}, ${this.props.y})`;

        return (
            <g transform={transform}>
                <ReactTransitionGroup component="g">
                    {this.state.alphabet.map((d, i) => (
                        <Letter d={d} i={i} key={`letter-${d}`} />
                     ))}
                </ReactTransitionGroup>
            </g>
        );
    }

We use an SVG transformation to move our alphabet into the specified (x, y) position, then define aReactTransitionGroup and map through this.state.alphabet to render a bunch of Letter components with wanton disregard.

Each Letter gets its current text, d, and index, i. The key attribute helps React recognize which component is which. Using ReactTransitionGroup gives us special component lifecycle methods that help with smooth transitions.

ReactTransitionGroup

In addition to the normal lifecycle hooks that tell us when a component mounts, updates, and unmounts, ReactTransitionGroup gives us access to componentWillEntercomponentWillLeave, and a few others. Notice something familiar?

componentWillEnter is the same as D3’s .enter()componentWillLeave is the same as D3’s .exit(), andcomponentWillUpdate is the same as D3’s .update().

“The same” is a strong concept – they’re analogous. D3’s hooks operate on entire selections – groups of components – while React’s hooks operate on each component individually. In D3, an overlord is dictating what happens; in React, each component knows what to do.

That makes React code easier to understand. I think. ¯_(ツ)_/¯

ReactTransitionGroup gives us even more hooks, but these three are all we need. It’s nice that in bothcomponentWillEnter and componentWillLeave, we can use a callback to explicitly say “The transition is done. React, back to you”.

My thanks to Michelle Tilley for writing about ReactTransitionGroup on StackOverflow.

The Letter Component

Now we’re ready for the cool stuff – a component that can transition itself into and out of a visualization declaratively.

The basic skeleton for our Letter component looks like this:

// src/components/Alphabet/Letter.jsx

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import * as d3 from 'd3';

class Letter extends Component {
    state = {
        y: -60,
        x: 0,
        className: 'enter',
        fillOpacity: 1e-6
    }
    transition = d3.transition()
                   .duration(750)
                   .ease(d3.easeCubicInOut);
componentWillEnter(callback) {
        // start enter transition, then callback()
    }

    componentWillLeave(callback) {
        // start exit transition, then callback()
    }

    componentWillReceiveProps(nextProps) {
        if (this.props.i != nextProps.i) {
           // start update transition
        }
    }

    render() {
       // spit out a <text> element
    }
};

export default Letter;

We start with some dependencies and define a Letter component with a default state and a default transition. In most cases, you’d want to avoid using state for coordinates and other transient properties. That’s what props are for. With transitions we use state because it helps us keep React’s reality in sync with D3’s reality.

That said, those magic default values could be default props. That would make our Alphabet more flexible.

componentWillEnter

We put the enter transition in componentWillEnter.

// src/components/Alphabet/Letter.jsx
    componentWillEnter(callback) {
        let node = d3.select(ReactDOM.findDOMNode(this));

        this.setState({x: this.props.i*32});

        node.transition(this.transition)
            .attr('y', 0)
            .style('fill-opacity', 1)
            .on('end', () => {
                this.setState({y: 0, fillOpacity: 1});
                callback()
            });
    }

Then we update this.state.x using the current index and letter width. The width is a value that we Just Know™. Putting x in state helps us avoid jumpiness: The i prop changes on each update, but we want to delay when theLetter moves.

When a Letter first renders, it’s invisible and 60 pixels above the baseline. To animate it moving down and becoming visible, we use a D3 transition.

We use node.transition(this.transition) to start a new transition with default settings from earlier. Any.attr and .style changes that we make happen over time directly on the DOM element itself.

This confuses React because it assumes it’s the lord and master of the DOM. So we have to sync React’s reality with actual reality using a callback: .on('end', ...). We use setState() to update component state, and trigger the main callback. React now knows this letter is done appearing.

componentWillLeave

The exit transition goes in componentWillLeave(). Same concept as above, just in reverse.

// src/components/Alphabet/
    componentWillLeave(callback) {
        let node = d3.select(ReactDOM.findDOMNode(this));

        this.setState({className: 'exit'});

        node.transition(this.transition)
            .attr('y', 60)
            .style('fill-opacity', 1e-6)
            .on('end', () => {
                callback()
            });
    }

This time, we update state to change the className instead of x. That’s because x doesn’t change.

The exit transition itself is an inverse of the enter transition: letter moves down and becomes invisible. After the transition, we tell React it’s okay to remove the component.

componentWillReceiveProps

The update transition goes into componentWillReceiveProps().

// src/components/Alphabet/Letter.jsx
    componentWillReceiveProps(nextProps) {
        if (this.props.i != nextProps.i) {
            let node = d3.select(ReactDOM.findDOMNode(this));

            this.setState({className: 'update'});

            node.transition(this.transition)
                .attr('x', nextProps.i*32)
                .on('end', () => this.setState({x: nextProps.i*32}));
        }
    }

You know the pattern by now, don’t you? Update state, do transition, sync state with reality after transition.

In this case, we change the className, then move the letter into its new horizontal position.

render

After all that transition magic, you might be thinking “Holy cow, how do I render this!?”. I don’t blame ya!

But we did all the hard work. Rendering is straightforward:

// src/components/Alphabet/Letter.jsx
    render() {
        return (
            <text dy=".35em"
                  y={this.state.y}
                  x={this.state.x}
                  className={this.state.className}
                  style={{fillOpacity: this.state.fillOpacity}}>
                {this.props.d}
            </text>
        );
    }

We return an SVG <text> element rendered at an (x, y) position with a className and a fillOpacity. It shows a single letter given by the d prop.

As mentioned: using state for xyclassName, and fillOpacity is wrong in theory. You’d normally use props for that. But state is the simplest way I can think of to communicate between the render and lifecycle methods.

You Know the Basics!

Boom. That’s it. You know how to build an animated declarative visualization. That’s pretty cool if you ask me.

You now understand React well enough to make technical decisions. You can look at project and decide: “Yes, this is more than a throwaway toy. Components and debuggability will help me.”

For extra fun, you also know how to use React and D3 together to build declarative animations. A feat most difficult in the olden days.

How to Building Animated Components, or How React Makes D3 Better

Many of the best data visualizations you’ve seen online use D3. It is a great library, and with the recent v4 update, it became more robust than ever.

Posted on 31-08-2016 

Comment:

To comment you must be logged in members.

Files with category

  • Mini Youtube Using ReactJS

    Mini Youtube Using ReactJS

    View: 169    Download: 3   Comment: 0

    Category: Javascript     Fields: none

    This is one the best starter for ReactJS. MiniYoutube as the name suggests is a youtube like website developed using reactJS and youtube API. This project actually let's you search , play and list youtube videos. Do check it out and start learning...

  • AngularJS and REST API

    AngularJS and REST API

    View: 315    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    This is a tutorial for those interested in a quick introduction to AngularJS and REST API. We will build the familiar Periodic Table of the Elements found in every chemistry textbook, and allow the user to select a Chemical Element by clicking on...

  • Collective Intelligence, Recommending Items Based on Similar Users' Taste

    Collective Intelligence, Recommending Items Based on Similar Users' Taste

    View: 250    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Using Collaborative Filtering to find people who share tastes, and for making automatic recommendations based on things that other people like.

  • Think Like a Bird for Better Parallel Programming

    Think Like a Bird for Better Parallel Programming

    View: 226    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Coding an application to run in parallel is hard, right? I mean, it must be hard or we’d see parallel programs everywhere. All we'd see are slick parallel apps that use every available core effortlessly. Instead multi-threaded apps are the exception...

  • Getting Started with the Bing Search APIs

    Getting Started with the Bing Search APIs

    View: 249    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Bing Search API is a set of REST interfaces that find web pages, news, images, videos, entities, related searches, spelling corrections, and more in response to queries from any programming language that can generate a web request. Applications that...

  • Brief Introduction of SocketPro High Performance and Scalable Persistent Message Queue

    Brief Introduction of SocketPro High Performance and Scalable Persistent Message Queue

    View: 569    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Continuous in-line request/result batching, real-time stream sending/processing, asynchronous data transferring and parallel computation for best performance and scalability

  • Iteration Over Java Collections with High Performance

    Iteration Over Java Collections with High Performance

    View: 219    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Java developers usually deal with Collections such as ArrayList, HashSet, Java 8 come with lambda and streaming API helps us to easily work with Collections. In most cases, we work with few thousands of items and performance isn't a concern. But in...

  • SR2JLIB - A Symbolic Regression Library for Java

    SR2JLIB - A Symbolic Regression Library for Java

    View: 215    Download: 0   Comment: 0

    Category: Javascript     Fields: Other

    Grammar-Guided Genetic Programming library featuring: multi-threading, just-in-time compilation of individuals, dynamic class loading, and JNI interfacing with C/C++ code

 
File suggestion for you
Loading...
File top downloads
Loading...
Loading...
Codetitle - library source code to share, download the file to the community
Copyright © 2018. All rights reserved. codetitle Develope by Vinagon .Ltd