Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

new(vx-react-spring): add AnimatedGridRows/Columns + animationTrajectory #787

Merged
merged 8 commits into from
Aug 21, 2020
5 changes: 3 additions & 2 deletions packages/vx-axis/src/axis/AxisRenderer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { TextProps } from '@vx/text/lib/Text';
import getLabelTransform from '../utils/getLabelTransform';
import { AxisRendererProps, AxisScale } from '../types';
import Ticks from './Ticks';
import { Orientation } from '..';

const defaultTextProps: Partial<TextProps> = {
textAnchor: 'middle',
Expand All @@ -26,15 +27,15 @@ export default function AxisRenderer<Scale extends AxisScale>({
labelClassName,
labelOffset = 14,
labelProps = defaultTextProps,
orientation,
orientation = Orientation.bottom,
scale,
stroke = '#222',
strokeDasharray,
strokeWidth = 1,
tickClassName,
tickComponent,
tickLabelProps = (/** tickValue, index */) => defaultTextProps,
tickLength,
tickLength = 8,
tickStroke = '#222',
tickTransform,
ticks,
Expand Down
22 changes: 11 additions & 11 deletions packages/vx-axis/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ export type TicksRendererProps<Scale extends AxisScale> = {
| 'ticks'
>;

interface CommonProps<Scale extends AxisScale> {
export type CommonProps<Scale extends AxisScale> = {
/** 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. */
Expand All @@ -60,11 +60,11 @@ interface CommonProps<Scale extends AxisScale> {
/** Props applied to the axis label component. */
labelProps?: Partial<TextProps>;
/** 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. */
Expand All @@ -78,16 +78,16 @@ interface CommonProps<Scale extends AxisScale> {
/** Override the component used to render all tick lines and labels. */
ticksComponent?: (tickRendererProps: TicksRendererProps<Scale>) => React.ReactNode;
/** A [d3 formatter](https://github.com/d3/d3-scale/blob/master/README.md#continuous_tickFormat) for the tick text. */
tickFormat: TickFormatter<ScaleInput<Scale>>;
tickFormat?: TickFormatter<ScaleInput<Scale>>;
/** A function that returns props for a given tick label. */
tickLabelProps?: TickLabelProps<ScaleInput<Scale>>;
/** 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;
Expand Down Expand Up @@ -119,7 +119,7 @@ export type AxisRendererProps<Scale extends AxisScale> = CommonProps<Scale> & {
ticks: ComputedTick<Scale>[];
};

export type SharedAxisProps<Scale extends AxisScale> = Partial<CommonProps<Scale>> & {
export type SharedAxisProps<Scale extends AxisScale> = CommonProps<Scale> & {
/** The class name applied to the outermost axis group element. */
axisClassName?: string;
/** A left pixel offset applied to the entire axis. */
Expand Down
1 change: 1 addition & 0 deletions packages/vx-demo/src/components/Gallery/AxisTile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export default function AxisTile() {
title="Axes & scales"
description="<Axis.AxisBottom />"
detailsStyles={detailsStyles}
detailsHeight={20}
exampleProps={exampleProps}
exampleRenderer={Axis}
exampleUrl="/axis"
Expand Down
4 changes: 3 additions & 1 deletion packages/vx-demo/src/pages/docs/react-spring.tsx
Original file line number Diff line number Diff line change
@@ -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];

Expand Down
210 changes: 134 additions & 76 deletions packages/vx-demo/src/sandboxes/vx-axis/Example.tsx
Original file line number Diff line number Diff line change
@@ -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 } from '@vx/react-spring';
import { AnimatedAxis, AnimatedGridRows, AnimatedGridColumns } from '@vx/react-spring';
import { LinearGradient } from '@vx/gradient';
import { timeFormat } from 'd3-time-format';

Expand All @@ -13,14 +12,21 @@ 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',
} as const);

const getMinMax = (vals: (number | { valueOf(): number })[]) => {
const numericVals = vals.map(coerceNumber);
return [Math.min(...numericVals), Math.max(...numericVals)];
Expand All @@ -41,69 +47,74 @@ export default function Example({
const width = outerWidth - margin.left - margin.right;
const height = outerHeight - margin.top - margin.bottom;
const [dataToggle, setDataToggle] = useState(true);

if (width < 10) return null;
const [animationTrajectory, setAnimationTrajectory] = useState<
'outside' | 'center' | 'min' | 'max'
>('center');

interface AxisDemoProps<Scale extends AxisScale> extends SharedAxisProps<Scale> {
values: ScaleInput<Scale>[];
}

// 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<AxisScale<number>>[] = useMemo(() => {
// toggle between two value ranges to demo animation
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all just indented to memoize

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<AxisScale<number>>[] = [
{
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',
},
{
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',
},
label: 'log',
},
];
];
}, [dataToggle, width]);

const scalePadding = 50;
if (width < 10) return null;

const scalePadding = 40;
const scaleHeight = height / axes.length - scalePadding;

const yScale = scaleLinear({
Expand Down Expand Up @@ -131,9 +142,28 @@ export default function Example({
<g transform={`translate(${margin.left},${margin.top})`}>
{axes.map(({ scale, values, label, tickFormat }, i) => (
<g key={`scale-${i}`} transform={`translate(0, ${i * (scaleHeight + scalePadding)})`}>
<AnimatedGridRows
// force remount when this changes to see the animation difference
key={`gridrows-${animationTrajectory}`}
scale={yScale}
stroke={gridColor}
width={width}
numTicks={dataToggle ? 1 : 3}
animationTrajectory={animationTrajectory}
/>
<AnimatedGridColumns
// force remount when this changes to see the animation difference
key={`gridcolumns-${animationTrajectory}`}
scale={scale}
stroke={gridColor}
height={scaleHeight}
numTicks={dataToggle ? 5 : 2}
animationTrajectory={animationTrajectory}
/>
<AreaClosed
data={values.map(x => [
(scale(x) ?? 0) +
// offset point half of band width for band scales
('bandwidth' in scale && typeof scale!.bandwidth !== 'undefined'
? scale.bandwidth!() / 2
: 0),
Expand All @@ -144,28 +174,16 @@ export default function Example({
fill={gridColor}
fillOpacity={0.2}
/>
<Grid
xScale={scale}
yScale={yScale}
stroke={gridColor}
width={width}
height={scaleHeight}
numTicksRows={2}
numTicksColumns={numTickColumns}
/>
<AnimatedAxis
// force remount when this changes to see the animation difference
key={`axis-${animationTrajectory}`}
orientation={Orientation.bottom}
top={scaleHeight}
scale={scale}
tickFormat={tickFormat}
stroke={axisColor}
tickStroke={axisColor}
tickLabelProps={() => ({
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}
Expand All @@ -180,12 +198,52 @@ export default function Example({
fontFamily: 'sans-serif',
textAnchor: 'start',
}}
animationTrajectory={animationTrajectory}
/>
</g>
))}
</g>
</svg>
{showControls && <button onClick={() => setDataToggle(!dataToggle)}>Update scales</button>}
{showControls && (
<>
<div style={{ fontSize: 11 }}>
<strong>animation trajectory</strong>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('outside')}
checked={animationTrajectory === 'outside'}
/>{' '}
outside
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('center')}
checked={animationTrajectory === 'center'}
/>{' '}
center
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('min')}
checked={animationTrajectory === 'min'}
/>{' '}
min
</label>
<label>
<input
type="radio"
onChange={() => setAnimationTrajectory('max')}
checked={animationTrajectory === 'max'}
/>{' '}
max
</label>
</div>
<button onClick={() => setDataToggle(!dataToggle)}>Update scales</button>
</>
)}
</>
);
}
Loading