React &

Data Visualization

@fraserxu

Wiredcraft

This talk is *not* about

  • React tutorial
  • Data visualisation tutorial
  • D3 introduction

React

A JavaScript library for building user interfaces

Process of Data Visualization

  • Analysis data
  • Data aggregation
  • Visualize data(rendering)

Raw Data

Data aggregation

d3.csv('data/shanghai_air.csv', function(data) {
  d3.select('#area')
    .on('change', function() {
      redraw(data, this.value)
    })
    .selectAll('option')
    .data(data
      .map(function(d) { return d['监测点'] })
      .filter(function(e, i, arr) {
        return arr.lastIndexOf(e) === i
    }))
    .enter().append('option')
    .attr('value', function(d) { return d })
    .attr('selected', function(d) { if (d === '') return true})
    .text(function(d) {
      if (d === '') {
        return '全市'
      } else {
        return d
      }
    })

  redraw(data)
})

Visualize data(rendering)

How?

What?

D3.js is a JavaScript library for manipulating documents based on data. D3 helps you bring data to life using HTML, SVG, and CSS. D3’s emphasis on web standards gives you the full capabilities of modern browsers without tying yourself to a proprietary framework, combining powerful visualization components and a data-driven approach to DOM manipulation.

Prepare HTML

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>D3 Demo</title>
  <script type="text/javascript" src="../node_modules/d3/d3.min.js"></script>
</head>
<body>
  <script>  
  </script>
</body>
</html>

Append SVG

var svg = d3.select("body").append("svg")
  .attr("width", width + margin.left + margin.right)
  .attr("height", height + margin.top + margin.bottom)
  .append("g")
  .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

Set Scale

var x = d3.scale.ordinal()
    .rangeRoundBands([0, width], .1);

var y = d3.scale.linear()
    .range([height, 0]);

var xAxis = d3.svg.axis()
    .scale(x)
    .orient("bottom");

var yAxis = d3.svg.axis()
    .scale(y)
    .orient("left")
    .ticks(10, "%");

Load Data

d3.tsv("../data/letter.tsv", type, function(error, data) {
  // do something here with data

  // set domain
  x.domain(data.map(function(d) { return d.letter; }));
  y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
});

Append Bar

svg.selectAll(".bar")
    .data(data)
  .enter().append("rect")
    .attr("class", "bar")
    .attr("x", function(d) { return x(d.letter); })
    .attr("width", x.rangeBand())
    .attr("y", function(d) { return y(d.frequency); })
    .attr("height", function(d) { return height - y(d.frequency); });

Append Axis

svg.append("g")
    .attr("class", "x axis")
    .attr("transform", "translate(0," + height + ")")
    .call(xAxis);

svg.append("g")
    .attr("class", "y axis")
    .call(yAxis)
  .append("text")
    .attr("transform", "rotate(-90)")
    .attr("y", 6)
    .attr("dy", ".71em")
    .style("text-anchor", "end")
    .text("Frequency");

How?

Three simple guidelines

  • One source of truth
  • Stateless all the things
  • Don't make to many assumptions

A React App

'use strict';

import React from 'react';

class App extends React.Component {

  displayName: 'App'

  constructor() {
    super();

    this.state = {
      name: 'Fraser Xu'
    };
  }

  render() {
    let { name } = this.state;
    return (
      <h1>Hi {name}</h1>
    )
  }

}

export default App;

What we want

'use strict';

import React from 'react';
import ChartComponent form './ChartComponent';

class App extends React.Component {

  displayName: 'App'

  constructor() {
    super();

    this.state = {
      data: [1, 2, 3, 4, 5]
    };
  }

  render() {
    let { data } = this.state;
    return (
      <ChartComponent data={data} />
    )
  }

}

export default App;

With D3

  • Low level API
  • Flexible code structure
  • Easy customisation
  • Lots of code

 

With Chart Library

  • High level API
  • Ready for use style
  • Hard to customisation

A chart component With D3

'use strict';

import React from 'react';
import d3 from 'd3';

class Barchart extends React.Component {
  displayName: 'Barchart'
  constructor(props) {
    super(props);
  }
  componentDidMount() {
    this.renderBarChart();
  }
  renderBarChart() {
    // render chart
  }
  render() {
    return (
      <svg />
    )
  }
}

