clientjs/packages/dashboard-components/src/ui/RowChart.jsx
author ymh <ymh.work@gmail.com>
Mon, 04 Apr 2022 17:02:10 +0200
changeset 26 eb14941af2e7
parent 0 5f4fcbc80b37
permissions -rw-r--r--
Added tag 0.2.2 for changeset 7f7cdcd01dea

/* eslint func-names: "off" */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import * as d3 from 'd3';
import { withRouter } from 'react-router-dom';

import './RowChart.scss';


/**
 * TODO: As a first implementation we have chosen to delagate the barchart DOM drawing to D3.
 * This may not be the optimum solution. This may have to be rewrittten to use
 * React for elementt creation and D3 as the visualisation kernel.
 */
class RowChart extends Component {
  static propTypes = {
    data: PropTypes.PropTypes.arrayOf(PropTypes.object).isRequired,
    tagPrefix: PropTypes.string.isRequired,
    history: PropTypes.object.isRequired,
  }

  componentDidMount() {
    this.drawChart();
  }

  componentDidUpdate() {
    this.drawChart();
  }

  drawChart() {
    const { node } = this;
    const { data, tagPrefix } = this.props;

    const width = 800;
    const barHeight = 30;
    const height = barHeight * data.length;
    const valueMargin = 4;
    const yAxisHMargin = 10;
    const yAxisVMargin = 10;
    const barVMargin = 10;

    const y = d3.scaleBand()
      .rangeRound([0, height])
      .padding(0.1)
      .domain(data.map(d => d.tag));

    const yAxis = d3.axisLeft(y);

    d3.select(node).selectAll('svg').remove();
    const chart = d3.select(node)
      .append('svg')
      .attr('width', '100%');

    const barsContainer = chart.append('g')
      .attr('class', 'bars-container');

    const yAxisContainer = chart.append('g')
      .attr('class', 'y-axis axis')
      .call(yAxis);

    let yAxisWidth = 0;
    chart.selectAll('.y-axis text')
      .each(function () { yAxisWidth = Math.max(yAxisWidth, this.getBBox().width); });

    const x = d3.scaleLinear()
      .range([0, width - yAxisWidth - yAxisHMargin])
      .domain([0, d3.max(data, d => d.count)]);

    const xAxis = d3.axisBottom(x);

    const xAxisContainer = chart.append('g')
      .attr('class', 'y-axis axis')
      .attr('transform', `translate(${[yAxisWidth + 2 * yAxisHMargin, height + yAxisVMargin]})`)
      .call(xAxis);

    const bar = barsContainer.selectAll('.bars-container')
      .data(data)
      .enter().append('g')
      .attr('transform', (d, i) => `translate(0,${i * barHeight})`)
      .attr('class', 'graph-bar');

    bar.append('rect')
      .attr('width', d => x(d.count))
      .attr('height', barHeight - barVMargin)
      .attr('fill', d => d.color)
      .attr('stroke', d => d.color)
      .attr('transform', 'translate(0,5)')
      .on('click', (d) => {
        const { history } = this.props;
        history.push(`/annotations/${tagPrefix}${d.tag}`);
      });

    bar.append('text')
      .attr('y', barHeight / 2)
      .attr('dy', '.35em')
      .attr('dx', -valueMargin)
      .attr('text-anchor', 'end')
      .text(d => d.count)
      .attr('x', function (d) {
        const xWidth = this.getBBox().width;
        return Math.max(xWidth + valueMargin, x(d.count));
      });

    yAxisContainer.attr('transform', `translate(${yAxisWidth + yAxisHMargin},0)`);
    barsContainer.attr('transform', `translate(${yAxisWidth + 2 * yAxisHMargin},0)`);

    const totalHeight = height + xAxisContainer.node().getBBox().height + yAxisVMargin;
    const totalWidth = width + 2 * yAxisHMargin;

    chart.attr('viewBox', [0, 0, totalWidth, totalHeight].join(' '));
  }

  render() {
    return (
      <div className="row chart-container">
        <div className="col col-12" ref={(elt) => { this.node = elt; }} />
      </div>
    );
  }
}

export default withRouter(RowChart);