Reference Source

src/components/LineChart.js

import { line } from 'd3-shape';
import { select, event } from 'd3-selection';
import { scalePoint } from 'd3-scale';
import AxisChart from './AxisChart';

/**
* Create LineCharts from the supplied data, based on the JSON config.
*
* @class LineChart
* @extends {AxisChart}
* @constructor
*/
export default class LineChart extends AxisChart {

  /**
  * The local collection of lines
  *
  * @property aLines
  * @type {Array}
  */
  aLines;

  /**
  * The local collection of circles
  *
  * @property aCircles
  * @type {Array}
  */
  aCircles;

  /**
  * Constructor used to set chart type
  *
  * @method constructor
  */
  constructor(oParams = {}) {
    super(oParams);
    this.sChartType = 'line';
    this.oScaleX = scalePoint();
  }

  /**
  * Render the chart including lines, axes and labels
  *
  * @method renderChart
  */
  renderChart(bReset = false) {
    super.renderChart();
    const { aValues } = this.jConfig;
    const { oScaleX, oScaleY } = this;

    this.aLines = this.aLines || [];
    this.aCircles = this.aCircles || [];

    // Iterate through config value keys
    aValues.forEach((oValues, i) => {
      const { oColor } = oValues;

      // Reset lines data and clear graph
      if (bReset && this.aLines[i]) {
        this.aLines[i] = this.d3ChartGroup.selectAll(`path.lines-${i}`).data([]);
        this.aLines[i].exit().remove();
        this.aLines[i] = undefined;
      }

      if (!this.aLines[i]) {
        // define the line
        this.aLines[i] = line();

        // Add the valueline path.
        this.d3ChartGroup.append('path')
            .data([this.aData])
            .attr('class', `line lines-${i}`)
            .attr('stroke', oColor);
      }

      // Update lines
      this.aLines[i]
        .x(d => oScaleX(d.sLabel) + (oScaleX.bandwidth() / 2))
        .y(d => oScaleY(d.aValues[i]));
      this.d3ChartGroup.selectAll(`path.lines-${i}`)
        .data([this.aData])
        .attr('d', this.aLines[i]);

      // Reset circles data and clear graph
      if (bReset && this.aCircles[i]) {
        this.aCircles[i] = this.d3ChartGroup.selectAll(`circle.circles-${i}`).data([]);
        this.aCircles[i].exit().remove();
        this.aCircles[i] = undefined;
      }

      // Add circles for each value
      if (!this.aCircles[i]) {
        // Bind circles data
        this.aCircles[i] = this.d3ChartGroup.selectAll(`circle.circles-${i}`).data(this.aData);
        // Add new circle elements and set base attributes
        this.aCircles[i]
          .enter()
          .append('circle')
          .on('mousemove', (d) => {
            this.oTooltip.ping([d.sLabel, oValues.sName, d.aValues[i]]);
          })
          .on('mouseover', (d) => {
            d.oColor = this.jConfig.aValues[i].oColor;
            select(event.target).attr('fill', d.oColor.darker(1));
          })
          .on('mousedown', (d) => {
            if (this.jConfig.fnClickCallback) {
              this.jConfig.fnClickCallback({
                oEvent: event,
                jData: d
              });
            }
          })
          .on('mouseout', (d) => {
            this.oTooltip.hide();
            select(event.target).attr('fill', d.oColor);
          })
          .attr('class', `circles circles-${i}`)
          .attr('fill', oColor)
          .attr('r', 5);
      }

      // Update circles
      this.d3ChartGroup.selectAll(`circle.circles-${i}`)
        .data(this.aData)
        .attr('cy', d => oScaleY(d.aValues[i]))
        .attr('cx', d => oScaleX(d.sLabel) + (oScaleX.bandwidth() / 2));
    });
  }

}

module.exports = LineChart;