|
1 | | -import React, { Component } from 'react'; |
2 | | -import PropTypes from 'prop-types'; |
| 1 | +import { entityAttributesProperty } from '@codaco/shared-consts'; |
3 | 2 | import { findIndex } from 'lodash'; |
4 | | -import getAbsoluteBoundingRect from '../../utils/getAbsoluteBoundingRect'; |
5 | | -import { ConvexHull } from './ConvexHull'; |
| 3 | +import PropTypes from 'prop-types'; |
| 4 | +import React, { |
| 5 | + useEffect, useMemo, useRef, useState, |
| 6 | +} from 'react'; |
| 7 | +import { useSelector } from 'react-redux'; |
| 8 | +import { getCategoricalOptions } from '../../selectors/network'; |
| 9 | +import ConvexHull from './ConvexHull'; |
| 10 | +import { getStageForCurrentSession } from '../../selectors/session'; |
| 11 | + |
| 12 | +const useResizeObserver = (callback, elementRef, options) => { |
| 13 | + useEffect(() => { |
| 14 | + if (!elementRef.current) return; |
| 15 | + |
| 16 | + const resizeObserver = new ResizeObserver(callback); |
| 17 | + resizeObserver.observe(elementRef.current, options); |
| 18 | + |
| 19 | + // eslint-disable-next-line consistent-return |
| 20 | + return () => { |
| 21 | + resizeObserver.disconnect(); |
| 22 | + }; |
| 23 | + }, [callback, elementRef, options]); |
| 24 | +}; |
6 | 25 |
|
7 | 26 | const getColor = (group, options) => { |
8 | 27 | const colorIndex = findIndex(options, ['value', group]) + 1 || 1; |
9 | 28 | const color = `cat-color-seq-${colorIndex}`; |
10 | 29 | return color; |
11 | 30 | }; |
12 | 31 |
|
13 | | -class ConvexHulls extends Component { |
14 | | - constructor() { |
15 | | - super(); |
| 32 | +const getNodesByGroup = (nodes, categoricalVariable) => { |
| 33 | + const groupedList = {}; |
16 | 34 |
|
17 | | - this.hullComponent = React.createRef(); |
18 | | - this.state = { |
19 | | - size: { width: 0, height: 0 }, |
20 | | - }; |
21 | | - } |
22 | | - |
23 | | - componentDidMount() { |
24 | | - this.updateSize(); |
25 | | - } |
26 | | - |
27 | | - shouldComponentUpdate() { |
28 | | - this.updateSize(); |
29 | | - return true; |
30 | | - } |
31 | | - |
32 | | - updateSize = () => { |
33 | | - const { size } = this.state; |
34 | | - if (this.hullComponent.current && ( |
35 | | - size.width !== getAbsoluteBoundingRect(this.hullComponent.current).width |
36 | | - || size.height !== getAbsoluteBoundingRect(this.hullComponent.current).height)) { |
37 | | - this.setState({ |
38 | | - size: { |
39 | | - width: getAbsoluteBoundingRect(this.hullComponent.current).width, |
40 | | - height: getAbsoluteBoundingRect(this.hullComponent.current).height, |
41 | | - }, |
42 | | - }); |
| 35 | + nodes.forEach((node) => { |
| 36 | + const categoricalValues = node[entityAttributesProperty][categoricalVariable]; |
| 37 | + |
| 38 | + // Filter out nodes with no value for this variable. |
| 39 | + if (!categoricalValues) { |
| 40 | + return; |
43 | 41 | } |
44 | | - } |
45 | | - |
46 | | - render() { |
47 | | - const { |
48 | | - nodesByGroup, |
49 | | - layoutVariable, |
50 | | - categoricalOptions, |
51 | | - } = this.props; |
52 | | - const { |
53 | | - size, |
54 | | - } = this.state; |
55 | | - |
56 | | - return ( |
57 | | - <div style={{ width: '100%', height: '100%' }} ref={this.hullComponent}> |
58 | | - {Object.values(nodesByGroup).map(({ group, nodes }, index) => { |
59 | | - const color = getColor(group, categoricalOptions); |
60 | | - return ( |
61 | | - <ConvexHull |
62 | | - windowDimensions={size} |
63 | | - color={color} |
64 | | - nodePoints={nodes} |
65 | | - key={index} |
66 | | - layoutVariable={layoutVariable} |
67 | | - /> |
68 | | - ); |
69 | | - })} |
70 | | - </div> |
71 | | - ); |
72 | | - } |
73 | | -} |
74 | 42 |
|
75 | | -ConvexHulls.propTypes = { |
76 | | - layoutVariable: PropTypes.string.isRequired, |
77 | | - nodesByGroup: PropTypes.object.isRequired, |
78 | | - categoricalOptions: PropTypes.array, |
| 43 | + categoricalValues.forEach((categoricalValue) => { |
| 44 | + if (groupedList[categoricalValue]) { |
| 45 | + groupedList[categoricalValue].nodes.push(node); |
| 46 | + } else { |
| 47 | + groupedList[categoricalValue] = { group: categoricalValue, nodes: [] }; |
| 48 | + groupedList[categoricalValue].nodes.push(node); |
| 49 | + } |
| 50 | + }); |
| 51 | + }); |
| 52 | + |
| 53 | + return groupedList; |
| 54 | +}; |
| 55 | + |
| 56 | +const ConvexHulls = ({ nodes, groupVariable, layoutVariable }) => { |
| 57 | + const hullRef = useRef(null); |
| 58 | + const [size, setSize] = useState({ width: 0, height: 0 }); |
| 59 | + |
| 60 | + const nodesByGroup = useMemo( |
| 61 | + () => getNodesByGroup(nodes, groupVariable), |
| 62 | + [nodes, groupVariable], |
| 63 | + ); |
| 64 | + const categoricalOptions = useSelector((state) => getCategoricalOptions(state, { |
| 65 | + stage: getStageForCurrentSession(state), |
| 66 | + variableId: groupVariable, |
| 67 | + })); |
| 68 | + |
| 69 | + useResizeObserver(hullRef, () => { |
| 70 | + setSize({ |
| 71 | + width: hullRef.contentRect.width, |
| 72 | + height: hullRef.contentRect.height, |
| 73 | + }); |
| 74 | + }); |
| 75 | + |
| 76 | + useEffect(() => { |
| 77 | + setSize({ |
| 78 | + width: hullRef.current.clientWidth, |
| 79 | + height: hullRef.current.clientHeight, |
| 80 | + }); |
| 81 | + }, [hullRef]); |
| 82 | + |
| 83 | + return ( |
| 84 | + <div style={{ width: '100%', height: '100%' }} ref={hullRef}> |
| 85 | + {Object.values(nodesByGroup).map(({ group, nodes: groupNodes }, index) => { |
| 86 | + const color = getColor(group, categoricalOptions); |
| 87 | + return ( |
| 88 | + <ConvexHull |
| 89 | + windowDimensions={size} |
| 90 | + color={color} |
| 91 | + nodePoints={groupNodes} |
| 92 | + key={index} |
| 93 | + layoutVariable={layoutVariable} |
| 94 | + /> |
| 95 | + ); |
| 96 | + })} |
| 97 | + </div> |
| 98 | + ); |
79 | 99 | }; |
80 | 100 |
|
81 | | -ConvexHulls.defaultProps = { |
82 | | - categoricalOptions: [], |
| 101 | +ConvexHulls.propTypes = { |
| 102 | + layoutVariable: PropTypes.string.isRequired, |
| 103 | + nodes: PropTypes.array.isRequired, |
| 104 | + groupVariable: PropTypes.string.isRequired, |
83 | 105 | }; |
84 | 106 |
|
85 | 107 | export default ConvexHulls; |
0 commit comments