|
1 | | -// Display a d3 Sunburst diagram |
2 | 1 | import PropTypes from 'prop-types'; |
3 | | -import React from 'react'; |
| 2 | +import React, { useEffect, useRef, useState } from 'react'; |
4 | 3 | import round from 'lodash/round'; |
5 | 4 | import { select } from 'd3-selection'; |
6 | 5 | import { hierarchy, partition } from 'd3-hierarchy'; |
7 | 6 | import { arc } from 'd3-shape'; |
8 | 7 | import { getSize } from './sunburst-container-utils.js'; |
9 | 8 |
|
10 | | -function getDistance (capture) { |
| 9 | +function getDistance(capture) { |
11 | 10 | if (capture.similarity !== -1) { |
12 | 11 | return `${capture.name} - Differences: ${round(capture.similarity, 2)}%`; |
13 | 12 | } |
14 | 13 | } |
15 | 14 |
|
16 | | -export default class D3Sunburst extends React.Component { |
17 | | - static propTypes = { |
18 | | - simhashData: PropTypes.object, |
19 | | - url: PropTypes.string, |
20 | | - urlPrefix: PropTypes.string |
21 | | - }; |
| 15 | +export default function D3Sunburst({ simhashData, urlPrefix, url }) { |
| 16 | + const chartRef = useRef(null); |
| 17 | + const [hint, setHint] = useState(''); |
22 | 18 |
|
23 | | - state = { |
24 | | - hint: '' |
25 | | - }; |
26 | | - |
27 | | - constructor (props) { |
28 | | - super(props); |
29 | | - this.chartRef = React.createRef(); |
30 | | - } |
31 | | - |
32 | | - componentDidMount () { |
33 | | - const { simhashData, urlPrefix, url } = this.props; |
| 19 | + useEffect(() => { |
34 | 20 | const width = getSize(); |
35 | 21 | const height = getSize(); |
36 | 22 | const radius = Math.min(width, height) / 2; |
37 | 23 |
|
38 | | - // Create primary <g> element |
39 | | - const g = select(this.chartRef.current) |
| 24 | + const svg = select(chartRef.current) |
40 | 25 | .attr('width', width) |
41 | | - .attr('height', height) |
42 | | - .append('g') |
43 | | - .attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')'); |
| 26 | + .attr('height', height); |
| 27 | + |
| 28 | + const g = svg.append('g') |
| 29 | + .attr('transform', `translate(${width / 2},${height / 2})`); |
44 | 30 |
|
45 | | - // Data strucure |
46 | 31 | const structure = partition().size([2 * Math.PI, radius]); |
47 | 32 |
|
48 | | - // Find data root |
49 | | - const root = hierarchy(simhashData).sum(function (d) { return 5; }); |
50 | | - // Size arcs |
| 33 | + const root = hierarchy(simhashData).sum(() => 5); |
51 | 34 | structure(root); |
52 | | - const myarc = arc() |
53 | | - .startAngle(function (d) { return d.x0; }) |
54 | | - .endAngle(function (d) { return d.x1; }) |
55 | | - .innerRadius(function (d) { return d.y0; }) |
56 | | - .outerRadius(function (d) { return d.y1; }); |
57 | | - const component = this; |
58 | 35 |
|
59 | | - // Put it all together |
| 36 | + const myArc = arc() |
| 37 | + .startAngle(d => d.x0) |
| 38 | + .endAngle(d => d.x1) |
| 39 | + .innerRadius(d => d.y0) |
| 40 | + .outerRadius(d => d.y1); |
| 41 | + |
60 | 42 | g.selectAll('path') |
61 | 43 | .data(root.descendants()) |
62 | 44 | .enter().append('path') |
63 | | - .attr('display', function (d) { return d.depth ? null : 'none'; }) |
64 | | - .attr('d', myarc) |
| 45 | + .attr('display', d => (d.depth ? null : 'none')) |
| 46 | + .attr('d', myArc) |
65 | 47 | .style('stroke', '#fff') |
66 | | - .style('fill', function (d) { return d.data.clr; }) |
| 48 | + .style('fill', d => d.data.clr) |
67 | 49 | .on('mouseover', function (e, d) { |
68 | 50 | select(e.currentTarget).style('cursor', 'pointer').style('stroke', 'black'); |
69 | | - component.setState({ hint: getDistance(d.data) }); |
| 51 | + setHint(getDistance(d.data)); |
70 | 52 | }) |
71 | | - .on('mouseleave', function (e, d) { |
| 53 | + .on('mouseleave', function (e) { |
72 | 54 | select(e.currentTarget).style('cursor', 'default').style('stroke', ''); |
73 | | - component.setState({ hint: '' }); |
| 55 | + setHint(''); |
74 | 56 | }) |
75 | 57 | .on('click', function (e, d) { |
76 | 58 | if (d.data.timestamp !== simhashData.timestamp) { |
77 | | - const captureUrl = urlPrefix + d.data.timestamp + '/' + simhashData.timestamp + '/' + url; |
| 59 | + const captureUrl = `${urlPrefix}${d.data.timestamp}/${simhashData.timestamp}/${url}`; |
78 | 60 | window.open(captureUrl, '_blank'); |
79 | 61 | } |
80 | 62 | }); |
81 | | - } |
| 63 | + }, [simhashData, urlPrefix, url]); |
82 | 64 |
|
83 | | - render () { |
84 | | - return ( |
85 | | - <> |
86 | | - <p className="text-center" style={{ marginTop: '10px' }}>{ this.state.hint } </p> |
87 | | - <div className="text-center"> |
88 | | - <svg ref={this.chartRef}></svg> |
89 | | - </div> |
90 | | - </> |
91 | | - ); |
92 | | - } |
| 65 | + return ( |
| 66 | + <> |
| 67 | + <p className="text-center" style={{ marginTop: '10px' }}>{hint} </p> |
| 68 | + <div className="text-center"> |
| 69 | + <svg ref={chartRef}></svg> |
| 70 | + </div> |
| 71 | + </> |
| 72 | + ); |
93 | 73 | } |
| 74 | + |
| 75 | +D3Sunburst.propTypes = { |
| 76 | + simhashData: PropTypes.object, |
| 77 | + url: PropTypes.string, |
| 78 | + urlPrefix: PropTypes.string |
| 79 | +}; |
0 commit comments