Skip to content

Commit

Permalink
Merge 2c4ee1b into 474ec7e
Browse files Browse the repository at this point in the history
  • Loading branch information
williaster authored Aug 13, 2020
2 parents 474ec7e + 2c4ee1b commit a5cb19d
Show file tree
Hide file tree
Showing 16 changed files with 394 additions and 166 deletions.
6 changes: 2 additions & 4 deletions packages/vx-axis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,8 @@
"@vx/scale": "0.0.198",
"@vx/text": "0.0.198",
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
},
"devDependencies": {
"@vx/scale": "0.0.198"
"prop-types": "^15.6.0",
"react-spring": "^8.0.27"
},
"peerDependencies": {
"react": "^16.3.0-0"
Expand Down
10 changes: 10 additions & 0 deletions packages/vx-axis/src/axis/AnimatedAxis.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import React from 'react';
import Axis, { AxisProps } from './Axis';
import AnimatedTicksRenderer from './AnimatedTicksRenderer';
import { AxisScale } from '../types';

export default function AnimatedAxis<Scale extends AxisScale>(
axisProps: Omit<AxisProps<Scale>, 'ticksComponent'>,
) {
return <Axis {...axisProps} ticksComponent={AnimatedTicksRenderer} />;
}
62 changes: 62 additions & 0 deletions packages/vx-axis/src/axis/AnimatedTicksRenderer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React from 'react';
import { animated, useTransition, interpolate } from 'react-spring';
import cx from 'classnames';
import { Text } from '@vx/text';

import Orientation from '../../constants/orientation';
import { TicksRendererProps, AxisScale } from '../../types';
import useTickTransitionConfig from './useTickTransitionConfig';

export default function TicksRenderer<Scale extends AxisScale>({
hideTicks,
horizontal,
orientation,
scale,
tickClassName,
tickLabelProps: allTickLabelProps,
tickStroke = '#222',
tickTransform,
ticks,
}: TicksRendererProps<Scale>) {
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 don't handle fromX, etc.
const { fromX, toX, fromY, toY, opacity } = props;
const tickLabelProps = allTickLabelProps[index] ?? allTickLabelProps[0] ?? {};
return (
<animated.g key={key} className={cx('vx-axis-tick', tickClassName)} transform={tickTransform}>
{!hideTicks && (
<animated.line
x1={fromX}
x2={toX}
y1={fromY}
y2={toY}
stroke={tickStroke}
strokeLinecap="square"
strokeOpacity={opacity}
/>
)}
{/** animate the group, not the Text */}
<animated.g
key={index}
transform={interpolate(
[toX, toY],
(interpolatedX, interpolatedY) =>
`translate(${interpolatedX},${interpolatedY +
(orientation === Orientation.bottom && typeof tickLabelProps.fontSize === 'number'
? tickLabelProps.fontSize ?? 10
: 0)})`,
)}
opacity={opacity}
>
<Text {...tickLabelProps}>{item.formattedValue}</Text>
</animated.g>
</animated.g>
);
});
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useMemo } from 'react';
import { coerceNumber } from '@vx/scale';
import { AxisScale, ComputedTick, TicksRendererProps } from '../../types';

function enterUpdate<Scale extends AxisScale>({ from, to }: ComputedTick<Scale>) {
return {
fromX: from.x,
toX: to.x,
fromY: from.y,
toY: to.y,
opacity: 1,
};
}

