From 713eaf3214f32b2abf3eb54499c2e5f116b458d5 Mon Sep 17 00:00:00 2001 From: Charlie Brown Date: Wed, 20 Nov 2024 15:27:05 -0600 Subject: [PATCH] Migrate container demos to docs (#2983) --- demo/ts/app.tsx | 28 - demo/ts/components/create-container-demo.tsx | 287 ------- demo/ts/components/selection-demo.tsx | 391 ---------- .../victory-brush-container-demo.tsx | 369 --------- .../victory-cursor-container-demo.tsx | 274 ------- .../victory-voronoi-container-demo.tsx | 702 ----------------- .../victory-zoom-container-demo.tsx | 571 -------------- website/docs/api/victory-brush-container.mdx | 6 +- website/docs/api/victory-container.mdx | 2 +- website/docs/api/victory-cursor-container.mdx | 2 +- website/docs/charts/bar.mdx | 2 +- website/docs/charts/candlestick.mdx | 2 +- website/docs/guides/brush-and-zoom.mdx | 166 ---- website/docs/guides/containers.mdx | 229 +----- website/docs/guides/data-selection.mdx | 412 ++++++++++ website/docs/guides/pan-and-zoom.mdx | 276 +++++++ website/docs/guides/tooltips.mdx | 738 ++++++++++++------ 17 files changed, 1228 insertions(+), 3229 deletions(-) delete mode 100644 demo/ts/components/create-container-demo.tsx delete mode 100644 demo/ts/components/selection-demo.tsx delete mode 100644 demo/ts/components/victory-brush-container-demo.tsx delete mode 100644 demo/ts/components/victory-cursor-container-demo.tsx delete mode 100644 demo/ts/components/victory-voronoi-container-demo.tsx delete mode 100644 demo/ts/components/victory-zoom-container-demo.tsx delete mode 100644 website/docs/guides/brush-and-zoom.mdx create mode 100644 website/docs/guides/data-selection.mdx create mode 100644 website/docs/guides/pan-and-zoom.mdx diff --git a/demo/ts/app.tsx b/demo/ts/app.tsx index 8dcf6cf6f..de643e125 100644 --- a/demo/ts/app.tsx +++ b/demo/ts/app.tsx @@ -2,15 +2,9 @@ import * as React from "react"; import * as ReactDOM from "react-dom"; import AccessibilityDemo from "./components/accessibility-demo"; -import BrushContainerDemo from "./components/victory-brush-container-demo"; import BrushLineDemo from "./components/victory-brush-line-demo"; -import CreateContainerDemo from "./components/create-container-demo"; -import CursorContainerDemo from "./components/victory-cursor-container-demo"; import DraggableDemo from "./components/draggable-demo"; -import SelectionDemo from "./components/selection-demo"; import StackedThemeDemos from "./components/stacked-theme-demo"; -import VoronoiContainerDemo from "./components/victory-voronoi-container-demo"; -import ZoomContainerDemo from "./components/victory-zoom-container-demo"; import ThemeBuilder from "./components/theme-builder"; const DEMO_ROUTES = { @@ -18,34 +12,12 @@ const DEMO_ROUTES = { component: AccessibilityDemo, name: "AccessibilityDemo", }, - "/demo/brush-container": { - component: BrushContainerDemo, - name: "BrushContainerDemo", - }, "/demo/brush-line": { component: BrushLineDemo, name: "BrushLineDemo" }, - - "/demo/create-container": { - component: CreateContainerDemo, - name: "CreateContainerDemo", - }, - "/demo/cursor-container": { - component: CursorContainerDemo, - name: "CursorContainerDemo", - }, "/demo/draggable": { component: DraggableDemo, name: "DraggableDemo" }, - "/demo/selection": { component: SelectionDemo, name: "SelectionDemo" }, "/demo/stacked-theme": { component: StackedThemeDemos, name: "StackedThemeDemos", }, - "/demo/voronoi-container": { - component: VoronoiContainerDemo, - name: "VoronoiContainerDemo", - }, - "/demo/zoom-container": { - component: ZoomContainerDemo, - name: "ZoomContainerDemo", - }, }; class Home extends React.Component { diff --git a/demo/ts/components/create-container-demo.tsx b/demo/ts/components/create-container-demo.tsx deleted file mode 100644 index ca7bb7018..000000000 --- a/demo/ts/components/create-container-demo.tsx +++ /dev/null @@ -1,287 +0,0 @@ -import React from "react"; -import round from "lodash/round"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { ContainerType, createContainer } from "victory-create-container"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictoryTooltip } from "victory-tooltip"; -import { VictoryLegend } from "victory-legend"; -import { VictoryTheme } from "victory-core"; - -const themeColors = VictoryTheme.clean.palette?.colors || {}; - -const Charts = ({ - behaviors, -}: { - behaviors: [ContainerType, ContainerType]; -}) => { - const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", - }; - const chartStyle = { - parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" }, - }; - const CustomContainer = createContainer(behaviors[0], behaviors[1]); - const behaviorsList = behaviors.map((behavior) => `"${behavior}"`).join(", "); - - return ( -
-
{`VictoryCreateContainer(${behaviorsList})`}
-
- {/* A */} - `y: ${datum.y}`} - labelComponent={ - - } - /> - } - > - - (active ? 4 : 2), - }, - labels: { fill: themeColors.red }, - }} - /> - - (active ? 4 : 2), - }, - labels: { fill: themeColors.orange }, - }} - /> - - (active ? 4 : 2), - }, - labels: { fill: themeColors.yellow }, - }} - /> - - {/* B */} - round(datum.x, 2).toString()} - cursorLabel={({ datum }) => round(datum.x, 2)} - selectionStyle={{ - stroke: themeColors.red, - strokeWidth: 2, - fill: themeColors.red, - fillOpacity: 0.1, - }} - defaultCursorValue={0.99} - /> - } - > - - active - ? themeColors.red || "tomato" - : themeColors.yellow || "yellow", - }, - }} - size={({ active }) => (active ? 5 : 3)} - y={(d) => d.x * d.x} - /> - - {/* C */} - } - > - - (active ? 5 : 3)} - labels={({ datum }) => datum.y} - labelComponent={ - - } - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 0 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - labels={({ datum }) => 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 }, - ]} - /> - datum.y} - labelComponent={ - - } - size={({ active }) => (active ? 5 : 3)} - /> - - - {/* D */} - } - > - (active ? 10 : 8)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 10 : 8)} - 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 ? 10 : 8)} - data={[ - { x: 1, y: 5 }, - { x: 2, y: -4 }, - { x: 3, y: -2 }, - { x: 4, y: -3 }, - { x: 5, y: -1 }, - { x: 6, y: 3 }, - { x: 7, y: -3 }, - ]} - /> - -
-
- ); -}; - -class App extends React.Component { - render() { - return ( -
- - - - -
- ); - } -} - -export default App; diff --git a/demo/ts/components/selection-demo.tsx b/demo/ts/components/selection-demo.tsx deleted file mode 100644 index 22abdf5d8..000000000 --- a/demo/ts/components/selection-demo.tsx +++ /dev/null @@ -1,391 +0,0 @@ -import React from "react"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictorySelectionContainer } from "victory-selection-container"; -import { VictoryLegend } from "victory-legend"; -import { VictoryTooltip } from "victory-tooltip"; -import { VictoryStyleInterface, VictoryTheme } from "victory-core"; - -const themeColors = VictoryTheme.clean.palette?.colors || {}; -const { - red = "red", - green = "green", - blue = "blue", - pink = "pink", - cyan = "cyan", - purple = "purple", - orange = "orange", - yellow = "gold", -} = themeColors; - -const activeStrokeStyle: VictoryStyleInterface = { - data: { - stroke: ({ active }) => (active ? purple : "none"), - strokeWidth: 2, - }, -}; -interface SelectionDemoState { - points: { x: number; y: number }[]; -} - -interface DataSet { - data?: { x: number; y: number }[]; -} - -export default class SelectionDemo extends React.Component< - any, - SelectionDemoState -> { - constructor(props: any) { - super(props); - this.state = { - points: [], - }; - } - - handleSelection(datasets: DataSet[]) { - const points = datasets.reduce( - (memo: any, dataset: DataSet) => memo.concat(dataset.data), - [], - ); - this.setState({ points }); - } - - handleClearSelection() { - this.setState({ points: [] }); - } - - listData() { - const points = this.state.points.map( - (point: { x: number; y: number }, index: number) => { - return
  • {`${point.x}, ${point.y}`}
  • ; - }, - ); - - return ( -
    -

    Points

    - -
    - ); - } - - render() { - const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", - }; - - const chartStyle = { - parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" }, - }; - - return ( -
    -
    - {this.listData()} - - } - > - - - - - - - } - > - - - (active ? cyan : yellow) }, - }} - labels={({ datum }) => datum.y} - labelComponent={} - /> - - - - - (active ? green : yellow) }, - }} - labels={({ datum }) => datum.y} - labelComponent={} - /> - - - - - (active ? pink : yellow) }, - }} - labels={({ datum }) => datum.y} - labelComponent={} - /> - - - - (active ? red : yellow), - }, - }} - containerComponent={ - - } - size={({ active }) => (active ? 5 : 3)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - - (active ? pink : purple), - }, - }} - containerComponent={ - - } - y={(d) => d.x * d.x} - /> - - - } - > - (active ? 5 : 3)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - 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 ? 5 : 3)} - /> - - - - } - > - - - - - - -
    -
    - ); - } -} diff --git a/demo/ts/components/victory-brush-container-demo.tsx b/demo/ts/components/victory-brush-container-demo.tsx deleted file mode 100644 index e7b1e2f36..000000000 --- a/demo/ts/components/victory-brush-container-demo.tsx +++ /dev/null @@ -1,369 +0,0 @@ -import React from "react"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { VictoryAxis } from "victory-axis"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictoryLegend } from "victory-legend"; -import { VictoryZoomContainer } from "victory-zoom-container"; -import { VictoryBrushContainer } from "victory-brush-container"; -import { DomainTuple, VictoryTheme } from "victory-core"; - -const themeColors = VictoryTheme.clean.palette?.colors || {}; - -interface VictoryBrushContainerDemoState { - zoomDomain: { - x?: DomainTuple; - y?: DomainTuple; - }; -} - -export default class VictoryBrushContainerDemo extends React.Component< - any, - VictoryBrushContainerDemoState -> { - constructor(props: any) { - super(props); - } - - handleZoom(domain: { x?: DomainTuple; y?: DomainTuple }) { - this.setState({ zoomDomain: domain }); - } - - render() { - const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", - }; - - const chartStyle = { - parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" }, - }; - - return ( -
    -
    - - } - > - - - - } - > - new Date(x).getFullYear()} - /> - - - - - } - > - - - - - - - - - active - ? themeColors.blue || "blue" - : themeColors.orange || "orange", - }, - }} - domain={{ x: [0, 10], y: [-5, 5] }} - containerComponent={ - - } - size={({ active }) => (active ? 5 : 3)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - - - active - ? themeColors.blue || "blue" - : themeColors.orange || "orange", - }, - }} - containerComponent={ - - } - size={({ active }) => (active ? 5 : 3)} - y={(d) => d.x * d.x} - /> - - - } - > - (active ? 5 : 3)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - 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 ? 5 : 3)} - /> - - - } - > - (active ? 5 : 3)} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - 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 }, - ]} - /> - - - - } - handleWidth={1} - handleStyle={{ - stroke: themeColors.cyan, - }} - /> - } - data={[ - { x: 1, y: -3 }, - { x: 2, y: 5 }, - { x: 3, y: -3 }, - { x: 4, y: 0 }, - { x: 5, y: -5 }, - { x: 6, y: 2 }, - { x: 7, y: 0 }, - ]} - /> -
    -
    - ); - } -} diff --git a/demo/ts/components/victory-cursor-container-demo.tsx b/demo/ts/components/victory-cursor-container-demo.tsx deleted file mode 100644 index 07d01f94e..000000000 --- a/demo/ts/components/victory-cursor-container-demo.tsx +++ /dev/null @@ -1,274 +0,0 @@ -import React from "react"; -import { random, range, round } from "lodash"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictoryCursorContainer } from "victory-cursor-container"; -import { VictoryTooltip } from "victory-tooltip"; -import { VictoryTheme, CoordinatesPropType } from "victory-core"; - -const themeColors = VictoryTheme.clean.palette?.colors || {}; - -interface VictoryCursorContainerStateInterface { - data: { a: number; b: number }[]; - cursorValue: CoordinatesPropType; - bigData: CoordinatesPropType[]; -} - -const makeData = () => - range(1500).map((x) => ({ x, y: x + 10 * Math.random() })); - -class App extends React.Component { - defaultCursorValue?: CoordinatesPropType = undefined; - setStateInterval?: number = undefined; - - constructor(props: any) { - super(props); - this.defaultCursorValue = { x: 2.25, y: 1.75 }; - - this.state = { - data: this.getData(), - cursorValue: this.defaultCursorValue, - bigData: makeData(), - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - data: this.getData(), - }); - }, 3000); - } - - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } - - getData() { - const bars = random(6, 10); - return range(bars).map((bar) => { - return { a: bar + 1, b: random(2, 10) }; - }); - } - - render() { - const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", - }; - - const chartStyle = { - parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" }, - }; - - return ( -
    -
    - round(datum.x, 2)} - /> - } - > - - - - round(datum.x, 2)} - cursorDimension="y" - defaultCursorValue={3} - /> - } - > - - - - - - - - - - - active - ? themeColors.teal || "teal" - : themeColors.purple || "purple", - }, - }} - containerComponent={ - round(datum.x, 2)} - defaultCursorValue={1} - /> - } - size={({ active }) => (active ? 5 : 3)} - data={this.state.data} - x="a" - y="b" - /> - - } - size={({ active }) => (active ? 5 : 3)} - y={(d) => d.x * d.x} - /> - - round(datum.x, 2)} - cursorLabelOffset={15} - /> - } - > - - (active ? 5 : 3)} - labels={({ datum }) => datum.y} - labelComponent={} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 0 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - labels={({ datum }) => 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 }, - ]} - /> - d.y} - labelComponent={} - size={({ active }) => (active ? 5 : 3)} - /> - - - - } - > - - - - -
    -
    - ); - } -} - -export default App; diff --git a/demo/ts/components/victory-voronoi-container-demo.tsx b/demo/ts/components/victory-voronoi-container-demo.tsx deleted file mode 100644 index 96114e7ef..000000000 --- a/demo/ts/components/victory-voronoi-container-demo.tsx +++ /dev/null @@ -1,702 +0,0 @@ -import React from "react"; -import { random, range } from "lodash"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictoryVoronoiContainer } from "victory-voronoi-container"; -import { Flyout, VictoryTooltip } from "victory-tooltip"; -import { VictoryLegend } from "victory-legend"; -import { VictoryLabel, VictoryTheme } from "victory-core"; - -interface VictoryVoronoiContainerDemoState { - data: { - a: number; - b: number; - }[]; -} - -const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", -}; - -const chartStyle: { [key: string]: React.CSSProperties } = { - parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" }, -}; - -const themeColors = VictoryTheme.clean.palette?.colors || {}; -const { - red = "red", - green = "green", - blue = "blue", - purple = "purple", - orange = "orange", - yellow = "yellow", -} = themeColors; - -export default class VictoryVoronoiContainerDemo extends React.Component< - any, - VictoryVoronoiContainerDemoState -> { - setStateInterval?: number = undefined; - - constructor(props: any) { - super(props); - this.state = { - data: this.getData(), - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - data: this.getData(), - }); - }, 3000); - } - - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } - - getData() { - const bars = random(6, 10); - return range(bars).map((bar) => { - return { a: bar + 1, b: random(2, 10) }; - }); - } - - render() { - const dy = 13; - const CustomLabel = (props: any) => { - const x = - props.x - 2 - 4 * Math.max(...props.text.map((t: string) => t.length)); - const startY = 2 + props.y - (props.text.length * dy) / 2; - return ( - - {props.activePoints.map((pt: any, idx: number) => { - return ( - - ); - })} - - - ); - }; - - const CustomFlyout = (props: any) => { - return ; - }; - - return ( -
    -
    - `y: ${datum.y}`} - labelComponent={} - /> - } - > - - - - - `I'm kind of a long label ${datum.y}`} - mouseFollowTooltips - labelComponent={} - /> - } - > - (active ? 8 : 3)} - /> - (active ? 5 : 3)} - /> - - - datum.y} /> - } - > - (active ? 5 : 3)} - /> - - - `y:${datum.y}`} - labelComponent={ - - } - /> - } - > - (active ? 4 : 2), - }, - labels: { fill: red }, - }} - /> - - (active ? 4 : 2), - }, - labels: { fill: blue }, - }} - /> - - (active ? 4 : 2), - }, - labels: { fill: yellow }, - }} - /> - - - (active ? red : yellow), - }, - }} - containerComponent={ - datum._x} - labelComponent={} - /> - } - size={({ active }) => (active ? 5 : 3)} - data={this.state.data} - x="a" - y="b" - /> - - - } - > - d.x * d.x} /> - (active ? red : yellow), - }, - }} - size={({ active }) => (active ? 5 : 3)} - y={(d) => d.x * d.x} - /> - - - datum.y} - labelComponent={} - /> - } - > - - (active ? 8 : 3)} - /> - - - - (active ? 5 : 3)} - /> - - - - (active ? 5 : 3)} /> - - - - - - } - > - - - (active ? 5 : 3)} - labels={({ datum }) => datum.y} - labelComponent={} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 0 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? 5 : 3)} - labels={({ datum }) => 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 }, - ]} - /> - datum.y} - labelComponent={} - size={({ active }) => (active ? 5 : 3)} - /> - - - - - } - > - - (active ? yellow : "none"), - strokeWidth: 2, - }, - }} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? yellow : "none"), - strokeWidth: 2, - }, - }} - 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 ? yellow : "none"), - strokeWidth: 2, - }, - }} - data={[ - { x: 1, y: 5 }, - { x: 2, y: -4 }, - { x: 3, y: -2 }, - { x: 4, y: -3 }, - { x: 5, y: -1 }, - { x: 6, y: 3 }, - { x: 7, y: -3 }, - ]} - /> - - - - } - > - (active ? yellow : "none"), - strokeWidth: 2, - }, - }} - data={[ - { x: 1, y: -5 }, - { x: 2, y: 4 }, - { x: 3, y: 2 }, - { x: 4, y: 3 }, - { x: 5, y: 1 }, - { x: 6, y: -3 }, - { x: 7, y: 3 }, - ]} - /> - (active ? yellow : "none"), - strokeWidth: 2, - }, - }} - 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 ? yellow : "none"), - strokeWidth: 2, - }, - }} - data={[ - { x: 1, y: 5 }, - { x: 2, y: -4 }, - { x: 3, y: -2 }, - { x: 4, y: -3 }, - { x: 5, y: -1 }, - { x: 6, y: 3 }, - { x: 7, y: -3 }, - ]} - /> - - - `${datum.l}: ${datum.y}`} - labelComponent={ - } - labelComponent={} - /> - } - /> - } - > - (active ? 4 : 2), - }, - }} - /> - - (active ? 4 : 2), - }, - }} - /> - - - `y: ${datum.y}`} - labelComponent={} - voronoiPadding={{ - bottom: 50, - left: 50, - right: 50, - top: 100, - }} - /> - } - > - - - - -
    -
    - ); - } -} diff --git a/demo/ts/components/victory-zoom-container-demo.tsx b/demo/ts/components/victory-zoom-container-demo.tsx deleted file mode 100644 index d6172a62f..000000000 --- a/demo/ts/components/victory-zoom-container-demo.tsx +++ /dev/null @@ -1,571 +0,0 @@ -import React from "react"; -import { range, random, minBy, maxBy, last } from "lodash"; -import { VictoryChart } from "victory-chart"; -import { VictoryStack } from "victory-stack"; -import { VictoryGroup } from "victory-group"; -import { VictoryAxis } from "victory-axis"; -import { VictoryArea } from "victory-area"; -import { VictoryBar } from "victory-bar"; -import { VictoryLine } from "victory-line"; -import { VictoryScatter } from "victory-scatter"; -import { VictoryZoomContainer } from "victory-zoom-container"; -import { VictoryTooltip } from "victory-tooltip"; -import { VictoryLegend } from "victory-legend"; -import { - CoordinatesPropType, - DomainTuple, - VictoryClipContainer, - VictoryPortal, - VictoryTheme, -} from "victory-core"; - -const allData = range(0, 10, 0.001).map((x) => ({ - x, - y: (Math.sin((Math.PI * x) / 2) * x) / 10, -})); - -const themeColors = VictoryTheme.clean.palette?.colors || {}; -const { - red = "red", - orange = "orange", - yellow = "yellow", - blue = "blue", - teal = "teal", - pink = "pink", - purple = "purple", - green = "green", -} = themeColors; - -interface CustomChartState { - zoomedXDomain: DomainTuple; -} - -class CustomChart extends React.Component { - entireDomain: { x: DomainTuple; y: DomainTuple }; - - constructor(props: any) { - super(props); - - this.entireDomain = this.getEntireDomain(props); - - this.state = { - zoomedXDomain: this.entireDomain.x, - }; - } - - onDomainChange(domain: { x: DomainTuple; y: DomainTuple }) { - this.setState({ - zoomedXDomain: domain.x, - }); - } - - getData() { - const { zoomedXDomain } = this.state; - const { data, maxPoints } = this.props; - const filtered = data.filter( - (d: { x: number }) => d.x >= zoomedXDomain[0] && d.x <= zoomedXDomain[1], - ); - - if (filtered.length > maxPoints) { - const k = Math.ceil(filtered.length / maxPoints); - return filtered.filter((d: { x: number[] }, i: number) => i % k === 0); - } - return filtered; - } - - getEntireDomain(props: { data: CoordinatesPropType[] }) { - const { data }: { data: CoordinatesPropType[] } = props; - - const minPoint = minBy(data, (d: CoordinatesPropType) => d.y); - const yMin = minPoint ? minPoint.y : 0; - - const maxPoint = maxBy(data, (d: CoordinatesPropType) => d.y); - const yMax = maxPoint ? maxPoint.y : 0; - - const lastPoint = last(data); - const xLast = lastPoint ? lastPoint.x : 0; - - const yArr: DomainTuple = [yMin, yMax]; - const xArr: DomainTuple = [data[0].x, xLast]; - - return { - x: xArr, - y: yArr, - }; - } - - render() { - const renderedData = this.getData(); - return ( - - } - > - - - ); - } -} - -interface VictoryZoomContainerDemoState { - arrayData: number[][]; - barData: { - x: number; - y: number; - }[]; - data: { - a: number; - b: number; - }[]; - transitionData: { - x: number; - y: number; - }[]; - zoomDomain: { - x?: DomainTuple; - y?: DomainTuple; - }; -} - -const parentStyle: React.CSSProperties = { - border: "1px solid #ccc", - margin: "2%", - maxWidth: "40%", -}; - -const containerStyle: React.CSSProperties = { - display: "flex", - flexDirection: "row", - flexWrap: "wrap", - alignItems: "center", - justifyContent: "center", -}; - -const makeData = () => range(-50, 75).map((i) => ({ x: i, y: Math.random() })); - -export default class VictoryZoomContainerDemo extends React.Component< - any, - VictoryZoomContainerDemoState -> { - setStateInterval?: number = undefined; - - constructor(props: any) { - super(props); - this.state = { - barData: makeData(), - data: this.getData(), - transitionData: this.getTransitionData(), - arrayData: this.getArrayData(), - zoomDomain: this.getZoomDomain(), - }; - } - - componentDidMount() { - this.setStateInterval = window.setInterval(() => { - this.setState({ - data: this.getData(), - transitionData: this.getTransitionData(), - }); - }, 3000); - } - - componentWillUnmount() { - window.clearInterval(this.setStateInterval); - } - - getZoomDomain() { - const yZoomDomain: DomainTuple = [random(0, 0.4), random(0.6, 1)]; - - return { - y: yZoomDomain, - }; - } - - getTransitionData() { - const lines = random(6, 10); - return range(lines).map((line) => { - return { x: line, y: random(2, 10) }; - }); - } - - getData() { - return range(50).map((i) => { - return { - a: i + 20, - b: Math.random(), - }; - }); - } - getArrayData() { - return range(40).map((i) => [i, i + Math.random() * 3]); - } - - getStyles() { - const colors = ["red", "orange", "cyan", "green", "blue", "purple"]; - return { - stroke: colors[random(0, 5)], - strokeWidth: random(1, 5), - }; - } - render() { - return ( -
    - - - } - style={{ parent: parentStyle }} - data={this.state.transitionData} - > - - - - ref} - /> - } - scale={{ - x: "time", - }} - > - new Date(x).getFullYear()} /> - - - - - } - /> - } - > - - - - - - - } - /> - } - > - datum.x} - labelComponent={} - /> - - - } - > - { - return [ - { - mutation: (props) => { - return { - style: Object.assign({}, props.style, { - stroke: "orange", - }), - }; - }, - }, - { - target: "labels", - mutation: () => { - return { text: "hey" }; - }, - }, - ]; - }, - }, - }, - ]} - data={range(0, 100)} - y={(d) => d * d} - /> - - - } - > - - - - - - - - - - } - animate={{ duration: 500 }} - style={{ parent: parentStyle }} - > - Math.sin(2 * Math.PI * d.x)} - samples={25} - /> - - - } - events={[ - { - childName: "area-1", - target: "data", - eventHandlers: { - onClick: () => { - return [ - { - childName: "area-2", - target: "data", - mutation: (props) => { - return { - style: Object.assign({}, props.style, { - fill: yellow, - }), - }; - }, - }, - { - childName: "area-3", - target: "data", - mutation: (props) => { - return { - style: Object.assign({}, props.style, { - fill: orange, - }), - }; - }, - }, - { - childName: "area-4", - target: "data", - mutation: (props) => { - return { - style: Object.assign({}, props.style, { fill: red }), - }; - }, - }, - ]; - }, - }, - }, - ]} - > - - - - - - - - - - - } - > - - - - - - } - style={{ parent: parentStyle }} - > - - - - } - style={{ parent: parentStyle }} - horizontal - > - - -
    - ); - } -} diff --git a/website/docs/api/victory-brush-container.mdx b/website/docs/api/victory-brush-container.mdx index c33b8866f..3f4af8260 100644 --- a/website/docs/api/victory-brush-container.mdx +++ b/website/docs/api/victory-brush-container.mdx @@ -5,14 +5,13 @@ title: VictoryBrushContainer Adds the ability to highlight a region of a chart, and interact with highlighted regions. :::info -For examples of `VictoryBrushContainer` in action, visit the [containers](/docs/guides/containers) guide. +For examples of `VictoryBrushContainer` in action, visit the [Data Selection](/docs/guides/data-selection) guide. ::: ## Inherited Props ## Component Props @@ -253,6 +252,3 @@ _example:_ `onBrushDomainChange={(domain, props) => handleDomainChange(domain, p The optional `onBrushDomainChangeEnd` prop accepts a function to be called only on mouse up events. The function accepts the parameters of `domain` (the updated domain), and `props` (the props used by `VictoryBrushContainer`). _example:_ `onBrushDomainChangeEnd={(domain, props) => handleDomainChangeEnd(domain, props)}` - -[brush and zoom guide]: /guides/brush-and-zoom -[victorycontainer]: /docs/api/victory-container diff --git a/website/docs/api/victory-container.mdx b/website/docs/api/victory-container.mdx index 02c214a14..d5fb3b80f 100644 --- a/website/docs/api/victory-container.mdx +++ b/website/docs/api/victory-container.mdx @@ -7,7 +7,7 @@ By default, `VictoryContainer` renders responsive SVGs. `VictoryContainer` also container that can be accessed via [VictoryPortal][] in order to render specified children above others. All other Victory containers extend `VictoryContainer`. Check out documentation for [common container props][] for more information about customizing container props. :::info -For examples of `VictoryContainer` in action, visit the [containers](/docs/guides/containers) guide. +For an overview of containers, visit the [containers](/docs/guides/containers) guide. ::: ## Inherited Props diff --git a/website/docs/api/victory-cursor-container.mdx b/website/docs/api/victory-cursor-container.mdx index 280637999..e60472352 100644 --- a/website/docs/api/victory-cursor-container.mdx +++ b/website/docs/api/victory-cursor-container.mdx @@ -5,7 +5,7 @@ title: VictoryCursorContainer Adds a cursor to a chart to inspect coordinates. :::info -For examples of `VictoryCursorContainer` in action, visit the [containers](/docs/guides/containers) guide. +For examples of `VictoryCursorContainer` in action, visit the [Tooltips](/docs/guides/tooltips) guide. ::: ## Inherited Props diff --git a/website/docs/charts/bar.mdx b/website/docs/charts/bar.mdx index ce3dc8880..7b313e3ea 100644 --- a/website/docs/charts/bar.mdx +++ b/website/docs/charts/bar.mdx @@ -281,7 +281,7 @@ render(); ## Bar Charts - Zoom & Pan -Bar charts support zoom and pan behavior by using the `VictoryZoomContainer` component. See the [Brush & Zoom](/docs/guides/brush-and-zoom) guide for more information. +Bar charts support pan and zoom behavior by using the `VictoryZoomContainer` component. See the [Pan & Zoom](/docs/guides/pan-and-zoom) guide for more information. ```jsx live - } - > - datum.y % 5 === 0 ? 1 : 0.7, - fill: ({ datum }) => datum.y % 5 === 0 ? "lightblue" : "lightgreen" - } - }} - /> - - ); -} - -function getScatterData() { - return _.range(50).map((index) => { - return { - x: _.random(1, 50), - y: _.random(10, 90), - size: _.random(8) + 3 - }; - }); -} - -render(); -``` - -In the next example, `VictoryZoomContainer` and `VictoryBrushContainer` are used to create a zoomable chart with a mini-map brush control. -Here, the `onZoomDomainChange` prop on `VictoryZoomContainer` alters the `brushDomain` prop on `VictoryBrushContainer` tying highlighted brush region of the mini-map to the zoom level of the chart. -The `onBrushDomainChange` prop on `VictoryBrushContainer` alters the `zoomDomain` prop on `VictoryZoomContainer` so that the zoomed level of the chart matches the highlighted region of the mini-map. - - -```jsx live noInline -function App() { - const [state, setState] = React.useState({}); - - function handleZoom(domain) { - setState({selectedDomain: domain}); - } - - function handleBrush(domain) { - setState({zoomDomain: domain}); - } - - return ( -
    - - } - > - - - - - - } - > - new Date(x).getFullYear()} - /> - - -
    - ); -} - -render(); -``` - -`VictoryBrushContainer` may be used with any Victory component that works on an x-y coordinate system. -Brushing behavior may be limited to the x or y dimensions with the `brushDimension` prop, and the selected -area may be styled, or even replaced with a custom component. - -```jsx live - - } - style={{ - data: {stroke: "lightblue"} - }} - data={[ - {x: 1, y: -3}, - {x: 2, y: 5}, - {x: 3, y: -3}, - {x: 4, y: 0}, - {x: 5, y: -5}, - {x: 6, y: 2}, - {x: 7, y: 0} - ]} -/> -``` diff --git a/website/docs/guides/containers.mdx b/website/docs/guides/containers.mdx index fe628e69e..e142a3e1b 100644 --- a/website/docs/guides/containers.mdx +++ b/website/docs/guides/containers.mdx @@ -6,7 +6,7 @@ title: Containers Victory containers have default `width`, `height`, and `padding` props defined in the default [theme](/docs/guides/themes). -Victory renders components into responsive `svg` containers by default. Responsive containers will have a `viewBox` attribute set to `viewBox={"0 0 width, height"}` and styles `width: "100%" height: "auto"` in addition to any styles provided via props. Because Victory renders responsive containers, the `width` and `height` props do not determine the width and height of the chart in number of pixels, but instead define an aspect ratio for the chart. The exact number of pixels will depend on the size of the container the chart is rendered into. +Victory renders components into responsive `svg` containers by default using `VictoryContainer`. `VictoryContainer` is a responsive container with a `viewBox` attribute set to `viewBox={"0 0 width, height"}` and styles `width: "100%" height: "auto"` in addition to any styles provided via props. Because Victory renders responsive containers, the `width` and `height` props do not determine the width and height of the chart in number of pixels, but instead define an aspect ratio for the chart. The exact number of pixels will depend on the size of the container the chart is rendered into. ## Fixed Size Containers @@ -131,7 +131,7 @@ Some components should _always_ render above others. Use `VictoryPortal` to rend ``` -## Container Types +## Basic Container Types Victory renders charts into top-level container components. The most commonly used container is `VictoryChart`. @@ -141,159 +141,38 @@ Containers are responsible for rendering children into a responsive svg, and pro `VictoryContainer` provides a top-level `` element for other Victory components to render within. Most containers extend `VictoryContainer` to add extra functionality. +See the [full API here](/docs/api/victory-container). + ### VictoryChart `VictoryChart` is a container that renders a set of children on a set of Cartesian or polar axes. `VictoryChart` reconciles the domain for all its children, controls the layout of the chart, and coordinates animations and shared events. If no children are provided, `VictoryChart` will render a set of empty default axes. See the [full API here](/docs/api/victory-chart). -```jsx live - - - -``` +## Advanced Container Types + +- [VictorySelectionContainer](#victoryselectioncontainer) +- [VictoryZoomContainer](#victoryzoomcontainer) +- [VictoryVoronoiContainer](#victoryvoronoicontainer) ### VictoryGroup `VictoryGroup` is a container that renders a given set of children with shared props. This is useful for creating a group of components that share styles or data, or rendering multiple charts without axes. -See the [full API here](/docs/api/victory-chart). - -```jsx live - - - - - - - -``` - -:::note -Use `VictoryGroup` to render multiple charts without axes. -::: - -```jsx live - - - - -``` +See the [full API here](/docs/api/victory-group). ### VictoryBrushContainer `VictoryBrushContainer` adds the ability to highlight a region of a chart, and interact with highlighted regions, either by moving the region, expanding the region, or selecting a new region. -`VictoryBrushContainer` is useful for selecting a region of a larger dataset by domain. Create a -brush control by tying the domain of the selected region to the domain of a separate chart. -See the [brush and zoom guide][] for an example of using `VictoryBrushContainer` to create a brush -control. - -`VictoryBrushContainer` is similar to `VictorySelectionContainer`. `VictoryBrushContainer` may be -used to identify the domain of a selected region, whereas `VictorySelectionContainer` may be used to -identify a list of data points within a selected region. `VictoryBrushContainer` will also create -persistent highlighted regions, whereas regions created by `VictorySelectionContainer` -disappear after `onMouseUp` events. - -`VictoryBrushContainer` may be used with any Victory component that works with an x-y coordinate -system, and should be added as the `containerComponent` of the top-level component. -However, the component that uses it must be standalone -(`standalone={true}`), which is the default for all top-level Victory components. -```jsx live - - } - theme={VictoryTheme.clean} -> - - -``` +See the [Brush Selection](/docs/guides/data-selection) guide. ### VictoryCursorContainer -`VictoryCursorContainer` adds a cursor to a chart to inspect coordinates. -The cursor can either be a 2-dimensional crosshair, or a 1-dimensional line. -The cursor moves with the mouse (or on touch on mobile devices) along the visible domain of the chart. -The cursor can also display a label for the active coordinates using the `cursorLabel` prop. +`VictoryCursorContainer` adds a cursor to a chart to inspect coordinates. The cursor can either be a 2-dimensional crosshair, or a 1-dimensional line. The cursor moves with the mouse (or on touch on mobile devices) along the visible domain of the chart. -`VictoryCursorContainer` may be used with any Victory component that works with an x-y coordinate -system, and should be added as the `containerComponent` of the top-level component. -However, the component that uses it must be standalone -(`standalone={true}`), which is the default for all top-level Victory components. - -Note that the cursor allows you to inspect the entire domain, not just the data points. -If you would like to instead highlight only the data points, consider using [VictoryVoronoiContainer][]. - -```jsx live - - `${_.round(datum.x, 2)}, ${_.round( - datum.y, - 2, - )}` - } - /> - } -/> -``` +See the [Cursor Tooltips](/docs/guides/tooltips) guide. ### VictorySelectionContainer @@ -302,96 +181,22 @@ Clicking and dragging will select an x-y region, and add the `active` prop to an corresponding to data points within the region. Create a select-box control by tying the set of selected data points to other elements, such as a filtered table. -`VictorySelectionContainer` is similar to `VictoryBrushContainer`. `VictoryBrushContainer` may be -used to identify the domain of a selected region, whereas `VictorySelectionContainer` may be used to -identify a list of data points within a selected region. `VictoryBrushContainer` will also create -persistent highlighted regions, whereas regions created by `VictorySelectionContainer` -disappear after `onMouseUp` events. - -`VictorySelectionContainer` may be used with any Victory component that works with an x-y coordinate -system, and should be added as the `containerComponent` of the top-level component. -However, the component that uses it must be standalone -(`standalone={true}`), which is the default for all top-level Victory components. - -```jsx live - - } - theme={VictoryTheme.clean} -> - - active ? "tomato" : "gray", - }, - }} - /> - -``` +See the [Data Selection](/docs/guides/data-selection) guide. ### VictoryZoomContainer `VictoryZoomContainer` provides pan and zoom behavior for any Victory component that works with an x, y axis. Zoom events are controlled by scrolling, and panning events are controlled by dragging. -`VictoryZoomContainer` may be used with any Victory component that works with an x-y coordinate -system, and should be added as the `containerComponent` of the top-level component. However, the component that uses it must be standalone -(`standalone={true}`), which is the default for all top-level Victory components. - -```jsx live - - } - theme={VictoryTheme.clean} -> - - Math.sin(2 * Math.PI * datum.x) - } - /> - -``` +See the [Pan and Zoom](/docs/guides/pan-and-zoom) guide. ### VictoryVoronoiContainer `VictoryVoronoiContainer` adds the ability to associate a mouse position with the data point(s) closest to it. When this container is added to a chart, changes in mouse position will add the `active` -prop to data and label components closest to the current mouse position. The closeness of data -points to a given position is determined by calculating a [voronoi diagram][] based on the data of -every child `VictoryVoronoiContainer` renders. This container is useful for adding hover interactions, -like tooltips, to small data points, or charts with dense or overlapping data. - -`VictoryVoronoiContainer` may be used with any Victory component that works with an x-y coordinate -system, and should be added as the `containerComponent` of the top-level component. -However, the component that uses it must be standalone -(`standalone={true}`), which is the default for all top-level Victory components. +prop to data and label components closest to the current mouse position. -```jsx live - - `${_.round(datum.x, 2)}, ${_.round( - datum.y, - 2, - )}` - } - /> - } - theme={VictoryTheme.clean} -> - - Math.sin(2 * Math.PI * datum.x) - } - /> - -``` +See the [Tooltips](/docs/guides/tooltips) guide. ### Multiple Containers diff --git a/website/docs/guides/data-selection.mdx b/website/docs/guides/data-selection.mdx new file mode 100644 index 000000000..5c179148b --- /dev/null +++ b/website/docs/guides/data-selection.mdx @@ -0,0 +1,412 @@ +--- +title: Data Selection +--- + +Victory allows multiple ways to select data points on a chart. `VictorySelectionContainer` is a container component that allows users to select data points within a region of a chart. Use `VictoryBrushContainer` to identify the domain of a selected region. + +:::info +`VictorySelectionContainer` is similar to `VictoryBrushContainer`. `VictoryBrushContainer` may be +used to identify the domain of a selected region, whereas `VictorySelectionContainer` may be used to +identify a list of data points within a selected region. `VictoryBrushContainer` will also create +persistent highlighted regions, whereas regions created by `VictorySelectionContainer` +disappear after `onMouseUp` events. +::: + +## VictorySelectionContainer + +Use `VictorySelectionContainer` to add data selection behavior to any Victory components that work with an x-y coordinate system. + +See the [full API here](/docs/api/victory-selection-container). + +### Basic + +`VictorySelectionContainer` may be used with any Victory component that works with an x-y coordinate +system, and should be added as the `containerComponent` of the top-level component. +However, the component that uses it must be standalone +(`standalone={true}`), which is the default for all top-level Victory components. + +```jsx live + + } + theme={VictoryTheme.clean} +> + + active ? "tomato" : "gray", + }, + }} + /> + +``` + +### Active Points + +The `VictorySelectionContainer` will automatically set an `active` prop on the data points that are within the selected region. This prop can be used to style the selected points differently. + +```jsx live + + } +> + + + active ? VictoryTheme.clean.palette?.colors.purple : "none", + strokeWidth: 2, + }, + }} + data={[ + { x: 1, y: -5 }, + { x: 2, y: 4 }, + { x: 3, y: 2 }, + { x: 4, y: 3 }, + { x: 5, y: 1 }, + { x: 6, y: -3 }, + { x: 7, y: 3 }, + ]} + /> + + active ? VictoryTheme.clean.palette?.colors.purple : "none", + strokeWidth: 2, + }, + }} + 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 ? VictoryTheme.clean.palette?.colors.purple : "none", + strokeWidth: 2, + }, + }} + data={[ + { x: 1, y: 5 }, + { x: 2, y: -4 }, + { x: 3, y: -2 }, + { x: 4, y: -3 }, + { x: 5, y: -1 }, + { x: 6, y: 3 }, + { x: 7, y: -3 }, + ]} + /> + + +``` + +### Selection Limits + +The `selectionDimension` prop may be used to limit brushing behavior to a single dimension. In the example below, the `selectionDimension` prop is set to `"y"`, allowing users to select a region of the chart along the y-axis only. + +```jsx live + + } + theme={VictoryTheme.clean} +> + + active ? "tomato" : "gray", + }, + }} + /> + +``` + +### Events + +Use the `onSelection` prop to define a function that will be called with the selected domain +when the selection area changes. + +```jsx live noInline +function App() { + const [selection, setSelection] = + React.useState({}); + + const handleSelection = ( + datasets, + ) => { + const points = datasets.reduce( + (memo, dataset) => + memo.concat(dataset.data), + [], + ); + setSelection({ points }); + }; + + return ( + + } + > + + + + {selection && ( + ({ x, y }), + ), + )} + /> + )} + + ); +} + +render(); +``` + +## VictoryBrushContainer + +Use `VictoryBrushContainer` to add highlighting and selection to any Victory components that work with an x-y coordinate system. + +See the [full API here](/docs/api/victory-brush-container). + +### Basic + +In the example below, the `VictoryBrushContainer` component is used to highlight a region of a line chart. The brush behavior is unconstrained by default, allowing users to click and drag to select a region of the chart. + +```jsx live + + } + style={{ + data: { stroke: "lightblue" }, + }} + data={[ + { x: 1, y: -3 }, + { x: 2, y: 5 }, + { x: 3, y: -3 }, + { x: 4, y: 0 }, + { x: 5, y: -5 }, + { x: 6, y: 2 }, + { x: 7, y: 0 }, + ]} +/> +``` + +### Selection Limits + +The `brushDimension` prop may be used to limit brushing behavior to a single dimension. In the example below, the `brushDimension` prop is set to `"y"`, allowing users to select a region of the chart along the y-axis only and the `brushDomain` restricts the highlighted area to the specified range. + +```jsx live + + } + style={{ + data: { stroke: "lightblue" }, + }} + data={[ + { x: 1, y: -3 }, + { x: 2, y: 5 }, + { x: 3, y: -3 }, + { x: 4, y: 0 }, + { x: 5, y: -5 }, + { x: 6, y: 2 }, + { x: 7, y: 0 }, + ]} +/> +``` + +### Events + +`VictoryBrushContainer` exposes several events you can use to access the selection range. In the example below, you can monitor your browser's console to see the events in action. + +```jsx live + + console.log( + "[onBrushCleared]", + { domain, props }, + ) + } + onBrushDomainChange={( + domain, + props, + ) => + console.log( + "[onBrushDomainChange]", + { domain, props }, + ) + } + onBrushDomainChangeEnd={( + domain, + props, + ) => + console.log( + "[onBrushDomainChangeEnd]", + { domain, props }, + ) + } + /> + } +> + + + + + + +``` diff --git a/website/docs/guides/pan-and-zoom.mdx b/website/docs/guides/pan-and-zoom.mdx new file mode 100644 index 000000000..ba6132eb4 --- /dev/null +++ b/website/docs/guides/pan-and-zoom.mdx @@ -0,0 +1,276 @@ +--- +title: Pan and Zoom +--- + +Use `VictoryZoomContainer` to add panning and zooming behavior to any Victory components that work with an x-y coordinate system. + +See the [full API here](/docs/api/victory-zoom-container). + +## Basic + +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. + +```jsx live noInline +function App() { + return ( + + } + > + + datum.y % 5 === 0 + ? 1 + : 0.7, + fill: ({ datum }) => + datum.y % 5 === 0 + ? "lightblue" + : "lightgreen", + }, + }} + /> + + ); +} + +function getScatterData() { + return _.range(50).map((index) => { + return { + x: _.random(1, 50), + y: _.random(10, 90), + size: _.random(8) + 3, + }; + }); +} + +render(); +``` + +## Limits + +The `allowZoom` and `allowPan` props may be used to restrict zooming and panning behavior. In the example below, zooming is disabled, but panning is allowed. + +```jsx live noInline +function App() { + return ( + + } + > + + datum.y % 5 === 0 + ? 1 + : 0.7, + fill: ({ datum }) => + datum.y % 5 === 0 + ? "lightblue" + : "lightgreen", + }, + }} + /> + + ); +} + +function getScatterData() { + return _.range(50).map((index) => { + return { + x: _.random(1, 50), + y: _.random(10, 90), + size: _.random(8) + 3, + }; + }); +} + +render(); +``` + +## Combined with Brushing + +In the next example, `VictoryZoomContainer` and `VictoryBrushContainer` are used to create a zoomable chart with a mini-map brush control. +Here, the `onZoomDomainChange` prop on `VictoryZoomContainer` alters the `brushDomain` prop on `VictoryBrushContainer` tying highlighted brush region of the mini-map to the zoom level of the chart. +The `onBrushDomainChange` prop on `VictoryBrushContainer` alters the `zoomDomain` prop on `VictoryZoomContainer` so that the zoomed level of the chart matches the highlighted region of the mini-map. + +:::info +For more information on brushing, see the [Data Selection](/docs/guides/data-selection) guide. +::: + +```jsx live noInline +function App() { + const [state, setState] = + React.useState({}); + + function handleZoom(domain) { + setState({ + selectedDomain: domain, + }); + } + + function handleBrush(domain) { + setState({ zoomDomain: domain }); + } + + return ( +
    + + } + > + + + + + } + > + + new Date(x).getFullYear() + } + /> + + +
    + ); +} + +render(); +``` diff --git a/website/docs/guides/tooltips.mdx b/website/docs/guides/tooltips.mdx index 2bde73035..7ceb894e0 100644 --- a/website/docs/guides/tooltips.mdx +++ b/website/docs/guides/tooltips.mdx @@ -2,13 +2,11 @@ title: Tooltips --- -[`VictoryTooltip`][] is a label component with `defaultEvents` It renders a customizable flyout container as well as a `VictoryLabel` component. `VictoryTooltip` can be used with any Victory component by setting the `labelComponent` prop like so `labelComponent={` +Victory supports multiple ways to show tooltips on your charts. Tooltips can be added to any Victory component, and can be customized to suit your needs. This guide will cover the basics of adding tooltips to your charts, as well as more advanced configurations. -This guide discusses customization and advanced usage of tooltips in Victory +## Basic -## Simple tooltips - -The simplest way to add tooltips to a chart is to use `VictoryTooltip` as a `labelComponent` as in the example below: +The simplest way to add tooltips to a chart is to use `VictoryTooltip` as a `labelComponent`. By default, the `labelComponent` will display the `label` prop of the data point it is associated with, unless you specify a custom `labels` function. ```jsx live } data={[ - { - x: 2, - y: 5, - label: "right-side-up", - }, - { - x: 4, - y: -6, - label: "upside-down", - }, - { x: 6, y: 4, label: "tiny" }, - { - x: 8, - y: -5, - label: "or a little \n BIGGER", - }, - { - x: 10, - y: 7, - label: "automatically", - }, + { x: 2, y: 5 }, + { x: 4, y: -6 }, + { x: 6, y: 4 }, + { x: 8, y: -5 }, + { x: 10, y: 7 }, ]} - style={{ - data: { width: 20 }, - }} + labels={({ datum }) => datum.y} /> ``` -When tooltips are added to a chart in this way, `defaultEvents` on `VictoryTooltip` are automatically added to the component using them, in this case `VictoryBar`. By default, `VictoryTooltip` will adjust its position, orientation, and the width and height of its container to match the corresponding data and labels. - -## Customizing Tooltips +## Styles -Tooltips can be customized directly on the `VictoryTooltip` component +`VictoryTooltip` can be styled using the `style` prop. The `style` prop accepts an object with `data`, `labels`, and `flyout` keys. The `data` key styles the data point, the `labels` key styles the text of the tooltip, and the `flyout` key styles the background of the tooltip. ```jsx live ``` -`VictoryTooltip` is composed of [`VictoryLabel`][] and the primitive [`Flyout`][] component. Both of these components are highly configurable, but may also be replaced if necessary. +## Events -```jsx live noInline -const colors = - VictoryTheme.clean.palette.cool; +`VictoryTooltip` automatically attaches events to data components. When events of the same type are specified for data components, it is necessary to reconcile events so that tooltips still work. For web, the default tooltip events are: -function CustomFlyout(props) { - const { x, y, orientation } = props; - const newY = - orientation === "bottom" - ? y - 35 - : y + 35; - return ( - - - - - - ); -} +```jsx +static defaultEvents = [{ + target: "data", + eventHandlers: { + onMouseOver: () => ({ + target: "labels", + mutation: () => ({ active: true }) + }), + onMouseOut: () => ({ + target: "labels", + mutation: () => ({ active: undefined }) + }), + onFocus: () => ({ + target: "labels", + mutation: () => ({ active: true }) + }), + onBlur: () => ({ + target: "labels", + mutation: () => ({ active: undefined }) + }) + } +}]; +``` -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: { width: 20 }, - }} - /> - - ); -} -render(); +:::warning +When other `onMouseOver` and `onMouseOut` events are specified for data, the event returns described above must be added to the events for tooltips to continue to work properly. +::: + +```jsx live + + } + 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: { width: 20 }, + }} + events={[ + { + target: "data", + eventHandlers: { + onMouseOver: () => { + return [ + { + target: "data", + mutation: () => ({ + style: { + fill: "gold", + width: 30, + }, + }), + }, + { + target: "labels", + mutation: () => ({ + active: true, + }), + }, + ]; + }, + onMouseOut: () => { + return [ + { + target: "data", + mutation: () => {}, + }, + { + target: "labels", + mutation: () => ({ + active: false, + }), + }, + ]; + }, + }, + }, + ]} + /> + ``` -## Tooltips with VictoryVoronoiContainer +--- + +## Voronoi + +Use `VictoryVoronoiContainer` to associate a mouse position with the data point(s) +closest to it. When this container is added to a chart, changes in mouse position will add the `active` +prop to data and label components closest to the current mouse position. The closeness of data +points to a given position is determined by calculating a voronoi diagram based on the data of +every child `VictoryVoronoiContainer` renders. This container is useful for adding hover interactions, +like tooltips, to small data points, or charts with dense or overlapping data. + +See the [full API here](/docs/api/victory-voronoi-container). + +### Basic -Voronoi tooltips are useful for adding tooltips to a line, or adding tooltips to data points that -are too small to hover over effectively. `VictoryVoronoiContainer` calculates a voronoi diagram -based on the data of every child component it renders. The voronoi data is used to associate a -mouse position with its nearest data point(s). When `VictoryVoronoiContainer` is added as the -`containerComponent` of your chart, changes in mouse position will add and remove the `active` prop -on appropriate data and label elements. +`VictoryVoronoiContainer` may be used with any Victory component that works with an x-y coordinate +system, and should be added as the `containerComponent` of the top-level component. +However, the component that uses it must be standalone +(`standalone={true}`), which is the default for all top-level Victory components. ```jsx live ``` -## Multi-point Tooltips with VictoryVoronoiContainer +### Follow Tooltips + +When using `VictoryVoronoiContainer` with `VictoryTooltip`, you can add tooltips to your chart that follow the mouse position. + +```jsx live + + `Data ${datum.y}` + } + mouseFollowTooltips + labelComponent={ + + } + /> + } +> + + active ? 8 : 3 + } + /> + + active ? 5 : 3 + } + /> + +``` + +### Active Points + +`VictoryVoronoiContainer` adds the `active` prop to any data point closest to the current mouse position. This prop can be used to style the active data point differently from the rest. + +```jsx live + + `${_.round(datum.x, 2)}, ${_.round( + datum.y, + 2, + )}` + } + /> + } + theme={VictoryTheme.clean} +> + + Math.sin(2 * Math.PI * datum.x) + } + style={{ + data: { + fill: ({ active }) => + active + ? VictoryTheme.clean.palette + ?.colors.red + : VictoryTheme.clean.palette + ?.colors.blue, + }, + }} + /> + +``` + +### Multipoint Labels `VictoryVoronoiContainer` can also be used to create multi-point labels when the `labels` prop is provided. In the example below the `voronoiDimension` prop indicates that the voronoi diagram @@ -322,139 +433,330 @@ several tooltips being active at the same time. Provide a `labels` and (optional ``` -`VictoryVoronoiContainer` also has a `mouseFollowTooltips` boolean prop that works with single point and multi-point tooltip labels. +--- + +## Cursor + +Use `VictoryCursorContainer` to add a cursor to a chart to inspect coordinates. +The cursor can either be a 2-dimensional crosshair, or a 1-dimensional line. +The cursor moves with the mouse (or on touch on mobile devices) along the visible domain of the chart. +The cursor can also display a label for the active coordinates using the `cursorLabel` prop. + +See the [full API here](/docs/api/victory-cursor-container). + +### Line Charts + +Using the `VictoryCursorContainer` component, you can add a 2D cursor to a line chart. ```jsx live - `y: ${datum.y}` + + `${_.round(datum.x, 2)}, ${_.round( + datum.y, + 2, + )}` } /> } theme={VictoryTheme.clean} > - d.x * d.x} /> ``` -## Tooltips with Other Events - -`VictoryTooltip` automatically attaches events to data components. When events of the same type are specified for data components, it is necessary to reconcile events so that tooltips still work. For web, the default tooltip events are: +### Dimension Limits -`VictoryTooltip` uses `defaultEvents` which are prepended onto any events array provided in props. When `events` container `onMouseOver` and `onMouseOut` events, they will interfere with the `defaultEvents` on `VictoryTooltip` to correct this, your events prop will need to return the same mutations as `defaultEvents`. [Read about tooltip events here](/docs/guides/tooltips). +You can also use the `cursorDimension` prop to create a 1D cursor. This is useful for line charts where you only want to inspect one dimension. Note you can also set a `defaultCursorValue` to set the initial position of the cursor. -```jsx -static defaultEvents = [{ - target: "data", - eventHandlers: { - onMouseOver: () => ({ - target: "labels", - mutation: () => ({ active: true }) - }), - onMouseOut: () => ({ - target: "labels", - mutation: () => ({ active: undefined }) - }), - onFocus: () => ({ - target: "labels", - mutation: () => ({ active: true }) - }), - onBlur: () => ({ - target: "labels", - mutation: () => ({ active: undefined }) - }) +```jsx live + + `${_.round(datum.x, 2)}, ${_.round( + datum.y, + 2, + )}` + } + cursorDimension="x" + defaultCursorValue={0.3} + /> } -}]; + theme={VictoryTheme.clean} +> + d.x * d.x} + /> + ``` -When other `onMouseOver` and `onMouseOut` events are specified for data, the event returns described above must be added to the events for tooltips to continue to work properly. +### Bar Charts + +You can also use the `VictoryCursorContainer` component with bar charts. ```jsx live + _.round(datum.x, 2) + } + cursorDimension="y" + defaultCursorValue={3} + /> + } > - } - 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: { width: 20 }, - }} - events={[ - { - target: "data", - eventHandlers: { - onMouseOver: () => { - return [ - { - target: "data", - mutation: () => ({ - style: { - fill: "gold", - width: 30, - }, - }), - }, - { - target: "labels", - mutation: () => ({ - active: true, - }), - }, - ]; - }, - onMouseOut: () => { - return [ - { - target: "data", - mutation: () => {}, - }, - { - target: "labels", - mutation: () => ({ - active: false, - }), - }, - ]; - }, - }, - }, - ]} - /> + + + + + + + ``` -## Custom Tooltips - Victory Label +### Scatter Charts + +You can also use the `VictoryCursorContainer` component with scatter charts. Note how we can apply the container directly to the `VictoryScatter` component. + +```jsx live + + active + ? VictoryTheme.clean.palette + ?.colors.teal || "teal" + : VictoryTheme.clean.palette + ?.colors.purple || + "purple", + }, + }} + containerComponent={ + + _.round(datum.x, 2) + } + defaultCursorValue={1} + /> + } + data={[ + { x: -3, y: 2 }, + { x: 0, y: -2 }, + { x: -8, y: 1 }, + { x: -2, y: -3 }, + { x: 7, y: 5 }, + { x: -8, y: 6 }, + { x: -1, y: 3 }, + { x: -4, y: -5 }, + { x: -6, y: -5 }, + ]} +/> +``` + +### Events + +You can also use the `onCursorChange` prop to listen to cursor changes and inspect the values. + +```jsx live noInline +function App() { + const [cursorValue, setCursorValue] = + React.useState(null); + + function onCursorChange(value) { + setCursorValue(value); + } + + return ( + + _.round(datum.x, 2) + } + cursorLabelOffset={15} + onCursorChange={ + onCursorChange + } + /> + } + > + + + + + + {cursorValue && ( + + )} + + ); +} + +render(); +``` + +--- + +## Custom Components + +`VictoryTooltip` is composed of [`VictoryLabel`](/docs/api/victory-label) and the primitive [`Flyout`](/docs/api/victory-primitives#flyout) component. Both of these components are highly configurable, but may also be replaced if necessary. + +### Custom Flyout + +An example of replacing the default `Flyout` component with a custom component. + +```jsx live noInline +const colors = + VictoryTheme.clean.palette.cool; + +function CustomFlyout(props) { + const { x, y, orientation } = props; + const newY = + orientation === "bottom" + ? y - 35 + : y + 35; + return ( + + + + + + ); +} + +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: { width: 20 }, + }} + /> + + ); +} +render(); +``` + +### Custom Label + +An example of using custom labels with a donut chart. ```jsx live noInline function CustomLabel(props) { @@ -510,7 +812,7 @@ function App() { render(); ``` -## Custom Tooltips - Wrapping +### Custom Wrapping The events that control `VictoryTooltip` are stored on the static `defaultEvents` property. Wrapped instances of `VictoryTooltip` will need to replicate or hoist this property in order to add automatic events to the components that use them. @@ -582,7 +884,3 @@ In Victory Native tooltips are much more reliable when using `VictoryVoronoiCont /> ``` - -[`victorytooltip`]: /docs/api/victory-tooltip -[`victorylabel`]: /docs/api/victory-label -[`flyout`]: /docs/api/victory-primitives#flyout