Barchart.defaultProps = {
  width: 800,
  height: 200,
  fillColor: '#d70206'
}

export default Barchart;

Use D3 build DOM

  • Powerful D3
  • Lost control of DOM by React
  • No Virtual DOM

Use SVG build DOM

  • Virtual DOM
  • Lost built-in D3 update and animation

The D3 way redenering logic

let { width, height, fillColor, data } = this.props;

let values = data.slice();

let yScale = d3.scale.linear()
  .range([height, 0]);

yScale.domain([0, Math.max.apply(null, values)]);

let svg = React.findDOMNode(this);

let chart = d3.select(svg)
  .attr('width', this.props.width)
  .attr('height', this.props.height + 1);

let barWidth = width / values.length;

let bar = chart.selectAll('g')
    .data(values)
  .enter().append('g')
    .attr('transform', (d, i) => `translate(${i * barWidth}, 0)`);

bar.append('rect')
  .attr('y', (d) => yScale(d))
  .attr('height', (d) => height - yScale(d))
  .attr('width', (d) => barWidth - 1)
  .attr('fill', fillColor);

How to use

'use strict';

import React from 'react';

import Barchart from './Barchart';

class App extends React.Component {

  displayName: 'App'

  render() {
    let data = {
      series: [
        [1, 2, 4, 8, 6]
      ]
    };

    return (
      <div>
        <Barchart data={data.series[0]} />
      </div>
    )
  }

}

React.render(<App />, document.body);

The React(SVG) way redenering logic

'use strict';

import React from 'react';

class Chart extends React.Component {

  displayName: 'Chart'

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <svg width={this.props.width} height={this.props.height}>{this.props.children}</svg>
    )
  }
}

export default Chart;
'use strict';

import React from 'react';

class Bar extends React.Component {

  displayName: 'Bar'

  constructor(props) {
    super(props);
  }

  render() {
    return (
      <rect fill={this.props.color}
        width={this.props.width} height={this.props.height}
        x={this.props.offset} y={this.props.availableHeight - this.props.height} />
    )
  }

}

Bar.defaultProps = {
  width: 800,
  height: 200,
  fillColor: '#d70206'
}

export default Bar;
'use strict';
import React from 'react';
import d3 from 'd3';
import Bar from './Bar';
class DataSeries extends React.Component {
  displayName: 'DataSeries'
  constructor(props) {
    super(props);
  }
  render() {
    let { data, width, height, color } = this.props;
    let yScale = d3.scale.linear().domain([0, d3.max(data)])
      .range([0, height]);
    let xScale = d3.scale.ordinal().domain(d3.range(data.length))
      .rangeRoundBands([0, width], 0.05);
    let bars = this.props.data.map((point, i) => {
      return (
        <Bar 
            height={yScale(point)}
            width={xScale.rangeBand()}
            offset={xScale(i)}
            availableHeight={height}
            color={color}
            key={i} 
        />
      )
    });
    return (
      <g>{bars}</g>
    );
  }
}
export default DataSeries;
'use strict';

import React from 'react';

import Chart from './Chart';
import DataSeries from './DataSeries';

class App extends React.Component {

  displayName: 'App'

  render() {
    return (
      <Chart width={this.props.width} heigh={this.props.heigh}>
        <DataSeries
          data={[ 30, 10, 5, 8, 15, 10 ]}
          width={this.props.width}
          height={this.props.height}
          color="cornflowerblue"
        />
      </Chart>
    )
  }

}

React.render(<App width={600} height={800} />, document.body);

With chart Library(react-chartist)

'use strict';

import React from 'react';
import ChartistGraph from 'react-chartist';
class App extends React.Component {
  displayName: 'App'
  render() {
    let data = {
      labels: ['W1', 'W2', 'W3', 'W4', 'W5'],
      series: [
        [1, 2, 4, 8, 6]
      ]
    };
    let options = {
      high: 10,
      axisX: {
        labelInterpolationFnc: function(value, index) {
          return index % 2 === 0 ? value : null;
        }
      }
    };
    let type = 'Bar'
    return (
      <div>
        <ChartistGraph data={data} options={options} type={type} />
      </div>
    )
  }
}
React.render(<App />, document.body);

Disclaimer

Conclusion

Choose the way you like!

Further reading

QA?

deck

By fraser xu

deck

  • 2,453
Loading comments...

More from fraser xu