block by milroc aacdb75156d51d9dbe4d

A quick React + D3 sparkline

Full Screen

Just a quick example of how I use React + d3 now a days.

index.html

<!DOCTYPE html>
<head>
  <meta charset="utf-8">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
  <script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xpa1/t39.3284-6/12512178_218562685145124_130271029_n.js"></script>
  <script src="https://fbcdn-dragon-a.akamaihd.net/hphotos-ak-xfa1/t39.3284-6/12512184_1664789273772979_614489084_n.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-core/5.8.24/browser.js"></script>
</head>
<body>
  <div id='example'></div>
  <script type="text/babel">
class Sparkline extends React.Component {
  constructor(props) {
    super(props);
    this.xScale = d3.scale.linear();
    this.yScale = d3.scale.linear();
    this.line = d3.svg.line();
    this._updateDataTransforms(props);
  }

  componentDidMount() {
    const self = this;
    d3.select(ReactDOM.findDOMNode(this.refs.svg))
      .on('mousemove', function() { self._onMouseMove(d3.mouse(this)[0]); })
      .on('mouseleave', function() { self._onMouseMove(null); });
  }

  componentWillReceiveNewProps(newProps) {
    this._updateDataTransforms(newProps);
  }

  _updateDataTransforms(props) {
    const {xAccessor, yAccessor, width, height, data} = props;

    this.xScale
      .domain([0, data.length])
      .range([0, width]);

    this.yScale
      .domain([0, 10])
      .range([height, 0]);

    this.line
      .x((d, i) => this.xScale(xAccessor(d, i)))
      .y((d, i) => this.yScale(yAccessor(d, i)));

    this.bisectByX = d3.bisector(xAccessor).left;
  }

  _onMouseMove(xPixelPos) {
    const {data, onHover} = this.props;
    if (xPixelPos === null) {
      onHover(null, null);
    }
    else {
      const xValue = this.xScale.invert(xPixelPos);
      const i = this.bisectByX(data, xValue, 1);
      onHover(data[i], i);
    }
  }

  render() {
    const {data, width, height, xAccessor, hovered} = this.props;
    const hoveredRender = (hovered)
      ? (
        <line
          x1={this.xScale(xAccessor(hovered))}
          x2={this.xScale(xAccessor(hovered))}
          y0={0}
          y1={height}
          style={{strokeWidth: '0.5px', stroke: 'steelblue'}}
        />
      )
      : null;
    return (
      <svg width={width} height={height} ref="svg">
        <path
          style={{fill: 'none', strokeWidth: '0.5px', stroke: 'steelblue'}}
          d={this.line(data)}
        />
        {hoveredRender}
      </svg>
    );
  }
}
Sparkline.defaultProps = {
  xAccessor: ({x}) => x,
  yAccessor: ({y}) => y,
};

class Example extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      hovered: null,
    };
  }
  render() {
    const {data} = this.props;
    const {hovered} = this.state;
    const value = (hovered)
      ? hovered.y
      : data.reduce((s, {y}) => s + y, 0); // total
    return (
      <div>
        <Sparkline
          data={data}
          width={100}
          height={20}
          hovered={hovered}
          onHover={(hovered, index) => this.setState({hovered})}
        />
        {value}
      </div>
    );
  }
}

let data = [3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9, 3, 6, 2, 7, 5, 2, 1, 3, 8, 9, 2, 5, 9];
// Interesting fact: d3.bisect accessors assume your not bisecting by the index.
// Duh...
data = data.map((y, x) => { return {x, y}; });

ReactDOM.render(
  <Example data={data} />,
  document.getElementById('example')
);
  </script>
</body>