export default function useTickTransitionConfig<Scale extends AxisScale>({
horizontal,
scale,
}: Pick<TicksRendererProps<Scale>, '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<Scale>) => {
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]);
}
25 changes: 13 additions & 12 deletions packages/vx-axis/src/axis/Axis.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,20 +46,21 @@ export default function Axis<Scale extends AxisScale>({
horizontal,
);

const ticks = (tickValues ?? getTicks(scale, numTicks))
const filteredTickValues = (tickValues ?? getTicks(scale, numTicks))
.map((value, index) => ({ value, index }))
.filter(({ value }) => !hideZero || (value !== 0 && value !== '0'))
.map(({ value, index }) => {
const scaledValue = coerceNumber(tickPosition(value));
.filter(({ value }) => !hideZero || (value !== 0 && value !== '0'));

return {
value,
index,
from: createPoint({ x: scaledValue, y: 0 }, horizontal),
to: createPoint({ x: scaledValue, y: tickLength * tickSign }, horizontal),
formattedValue: format(value, index),
};
});
const ticks = filteredTickValues.map(({ value, index }) => {
const scaledValue = coerceNumber(tickPosition(value));

return {
value,
index,
from: createPoint({ x: scaledValue, y: 0 }, horizontal),
to: createPoint({ x: scaledValue, y: tickLength * tickSign }, horizontal),
formattedValue: format(value, index, filteredTickValues),
};
});

return (
<Group className={cx('vx-axis', axisClassName)} top={top} left={left}>
Expand Down
13 changes: 8 additions & 5 deletions packages/vx-axis/src/axis/AxisBottom.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,19 @@ import Axis from './Axis';
import Orientation from '../constants/orientation';
import { SharedAxisProps, AxisScale } from '../types';

export default function AxisBottom<Scale extends AxisScale>({
axisClassName,
labelOffset = 8,
tickLabelProps = (/** tickValue, index */) => ({
export const bottomTickLabelProps = (/** tickValue, index */) =>
({
dy: '0.25em',
fill: '#222',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'middle',
}),
} as const);

export default function AxisBottom<Scale extends AxisScale>({
axisClassName,
labelOffset = 8,
tickLabelProps = bottomTickLabelProps,
tickLength = 8,
...restProps
}: SharedAxisProps<Scale>) {
Expand Down
13 changes: 8 additions & 5 deletions packages/vx-axis/src/axis/AxisLeft.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,20 @@ import Axis from './Axis';
import Orientation from '../constants/orientation';
import { SharedAxisProps, AxisScale } from '../types';

export default function AxisLeft<Scale extends AxisScale>({
axisClassName,
labelOffset = 36,
tickLabelProps = (/** tickValue, index */) => ({
export const leftTickLabelProps = (/** tickValue, index */) =>
({
dx: '-0.25em',
dy: '0.25em',
fill: '#222',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'end',
}),
} as const);

export default function AxisLeft<Scale extends AxisScale>({
axisClassName,
labelOffset = 36,
tickLabelProps = leftTickLabelProps,
tickLength = 8,
...restProps
}: SharedAxisProps<Scale>) {
Expand Down
58 changes: 21 additions & 37 deletions packages/vx-axis/src/axis/AxisRenderer.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
import React from 'react';
import cx from 'classnames';
import { Line } from '@vx/shape';
import { Group } from '@vx/group';
import { Text } from '@vx/text';

import { TextProps } from '@vx/text/lib/Text';
import Orientation from '../constants/orientation';
import getLabelTransform from '../utils/getLabelTransform';
import { AxisRendererProps, AxisScale } from '../types';
import TicksRenderer from './TicksRenderer';

const defaultTextProps: Partial<TextProps> = {
textAnchor: 'middle',
Expand All @@ -30,51 +29,36 @@ export default function AxisRenderer<Scale extends AxisScale>({
orientation,
scale,
stroke = '#222',
strokeWidth = 1,
strokeDasharray,
strokeWidth = 1,
tickClassName,
tickComponent,
tickLabelProps = (/** tickValue, index */) => defaultTextProps,
tickLength,
tickStroke = '#222',
tickTransform,
ticks,
ticksComponent = TicksRenderer,
}: AxisRendererProps<Scale>) {
let tickLabelFontSize = 10; // track the max tick label size to compute label offset

// compute the max tick label size to compute label offset
const allTickLabelProps = ticks.map(({ value, index }) => tickLabelProps(value, index));
const maxTickLabelFontSize = Math.max(
10,
...allTickLabelProps.map(props => (typeof props.fontSize === 'number' ? props.fontSize : 0)),
);
return (
<>
{ticks.map(({ value, index, from, to, formattedValue }) => {
const tickLabelPropsObj = tickLabelProps(value, index);
tickLabelFontSize = Math.max(
tickLabelFontSize,
(typeof tickLabelPropsObj.fontSize === 'number' && tickLabelPropsObj.fontSize) || 0,
);

const tickYCoord =
to.y + (horizontal && orientation !== Orientation.top ? tickLabelFontSize : 0);

return (
<Group
key={`vx-tick-${value}-${index}`}
className={cx('vx-axis-tick', tickClassName)}
transform={tickTransform}
>
{!hideTicks && <Line from={from} to={to} stroke={tickStroke} strokeLinecap="square" />}
{tickComponent ? (
tickComponent({
...tickLabelPropsObj,
x: to.x,
y: tickYCoord,
formattedValue,
})
) : (
<Text x={to.x} y={tickYCoord} {...tickLabelPropsObj}>
{formattedValue}
</Text>
)}
</Group>
);
{ticksComponent({
hideTicks,
horizontal,
orientation,
scale,
tickClassName,
tickComponent,
tickLabelProps: allTickLabelProps,
tickStroke,
tickTransform,
ticks,
})}

{!hideAxisLine && (
Expand All @@ -96,7 +80,7 @@ export default function AxisRenderer<Scale extends AxisScale>({
labelProps,
orientation,
range: scale.range(),
tickLabelFontSize,
tickLabelFontSize: maxTickLabelFontSize,
tickLength,
})}
{...labelProps}
Expand Down
13 changes: 8 additions & 5 deletions packages/vx-axis/src/axis/AxisRight.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,20 @@ import { SharedAxisProps, AxisScale } from '../types';

export type AxisRightProps<Scale extends AxisScale> = SharedAxisProps<Scale>;

export default function AxisRight<Scale extends AxisScale>({
axisClassName,
labelOffset = 36,
tickLabelProps = (/** tickValue, index */) => ({
export const rightTickLabelProps = (/** tickValue, index */) =>
({
dx: '0.25em',
dy: '0.25em',
fill: '#222',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'start',
}),
} as const);

export default function AxisRight<Scale extends AxisScale>({
axisClassName,
labelOffset = 36,
tickLabelProps = rightTickLabelProps,
tickLength = 8,
...restProps
}: AxisRightProps<Scale>) {
Expand Down
15 changes: 9 additions & 6 deletions packages/vx-axis/src/axis/AxisTop.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,19 @@ import { SharedAxisProps, AxisScale } from '../types';

export type AxisTopProps<Scale extends AxisScale> = SharedAxisProps<Scale>;

export default function AxisTop<Scale extends AxisScale>({
axisClassName,
labelOffset = 8,
tickLabelProps = (/** tickValue, index */) => ({
dy: '-0.25em',
export const topTickLabelProps = (/** tickValue, index */) =>
({
dy: '-0.75em',
fill: '#222',
fontFamily: 'Arial',
fontSize: 10,
textAnchor: 'middle',
}),
} as const);

export default function AxisTop<Scale extends AxisScale>({
axisClassName,
labelOffset = 8,
tickLabelProps = topTickLabelProps,
tickLength = 8,
...restProps
}: AxisTopProps<Scale>) {
Expand Down
Loading

0 comments on commit a5cb19d

Please sign in to comment.