diff --git a/demo/js/components/victory-zoom-container-demo.js b/demo/js/components/victory-zoom-container-demo.js index 7cb14432d..58d5d3df6 100644 --- a/demo/js/components/victory-zoom-container-demo.js +++ b/demo/js/components/victory-zoom-container-demo.js @@ -2,7 +2,7 @@ /*eslint-disable no-magic-numbers,react/no-multi-comp */ import React from "react"; import PropTypes from "prop-types"; -import { range, merge, random, minBy, maxBy, last, round } from "lodash"; +import { range, merge, random, minBy, maxBy, last } from "lodash"; import { VictoryChart } from "Packages/victory-chart/src/index"; import { VictoryStack } from "Packages/victory-stack/src/index"; import { VictoryGroup } from "Packages/victory-group/src/index"; @@ -61,11 +61,6 @@ class CustomChart extends React.Component { x: [data[0].x, last(data).x] }; } - getZoomFactor() { - const { zoomedXDomain } = this.state; - const factor = 10 / (zoomedXDomain[1] - zoomedXDomain[0]); - return round(factor, factor < 3 ? 1 : 0); - } render() { const renderedData = this.getData(); @@ -123,7 +118,7 @@ export default class App extends React.Component { getZoomDomain() { return { - y: [random(0, 0.4, 0.1), random(0.6, 1, 0.1)] + y: [random(0, 0.4), random(0.6, 1)] }; } diff --git a/demo/ts/app.tsx b/demo/ts/app.tsx index 0cfa99546..7fff9ea75 100644 --- a/demo/ts/app.tsx +++ b/demo/ts/app.tsx @@ -21,6 +21,7 @@ import TooltipDemo from "./components/victory-tooltip-demo"; import VictorySelectionContainerDemo from "./components/victory-selection-container-demo"; import VictorySharedEventsDemo from "./components/victory-shared-events-demo"; import VoronoiDemo from "./components/victory-voronoi-demo"; +import ZoomContainerDemo from "./components/victory-zoom-container-demo"; const MAP = { "/area": { component: AreaDemo, name: "AreaDemo" }, @@ -44,7 +45,8 @@ const MAP = { name: "VictorySelectionContainerDemo" }, "/victory-shared-events": { component: VictorySharedEventsDemo, name: "VictorySharedEventsDemo" }, - "/voronoi": { component: VoronoiDemo, name: "VoronoiDemo" } + "/voronoi": { component: VoronoiDemo, name: "VoronoiDemo" }, + "/zoom-container": { component: ZoomContainerDemo, name: "ZoomContainerDemo" } }; class Home extends React.Component { diff --git a/demo/ts/components/victory-brush-container-demo.tsx b/demo/ts/components/victory-brush-container-demo.tsx index 0814a0d6c..ebad31c45 100644 --- a/demo/ts/components/victory-brush-container-demo.tsx +++ b/demo/ts/components/victory-brush-container-demo.tsx @@ -10,11 +10,12 @@ import { VictoryScatter } from "@packages/victory-scatter"; import { VictoryLegend } from "@packages/victory-legend"; import { VictoryZoomContainer } from "@packages/victory-zoom-container"; import { VictoryBrushContainer } from "@packages/victory-brush-container"; +import { DomainTuple } from "@packages/victory-core"; interface VictoryBrushContainerDemoState { zoomDomain: { - x?: [number, number]; - y?: [number, number]; + x?: DomainTuple; + y?: DomainTuple; }; } @@ -29,7 +30,7 @@ export default class VictoryBrushContainerDemo extends React.Component< }; } - handleZoom(domain: { x?: [number, number]; y?: [number, number] }) { + handleZoom(domain: { x?: DomainTuple; y?: DomainTuple }) { this.setState({ zoomDomain: domain }); } diff --git a/demo/ts/components/victory-cursor-container-demo.tsx b/demo/ts/components/victory-cursor-container-demo.tsx index 44606e530..3a816a322 100644 --- a/demo/ts/components/victory-cursor-container-demo.tsx +++ b/demo/ts/components/victory-cursor-container-demo.tsx @@ -11,18 +11,18 @@ import { VictoryScatter } from "@packages/victory-scatter"; import { VictoryCursorContainer } from "@packages/victory-cursor-container"; import { VictoryTooltip } from "@packages/victory-tooltip"; import { VictoryLegend } from "@packages/victory-legend"; -import { VictoryTheme, CursorData } from "@packages/victory-core"; +import { VictoryTheme, CoordinatesPropType } from "@packages/victory-core"; interface VictoryCursorContainerStateInterface { data: { a: number; b: number }[]; - cursorValue: CursorData; - bigData: CursorData[]; + cursorValue: CoordinatesPropType; + bigData: CoordinatesPropType[]; } const makeData = () => range(1500).map((x) => ({ x, y: x + 10 * Math.random() })); class App extends React.Component { - defaultCursorValue?: CursorData = undefined; + defaultCursorValue?: CoordinatesPropType = undefined; setStateInterval?: number = undefined; constructor(props: any) { @@ -67,7 +67,7 @@ class App extends React.Component { const chartStyle = { parent: { border: "1px solid #ccc", margin: "2%", maxWidth: "40%" } }; - const cursorLabel = (datum: CursorData) => round(datum.x, 2); + const cursorLabel = (datum: CoordinatesPropType) => round(datum.x, 2); return (
@@ -155,7 +155,7 @@ class App extends React.Component { }} containerComponent={ round(datum.x, 2)} + cursorLabel={(datum: CoordinatesPropType) => round(datum.x, 2)} cursorDimension="x" defaultCursorValue={1} /> @@ -193,7 +193,7 @@ class App extends React.Component { round(datum.x, 2)} + cursorLabel={(datum: CoordinatesPropType) => round(datum.x, 2)} cursorLabelOffset={15} /> } diff --git a/demo/ts/components/victory-zoom-container-demo.tsx b/demo/ts/components/victory-zoom-container-demo.tsx new file mode 100644 index 000000000..c83cfacdb --- /dev/null +++ b/demo/ts/components/victory-zoom-container-demo.tsx @@ -0,0 +1,493 @@ +/*eslint-disable no-magic-numbers,react/no-multi-comp */ +import React from "react"; +import { range, merge, random, minBy, maxBy, last } from "lodash"; +import { VictoryChart } from "@packages/victory-chart"; +import { VictoryStack } from "@packages/victory-stack"; +import { VictoryGroup } from "@packages/victory-group"; +import { VictoryAxis } from "@packages/victory-axis"; +import { VictoryArea } from "@packages/victory-area"; +import { VictoryBar } from "@packages/victory-bar"; +import { VictoryLine } from "@packages/victory-line"; +import { VictoryScatter } from "@packages/victory-scatter"; +import { VictoryZoomContainer } from "@packages/victory-zoom-container"; +import { VictoryTooltip } from "@packages/victory-tooltip"; +import { VictoryLegend } from "@packages/victory-legend"; +import { + CoordinatesPropType, + DomainTuple, + VictoryClipContainer, + VictoryPortal, + VictoryTheme +} from "@packages/victory-core"; + +const allData = range(0, 10, 0.001).map((x) => ({ + x, + y: (Math.sin((Math.PI * x) / 2) * x) / 10 +})); + +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; + }[]; + style: React.CSSProperties; + 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(), + style: { + stroke: "blue", + strokeWidth: 2 + }, + zoomDomain: this.getZoomDomain() + }; + } + + componentDidMount() { + /* eslint-disable react/no-did-mount-set-state */ + this.setStateInterval = window.setInterval(() => { + this.setState({ + data: this.getData(), + transitionData: this.getTransitionData(), + style: this.getStyles() + }); + }, 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} + > + + + + + } + scale={{ + x: "time" + }} + > + new Date(x).getFullYear()} /> + + + + + } + /> + } + > + + + + + + + } + /> + } + > + datum.x} + labelComponent={} + /> + + + }> + { + return [ + { + mutation: (props) => { + return { style: merge({}, 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} + /> + + + } + theme={VictoryTheme.material} + events={[ + { + childName: "area-1", + target: "data", + eventHandlers: { + onClick: () => { + return [ + { + childName: "area-2", + target: "data", + mutation: (props) => { + return { style: merge({}, props.style, { fill: "gold" }) }; + } + }, + { + childName: "area-3", + target: "data", + mutation: (props) => { + return { + style: merge({}, props.style, { fill: "orange" }) + }; + } + }, + { + childName: "area-4", + target: "data", + mutation: (props) => { + return { + style: merge({}, props.style, { fill: "red" }) + }; + } + } + ]; + } + } + } + ]} + > + + + + + + + + + + + } + > + + + + +
+ ); + } +} diff --git a/packages/victory-brush-container/src/index.d.ts b/packages/victory-brush-container/src/index.d.ts index dce164f56..0bdb9139f 100644 --- a/packages/victory-brush-container/src/index.d.ts +++ b/packages/victory-brush-container/src/index.d.ts @@ -1,27 +1,27 @@ import * as React from "react"; -import { RangeTuple, VictoryContainerProps } from "victory-core"; +import { DomainTuple, VictoryContainerProps } from "victory-core"; export interface VictoryBrushContainerProps extends VictoryContainerProps { allowDrag?: boolean; allowResize?: boolean; brushComponent?: React.ReactElement; brushDimension?: "x" | "y"; - brushDomain?: { x?: RangeTuple; y?: RangeTuple }; + brushDomain?: { x?: DomainTuple; y?: DomainTuple }; brushStyle?: React.CSSProperties; defaultBrushArea?: "all" | "none" | "disable" | "move"; disable?: boolean; handleComponent?: React.ReactElement; handleStyle?: React.CSSProperties; onBrushCleared?: ( - domain: { x?: RangeTuple; y?: RangeTuple }, + domain: { x: DomainTuple; y: DomainTuple }, props: VictoryBrushContainerProps ) => void; onBrushDomainChange?: ( - domain: { x?: RangeTuple; y?: RangeTuple }, + domain: { x: DomainTuple; y: DomainTuple }, props: VictoryBrushContainerProps ) => void; onBrushDomainChangeEnd?: ( - domain: { x?: RangeTuple; y?: RangeTuple }, + domain: { x: DomainTuple; y: DomainTuple }, props: VictoryBrushContainerProps ) => void; } diff --git a/packages/victory-core/src/index.d.ts b/packages/victory-core/src/index.d.ts index 5699008cb..408671de0 100644 --- a/packages/victory-core/src/index.d.ts +++ b/packages/victory-core/src/index.d.ts @@ -107,7 +107,7 @@ export type VictoryStyleObject = { [K in keyof React.CSSProperties]: StringOrNum export type StringOrNumberOrList = string | number | (string | number)[]; -export type CursorData = { +export type CoordinatesPropType = { x: number; y: number; }; diff --git a/packages/victory-cursor-container/src/index.d.ts b/packages/victory-cursor-container/src/index.d.ts index 4a0758502..89f1138ca 100644 --- a/packages/victory-cursor-container/src/index.d.ts +++ b/packages/victory-cursor-container/src/index.d.ts @@ -1,15 +1,15 @@ import * as React from "react"; -import { VictoryContainerProps, CursorData } from "victory-core"; +import { VictoryContainerProps, CoordinatesPropType } from "victory-core"; export interface VictoryCursorContainerProps extends VictoryContainerProps { cursorComponent?: React.ReactElement; cursorDimension?: "x" | "y"; - cursorLabel?: (point: CursorData) => any | void; + cursorLabel?: (point: CoordinatesPropType) => any | void; cursorLabelComponent?: React.ReactElement; - cursorLabelOffset?: number | CursorData; - defaultCursorValue?: number | CursorData; + cursorLabelOffset?: number | CoordinatesPropType; + defaultCursorValue?: number | CoordinatesPropType; disable?: boolean; - onCursorChange?: (value: CursorData, props: VictoryCursorContainerProps) => void; + onCursorChange?: (value: CoordinatesPropType, props: VictoryCursorContainerProps) => void; } export class VictoryCursorContainer extends React.Component {} diff --git a/packages/victory-zoom-container/src/index.d.ts b/packages/victory-zoom-container/src/index.d.ts index 3af3f892d..1ba05f89e 100644 --- a/packages/victory-zoom-container/src/index.d.ts +++ b/packages/victory-zoom-container/src/index.d.ts @@ -1,21 +1,19 @@ import * as React from "react"; -import { RangeTuple, VictoryContainerProps, CursorData } from "victory-core"; +import { DomainTuple, VictoryContainerProps } from "victory-core"; export interface VictoryZoomContainerProps extends VictoryContainerProps { allowPan?: boolean; allowZoom?: boolean; - brushStyle?: React.CSSProperties; clipContainerComponent?: React.ReactElement; - defaultBrushArea?: "all" | "none" | "disable"; disable?: boolean; downsample?: number | boolean; - minimumZoom?: CursorData; + minimumZoom?: { x?: number; y?: number }; onZoomDomainChange?: ( - domain: { x?: RangeTuple; y?: RangeTuple }, + domain: { x: DomainTuple; y: DomainTuple }, props: VictoryZoomContainerProps ) => void; zoomDimension?: "x" | "y"; - zoomDomain?: { x?: RangeTuple; y?: RangeTuple }; + zoomDomain?: { x?: DomainTuple; y?: DomainTuple }; } export class VictoryZoomContainer extends React.Component {}