From 44e3a9dda1224eb05f56495ab96c098eae1768c0 Mon Sep 17 00:00:00 2001 From: Charlie Brown Date: Tue, 14 May 2024 13:13:44 -0500 Subject: [PATCH 1/2] Modernize react example code in docs Convert demos, example code, and gallery code to use Functional React Components instead of Class Components to modernize the examples --- docs/src/content/common-props/common-props.md | 253 +++++---- docs/src/content/docs/victory-scatter.md | 41 +- .../src/content/docs/victory-shared-events.md | 148 +++--- .../src/content/gallery/alternative-events.md | 81 ++- .../animating-circular-progress-bar.md | 105 ++-- docs/src/content/gallery/area-hover.md | 100 ++-- docs/src/content/gallery/brush-zoom.md | 137 +++-- docs/src/content/gallery/column-chart.md | 50 +- .../content/gallery/custom-tooltip-labels.md | 70 ++- docs/src/content/gallery/happy-holidays.md | 20 +- .../gallery/horizontal-grouped-bars.md | 88 ++- .../gallery/horizontal-stacked-bars.md | 72 ++- docs/src/content/gallery/interpolation.md | 76 ++- .../gallery/multiple-dependent-axes.md | 76 ++- .../gallery/multipoint-tooltip-labels.md | 124 ++--- .../content/gallery/parallel-brush-axis.md | 148 +++--- docs/src/content/gallery/polar-cardioid.md | 56 +- docs/src/content/gallery/radar-chart.md | 137 +++-- .../gallery/stacked-bars-central-axis.md | 89 ++-- .../content/gallery/stacked-grouped-bars.md | 69 ++- .../src/content/gallery/stacked-polar-bars.md | 224 ++++---- docs/src/content/gallery/stream-graph.md | 158 +++--- .../content/gallery/victory-area-animation.md | 84 +-- .../content/gallery/victory-area-stroke.md | 74 ++- .../content/gallery/victory-line-null-data.md | 43 +- .../gallery/victory-pie-center-label.md | 44 +- .../gallery/victory-portal-stacked-area.md | 110 ++-- .../gallery/voronoi-tooltips-grouped.md | 106 ++-- docs/src/content/guides/animations.md | 203 ++++--- docs/src/content/guides/brush-and-zoom.md | 232 ++++---- docs/src/content/guides/custom-charts.md | 500 +++++++++--------- docs/src/content/guides/custom-components.md | 252 +++++---- docs/src/content/guides/events.md | 136 +++-- docs/src/content/guides/tooltips.md | 138 +++-- docs/src/content/guides/zoom-large-data.md | 181 ++++--- docs/src/content/introduction/native.md | 18 +- 36 files changed, 2118 insertions(+), 2325 deletions(-) diff --git a/docs/src/content/common-props/common-props.md b/docs/src/content/common-props/common-props.md index 1dca183b4..786640640 100644 --- a/docs/src/content/common-props/common-props.md +++ b/docs/src/content/common-props/common-props.md @@ -28,67 +28,61 @@ See the [Animations Guide][] for more detail on animations and transitions _example:_ `animate={{ duration: 2000 }}` ```playground_norender -class App extends React.Component { - - render() { - return ( - - datum.opacity || 1 } }} - animate={{ - animationWhitelist: ["style", "data", "size"], // Try removing "size" - onExit: { - duration: 500, - before: () => ({ opacity: 0.3, _y: 0 }) - }, - onEnter: { - duration: 500, - before: () => ({ opacity: 0.3, _y: 0 }), - after: (datum) => ({ opacity: 1, _y: datum._y }) - } - }} - /> - - ); - } - - constructor(props) { - super(props); - this.state = { - data: this.getData(), - size: this.getSize() - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - data: this.getData(), - size: this.getSize() +function App(props) { + const [state, setState] = React.useState({ + data: getData(), + size: getSize() + }); + + React.useEffect(() => { + const setStateInterval = window.setInterval(() => { + setState({ + data: getData(), + size: getSize() }); }, 3000); - } - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } + return () => { + window.clearInterval(setStateInterval); + }; + }, []); - getData() { - const num = Math.floor(10 * Math.random() + 5); - const points = new Array(num).fill(1); - return points.map((point, index) => { - return { x: index + 1, y: Math.random() }; - }); - } + return ( + + datum.opacity || 1 } }} + animate={{ + animationWhitelist: ["style", "data", "size"], // Try removing "size" + onExit: { + duration: 500, + before: () => ({ opacity: 0.3, _y: 0 }) + }, + onEnter: { + duration: 500, + before: () => ({ opacity: 0.3, _y: 0 }), + after: (datum) => ({ opacity: 1, _y: datum._y }) + } + }} + /> + + ); +} - getSize() { - return Math.random() * 10 - } +function getData() { + const num = Math.floor(10 * Math.random() + 5); + const points = new Array(num).fill(1); + return points.map((point, index) => { + return { x: index + 1, y: Math.random() }; + }); +} + +function getSize() { + return Math.random() * 10 } render(); @@ -201,31 +195,29 @@ See the [Custom Components Guide][] for more detail on creating your own `dataCo _examples:_ `dataComponent={}` ```playground_norender -class CatPoint extends React.Component { - render() { - const {x, y, datum} = this.props; // VictoryScatter supplies x, y and datum - const cat = datum._y >= 0 ? "😻" : "😹"; - return ( - - {cat} - - ); - } +function CatPoint(props) { + const {x, y, datum} = props; // VictoryScatter supplies x, y and datum + const cat = datum._y >= 0 ? "😻" : "😹"; + + return ( + + {cat} + + ); } -class App extends React.Component { - render() { - return ( - - } - y={(d) => Math.sin(2 * Math.PI * d.x)} - samples={15} - /> - - ); - } +function App() { + return ( + + } + y={(d) => Math.sin(2 * Math.PI * d.x)} + samples={15} + /> + + ); } + render(); ``` @@ -418,77 +410,74 @@ externalEventMutations: PropTypes.arrayOf( The `target`, `eventKey`, and `childName` (when applicable) must always be specified. The `mutation` function will be called with the current props of the element specified by the `target`, `eventKey` and `childName` provided. The mutation function should return a mutation object for that element. The `callback` prop should be used to clear the `externalEventMutations` prop once the mutation has been applied. Clearing `externalEventMutations` is crucial for charts that animate. ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - externalMutations: undefined - }; - } - removeMutation() { - this.setState({ +function App() { + const [state, setState] = React.useState({ + externalMutations: undefined + }); + + function removeMutation() { + setState({ externalMutations: undefined }); } - clearClicks() { - this.setState({ + function clearClicks() { + setState({ externalMutations: [ { childName: "Bar-1", target: ["data"], eventKey: "all", mutation: () => ({ style: undefined }), - callback: this.removeMutation.bind(this) + callback: removeMutation } ] }); } - render() { - const buttonStyle = { - backgroundColor: "black", - color: "white", - padding: "10px", - marginTop: "10px" - }; - return ( -
- - ({ - target: "data", - mutation: () => ({ style: { fill: "orange" } }) - }) - } + const buttonStyle = { + backgroundColor: "black", + color: "white", + padding: "10px", + marginTop: "10px" + }; + + return ( +
+ + ({ + target: "data", + mutation: () => ({ style: { fill: "orange" } }) + }) } + } + ]} + > + "click me!"} + data={[ + { x: 1, y: 2 }, + { x: 2, y: 4 }, + { x: 3, y: 1 }, + { x: 4, y: 5 } ]} - > - "click me!"} - data={[ - { x: 1, y: 2 }, - { x: 2, y: 4 }, - { x: 3, y: 1 }, - { x: 4, y: 5 } - ]} - /> - -
- ) - } + /> +
+
+ ) } render(); diff --git a/docs/src/content/docs/victory-scatter.md b/docs/src/content/docs/victory-scatter.md index c8cbe90b1..029bdeeef 100644 --- a/docs/src/content/docs/victory-scatter.md +++ b/docs/src/content/docs/victory-scatter.md @@ -124,30 +124,27 @@ See the [Custom Components Guide][] for more detail on creating your own `dataCo _default:_ `` ```playground_norender -class CatPoint extends React.Component { - render() { - const {x, y, datum} = this.props; // VictoryScatter supplies x, y and datum - const cat = datum._y >= 0 ? "😻" : "😹"; - return ( - - {cat} - - ); - } +function CatPoint(props) { + const {x, y, datum} = props; // VictoryScatter supplies x, y and datum + const cat = datum._y >= 0 ? "😻" : "😹"; + + return ( + + {cat} + + ); } -class App extends React.Component { - render() { - return ( - - } - y={(d) => Math.sin(2 * Math.PI * d.x)} - samples={15} - /> - - ); - } +function App() { + return ( + + } + y={(d) => Math.sin(2 * Math.PI * d.x)} + samples={15} + /> + + ); } render(); ``` diff --git a/docs/src/content/docs/victory-shared-events.md b/docs/src/content/docs/victory-shared-events.md index 02059d9f9..1ddc95eed 100644 --- a/docs/src/content/docs/victory-shared-events.md +++ b/docs/src/content/docs/victory-shared-events.md @@ -115,99 +115,95 @@ externalEventMutations: PropTypes.arrayOf( The `target`, `eventKey`, and `childName` (when applicable) must always be specified. The `mutation` function will be called with the current props of the element specified by the `target`, `eventKey` and `childName` provided. The mutation function should return a mutation object for that element. The `callback` prop should be used to clear the `externalEventMutations` prop once the mutation has been applied. Clearing `externalEventMutations` is crucial for charts that animate. ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - externalMutations: undefined - }; - } +function App() { + const [state, setState] = React.useState({ + externalMutations: undefined + }); - removeMutation() { - this.setState({ + function removeMutation() { + setState({ externalMutations: undefined }); } - clearClicks() { - this.setState({ + function clearClicks() { + setState({ externalMutations: [ { childName: ["bar", "pie"], target: ["data"], eventKey: "all", mutation: () => ({ style: undefined }), - callback: this.removeMutation.bind(this) + callback: removeMutation } ] }); } - render() { - const buttonStyle = { - backgroundColor: "black", - color: "white", - padding: "10px", - marginTop: "10px" - }; - return ( -
- - - { - return [{ - childName: ["pie", "bar"], - mutation: (props) => { - return { - style: Object.assign({}, props.style, {fill: "tomato"}) - }; - } - }]; - } + const buttonStyle = { + backgroundColor: "black", + color: "white", + padding: "10px", + marginTop: "10px" + }; + + return ( +
+ + + { + return [{ + childName: ["pie", "bar"], + mutation: (props) => { + return { + style: Object.assign({}, props.style, {fill: "tomato"}) + }; + } + }]; } - }]} - > - - } - /> - - - - - - -
- ) - } + } + }]} + > + + } + /> + + + + + + +
+ ) } render(); diff --git a/docs/src/content/gallery/alternative-events.md b/docs/src/content/gallery/alternative-events.md index 0c7e1a9eb..bd07bd860 100644 --- a/docs/src/content/gallery/alternative-events.md +++ b/docs/src/content/gallery/alternative-events.md @@ -4,52 +4,47 @@ title: Alternative Events --- ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - clicked: false, +function App() { + const [state, setState] = React.useState({ + clicked: false, + style: { + data: { fill: "tomato" } + } + }); + + const handleMouseOver = () => { + const fillColor = state.clicked ? "blue" : "tomato"; + const clicked = !state.clicked; + setState({ + clicked, style: { - data: { fill: "tomato" } + data: { fill: fillColor } } - }; - } - - render() { - const handleMouseOver = () => { - const fillColor = this.state.clicked ? "blue" : "tomato"; - const clicked = !this.state.clicked; - this.setState({ - clicked, - style: { - data: { fill: fillColor } - } - }); - }; + }); + }; - return ( -
- - - } - style={this.state.style} - data={[ - { x: new Date(1986, 1, 1), y: 2 }, - { x: new Date(1996, 1, 1), y: 3 }, - { x: new Date(2006, 1, 1), y: 5 }, - { x: new Date(2016, 1, 1), y: 4 } - ]} - /> - -
- ); - } - } + return ( +
+ + + } + style={state.style} + data={[ + { x: new Date(1986, 1, 1), y: 2 }, + { x: new Date(1996, 1, 1), y: 3 }, + { x: new Date(2006, 1, 1), y: 5 }, + { x: new Date(2016, 1, 1), y: 4 } + ]} + /> + +
+ ); +} render(); ``` diff --git a/docs/src/content/gallery/animating-circular-progress-bar.md b/docs/src/content/gallery/animating-circular-progress-bar.md index 59ccd0933..3be1d4586 100644 --- a/docs/src/content/gallery/animating-circular-progress-bar.md +++ b/docs/src/content/gallery/animating-circular-progress-bar.md @@ -4,69 +4,64 @@ title: Animating Circular Progress Bar --- ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - percent: 25, data: this.getData(0) - }; - } +function App() { + const [state, setState] = React.useState({ + percent: 25, data: getData(0) + }); - componentDidMount() { - let percent = 25; - this.setStateInterval = window.setInterval(() => { + React.useState(() => { + const setStateInterval = window.setInterval(() => { + let percent = 25; percent += (Math.random() * 25); percent = (percent > 100) ? 0 : percent; - this.setState({ - percent, data: this.getData(percent) + setState({ + percent, data: getData(percent) }); }, 2000); - } - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } + return () => { + window.clearInterval(setStateInterval); + } + }, []); - getData(percent) { - return [{ x: 1, y: percent }, { x: 2, y: 100 - percent }]; - } + return ( +
+ + null} + style={{ + data: { fill: ({ datum }) => { + const color = datum.y > 30 ? "green" : "red"; + return datum.x === 1 ? color : "transparent"; + } + } + }} + /> + + {(newProps) => { + return ( + + ); + }} + + +
+ ); +} - render() { - return ( -
- - null} - style={{ - data: { fill: ({ datum }) => { - const color = datum.y > 30 ? "green" : "red"; - return datum.x === 1 ? color : "transparent"; - } - } - }} - /> - - {(newProps) => { - return ( - - ); - }} - - -
- ); - } +function getData(percent) { + return [{ x: 1, y: percent }, { x: 2, y: 100 - percent }]; } render(); diff --git a/docs/src/content/gallery/area-hover.md b/docs/src/content/gallery/area-hover.md index e2d48aa36..52580a6e8 100644 --- a/docs/src/content/gallery/area-hover.md +++ b/docs/src/content/gallery/area-hover.md @@ -4,7 +4,7 @@ title: Area Hover Styles --- ```playground_norender -const CustomArea = props => { +function CustomArea(props) { if (!props.active) { return ; } else { @@ -46,64 +46,56 @@ const CustomArea = props => { ); } -}; +} -class App extends React.Component { - constructor() { - super(); - this.state = { - activeX: null - }; - this.setActiveX = this.onActivated.bind(this); - } +function App() { + const [state, setState] = React.useState({ + activeX: null + }); - onActivated(points, props) { - this.setState({ activeX: points[0]._x }); + function onActivated(points, props) { + setState({ activeX: points[0]._x }); } - render() { - return ( - - } - > - - } - /> + return ( + + } + > + + } + /> - } - /> - } - /> - - - ); - } + } + /> + } + /> + + + ); } render(); diff --git a/docs/src/content/gallery/brush-zoom.md b/docs/src/content/gallery/brush-zoom.md index 242f1e3d0..c824ef6db 100644 --- a/docs/src/content/gallery/brush-zoom.md +++ b/docs/src/content/gallery/brush-zoom.md @@ -4,84 +4,79 @@ title: Brush and Zoom --- ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - zoomDomain: { x: [new Date(1990, 1, 1), new Date(2009, 1, 1)] } - }; - } +function App() { + const [state, setState] = React.useState({ + zoomDomain: { x: [new Date(1990, 1, 1), new Date(2009, 1, 1)] } + }); - handleZoom(domain) { - this.setState({ zoomDomain: domain }); + function handleZoom(domain) { + setState({ zoomDomain: domain }); } - render() { - return ( -
- + + } + > + + + + } > - - - - - } - > - new Date(x).getFullYear()} - /> - - -
- ); - } + new Date(x).getFullYear()} + /> + + + + ); } render(); diff --git a/docs/src/content/gallery/column-chart.md b/docs/src/content/gallery/column-chart.md index faef7ee16..223ae4eaa 100644 --- a/docs/src/content/gallery/column-chart.md +++ b/docs/src/content/gallery/column-chart.md @@ -28,9 +28,11 @@ const myDataset = [ ] ]; -class App extends React.Component { - // This is an example of a function you might use to transform your data to make 100% data - transformData(dataset) { +function App() { + // This is an example of a function you might + // use to transform your data to make 100% data + + function transformData(dataset) { const totals = dataset[0].map((data, i) => { return dataset.reduce((memo, curr) => { return memo + curr[i].y; @@ -43,30 +45,26 @@ class App extends React.Component { }); } - render() { - const dataset = this.transformData(myDataset); - return ( -
- + + - - {dataset.map((data, i) => { - return ; - })} - - `${tick}%`} - /> - - -
- ); - } + {dataset.map((data, i) => { + return ; + })} + + `${tick}%`} + /> + + + + ); } render(); diff --git a/docs/src/content/gallery/custom-tooltip-labels.md b/docs/src/content/gallery/custom-tooltip-labels.md index e2ede3be1..8123412ca 100644 --- a/docs/src/content/gallery/custom-tooltip-labels.md +++ b/docs/src/content/gallery/custom-tooltip-labels.md @@ -4,48 +4,44 @@ title: Custom Tooltip Labels --- ```playground_norender -class CustomLabel extends React.Component { - render() { - return ( - - - - - ); - } +function CustomLabel(props) { + return ( + + + + + ); } CustomLabel.defaultEvents = VictoryTooltip.defaultEvents; -class App extends React.Component { - render() { - return ( - `# ${datum.y}`} - labelComponent={} - data={[ - { x: 1, y: 5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 } - ]} - /> - ); - } +function App() { + return ( + `# ${datum.y}`} + labelComponent={} + data={[ + { x: 1, y: 5 }, + { x: 2, y: 4 }, + { x: 3, y: 2 }, + { x: 4, y: 3 }, + { x: 5, y: 1 } + ]} + /> + ); } render(); diff --git a/docs/src/content/gallery/happy-holidays.md b/docs/src/content/gallery/happy-holidays.md index da209ad29..498105f1d 100644 --- a/docs/src/content/gallery/happy-holidays.md +++ b/docs/src/content/gallery/happy-holidays.md @@ -113,17 +113,15 @@ function Snow() { ); } -class App extends React.Component { - render() { - return ( -
- - - - -
- ); - } +function App() { + return ( +
+ + + + +
+ ); } render(); diff --git a/docs/src/content/gallery/horizontal-grouped-bars.md b/docs/src/content/gallery/horizontal-grouped-bars.md index 4b091375e..c0ad5f3d2 100644 --- a/docs/src/content/gallery/horizontal-grouped-bars.md +++ b/docs/src/content/gallery/horizontal-grouped-bars.md @@ -4,51 +4,49 @@ title: Horizontal Grouped Bars --- ``` playground_norender -class App extends React.Component { - render() { - return ( -
- - - - - - - -
- ); - } +function App() { + return ( +
+ + + + + + + +
+ ); } render(); diff --git a/docs/src/content/gallery/horizontal-stacked-bars.md b/docs/src/content/gallery/horizontal-stacked-bars.md index 1072df33a..28d060f9e 100644 --- a/docs/src/content/gallery/horizontal-stacked-bars.md +++ b/docs/src/content/gallery/horizontal-stacked-bars.md @@ -16,43 +16,41 @@ const styles = [ const labelFn = ({ datum }) => datum.y < axisTickValues[0] ? `${datum.y} Low` : `${datum.y} Normal`; -class App extends React.Component { - render() { - return ( -
- - - - {data.map((d, i) => ( - - } - /> - ))} - - -
- ); - } +function App() { + return ( +
+ + + + {data.map((d, i) => ( + + } + /> + ))} + + +
+ ); } render(); diff --git a/docs/src/content/gallery/interpolation.md b/docs/src/content/gallery/interpolation.md index 321d1bb92..87caca73b 100644 --- a/docs/src/content/gallery/interpolation.md +++ b/docs/src/content/gallery/interpolation.md @@ -42,48 +42,44 @@ const InterpolationSelect = ({ currentValue, values, onChange }) => ( ); -class App extends React.Component { - constructor() { - super(); - this.state = { - interpolation: "linear", - polar: false - }; - } - render() { - return ( -
- this.setState({ interpolation: event.target.value })} +function App() { + const [state, setState] = React.useState({ + interpolation: "linear", + polar: false + }); + + return ( +
+ setState({ interpolation: event.target.value })} + /> + setState({ + polar: event.target.checked, + interpolation: "linear" + }) + } + style={{ marginLeft: 25, marginRight: 5 }} + /> + + + - this.setState({ - polar: event.target.checked, - interpolation: "linear" - }) - } - style={{ marginLeft: 25, marginRight: 5 }} + - - - - - -
- ); - } + +
+ ); } render(); diff --git a/docs/src/content/gallery/multiple-dependent-axes.md b/docs/src/content/gallery/multiple-dependent-axes.md index 8858090a6..801e937f8 100644 --- a/docs/src/content/gallery/multiple-dependent-axes.md +++ b/docs/src/content/gallery/multiple-dependent-axes.md @@ -14,50 +14,46 @@ const maxima = data.map( (dataset) => Math.max(...dataset.map((d) => d.y)) ); -const xOffsets = [50, 200, 350]; +const xOffsets = [50, 200, 300]; const tickPadding = [ 0, 0, -15 ]; const anchors = ["end", "end", "start"]; const colors = ["black", "red", "blue"]; -class App extends React.Component { - - render() { - return ( -
- - - {data.map((d, i) => ( - t * maxima[i]} - /> - ))} - {data.map((d, i) => ( - datum.y / maxima[i]} - /> - ))} - -
- ); - } +function App() { + return ( +
+ + + {data.map((d, i) => ( + t * maxima[i]} + /> + ))} + {data.map((d, i) => ( + datum.y / maxima[i]} + /> + ))} + +
+ ); } render(); diff --git a/docs/src/content/gallery/multipoint-tooltip-labels.md b/docs/src/content/gallery/multipoint-tooltip-labels.md index f3281a342..6aed0dc57 100644 --- a/docs/src/content/gallery/multipoint-tooltip-labels.md +++ b/docs/src/content/gallery/multipoint-tooltip-labels.md @@ -3,72 +3,64 @@ id: 8 title: Multipoint Tooltip Labels --- -``` playground_norender -class App extends React.Component { - render() { - return ( - `y: ${datum.y}`} - labelComponent={ - } - />} - > - active ? 4 : 2 - }, - labels: { fill: "tomato" } - }} - /> +``` playground + `y: ${datum.y}`} + labelComponent={ + } + />} +> + active ? 4 : 2 + }, + labels: { fill: "tomato" } + }} + /> - active ? 4 : 2 - }, - labels: { fill: "blue" } - }} - /> + active ? 4 : 2 + }, + labels: { fill: "blue" } + }} + /> - active ? 4 : 2 - }, - labels: { fill: "black" } - }} - /> - - ); - } -} - -render(); + active ? 4 : 2 + }, + labels: { fill: "black" } + }} + /> + ``` diff --git a/docs/src/content/gallery/parallel-brush-axis.md b/docs/src/content/gallery/parallel-brush-axis.md index 56367515d..6f9963718 100644 --- a/docs/src/content/gallery/parallel-brush-axis.md +++ b/docs/src/content/gallery/parallel-brush-axis.md @@ -14,47 +14,45 @@ const data = [ ]; const attributes = ["strength", "intelligence", "speed", "luck"]; const height = 500; -const width = 500; +const width = 700; const padding = { top: 100, left: 50, right: 50, bottom: 50 }; -class App extends React.Component { - constructor() { - super(); - const maximumValues = this.getMaximumValues(); - const datasets = this.normalizeData(maximumValues); - this.state = { - maximumValues, datasets, filters: {}, activeDatasets: [], isFiltered: false - }; - } +function getMaximumValues() { + // Find the maximum value for each axis. This will be used to normalize data and re-scale axis ticks + return attributes.map((attribute) => { + return data.reduce((memo, datum) => { + return datum[attribute] > memo ? datum[attribute] : memo; + }, -Infinity); + }); +} - getMaximumValues() { - // Find the maximum value for each axis. This will be used to normalize data and re-scale axis ticks - return attributes.map((attribute) => { - return data.reduce((memo, datum) => { - return datum[attribute] > memo ? datum[attribute] : memo; - }, -Infinity); - }); - } +function normalizeData(maximumValues) { + // construct normalized datasets by dividing the value for each attribute by the maximum value + return data.map((datum) => ({ + name: datum.name, + data: attributes.map((attribute, i) => ( + { x: attribute, y: datum[attribute] / maximumValues[i] } + )) + })); +} - normalizeData(maximumValues) { - // construct normalized datasets by dividing the value for each attribute by the maximum value - return data.map((datum) => ({ - name: datum.name, - data: attributes.map((attribute, i) => ( - { x: attribute, y: datum[attribute] / maximumValues[i] } - )) - })); - } +function App() { + const maximumValues = getMaximumValues(); + const datasets = normalizeData(maximumValues); + + const [state, setState] = React.useState({ + maximumValues, datasets, filters: {}, activeDatasets: [], isFiltered: false + }); - addNewFilters(domain, props) { - const filters = this.state.filters || {}; + function addNewFilters(domain, props) { + const filters = state.filters || {}; const extent = domain && Math.abs(domain[1] - domain[0]); const minVal = 1 / Number.MAX_SAFE_INTEGER; filters[props.name] = extent <= minVal ? undefined : domain; return filters; } - getActiveDatasets(filters) { + function getActiveDatasets(filters) { // Return the names from all datasets that have values within all filters const isActive = (dataset) => { return _.keys(filters).reduce((memo, name) => { @@ -67,69 +65,67 @@ class App extends React.Component { }, true); }; - return this.state.datasets.map((dataset) => { + return state.datasets.map((dataset) => { return isActive(dataset, filters) ? dataset.name : null; }).filter(Boolean); } - onDomainChange(domain, props) { - const filters = this.addNewFilters(domain, props); + function onDomainChange(domain, props) { + const filters = addNewFilters(domain, props); const isFiltered = !_.isEmpty(_.values(filters).filter(Boolean)); - const activeDatasets = isFiltered ? this.getActiveDatasets(filters) : this.state.datasets; - this.setState({ activeDatasets, filters, isFiltered }); + const activeDatasets = isFiltered ? getActiveDatasets(filters) : state.datasets; + setState({ activeDatasets, filters, isFiltered }); } - isActive(dataset) { + function isActive(dataset) { // Determine whether a given dataset is active - return !this.state.isFiltered ? true : _.includes(this.state.activeDatasets, dataset.name); + return !state.isFiltered ? true : _.includes(state.activeDatasets, dataset.name); } - getAxisOffset(index) { + function getAxisOffset(index) { const step = (width - padding.left - padding.right) / (attributes.length - 1); return step * index + padding.left; } - render() { - return ( - - + } + /> + {state.datasets.map((dataset) => ( + } + style={{ data: { + stroke: "tomato", + opacity: isActive(dataset) ? 1 : 0.2 + } }} + /> + ))} + {attributes.map((attribute, index) => ( + + } + offsetX={getAxisOffset(index)} style={{ - tickLabels: { fontSize: 20 }, axis: { stroke: "none" } + tickLabels: { fontSize: 15, padding: 15, pointerEvents: "none" }, }} - tickLabelComponent={} + tickValues={[0.2, 0.4, 0.6, 0.8, 1]} + tickFormat={(tick) => Math.round(tick * state.maximumValues[index])} /> - {this.state.datasets.map((dataset) => ( - } - style={{ data: { - stroke: "tomato", - opacity: this.isActive(dataset) ? 1 : 0.2 - } }} - /> - ))} - {attributes.map((attribute, index) => ( - - } - offsetX={this.getAxisOffset(index)} - style={{ - tickLabels: { fontSize: 15, padding: 15, pointerEvents: "none" }, - }} - tickValues={[0.2, 0.4, 0.6, 0.8, 1]} - tickFormat={(tick) => Math.round(tick * this.state.maximumValues[index])} - /> - ))} - - ); - } + ))} + + ); } render(); diff --git a/docs/src/content/gallery/polar-cardioid.md b/docs/src/content/gallery/polar-cardioid.md index dbb236490..09de22bc1 100644 --- a/docs/src/content/gallery/polar-cardioid.md +++ b/docs/src/content/gallery/polar-cardioid.md @@ -6,35 +6,33 @@ title: Polar Cardioid ```playground_norender const colors = [ "#428517", "#77D200", "#D6D305", "#EC8E19", "#C92B05"]; -class App extends React.Component { - render() { - return ( - - ""} - /> - - { [5, 4, 3, 2, 1].map((val, i) => { - return ( - val * (1 - Math.cos(d.x))} - /> - ); - })} - - ); - } +function App() { + return ( + + ""} + /> + + { [5, 4, 3, 2, 1].map((val, i) => { + return ( + val * (1 - Math.cos(d.x))} + /> + ); + })} + + ); } render(); diff --git a/docs/src/content/gallery/radar-chart.md b/docs/src/content/gallery/radar-chart.md index 806a114f0..d8c62c26c 100644 --- a/docs/src/content/gallery/radar-chart.md +++ b/docs/src/content/gallery/radar-chart.md @@ -10,81 +10,76 @@ const characterData = [ { strength: 5, intelligence: 225, luck: 3, stealth: 60, charisma: 120 } ]; -class App extends React.Component { - constructor(props) { - super(props); - this.state = { - data: this.processData(characterData), - maxima: this.getMaxima(characterData) - }; - } +function App() { + const [state, setState] = React.useState({ + data: processData(characterData), + maxima: getMaxima(characterData) + }); - getMaxima(data) { - const groupedData = Object.keys(data[0]).reduce((memo, key) => { - memo[key] = data.map((d) => d[key]); - return memo; - }, {}); - return Object.keys(groupedData).reduce((memo, key) => { - memo[key] = Math.max(...groupedData[key]); - return memo; - }, {}); - } + return ( + + + {state.data.map((data, i) => { + return ; + })} + + { + Object.keys(state.maxima).map((key, i) => { + return ( + + } + labelPlacement="perpendicular" + axisValue={i + 1} label={key} + tickFormat={(t) => Math.ceil(t * state.maxima[key])} + tickValues={[0.25, 0.5, 0.75]} + /> + ); + }) + } + ""} + style={{ + axis: { stroke: "none" }, + grid: { stroke: "grey", opacity: 0.5 } + }} + /> - processData(data) { - const maxByGroup = this.getMaxima(data); - const makeDataArray = (d) => { - return Object.keys(d).map((key) => { - return { x: key, y: d[key] / maxByGroup[key] }; - }); - }; - return data.map((datum) => makeDataArray(datum)); - } + + ); +} - render() { - return ( - - - {this.state.data.map((data, i) => { - return ; - })} - - { - Object.keys(this.state.maxima).map((key, i) => { - return ( - - } - labelPlacement="perpendicular" - axisValue={i + 1} label={key} - tickFormat={(t) => Math.ceil(t * this.state.maxima[key])} - tickValues={[0.25, 0.5, 0.75]} - /> - ); - }) - } - ""} - style={{ - axis: { stroke: "none" }, - grid: { stroke: "grey", opacity: 0.5 } - }} - /> +function getMaxima(data) { + const groupedData = Object.keys(data[0]).reduce((memo, key) => { + memo[key] = data.map((d) => d[key]); + return memo; + }, {}); + return Object.keys(groupedData).reduce((memo, key) => { + memo[key] = Math.max(...groupedData[key]); + return memo; + }, {}); +} - - ); - } +function processData(data) { + const maxByGroup = getMaxima(data); + const makeDataArray = (d) => { + return Object.keys(d).map((key) => { + return { x: key, y: d[key] / maxByGroup[key] }; + }); + }; + return data.map((datum) => makeDataArray(datum)); } render(); diff --git a/docs/src/content/gallery/stacked-bars-central-axis.md b/docs/src/content/gallery/stacked-bars-central-axis.md index e873466b7..f2cd4c4be 100644 --- a/docs/src/content/gallery/stacked-bars-central-axis.md +++ b/docs/src/content/gallery/stacked-bars-central-axis.md @@ -24,54 +24,51 @@ const dataB = dataA.map((point) => { const width = 400; const height = 400; -class App extends React.Component { - - render() { - return ( - + - - (-Math.abs(data.y))} - labels={({ datum }) => (`${Math.abs(datum.y)}%`)} - /> - (`${Math.abs(datum.y)}%`)} - /> - - - - } - tickValues={dataA.map((point) => point.x).reverse()} + (-Math.abs(data.y))} + labels={({ datum }) => (`${Math.abs(datum.y)}%`)} /> - - ); - } + (`${Math.abs(datum.y)}%`)} + /> + + + + } + tickValues={dataA.map((point) => point.x).reverse()} + /> + + ); } render(); diff --git a/docs/src/content/gallery/stacked-grouped-bars.md b/docs/src/content/gallery/stacked-grouped-bars.md index 35f386a11..bd1226971 100644 --- a/docs/src/content/gallery/stacked-grouped-bars.md +++ b/docs/src/content/gallery/stacked-grouped-bars.md @@ -4,43 +4,40 @@ title: Stacked Grouped Bars --- ```playground_norender -class App extends React.Component { +function App() { + const getBarData = () => { + return [1, 2, 3, 4, 5].map(() => { + return [ + { x: 1, y: Math.random() }, + { x: 2, y: Math.random() }, + { x: 3, y: Math.random() } + ]; + }); + }; - render() { - const getBarData = () => { - return [1, 2, 3, 4, 5].map(() => { - return [ - { x: 1, y: Math.random() }, - { x: 2, y: Math.random() }, - { x: 3, y: Math.random() } - ]; - }); - }; - - return ( -
- - - - {getBarData().map((data, index) => { - return ; - })} - - - {getBarData().map((data, index) => { - return ; - })} - - - {getBarData().map((data, index) => { - return ; - })} - - - -
- ); - } + return ( +
+ + + + {getBarData().map((data, index) => { + return ; + })} + + + {getBarData().map((data, index) => { + return ; + })} + + + {getBarData().map((data, index) => { + return ; + })} + + + +
+ ); } render(); diff --git a/docs/src/content/gallery/stacked-polar-bars.md b/docs/src/content/gallery/stacked-polar-bars.md index 7035fa0ce..f74091a86 100644 --- a/docs/src/content/gallery/stacked-polar-bars.md +++ b/docs/src/content/gallery/stacked-polar-bars.md @@ -8,139 +8,131 @@ const directions = { 0: "E", 45: "NE", 90: "N", 135: "NW", 180: "W", 225: "SW", 270: "S", 315: "SE" }; - const orange = { base: "gold", highlight: "darkOrange" }; - const red = { base: "tomato", highlight: "orangeRed" }; - const innerRadius = 30; -class CompassCenter extends React.Component { +function CompassCenter(props) { + const { origin } = props; + const circleStyle = { + stroke: red.base, strokeWidth: 2, fill: orange.base + }; - render() { - const { origin } = this.props; - const circleStyle = { - stroke: red.base, strokeWidth: 2, fill: orange.base - }; - return ( - - - - ); - } + return ( + + + + ); } -class CenterLabel extends React.Component { - render() { - const { datum, active, color } = this.props; - const text = [ `${directions[datum._x]}`, `${Math.round(datum._y1)} mph` ]; - const baseStyle = { fill: color.highlight, textAnchor: "middle" }; - const style = [ - { ...baseStyle, fontSize: 18, fontWeight: "bold" }, - { ...baseStyle, fontSize: 12 } - ]; +function CenterLabel(props) { + const { datum, active, color } = props; + const text = [ `${directions[datum._x]}`, `${Math.round(datum._y1)} mph` ]; + const baseStyle = { fill: color.highlight, textAnchor: "middle" }; + const style = [ + { ...baseStyle, fontSize: 18, fontWeight: "bold" }, + { ...baseStyle, fontSize: 12 } + ]; - return active ? - ( - - ) : null; - } + return active ? + ( + + ) : null; } -class App extends React.Component { - constructor(props) { - super(props); - this.state = { wind: this.getWindData() }; - } +function App() { + const [state, setState] = React.useState({ wind: getWindData() }); - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ wind: this.getWindData() }); + React.useState(() => { + const setStateInterval = window.setInterval(() => { + setState({ wind: getWindData() }); }, 4000); - } - getWindData() { - return _.keys(directions).map((d) => { - const speed = Math.floor(_.random() * 17) + 4; - return { - windSpeed: speed, - windGust: speed + _.random(2, 10), - windBearing: +d - }; - }); - } + return () => { + window.clearInterval(setStateInterval); + } + }, []); - render() { - return ( - { - return [ - { target: "labels", mutation: () => ({ active: true }) }, - { target: "data", mutation: () => ({ active: true }) } - ]; - }, - onMouseOut: () => { - return [ - { target: "labels", mutation: () => ({ active: false }) }, - { target: "data", mutation: () => ({ active: false }) } - ]; - } + return ( + { + return [ + { target: "labels", mutation: () => ({ active: true }) }, + { target: "data", mutation: () => ({ active: true }) } + ]; + }, + onMouseOut: () => { + return [ + { target: "labels", mutation: () => ({ active: false }) }, + { target: "data", mutation: () => ({ active: false }) } + ]; } - }]} - > - ""} + } + }]} + > + ""} + /> + +k)} + tickFormat={_.values(directions)} + /> + + active ? orange.highlight : orange.base, + width: 40 + } }} + data={state.wind} + x="windBearing" + y="windSpeed" + labels={() => ""} + labelComponent={} /> - +k)} - tickFormat={_.values(directions)} + a ? red.highlight : red.base, + width: 40 + } }} + data={state.wind} + x="windBearing" + y={(d) => d.windGust - d.windSpeed} + labels={() => ""} + labelComponent={} /> - - active ? orange.highlight : orange.base, - width: 40 - } }} - data={this.state.wind} - x="windBearing" - y="windSpeed" - labels={() => ""} - labelComponent={} - /> - a ? red.highlight : red.base, - width: 40 - } }} - data={this.state.wind} - x="windBearing" - y={(d) => d.windGust - d.windSpeed} - labels={() => ""} - labelComponent={} - /> - - - - ); - } - } + + + + ); +} + +function getWindData() { + return _.keys(directions).map((d) => { + const speed = Math.floor(_.random() * 17) + 4; + return { + windSpeed: speed, + windGust: speed + _.random(2, 10), + windBearing: +d + }; + }); +} render(); ``` diff --git a/docs/src/content/gallery/stream-graph.md b/docs/src/content/gallery/stream-graph.md index edee92784..ac1e6aaaf 100644 --- a/docs/src/content/gallery/stream-graph.md +++ b/docs/src/content/gallery/stream-graph.md @@ -5,7 +5,7 @@ title: Stream Graph ```playground_norender // This custom path component is supplied to `Area` as the `pathComponent` prop -const GradientPath = props => { +function GradientPath(props) { const toGrayscale = color => { const integerColor = parseInt(color.replace("#", ""), 16); const r = (integerColor >> 16) & 255; // eslint-disable-line no-bitwise @@ -39,95 +39,87 @@ const GradientPath = props => { ); -}; - -class App extends React.Component { - constructor() { - super(); - this.state = { percent: 62 }; - } - - getStreamData() { - return _.range(7).map(i => - _.range(26).map(j => ({ - x: j, - y: (10 - i) * _.random(10 - i, 20 - 2 * i), - _y0: -1 * (10 - i) * _.random(10 - i, 20 - 2 * i) - })) - ); - } +} - render() { - const streamData = this.getStreamData(); +function App() { + const [state, setState] = React.useState({ percent: 62 }); - const colors = [ - "#006064", - "#00796B", - "#8BC34A", - "#DCE775", - "#FFF59D", - "#F4511E", - "#c33409" - ]; + const streamData = getStreamData(); + const colors = [ + "#006064", + "#00796B", + "#8BC34A", + "#DCE775", + "#FFF59D", + "#F4511E", + "#c33409" + ]; - return ( - + - - + tickValues={[2, 4, 6, 8, 10, 12, 14, 17, 19, 21, 23, 25]} + /> + - {streamData.map((d, i) => ( - } - /> - } - /> - ))} - ( + } + /> + } /> - - ); - } + ))} + + + ); +} + +function getStreamData() { + return _.range(7).map(i => + _.range(26).map(j => ({ + x: j, + y: (10 - i) * _.random(10 - i, 20 - 2 * i), + _y0: -1 * (10 - i) * _.random(10 - i, 20 - 2 * i) + })) + ); } render(); diff --git a/docs/src/content/gallery/victory-area-animation.md b/docs/src/content/gallery/victory-area-animation.md index 9f5c2af21..0af3f8af5 100644 --- a/docs/src/content/gallery/victory-area-animation.md +++ b/docs/src/content/gallery/victory-area-animation.md @@ -1,55 +1,55 @@ --- id: 14 -title: VictoryArea Animation +title: Victory Area Animation --- ```playground_norender -class App extends React.Component { - constructor(props) { - super(props); - this.state = { data: this.getData() }; - } +function App() { + const [state, setState] = React.useState({ data: getData() }); - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ data: this.getData() }); + React.useState(() => { + const setStateInterval = window.setInterval(() => { + setState({ data: getData() }); }, 4000); - } - getData() { - return _.range(7).map(() => { - return [ - { x: 1, y: _.random(1, 5) }, - { x: 2, y: _.random(1, 10) }, - { x: 3, y: _.random(2, 10) }, - { x: 4, y: _.random(2, 10) }, - { x: 5, y: _.random(2, 15) } - ]; - }); - } + return () => { + window.clearInterval(setStateInterval); + } + }, []); - render() { - return ( - + - - {this.state.data.map((data, i) => { - return ( - - ); - })} - - - ); - } + {state.data.map((data, i) => { + return ( + + ); + })} + + + ); +} + + +function getData() { + return _.range(7).map(() => { + return [ + { x: 1, y: _.random(1, 5) }, + { x: 2, y: _.random(1, 10) }, + { x: 3, y: _.random(2, 10) }, + { x: 4, y: _.random(2, 10) }, + { x: 5, y: _.random(2, 15) } + ]; + }); } render(); diff --git a/docs/src/content/gallery/victory-area-stroke.md b/docs/src/content/gallery/victory-area-stroke.md index 5d21c61e9..c738ece10 100644 --- a/docs/src/content/gallery/victory-area-stroke.md +++ b/docs/src/content/gallery/victory-area-stroke.md @@ -3,45 +3,37 @@ id: 15 title: VictoryArea with Stroke --- -```playground_norender -class App extends React.Component { - render() { - return ( - - - - - - - ); - } -} - -render(); +```playground + + + + + + ``` diff --git a/docs/src/content/gallery/victory-line-null-data.md b/docs/src/content/gallery/victory-line-null-data.md index 179815447..ce74bcc24 100644 --- a/docs/src/content/gallery/victory-line-null-data.md +++ b/docs/src/content/gallery/victory-line-null-data.md @@ -3,30 +3,21 @@ id: 16 title: VictoryLine with Null Data --- -```playground_norender -class App extends React.Component { - - render() { - return ( - - - - ); - } - } - -render(); +```playground + + + ``` diff --git a/docs/src/content/gallery/victory-pie-center-label.md b/docs/src/content/gallery/victory-pie-center-label.md index f687dbbdf..c25911437 100644 --- a/docs/src/content/gallery/victory-pie-center-label.md +++ b/docs/src/content/gallery/victory-pie-center-label.md @@ -3,30 +3,22 @@ id: 17 title: VictoryPie with Center Label --- -```playground_norender -class App extends React.Component { - render() { - return ( - - - - - ); - } -} - -render(); +```playground + + + + ``` diff --git a/docs/src/content/gallery/victory-portal-stacked-area.md b/docs/src/content/gallery/victory-portal-stacked-area.md index 1cd18586f..d8f938058 100644 --- a/docs/src/content/gallery/victory-portal-stacked-area.md +++ b/docs/src/content/gallery/victory-portal-stacked-area.md @@ -3,64 +3,54 @@ id: 18 title: Stacked Areas with VictoryPortal --- -```playground_norender -class App extends React.Component { - render() { - return ( -
- - - - - - - - - - - - - - - - - - - - - - -
- ); - } -} - -render(); +```playground + + + + + + + + + + + + + + + + + + + + + + ``` diff --git a/docs/src/content/gallery/voronoi-tooltips-grouped.md b/docs/src/content/gallery/voronoi-tooltips-grouped.md index 9b374ca9c..8f01f8291 100644 --- a/docs/src/content/gallery/voronoi-tooltips-grouped.md +++ b/docs/src/content/gallery/voronoi-tooltips-grouped.md @@ -3,62 +3,52 @@ id: 19 title: Voronoi Tooltips with Grouped Components --- -```playground_norender -class App extends React.Component { - render() { - return ( - } - > - `y: ${datum.y}`} - labelComponent={ - - } - data={[ - { x: 1, y: -3 }, - { x: 2, y: 5 }, - { x: 3, y: 3 }, - { x: 4, y: 0 }, - { x: 5, y: -2 }, - { x: 6, y: -2 }, - { x: 7, y: 5 } - ]} - > - - active ? 8 : 3} - /> - - `y: ${datum.y}`} - labelComponent={ - - } - data={[ - { x: 1, y: 3 }, - { x: 2, y: 1 }, - { x: 3, y: 2 }, - { x: 4, y: -2 }, - { x: 5, y: -1 }, - { x: 6, y: 2 }, - { x: 7, y: 3 } - ]} - > - - active ? 8 : 3} - /> - - - ); - } -} - -render(); +```playground +}> + `y: ${datum.y}`} + labelComponent={ + + } + data={[ + { x: 1, y: -3 }, + { x: 2, y: 5 }, + { x: 3, y: 3 }, + { x: 4, y: 0 }, + { x: 5, y: -2 }, + { x: 6, y: -2 }, + { x: 7, y: 5 } + ]} + > + + active ? 8 : 3} + /> + + `y: ${datum.y}`} + labelComponent={ + + } + data={[ + { x: 1, y: 3 }, + { x: 2, y: 1 }, + { x: 3, y: 2 }, + { x: 4, y: -2 }, + { x: 5, y: -1 }, + { x: 6, y: 2 }, + { x: 7, y: 3 } + ]} + > + + active ? 8 : 3} + /> + + ``` diff --git a/docs/src/content/guides/animations.md b/docs/src/content/guides/animations.md index f308e4db8..ae799a9c0 100644 --- a/docs/src/content/guides/animations.md +++ b/docs/src/content/guides/animations.md @@ -11,65 +11,58 @@ scope: VictoryAnimation is able to animate changes in props using [d3-interpolate][]. Victory components define their animations via the `animate` prop. `duration`, `delay`, `easing` and `onEnd` functions may all be specified via the `animate` prop. ```playground_norender -class App extends React.Component { - - constructor(props) { - super(props); - this.state = { - scatterData: this.getScatterData() - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - scatterData: this.getScatterData() +function App() { + const [state, setState] = React.useState({ + scatterData: getScatterData() + }); + + React.useState(() => { + const setStateInterval = window.setInterval(() => { + setState({ + scatterData: getScatterData() }); }, 3000); - } - - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } - - - getScatterData() { - const colors =[ - "violet", "cornflowerblue", "gold", "orange", - "turquoise", "tomato", "greenyellow" - ]; - const symbols = [ - "circle", "star", "square", "triangleUp", - "triangleDown", "diamond", "plus" - ]; - return range(25).map((index) => { - const scaledIndex = Math.floor(index % 7); - return { - x: random(10, 50), - y: random(2, 100), - size: random(8) + 3, - symbol: symbols[scaledIndex], - fill: colors[random(0, 6)], - opacity: 0.6 - }; - }); - } - - render() { - return ( - - datum.fill, - opacity: ({ datum }) => datum.opacity - } - }} - /> - - ); - } + + return () => { + window.clearInterval(setStateInterval); + } + }, []); + + return ( + + datum.fill, + opacity: ({ datum }) => datum.opacity + } + }} + /> + + ); +} + +function getScatterData() { + const colors =[ + "violet", "cornflowerblue", "gold", "orange", + "turquoise", "tomato", "greenyellow" + ]; + const symbols = [ + "circle", "star", "square", "triangleUp", + "triangleDown", "diamond", "plus" + ]; + return range(25).map((index) => { + const scaledIndex = Math.floor(index % 7); + return { + x: random(10, 50), + y: random(2, 100), + size: random(8) + 3, + symbol: symbols[scaledIndex], + fill: colors[random(0, 6)], + opacity: 0.6 + }; + }); } render(); @@ -83,59 +76,53 @@ Victory components define default transitions for entering and exiting nodes, bu *note:* Use private variables `_x`, `_y`, `_y0` and `_y1` when altering position data during transitions. ```playground_norender -class App extends React.Component { - - constructor(props) { - super(props); - this.state = { - data: this.getData() - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - data: this.getData() +function App() { + const [state, setState] = React.useState({ + data: getData() + }); + + React.useState(() => { + const setStateInterval = window.setInterval(() => { + setState({ + data: getData() }); }, 3000); - } - - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } - - getData() { - const bars = random(6, 10); - return range(bars).map((bar) => { - return {x: bar + 1, y: random(2, 10)}; - }); - } - - render() { - return ( - - ({ - _y: 0, - fill: "orange", - label: "BYE" - }) - } - }} - /> - - ); - } + + return () => { + window.clearInterval(setStateInterval); + } + }, []); + + return ( + + ({ + _y: 0, + fill: "orange", + label: "BYE" + }) + } + }} + /> + + ); +} + +function getData() { + const bars = random(6, 10); + return range(bars).map((bar) => { + return {x: bar + 1, y: random(2, 10)}; + }); } render(); diff --git a/docs/src/content/guides/brush-and-zoom.md b/docs/src/content/guides/brush-and-zoom.md index ad0328867..c95ddf46f 100644 --- a/docs/src/content/guides/brush-and-zoom.md +++ b/docs/src/content/guides/brush-and-zoom.md @@ -13,44 +13,37 @@ Use `VictoryZoomContainer` as your containerComponent to add panning and zooming In the example below, an initial domain is set with the `zoomDomain` prop. This prop may also be used to trigger pan and zoom behavior from other components. ```playground_norender -class App extends React.Component { - - constructor(props) { - super(props); - } - - state = { - data: this.getScatterData() - } - - getScatterData() { - return range(50).map((index) => { - return { - x: random(1, 50), - y: random(10, 90), - size: random(8) + 3 - }; - }); - } +function App() { + return ( + + } + > + datum.y % 5 === 0 ? 1 : 0.7, + fill: ({ datum }) => datum.y % 5 === 0 ? "tomato" : "black" + } + }} + /> + + ); +} - render() { - return ( - } - > - datum.y % 5 === 0 ? 1 : 0.7, - fill: ({ datum }) => datum.y % 5 === 0 ? "tomato" : "black" - } - }} - /> - - ); - } +function getScatterData() { + return range(50).map((index) => { + return { + x: random(1, 50), + y: random(10, 90), + size: random(8) + 3 + }; + }); } render(); @@ -62,99 +55,94 @@ The `onBrushDomainChange` prop on `VictoryBrushContainer` alters the `zoomDomain ```playground_norender -class App extends React.Component { - - constructor() { - super(); - this.state = {}; - } +function App() { + const [state, setState] = React.useState({}); - handleZoom(domain) { - this.setState({selectedDomain: domain}); + function handleZoom(domain) { + setState({selectedDomain: domain}); } - handleBrush(domain) { - this.setState({zoomDomain: domain}); + function handleBrush(domain) { + setState({zoomDomain: domain}); } - render() { - return ( -
- - } - > - + - - - - - } - > - new Date(x).getFullYear()} + } + > + + + + + - - -
- ); - } + } + > + new Date(x).getFullYear()} + /> + + + + ); } + render(); ``` diff --git a/docs/src/content/guides/custom-charts.md b/docs/src/content/guides/custom-charts.md index 79deec60c..8c4eaf9ae 100644 --- a/docs/src/content/guides/custom-charts.md +++ b/docs/src/content/guides/custom-charts.md @@ -17,284 +17,282 @@ The following example shows how to create a chart with multiple independent axes ```playground_norender -class CustomTheme extends React.Component { - render() { - const styles = this.getStyles(); - const dataSetOne = this.getDataSetOne(); - const dataSetTwo = this.getDataSetTwo(); - const tickValues = this.getTickValues(); +function CustomTheme() { + const styles = getStyles(); + const dataSetOne = getDataSetOne(); + const dataSetTwo = getDataSetTwo(); + const tickValues = getTickValues(); - return ( - + return ( + - {/* Create stylistic elements */} - - + {/* Create stylistic elements */} + + - {/* Define labels */} - - - - + {/* Define labels */} + + + + - - {/* Add shared independent axis */} - { - if (x.getFullYear() === 2000) { - return x.getFullYear(); - } - if (x.getFullYear() % 5 === 0) { - return x.getFullYear().toString().slice(2); - } + + {/* Add shared independent axis */} + { + if (x.getFullYear() === 2000) { + return x.getFullYear(); + } + if (x.getFullYear() % 5 === 0) { + return x.getFullYear().toString().slice(2); } } - /> - - {/* - Add the dependent axis for the first data set. - Note that all components plotted against this axis will have the same y domain - */} - + } + /> - {/* Red annotation line */} - + {/* + Add the dependent axis for the first data set. + Note that all components plotted against this axis will have the same y domain + */} + - {/* dataset one */} - + {/* Red annotation line */} + - {/* - Add the dependent axis for the second data set. - Note that all components plotted against this axis will have the same y domain - */} - + {/* dataset one */} + - {/* dataset two */} - - - - ); - } + {/* + Add the dependent axis for the second data set. + Note that all components plotted against this axis will have the same y domain + */} + - getDataSetOne() { - return [ - {x: new Date(2000, 1, 1), y: 12}, - {x: new Date(2000, 6, 1), y: 10}, - {x: new Date(2000, 12, 1), y: 11}, - {x: new Date(2001, 1, 1), y: 5}, - {x: new Date(2002, 1, 1), y: 4}, - {x: new Date(2003, 1, 1), y: 6}, - {x: new Date(2004, 1, 1), y: 5}, - {x: new Date(2005, 1, 1), y: 7}, - {x: new Date(2006, 1, 1), y: 8}, - {x: new Date(2007, 1, 1), y: 9}, - {x: new Date(2008, 1, 1), y: -8.5}, - {x: new Date(2009, 1, 1), y: -9}, - {x: new Date(2010, 1, 1), y: 5}, - {x: new Date(2013, 1, 1), y: 1}, - {x: new Date(2014, 1, 1), y: 2}, - {x: new Date(2015, 1, 1), y: -5} - ]; - } + {/* dataset two */} + + + + ); +} - getDataSetTwo() { - return [ - {x: new Date(2000, 1, 1), y: 5}, - {x: new Date(2003, 1, 1), y: 6}, - {x: new Date(2004, 1, 1), y: 4}, - {x: new Date(2005, 1, 1), y: 10}, - {x: new Date(2006, 1, 1), y: 12}, - {x: new Date(2007, 2, 1), y: 48}, - {x: new Date(2008, 1, 1), y: 19}, - {x: new Date(2009, 1, 1), y: 31}, - {x: new Date(2011, 1, 1), y: 49}, - {x: new Date(2014, 1, 1), y: 40}, - {x: new Date(2015, 1, 1), y: 21} - ]; - } +function getDataSetOne() { + return [ + {x: new Date(2000, 1, 1), y: 12}, + {x: new Date(2000, 6, 1), y: 10}, + {x: new Date(2000, 12, 1), y: 11}, + {x: new Date(2001, 1, 1), y: 5}, + {x: new Date(2002, 1, 1), y: 4}, + {x: new Date(2003, 1, 1), y: 6}, + {x: new Date(2004, 1, 1), y: 5}, + {x: new Date(2005, 1, 1), y: 7}, + {x: new Date(2006, 1, 1), y: 8}, + {x: new Date(2007, 1, 1), y: 9}, + {x: new Date(2008, 1, 1), y: -8.5}, + {x: new Date(2009, 1, 1), y: -9}, + {x: new Date(2010, 1, 1), y: 5}, + {x: new Date(2013, 1, 1), y: 1}, + {x: new Date(2014, 1, 1), y: 2}, + {x: new Date(2015, 1, 1), y: -5} + ]; +} - getTickValues() { - return [ - new Date(1999, 1, 1), - new Date(2000, 1, 1), - new Date(2001, 1, 1), - new Date(2002, 1, 1), - new Date(2003, 1, 1), - new Date(2004, 1, 1), - new Date(2005, 1, 1), - new Date(2006, 1, 1), - new Date(2007, 1, 1), - new Date(2008, 1, 1), - new Date(2009, 1, 1), - new Date(2010, 1, 1), - new Date(2011, 1, 1), - new Date(2012, 1, 1), - new Date(2013, 1, 1), - new Date(2014, 1, 1), - new Date(2015, 1, 1), - new Date(2016, 1, 1) - ]; - } +function getDataSetTwo() { + return [ + {x: new Date(2000, 1, 1), y: 5}, + {x: new Date(2003, 1, 1), y: 6}, + {x: new Date(2004, 1, 1), y: 4}, + {x: new Date(2005, 1, 1), y: 10}, + {x: new Date(2006, 1, 1), y: 12}, + {x: new Date(2007, 2, 1), y: 48}, + {x: new Date(2008, 1, 1), y: 19}, + {x: new Date(2009, 1, 1), y: 31}, + {x: new Date(2011, 1, 1), y: 49}, + {x: new Date(2014, 1, 1), y: 40}, + {x: new Date(2015, 1, 1), y: 21} + ]; +} - getStyles() { - const BLUE_COLOR = "#00a3de"; - const RED_COLOR = "#7c270b"; +function getTickValues() { + return [ + new Date(1999, 1, 1), + new Date(2000, 1, 1), + new Date(2001, 1, 1), + new Date(2002, 1, 1), + new Date(2003, 1, 1), + new Date(2004, 1, 1), + new Date(2005, 1, 1), + new Date(2006, 1, 1), + new Date(2007, 1, 1), + new Date(2008, 1, 1), + new Date(2009, 1, 1), + new Date(2010, 1, 1), + new Date(2011, 1, 1), + new Date(2012, 1, 1), + new Date(2013, 1, 1), + new Date(2014, 1, 1), + new Date(2015, 1, 1), + new Date(2016, 1, 1) + ]; +} - return { - parent: { - background: "#ccdee8", - boxSizing: "border-box", - display: "inline", - padding: 0, - fontFamily: "'Fira Sans', sans-serif" - }, - title: { - textAnchor: "start", - verticalAnchor: "end", - fill: "#000000", - fontFamily: "inherit", - fontSize: "18px", - fontWeight: "bold" - }, - labelNumber: { - textAnchor: "middle", - fill: "#ffffff", - fontFamily: "inherit", - fontSize: "14px" - }, +function getStyles() { + const BLUE_COLOR = "#00a3de"; + const RED_COLOR = "#7c270b"; - // INDEPENDENT AXIS - axisYears: { - axis: { stroke: "black", strokeWidth: 1}, - ticks: { - size: ({ tick }) => { - const tickSize = - tick.getFullYear() % 5 === 0 ? 10 : 5; - return tickSize; - }, - stroke: "black", - strokeWidth: 1 - }, - tickLabels: { - fill: "black", - fontFamily: "inherit", - fontSize: 16 - } - }, + return { + parent: { + background: "#ccdee8", + boxSizing: "border-box", + display: "inline", + padding: 0, + fontFamily: "'Fira Sans', sans-serif" + }, + title: { + textAnchor: "start", + verticalAnchor: "end", + fill: "#000000", + fontFamily: "inherit", + fontSize: "18px", + fontWeight: "bold" + }, + labelNumber: { + textAnchor: "middle", + fill: "#ffffff", + fontFamily: "inherit", + fontSize: "14px" + }, - // DATA SET ONE - axisOne: { - grid: { - stroke: ({ tick }) => - tick === -10 ? "transparent" : "#ffffff", - strokeWidth: 2 + // INDEPENDENT AXIS + axisYears: { + axis: { stroke: "black", strokeWidth: 1}, + ticks: { + size: ({ tick }) => { + const tickSize = + tick.getFullYear() % 5 === 0 ? 10 : 5; + return tickSize; }, - axis: { stroke: BLUE_COLOR, strokeWidth: 0 }, - ticks: { strokeWidth: 0 }, - tickLabels: { - fill: BLUE_COLOR, - fontFamily: "inherit", - fontSize: 16 - } + stroke: "black", + strokeWidth: 1 }, - labelOne: { - fill: BLUE_COLOR, + tickLabels: { + fill: "black", fontFamily: "inherit", - fontSize: 12, - fontStyle: "italic" - }, - lineOne: { - data: { stroke: BLUE_COLOR, strokeWidth: 4.5 } + fontSize: 16 + } + }, + + // DATA SET ONE + axisOne: { + grid: { + stroke: ({ tick }) => + tick === -10 ? "transparent" : "#ffffff", + strokeWidth: 2 }, - axisOneCustomLabel: { + axis: { stroke: BLUE_COLOR, strokeWidth: 0 }, + ticks: { strokeWidth: 0 }, + tickLabels: { fill: BLUE_COLOR, fontFamily: "inherit", - fontWeight: 300, - fontSize: 21 - }, + fontSize: 16 + } + }, + labelOne: { + fill: BLUE_COLOR, + fontFamily: "inherit", + fontSize: 12, + fontStyle: "italic" + }, + lineOne: { + data: { stroke: BLUE_COLOR, strokeWidth: 4.5 } + }, + axisOneCustomLabel: { + fill: BLUE_COLOR, + fontFamily: "inherit", + fontWeight: 300, + fontSize: 21 + }, - // DATA SET TWO - axisTwo: { - axis: { stroke: RED_COLOR, strokeWidth: 0 }, - tickLabels: { - fill: RED_COLOR, - fontFamily: "inherit", - fontSize: 16 - } - }, - labelTwo: { - textAnchor: "end", + // DATA SET TWO + axisTwo: { + axis: { stroke: RED_COLOR, strokeWidth: 0 }, + tickLabels: { fill: RED_COLOR, fontFamily: "inherit", - fontSize: 12, - fontStyle: "italic" - }, - lineTwo: { - data: { stroke: RED_COLOR, strokeWidth: 4.5 } - }, - - // HORIZONTAL LINE - lineThree: { - data: { stroke: "#e95f46", strokeWidth: 2 } + fontSize: 16 } - }; - } + }, + labelTwo: { + textAnchor: "end", + fill: RED_COLOR, + fontFamily: "inherit", + fontSize: 12, + fontStyle: "italic" + }, + lineTwo: { + data: { stroke: RED_COLOR, strokeWidth: 4.5 } + }, + + // HORIZONTAL LINE + lineThree: { + data: { stroke: "#e95f46", strokeWidth: 2 } + } + }; } render(); diff --git a/docs/src/content/guides/custom-components.md b/docs/src/content/guides/custom-components.md index 8d8533328..066dadfd2 100644 --- a/docs/src/content/guides/custom-components.md +++ b/docs/src/content/guides/custom-components.md @@ -35,47 +35,44 @@ Victory components set props on their primitive components, but these may be ove Victory components may be wrapped to customize or change behavior. Wrapper components should apply any props they receive from other Victory components to the components they render. ```playground_norender -class WrapperComponent extends React.Component { - renderChildren() { - const children = React.Children.toArray(this.props.children); +function WrapperComponent(props) { + function renderChildren() { + const children = React.Children.toArray(props.children); return children.map((child) => { // children should be rendered with props from their parent Victory components assigned // Components like `VictoryChart` expect to control props like `domain` for their children // Some props should be merged rather than overridden - const style = _.merge(child.props.style, this.props.style); - return React.cloneElement(child, Object.assign({}, child.props, this.props, { style })); + const style = _.merge(child.props.style, props.style); + return React.cloneElement(child, Object.assign({}, child.props, props, { style })); }); } - render() { - return ( - - - - - { this.renderChildren() } - - ); - } + return ( + + + + + { renderChildren() } + + ); } -class App extends React.Component { - render() { - return ( - - - Math.sin(2 * Math.PI * d.x)} - samples={15} - symbol="square" - size={6} - style={{ data: { stroke: "tomato", strokeWidth: 3 }}} - /> - - - ); - } +function App() { + return ( + + + Math.sin(2 * Math.PI * d.x)} + samples={15} + symbol="square" + size={6} + style={{ data: { stroke: "tomato", strokeWidth: 3 }}} + /> + + + ); } + render(); ``` @@ -84,32 +81,29 @@ render(); Any component that renders valid svg elements (or elements wrapped in ``) may be used as a `dataComponent` or `labelComponent` in Victory components. Custom components will be provided with the same props as default components. In the following example, a custom `CatPoint` component is used in place of `Point` in `VictoryScatter`. ```playground_norender -class CatPoint extends React.Component { - render() { - const {x, y, datum} = this.props; - const cat = datum._y >= 0 ? "😻" : "😹"; - return ( - - {cat} - - ); - } +function CatPoint(props) { + const {x, y, datum} = props; + const cat = datum._y >= 0 ? "😻" : "😹"; + + return ( + + {cat} + + ); } -class App extends React.Component { - render() { - return ( - - - Math.sin(2 * Math.PI * d.x) - } - samples={25} - dataComponent={} - /> - - ); - } +function App() { + return ( + + + Math.sin(2 * Math.PI * d.x) + } + samples={25} + dataComponent={} + /> + + ); } render(); ``` @@ -123,29 +117,23 @@ const SAMPLE_DATA = [ {x: 6, y: 3} ]; -class Polygon extends React.Component { - getPoints(data, scale) { - return data.reduce((pointStr, {x, y}) => - `${pointStr} ${scale.x(x)},${scale.y(y)}` - , ''); - } +function getPoints(data, scale) { + return data.reduce((pointStr, {x, y}) => + `${pointStr} ${scale.x(x)},${scale.y(y)}` + , ''); +} - render() { - // data and style are explicitly supplied to the Polygon component - // scale is provided by VictoryChart - const { data, style, scale } = this.props; - const points = this.getPoints(data, scale); - return ; - } +function Polygon(props) { + // data and style are explicitly supplied to the Polygon component + // scale is provided by VictoryChart + const { data, style, scale } = props; + const points = getPoints(data, scale); + return ; } -class App extends React.Component { - render() { - return +function App() { + return ( + - ; - } + + ); } render(); @@ -163,67 +151,63 @@ render(); Other Victory components may even be used in creating custom components, as in the example below. ```playground_norender -class CustomPie extends React.Component { - render() { - const {datum, x, y} = this.props; - const pieWidth = 120; - - return ( - - - - ); - } +function CustomPie(props) { + const {datum, x, y} = props; + const pieWidth = 120; + + return ( + + + + ); } -class CustomDataComponent extends React.Component { - render() { - const data = [ - {x: "Jan", y: 30}, - {x: "Feb", y: 32}, - {x: "Mar", y: 65}, - {x: "Apr", y: 38}, - {x: "May", y: 50}, - {x: "Jun", y: 47}, - {x: "Jul", y: 38}, - {x: "Aug", y: 48}, - {x: "Sep", y: 80}, - {x: "Oct", y: 73}, - {x: "Nov", y: 76}, - {x: "Dec", y: 100} - ]; +function CustomDataComponent() { + const data = [ + {x: "Jan", y: 30}, + {x: "Feb", y: 32}, + {x: "Mar", y: 65}, + {x: "Apr", y: 38}, + {x: "May", y: 50}, + {x: "Jun", y: 47}, + {x: "Jul", y: 38}, + {x: "Aug", y: 48}, + {x: "Sep", y: 80}, + {x: "Oct", y: 73}, + {x: "Nov", y: 76}, + {x: "Dec", y: 100} + ]; - const pieData = data.map((datum) => { - datum.pie = [ - {x: "Lions", y: Math.round(Math.random() * 10)}, - {x: "Tigers", y: Math.round(Math.random() * 10)}, - {x: "Bears", y: Math.round(Math.random() * 10)} - ]; - return datum; - }); + const pieData = data.map((datum) => { + datum.pie = [ + {x: "Lions", y: Math.round(Math.random() * 10)}, + {x: "Tigers", y: Math.round(Math.random() * 10)}, + {x: "Bears", y: Math.round(Math.random() * 10)} + ]; + return datum; + }); - return ( - - - - - } - /> - - - ); - } + return ( + + + + + } + /> + + + ); } render(); diff --git a/docs/src/content/guides/events.md b/docs/src/content/guides/events.md index 388a91caf..d5652e54b 100644 --- a/docs/src/content/guides/events.md +++ b/docs/src/content/guides/events.md @@ -183,78 +183,74 @@ externalEventMutations: PropTypes.arrayOf( The `target`, `eventKey`, and `childName` (when applicable) must always be specified. The `mutation` function will be called with the current props of the element specified by the `target`, `eventKey` and `childName` provided. The mutation function should return a mutation object for that element. The `callback` prop should be used to clear the `externalEventMutations` prop once the mutation has been applied. Clearing `externalEventMutations` is crucial for charts that animate. ```playground_norender -class App extends React.Component { - constructor() { - super(); - this.state = { - externalMutations: undefined - }; - } +function App() { + const [state, setState] = React.useState({ + externalMutations: undefined + }); - removeMutation() { - this.setState({ + function removeMutation() { + setState({ externalMutations: undefined }); } - clearClicks() { - this.setState({ + function clearClicks() { + setState({ externalMutations: [ { childName: "Bar-1", target: ["data"], eventKey: "all", mutation: () => ({ style: undefined }), - callback: this.removeMutation.bind(this) + callback: removeMutation } ] }); } - render() { - const buttonStyle = { - backgroundColor: "black", - color: "white", - padding: "10px", - marginTop: "10px" - }; - return ( -
- - ({ - target: "data", - mutation: () => ({ style: { fill: "orange" } }) - }) - } + const buttonStyle = { + backgroundColor: "black", + color: "white", + padding: "10px", + marginTop: "10px" + }; + + return ( +
+ + ({ + target: "data", + mutation: () => ({ style: { fill: "orange" } }) + }) } + } + ]} + > + "click me!"} + data={[ + { x: 1, y: 2 }, + { x: 2, y: 4 }, + { x: 3, y: 1 }, + { x: 4, y: 5 } ]} - > - "click me!"} - data={[ - { x: 1, y: 2 }, - { x: 2, y: 4 }, - { x: 3, y: 1 }, - { x: 4, y: 5 } - ]} - /> - -
- ) - } + /> +
+
+ ) } render(); @@ -267,22 +263,22 @@ _Note_ External mutations are applied to the same state object that is used to c For simple events, it may be desirable to bypass Victory's event system. To do so, specify `events` props directly on primitive components rather than using the `events` prop on Victory components. The simple `events` prop should be given as an object whose properties are event names like `onClick`, and whose values are event handlers. Events specified this way will only be called with the standard event objects. ```playground - alert(`(${evt.clientX}, ${evt.clientY})`) - }} - /> - } - /> + alert(`(${evt.clientX}, ${evt.clientY})`) + }} + /> + } +/> ``` ## Events on Custom Components diff --git a/docs/src/content/guides/tooltips.md b/docs/src/content/guides/tooltips.md index fd0498692..07f8df149 100644 --- a/docs/src/content/guides/tooltips.md +++ b/docs/src/content/guides/tooltips.md @@ -74,47 +74,43 @@ Tooltips can be customized directly on the `VictoryTooltip` component `VictoryTooltip` is composed of [`VictoryLabel`][] and the primitive [`Flyout`][] component. Both of these components are highly configurable, but may also be replaced if necessary. ```playground_norender -class CustomFlyout extends React.Component { - render() { - const {x, y, orientation} = this.props; - const newY = orientation === "bottom" ? y - 35 : y + 35; - return ( - - - - - - ); - } +function CustomFlyout(props) { + const { x, y, orientation } = props; + const newY = orientation === "bottom" ? y - 35 : y + 35; + return ( + + + + + + ); } -class App extends React.Component { - render() { - return ( - - } - /> - } - data={[ - {x: 2, y: 5, label: "A"}, - {x: 4, y: -6, label: "B"}, - {x: 6, y: 4, label: "C"}, - {x: 8, y: -5, label: "D"}, - {x: 10, y: 7, label: "E"} - ]} - style={{ - data: {fill: "tomato", width: 20}, - labels: { fill: "tomato"} - }} - /> - - ); - } +function App() { + return ( + + } + /> + } + data={[ + {x: 2, y: 5, label: "A"}, + {x: 4, y: -6, label: "B"}, + {x: 6, y: 4, label: "C"}, + {x: 8, y: -5, label: "D"}, + {x: 10, y: 7, label: "E"} + ]} + style={{ + data: {fill: "tomato", width: 20}, + labels: { fill: "tomato"} + }} + /> + + ); } render(); ``` @@ -344,41 +340,37 @@ The events that control `VictoryTooltip` are stored on the static `defaultEvents ```playground_norender -class CustomTooltip extends React.Component { - static defaultEvents = VictoryTooltip.defaultEvents - render() { - const {x, y} = this.props; - const rotation = `rotate(45 ${x} ${y})` - return ( - - - - ); - } +function CustomTooltip(props) { + const { x, y } = props; + const rotation = `rotate(45 ${x} ${y})` + return ( + + + + ); } - -class App extends React.Component { - render() { - return ( - - } - data={[ - {x: 2, y: 5, label: "A"}, - {x: 4, y: -6, label: "B"}, - {x: 6, y: 4, label: "C"}, - {x: 8, y: -5, label: "D"}, - {x: 10, y: 7, label: "E"} - ]} - style={{ - data: {fill: "tomato", width: 20} - }} - /> - - ); - } +CustomTooltip.defaultEvents = VictoryTooltip.defaultEvents; + +function App() { + return ( + + } + data={[ + {x: 2, y: 5, label: "A"}, + {x: 4, y: -6, label: "B"}, + {x: 6, y: 4, label: "C"}, + {x: 8, y: -5, label: "D"}, + {x: 10, y: 7, label: "E"} + ]} + style={{ + data: {fill: "tomato", width: 20} + }} + /> + + ); } render(); ``` diff --git a/docs/src/content/guides/zoom-large-data.md b/docs/src/content/guides/zoom-large-data.md index 6c83bc8a1..d8fcc2f47 100644 --- a/docs/src/content/guides/zoom-large-data.md +++ b/docs/src/content/guides/zoom-large-data.md @@ -27,17 +27,15 @@ In this guide, we'll be working with time-series data. We'll make a few basic as These just serve to simplify the example. We'll start with a simple chart: ```js -class CustomChart extends React.Component { - render() { - return ( - } - - - ); - } +function CustomChart(props) { + const [state, setState] = React.useState({}); + + return ( + } + + + ); } - -render(); ``` ## Render only visible points @@ -49,10 +47,10 @@ To do this, we must keep track of the chart's visible domain; ```js } > - + ``` @@ -60,7 +58,7 @@ Update the state every time the domain changes (note that we are only keeping tr ```js onDomainChange(domain) { - this.setState({ + setState({ zoomedXDomain: domain.x, }); } @@ -70,9 +68,9 @@ Use this `zoomedXDomain` state to filter out all data that isn't currently visib Here we're making a simple `getData` method; note the data array's `filter` function: ```js -getData() { - const { zoomedXDomain } = this.state; - const { data } = this.props; +function getData() { + const { zoomedXDomain } = state; + const { data } = props; return data.filter( // is d "between" the ends of the visible x-domain? (d) => (d.x >= zoomedXDomain[0] && d.x <= zoomedXDomain[1])); @@ -88,7 +86,7 @@ In fact, there would be no way to zoom back out! To remedy this, we must calculate the domain of the entire dataset: ```js -getEntireDomain(props) { +function getEntireDomain(props) { const { data } = props; return { y: [_.minBy(data, d => d.y).y, _.maxBy(data, d => d.y).y], @@ -104,25 +102,23 @@ Because we are assuming the data is static we just need to call this function on The calculated `x` domain will also be used as the initial value for `state.zoomedXDomain`: ```js -constructor(props) { - super(); - this.entireDomain = this.getEntireDomain(props); - this.state = { - zoomedXDomain: this.entireDomain.x, - }; -} +const entireDomain = getEntireDomain(props); +const [state, setState] = React.useState({ + zoomedXDomain: entireDomain.x, +}); ``` -The static value `this.entireDomain` can then be used by `VictoryChart`: +The static value `entireDomain` can then be used by `VictoryChart`: + ```js } > - + ``` @@ -142,9 +138,9 @@ All of the existing logic in `getData` will stay and another `filter` will be added afterwards: ```js -getData() { - const { zoomedXDomain } = this.state; - const { data, maxPoints } = this.props; +function getData() { + const { zoomedXDomain } = state; + const { data, maxPoints } = props; const filtered = data.filter( (d) => (d.x >= zoomedXDomain[0] && d.x <= zoomedXDomain[1])); @@ -165,28 +161,36 @@ no matter the zoom level. ## Demo ```playground_norender -// 10000 points (10 / 0.001 = 10000) - see what happens when you render 50k or 100k +// 10000 points (10 / 0.001 = 10000) +// see what happens when you render 50k or 100k const allData = _.range(0, 10, 0.001).map(x => ({ x: x, y: Math.sin(Math.PI*x/2) * x / 10 })); -class CustomChart extends React.Component { - constructor(props) { - super(); - this.entireDomain = this.getEntireDomain(props); - this.state = { - zoomedXDomain: this.entireDomain.x, - }; - } - onDomainChange(domain) { - this.setState({ +function getEntireDomain(props) { + const { data } = props; + return { + y: [_.minBy(data, d => d.y).y, _.maxBy(data, d => d.y).y], + x: [ data[0].x, _.last(data).x ] + }; +} + +function CustomChart(props) { + const entireDomain = getEntireDomain(props); + const [state, setState] = React.useState({ + zoomedXDomain: entireDomain.x, + }); + + function onDomainChange(domain) { + setState({ zoomedXDomain: domain.x, }); } - getData() { - const { zoomedXDomain } = this.state; - const { data, maxPoints } = this.props; + + function getData() { + const { zoomedXDomain } = state; + const { data, maxPoints } = props; const filtered = data.filter( (d) => (d.x >= zoomedXDomain[0] && d.x <= zoomedXDomain[1])); @@ -198,39 +202,32 @@ class CustomChart extends React.Component { } return filtered; } - getEntireDomain(props) { - const { data } = props; - return { - y: [_.minBy(data, d => d.y).y, _.maxBy(data, d => d.y).y], - x: [ data[0].x, _.last(data).x ] - }; - } - getZoomFactor() { - const { zoomedXDomain } = this.state; + + function getZoomFactor() { + const { zoomedXDomain } = state; const factor = 10 / (zoomedXDomain[1] - zoomedXDomain[0]); return _.round(factor, factor < 3 ? 1 : 0); } - render() { - const renderedData = this.getData(); - return ( -
- } - > - - -
- {this.getZoomFactor()}x zoom; - rendering {renderedData.length} of {this.props.data.length} -
+ + const renderedData = getData(); + return ( +
+ } + > + + +
+ {getZoomFactor()}x zoom; + rendering {renderedData.length} of {props.data.length}
- ); - } +
+ ); } render(); @@ -247,24 +244,24 @@ This apparent movement of the points while zooming happens because different poi Here is an example that reduces flicker by reliably choosing the same data points to display: ```js -getData() { - const { zoomedXDomain } = this.state; - const { data, maxPoints } = this.props; - - const startIndex = data.findIndex((d) => d.x >= zoomedXDomain[0]); - const endIndex = data.findIndex((d) => d.x > zoomedXDomain[1]); - const filtered = data.slice(startIndex, endIndex); - - if (filtered.length > maxPoints ) { - // limit k to powers of 2, e.g. 64, 128, 256 - // so that the same points will be chosen reliably, reducing flicker - const k = Math.pow(2, Math.ceil(Math.log2(filtered.length / maxPoints))); - return filtered.filter( - // ensure modulo is always calculated from same reference: i + startIndex - (d, i) => (((i + startIndex) % k) === 0) - ); - } - return filtered; +function getData() { + const { zoomedXDomain } = state; + const { data, maxPoints } = props; + + const startIndex = data.findIndex((d) => d.x >= zoomedXDomain[0]); + const endIndex = data.findIndex((d) => d.x > zoomedXDomain[1]); + const filtered = data.slice(startIndex, endIndex); + + if (filtered.length > maxPoints ) { + // limit k to powers of 2, e.g. 64, 128, 256 + // so that the same points will be chosen reliably, reducing flicker + const k = Math.pow(2, Math.ceil(Math.log2(filtered.length / maxPoints))); + return filtered.filter( + // ensure modulo is always calculated from same reference: i + startIndex + (d, i) => (((i + startIndex) % k) === 0) + ); + } + return filtered; } ``` diff --git a/docs/src/content/introduction/native.md b/docs/src/content/introduction/native.md index ebde86770..3345f6fdd 100644 --- a/docs/src/content/introduction/native.md +++ b/docs/src/content/introduction/native.md @@ -62,16 +62,14 @@ const data = [ { quarter: 4, earnings: 19000 } ]; -export default class App extends React.Component { - render() { - return ( - - - - - - ); - } +export default function App() { + return ( + + + + + + ); } const styles = StyleSheet.create({ From bb43668a56f25dc82448e34433bb81b0bd458a29 Mon Sep 17 00:00:00 2001 From: Charlie Brown Date: Tue, 14 May 2024 13:16:36 -0500 Subject: [PATCH 2/2] Kick CI