Skip to content

Commit

Permalink
Merge pull request #773 from kristw/kristw--axis
Browse files Browse the repository at this point in the history
feat: update vx/axis types
  • Loading branch information
hshoff authored Aug 2, 2020
2 parents 5d1dbcf + e62f03d commit 2a5e23e
Show file tree
Hide file tree
Showing 36 changed files with 634 additions and 620 deletions.
1 change: 1 addition & 0 deletions packages/vx-axis/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
"@vx/group": "0.0.198",
"@vx/point": "0.0.198",
"@vx/shape": "0.0.198",
"@vx/scale": "0.0.198",
"@vx/text": "0.0.198",
"classnames": "^2.2.5",
"prop-types": "^15.6.0"
Expand Down
240 changes: 55 additions & 185 deletions packages/vx-axis/src/axis/Axis.tsx
Original file line number Diff line number Diff line change
@@ -1,216 +1,86 @@
import React from 'react';
import cx from 'classnames';
import { Line } from '@vx/shape';
import { Point } from '@vx/point';
import { Group } from '@vx/group';
import { Text } from '@vx/text';
import center from '../utils/center';
import getLabelTransform from '../utils/labelTransform';
import ORIENT from '../constants/orientation';
import toString from '../utils/toString';
import toNumberOrUndefined from '../utils/toNumberOrUndefined';
import { SharedAxisProps, AxisOrientation } from '../types';
import { getTicks, coerceNumber } from '@vx/scale';
import { SharedAxisProps, AxisScale } from '../types';
import AxisRenderer from './AxisRenderer';
import getTickPosition from '../utils/getTickPosition';
import getTickFormatter from '../utils/getTickFormatter';
import createPoint from '../utils/createPoint';
import Orientation from '../constants/orientation';

