|
1 /* eslint func-names: "off" */ |
|
2 import React, { Component } from 'react'; |
|
3 import PropTypes from 'prop-types'; |
|
4 import * as d3 from 'd3'; |
|
5 import { withRouter } from 'react-router-dom'; |
|
6 |
|
7 import './RowChart.scss'; |
|
8 |
|
9 |
|
10 /** |
|
11 * TODO: As a first implementation we have chosen to delagate the barchart DOM drawing to D3. |
|
12 * This may not be the optimum solution. This may have to be rewrittten to use |
|
13 * React for elementt creation and D3 as the visualisation kernel. |
|
14 */ |
|
15 class RowChart extends Component { |
|
16 static propTypes = { |
|
17 data: PropTypes.PropTypes.arrayOf(PropTypes.object).isRequired, |
|
18 tagPrefix: PropTypes.string.isRequired, |
|
19 history: PropTypes.object.isRequired, |
|
20 } |
|
21 |
|
22 componentDidMount() { |
|
23 this.drawChart(); |
|
24 } |
|
25 |
|
26 componentDidUpdate() { |
|
27 this.drawChart(); |
|
28 } |
|
29 |
|
30 drawChart() { |
|
31 const { node } = this; |
|
32 const { data, tagPrefix } = this.props; |
|
33 |
|
34 const width = 800; |
|
35 const barHeight = 30; |
|
36 const height = barHeight * data.length; |
|
37 const valueMargin = 4; |
|
38 const yAxisHMargin = 10; |
|
39 const yAxisVMargin = 10; |
|
40 const barVMargin = 10; |
|
41 |
|
42 const y = d3.scaleBand() |
|
43 .rangeRound([0, height]) |
|
44 .padding(0.1) |
|
45 .domain(data.map(d => d.tag)); |
|
46 |
|
47 const yAxis = d3.axisLeft(y); |
|
48 |
|
49 d3.select(node).selectAll('svg').remove(); |
|
50 const chart = d3.select(node) |
|
51 .append('svg') |
|
52 .attr('width', '100%'); |
|
53 |
|
54 const barsContainer = chart.append('g') |
|
55 .attr('class', 'bars-container'); |
|
56 |
|
57 const yAxisContainer = chart.append('g') |
|
58 .attr('class', 'y-axis axis') |
|
59 .call(yAxis); |
|
60 |
|
61 let yAxisWidth = 0; |
|
62 chart.selectAll('.y-axis text') |
|
63 .each(function () { yAxisWidth = Math.max(yAxisWidth, this.getBBox().width); }); |
|
64 |
|
65 const x = d3.scaleLinear() |
|
66 .range([0, width - yAxisWidth - yAxisHMargin]) |
|
67 .domain([0, d3.max(data, d => d.count)]); |
|
68 |
|
69 const xAxis = d3.axisBottom(x); |
|
70 |
|
71 const xAxisContainer = chart.append('g') |
|
72 .attr('class', 'y-axis axis') |
|
73 .attr('transform', `translate(${[yAxisWidth + 2 * yAxisHMargin, height + yAxisVMargin]})`) |
|
74 .call(xAxis); |
|
75 |
|
76 const bar = barsContainer.selectAll('.bars-container') |
|
77 .data(data) |
|
78 .enter().append('g') |
|
79 .attr('transform', (d, i) => `translate(0,${i * barHeight})`) |
|
80 .attr('class', 'graph-bar'); |
|
81 |
|
82 bar.append('rect') |
|
83 .attr('width', d => x(d.count)) |
|
84 .attr('height', barHeight - barVMargin) |
|
85 .attr('fill', d => d.color) |
|
86 .attr('stroke', d => d.color) |
|
87 .attr('transform', 'translate(0,5)') |
|
88 .on('click', (d) => { |
|
89 const { history } = this.props; |
|
90 history.push(`/annotations/${tagPrefix}${d.tag}`); |
|
91 }); |
|
92 |
|
93 bar.append('text') |
|
94 .attr('y', barHeight / 2) |
|
95 .attr('dy', '.35em') |
|
96 .attr('dx', -valueMargin) |
|
97 .attr('text-anchor', 'end') |
|
98 .text(d => d.count) |
|
99 .attr('x', function (d) { |
|
100 const xWidth = this.getBBox().width; |
|
101 return Math.max(xWidth + valueMargin, x(d.count)); |
|
102 }); |
|
103 |
|
104 yAxisContainer.attr('transform', `translate(${yAxisWidth + yAxisHMargin},0)`); |
|
105 barsContainer.attr('transform', `translate(${yAxisWidth + 2 * yAxisHMargin},0)`); |
|
106 |
|
107 const totalHeight = height + xAxisContainer.node().getBBox().height + yAxisVMargin; |
|
108 const totalWidth = width + 2 * yAxisHMargin; |
|
109 |
|
110 chart.attr('viewBox', [0, 0, totalWidth, totalHeight].join(' ')); |
|
111 } |
|
112 |
|
113 render() { |
|
114 return ( |
|
115 <div className="row chart-container"> |
|
116 <div className="col col-12" ref={(elt) => { this.node = elt; }} /> |
|
117 </div> |
|
118 ); |
|
119 } |
|
120 } |
|
121 |
|
122 export default withRouter(RowChart); |