From 99adc9a4fca4ccefeb494b05cf7a2a3037a9f91e Mon Sep 17 00:00:00 2001 From: Andrew Michael McNutt Date: Sun, 31 May 2020 12:52:19 -0500 Subject: [PATCH 1/2] Update problematic showcase examples to use hooks --- .../force-directed-example.js | 48 ++--- .../force-directed-graph.js | 119 +++++------ .../responsive-vis/responsive-bar-chart.js | 101 +++++----- .../responsive-vis/responsive-scatterplot.js | 181 ++++++++--------- .../responsive-vis/responsive-vis-example.js | 185 +++++++++--------- packages/showcase/sunbursts/clock-example.js | 88 ++++----- 6 files changed, 323 insertions(+), 399 deletions(-) diff --git a/packages/showcase/examples/force-directed-graph/force-directed-example.js b/packages/showcase/examples/force-directed-graph/force-directed-example.js index 7048044b2..43e3d90e6 100644 --- a/packages/showcase/examples/force-directed-graph/force-directed-example.js +++ b/packages/showcase/examples/force-directed-graph/force-directed-example.js @@ -18,36 +18,30 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {useState} from 'react'; import LesMisData from './les-mis-data.json'; import './force-directed.scss'; import ForceDirectedGraph from './force-directed-graph'; -export default class ForceDirectedExample extends React.Component { - state = { - strength: Math.random() * 60 - 30 - }; - - render() { - const {strength} = this.state; - return ( -
- - -
- ); - } +const makeStrength = () => Math.random() * 60 - 30; +export default function ForceDirectedExample() { + const [strength, setStrength] = useState(makeStrength()); + return ( +
+ + +
+ ); } diff --git a/packages/showcase/examples/force-directed-graph/force-directed-graph.js b/packages/showcase/examples/force-directed-graph/force-directed-graph.js index 779357de0..a3a530310 100644 --- a/packages/showcase/examples/force-directed-graph/force-directed-graph.js +++ b/packages/showcase/examples/force-directed-graph/force-directed-graph.js @@ -18,8 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; -import PropTypes from 'prop-types'; +import React, {useState, useEffect} from 'react'; import {forceSimulation, forceLink, forceManyBody, forceCenter} from 'd3-force'; import {XYPlot, MarkSeriesCanvas, LineSeriesCanvas} from 'react-vis'; @@ -44,9 +43,9 @@ const colors = [ /** * Create the list of nodes to render. * @returns {Array} Array of nodes. - * @private */ function generateSimulation(props) { + console.log('generate sim'); const {data, height, width, maxSteps, strength} = props; if (!data) { return {nodes: [], links: []}; @@ -54,7 +53,7 @@ function generateSimulation(props) { // copy the data const nodes = data.nodes.map(d => ({...d})); const links = data.links.map(d => ({...d})); - // build the simuatation + // build the simulation const simulation = forceSimulation(nodes) .force( 'link', @@ -76,71 +75,51 @@ function generateSimulation(props) { return {nodes, links}; } -class ForceDirectedGraph extends React.Component { - static get defaultProps() { - return { - className: '', - data: {nodes: [], links: []}, - maxSteps: 50 - }; - } - - static get propTypes() { - return { - className: PropTypes.string, - data: PropTypes.object.isRequired, - height: PropTypes.number.isRequired, - width: PropTypes.number.isRequired, - steps: PropTypes.number - }; - } +export default function ForceDirectedGraph(props) { + const { + className = '', + height, + width, + animation, + data = { + nodes: [], + links: [] + }, + maxSteps = 50, + strength + } = props; + const [{nodes, links}, setData] = useState({ + nodes: [], + links: [] + }); + useEffect(() => { + setData(generateSimulation({data, height, width, maxSteps, strength})); + }, [data, height, width, maxSteps, strength]); - constructor(props) { - super(props); - this.state = { - data: generateSimulation(props) - }; - } - - UNSAFE_componentWillReceiveProps(nextProps) { - this.setState({ - data: generateSimulation(nextProps) - }); - } - - render() { - const {className, height, width, animation} = this.props; - const {data} = this.state; - const {nodes, links} = data; - return ( - - {links.map(({source, target}, index) => { - return ( - - ); - })} - - - ); - } + return ( + + {links.map(({source, target}, index) => { + return ( + + ); + })} + + + ); } - -ForceDirectedGraph.displayName = 'ForceDirectedGraph'; - -export default ForceDirectedGraph; diff --git a/packages/showcase/examples/responsive-vis/responsive-bar-chart.js b/packages/showcase/examples/responsive-vis/responsive-bar-chart.js index d9bed0823..8d5b18c19 100644 --- a/packages/showcase/examples/responsive-vis/responsive-bar-chart.js +++ b/packages/showcase/examples/responsive-vis/responsive-bar-chart.js @@ -48,59 +48,56 @@ function updateDataForArea(data, ppp) { return sample; } -export default class ResponsiveBarChart extends React.Component { - // todo build a root responsive class that has this as a class method - getFeatures() { - const {data, height, margin, width} = this.props; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); - return filterFeatures(BARCHART_FEATURES, ppp); - } +export function getFeatures(props) { + const {data, height, margin, width} = props; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); + return filterFeatures(BARCHART_FEATURES, ppp); +} - render() { - const {data, height, margin, width} = this.props; +export default function ResponsiveBarChart(props) { + const {data, height, margin, width} = props; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); - const featuresToRender = filterFeatures(BARCHART_FEATURES, ppp); - const updatedData = featuresToRender.area - ? updateDataForArea(data, ppp) - : data; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); + const featuresToRender = filterFeatures(BARCHART_FEATURES, ppp); + const updatedData = featuresToRender.area + ? updateDataForArea(data, ppp) + : data; - return ( -
- - {featuresToRender.xaxis && } - {featuresToRender.yaxis && } - {featuresToRender.bars && ( - - )} - {featuresToRender.area && ( - - )} - -
- ); - } + return ( +
+ + {featuresToRender.xaxis && } + {featuresToRender.yaxis && } + {featuresToRender.bars && ( + + )} + {featuresToRender.area && ( + + )} + +
+ ); } diff --git a/packages/showcase/examples/responsive-vis/responsive-scatterplot.js b/packages/showcase/examples/responsive-vis/responsive-scatterplot.js index ac3b0f449..39f778fa6 100644 --- a/packages/showcase/examples/responsive-vis/responsive-scatterplot.js +++ b/packages/showcase/examples/responsive-vis/responsive-scatterplot.js @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {useState, useEffect} from 'react'; import { XYPlot, MarkSeries, @@ -67,38 +67,29 @@ export const SCATTERPLOT_FEATURES = [ } ]; -export default class ResponsiveScatterplot extends React.Component { - state = { - binData: [], - hoveredPoint: false, - selectedPoints: [] - }; - - UNSAFE_componentWillReceiveProps(nextProps) { - // not the greatest - this.setState({ - binData: transformToBinData( - nextProps.data, - nextProps.width, - nextProps.height - ) - }); - } - - getFeatures() { - const {data, height, margin, width} = this.props; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; - const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); - return filterFeatures(SCATTERPLOT_FEATURES, ppp); - } +export function getFeatures(props) { + const {data, height, margin, width} = props; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; + const ppp = getPPP(innerWidth, innerHeight, data, 'HEIGHT'); + return filterFeatures(SCATTERPLOT_FEATURES, ppp); +} - _select(accessor) { +export default function ResponsiveScatterplot(props) { + const {data, height, margin, width} = props; + const [binData, setBinData] = useState([]); + const [hoveredPoint, setHoveredPoint] = useState(false); + const [selectedPoints, setSelectedPoints] = useState([]); + useEffect(() => { + setBinData(transformToBinData(data, width, height)); + }, [data, width, height]); + // const {binData, hoveredPoint, selectedPoints} = this.state; + function select(accessor) { return (value, e) => { e.event.stopPropagation(); let foundValue = false; - const selectedPoints = this.state.selectedPoints.filter(row => { + const updatedSelectedPoints = selectedPoints.filter(row => { if (accessor(row) === accessor(value)) { foundValue = true; } @@ -106,86 +97,70 @@ export default class ResponsiveScatterplot extends React.Component { }); if (!foundValue) { - selectedPoints.push(value); + updatedSelectedPoints.push(value); } - - this.setState({selectedPoints}); + setSelectedPoints(updatedSelectedPoints); }; } - render() { - const {binData, hoveredPoint, selectedPoints} = this.state; - const {data, height, margin, width} = this.props; - const innerWidth = width - margin.left - margin.right; - const innerHeight = height - margin.top - margin.bottom; + const innerWidth = width - margin.left - margin.right; + const innerHeight = height - margin.top - margin.bottom; - const ppp = getPPP(innerWidth, innerHeight, data, 'TWOD'); - const featuresToRender = filterFeatures(SCATTERPLOT_FEATURES, ppp); - const rememberVal = - featuresToRender.pointSelection || featuresToRender.tooltips; - const rememberBin = - featuresToRender.bintips || featuresToRender.binSelection; + const ppp = getPPP(innerWidth, innerHeight, data, 'TWOD'); + const featuresToRender = filterFeatures(SCATTERPLOT_FEATURES, ppp); + const rememberVal = + featuresToRender.pointSelection || featuresToRender.tooltips; + const rememberBin = featuresToRender.bintips || featuresToRender.binSelection; - const pointRadii = computeRadius(data, innerWidth, innerHeight); + const pointRadii = computeRadius(data, innerWidth, innerHeight); - const showHint = - (featuresToRender.tooltips || featuresToRender.bintips) && hoveredPoint; - return ( -
- - {featuresToRender.axes && } - {featuresToRender.axes && } - {featuresToRender.bins && ( - this.setState({hoveredPoint: value}) - : null - } - onValueMouseOut={ - rememberBin ? () => this.setState({hoveredPoint: null}) : null - } - onValueClick={ - featuresToRender.binSelection - ? this._select(d => `${d.x}-${d.y}`) - : null - } - data={manicureData(binData, hoveredPoint, selectedPoints, true)} - /> - )} - {featuresToRender.points && ( - this.setState({hoveredPoint: value}) - : null - } - onValueMouseOut={ - rememberVal ? () => this.setState({hoveredPoint: null}) : null - } - onValueClick={ - featuresToRender.pointSelection - ? this._select(d => d.label) - : null - } - data={manicureData(data, hoveredPoint, selectedPoints, false)} - /> - )} - {showHint && } - {featuresToRender.labels && ( - - )} - -
- ); - } + const showHint = + (featuresToRender.tooltips || featuresToRender.bintips) && hoveredPoint; + return ( +
+ + {featuresToRender.axes && } + {featuresToRender.axes && } + {featuresToRender.bins && ( + setHoveredPoint(value) : null + } + onValueMouseOut={rememberBin ? () => setHoveredPoint(null) : null} + onValueClick={ + featuresToRender.binSelection + ? select(d => `${d.x}-${d.y}`) + : null + } + data={manicureData(binData, hoveredPoint, selectedPoints, true)} + /> + )} + {featuresToRender.points && ( + setHoveredPoint(value) : null + } + onValueMouseOut={rememberVal ? () => setHoveredPoint(null) : null} + onValueClick={ + featuresToRender.pointSelection ? select(d => d.label) : null + } + data={manicureData(data, hoveredPoint, selectedPoints, false)} + /> + )} + {showHint && } + {featuresToRender.labels && ( + + )} + +
+ ); } diff --git a/packages/showcase/examples/responsive-vis/responsive-vis-example.js b/packages/showcase/examples/responsive-vis/responsive-vis-example.js index 72fda453f..216499c4d 100644 --- a/packages/showcase/examples/responsive-vis/responsive-vis-example.js +++ b/packages/showcase/examples/responsive-vis/responsive-vis-example.js @@ -18,11 +18,15 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {useState} from 'react'; import {createData, getPPP} from './responsive-vis-utils'; -import ResponsiveScatterplot from './responsive-scatterplot'; -import ResponsiveBarChart from './responsive-bar-chart'; +import ResponsiveScatterplot, { + getFeatures as getScatterplotFeatures +} from './responsive-scatterplot'; +import ResponsiveBarChart, { + getFeatures as getBarFeatures +} from './responsive-bar-chart'; import 'styles/examples.scss'; import './responsive-vis.scss'; @@ -30,105 +34,92 @@ import './responsive-vis.scss'; const ASPECT_RATIO = 1.2; const EXAMPLE_MARGIN = {left: 60, top: 60, bottom: 50, right: 50}; -export default class ResponsiveVisDemo extends React.Component { - state = { - dataSize: 1, - visSize: 400, - data: createData(5, true), - chartType: 'barChart' - }; +export default function ResponsiveVisDemo() { + const [chartType, setChartType] = useState('barChart'); + const [data, setData] = useState(createData(5, true)); + const [visSize, setVisSize] = useState(400); + const [dataSize, setDataSize] = useState(1); + + const ResponsiveChartType = + chartType === 'barChart' ? ResponsiveBarChart : ResponsiveScatterplot; - handleTypeClick(chartType) { - const {dataSize} = this.state; - return () => { - this.setState({ - chartType, - data: createData(~~Math.pow(10, dataSize), chartType === 'barChart') - }); - }; - } + const handleTypeClick = chartType => () => { + setChartType(chartType); + setData(createData(~~Math.pow(10, dataSize), chartType === 'barChart')); + }; - renderControls() { - const {chartType, data, dataSize, visSize} = this.state; - const width = visSize; - const height = visSize * ASPECT_RATIO; - const ppp = getPPP(width, height, data, 'TWOD'); - const featuresToRender = - (this.responsiveExample && this.responsiveExample.getFeatures()) || {}; + const width = visSize; + const height = visSize * ASPECT_RATIO; + const ppp = getPPP(width, height, data, 'TWOD'); + const featuresProps = {width, height, data, margin: EXAMPLE_MARGIN}; + const featuresToRender = (chartType === 'barChart' + ? getBarFeatures + : getScatterplotFeatures)(featuresProps); - return ( -
-
{`Points Per Pixel: ${ppp}`}
-
- {`Features: ${Object.keys(featuresToRender).join(', ')}`} -
-
-
- Scatterplot + return ( +
+
+
+
{`Points Per Pixel: ${ppp}`}
+
+ {`Features: ${Object.keys(featuresToRender).join(', ')}`}
-
- BarChart +
+
+ Scatterplot +
+
+ BarChart +
-
- {`Data Size: ${~~Math.pow(10, dataSize)}`} - { - this.setState({ - dataSize: e.target.value, - data: createData( - ~~Math.pow(10, e.target.value), - this.state.chartType === 'barChart' - ) - }); - }} - type="range" - min={1} - max={6} - step={0.1} - value={dataSize} - /> - - {`Visualization size: ${visSize}`} - this.setState({visSize: ~~e.target.value})} - type="range" - min={100} - max={1000} - value={visSize} - /> -
- ); - } + {`Data Size: ${~~Math.pow(10, dataSize)}`} + { + setDataSize(e.target.value); + setData( + createData( + ~~Math.pow(10, e.target.value), + chartType === 'barChart' + ) + ); + }} + type="range" + min={1} + max={6} + step={0.1} + value={dataSize} + /> - render() { - const {chartType, data, visSize} = this.state; - const ResponsiveChartType = - chartType === 'barChart' ? ResponsiveBarChart : ResponsiveScatterplot; - return ( -
-
{this.renderControls()}
- (this.responsiveExample = ref)} - margin={EXAMPLE_MARGIN} - height={ASPECT_RATIO * visSize} - width={visSize} - /> + {`Visualization size: ${visSize}`} + setVisSize(~~e.target.value)} + type="range" + min={100} + max={1000} + value={visSize} + /> +
- ); - } + +
+ ); } diff --git a/packages/showcase/sunbursts/clock-example.js b/packages/showcase/sunbursts/clock-example.js index 0666bcc97..3660b1794 100644 --- a/packages/showcase/sunbursts/clock-example.js +++ b/packages/showcase/sunbursts/clock-example.js @@ -18,7 +18,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import React from 'react'; +import React, {useState, useEffect} from 'react'; import {XYPlot, ArcSeries} from 'react-vis'; @@ -30,53 +30,41 @@ function getSeconds() { return Math.floor(new Date().getTime() / 1000); } -export default class ClockExample extends React.Component { - state = { - time: 0 - }; - - componentDidMount() { - this._timerId = setInterval(() => this.setState({time: getSeconds()}), 100); - } - - componentWillUnmount() { - clearInterval(this._timerId); - this.setState({timerId: false}); - } - - render() { - const {time} = this.state; - const seconds = time % 60; - const minutes = (time / 60) % 60; - const hours = (time / (60 * 24)) % 24; - return ( - d.time} - getAngle0={() => 0} - height={300} - > - - - ); - } +export default function ClockExample() { + const [time, setTime] = useState(getSeconds()); + useEffect(() => { + setInterval(() => setTime(getSeconds()), 100); + }, []); + const seconds = time % 60; + const minutes = (time / 60) % 60; + const hours = (time / (60 * 24)) % 24; + return ( + d.time} + getAngle0={() => 0} + height={300} + > + + + ); } From 3f987d1390d4ef513536f601dfe0644a054d7644 Mon Sep 17 00:00:00 2001 From: Andrew Michael McNutt Date: Thu, 4 Jun 2020 10:45:49 -0500 Subject: [PATCH 2/2] respond to requested changes --- .../examples/force-directed-graph/force-directed-example.js | 4 ++-- .../examples/force-directed-graph/force-directed-graph.js | 1 - .../examples/responsive-vis/responsive-vis-example.js | 2 +- packages/showcase/sunbursts/clock-example.js | 3 ++- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/showcase/examples/force-directed-graph/force-directed-example.js b/packages/showcase/examples/force-directed-graph/force-directed-example.js index 43e3d90e6..386a0a410 100644 --- a/packages/showcase/examples/force-directed-graph/force-directed-example.js +++ b/packages/showcase/examples/force-directed-graph/force-directed-example.js @@ -26,14 +26,14 @@ import ForceDirectedGraph from './force-directed-graph'; const makeStrength = () => Math.random() * 60 - 30; export default function ForceDirectedExample() { - const [strength, setStrength] = useState(makeStrength()); + const [strength, setStrength] = useState(makeStrength); return (
createData(5, true)); const [visSize, setVisSize] = useState(400); const [dataSize, setDataSize] = useState(1); diff --git a/packages/showcase/sunbursts/clock-example.js b/packages/showcase/sunbursts/clock-example.js index 3660b1794..41eb4cf1a 100644 --- a/packages/showcase/sunbursts/clock-example.js +++ b/packages/showcase/sunbursts/clock-example.js @@ -33,7 +33,8 @@ function getSeconds() { export default function ClockExample() { const [time, setTime] = useState(getSeconds()); useEffect(() => { - setInterval(() => setTime(getSeconds()), 100); + const handle = setInterval(() => setTime(getSeconds()), 100); + return () => clearInterval(handle); }, []); const seconds = time % 60; const minutes = (time / 60) % 60;