govau/performance-dashboard

View on GitHub
client/src/_dashboard-legacy/d3-charts-dto/lib/javascripts/Charts/Legend.js

Summary

Maintainability
B
5 hrs
Test Coverage
let $ = window.jQuery || {};
import d3 from 'd3';
import formatData from './../Helpers/formatData';
import getDate from './../Helpers/getDate';
import defined from './../Helpers/defined';
import _ from 'lodash';

const iconSize = 12;

/** Class representing a legend. */
class Legend {
  /**
   * Legend class constructor
   * @param  {object} options Object with the following properties:
   * @param {object} [options.chart] The chart object to append this legend to
   */
  constructor(options) {
    this.chart = options.chart;
    this.widget_id = options.widget_id;
    this.type = this.chart.type;
    this.displayRoundedData = this.chart.displayRoundedData;

    // todo - always supply non-transformed data as the legend data
    // hack: if legendData is provided, use that instead of the data
    // which has been transformed for display in chart
    if (this.legendData) {
      this.data =
        this.chart.type === 'bar'
          ? _.reverse(this.chart.legendData)
          : this.chart.transformedData;
    } else {
      this.data =
        this.chart.type === 'bar'
          ? _.reverse(this.chart.transformedData)
          : this.chart.transformedData;
    }

    // insert as last element *after* .chart
    this.container = this.chart.element
      .insert('div', '.chart + *')
      .attr('class', 'legend');
    this.init();
  }

  /**
   * Initialise the legend
   * @return {undefined}
   */
  init() {
    this.render();
    this.updateLegendIcon();
  }

  /**
   * render legend, create a div with tables to display legend and data
   * @return {undefined}
   */
  render() {
    this.date = this.container
      .append('div')
      .attr('class', 'date')
      .attr('style', 'display:none')
      .datum(this.data[0]);

    let row = this.container
      .append('table')
      .attr('style', this.data.length > 7 ? 'font-size: 0.8rem;' : '') // stodo - hack fix: scale the legend data down for nationalmap.gov.au
      .selectAll('tr')
      .data(this.data)
      .enter()
      .append('tr');

    this.th = row.append('th');

    let svg = this.th
      .append('svg')
      .attr('width', iconSize)
      .attr('height', iconSize)
      .append('g')
      .attr('class', 'legend--icon');

    svg
      .append('rect')
      .attr('x', 0)
      .attr('y', 0)
      .attr('width', iconSize)
      .attr('height', iconSize)
      .attr('fill', d => (defined(d[0]) ? d[0].color : d.data.color));

    svg
      .append('line')
      .attr('x1', 0)
      .attr('x2', iconSize)
      .attr('y1', iconSize / 2)
      .attr('y2', iconSize / 2)
      .attr('stroke-linecap', 'butt')
      .attr('stroke', d => (defined(d[0]) ? d[0].color : d.data.color))
      .attr('stroke-dasharray', d => (defined(d[0]) ? d[0].altLineStyle : 0));

    this.th
      .append('span')
      .attr('class', 'legend--data-name')
      .text(this.getDataName.bind(this));

    this.td = row.append('td');

    if (this.type === 'pie') {
      this.td.text(d =>
        formatData(
          d.data.y,
          this.chart.prefix,
          this.chart.suffix,
          this.displayRoundedData,
        ),
      );
    }
  }

  /**
   * Get the name of the data
   * @param  {number} d The current data point
   * @return {undefined}
   */
  getDataName(d) {
    if (this.type !== 'pie') {
      return `${d[0].name}`;
    }
    return `${d.data.name}`;
  }

  /**
   * Update the icon for the legend
   * The reason this is a seperate function is the legends have different icons when in normal mode and high contrast mode
   * @return {undefined}
   */
  updateLegendIcon() {
    let that = this;
    let squares = this.th.selectAll('.legend--icon rect');
    let lines = this.th.selectAll('.legend--icon line');

    squares.each(function() {
      d3.select(this)
        .attr('fill', d =>
          defined(d[0])
            ? that.chart.isHighContrastMode
              ? d[0].altColor
              : d[0].color
            : that.chart.isHighContrastMode
            ? d.data.altColor
            : d.data.color,
        )
        .attr(
          'visibility',
          that.chart.type === 'line' && that.chart.isHighContrastMode === true
            ? 'hidden'
            : 'visible',
        )
        .attr('rx', that.chart.isHighContrastMode ? 2 : iconSize / 2)
        .attr('ry', that.chart.isHighContrastMode ? 2 : iconSize / 2);
    });

    lines.each(function() {
      d3.select(this).attr(
        'visibility',
        that.chart.type === 'line' && that.chart.isHighContrastMode === true
          ? 'visible'
          : 'hidden',
      );
    });
  }

  /**
   * When user hover on the chart, the  data displayed in the legend should be updated
   * @param  {number} i the index of data being hovered
   * @return {undefined}
   */
  hover(i) {
    this.date.text(d => {
      const dateString = getDate().long(d[i].x);
      $(window).trigger(`update:legend_label:${this.widget_id}`, dateString);
      return dateString;
    });

    this.td.text(d => {
      return formatData(
        d[i].y,
        this.chart.prefix,
        this.chart.suffix,
        this.displayRoundedData,
      );
    });
  }
}

export default Legend;
module.exports = Legend;