From 7bb2b8894de952116b14e15bda68ed22d925e421 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 20 Aug 2020 10:29:07 -0700 Subject: [PATCH 1/8] new(vx-react-spring): add AnimatedGridRows, make line transition config generic --- .../vx-demo/src/sandboxes/vx-axis/Example.tsx | 73 ++++++++++++--- packages/vx-grid/src/grids/Grid.tsx | 3 +- packages/vx-grid/src/grids/GridColumns.tsx | 53 ++++++----- packages/vx-grid/src/grids/GridRows.tsx | 53 ++++++----- packages/vx-grid/src/types.ts | 14 ++- packages/vx-react-spring/package.json | 1 + .../vx-react-spring/src/axis/AnimatedAxis.tsx | 22 +++-- .../src/axis/AnimatedTicks.tsx | 82 +++++++++++++++++ .../src/axis/AnimatedTicks/index.tsx | 62 ------------- .../AnimatedTicks/useTickTransitionConfig.ts | 54 ----------- .../src/grid/AnimatedGridRows.tsx | 75 ++++++++++++++++ packages/vx-react-spring/src/index.ts | 1 + .../spring-configs/useLineTransitionConfig.ts | 89 +++++++++++++++++++ 13 files changed, 397 insertions(+), 185 deletions(-) create mode 100644 packages/vx-react-spring/src/axis/AnimatedTicks.tsx delete mode 100644 packages/vx-react-spring/src/axis/AnimatedTicks/index.tsx delete mode 100644 packages/vx-react-spring/src/axis/AnimatedTicks/useTickTransitionConfig.ts create mode 100644 packages/vx-react-spring/src/grid/AnimatedGridRows.tsx create mode 100644 packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts diff --git a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx index 58b729d75..29aa5777b 100644 --- a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx +++ b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx @@ -4,7 +4,7 @@ import { Grid } from '@vx/grid'; import { curveMonotoneX } from '@vx/curve'; import { scaleUtc, scaleLinear, scaleLog, scaleBand, ScaleInput, coerceNumber } from '@vx/scale'; import { Orientation, SharedAxisProps, AxisScale } from '@vx/axis'; -import { AnimatedAxis } from '@vx/react-spring'; +import { AnimatedAxis, AnimatedGridRows } from '@vx/react-spring'; import { LinearGradient } from '@vx/gradient'; import { timeFormat } from 'd3-time-format'; @@ -41,6 +41,9 @@ export default function Example({ const width = outerWidth - margin.left - margin.right; const height = outerHeight - margin.top - margin.bottom; const [dataToggle, setDataToggle] = useState(true); + const [animationTrajectory, setAnimationTrajectory] = useState< + 'outside' | 'inside' | 'min' | 'max' + >('min'); if (width < 10) return null; @@ -131,6 +134,23 @@ export default function Example({ {axes.map(({ scale, values, label, tickFormat }, i) => ( + {/* */} + [ (scale(x) ?? 0) + @@ -144,15 +164,6 @@ export default function Example({ fill={gridColor} fillOpacity={0.2} /> - ))} - {showControls && } + {showControls && ( + <> + +
+ animation trajectory + + + + +
+ + )} ); } diff --git a/packages/vx-grid/src/grids/Grid.tsx b/packages/vx-grid/src/grids/Grid.tsx index 88a4edffc..ff518743c 100644 --- a/packages/vx-grid/src/grids/Grid.tsx +++ b/packages/vx-grid/src/grids/Grid.tsx @@ -13,7 +13,8 @@ type CommonPropsToOmit = | 'lineStyle' | 'tickValues' | 'from' - | 'to'; + | 'to' + | 'children'; export type GridProps = Omit< AllGridRowsProps & AllGridColumnProps, diff --git a/packages/vx-grid/src/grids/GridColumns.tsx b/packages/vx-grid/src/grids/GridColumns.tsx index 6deb292fa..3e0c024ca 100644 --- a/packages/vx-grid/src/grids/GridColumns.tsx +++ b/packages/vx-grid/src/grids/GridColumns.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import Line, { LineProps } from '@vx/shape/lib/shapes/Line'; import { Group } from '@vx/group'; import { Point } from '@vx/point'; -import { getTicks, ScaleInput } from '@vx/scale'; +import { getTicks, ScaleInput, coerceNumber } from '@vx/scale'; import { CommonGridProps, GridScale } from '../types'; export type GridColumnProps = CommonGridProps & { @@ -37,34 +37,39 @@ export default function GridColumns({ lineStyle, offset, tickValues, + children, ...restProps }: AllGridColumnProps) { const ticks = tickValues ?? getTicks(scale, numTicks); + const tickLines = ticks.map(d => { + const x = offset ? (coerceNumber(scale(d)) || 0) + offset : coerceNumber(scale(d)) || 0; + return { + from: new Point({ + x, + y: 0, + }), + to: new Point({ + x, + y: height, + }), + }; + }); return ( - {ticks.map((d, i) => { - const x = offset ? (scale(d) || 0) + offset : scale(d) || 0; - const fromPoint = new Point({ - x, - y: 0, - }); - const toPoint = new Point({ - x, - y: height, - }); - return ( - - ); - })} + {children + ? children({ lines: tickLines }) + : tickLines.map(({ from, to }, i) => ( + + ))} ); } diff --git a/packages/vx-grid/src/grids/GridRows.tsx b/packages/vx-grid/src/grids/GridRows.tsx index f90ee1a14..268bd350b 100644 --- a/packages/vx-grid/src/grids/GridRows.tsx +++ b/packages/vx-grid/src/grids/GridRows.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import Line, { LineProps } from '@vx/shape/lib/shapes/Line'; import { Group } from '@vx/group'; import { Point } from '@vx/point'; -import { getTicks, ScaleInput } from '@vx/scale'; +import { getTicks, ScaleInput, coerceNumber } from '@vx/scale'; import { CommonGridProps, GridScale } from '../types'; export type GridRowsProps = CommonGridProps & { @@ -33,6 +33,7 @@ export default function GridRows({ strokeWidth = 1, strokeDasharray, className, + children, numTicks = 10, lineStyle, offset, @@ -40,31 +41,35 @@ export default function GridRows({ ...restProps }: AllGridRowsProps) { const ticks = tickValues ?? getTicks(scale, numTicks); + const tickLines = ticks.map(d => { + const y = offset ? (coerceNumber(scale(d)) || 0) + offset : coerceNumber(scale(d)) || 0; + return { + from: new Point({ + x: 0, + y, + }), + to: new Point({ + x: width, + y, + }), + }; + }); return ( - {ticks.map((d, i) => { - const y = offset ? (scale(d) || 0) + offset : scale(d) || 0; - const fromPoint = new Point({ - x: 0, - y, - }); - const toPoint = new Point({ - x: width, - y, - }); - return ( - - ); - })} + {children + ? children({ lines: tickLines }) + : tickLines.map(({ from, to }, i) => ( + + ))} ); } diff --git a/packages/vx-grid/src/types.ts b/packages/vx-grid/src/types.ts index a20bd4e55..7a684c94d 100644 --- a/packages/vx-grid/src/types.ts +++ b/packages/vx-grid/src/types.ts @@ -1,13 +1,21 @@ -import { D3Scale } from '@vx/scale'; +import { D3Scale, NumberLike } from '@vx/scale'; + +// In order to plot values on an axis, output of the scale must be number. +// Some scales return undefined. +export type GridScaleOutput = number | NumberLike | undefined; /** A catch-all type for scales that are compatible with grid */ -export type GridScale = +export type GridScale = // eslint-disable-next-line @typescript-eslint/no-explicit-any - D3Scale; + D3Scale; + +export type GridLines = { from: { x?: number; y?: number }; to: { x?: number; y?: number } }[]; export type CommonGridProps = { /** classname to apply to line group element. */ className?: string; + /** Optionally override rendering of grid lines. */ + children?: (props: { lines: GridLines }) => React.ReactNode; /** Top offset to apply to glyph g element container. */ top?: number; /** Left offset to apply to glyph g element container. */ diff --git a/packages/vx-react-spring/package.json b/packages/vx-react-spring/package.json index 47e4563ab..4540d643c 100644 --- a/packages/vx-react-spring/package.json +++ b/packages/vx-react-spring/package.json @@ -45,6 +45,7 @@ "@types/classnames": "^2.2.9", "@types/react": "*", "@vx/axis": "0.0.198", + "@vx/grid": "0.0.198", "@vx/scale": "0.0.198", "@vx/text": "0.0.198", "classnames": "^2.2.5", diff --git a/packages/vx-react-spring/src/axis/AnimatedAxis.tsx b/packages/vx-react-spring/src/axis/AnimatedAxis.tsx index 9a49f5e2c..27998802a 100644 --- a/packages/vx-react-spring/src/axis/AnimatedAxis.tsx +++ b/packages/vx-react-spring/src/axis/AnimatedAxis.tsx @@ -1,10 +1,20 @@ -import React from 'react'; +import React, { useMemo } from 'react'; import Axis, { AxisProps } from '@vx/axis/lib/axis/Axis'; -import { AxisScale } from '@vx/axis/lib/types'; +import { AxisScale, TicksRendererProps } from '@vx/axis/lib/types'; import AnimatedTicks from './AnimatedTicks'; +import { TransitionConfig } from '../spring-configs/useLineTransitionConfig'; -export default function AnimatedAxis( - axisProps: Omit, 'ticksComponent'>, -) { - return ; +export default function AnimatedAxis({ + animationTrajectory, + ...axisProps +}: Omit, 'ticksComponent'> & + Pick, 'animationTrajectory'>) { + // wrap the ticksComponent so we can pass animationTrajectory + const ticksComponent = useMemo( + () => (ticks: TicksRendererProps) => ( + + ), + [animationTrajectory], + ); + return ; } diff --git a/packages/vx-react-spring/src/axis/AnimatedTicks.tsx b/packages/vx-react-spring/src/axis/AnimatedTicks.tsx new file mode 100644 index 000000000..2138ddabd --- /dev/null +++ b/packages/vx-react-spring/src/axis/AnimatedTicks.tsx @@ -0,0 +1,82 @@ +import React from 'react'; +import { animated, useTransition, interpolate } from 'react-spring'; +import cx from 'classnames'; +import Orientation from '@vx/axis/lib/constants/orientation'; +import { TicksRendererProps, AxisScale } from '@vx/axis/lib/types'; +import { Text } from '@vx/text'; + +import useLineTransitionConfig, { + TransitionConfig, +} from '../spring-configs/useLineTransitionConfig'; + +export default function AnimatedTicks({ + hideTicks, + horizontal, + orientation, + scale, + tickClassName, + tickLabelProps: allTickLabelProps, + tickStroke = '#222', + tickTransform, + ticks, + animationTrajectory, +}: TicksRendererProps & Pick, 'animationTrajectory'>) { + const animatedTicks = useTransition(ticks, tick => `${tick.value}-${horizontal}`, { + unique: true, + ...useLineTransitionConfig({ scale, animateXOrY: horizontal ? 'x' : 'y', animationTrajectory }), + }); + + return ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {animatedTicks.map( + ( + { + item, + key, + // @ts-ignore react-spring types only include CSSProperties + props: { fromX, toX, fromY, toY, opacity }, + }, + index, + ) => { + const tickLabelProps = allTickLabelProps[index] ?? allTickLabelProps[0] ?? {}; + return ( + + {!hideTicks && ( + + )} + {/** animate the group, not the Text */} + + `translate(${interpolatedX},${interpolatedY + + (orientation === Orientation.bottom && + typeof tickLabelProps.fontSize === 'number' + ? tickLabelProps.fontSize ?? 10 + : 0)})`, + )} + opacity={opacity} + > + {item.formattedValue} + + + ); + }, + )} + + ); +} diff --git a/packages/vx-react-spring/src/axis/AnimatedTicks/index.tsx b/packages/vx-react-spring/src/axis/AnimatedTicks/index.tsx deleted file mode 100644 index 128115d32..000000000 --- a/packages/vx-react-spring/src/axis/AnimatedTicks/index.tsx +++ /dev/null @@ -1,62 +0,0 @@ -import React from 'react'; -import { animated, useTransition, interpolate } from 'react-spring'; -import cx from 'classnames'; -import Orientation from '@vx/axis/lib/constants/orientation'; -import { TicksRendererProps, AxisScale } from '@vx/axis/lib/types'; -import { Text } from '@vx/text'; - -import useTickTransitionConfig from './useTickTransitionConfig'; - -export default function AnimatedTicks({ - hideTicks, - horizontal, - orientation, - scale, - tickClassName, - tickLabelProps: allTickLabelProps, - tickStroke = '#222', - tickTransform, - ticks, -}: TicksRendererProps) { - const transitionConfig = useTickTransitionConfig({ horizontal, scale }); - const animatedTicks = useTransition(ticks, tick => `${tick.value}-${horizontal}`, { - unique: true, - ...transitionConfig, - }); - - return animatedTicks.map(({ item, key, props }, index) => { - // @ts-ignore react-spring types only include CSSProperties - const { fromX, toX, fromY, toY, opacity } = props; - const tickLabelProps = allTickLabelProps[index] ?? allTickLabelProps[0] ?? {}; - return ( - - {!hideTicks && ( - - )} - {/** animate the group, not the Text */} - - `translate(${interpolatedX},${interpolatedY + - (orientation === Orientation.bottom && typeof tickLabelProps.fontSize === 'number' - ? tickLabelProps.fontSize ?? 10 - : 0)})`, - )} - opacity={opacity} - > - {item.formattedValue} - - - ); - }); -} diff --git a/packages/vx-react-spring/src/axis/AnimatedTicks/useTickTransitionConfig.ts b/packages/vx-react-spring/src/axis/AnimatedTicks/useTickTransitionConfig.ts deleted file mode 100644 index 9a956607e..000000000 --- a/packages/vx-react-spring/src/axis/AnimatedTicks/useTickTransitionConfig.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useMemo } from 'react'; -import { coerceNumber } from '@vx/scale'; -import { AxisScale, ComputedTick, TicksRendererProps } from '@vx/axis/lib/types'; - -function enterUpdate({ from, to }: ComputedTick) { - return { - fromX: from.x, - toX: to.x, - fromY: from.y, - toY: to.y, - opacity: 1, - }; -} - -export default function useTickTransitionConfig({ - horizontal, - scale, -}: Pick, 'scale' | 'horizontal'>) { - return useMemo(() => { - const [a, b] = scale.range(); - const isDescending = b != null && a != null && b < a; - const [minPosition, maxPosition] = isDescending ? [b, a] : [a, b]; - const scaleLength = b != null && a != null ? Math.abs(coerceNumber(b) - coerceNumber(a)) : 0; - - const fromLeave = ({ from, to, value }: ComputedTick) => { - const scaledValue = scale(value) ?? 0; - - return { - fromX: horizontal - ? // for top/bottom scales, enter from left or right based on value - scaledValue < scaleLength / 2 - ? minPosition - : maxPosition - : // for left/right scales, don't animate x - from.x, - // same logic as above for the `to` Point - toX: horizontal ? (scaledValue < scaleLength / 2 ? minPosition : maxPosition) : to.x, - // for top/bottom scales, don't animate y - fromY: horizontal - ? // for top/bottom scales, don't animate y - from.y - : // for left/right scales, animate from top or bottom based on value - scaledValue < scaleLength / 2 - ? minPosition - : maxPosition, - // same logic as above for the `to` Point - toY: horizontal ? to.y : scaledValue < scaleLength / 2 ? minPosition : maxPosition, - opacity: 0, - }; - }; - - return { from: fromLeave, leave: fromLeave, enter: enterUpdate, update: enterUpdate }; - }, [horizontal, scale]); -} diff --git a/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx b/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx new file mode 100644 index 000000000..bf0dea5fa --- /dev/null +++ b/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx @@ -0,0 +1,75 @@ +import React from 'react'; +import { animated, useTransition } from 'react-spring'; +import GridRows, { GridRowsProps } from '@vx/grid/lib/grids/GridRows'; +import { GridScale, GridLines } from '@vx/grid/lib/types'; +import useLineTransitionConfig, { + TransitionConfig, +} from '../spring-configs/useLineTransitionConfig'; + +export default function AnimatedGridRows({ + scale, + width, + numTicks, + tickValues, + offset, + className, + animationTrajectory, + ...lineProps +}: Omit, 'children'> & Pick, 'animationTrajectory'>) { + return ( + + {({ lines }) => ( + + )} + + ); +} + +function AnimatedGridRowLines({ + scale, + lines, + animationTrajectory, + ...lineProps +}: Omit, 'children' | 'width' | 'numTicks' | 'offset' | 'className'> & { + lines: GridLines; +} & Pick, 'animationTrajectory'>) { + const animatedLines = useTransition(lines, line => `${line?.from?.y}`, { + unique: true, + ...useLineTransitionConfig({ + scale, + animateXOrY: 'y', + animationTrajectory, + }), + }); + + return ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {animatedLines.map(( + // @ts-ignore react-spring types only include CSSProperties + { key, props: { fromX, toX, fromY, toY, opacity } }, + ) => ( + + ))} + + ); +} diff --git a/packages/vx-react-spring/src/index.ts b/packages/vx-react-spring/src/index.ts index 08ee44bf0..e034deb5a 100644 --- a/packages/vx-react-spring/src/index.ts +++ b/packages/vx-react-spring/src/index.ts @@ -1,2 +1,3 @@ export { default as AnimatedTicks } from './axis/AnimatedTicks'; export { default as AnimatedAxis } from './axis/AnimatedAxis'; +export { default as AnimatedGridRows } from './grid/AnimatedGridRows'; diff --git a/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts new file mode 100644 index 000000000..5428f8c24 --- /dev/null +++ b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts @@ -0,0 +1,89 @@ +import { useMemo } from 'react'; +import { coerceNumber } from '@vx/scale'; +import { AxisScale } from '@vx/axis/lib/types'; + +interface Point { + x?: number; + y?: number; +} + +interface Line { + from: Point; + to: Point; +} + +function getAnimatedValue( + animationTrajectory: 'outside' | 'inside' | 'min' | 'max', + positionOnScale: number | undefined, + scaleMin: number | undefined, + scaleMax: number | undefined, + scaleHalfwayPoint: number, +): number { + switch (animationTrajectory) { + case 'inside': + return scaleHalfwayPoint; + case 'min': + return scaleMin ?? 0; + case 'max': + return scaleMax ?? 0; + case 'outside': + default: + return ((positionOnScale ?? 0) < scaleHalfwayPoint ? scaleMin : scaleMax) ?? 0; + } +} + +function enterUpdate({ from, to }: Line) { + return { + fromX: from.x, + toX: to.x, + fromY: from.y, + toY: to.y, + opacity: 1, + }; +} + +export type TransitionConfig = { + /** Scale along which animation occurs. */ + scale: Scale; + /** Whether to animate the `x` or `y` values of a Line. */ + animateXOrY: 'x' | 'y'; + /** The scale position entering lines come from, and exiting lines leave to. */ + animationTrajectory?: 'outside' | 'inside' | 'min' | 'max'; +}; + +/** + * A hook that returns `react-spring` transition config for animating a Line + * horizontally, vertically, and from a specific starting point. + */ +export default function useLineTransitionConfig({ + scale, + animateXOrY, + animationTrajectory = 'outside', +}: TransitionConfig) { + const shouldAnimateX = animateXOrY === 'x'; + return useMemo(() => { + const [a, b] = scale.range().map(coerceNumber); + const isDescending = b != null && a != null && b < a; + const [scaleMin, scaleMax] = isDescending ? [b, a] : [a, b]; + const scaleLength = b != null && a != null ? Math.abs(b - a) : 0; + const scaleHalfwayPoint = scaleLength / 2; + + const fromLeave = ({ from, to }: Line) => ({ + fromX: shouldAnimateX + ? getAnimatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) + : from.x, + toX: shouldAnimateX + ? getAnimatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) + : to.x, + fromY: shouldAnimateX + ? from.y + : getAnimatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), + toY: shouldAnimateX + ? to.y + : getAnimatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), + opacity: 0, + }); + + return { from: fromLeave, leave: fromLeave, enter: enterUpdate, update: enterUpdate }; + }, [scale, shouldAnimateX, animationTrajectory]); +} From 2d135cded5877a37dbabe4824adcb5bdb9a51d22 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 20 Aug 2020 15:08:41 -0700 Subject: [PATCH 2/8] new(vx-react-spring): add AnimatedGridColumns, factor out AnimatedGridLines, demo improvements --- .../vx-demo/src/sandboxes/vx-axis/Example.tsx | 162 +++++++++--------- packages/vx-grid/src/grids/Grid.tsx | 4 +- packages/vx-grid/src/grids/GridColumns.tsx | 8 +- .../src/grid/AnimatedGridColumns.tsx | 38 ++++ .../src/grid/AnimatedGridLines.tsx | 51 ++++++ .../src/grid/AnimatedGridRows.tsx | 50 +----- packages/vx-react-spring/src/index.ts | 1 + .../spring-configs/useLineTransitionConfig.ts | 23 ++- 8 files changed, 200 insertions(+), 137 deletions(-) create mode 100644 packages/vx-react-spring/src/grid/AnimatedGridColumns.tsx create mode 100644 packages/vx-react-spring/src/grid/AnimatedGridLines.tsx diff --git a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx index 29aa5777b..66a66fea5 100644 --- a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx +++ b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx @@ -1,10 +1,9 @@ -import React, { useState } from 'react'; +import React, { useState, useMemo } from 'react'; import AreaClosed from '@vx/shape/lib/shapes/AreaClosed'; -import { Grid } from '@vx/grid'; import { curveMonotoneX } from '@vx/curve'; import { scaleUtc, scaleLinear, scaleLog, scaleBand, ScaleInput, coerceNumber } from '@vx/scale'; import { Orientation, SharedAxisProps, AxisScale } from '@vx/axis'; -import { AnimatedAxis, AnimatedGridRows } from '@vx/react-spring'; +import { AnimatedAxis, AnimatedGridRows, AnimatedGridColumns } from '@vx/react-spring'; import { LinearGradient } from '@vx/gradient'; import { timeFormat } from 'd3-time-format'; @@ -13,14 +12,20 @@ const axisColor = '#fff'; const tickLabelColor = '#fff'; export const labelColor = '#340098'; const gridColor = '#6e0fca'; -const numTickColumns = 5; const margin = { top: 40, right: 150, - bottom: 50, + bottom: 20, left: 50, }; +const tickLabelProps = () => ({ + fill: tickLabelColor, + fontSize: 12, + fontFamily: 'sans-serif', + textAnchor: 'middle', +}); + const getMinMax = (vals: (number | { valueOf(): number })[]) => { const numericVals = vals.map(coerceNumber); return [Math.min(...numericVals), Math.max(...numericVals)]; @@ -45,68 +50,70 @@ export default function Example({ 'outside' | 'inside' | 'min' | 'max' >('min'); - if (width < 10) return null; - interface AxisDemoProps extends SharedAxisProps { values: ScaleInput[]; } - // toggle between two value ranges to demo animation - const linearValues = dataToggle ? [0, 2, 4, 6, 8, 10] : [6, 8, 10, 12]; - const bandValues = dataToggle ? ['a', 'b', 'c', 'd'] : ['d', 'c', 'b', 'a']; - const timeValues = dataToggle - ? [new Date('2020-01-01'), new Date('2020-02-01')] - : [new Date('2020-02-01'), new Date('2020-03-01')]; - const logValues = dataToggle ? [1, 10, 100, 1000, 10000] : [0.0001, 0.001, 0.1, 1, 10, 100]; + const axes: AxisDemoProps>[] = useMemo(() => { + // toggle between two value ranges to demo animation + const linearValues = dataToggle ? [0, 2, 4, 6, 8, 10] : [6, 8, 10, 12]; + const bandValues = dataToggle ? ['a', 'b', 'c', 'd'] : ['d', 'c', 'b', 'a']; + const timeValues = dataToggle + ? [new Date('2020-01-01'), new Date('2020-02-01')] + : [new Date('2020-02-01'), new Date('2020-03-01')]; + const logValues = dataToggle ? [1, 10, 100, 1000, 10000] : [0.0001, 0.001, 0.1, 1, 10, 100]; - const axes: AxisDemoProps>[] = [ - { - scale: scaleLinear({ - domain: getMinMax(linearValues), - range: [0, width], - }), - values: linearValues, - tickFormat: (v: number, index: number, ticks: { value: number; index: number }[]) => - index === 0 ? 'first' : index === ticks[ticks.length - 1].index ? 'last' : `${v}`, - label: 'linear', - }, - { - scale: scaleBand({ - domain: bandValues, - range: [0, width], - paddingOuter: 0, - paddingInner: 1, - }), - values: bandValues, - tickFormat: (v: string) => v, - label: 'categories', - }, - { - scale: scaleUtc({ - domain: getMinMax(timeValues), - range: [0, width], - }), - values: timeValues, - tickFormat: (v: Date, i: number) => - i === 3 ? '🎉' : width > 400 || i % 2 === 0 ? timeFormat('%b %d')(v) : '', - label: 'time', - }, - { - scale: scaleLog({ - domain: getMinMax(logValues), - range: [0, width], - }), - values: logValues, - tickFormat: (v: number) => { - const asString = `${v}`; - // label only major ticks - return asString.match(/^[.01?[\]]*$/) ? asString : ''; + return [ + { + scale: scaleLinear({ + domain: getMinMax(linearValues), + range: [0, width], + }), + values: linearValues, + tickFormat: (v: number, index: number, ticks: { value: number; index: number }[]) => + index === 0 ? 'first' : index === ticks[ticks.length - 1].index ? 'last' : `${v}`, + label: 'linear', + }, + { + scale: scaleBand({ + domain: bandValues, + range: [0, width], + paddingOuter: 0, + paddingInner: 1, + }), + values: bandValues, + tickFormat: (v: string) => v, + label: 'categories', + }, + { + scale: scaleUtc({ + domain: getMinMax(timeValues), + range: [0, width], + }), + values: timeValues, + tickFormat: (v: Date, i: number) => + i === 3 ? '🎉' : width > 400 || i % 2 === 0 ? timeFormat('%b %d')(v) : '', + label: 'time', }, - label: 'log', - }, - ]; + { + scale: scaleLog({ + domain: getMinMax(logValues), + range: [0, width], + }), + values: logValues, + tickFormat: (v: number) => { + const asString = `${v}`; + // label only major ticks + return asString.match(/^[.01?[\]]*$/) ? asString : ''; + }, + label: 'log', + }, + ]; + }, [dataToggle, width]); + + if (width < 10) return null; - const scalePadding = 50; + const scalePadding = 40; const scaleHeight = height / axes.length - scalePadding; const yScale = scaleLinear({ @@ -134,26 +141,28 @@ export default function Example({ {axes.map(({ scale, values, label, tickFormat }, i) => ( - {/* */} + [ (scale(x) ?? 0) + + // offset point half of band width for band scales ('bandwidth' in scale && typeof scale!.bandwidth !== 'undefined' ? scale.bandwidth!() / 2 : 0), @@ -165,18 +174,15 @@ export default function Example({ fillOpacity={0.2} /> ({ - fill: tickLabelColor, - fontSize: 12, - fontFamily: 'sans-serif', - textAnchor: 'middle', - })} + tickLabelProps={tickLabelProps} tickValues={label === 'log' || label === 'time' ? undefined : values} numTicks={label === 'time' ? 6 : undefined} label={label} @@ -199,8 +205,7 @@ export default function Example({ {showControls && ( <> - -
+
animation trajectory
+ )} diff --git a/packages/vx-grid/src/grids/Grid.tsx b/packages/vx-grid/src/grids/Grid.tsx index ff518743c..610080663 100644 --- a/packages/vx-grid/src/grids/Grid.tsx +++ b/packages/vx-grid/src/grids/Grid.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import { Group } from '@vx/group'; import { ScaleInput } from '@vx/scale'; import GridRows, { AllGridRowsProps } from './GridRows'; -import GridColumns, { AllGridColumnProps } from './GridColumns'; +import GridColumns, { AllGridColumnsProps } from './GridColumns'; import { CommonGridProps, GridScale } from '../types'; type CommonPropsToOmit = @@ -17,7 +17,7 @@ type CommonPropsToOmit = | 'children'; export type GridProps = Omit< - AllGridRowsProps & AllGridColumnProps, + AllGridRowsProps & AllGridColumnsProps, CommonPropsToOmit > & { /** `@vx/scale` or `d3-scale` object used to map from ScaleInput to x-coordinates (GridColumns). */ diff --git a/packages/vx-grid/src/grids/GridColumns.tsx b/packages/vx-grid/src/grids/GridColumns.tsx index 3e0c024ca..8ded12524 100644 --- a/packages/vx-grid/src/grids/GridColumns.tsx +++ b/packages/vx-grid/src/grids/GridColumns.tsx @@ -6,7 +6,7 @@ import { Point } from '@vx/point'; import { getTicks, ScaleInput, coerceNumber } from '@vx/scale'; import { CommonGridProps, GridScale } from '../types'; -export type GridColumnProps = CommonGridProps & { +export type GridColumnsProps = CommonGridProps & { /** `@vx/scale` or `d3-scale` object used to convert value to position. */ scale: Scale; /** @@ -18,10 +18,10 @@ export type GridColumnProps = CommonGridProps & { height: number; }; -export type AllGridColumnProps = GridColumnProps & +export type AllGridColumnsProps = GridColumnsProps & Omit< LineProps & Omit, keyof LineProps>, - keyof GridColumnProps + keyof GridColumnsProps >; export default function GridColumns({ @@ -39,7 +39,7 @@ export default function GridColumns({ tickValues, children, ...restProps -}: AllGridColumnProps) { +}: AllGridColumnsProps) { const ticks = tickValues ?? getTicks(scale, numTicks); const tickLines = ticks.map(d => { const x = offset ? (coerceNumber(scale(d)) || 0) + offset : coerceNumber(scale(d)) || 0; diff --git a/packages/vx-react-spring/src/grid/AnimatedGridColumns.tsx b/packages/vx-react-spring/src/grid/AnimatedGridColumns.tsx new file mode 100644 index 000000000..2b5a337cd --- /dev/null +++ b/packages/vx-react-spring/src/grid/AnimatedGridColumns.tsx @@ -0,0 +1,38 @@ +import React from 'react'; +import GridColumns, { GridColumnsProps } from '@vx/grid/lib/grids/GridColumns'; +import { GridScale } from '@vx/grid/lib/types'; +import { TransitionConfig } from '../spring-configs/useLineTransitionConfig'; +import AnimatedGridLines from './AnimatedGridLines'; + +export default function AnimatedGridColumns({ + scale, + height, + numTicks, + tickValues, + offset, + className, + animationTrajectory, + ...lineProps +}: Omit, 'children'> & + Pick, 'animationTrajectory'>) { + return ( + + {({ lines }) => ( + String(line?.from?.x ?? '')} + {...lineProps} + /> + )} + + ); +} diff --git a/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx b/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx new file mode 100644 index 000000000..8735052a6 --- /dev/null +++ b/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx @@ -0,0 +1,51 @@ +import React, { SVGProps } from 'react'; +import { animated, useTransition } from 'react-spring'; +import { GridScale, GridLines } from '@vx/grid/lib/types'; +import useLineTransitionConfig, { + TransitionConfig, +} from '../spring-configs/useLineTransitionConfig'; + +export type AnimatedGridLinesProps = { + lines: GridLines; + lineKey: (line: GridLines[number]) => string; + scale: Scale; +} & Omit, 'ref'> & + Pick, 'animationTrajectory' | 'animateXOrY'>; + +export default function AnimatedGridLines({ + scale, + lines, + animationTrajectory, + animateXOrY, + lineKey, + ...lineProps +}: AnimatedGridLinesProps) { + const animatedLines = useTransition(lines, lineKey, { + unique: true, + ...useLineTransitionConfig({ + scale, + animateXOrY, + animationTrajectory, + }), + }); + + return ( + // eslint-disable-next-line react/jsx-no-useless-fragment + <> + {animatedLines.map(( + // @ts-ignore react-spring types only include CSSProperties + { key, props: { fromX, toX, fromY, toY, opacity } }, + ) => ( + + ))} + + ); +} diff --git a/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx b/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx index bf0dea5fa..0b98323ff 100644 --- a/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx +++ b/packages/vx-react-spring/src/grid/AnimatedGridRows.tsx @@ -1,10 +1,8 @@ import React from 'react'; -import { animated, useTransition } from 'react-spring'; import GridRows, { GridRowsProps } from '@vx/grid/lib/grids/GridRows'; -import { GridScale, GridLines } from '@vx/grid/lib/types'; -import useLineTransitionConfig, { - TransitionConfig, -} from '../spring-configs/useLineTransitionConfig'; +import { GridScale } from '@vx/grid/lib/types'; +import { TransitionConfig } from '../spring-configs/useLineTransitionConfig'; +import AnimatedGridLines from './AnimatedGridLines'; export default function AnimatedGridRows({ scale, @@ -25,51 +23,15 @@ export default function AnimatedGridRows({ className={className} > {({ lines }) => ( - String(line?.from?.y ?? '')} {...lineProps} /> )} ); } - -function AnimatedGridRowLines({ - scale, - lines, - animationTrajectory, - ...lineProps -}: Omit, 'children' | 'width' | 'numTicks' | 'offset' | 'className'> & { - lines: GridLines; -} & Pick, 'animationTrajectory'>) { - const animatedLines = useTransition(lines, line => `${line?.from?.y}`, { - unique: true, - ...useLineTransitionConfig({ - scale, - animateXOrY: 'y', - animationTrajectory, - }), - }); - - return ( - // eslint-disable-next-line react/jsx-no-useless-fragment - <> - {animatedLines.map(( - // @ts-ignore react-spring types only include CSSProperties - { key, props: { fromX, toX, fromY, toY, opacity } }, - ) => ( - - ))} - - ); -} diff --git a/packages/vx-react-spring/src/index.ts b/packages/vx-react-spring/src/index.ts index e034deb5a..f27460440 100644 --- a/packages/vx-react-spring/src/index.ts +++ b/packages/vx-react-spring/src/index.ts @@ -1,3 +1,4 @@ export { default as AnimatedTicks } from './axis/AnimatedTicks'; export { default as AnimatedAxis } from './axis/AnimatedAxis'; export { default as AnimatedGridRows } from './grid/AnimatedGridRows'; +export { default as AnimatedGridColumns } from './grid/AnimatedGridColumns'; diff --git a/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts index 5428f8c24..5e08c4d93 100644 --- a/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts +++ b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts @@ -1,6 +1,7 @@ import { useMemo } from 'react'; import { coerceNumber } from '@vx/scale'; import { AxisScale } from '@vx/axis/lib/types'; +import { GridScale } from '@vx/grid/lib/types'; interface Point { x?: number; @@ -12,7 +13,7 @@ interface Line { to: Point; } -function getAnimatedValue( +function animatedValue( animationTrajectory: 'outside' | 'inside' | 'min' | 'max', positionOnScale: number | undefined, scaleMin: number | undefined, @@ -42,7 +43,7 @@ function enterUpdate({ from, to }: Line) { }; } -export type TransitionConfig = { +export type TransitionConfig = { /** Scale along which animation occurs. */ scale: Scale; /** Whether to animate the `x` or `y` values of a Line. */ @@ -55,10 +56,10 @@ export type TransitionConfig = { * A hook that returns `react-spring` transition config for animating a Line * horizontally, vertically, and from a specific starting point. */ -export default function useLineTransitionConfig({ +export default function useLineTransitionConfig({ scale, animateXOrY, - animationTrajectory = 'outside', + animationTrajectory: initAnimationTrajectory = 'outside', }: TransitionConfig) { const shouldAnimateX = animateXOrY === 'x'; return useMemo(() => { @@ -67,23 +68,27 @@ export default function useLineTransitionConfig({ const [scaleMin, scaleMax] = isDescending ? [b, a] : [a, b]; const scaleLength = b != null && a != null ? Math.abs(b - a) : 0; const scaleHalfwayPoint = scaleLength / 2; + let animationTrajectory = initAnimationTrajectory; + // correct direction for descending scales (like y-axis) + if (isDescending && initAnimationTrajectory === 'min') animationTrajectory = 'max'; + if (isDescending && initAnimationTrajectory === 'max') animationTrajectory = 'min'; const fromLeave = ({ from, to }: Line) => ({ fromX: shouldAnimateX - ? getAnimatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) + ? animatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) : from.x, toX: shouldAnimateX - ? getAnimatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) + ? animatedValue(animationTrajectory, from.x, scaleMin, scaleMax, scaleHalfwayPoint) : to.x, fromY: shouldAnimateX ? from.y - : getAnimatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), + : animatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), toY: shouldAnimateX ? to.y - : getAnimatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), + : animatedValue(animationTrajectory, from.y, scaleMin, scaleMax, scaleHalfwayPoint), opacity: 0, }); return { from: fromLeave, leave: fromLeave, enter: enterUpdate, update: enterUpdate }; - }, [scale, shouldAnimateX, animationTrajectory]); + }, [scale, shouldAnimateX, initAnimationTrajectory]); } From 98a1f7e202995b75c455ef0a6182a11dcec7410c Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Thu, 20 Aug 2020 20:02:13 -0700 Subject: [PATCH 3/8] test(vx-react-spring): add AnimatedGridColumns + AnimatedGridRows tests --- packages/vx-react-spring/test/AnimatedGridColumns.test.tsx | 7 +++++++ packages/vx-react-spring/test/AnimatedGridRows.test.tsx | 7 +++++++ .../vx-react-spring/test/useLineTransitionConfig.test.tsx | 7 +++++++ .../vx-react-spring/test/useTickTransitionConfig.test.tsx | 7 ------- 4 files changed, 21 insertions(+), 7 deletions(-) create mode 100644 packages/vx-react-spring/test/AnimatedGridColumns.test.tsx create mode 100644 packages/vx-react-spring/test/AnimatedGridRows.test.tsx create mode 100644 packages/vx-react-spring/test/useLineTransitionConfig.test.tsx delete mode 100644 packages/vx-react-spring/test/useTickTransitionConfig.test.tsx diff --git a/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx b/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx new file mode 100644 index 000000000..bdf70a5cb --- /dev/null +++ b/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx @@ -0,0 +1,7 @@ +import { AnimatedGridColumns } from '../src'; + +describe('AnimatedGridColumns', () => { + it('should be defined', () => { + expect(AnimatedGridColumns).toBeDefined(); + }); +}); diff --git a/packages/vx-react-spring/test/AnimatedGridRows.test.tsx b/packages/vx-react-spring/test/AnimatedGridRows.test.tsx new file mode 100644 index 000000000..f6e9471dd --- /dev/null +++ b/packages/vx-react-spring/test/AnimatedGridRows.test.tsx @@ -0,0 +1,7 @@ +import { AnimatedGridRows } from '../src'; + +describe('AnimatedGridRows', () => { + it('should be defined', () => { + expect(AnimatedGridRows).toBeDefined(); + }); +}); diff --git a/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx b/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx new file mode 100644 index 000000000..81d9dbc7b --- /dev/null +++ b/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx @@ -0,0 +1,7 @@ +import useLineTransitionConfig from '../src/spring-configs/useLineTransitionConfig'; + +describe('useLineTransitionConfig', () => { + it('should be defined', () => { + expect(useLineTransitionConfig).toBeDefined(); + }); +}); diff --git a/packages/vx-react-spring/test/useTickTransitionConfig.test.tsx b/packages/vx-react-spring/test/useTickTransitionConfig.test.tsx deleted file mode 100644 index a70d9146c..000000000 --- a/packages/vx-react-spring/test/useTickTransitionConfig.test.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import useTickTransitionConfig from '../src/axis/AnimatedTicks/useTickTransitionConfig'; - -describe('useTickTransitionConfig', () => { - it('should be defined', () => { - expect(useTickTransitionConfig).toBeDefined(); - }); -}); From 2fa6038e5037b286c2479efc1c3886ca605c15fd Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 21 Aug 2020 08:49:52 -0700 Subject: [PATCH 4/8] tests(vx-react-spring): add more and better tests --- .../spring-configs/useLineTransitionConfig.ts | 6 +- .../test/AnimatedAxis.test.tsx | 9 ++ .../test/AnimatedGridColumns.test.tsx | 13 +++ .../test/AnimatedGridRows.test.tsx | 10 +++ .../test/AnimatedTicks.test.tsx | 19 ++++ .../test/useLineTransitionConfig.test.tsx | 87 +++++++++++++++++++ 6 files changed, 141 insertions(+), 3 deletions(-) diff --git a/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts index 5e08c4d93..3f37e36f3 100644 --- a/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts +++ b/packages/vx-react-spring/src/spring-configs/useLineTransitionConfig.ts @@ -14,14 +14,14 @@ interface Line { } function animatedValue( - animationTrajectory: 'outside' | 'inside' | 'min' | 'max', + animationTrajectory: 'outside' | 'center' | 'min' | 'max', positionOnScale: number | undefined, scaleMin: number | undefined, scaleMax: number | undefined, scaleHalfwayPoint: number, ): number { switch (animationTrajectory) { - case 'inside': + case 'center': return scaleHalfwayPoint; case 'min': return scaleMin ?? 0; @@ -49,7 +49,7 @@ export type TransitionConfig = { /** Whether to animate the `x` or `y` values of a Line. */ animateXOrY: 'x' | 'y'; /** The scale position entering lines come from, and exiting lines leave to. */ - animationTrajectory?: 'outside' | 'inside' | 'min' | 'max'; + animationTrajectory?: 'outside' | 'center' | 'min' | 'max'; }; /** diff --git a/packages/vx-react-spring/test/AnimatedAxis.test.tsx b/packages/vx-react-spring/test/AnimatedAxis.test.tsx index a64a832f4..67260bfd6 100644 --- a/packages/vx-react-spring/test/AnimatedAxis.test.tsx +++ b/packages/vx-react-spring/test/AnimatedAxis.test.tsx @@ -1,7 +1,16 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { scaleLinear } from '@vx/scale'; import { AnimatedAxis } from '../src'; describe('AnimatedAxis', () => { it('should be defined', () => { expect(AnimatedAxis).toBeDefined(); }); + + it('should not throw', () => { + expect(() => + shallow(), + ).not.toThrow(); + }); }); diff --git a/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx b/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx index bdf70a5cb..d5df311cf 100644 --- a/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx +++ b/packages/vx-react-spring/test/AnimatedGridColumns.test.tsx @@ -1,7 +1,20 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { scaleLinear } from '@vx/scale'; import { AnimatedGridColumns } from '../src'; describe('AnimatedGridColumns', () => { it('should be defined', () => { expect(AnimatedGridColumns).toBeDefined(); }); + it('should not throw', () => { + expect(() => + shallow( + , + ), + ).not.toThrow(); + }); }); diff --git a/packages/vx-react-spring/test/AnimatedGridRows.test.tsx b/packages/vx-react-spring/test/AnimatedGridRows.test.tsx index f6e9471dd..7d1d1906c 100644 --- a/packages/vx-react-spring/test/AnimatedGridRows.test.tsx +++ b/packages/vx-react-spring/test/AnimatedGridRows.test.tsx @@ -1,7 +1,17 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { scaleLinear } from '@vx/scale'; import { AnimatedGridRows } from '../src'; describe('AnimatedGridRows', () => { it('should be defined', () => { expect(AnimatedGridRows).toBeDefined(); }); + it('should not throw', () => { + expect(() => + shallow( + , + ), + ).not.toThrow(); + }); }); diff --git a/packages/vx-react-spring/test/AnimatedTicks.test.tsx b/packages/vx-react-spring/test/AnimatedTicks.test.tsx index a9cfe08c1..ffa900df7 100644 --- a/packages/vx-react-spring/test/AnimatedTicks.test.tsx +++ b/packages/vx-react-spring/test/AnimatedTicks.test.tsx @@ -1,7 +1,26 @@ +import React from 'react'; +import { shallow } from 'enzyme'; +import { scaleLinear } from '@vx/scale'; import { AnimatedTicks } from '../src'; describe('AnimatedTicks', () => { it('should be defined', () => { expect(AnimatedTicks).toBeDefined(); }); + it('should not throw', () => { + expect(() => + shallow( + , + ), + ).not.toThrow(); + }); }); diff --git a/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx b/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx index 81d9dbc7b..245efb5d3 100644 --- a/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx +++ b/packages/vx-react-spring/test/useLineTransitionConfig.test.tsx @@ -1,7 +1,94 @@ +import React from 'react'; +import { scaleLinear } from '@vx/scale'; +import { shallow } from 'enzyme'; import useLineTransitionConfig from '../src/spring-configs/useLineTransitionConfig'; +const scale = scaleLinear({ domain: [0, 10], range: [0, 10] }); +const invertedScale = scaleLinear({ domain: [0, 10], range: [10, 0] }); +const verticalLine = { from: { x: 0, y: 0 }, to: { x: 0, y: 5 } }; +const verticalLineMax = { from: { x: 8, y: 0 }, to: { x: 8, y: 5 } }; + describe('useLineTransitionConfig', () => { it('should be defined', () => { expect(useLineTransitionConfig).toBeDefined(); }); + it('should return react-spring config with from, enter, update, leave keys', () => { + expect.assertions(1); + function HookTest() { + const config = useLineTransitionConfig({ scale, animateXOrY: 'x' }); + expect(config).toMatchObject({ + from: expect.any(Function), + enter: expect.any(Function), + update: expect.any(Function), + leave: expect.any(Function), + }); + return null; + } + shallow(); + }); + it('should animate from scale min', () => { + expect.assertions(2); + function HookTest() { + const config = useLineTransitionConfig({ + scale, + animateXOrY: 'x', + animationTrajectory: 'min', + }); + const invertedConfig = useLineTransitionConfig({ + scale: invertedScale, + animateXOrY: 'y', + animationTrajectory: 'min', + }); + expect(config.from(verticalLine).fromX).toBe(0); + expect(invertedConfig.from(verticalLine).fromY).toBe(10); + return null; + } + shallow(); + }); + it('should animate from scale max', () => { + expect.assertions(2); + function HookTest() { + const config = useLineTransitionConfig({ + scale, + animateXOrY: 'x', + animationTrajectory: 'max', + }); + const invertedConfig = useLineTransitionConfig({ + scale: invertedScale, + animateXOrY: 'y', + animationTrajectory: 'max', + }); + expect(config.from(verticalLine).fromX).toBe(10); + expect(invertedConfig.from(verticalLine).fromY).toBe(0); + return null; + } + shallow(); + }); + it('should animate from outside', () => { + expect.assertions(2); + function HookTest() { + const config = useLineTransitionConfig({ + scale, + animateXOrY: 'x', + animationTrajectory: 'outside', + }); + expect(config.from(verticalLine).fromX).toBe(0); + expect(config.from(verticalLineMax).fromX).toBe(10); + return null; + } + shallow(); + }); + it('should animate from center', () => { + expect.assertions(1); + function HookTest() { + const config = useLineTransitionConfig({ + scale, + animateXOrY: 'x', + animationTrajectory: 'center', + }); + expect(config.from(verticalLine).fromX).toBe(5); + return null; + } + shallow(); + }); }); From 3bfbe6f8390b9465d588a04add48ed17f4f2dd6f Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 21 Aug 2020 09:06:08 -0700 Subject: [PATCH 5/8] fix(vx-react-spring/AnimatedGridLines): omit SVGProps own scale prop --- packages/vx-react-spring/src/grid/AnimatedGridLines.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx b/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx index 8735052a6..e09af2b01 100644 --- a/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx +++ b/packages/vx-react-spring/src/grid/AnimatedGridLines.tsx @@ -9,7 +9,7 @@ export type AnimatedGridLinesProps = { lines: GridLines; lineKey: (line: GridLines[number]) => string; scale: Scale; -} & Omit, 'ref'> & +} & Omit, 'ref' | 'scale'> & Pick, 'animationTrajectory' | 'animateXOrY'>; export default function AnimatedGridLines({ From 89a9c5a63884aca63df7d178be87b6b8ebd7cbe1 Mon Sep 17 00:00:00 2001 From: Chris Williams Date: Fri, 21 Aug 2020 09:33:22 -0700 Subject: [PATCH 6/8] fix(vx-react-spring): update docs, fix grid prop typo, fix demo type --- packages/vx-axis/src/types.ts | 22 +++++++++---------- .../src/components/Gallery/AxisTile.tsx | 1 + .../vx-demo/src/pages/docs/react-spring.tsx | 4 +++- .../vx-demo/src/sandboxes/vx-axis/Example.tsx | 10 ++++----- packages/vx-grid/src/grids/GridColumns.tsx | 2 +- packages/vx-grid/src/grids/GridRows.tsx | 2 +- 6 files changed, 22 insertions(+), 19 deletions(-) diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index a9c9b486c..38dedff40 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -42,15 +42,15 @@ export type TicksRendererProps = { | 'ticks' >; -interface CommonProps { +export type CommonProps = { /** The class name applied to the axis line element. */ axisLineClassName?: string; /** If true, will hide the axis line. */ - hideAxisLine: boolean; + hideAxisLine?: boolean; /** If true, will hide the ticks (but not the tick labels). */ - hideTicks: boolean; + hideTicks?: boolean; /** If true, will hide the '0' value tick and tick label. */ - hideZero: boolean; + hideZero?: boolean; /** The text for the axis label. */ label?: string; /** The class name applied to the axis label text element. */ @@ -60,11 +60,11 @@ interface CommonProps { /** Props applied to the axis label component. */ labelProps?: Partial; /** The number of ticks wanted for the axis (note this is approximate) */ - numTicks: number; + numTicks?: number; /** Placement of the axis */ - orientation: Orientation; + orientation?: Orientation; /** Pixel padding to apply to both sides of the axis. */ - rangePadding: number; + rangePadding?: number; /** The color for the stroke of the lines. */ stroke?: string; /** The pixel value for the width of the lines. */ @@ -78,16 +78,16 @@ interface CommonProps { /** Override the component used to render all tick lines and labels. */ ticksComponent?: (tickRendererProps: TicksRendererProps) => React.ReactNode; /** A [d3 formatter](https://github.com/d3/d3-scale/blob/master/README.md#continuous_tickFormat) for the tick text. */ - tickFormat: TickFormatter>; + tickFormat?: TickFormatter>; /** A function that returns props for a given tick label. */ tickLabelProps?: TickLabelProps>; /** The length of the tick lines. */ - tickLength: number; + tickLength?: number; /** The color for the tick's stroke value. */ tickStroke?: string; /** A custom SVG transform value to be applied to each tick group. */ tickTransform?: string; -} +}; interface Point { x: number; @@ -119,7 +119,7 @@ export type AxisRendererProps = CommonProps & { ticks: ComputedTick[]; }; -export type SharedAxisProps = Partial> & { +export type SharedAxisProps = CommonProps & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ diff --git a/packages/vx-demo/src/components/Gallery/AxisTile.tsx b/packages/vx-demo/src/components/Gallery/AxisTile.tsx index 0793dbd5e..51c3df9e3 100644 --- a/packages/vx-demo/src/components/Gallery/AxisTile.tsx +++ b/packages/vx-demo/src/components/Gallery/AxisTile.tsx @@ -14,6 +14,7 @@ export default function AxisTile() { title="Axes & scales" description="" detailsStyles={detailsStyles} + detailsHeight={20} exampleProps={exampleProps} exampleRenderer={Axis} exampleUrl="/axis" diff --git a/packages/vx-demo/src/pages/docs/react-spring.tsx b/packages/vx-demo/src/pages/docs/react-spring.tsx index f8bb8721f..63fd0ffd4 100644 --- a/packages/vx-demo/src/pages/docs/react-spring.tsx +++ b/packages/vx-demo/src/pages/docs/react-spring.tsx @@ -1,10 +1,12 @@ import React from 'react'; import ReactSpringReadme from '!!raw-loader!../../../../vx-react-spring/README.md'; import AnimatedAxis from '../../../../vx-react-spring/src/axis/AnimatedAxis'; +import AnimatedGridColumns from '../../../../vx-react-spring/src/grid/AnimatedGridColumns'; +import AnimatedGridRows from '../../../../vx-react-spring/src/grid/AnimatedGridRows'; import DocPage from '../../components/DocPage'; import AxisTile from '../../components/Gallery/AxisTile'; -const components = [AnimatedAxis]; +const components = [AnimatedAxis, AnimatedGridColumns, AnimatedGridRows]; const examples = [AxisTile]; diff --git a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx index 66a66fea5..92b916ea1 100644 --- a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx +++ b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx @@ -47,8 +47,8 @@ export default function Example({ const height = outerHeight - margin.top - margin.bottom; const [dataToggle, setDataToggle] = useState(true); const [animationTrajectory, setAnimationTrajectory] = useState< - 'outside' | 'inside' | 'min' | 'max' - >('min'); + 'outside' | 'center' | 'min' | 'max' + >('center'); interface AxisDemoProps extends SharedAxisProps { values: ScaleInput[]; @@ -218,10 +218,10 @@ export default function Example({