export type AxisProps<ScaleInput> = SharedAxisProps<ScaleInput> & {
orientation?: AxisOrientation;
export type AxisProps<Scale extends AxisScale> = SharedAxisProps<Scale> & {
orientation?: Orientation;
};

export default function Axis<ScaleInput>({
children,
export default function Axis<Scale extends AxisScale>({
children = AxisRenderer,
axisClassName,
axisLineClassName,
hideAxisLine = false,
hideTicks = false,
hideZero = false,
label = '',
labelClassName,
labelOffset = 14,
labelProps = {
textAnchor: 'middle',
fontFamily: 'Arial',
fontSize: 10,
fill: '#222',
},
left = 0,
numTicks = 10,
orientation = ORIENT.bottom,
orientation = Orientation.bottom,
rangePadding = 0,
scale,
stroke = '#222',
strokeWidth = 1,
strokeDasharray,
tickClassName,
tickFormat,
tickLabelProps = (/** tickValue, index */) => ({
textAnchor: 'middle',
fontFamily: 'Arial',
fontSize: 10,
fill: '#222',
}),
tickLength = 8,
tickStroke = '#222',
tickTransform,
tickValues,
tickComponent,
top = 0,
}: AxisProps<ScaleInput>) {
const values =
tickValues ||
(scale.ticks
? scale.ticks(numTicks)
: scale
.domain()
.filter(
(_, index, arr) =>
numTicks == null ||
arr.length <= numTicks ||
index % Math.round((arr.length - 1) / numTicks) === 0,
));
const format = tickFormat || (scale.tickFormat ? scale.tickFormat() : toString);
...restProps
}: AxisProps<Scale>) {
const format = tickFormat ?? getTickFormatter(scale);

const range = scale.range();
const range0 = Number(range[0]) + 0.5 - rangePadding;
const range1 = Number(range[range.length - 1]) + 0.5 + rangePadding;
const isLeft = orientation === Orientation.left;
const isTop = orientation === Orientation.top;
const horizontal = isTop || orientation === Orientation.bottom;

const isLeft = orientation === ORIENT.left;
const isTop = orientation === ORIENT.top;
const axisIsHorizontal = isTop || orientation === ORIENT.bottom;
const tickPosition = getTickPosition(scale);
const tickSign = isLeft || isTop ? -1 : 1;

const position = center(scale.copy());

const axisFromPoint = new Point({
x: axisIsHorizontal ? range0 : 0,
y: axisIsHorizontal ? 0 : range0,
});
const axisToPoint = new Point({
x: axisIsHorizontal ? range1 : 0,
y: axisIsHorizontal ? 0 : range1,
});
const range = scale.range();
const axisFromPoint = createPoint({ x: Number(range[0]) + 0.5 - rangePadding, y: 0 }, horizontal);
const axisToPoint = createPoint(
{ x: Number(range[range.length - 1]) + 0.5 + rangePadding, y: 0 },
horizontal,
);

let tickLabelFontSize = 10; // track the max tick label size to compute label offset
const ticks = (tickValues ?? getTicks(scale, numTicks))
.map((value, index) => ({ value, index }))
.filter(({ value }) => !hideZero || (value !== 0 && value !== '0'))
.map(({ value, index }) => {
const scaledValue = coerceNumber(tickPosition(value));

if (children) {
return (
<Group className={cx('vx-axis', axisClassName)} top={top} left={left}>
{children({
axisFromPoint,
axisToPoint,
horizontal: axisIsHorizontal,
tickSign,
numTicks,
label,
rangePadding,
tickLength,
tickFormat: format,
tickPosition: position,
ticks: values.map((value, index) => {
const scaledValue = toNumberOrUndefined(position(value));
const from = new Point({
x: axisIsHorizontal ? scaledValue : 0,
y: axisIsHorizontal ? 0 : scaledValue,
});
const to = new Point({
x: axisIsHorizontal ? scaledValue : tickSign * tickLength,
y: axisIsHorizontal ? tickLength * tickSign : scaledValue,
});
return {
value,
index,
from,
to,
formattedValue: format(value, index),
};
}),
})}
</Group>
);
}
return {
value,
index,
from: createPoint({ x: scaledValue, y: 0 }, horizontal),
to: createPoint({ x: scaledValue, y: tickLength * tickSign }, horizontal),
formattedValue: format(value, index),
};
});

return (
<Group className={cx('vx-axis', axisClassName)} top={top} left={left}>
{values.map((val, index) => {
if (
hideZero &&
((typeof val === 'number' && val === 0) || (typeof val === 'string' && val === '0'))
) {
return null;
}
const scaledValue = toNumberOrUndefined(position(val));
const tickFromPoint = new Point({
x: axisIsHorizontal ? scaledValue : 0,
y: axisIsHorizontal ? 0 : scaledValue,
});
const tickToPoint = new Point({
x: axisIsHorizontal ? scaledValue : tickSign * tickLength,
y: axisIsHorizontal ? tickLength * tickSign : scaledValue,
});

const tickLabelPropsObj = tickLabelProps(val, index);
tickLabelFontSize = Math.max(
tickLabelFontSize,
(typeof tickLabelPropsObj.fontSize === 'number' && tickLabelPropsObj.fontSize) || 0,
);

const tickYCoord = tickToPoint.y + (axisIsHorizontal && !isTop ? tickLabelFontSize : 0);
const formattedValue = format(val, index);
return (
<Group
key={`vx-tick-${val}-${index}`}
className={cx('vx-axis-tick', tickClassName)}
transform={tickTransform}
>
{!hideTicks && (
<Line
from={tickFromPoint}
to={tickToPoint}
stroke={tickStroke}
strokeLinecap="square"
/>
)}
{tickComponent ? (
tickComponent({
...tickLabelPropsObj,
x: tickToPoint.x,
y: tickYCoord,
formattedValue,
})
) : (
<Text x={tickToPoint.x} y={tickYCoord} {...tickLabelPropsObj}>
{formattedValue}
</Text>
)}
</Group>
);
{children({
...restProps,
axisFromPoint,
axisToPoint,
hideAxisLine,
hideTicks,
hideZero,
horizontal,
numTicks,
orientation,
rangePadding,
scale,
tickFormat: format,
tickLength,
tickPosition,
tickSign,
ticks,
})}

{!hideAxisLine && (
<Line
className={cx('vx-axis-line', axisLineClassName)}
from={axisFromPoint}
to={axisToPoint}
stroke={stroke}
strokeWidth={strokeWidth}
strokeDasharray={strokeDasharray}
/>
)}

{label && (
<Text
className={cx('vx-axis-label', labelClassName)}
{...getLabelTransform({
labelOffset,
labelProps,
orientation,
range,
tickLabelFontSize,
tickLength,
})}
{...labelProps}
>
{label}
</Text>
)}
</Group>
);
}
58 changes: 7 additions & 51 deletions packages/vx-axis/src/axis/AxisBottom.tsx
Original file line number Diff line number Diff line change
@@ -1,31 +1,12 @@
import React from 'react';
import cx from 'classnames';
import Axis from './Axis';
import ORIENT from '../constants/orientation';
import { SharedAxisProps } from '../types';
import Orientation from '../constants/orientation';
import { SharedAxisProps, AxisScale } from '../types';

export type AxisBottomProps<ScaleInput> = SharedAxisProps<ScaleInput>;

export default function AxisBottom<ScaleInput>({
children,
export default function AxisBottom<Scale extends AxisScale>({
axisClassName,
axisLineClassName,
hideAxisLine,
hideTicks,
hideZero,
label,
labelClassName,
labelOffset = 8,
labelProps,
left,
numTicks,
rangePadding,
scale,
stroke,
strokeWidth,
strokeDasharray,
tickClassName,
tickFormat,
tickLabelProps = (/** tickValue, index */) => ({
dy: '0.25em',
fill: '#222',
Expand All @@ -34,41 +15,16 @@ export default function AxisBottom<ScaleInput>({
textAnchor: 'middle',
}),
tickLength = 8,
tickStroke,
tickTransform,
tickValues,
tickComponent,
top,
}: AxisBottomProps<ScaleInput>) {
...restProps
}: SharedAxisProps<Scale>) {
return (
<Axis
axisClassName={cx('vx-axis-bottom', axisClassName)}
axisLineClassName={axisLineClassName}
hideAxisLine={hideAxisLine}
hideTicks={hideTicks}
hideZero={hideZero}
label={label}
labelClassName={labelClassName}
labelOffset={labelOffset}
labelProps={labelProps}
left={left}
numTicks={numTicks}
orientation={ORIENT.bottom}
rangePadding={rangePadding}
scale={scale}
stroke={stroke}
strokeWidth={strokeWidth}
strokeDasharray={strokeDasharray}
tickClassName={tickClassName}
tickFormat={tickFormat}
orientation={Orientation.bottom}
tickLabelProps={tickLabelProps}
tickLength={tickLength}
tickStroke={tickStroke}
tickTransform={tickTransform}
tickValues={tickValues}
tickComponent={tickComponent}
top={top}
children={children}
{...restProps}
/>
);
}
Loading

0 comments on commit 2a5e23e

Please sign in to comment.