From 59bd755c64023513b0370b96963bd21fca754ea9 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Tue, 28 Jul 2020 11:55:51 -0700 Subject: [PATCH 01/23] refactor: replace types in vx/axis --- packages/vx-axis/package.json | 1 + packages/vx-axis/src/axis/Axis.tsx | 4 +- packages/vx-axis/src/types.ts | 61 ++++++++++++----------- packages/vx-axis/src/utils/center.ts | 10 ++-- packages/vx-axis/test/Axis.test.tsx | 3 +- packages/vx-axis/test/AxisBottom.test.tsx | 3 +- packages/vx-axis/test/AxisLeft.test.tsx | 3 +- packages/vx-axis/test/AxisRight.test.tsx | 3 +- packages/vx-axis/test/AxisTop.test.tsx | 3 +- packages/vx-axis/test/scales.test.tsx | 49 ++++++++++-------- 10 files changed, 72 insertions(+), 68 deletions(-) diff --git a/packages/vx-axis/package.json b/packages/vx-axis/package.json index a82ed862f..7af83a4e2 100644 --- a/packages/vx-axis/package.json +++ b/packages/vx-axis/package.json @@ -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" diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index c8f5a3b07..1bca4d510 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -56,7 +56,7 @@ export default function Axis({ }: AxisProps) { const values = tickValues || - (scale.ticks + ('ticks' in scale ? scale.ticks(numTicks) : scale .domain() @@ -66,7 +66,7 @@ export default function Axis({ arr.length <= numTicks || index % Math.round((arr.length - 1) / numTicks) === 0, )); - const format = tickFormat || (scale.tickFormat ? scale.tickFormat() : toString); + const format = tickFormat || ('tickFormat' in scale ? scale.tickFormat() : toString); const range = scale.range(); const range0 = Number(range[0]) + 0.5 - rangePadding; diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index 5e91f5e0f..9f01b75c9 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -1,3 +1,4 @@ +import { PickD3Scale, ScaleType } from '@vx/scale'; import { TextProps } from '@vx/text/lib/Text'; export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; @@ -14,7 +15,11 @@ export type TickRendererProps = Partial & { formattedValue: FormattedValue; }; -export type SharedAxisProps = { +// In order to plot values on an axis, Output must be numeric or coercible to a number. +// Some scales return undefined. +export type ScaleOutput = number | { valueOf(): number } | undefined; + +export type SharedAxisProps = { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** The class name applied to the axis line element. */ @@ -40,7 +45,7 @@ export type SharedAxisProps = { /** Pixel padding to apply to both sides of the axis. */ rangePadding?: number; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: GenericScale; + scale: PickD3Scale; /** The color for the stroke of the lines. */ stroke?: string; /** The pixel value for the width of the lines. */ @@ -50,9 +55,9 @@ export type SharedAxisProps = { /** The class name applied to each tick group. */ tickClassName?: string; /** 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; + tickLabelProps?: TickLabelProps; /** The length of the tick lines. */ tickLength?: number; /** The color for the tick's stroke value. */ @@ -60,41 +65,37 @@ export type SharedAxisProps = { /** A custom SVG transform value to be applied to each tick group. */ tickTransform?: string; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ - tickValues?: ScaleInput[]; + tickValues?: Datum[]; /** Override the component used to render tick labels (instead of from @vx/text) */ tickComponent?: (tickRendererProps: TickRendererProps) => React.ReactNode; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ - children?: (renderProps: ChildRenderProps) => React.ReactNode; + children?: (renderProps: ChildRenderProps) => React.ReactNode; }; -// In order to plot values on an axis, Output must be numeric or coercible to a number. -// Some scales return undefined. -export type ScaleOutput = number | { valueOf(): number } | undefined; - -export type GenericScale = - | ScaleNoRangeRound - | ScaleWithRangeRound; +// export type GenericScale = +// | ScaleNoRangeRound +// | ScaleWithRangeRound; -interface ScaleNoRangeRound { - (value: ScaleInput): ScaleOutput | [ScaleOutput, ScaleOutput]; // quantize scales return an array - domain(): ScaleInput[] | [ScaleInput, ScaleInput]; - domain(scaleInput: ScaleInput[] | [ScaleInput, ScaleInput]): any; // we can't capture the copy of the type accurately - range(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; - range(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): any; - ticks?: (count: number) => ScaleInput[] | [ScaleInput, ScaleInput]; - bandwidth?: () => number; - round?: () => boolean; - tickFormat?: () => (input: ScaleInput) => FormattedValue; - copy(): this; -} +// interface ScaleNoRangeRound { +// (value: ScaleInput): ScaleOutput | [ScaleOutput, ScaleOutput]; // quantize scales return an array +// domain(): ScaleInput[] | [ScaleInput, ScaleInput]; +// domain(scaleInput: ScaleInput[] | [ScaleInput, ScaleInput]): unknown; // we can't capture the copy of the type accurately +// range(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; +// range(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): unknown; +// ticks?: (count: number) => ScaleInput[] | [ScaleInput, ScaleInput]; +// bandwidth?: () => number; +// round?: () => boolean; +// tickFormat?: () => (input: ScaleInput) => FormattedValue; +// copy(): this; +// } -// We cannot have optional methods AND overloads, so define a separate type for rangeRound -interface ScaleWithRangeRound extends ScaleNoRangeRound { - rangeRound(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; - rangeRound(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): any; -} +// // We cannot have optional methods AND overloads, so define a separate type for rangeRound +// interface ScaleWithRangeRound extends ScaleNoRangeRound { +// rangeRound(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; +// rangeRound(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): unknown; +// } export interface Point { x: number; diff --git a/packages/vx-axis/src/utils/center.ts b/packages/vx-axis/src/utils/center.ts index 83683be4c..db53c8fcd 100644 --- a/packages/vx-axis/src/utils/center.ts +++ b/packages/vx-axis/src/utils/center.ts @@ -1,12 +1,14 @@ -import { GenericScale } from '../types'; +import { D3Scale, DefaultThresholdInput } from '@vx/scale'; /** * Returns a function that applies a centering transform to a scaled value, * if `Output` is of type `number` and `scale.bandwidth()` is defined */ -export default function center(scale: GenericScale) { - let offset = scale.bandwidth ? scale.bandwidth() / 2 : 0; - if (scale.round && scale.round()) offset = Math.round(offset); +export default function center( + scale: D3Scale, +) { + let offset = 'bandwidth' in scale ? scale.bandwidth() / 2 : 0; + if ('round' in scale && scale.round()) offset = Math.round(offset); return (d: ScaleInput) => { const scaledValue = scale(d); diff --git a/packages/vx-axis/test/Axis.test.tsx b/packages/vx-axis/test/Axis.test.tsx index 40f49b5ca..a52f90258 100644 --- a/packages/vx-axis/test/Axis.test.tsx +++ b/packages/vx-axis/test/Axis.test.tsx @@ -5,7 +5,6 @@ import { Line } from '@vx/shape'; import { Text } from '@vx/text'; import { scaleBand, scaleLinear } from '@vx/scale'; import { Axis } from '../src'; -import { GenericScale } from '../src/types'; const axisProps = { orientation: 'left' as const, @@ -13,7 +12,7 @@ const axisProps = { range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), label: 'test axis', }; diff --git a/packages/vx-axis/test/AxisBottom.test.tsx b/packages/vx-axis/test/AxisBottom.test.tsx index 1a4752716..d75a2a65d 100644 --- a/packages/vx-axis/test/AxisBottom.test.tsx +++ b/packages/vx-axis/test/AxisBottom.test.tsx @@ -3,14 +3,13 @@ import { shallow } from 'enzyme'; import { scaleLinear } from '../../vx-scale/src'; import { Axis, AxisBottom } from '../src'; -import { GenericScale } from '../src/types'; const axisProps = { scale: scaleLinear({ range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), }; describe('', () => { diff --git a/packages/vx-axis/test/AxisLeft.test.tsx b/packages/vx-axis/test/AxisLeft.test.tsx index e943d3f97..2037614d3 100644 --- a/packages/vx-axis/test/AxisLeft.test.tsx +++ b/packages/vx-axis/test/AxisLeft.test.tsx @@ -3,14 +3,13 @@ import { shallow } from 'enzyme'; import { scaleLinear } from '../../vx-scale/src'; import { Axis, AxisLeft } from '../src'; -import { GenericScale } from '../src/types'; const axisProps = { scale: scaleLinear({ range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), }; describe('', () => { diff --git a/packages/vx-axis/test/AxisRight.test.tsx b/packages/vx-axis/test/AxisRight.test.tsx index 50dc26d75..c66dc4b5c 100644 --- a/packages/vx-axis/test/AxisRight.test.tsx +++ b/packages/vx-axis/test/AxisRight.test.tsx @@ -3,14 +3,13 @@ import { shallow } from 'enzyme'; import { scaleLinear } from '../../vx-scale/src'; import { Axis, AxisRight } from '../src'; -import { GenericScale } from '../src/types'; const axisProps = { scale: scaleLinear({ range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), }; describe('', () => { diff --git a/packages/vx-axis/test/AxisTop.test.tsx b/packages/vx-axis/test/AxisTop.test.tsx index 74a6b4698..d7fc5ac0e 100644 --- a/packages/vx-axis/test/AxisTop.test.tsx +++ b/packages/vx-axis/test/AxisTop.test.tsx @@ -3,14 +3,13 @@ import { shallow } from 'enzyme'; import { scaleLinear } from '../../vx-scale/src'; import { Axis, AxisTop } from '../src'; -import { GenericScale } from '../src/types'; const axisProps = { scale: scaleLinear({ range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), }; describe('', () => { diff --git a/packages/vx-axis/test/scales.test.tsx b/packages/vx-axis/test/scales.test.tsx index 979d95c9b..8f365bab5 100644 --- a/packages/vx-axis/test/scales.test.tsx +++ b/packages/vx-axis/test/scales.test.tsx @@ -13,16 +13,21 @@ import { scaleThreshold, scaleTime, scaleUtc, + D3Scale, + StringLike, + DefaultThresholdInput, } from '@vx/scale'; import { Axis } from '../src'; -import { GenericScale } from '../src/types'; +import { ScaleOutput } from '../src/types'; const axisProps = { orientation: 'left' as const, label: 'test axis', }; -function setup(scale: GenericScale) { +function setup( + scale: D3Scale, +) { return () => shallow(); } @@ -34,7 +39,7 @@ describe('Axis scales', () => { range: [10, 0], round: true, domain: ['a', 'b', 'c'], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -42,11 +47,11 @@ describe('Axis scales', () => { it('should render with scaleLinear', () => { expect( setup( - scaleLinear({ + scaleLinear({ range: [10, 0], round: true, domain: [0, 10], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -54,11 +59,11 @@ describe('Axis scales', () => { it('should render with scaleLog', () => { expect( setup( - scaleLog({ + scaleLog({ range: [10, 0], round: true, domain: [1, 10, 100, 1000], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -69,7 +74,7 @@ describe('Axis scales', () => { scaleOrdinal({ range: [0, 10], domain: ['a', 'b', 'c'], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -77,11 +82,11 @@ describe('Axis scales', () => { it('should render with scalePoint', () => { expect( setup( - scalePoint({ + scalePoint({ range: [0, 10], round: true, domain: ['a', 'b', 'c'], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -89,10 +94,10 @@ describe('Axis scales', () => { it('should render with scalePower', () => { expect( setup( - scalePower({ + scalePower({ range: [1, 2, 3, 4, 5], domain: [1, 10, 100, 1000, 10000], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -100,10 +105,10 @@ describe('Axis scales', () => { it('should render with scaleQuantile', () => { expect( setup( - scaleQuantile({ + scaleQuantile({ range: [0, 2, 4, 6, 8, 10], domain: [1, 10, 100, 1000, 10000], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -111,10 +116,10 @@ describe('Axis scales', () => { it('should render with scaleQuantize', () => { expect( setup( - scaleQuantize({ + scaleQuantize({ range: [1, 10], domain: [1, 10], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -125,7 +130,7 @@ describe('Axis scales', () => { scaleSymlog({ range: [1, 10], domain: [1, 10], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -136,7 +141,7 @@ describe('Axis scales', () => { scaleThreshold({ range: [1, 10], domain: [1, 10], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -144,10 +149,10 @@ describe('Axis scales', () => { it('should render with scaleTime', () => { expect( setup( - scaleTime({ + scaleTime({ range: [1, 10], domain: [new Date('2020-01-01'), new Date('2020-01-05')], - }) as GenericScale, + }), ), ).not.toThrow(); }); @@ -155,10 +160,10 @@ describe('Axis scales', () => { it('should render with scaleUtc', () => { expect( setup( - scaleUtc({ + scaleUtc({ range: [1, 10], domain: [new Date('2020-01-01'), new Date('2020-01-05')], - }) as GenericScale, + }), ), ).not.toThrow(); }); From b31fdcc0e6c0323eb38a585eae8a7b2a6c72e4bf Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Tue, 28 Jul 2020 16:14:46 -0700 Subject: [PATCH 02/23] refactor: simplify specific axes --- packages/vx-axis/src/axis/Axis.tsx | 6 +-- packages/vx-axis/src/axis/AxisBottom.tsx | 52 +++--------------------- packages/vx-axis/src/axis/AxisLeft.tsx | 52 +++--------------------- packages/vx-axis/src/axis/AxisRight.tsx | 52 +++--------------------- packages/vx-axis/src/axis/AxisTop.tsx | 52 +++--------------------- 5 files changed, 23 insertions(+), 191 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 1bca4d510..96e4b4c1e 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -11,11 +11,11 @@ import toString from '../utils/toString'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; import { SharedAxisProps, AxisOrientation } from '../types'; -export type AxisProps = SharedAxisProps & { +export type AxisProps = SharedAxisProps & { orientation?: AxisOrientation; }; -export default function Axis({ +export default function Axis({ children, axisClassName, axisLineClassName, @@ -53,7 +53,7 @@ export default function Axis({ tickValues, tickComponent, top = 0, -}: AxisProps) { +}: AxisProps) { const values = tickValues || ('ticks' in scale diff --git a/packages/vx-axis/src/axis/AxisBottom.tsx b/packages/vx-axis/src/axis/AxisBottom.tsx index 3bde70bdb..6994c4555 100644 --- a/packages/vx-axis/src/axis/AxisBottom.tsx +++ b/packages/vx-axis/src/axis/AxisBottom.tsx @@ -4,28 +4,11 @@ import Axis from './Axis'; import ORIENT from '../constants/orientation'; import { SharedAxisProps } from '../types'; -export type AxisBottomProps = SharedAxisProps; +export type AxisBottomProps = SharedAxisProps; -export default function AxisBottom({ - children, +export default function AxisBottom({ 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', @@ -34,41 +17,16 @@ export default function AxisBottom({ textAnchor: 'middle', }), tickLength = 8, - tickStroke, - tickTransform, - tickValues, - tickComponent, - top, -}: AxisBottomProps) { + ...restProps +}: AxisBottomProps) { return ( ); } diff --git a/packages/vx-axis/src/axis/AxisLeft.tsx b/packages/vx-axis/src/axis/AxisLeft.tsx index 061bf7f92..827dcfe0a 100644 --- a/packages/vx-axis/src/axis/AxisLeft.tsx +++ b/packages/vx-axis/src/axis/AxisLeft.tsx @@ -4,28 +4,11 @@ import Axis from './Axis'; import ORIENT from '../constants/orientation'; import { SharedAxisProps } from '../types'; -export type AxisLeftProps = SharedAxisProps; +export type AxisLeftProps = SharedAxisProps; -export default function AxisLeft({ - children, +export default function AxisLeft({ axisClassName, - axisLineClassName, - hideAxisLine, - hideTicks, - hideZero, - label, - labelClassName, labelOffset = 36, - labelProps, - left, - numTicks, - rangePadding, - scale, - stroke, - strokeWidth, - strokeDasharray, - tickClassName, - tickFormat, tickLabelProps = (/** tickValue, index */) => ({ dx: '-0.25em', dy: '0.25em', @@ -35,41 +18,16 @@ export default function AxisLeft({ textAnchor: 'end', }), tickLength = 8, - tickStroke, - tickTransform, - tickValues, - tickComponent, - top, -}: AxisLeftProps) { + ...restProps +}: AxisLeftProps) { return ( ); } diff --git a/packages/vx-axis/src/axis/AxisRight.tsx b/packages/vx-axis/src/axis/AxisRight.tsx index 510d36ee0..b0ecd5bb6 100644 --- a/packages/vx-axis/src/axis/AxisRight.tsx +++ b/packages/vx-axis/src/axis/AxisRight.tsx @@ -4,28 +4,11 @@ import Axis from './Axis'; import ORIENT from '../constants/orientation'; import { SharedAxisProps } from '../types'; -export type AxisRightProps = SharedAxisProps; +export type AxisRightProps = SharedAxisProps; -export default function AxisRight({ - children, +export default function AxisRight({ axisClassName, - axisLineClassName, - hideAxisLine, - hideTicks, - hideZero, - label, - labelClassName, labelOffset = 36, - labelProps, - left, - numTicks, - rangePadding, - scale, - stroke, - strokeWidth, - strokeDasharray, - tickClassName, - tickFormat, tickLabelProps = (/** tickValue, index */) => ({ dx: '0.25em', dy: '0.25em', @@ -35,41 +18,16 @@ export default function AxisRight({ textAnchor: 'start', }), tickLength = 8, - tickStroke, - tickTransform, - tickValues, - tickComponent, - top, -}: AxisRightProps) { + ...restProps +}: AxisRightProps) { return ( ); } diff --git a/packages/vx-axis/src/axis/AxisTop.tsx b/packages/vx-axis/src/axis/AxisTop.tsx index 0c6302420..0313a47a8 100644 --- a/packages/vx-axis/src/axis/AxisTop.tsx +++ b/packages/vx-axis/src/axis/AxisTop.tsx @@ -4,28 +4,11 @@ import Axis from './Axis'; import ORIENT from '../constants/orientation'; import { SharedAxisProps } from '../types'; -export type AxisTopProps = SharedAxisProps; +export type AxisTopProps = SharedAxisProps; -export default function AxisTop({ - children, +export default function AxisTop({ 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', @@ -34,41 +17,16 @@ export default function AxisTop({ textAnchor: 'middle', }), tickLength = 8, - tickStroke, - tickTransform, - tickValues, - tickComponent, - top, -}: AxisTopProps) { + ...restProps +}: AxisTopProps) { return ( ); } From ef0f86ff2bfcf9e55bcfb11d40bc1dd64d490031 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Tue, 28 Jul 2020 18:07:27 -0700 Subject: [PATCH 03/23] refactor: separate renderer and axis --- packages/vx-axis/src/axis/Axis.tsx | 219 ++++++--------------- packages/vx-axis/src/axis/AxisRenderer.tsx | 109 ++++++++++ packages/vx-axis/src/types.ts | 90 ++++----- 3 files changed, 213 insertions(+), 205 deletions(-) create mode 100644 packages/vx-axis/src/axis/AxisRenderer.tsx diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 96e4b4c1e..858e0cedc 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -1,15 +1,13 @@ 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 { SharedAxisProps, AxisOrientation, ChildRenderProps } from '../types'; +import AxisRenderer from './AxisRenderer'; +import center from '../utils/center'; import toString from '../utils/toString'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; -import { SharedAxisProps, AxisOrientation } from '../types'; export type AxisProps = SharedAxisProps & { orientation?: AxisOrientation; @@ -18,54 +16,20 @@ export type AxisProps = SharedAxisProps & { export default function Axis({ children, 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, 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, + ...restProps }: AxisProps) { - const values = - tickValues || - ('ticks' in scale - ? scale.ticks(numTicks) - : scale - .domain() - .filter( - (_, index, arr) => - numTicks == null || - arr.length <= numTicks || - index % Math.round((arr.length - 1) / numTicks) === 0, - )); const format = tickFormat || ('tickFormat' in scale ? scale.tickFormat() : toString); const range = scale.range(); @@ -75,9 +39,9 @@ export default function Axis({ const isLeft = orientation === ORIENT.left; const isTop = orientation === ORIENT.top; const axisIsHorizontal = isTop || orientation === ORIENT.bottom; - const tickSign = isLeft || isTop ? -1 : 1; - const position = center(scale.copy()); + const tickPosition = center(scale.copy()); + const tickSign = isLeft || isTop ? -1 : 1; const axisFromPoint = new Point({ x: axisIsHorizontal ? range0 : 0, @@ -88,129 +52,62 @@ export default function Axis({ y: axisIsHorizontal ? 0 : range1, }); - let tickLabelFontSize = 10; // track the max tick label size to compute label offset + const values = + tickValues || + ('ticks' in scale + ? scale.ticks(numTicks) + : scale + .domain() + .filter( + (_, index, arr) => + numTicks == null || + arr.length <= numTicks || + index % Math.round((arr.length - 1) / numTicks) === 0, + )); + + const ticks = values + .filter(value => !hideZero || value !== 0 || value !== '0') + .map((value, index) => { + const scaledValue = toNumberOrUndefined(tickPosition(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), + }; + }); - if (children) { - return ( - - {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), - }; - }), - })} - - ); - } + const childProps: ChildRenderProps = { + ...restProps, + axisFromPoint, + axisToPoint, + hideAxisLine, + hideTicks, + hideZero, + horizontal: axisIsHorizontal, + numTicks, + orientation, + rangePadding, + scale, + tickFormat: format, + tickLength, + tickPosition, + tickSign, + ticks, + }; return ( - {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 ( - - {!hideTicks && ( - - )} - {tickComponent ? ( - tickComponent({ - ...tickLabelPropsObj, - x: tickToPoint.x, - y: tickYCoord, - formattedValue, - }) - ) : ( - - {formattedValue} - - )} - - ); - })} - - {!hideAxisLine && ( - - )} - - {label && ( - - {label} - - )} + {children ? children(childProps) : } ); } diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx new file mode 100644 index 000000000..1f8312c4c --- /dev/null +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -0,0 +1,109 @@ +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 ORIENT from '../constants/orientation'; +import getLabelTransform from '../utils/labelTransform'; +import { ChildRenderProps } from '../types'; + +const defaultTextProps: Partial = { + textAnchor: 'middle', + fontFamily: 'Arial', + fontSize: 10, + fill: '#222', +}; + +export default function AxisRenderer({ + axisFromPoint, + axisLineClassName, + axisToPoint, + hideAxisLine, + hideTicks, + horizontal, + label = '', + labelClassName, + labelOffset = 14, + labelProps = defaultTextProps, + orientation, + scale, + stroke = '#222', + strokeWidth = 1, + strokeDasharray, + tickClassName, + tickComponent, + tickLabelProps = (/** tickValue, index */) => defaultTextProps, + tickLength, + tickStroke = '#222', + tickTransform, + ticks, +}: ChildRenderProps) { + let tickLabelFontSize = 10; // track the max tick label size to compute label offset + + return ( + <> + {ticks.map(({ value, from, to, formattedValue }, index) => { + const tickLabelPropsObj = tickLabelProps(value, index); + tickLabelFontSize = Math.max( + tickLabelFontSize, + (typeof tickLabelPropsObj.fontSize === 'number' && tickLabelPropsObj.fontSize) || 0, + ); + + const tickYCoord = + to.y + (horizontal && orientation !== ORIENT.top ? tickLabelFontSize : 0); + + return ( + + {!hideTicks && } + {tickComponent ? ( + tickComponent({ + ...tickLabelPropsObj, + x: to.x, + y: tickYCoord, + formattedValue, + }) + ) : ( + + {formattedValue} + + )} + + ); + })} + + {!hideAxisLine && ( + + )} + + {label && ( + + {label} + + )} + + ); +} diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index 9f01b75c9..b648f9835 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -5,9 +5,9 @@ export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; export type FormattedValue = string | number | undefined; -export type TickFormatter = (value: ScaleInput, tickIndex: number) => FormattedValue; +export type TickFormatter = (value: Datum, tickIndex: number) => FormattedValue; -export type TickLabelProps = (val: ScaleInput, index: number) => Partial; +export type TickLabelProps = (val: Datum, index: number) => Partial; export type TickRendererProps = Partial & { x: number; @@ -19,17 +19,20 @@ export type TickRendererProps = Partial & { // Some scales return undefined. export type ScaleOutput = number | { valueOf(): number } | undefined; -export type SharedAxisProps = { - /** The class name applied to the outermost axis group element. */ - axisClassName?: string; +export interface Point { + x: number; + y: number; +} + +interface 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. */ @@ -38,14 +41,12 @@ export type SharedAxisProps = { labelOffset?: number; /** Props applied to the axis label component. */ labelProps?: Partial; - /** A left pixel offset applied to the entire axis. */ - left?: number; /** The number of ticks wanted for the axis (note this is approximate) */ - numTicks?: number; + numTicks: number; + /** Placement of the axis */ + orientation: AxisOrientation; /** Pixel padding to apply to both sides of the axis. */ - rangePadding?: number; - /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: PickD3Scale; + rangePadding: number; /** The color for the stroke of the lines. */ stroke?: string; /** The pixel value for the width of the lines. */ @@ -54,20 +55,47 @@ export type SharedAxisProps = { strokeDasharray?: string; /** The class name applied to each tick group. */ tickClassName?: string; + /** Override the component used to render tick labels (instead of from @vx/text) */ + tickComponent?: (tickRendererProps: TickRendererProps) => 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; +} + +export type ChildRenderProps = CommonProps & { + axisFromPoint: Point; + axisToPoint: Point; + horizontal: boolean; + /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ + scale: PickD3Scale; + tickPosition: (value: Datum) => ScaleOutput; + /** Axis coordinate sign, -1 for left or top orientation. */ + tickSign: 1 | -1; + ticks: { + value: Datum; + index: number; + from: Point; + to: Point; + formattedValue: FormattedValue; + }[]; +}; + +export type SharedAxisProps = Partial> & { + /** The class name applied to the outermost axis group element. */ + axisClassName?: string; + /** A left pixel offset applied to the entire axis. */ + left?: number; + /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ + scale: PickD3Scale; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ tickValues?: Datum[]; - /** Override the component used to render tick labels (instead of from @vx/text) */ - tickComponent?: (tickRendererProps: TickRendererProps) => React.ReactNode; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ @@ -96,29 +124,3 @@ export type SharedAxisProps = { // rangeRound(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; // rangeRound(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): unknown; // } - -export interface Point { - x: number; - y: number; -} - -export type ChildRenderProps = { - axisFromPoint: Point; - axisToPoint: Point; - horizontal: boolean; - /** Axis coordinate sign, -1 for left or top orientation. */ - tickSign: 1 | -1; - numTicks: number; - label?: string; - rangePadding: number; - tickLength: number; - tickFormat: TickFormatter; - tickPosition: (value: ScaleInput) => ScaleOutput; - ticks: { - value: ScaleInput; - index: number; - from: Point; - to: Point; - formattedValue: FormattedValue; - }[]; -}; From 665f8c24288e68c5d8f02bf6027a09f471df3d0c Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 29 Jul 2020 15:31:57 -0700 Subject: [PATCH 04/23] correct typing of the functions --- packages/vx-axis/src/axis/Axis.tsx | 54 ++++++--------- packages/vx-axis/src/axis/AxisBottom.tsx | 8 +-- packages/vx-axis/src/axis/AxisLeft.tsx | 8 +-- packages/vx-axis/src/axis/AxisRenderer.tsx | 6 +- packages/vx-axis/src/axis/AxisRight.tsx | 8 +-- packages/vx-axis/src/axis/AxisTop.tsx | 8 +-- packages/vx-axis/src/types.ts | 65 +++++++------------ packages/vx-axis/src/utils/center.ts | 20 ------ .../vx-axis/src/utils/getTickFormatter.ts | 17 +++++ packages/vx-axis/src/utils/getTickPosition.ts | 23 +++++++ packages/vx-axis/src/utils/getTicks.ts | 33 ++++++++++ 11 files changed, 136 insertions(+), 114 deletions(-) delete mode 100644 packages/vx-axis/src/utils/center.ts create mode 100644 packages/vx-axis/src/utils/getTickFormatter.ts create mode 100644 packages/vx-axis/src/utils/getTickPosition.ts create mode 100644 packages/vx-axis/src/utils/getTicks.ts diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 858e0cedc..d7c075865 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -3,17 +3,18 @@ import cx from 'classnames'; import { Point } from '@vx/point'; import { Group } from '@vx/group'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps, AxisOrientation, ChildRenderProps } from '../types'; +import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; -import center from '../utils/center'; -import toString from '../utils/toString'; +import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; +import getTicks from '../utils/getTicks'; +import getTickFormatter from '../utils/getTickFormatter'; -export type AxisProps = SharedAxisProps & { +export type AxisProps = SharedAxisProps & { orientation?: AxisOrientation; }; -export default function Axis({ +export default function Axis({ children, axisClassName, hideAxisLine = false, @@ -29,8 +30,8 @@ export default function Axis({ tickValues, top = 0, ...restProps -}: AxisProps) { - const format = tickFormat || ('tickFormat' in scale ? scale.tickFormat() : toString); +}: AxisProps) { + const format = tickFormat ?? getTickFormatter(scale); const range = scale.range(); const range0 = Number(range[0]) + 0.5 - rangePadding; @@ -38,44 +39,31 @@ export default function Axis({ const isLeft = orientation === ORIENT.left; const isTop = orientation === ORIENT.top; - const axisIsHorizontal = isTop || orientation === ORIENT.bottom; + const horizontal = isTop || orientation === ORIENT.bottom; - const tickPosition = center(scale.copy()); + const tickPosition = getTickPosition(scale); const tickSign = isLeft || isTop ? -1 : 1; const axisFromPoint = new Point({ - x: axisIsHorizontal ? range0 : 0, - y: axisIsHorizontal ? 0 : range0, + x: horizontal ? range0 : 0, + y: horizontal ? 0 : range0, }); const axisToPoint = new Point({ - x: axisIsHorizontal ? range1 : 0, - y: axisIsHorizontal ? 0 : range1, + x: horizontal ? range1 : 0, + y: horizontal ? 0 : range1, }); - const values = - tickValues || - ('ticks' in scale - ? scale.ticks(numTicks) - : scale - .domain() - .filter( - (_, index, arr) => - numTicks == null || - arr.length <= numTicks || - index % Math.round((arr.length - 1) / numTicks) === 0, - )); - - const ticks = values + const ticks = (tickValues ?? getTicks(scale, numTicks)) .filter(value => !hideZero || value !== 0 || value !== '0') .map((value, index) => { const scaledValue = toNumberOrUndefined(tickPosition(value)); const from = new Point({ - x: axisIsHorizontal ? scaledValue : 0, - y: axisIsHorizontal ? 0 : scaledValue, + x: horizontal ? scaledValue : 0, + y: horizontal ? 0 : scaledValue, }); const to = new Point({ - x: axisIsHorizontal ? scaledValue : tickSign * tickLength, - y: axisIsHorizontal ? tickLength * tickSign : scaledValue, + x: horizontal ? scaledValue : tickSign * tickLength, + y: horizontal ? tickLength * tickSign : scaledValue, }); return { value, @@ -86,14 +74,14 @@ export default function Axis({ }; }); - const childProps: ChildRenderProps = { + const childProps: ChildRenderProps = { ...restProps, axisFromPoint, axisToPoint, hideAxisLine, hideTicks, hideZero, - horizontal: axisIsHorizontal, + horizontal, numTicks, orientation, rangePadding, diff --git a/packages/vx-axis/src/axis/AxisBottom.tsx b/packages/vx-axis/src/axis/AxisBottom.tsx index 6994c4555..bbce72448 100644 --- a/packages/vx-axis/src/axis/AxisBottom.tsx +++ b/packages/vx-axis/src/axis/AxisBottom.tsx @@ -2,11 +2,11 @@ import React from 'react'; import cx from 'classnames'; import Axis from './Axis'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps } from '../types'; +import { SharedAxisProps, AxisScale } from '../types'; -export type AxisBottomProps = SharedAxisProps; +export type AxisBottomProps = SharedAxisProps; -export default function AxisBottom({ +export default function AxisBottom({ axisClassName, labelOffset = 8, tickLabelProps = (/** tickValue, index */) => ({ @@ -18,7 +18,7 @@ export default function AxisBottom({ }), tickLength = 8, ...restProps -}: AxisBottomProps) { +}: AxisBottomProps) { return ( = SharedAxisProps; +export type AxisLeftProps = SharedAxisProps; -export default function AxisLeft({ +export default function AxisLeft({ axisClassName, labelOffset = 36, tickLabelProps = (/** tickValue, index */) => ({ @@ -19,7 +19,7 @@ export default function AxisLeft({ }), tickLength = 8, ...restProps -}: AxisLeftProps) { +}: AxisLeftProps) { return ( = { textAnchor: 'middle', @@ -16,7 +16,7 @@ const defaultTextProps: Partial = { fill: '#222', }; -export default function AxisRenderer({ +export default function AxisRenderer({ axisFromPoint, axisLineClassName, axisToPoint, @@ -39,7 +39,7 @@ export default function AxisRenderer({ tickStroke = '#222', tickTransform, ticks, -}: ChildRenderProps) { +}: ChildRenderProps) { let tickLabelFontSize = 10; // track the max tick label size to compute label offset return ( diff --git a/packages/vx-axis/src/axis/AxisRight.tsx b/packages/vx-axis/src/axis/AxisRight.tsx index b0ecd5bb6..0e75cb140 100644 --- a/packages/vx-axis/src/axis/AxisRight.tsx +++ b/packages/vx-axis/src/axis/AxisRight.tsx @@ -2,11 +2,11 @@ import React from 'react'; import cx from 'classnames'; import Axis from './Axis'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps } from '../types'; +import { SharedAxisProps, AxisScale } from '../types'; -export type AxisRightProps = SharedAxisProps; +export type AxisRightProps = SharedAxisProps; -export default function AxisRight({ +export default function AxisRight({ axisClassName, labelOffset = 36, tickLabelProps = (/** tickValue, index */) => ({ @@ -19,7 +19,7 @@ export default function AxisRight({ }), tickLength = 8, ...restProps -}: AxisRightProps) { +}: AxisRightProps) { return ( = SharedAxisProps; +export type AxisTopProps = SharedAxisProps; -export default function AxisTop({ +export default function AxisTop({ axisClassName, labelOffset = 8, tickLabelProps = (/** tickValue, index */) => ({ @@ -18,7 +18,7 @@ export default function AxisTop({ }), tickLength = 8, ...restProps -}: AxisTopProps) { +}: AxisTopProps) { return ( ; + export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; -export type FormattedValue = string | number | undefined; +export type FormattedValue = string | undefined; -export type TickFormatter = (value: Datum, tickIndex: number) => FormattedValue; +export type TickFormatter = (value: T, tickIndex: number) => FormattedValue; -export type TickLabelProps = (val: Datum, index: number) => Partial; +export type TickLabelProps = (value: T, index: number) => Partial; export type TickRendererProps = Partial & { x: number; @@ -15,16 +21,12 @@ export type TickRendererProps = Partial & { formattedValue: FormattedValue; }; -// In order to plot values on an axis, Output must be numeric or coercible to a number. -// Some scales return undefined. -export type ScaleOutput = number | { valueOf(): number } | undefined; - export interface Point { x: number; y: number; } -interface CommonProps { +interface CommonProps { /** The class name applied to the axis line element. */ axisLineClassName?: string; /** If true, will hide the axis line. */ @@ -58,9 +60,9 @@ interface CommonProps { /** Override the component used to render tick labels (instead of from @vx/text) */ tickComponent?: (tickRendererProps: TickRendererProps) => 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; + tickLabelProps?: TickLabelProps; /** The length of the tick lines. */ tickLength: number; /** The color for the tick's stroke value. */ @@ -69,17 +71,17 @@ interface CommonProps { tickTransform?: string; } -export type ChildRenderProps = CommonProps & { +export type ChildRenderProps = CommonProps[0]> & { axisFromPoint: Point; axisToPoint: Point; horizontal: boolean; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: PickD3Scale; - tickPosition: (value: Datum) => ScaleOutput; + scale: Scale; + tickPosition: (value: Parameters[0]) => AxisScaleOutput; /** Axis coordinate sign, -1 for left or top orientation. */ tickSign: 1 | -1; ticks: { - value: Datum; + value: Parameters[0]; index: number; from: Point; to: Point; @@ -87,40 +89,19 @@ export type ChildRenderProps = CommonProps & { }[]; }; -export type SharedAxisProps = Partial> & { +export type SharedAxisProps = Partial< + CommonProps[0]> +> & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ left?: number; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: PickD3Scale; + scale: Scale; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ - tickValues?: Datum[]; + tickValues?: Parameters[0][]; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ - children?: (renderProps: ChildRenderProps) => React.ReactNode; + children?: (renderProps: ChildRenderProps) => React.ReactNode; }; - -// export type GenericScale = -// | ScaleNoRangeRound -// | ScaleWithRangeRound; - -// interface ScaleNoRangeRound { -// (value: ScaleInput): ScaleOutput | [ScaleOutput, ScaleOutput]; // quantize scales return an array -// domain(): ScaleInput[] | [ScaleInput, ScaleInput]; -// domain(scaleInput: ScaleInput[] | [ScaleInput, ScaleInput]): unknown; // we can't capture the copy of the type accurately -// range(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; -// range(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): unknown; -// ticks?: (count: number) => ScaleInput[] | [ScaleInput, ScaleInput]; -// bandwidth?: () => number; -// round?: () => boolean; -// tickFormat?: () => (input: ScaleInput) => FormattedValue; -// copy(): this; -// } - -// // We cannot have optional methods AND overloads, so define a separate type for rangeRound -// interface ScaleWithRangeRound extends ScaleNoRangeRound { -// rangeRound(): ScaleOutput[] | [ScaleOutput, ScaleOutput]; -// rangeRound(scaleOutput: ScaleOutput[] | [ScaleOutput, ScaleOutput]): unknown; -// } diff --git a/packages/vx-axis/src/utils/center.ts b/packages/vx-axis/src/utils/center.ts deleted file mode 100644 index db53c8fcd..000000000 --- a/packages/vx-axis/src/utils/center.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { D3Scale, DefaultThresholdInput } from '@vx/scale'; - -/** - * Returns a function that applies a centering transform to a scaled value, - * if `Output` is of type `number` and `scale.bandwidth()` is defined - */ -export default function center( - scale: D3Scale, -) { - let offset = 'bandwidth' in scale ? scale.bandwidth() / 2 : 0; - if ('round' in scale && scale.round()) offset = Math.round(offset); - - return (d: ScaleInput) => { - const scaledValue = scale(d); - if (typeof scaledValue === 'number') return scaledValue + offset; - // quantize scales return an array of values - if (Array.isArray(scaledValue)) return Number(scaledValue[0]) + offset; - return scaledValue; - }; -} diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts new file mode 100644 index 000000000..89a415409 --- /dev/null +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -0,0 +1,17 @@ +import { AxisScale, TickFormatter } from '../types'; + +/** + * Returns a tick position for the given tick value + */ +export default function getTickFormatter(scale: Scale) { + // Broaden type before using 'xxx' in s as typeguard. + const s = scale as AxisScale; + + // For point or band scales, + // have to add offset to make the tick centered. + if ('tickFormat' in s) { + return s.tickFormat() as TickFormatter[0]>; + } + + return toString as TickFormatter[0]>; +} diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts new file mode 100644 index 000000000..cc80f0d97 --- /dev/null +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -0,0 +1,23 @@ +import { AxisScale, AxisScaleOutput } from '../types'; + +/** + * Create a function that returns a tick position for the given tick value + */ +export default function getTickPosition(scale: Scale) { + // Broaden type before using 'xxx' in s as typeguard. + const s = scale as AxisScale; + + // For point or band scales, + // have to add offset to make the tick centered. + if ('bandwidth' in s) { + let offset = s.bandwidth() / 2; + if (s.round()) offset = Math.round(offset); + return (d: Parameters[0]) => { + const scaledValue = s(d); + + return typeof scaledValue === 'number' ? scaledValue + offset : scaledValue; + }; + } + + return scale as (d: Parameters[0]) => AxisScaleOutput; +} diff --git a/packages/vx-axis/src/utils/getTicks.ts b/packages/vx-axis/src/utils/getTicks.ts new file mode 100644 index 000000000..500e9bc41 --- /dev/null +++ b/packages/vx-axis/src/utils/getTicks.ts @@ -0,0 +1,33 @@ +import { D3Scale, StringLike, DefaultThresholdInput } from '@vx/scale'; + +export default function getTicks< + Output, + DiscreteInput extends StringLike, + ThresholdInput extends DefaultThresholdInput +>(scale: D3Scale, numTicks?: number) { + if ('ticks' in scale) { + return scale.ticks(numTicks); + } + if ('quantiles' in scale) { + return scale.quantiles(); + } + if ('padding' in scale || 'unknown' in scale) { + return scale + .domain() + .filter( + (_, index, arr) => + numTicks == null || + arr.length <= numTicks || + index % Math.round((arr.length - 1) / numTicks) === 0, + ); + } + + return scale + .domain() + .filter( + (_, index, arr) => + numTicks == null || + arr.length <= numTicks || + index % Math.round((arr.length - 1) / numTicks) === 0, + ); +} From 45554568a8507cb3facc087b2c362f46ce842680 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 29 Jul 2020 16:54:39 -0700 Subject: [PATCH 05/23] reduce generics --- packages/vx-axis/src/axis/Axis.tsx | 2 +- packages/vx-axis/src/axis/AxisBottom.tsx | 4 +--- packages/vx-axis/src/axis/AxisLeft.tsx | 4 +--- packages/vx-axis/src/types.ts | 10 +++++--- .../vx-axis/src/utils/getTickFormatter.ts | 10 ++++++-- packages/vx-axis/src/utils/getTickPosition.ts | 8 ++++++- packages/vx-axis/test/Axis.test.tsx | 24 +++++++++++-------- packages/vx-axis/test/scales.test.tsx | 6 ++--- 8 files changed, 41 insertions(+), 27 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index d7c075865..c9c7f8ac3 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -95,7 +95,7 @@ export default function Axis({ return ( - {children ? children(childProps) : } + {children ? children(childProps) : {...childProps} />} ); } diff --git a/packages/vx-axis/src/axis/AxisBottom.tsx b/packages/vx-axis/src/axis/AxisBottom.tsx index bbce72448..1761bb5d9 100644 --- a/packages/vx-axis/src/axis/AxisBottom.tsx +++ b/packages/vx-axis/src/axis/AxisBottom.tsx @@ -4,8 +4,6 @@ import Axis from './Axis'; import ORIENT from '../constants/orientation'; import { SharedAxisProps, AxisScale } from '../types'; -export type AxisBottomProps = SharedAxisProps; - export default function AxisBottom({ axisClassName, labelOffset = 8, @@ -18,7 +16,7 @@ export default function AxisBottom({ }), tickLength = 8, ...restProps -}: AxisBottomProps) { +}: SharedAxisProps) { return ( = SharedAxisProps; - export default function AxisLeft({ axisClassName, labelOffset = 36, @@ -19,7 +17,7 @@ export default function AxisLeft({ }), tickLength = 8, ...restProps -}: AxisLeftProps) { +}: SharedAxisProps) { return ( ; +export type AxisScale< + Output extends AxisScaleOutput = AxisScaleOutput, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +> = D3Scale; export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; @@ -89,7 +93,7 @@ export type ChildRenderProps = CommonProps = Partial< +export type SharedAxisProps> = Partial< CommonProps[0]> > & { /** The class name applied to the outermost axis group element. */ diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts index 89a415409..9bf06efc5 100644 --- a/packages/vx-axis/src/utils/getTickFormatter.ts +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -1,9 +1,15 @@ -import { AxisScale, TickFormatter } from '../types'; +import { StringLike, DefaultThresholdInput } from '@vx/scale'; +import { AxisScale, TickFormatter, AxisScaleOutput } from '../types'; /** * Returns a tick position for the given tick value */ -export default function getTickFormatter(scale: Scale) { +export default function getTickFormatter< + Output extends AxisScaleOutput, + DiscreteInput extends StringLike, + ThresholdInput extends DefaultThresholdInput, + Scale extends AxisScale +>(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. const s = scale as AxisScale; diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts index cc80f0d97..491f785c3 100644 --- a/packages/vx-axis/src/utils/getTickPosition.ts +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -1,9 +1,15 @@ +import { StringLike, DefaultThresholdInput } from '@vx/scale'; import { AxisScale, AxisScaleOutput } from '../types'; /** * Create a function that returns a tick position for the given tick value */ -export default function getTickPosition(scale: Scale) { +export default function getTickPosition< + Output extends AxisScaleOutput, + DiscreteInput extends StringLike, + ThresholdInput extends DefaultThresholdInput, + Scale extends AxisScale +>(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. const s = scale as AxisScale; diff --git a/packages/vx-axis/test/Axis.test.tsx b/packages/vx-axis/test/Axis.test.tsx index a52f90258..00dbb7181 100644 --- a/packages/vx-axis/test/Axis.test.tsx +++ b/packages/vx-axis/test/Axis.test.tsx @@ -185,7 +185,9 @@ describe('', () => { }); test('tickFormat should have access to tick index', () => { - const wrapper = shallow( i} />); + const wrapper = shallow( + `${i}`} />, + ); expect( wrapper .children() @@ -196,15 +198,17 @@ describe('', () => { }); test('it should use center if scale is band', () => { - const overrideAxisProps = { - orientation: 'bottom' as const, - scale: scaleBand({ - range: [10, 0], - round: true, - domain: ['a', 'b'], - }), - }; - const wrapper = shallow(); + const wrapper = shallow( + , + ); const points = wrapper.children().find(Line); // First point expect(points.at(0).prop('from')).toEqual({ x: 8, y: 0 }); diff --git a/packages/vx-axis/test/scales.test.tsx b/packages/vx-axis/test/scales.test.tsx index 8f365bab5..e5f11678e 100644 --- a/packages/vx-axis/test/scales.test.tsx +++ b/packages/vx-axis/test/scales.test.tsx @@ -18,16 +18,14 @@ import { DefaultThresholdInput, } from '@vx/scale'; import { Axis } from '../src'; -import { ScaleOutput } from '../src/types'; +import { AxisScaleOutput, AxisScale } from '../src/types'; const axisProps = { orientation: 'left' as const, label: 'test axis', }; -function setup( - scale: D3Scale, -) { +function setup(scale: Scale) { return () => shallow(); } From e1033dd794f11077c0cfec0ad7dd97cf1ec46aaa Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 29 Jul 2020 17:21:14 -0700 Subject: [PATCH 06/23] fix: types in test --- packages/vx-axis/src/axis/Axis.tsx | 23 +++++++++++++++----- packages/vx-axis/src/axis/AxisRenderer.tsx | 9 ++++++-- packages/vx-axis/src/types.ts | 25 ++++++++++++++-------- packages/vx-axis/test/scales.test.tsx | 7 +++--- 4 files changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index c9c7f8ac3..b7f105776 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -2,6 +2,7 @@ import React from 'react'; import cx from 'classnames'; import { Point } from '@vx/point'; import { Group } from '@vx/group'; +import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; @@ -10,11 +11,19 @@ import toNumberOrUndefined from '../utils/toNumberOrUndefined'; import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; -export type AxisProps = SharedAxisProps & { +export type AxisProps< + Scale extends AxisScale, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +> = SharedAxisProps & { orientation?: AxisOrientation; }; -export default function Axis({ +export default function Axis< + Scale extends AxisScale, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +>({ children, axisClassName, hideAxisLine = false, @@ -30,7 +39,7 @@ export default function Axis({ tickValues, top = 0, ...restProps -}: AxisProps) { +}: AxisProps) { const format = tickFormat ?? getTickFormatter(scale); const range = scale.range(); @@ -74,7 +83,7 @@ export default function Axis({ }; }); - const childProps: ChildRenderProps = { + const childProps: ChildRenderProps = { ...restProps, axisFromPoint, axisToPoint, @@ -95,7 +104,11 @@ export default function Axis({ return ( - {children ? children(childProps) : {...childProps} />} + {children ? ( + children(childProps) + ) : ( + {...childProps} /> + )} ); } diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx index 84d21f478..b2037ccb2 100644 --- a/packages/vx-axis/src/axis/AxisRenderer.tsx +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -5,6 +5,7 @@ import { Group } from '@vx/group'; import { Text } from '@vx/text'; import { TextProps } from '@vx/text/lib/Text'; +import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; import getLabelTransform from '../utils/labelTransform'; import { ChildRenderProps, AxisScale } from '../types'; @@ -16,7 +17,11 @@ const defaultTextProps: Partial = { fill: '#222', }; -export default function AxisRenderer({ +export default function AxisRenderer< + Scale extends AxisScale, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +>({ axisFromPoint, axisLineClassName, axisToPoint, @@ -39,7 +44,7 @@ export default function AxisRenderer({ tickStroke = '#222', tickTransform, ticks, -}: ChildRenderProps) { +}: ChildRenderProps) { let tickLabelFontSize = 10; // track the max tick label size to compute label offset return ( diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index 7ba820129..b09a2e359 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -6,10 +6,9 @@ import { TextProps } from '@vx/text/lib/Text'; export type AxisScaleOutput = number | undefined; export type AxisScale< - Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput -> = D3Scale; +> = D3Scale; export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; @@ -75,12 +74,16 @@ interface CommonProps { tickTransform?: string; } -export type ChildRenderProps = CommonProps[0]> & { +export type ChildRenderProps< + Scale extends AxisScale, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +> = CommonProps[0]> & { axisFromPoint: Point; axisToPoint: Point; horizontal: boolean; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: Scale; + scale: AxisScale; tickPosition: (value: Parameters[0]) => AxisScaleOutput; /** Axis coordinate sign, -1 for left or top orientation. */ tickSign: 1 | -1; @@ -93,19 +96,23 @@ export type ChildRenderProps = CommonProps> = Partial< - CommonProps[0]> -> & { +export type SharedAxisProps< + Scale extends AxisScale, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput +> = Partial[0]>> & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ left?: number; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: Scale; + scale: AxisScale; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ tickValues?: Parameters[0][]; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ - children?: (renderProps: ChildRenderProps) => React.ReactNode; + children?: ( + renderProps: ChildRenderProps, + ) => React.ReactNode; }; diff --git a/packages/vx-axis/test/scales.test.tsx b/packages/vx-axis/test/scales.test.tsx index e5f11678e..af009e3a7 100644 --- a/packages/vx-axis/test/scales.test.tsx +++ b/packages/vx-axis/test/scales.test.tsx @@ -13,19 +13,20 @@ import { scaleThreshold, scaleTime, scaleUtc, - D3Scale, StringLike, DefaultThresholdInput, } from '@vx/scale'; import { Axis } from '../src'; -import { AxisScaleOutput, AxisScale } from '../src/types'; +import { AxisScale } from '../src/types'; const axisProps = { orientation: 'left' as const, label: 'test axis', }; -function setup(scale: Scale) { +function setup( + scale: AxisScale, +) { return () => shallow(); } From d6472a0e9570eae7975210a3dc1b49680c7de612 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 29 Jul 2020 17:56:25 -0700 Subject: [PATCH 07/23] test: update unit tests --- packages/vx-axis/src/axis/Axis.tsx | 20 ++- packages/vx-axis/src/axis/AxisRenderer.tsx | 6 +- packages/vx-axis/src/types.ts | 27 +++- packages/vx-axis/test/scales.test.tsx | 173 ++++++++++++--------- 4 files changed, 135 insertions(+), 91 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index b7f105776..2bf39c175 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -4,7 +4,13 @@ import { Point } from '@vx/point'; import { Group } from '@vx/group'; import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScale } from '../types'; +import { + SharedAxisProps, + AxisOrientation, + ChildRenderProps, + AxisScaleOutput, + AxisScale, +} from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; @@ -12,15 +18,15 @@ import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; export type AxisProps< - Scale extends AxisScale, + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput -> = SharedAxisProps & { +> = SharedAxisProps & { orientation?: AxisOrientation; }; export default function Axis< - Scale extends AxisScale, + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput >({ @@ -39,7 +45,7 @@ export default function Axis< tickValues, top = 0, ...restProps -}: AxisProps) { +}: AxisProps) { const format = tickFormat ?? getTickFormatter(scale); const range = scale.range(); @@ -83,7 +89,7 @@ export default function Axis< }; }); - const childProps: ChildRenderProps = { + const childProps: ChildRenderProps = { ...restProps, axisFromPoint, axisToPoint, @@ -107,7 +113,7 @@ export default function Axis< {children ? ( children(childProps) ) : ( - {...childProps} /> + {...childProps} /> )} ); diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx index b2037ccb2..0e660daa8 100644 --- a/packages/vx-axis/src/axis/AxisRenderer.tsx +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -8,7 +8,7 @@ import { TextProps } from '@vx/text/lib/Text'; import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; import getLabelTransform from '../utils/labelTransform'; -import { ChildRenderProps, AxisScale } from '../types'; +import { ChildRenderProps, AxisScaleOutput } from '../types'; const defaultTextProps: Partial = { textAnchor: 'middle', @@ -18,7 +18,7 @@ const defaultTextProps: Partial = { }; export default function AxisRenderer< - Scale extends AxisScale, + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput >({ @@ -44,7 +44,7 @@ export default function AxisRenderer< tickStroke = '#222', tickTransform, ticks, -}: ChildRenderProps) { +}: ChildRenderProps) { let tickLabelFontSize = 10; // track the max tick label size to compute label offset return ( diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index b09a2e359..1fefd379a 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -6,9 +6,10 @@ import { TextProps } from '@vx/text/lib/Text'; export type AxisScaleOutput = number | undefined; export type AxisScale< + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput -> = D3Scale; +> = D3Scale; export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; @@ -75,15 +76,20 @@ interface CommonProps { } export type ChildRenderProps< - Scale extends AxisScale, + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, + Scale extends AxisScale = AxisScale< + Output, + DiscreteInput, + ThresholdInput + > > = CommonProps[0]> & { axisFromPoint: Point; axisToPoint: Point; horizontal: boolean; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: AxisScale; + scale: AxisScale; tickPosition: (value: Parameters[0]) => AxisScaleOutput; /** Axis coordinate sign, -1 for left or top orientation. */ tickSign: 1 | -1; @@ -97,22 +103,27 @@ export type ChildRenderProps< }; export type SharedAxisProps< - Scale extends AxisScale, + Output extends AxisScaleOutput = AxisScaleOutput, DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, + Scale extends AxisScale = AxisScale< + Output, + DiscreteInput, + ThresholdInput + > > = Partial[0]>> & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ left?: number; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: AxisScale; + scale: Scale; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ tickValues?: Parameters[0][]; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ children?: ( - renderProps: ChildRenderProps, + renderProps: ChildRenderProps, ) => React.ReactNode; }; diff --git a/packages/vx-axis/test/scales.test.tsx b/packages/vx-axis/test/scales.test.tsx index af009e3a7..e3f46136f 100644 --- a/packages/vx-axis/test/scales.test.tsx +++ b/packages/vx-axis/test/scales.test.tsx @@ -13,156 +13,183 @@ import { scaleThreshold, scaleTime, scaleUtc, - StringLike, - DefaultThresholdInput, } from '@vx/scale'; import { Axis } from '../src'; -import { AxisScale } from '../src/types'; const axisProps = { orientation: 'left' as const, label: 'test axis', }; -function setup( - scale: AxisScale, -) { - return () => shallow(); -} - describe('Axis scales', () => { it('should render with scaleBand', () => { expect( - setup( - scaleBand({ - range: [10, 0], - round: true, - domain: ['a', 'b', 'c'], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleLinear', () => { expect( - setup( - scaleLinear({ - range: [10, 0], - round: true, - domain: [0, 10], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleLog', () => { expect( - setup( - scaleLog({ - range: [10, 0], - round: true, - domain: [1, 10, 100, 1000], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleOrdinal', () => { expect( - setup( - scaleOrdinal({ - range: [0, 10], - domain: ['a', 'b', 'c'], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scalePoint', () => { expect( - setup( - scalePoint({ - range: [0, 10], - round: true, - domain: ['a', 'b', 'c'], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scalePower', () => { expect( - setup( - scalePower({ - range: [1, 2, 3, 4, 5], - domain: [1, 10, 100, 1000, 10000], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleQuantile', () => { expect( - setup( - scaleQuantile({ - range: [0, 2, 4, 6, 8, 10], - domain: [1, 10, 100, 1000, 10000], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleQuantize', () => { expect( - setup( - scaleQuantize({ - range: [1, 10], - domain: [1, 10], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleSymlog', () => { expect( - setup( - scaleSymlog({ - range: [1, 10], - domain: [1, 10], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleThreshold', () => { expect( - setup( - scaleThreshold({ - range: [1, 10], - domain: [1, 10], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleTime', () => { expect( - setup( - scaleTime({ - range: [1, 10], - domain: [new Date('2020-01-01'), new Date('2020-01-05')], - }), + shallow( + , ), ).not.toThrow(); }); it('should render with scaleUtc', () => { expect( - setup( - scaleUtc({ - range: [1, 10], - domain: [new Date('2020-01-01'), new Date('2020-01-05')], - }), + shallow( + , ), ).not.toThrow(); }); From 79f9f79c98a6f822a6836e1ba86bae5272b94f4e Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Wed, 29 Jul 2020 18:06:43 -0700 Subject: [PATCH 08/23] fix: helper function types --- packages/vx-axis/src/axis/Axis.tsx | 12 ++---- .../vx-axis/src/utils/getTickFormatter.ts | 40 +++++++++++++++---- packages/vx-axis/src/utils/getTickPosition.ts | 12 ++++-- 3 files changed, 44 insertions(+), 20 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 2bf39c175..2da0d98d2 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -4,13 +4,7 @@ import { Point } from '@vx/point'; import { Group } from '@vx/group'; import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; -import { - SharedAxisProps, - AxisOrientation, - ChildRenderProps, - AxisScaleOutput, - AxisScale, -} from '../types'; +import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScaleOutput } from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; @@ -46,7 +40,7 @@ export default function Axis< top = 0, ...restProps }: AxisProps) { - const format = tickFormat ?? getTickFormatter(scale); + const format = tickFormat ?? getTickFormatter(scale); const range = scale.range(); const range0 = Number(range[0]) + 0.5 - rangePadding; @@ -56,7 +50,7 @@ export default function Axis< const isTop = orientation === ORIENT.top; const horizontal = isTop || orientation === ORIENT.bottom; - const tickPosition = getTickPosition(scale); + const tickPosition = getTickPosition(scale); const tickSign = isLeft || isTop ? -1 : 1; const axisFromPoint = new Point({ diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts index 9bf06efc5..b383ff2b9 100644 --- a/packages/vx-axis/src/utils/getTickFormatter.ts +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -1,17 +1,21 @@ -import { StringLike, DefaultThresholdInput } from '@vx/scale'; -import { AxisScale, TickFormatter, AxisScaleOutput } from '../types'; +import { StringLike, DefaultThresholdInput, D3Scale, DefaultOutput } from '@vx/scale'; +import { TickFormatter } from '../types'; /** * Returns a tick position for the given tick value */ export default function getTickFormatter< - Output extends AxisScaleOutput, - DiscreteInput extends StringLike, - ThresholdInput extends DefaultThresholdInput, - Scale extends AxisScale + Output = DefaultOutput, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, + Scale extends D3Scale = D3Scale< + Output, + DiscreteInput, + ThresholdInput + > >(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. - const s = scale as AxisScale; + const s = scale as D3Scale; // For point or band scales, // have to add offset to make the tick centered. @@ -21,3 +25,25 @@ export default function getTickFormatter< return toString as TickFormatter[0]>; } + +// import { StringLike, DefaultThresholdInput } from '@vx/scale'; +// import { AxisScale, TickFormatter, AxisScaleOutput } from '../types'; + +// /** +// * Returns a tick position for the given tick value +// */ +// export default function getTickFormatter< +// Output extends AxisScaleOutput, +// DiscreteInput extends StringLike, +// ThresholdInput extends DefaultThresholdInput +// >(scale: AxisScale) { +// type Scale = AxisScale; + +// // For point or band scales, +// // have to add offset to make the tick centered. +// if ('tickFormat' in scale) { +// return scale.tickFormat() as TickFormatter[0]>; +// } + +// return toString as TickFormatter[0]>; +// } diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts index 491f785c3..1c92b1919 100644 --- a/packages/vx-axis/src/utils/getTickPosition.ts +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -5,10 +5,14 @@ import { AxisScale, AxisScaleOutput } from '../types'; * Create a function that returns a tick position for the given tick value */ export default function getTickPosition< - Output extends AxisScaleOutput, - DiscreteInput extends StringLike, - ThresholdInput extends DefaultThresholdInput, - Scale extends AxisScale + Output extends AxisScaleOutput = AxisScaleOutput, + DiscreteInput extends StringLike = StringLike, + ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, + Scale extends AxisScale = AxisScale< + Output, + DiscreteInput, + ThresholdInput + > >(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. const s = scale as AxisScale; From 4fc90e994552e41be08b9c4c2c2a9c5150474496 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 02:13:29 -0700 Subject: [PATCH 09/23] feat: add AnyD3Scale type --- packages/vx-scale/src/types/Scale.ts | 21 ++++++++++++ packages/vx-scale/src/utils/inferScaleType.ts | 34 +++++++++---------- 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/packages/vx-scale/src/types/Scale.ts b/packages/vx-scale/src/types/Scale.ts index c57221196..410e18470 100644 --- a/packages/vx-scale/src/types/Scale.ts +++ b/packages/vx-scale/src/types/Scale.ts @@ -62,3 +62,24 @@ export type D3Scale< DiscreteInput extends StringLike = StringLike, ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput > = ValueOf>; + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AnyD3Scale = D3Scale; + +export type InferD3ScaleOutput = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Scale extends D3Scale ? X : DefaultOutput; + +export type InferD3ScaleDiscreteInput = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Scale extends D3Scale ? X : StringLike; + +export type InferD3ScaleThresholdInput = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + Scale extends D3Scale ? X : DefaultThresholdInput; + +export type UnpackD3Scale = D3Scale< + InferD3ScaleOutput, + InferD3ScaleDiscreteInput, + InferD3ScaleThresholdInput +>; diff --git a/packages/vx-scale/src/utils/inferScaleType.ts b/packages/vx-scale/src/utils/inferScaleType.ts index 146e0aa16..994324820 100644 --- a/packages/vx-scale/src/utils/inferScaleType.ts +++ b/packages/vx-scale/src/utils/inferScaleType.ts @@ -1,53 +1,51 @@ import { ScaleTime } from 'd3-scale'; -import { StringLike } from '../types/Base'; -import { DefaultThresholdInput, D3Scale } from '../types/Scale'; +import { D3Scale, AnyD3Scale, InferD3ScaleOutput } from '../types/Scale'; import { ScaleType } from '../types/ScaleConfig'; import isUtcScale from './isUtcScale'; -export default function inferScaleType< - Output, - DiscreteInput extends StringLike, - ThresholdInput extends DefaultThresholdInput ->(scale: D3Scale): ScaleType { +export default function inferScaleType(scale: Scale): ScaleType { + const s = scale as D3Scale; + // Try a sequence of typeguards to figure out the scale type - if ('paddingInner' in scale) { + if ('paddingInner' in s) { return 'band'; } - if ('padding' in scale) { + if ('padding' in s) { return 'point'; } - if ('quantiles' in scale) { + if ('quantiles' in s) { return 'quantile'; } - if ('base' in scale) { + if ('base' in s) { return 'log'; } - if ('exponent' in scale) { - return scale.exponent() === 0.5 ? 'sqrt' : 'pow'; + if ('exponent' in s) { + return s.exponent() === 0.5 ? 'sqrt' : 'pow'; } - if ('constant' in scale) { + if ('constant' in s) { return 'symlog'; } - if ('clamp' in scale) { + if ('clamp' in s) { // Linear, Time or Utc scales - if (scale.ticks()[0] instanceof Date) { + if (s.ticks()[0] instanceof Date) { + type Output = InferD3ScaleOutput; return isUtcScale(scale as ScaleTime) ? 'utc' : 'time'; } return 'linear'; } - if ('nice' in scale) { + if ('nice' in s) { return 'quantize'; } - if ('invertExtent' in scale) { + if ('invertExtent' in s) { return 'threshold'; } From 04fd1c43ca2382f827e6796d6dfe38d963bce395 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 03:11:11 -0700 Subject: [PATCH 10/23] fix: types --- packages/vx-axis/src/axis/Axis.tsx | 29 +++------- packages/vx-axis/src/axis/AxisRenderer.tsx | 11 +--- packages/vx-axis/src/types.ts | 55 +++++++------------ .../vx-axis/src/utils/getTickFormatter.ts | 42 ++------------ packages/vx-axis/src/utils/getTickPosition.ts | 18 ++---- packages/vx-axis/src/utils/getTicks.ts | 33 ++++------- packages/vx-axis/src/utils/labelTransform.ts | 4 +- packages/vx-scale/src/types/Scale.ts | 18 ++++-- 8 files changed, 66 insertions(+), 144 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 2da0d98d2..d7c075865 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -2,28 +2,19 @@ import React from 'react'; import cx from 'classnames'; import { Point } from '@vx/point'; import { Group } from '@vx/group'; -import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScaleOutput } from '../types'; +import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; -export type AxisProps< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput -> = SharedAxisProps & { +export type AxisProps = SharedAxisProps & { orientation?: AxisOrientation; }; -export default function Axis< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput ->({ +export default function Axis({ children, axisClassName, hideAxisLine = false, @@ -39,8 +30,8 @@ export default function Axis< tickValues, top = 0, ...restProps -}: AxisProps) { - const format = tickFormat ?? getTickFormatter(scale); +}: AxisProps) { + const format = tickFormat ?? getTickFormatter(scale); const range = scale.range(); const range0 = Number(range[0]) + 0.5 - rangePadding; @@ -50,7 +41,7 @@ export default function Axis< const isTop = orientation === ORIENT.top; const horizontal = isTop || orientation === ORIENT.bottom; - const tickPosition = getTickPosition(scale); + const tickPosition = getTickPosition(scale); const tickSign = isLeft || isTop ? -1 : 1; const axisFromPoint = new Point({ @@ -83,7 +74,7 @@ export default function Axis< }; }); - const childProps: ChildRenderProps = { + const childProps: ChildRenderProps = { ...restProps, axisFromPoint, axisToPoint, @@ -104,11 +95,7 @@ export default function Axis< return ( - {children ? ( - children(childProps) - ) : ( - {...childProps} /> - )} + {children ? children(childProps) : } ); } diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx index 0e660daa8..84d21f478 100644 --- a/packages/vx-axis/src/axis/AxisRenderer.tsx +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -5,10 +5,9 @@ import { Group } from '@vx/group'; import { Text } from '@vx/text'; import { TextProps } from '@vx/text/lib/Text'; -import { StringLike, DefaultThresholdInput } from '@vx/scale'; import ORIENT from '../constants/orientation'; import getLabelTransform from '../utils/labelTransform'; -import { ChildRenderProps, AxisScaleOutput } from '../types'; +import { ChildRenderProps, AxisScale } from '../types'; const defaultTextProps: Partial = { textAnchor: 'middle', @@ -17,11 +16,7 @@ const defaultTextProps: Partial = { fill: '#222', }; -export default function AxisRenderer< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput ->({ +export default function AxisRenderer({ axisFromPoint, axisLineClassName, axisToPoint, @@ -44,7 +39,7 @@ export default function AxisRenderer< tickStroke = '#222', tickTransform, ticks, -}: ChildRenderProps) { +}: ChildRenderProps) { let tickLabelFontSize = 10; // track the max tick label size to compute label offset return ( diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index 1fefd379a..69afa33c0 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -1,20 +1,22 @@ -import { D3Scale, StringLike, DefaultThresholdInput } from '@vx/scale'; +import { D3Scale, NumberLike, AnyD3Scale } from '@vx/scale'; import { TextProps } from '@vx/text/lib/Text'; // In order to plot values on an axis, output of the scale must be number. // Some scales return undefined. -export type AxisScaleOutput = number | undefined; +export type AxisScaleOutput = number | NumberLike | undefined; -export type AxisScale< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput -> = D3Scale; +/** A catch-all type for scales that are compatible with axis */ +export type AxisScale = + // eslint-disable-next-line @typescript-eslint/no-explicit-any + D3Scale; export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; export type FormattedValue = string | undefined; +/** Get type of tick value (scale input) from D3 scale */ +export type TickValue = Parameters[0]; + export type TickFormatter = (value: T, tickIndex: number) => FormattedValue; export type TickLabelProps = (value: T, index: number) => Partial; @@ -75,26 +77,22 @@ interface CommonProps { tickTransform?: string; } -export type ChildRenderProps< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, - Scale extends AxisScale = AxisScale< - Output, - DiscreteInput, - ThresholdInput - > -> = CommonProps[0]> & { +export type ChildRenderProps = CommonProps> & { + /** Start point of the axis line */ axisFromPoint: Point; + /** End point of the axis line */ axisToPoint: Point; + /** Whether this axis is horizontal */ horizontal: boolean; /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ - scale: AxisScale; - tickPosition: (value: Parameters[0]) => AxisScaleOutput; + scale: Scale; + /** Function to compute tick position along the axis from tick value */ + tickPosition: (value: TickValue) => AxisScaleOutput; /** Axis coordinate sign, -1 for left or top orientation. */ tickSign: 1 | -1; + /** Computed ticks with positions and formatted value */ ticks: { - value: Parameters[0]; + value: TickValue; index: number; from: Point; to: Point; @@ -102,16 +100,7 @@ export type ChildRenderProps< }[]; }; -export type SharedAxisProps< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, - Scale extends AxisScale = AxisScale< - Output, - DiscreteInput, - ThresholdInput - > -> = Partial[0]>> & { +export type SharedAxisProps = Partial>> & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ @@ -119,11 +108,9 @@ export type SharedAxisProps< /** A [d3](https://github.com/d3/d3-scale) or [vx](https://github.com/hshoff/vx/tree/master/packages/vx-scale) scale function. */ scale: Scale; /** An array of values that determine the number and values of the ticks. Falls back to `scale.ticks()` or `.domain()`. */ - tickValues?: Parameters[0][]; + tickValues?: TickValue[]; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ - children?: ( - renderProps: ChildRenderProps, - ) => React.ReactNode; + children?: (renderProps: ChildRenderProps) => React.ReactNode; }; diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts index b383ff2b9..3e726e37b 100644 --- a/packages/vx-axis/src/utils/getTickFormatter.ts +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -1,49 +1,17 @@ -import { StringLike, DefaultThresholdInput, D3Scale, DefaultOutput } from '@vx/scale'; -import { TickFormatter } from '../types'; +import { TickFormatter, AxisScale, TickValue } from '../types'; /** * Returns a tick position for the given tick value */ -export default function getTickFormatter< - Output = DefaultOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, - Scale extends D3Scale = D3Scale< - Output, - DiscreteInput, - ThresholdInput - > ->(scale: Scale) { +export default function getTickFormatter(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. - const s = scale as D3Scale; + const s = scale as AxisScale; // For point or band scales, // have to add offset to make the tick centered. if ('tickFormat' in s) { - return s.tickFormat() as TickFormatter[0]>; + return s.tickFormat() as TickFormatter>; } - return toString as TickFormatter[0]>; + return toString as TickFormatter>; } - -// import { StringLike, DefaultThresholdInput } from '@vx/scale'; -// import { AxisScale, TickFormatter, AxisScaleOutput } from '../types'; - -// /** -// * Returns a tick position for the given tick value -// */ -// export default function getTickFormatter< -// Output extends AxisScaleOutput, -// DiscreteInput extends StringLike, -// ThresholdInput extends DefaultThresholdInput -// >(scale: AxisScale) { -// type Scale = AxisScale; - -// // For point or band scales, -// // have to add offset to make the tick centered. -// if ('tickFormat' in scale) { -// return scale.tickFormat() as TickFormatter[0]>; -// } - -// return toString as TickFormatter[0]>; -// } diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts index 1c92b1919..19293e486 100644 --- a/packages/vx-axis/src/utils/getTickPosition.ts +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -1,19 +1,9 @@ -import { StringLike, DefaultThresholdInput } from '@vx/scale'; -import { AxisScale, AxisScaleOutput } from '../types'; +import { AxisScale, AxisScaleOutput, TickValue } from '../types'; /** * Create a function that returns a tick position for the given tick value */ -export default function getTickPosition< - Output extends AxisScaleOutput = AxisScaleOutput, - DiscreteInput extends StringLike = StringLike, - ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, - Scale extends AxisScale = AxisScale< - Output, - DiscreteInput, - ThresholdInput - > ->(scale: Scale) { +export default function getTickPosition(scale: Scale) { // Broaden type before using 'xxx' in s as typeguard. const s = scale as AxisScale; @@ -22,12 +12,12 @@ export default function getTickPosition< if ('bandwidth' in s) { let offset = s.bandwidth() / 2; if (s.round()) offset = Math.round(offset); - return (d: Parameters[0]) => { + return (d: TickValue) => { const scaledValue = s(d); return typeof scaledValue === 'number' ? scaledValue + offset : scaledValue; }; } - return scale as (d: Parameters[0]) => AxisScaleOutput; + return scale as (d: TickValue) => AxisScaleOutput; } diff --git a/packages/vx-axis/src/utils/getTicks.ts b/packages/vx-axis/src/utils/getTicks.ts index 500e9bc41..88247361c 100644 --- a/packages/vx-axis/src/utils/getTicks.ts +++ b/packages/vx-axis/src/utils/getTicks.ts @@ -1,28 +1,17 @@ -import { D3Scale, StringLike, DefaultThresholdInput } from '@vx/scale'; +import { AnyD3Scale } from '@vx/scale'; +import { TickValue } from '../types'; -export default function getTicks< - Output, - DiscreteInput extends StringLike, - ThresholdInput extends DefaultThresholdInput ->(scale: D3Scale, numTicks?: number) { - if ('ticks' in scale) { - return scale.ticks(numTicks); - } - if ('quantiles' in scale) { - return scale.quantiles(); - } - if ('padding' in scale || 'unknown' in scale) { - return scale - .domain() - .filter( - (_, index, arr) => - numTicks == null || - arr.length <= numTicks || - index % Math.round((arr.length - 1) / numTicks) === 0, - ); +export default function getTicks( + scale: Scale, + numTicks?: number, +): TickValue[] { + const s = scale as AnyD3Scale; + + if ('ticks' in s) { + return s.ticks(numTicks); } - return scale + return s .domain() .filter( (_, index, arr) => diff --git a/packages/vx-axis/src/utils/labelTransform.ts b/packages/vx-axis/src/utils/labelTransform.ts index ce964d543..9b5048ffb 100644 --- a/packages/vx-axis/src/utils/labelTransform.ts +++ b/packages/vx-axis/src/utils/labelTransform.ts @@ -1,12 +1,12 @@ import { TextProps } from '@vx/text/lib/Text'; import ORIENT from '../constants/orientation'; -import { AxisOrientation, ScaleOutput } from '../types'; +import { AxisOrientation, AxisScaleOutput } from '../types'; export interface TransformArgs { labelOffset: number; labelProps: Partial; orientation: AxisOrientation; - range: ScaleOutput[]; + range: AxisScaleOutput[]; tickLabelFontSize: number; tickLength: number; } diff --git a/packages/vx-scale/src/types/Scale.ts b/packages/vx-scale/src/types/Scale.ts index 410e18470..8f9bc3498 100644 --- a/packages/vx-scale/src/types/Scale.ts +++ b/packages/vx-scale/src/types/Scale.ts @@ -63,6 +63,18 @@ export type D3Scale< ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput > = ValueOf>; +/** + * A catch-all type for all D3 scales. + * + * Use this instead of `D3Scale` + * unless other generic types (`Output`, `DiscreteInput` and `ThresholdInput`) + * are also included and passed to `D3Scale`. + * Otherwise it may not match some scales (band, point, threshold) correctly and cause TS errors. + * + * Example error messages: + * * "Type 'StringLike' is not assignable to type 'string'" + * * "Type 'number' is not assignable to type 'DefaultThresholdInput'" + */ // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyD3Scale = D3Scale; @@ -77,9 +89,3 @@ export type InferD3ScaleDiscreteInput = export type InferD3ScaleThresholdInput = // eslint-disable-next-line @typescript-eslint/no-explicit-any Scale extends D3Scale ? X : DefaultThresholdInput; - -export type UnpackD3Scale = D3Scale< - InferD3ScaleOutput, - InferD3ScaleDiscreteInput, - InferD3ScaleThresholdInput ->; From d679ea394d9d95ea27350595b28c1f5e1eebe653 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 03:32:05 -0700 Subject: [PATCH 11/23] test: fix unit tests --- packages/vx-axis/src/axis/Axis.tsx | 47 +++++++++++----------- packages/vx-axis/src/axis/AxisRenderer.tsx | 2 +- packages/vx-axis/test/Axis.test.tsx | 2 +- packages/vx-axis/test/scales.test.tsx | 24 +++++------ 4 files changed, 37 insertions(+), 38 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index d7c075865..85570db00 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -3,7 +3,7 @@ import cx from 'classnames'; import { Point } from '@vx/point'; import { Group } from '@vx/group'; import ORIENT from '../constants/orientation'; -import { SharedAxisProps, AxisOrientation, ChildRenderProps, AxisScale } from '../types'; +import { SharedAxisProps, AxisOrientation, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; @@ -15,7 +15,7 @@ export type AxisProps = SharedAxisProps & { }; export default function Axis({ - children, + children = AxisRenderer, axisClassName, hideAxisLine = false, hideTicks = false, @@ -54,8 +54,9 @@ export default function Axis({ }); const ticks = (tickValues ?? getTicks(scale, numTicks)) - .filter(value => !hideZero || value !== 0 || value !== '0') - .map((value, index) => { + .map((value, index) => ({ value, index })) + .filter(({ value }) => !hideZero || (value !== 0 && value !== '0')) + .map(({ value, index }) => { const scaledValue = toNumberOrUndefined(tickPosition(value)); const from = new Point({ x: horizontal ? scaledValue : 0, @@ -74,28 +75,26 @@ export default function Axis({ }; }); - const childProps: ChildRenderProps = { - ...restProps, - axisFromPoint, - axisToPoint, - hideAxisLine, - hideTicks, - hideZero, - horizontal, - numTicks, - orientation, - rangePadding, - scale, - tickFormat: format, - tickLength, - tickPosition, - tickSign, - ticks, - }; - return ( - {children ? children(childProps) : } + {children({ + ...restProps, + axisFromPoint, + axisToPoint, + hideAxisLine, + hideTicks, + hideZero, + horizontal, + numTicks, + orientation, + rangePadding, + scale, + tickFormat: format, + tickLength, + tickPosition, + tickSign, + ticks, + })} ); } diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx index 84d21f478..5c731d70d 100644 --- a/packages/vx-axis/src/axis/AxisRenderer.tsx +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -44,7 +44,7 @@ export default function AxisRenderer({ return ( <> - {ticks.map(({ value, from, to, formattedValue }, index) => { + {ticks.map(({ value, index, from, to, formattedValue }) => { const tickLabelPropsObj = tickLabelProps(value, index); tickLabelFontSize = Math.max( tickLabelFontSize, diff --git a/packages/vx-axis/test/Axis.test.tsx b/packages/vx-axis/test/Axis.test.tsx index 00dbb7181..15b749519 100644 --- a/packages/vx-axis/test/Axis.test.tsx +++ b/packages/vx-axis/test/Axis.test.tsx @@ -194,7 +194,7 @@ describe('', () => { .find('.vx-axis-tick') .find(Text) .prop('children'), - ).toBe(0); + ).toBe('0'); }); test('it should use center if scale is band', () => { diff --git a/packages/vx-axis/test/scales.test.tsx b/packages/vx-axis/test/scales.test.tsx index e3f46136f..0777f54d3 100644 --- a/packages/vx-axis/test/scales.test.tsx +++ b/packages/vx-axis/test/scales.test.tsx @@ -23,7 +23,7 @@ const axisProps = { describe('Axis scales', () => { it('should render with scaleBand', () => { - expect( + expect(() => shallow( { }); it('should render with scaleLinear', () => { - expect( + expect(() => shallow( { }); it('should render with scaleLog', () => { - expect( + expect(() => shallow( { }); it('should render with scaleOrdinal', () => { - expect( + expect(() => shallow( { }); it('should render with scalePoint', () => { - expect( + expect(() => shallow( { }); it('should render with scalePower', () => { - expect( + expect(() => shallow( { }); it('should render with scaleQuantile', () => { - expect( + expect(() => shallow( { }); it('should render with scaleQuantize', () => { - expect( + expect(() => shallow( { }); it('should render with scaleSymlog', () => { - expect( + expect(() => shallow( { }); it('should render with scaleThreshold', () => { - expect( + expect(() => shallow( { }); it('should render with scaleTime', () => { - expect( + expect(() => shallow( { }); it('should render with scaleUtc', () => { - expect( + expect(() => shallow( Date: Thu, 30 Jul 2020 03:46:43 -0700 Subject: [PATCH 12/23] refactor: point creation --- packages/vx-axis/src/axis/Axis.tsx | 33 +++++++---------------- packages/vx-axis/src/utils/createPoint.ts | 5 ++++ packages/vx-axis/src/utils/getTicks.ts | 4 +++ 3 files changed, 19 insertions(+), 23 deletions(-) create mode 100644 packages/vx-axis/src/utils/createPoint.ts diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 85570db00..e84eb011d 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -1,6 +1,5 @@ import React from 'react'; import cx from 'classnames'; -import { Point } from '@vx/point'; import { Group } from '@vx/group'; import ORIENT from '../constants/orientation'; import { SharedAxisProps, AxisOrientation, AxisScale } from '../types'; @@ -9,6 +8,7 @@ import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; +import createPoint from '../utils/createPoint'; export type AxisProps = SharedAxisProps & { orientation?: AxisOrientation; @@ -33,10 +33,6 @@ export default function Axis({ }: AxisProps) { 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 === ORIENT.left; const isTop = orientation === ORIENT.top; const horizontal = isTop || orientation === ORIENT.bottom; @@ -44,33 +40,24 @@ export default function Axis({ const tickPosition = getTickPosition(scale); const tickSign = isLeft || isTop ? -1 : 1; - const axisFromPoint = new Point({ - x: horizontal ? range0 : 0, - y: horizontal ? 0 : range0, - }); - const axisToPoint = new Point({ - x: horizontal ? range1 : 0, - y: horizontal ? 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, + ); const ticks = (tickValues ?? getTicks(scale, numTicks)) .map((value, index) => ({ value, index })) .filter(({ value }) => !hideZero || (value !== 0 && value !== '0')) .map(({ value, index }) => { const scaledValue = toNumberOrUndefined(tickPosition(value)); - const from = new Point({ - x: horizontal ? scaledValue : 0, - y: horizontal ? 0 : scaledValue, - }); - const to = new Point({ - x: horizontal ? scaledValue : tickSign * tickLength, - y: horizontal ? tickLength * tickSign : scaledValue, - }); + return { value, index, - from, - to, + from: createPoint({ x: scaledValue, y: 0 }, horizontal), + to: createPoint({ x: scaledValue, y: tickLength * tickSign }, horizontal), formattedValue: format(value, index), }; }); diff --git a/packages/vx-axis/src/utils/createPoint.ts b/packages/vx-axis/src/utils/createPoint.ts new file mode 100644 index 000000000..86aac91ac --- /dev/null +++ b/packages/vx-axis/src/utils/createPoint.ts @@ -0,0 +1,5 @@ +import { Point } from '@vx/point'; + +export default function createPoint({ x, y }: Partial, horizontal: boolean) { + return horizontal ? new Point({ x, y }) : new Point({ y, x }); +} diff --git a/packages/vx-axis/src/utils/getTicks.ts b/packages/vx-axis/src/utils/getTicks.ts index 88247361c..7465995db 100644 --- a/packages/vx-axis/src/utils/getTicks.ts +++ b/packages/vx-axis/src/utils/getTicks.ts @@ -5,6 +5,10 @@ export default function getTicks( scale: Scale, numTicks?: number, ): TickValue[] { + // Because `Scale` is generic type which maybe a subset of AnyD3Scale + // that may not have `ticks` field, + // TypeScript will not let us do the `'ticks' in scale` check directly. + // Have to manually cast and expand type first. const s = scale as AnyD3Scale; if ('ticks' in s) { From 451e9c01e8296c64e6f394ed5a681216034f97d7 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 03:55:08 -0700 Subject: [PATCH 13/23] fix: revert inferscaletype --- packages/vx-scale/src/utils/inferScaleType.ts | 34 ++++++++++--------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/packages/vx-scale/src/utils/inferScaleType.ts b/packages/vx-scale/src/utils/inferScaleType.ts index 994324820..146e0aa16 100644 --- a/packages/vx-scale/src/utils/inferScaleType.ts +++ b/packages/vx-scale/src/utils/inferScaleType.ts @@ -1,51 +1,53 @@ import { ScaleTime } from 'd3-scale'; -import { D3Scale, AnyD3Scale, InferD3ScaleOutput } from '../types/Scale'; +import { StringLike } from '../types/Base'; +import { DefaultThresholdInput, D3Scale } from '../types/Scale'; import { ScaleType } from '../types/ScaleConfig'; import isUtcScale from './isUtcScale'; -export default function inferScaleType(scale: Scale): ScaleType { - const s = scale as D3Scale; - +export default function inferScaleType< + Output, + DiscreteInput extends StringLike, + ThresholdInput extends DefaultThresholdInput +>(scale: D3Scale): ScaleType { // Try a sequence of typeguards to figure out the scale type - if ('paddingInner' in s) { + if ('paddingInner' in scale) { return 'band'; } - if ('padding' in s) { + if ('padding' in scale) { return 'point'; } - if ('quantiles' in s) { + if ('quantiles' in scale) { return 'quantile'; } - if ('base' in s) { + if ('base' in scale) { return 'log'; } - if ('exponent' in s) { - return s.exponent() === 0.5 ? 'sqrt' : 'pow'; + if ('exponent' in scale) { + return scale.exponent() === 0.5 ? 'sqrt' : 'pow'; } - if ('constant' in s) { + if ('constant' in scale) { return 'symlog'; } - if ('clamp' in s) { + if ('clamp' in scale) { // Linear, Time or Utc scales - if (s.ticks()[0] instanceof Date) { - type Output = InferD3ScaleOutput; + if (scale.ticks()[0] instanceof Date) { return isUtcScale(scale as ScaleTime) ? 'utc' : 'time'; } return 'linear'; } - if ('nice' in s) { + if ('nice' in scale) { return 'quantize'; } - if ('invertExtent' in s) { + if ('invertExtent' in scale) { return 'threshold'; } From 39bf2ff0bf9b83092da91770c2457f1cb8912d62 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 03:57:57 -0700 Subject: [PATCH 14/23] fix: create point --- packages/vx-axis/src/utils/createPoint.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/vx-axis/src/utils/createPoint.ts b/packages/vx-axis/src/utils/createPoint.ts index 86aac91ac..a54b1ed74 100644 --- a/packages/vx-axis/src/utils/createPoint.ts +++ b/packages/vx-axis/src/utils/createPoint.ts @@ -1,5 +1,5 @@ import { Point } from '@vx/point'; export default function createPoint({ x, y }: Partial, horizontal: boolean) { - return horizontal ? new Point({ x, y }) : new Point({ y, x }); + return new Point(horizontal ? { x, y } : { y, x }); } From 4b1cb11fad99b5917aecc4fa6e70d6b54d5488ad Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 04:18:30 -0700 Subject: [PATCH 15/23] fix: demo --- .../vx-demo/src/sandboxes/vx-barstack-horizontal/Example.tsx | 4 ++-- packages/vx-demo/src/sandboxes/vx-barstack/Example.tsx | 2 +- packages/vx-demo/src/sandboxes/vx-brush/AreaChart.tsx | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/vx-demo/src/sandboxes/vx-barstack-horizontal/Example.tsx b/packages/vx-demo/src/sandboxes/vx-barstack-horizontal/Example.tsx index 5069af94a..46979e67b 100644 --- a/packages/vx-demo/src/sandboxes/vx-barstack-horizontal/Example.tsx +++ b/packages/vx-demo/src/sandboxes/vx-barstack-horizontal/Example.tsx @@ -144,7 +144,7 @@ export default withTooltip( ) } - + ( dy: '0.33em', })} /> - + - + {!hideBottomAxis && ( - + 520 ? 10 : 5} @@ -86,7 +86,7 @@ export default function AreaChart({ /> )} {!hideLeftAxis && ( - + Date: Thu, 30 Jul 2020 10:07:30 -0700 Subject: [PATCH 16/23] refactor: add ScaleInput type --- packages/vx-axis/src/index.ts | 2 + packages/vx-axis/src/types.ts | 37 +++++++++---------- .../vx-axis/src/utils/getTickFormatter.ts | 7 ++-- packages/vx-axis/src/utils/getTickPosition.ts | 7 ++-- packages/vx-axis/src/utils/getTicks.ts | 5 +-- packages/vx-scale/src/types/Scale.ts | 3 ++ 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/packages/vx-axis/src/index.ts b/packages/vx-axis/src/index.ts index 837ff5d15..0e45a4361 100644 --- a/packages/vx-axis/src/index.ts +++ b/packages/vx-axis/src/index.ts @@ -4,3 +4,5 @@ export { default as AxisRight } from './axis/AxisRight'; export { default as AxisTop } from './axis/AxisTop'; export { default as AxisBottom } from './axis/AxisBottom'; export { default as Orientation } from './constants/orientation'; + +export * from './types'; diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index 69afa33c0..c9b8a40ef 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -1,4 +1,4 @@ -import { D3Scale, NumberLike, AnyD3Scale } from '@vx/scale'; +import { D3Scale, NumberLike, ScaleInput } from '@vx/scale'; import { TextProps } from '@vx/text/lib/Text'; // In order to plot values on an axis, output of the scale must be number. @@ -12,12 +12,9 @@ export type AxisScale = export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; -export type FormattedValue = string | undefined; +type FormattedValue = string | undefined; -/** Get type of tick value (scale input) from D3 scale */ -export type TickValue = Parameters[0]; - -export type TickFormatter = (value: T, tickIndex: number) => FormattedValue; +export type TickFormatter = (value: T, index: number) => FormattedValue; export type TickLabelProps = (value: T, index: number) => Partial; @@ -27,12 +24,7 @@ export type TickRendererProps = Partial & { formattedValue: FormattedValue; }; -export interface Point { - x: number; - y: number; -} - -interface CommonProps { +interface CommonProps { /** The class name applied to the axis line element. */ axisLineClassName?: string; /** If true, will hide the axis line. */ @@ -66,9 +58,9 @@ interface CommonProps { /** Override the component used to render tick labels (instead of from @vx/text) */ tickComponent?: (tickRendererProps: TickRendererProps) => 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; + tickLabelProps?: TickLabelProps>; /** The length of the tick lines. */ tickLength: number; /** The color for the tick's stroke value. */ @@ -77,7 +69,12 @@ interface CommonProps { tickTransform?: string; } -export type ChildRenderProps = CommonProps> & { +interface Point { + x: number; + y: number; +} + +export type AxisRendererProps = CommonProps & { /** Start point of the axis line */ axisFromPoint: Point; /** End point of the axis line */ @@ -87,12 +84,12 @@ export type ChildRenderProps = CommonProps) => AxisScaleOutput; + tickPosition: (value: ScaleInput) => AxisScaleOutput; /** Axis coordinate sign, -1 for left or top orientation. */ tickSign: 1 | -1; /** Computed ticks with positions and formatted value */ ticks: { - value: TickValue; + value: ScaleInput; index: number; from: Point; to: Point; @@ -100,7 +97,7 @@ export type ChildRenderProps = CommonProps = Partial>> & { +export type SharedAxisProps = Partial> & { /** The class name applied to the outermost axis group element. */ axisClassName?: string; /** A left pixel offset applied to the entire axis. */ @@ -108,9 +105,9 @@ export type SharedAxisProps = Partial[]; + tickValues?: ScaleInput[]; /** A top pixel offset applied to the entire axis. */ top?: number; /** For more control over rendering or to add event handlers to datum, pass a function as children. */ - children?: (renderProps: ChildRenderProps) => React.ReactNode; + children?: (renderProps: AxisRendererProps) => React.ReactNode; }; diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts index 3e726e37b..acc7a5c70 100644 --- a/packages/vx-axis/src/utils/getTickFormatter.ts +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -1,4 +1,5 @@ -import { TickFormatter, AxisScale, TickValue } from '../types'; +import { ScaleInput } from '@vx/scale'; +import { TickFormatter, AxisScale } from '../types'; /** * Returns a tick position for the given tick value @@ -10,8 +11,8 @@ export default function getTickFormatter(scale: Scale) // For point or band scales, // have to add offset to make the tick centered. if ('tickFormat' in s) { - return s.tickFormat() as TickFormatter>; + return s.tickFormat() as TickFormatter>; } - return toString as TickFormatter>; + return toString as TickFormatter>; } diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts index 19293e486..cff2e6fe2 100644 --- a/packages/vx-axis/src/utils/getTickPosition.ts +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -1,4 +1,5 @@ -import { AxisScale, AxisScaleOutput, TickValue } from '../types'; +import { ScaleInput } from '@vx/scale'; +import { AxisScale, AxisScaleOutput } from '../types'; /** * Create a function that returns a tick position for the given tick value @@ -12,12 +13,12 @@ export default function getTickPosition(scale: Scale) { if ('bandwidth' in s) { let offset = s.bandwidth() / 2; if (s.round()) offset = Math.round(offset); - return (d: TickValue) => { + return (d: ScaleInput) => { const scaledValue = s(d); return typeof scaledValue === 'number' ? scaledValue + offset : scaledValue; }; } - return scale as (d: TickValue) => AxisScaleOutput; + return scale as (d: ScaleInput) => AxisScaleOutput; } diff --git a/packages/vx-axis/src/utils/getTicks.ts b/packages/vx-axis/src/utils/getTicks.ts index 7465995db..3b451dbac 100644 --- a/packages/vx-axis/src/utils/getTicks.ts +++ b/packages/vx-axis/src/utils/getTicks.ts @@ -1,10 +1,9 @@ -import { AnyD3Scale } from '@vx/scale'; -import { TickValue } from '../types'; +import { AnyD3Scale, ScaleInput } from '@vx/scale'; export default function getTicks( scale: Scale, numTicks?: number, -): TickValue[] { +): ScaleInput[] { // Because `Scale` is generic type which maybe a subset of AnyD3Scale // that may not have `ticks` field, // TypeScript will not let us do the `'ticks' in scale` check directly. diff --git a/packages/vx-scale/src/types/Scale.ts b/packages/vx-scale/src/types/Scale.ts index 8f9bc3498..1d5632eef 100644 --- a/packages/vx-scale/src/types/Scale.ts +++ b/packages/vx-scale/src/types/Scale.ts @@ -89,3 +89,6 @@ export type InferD3ScaleDiscreteInput = export type InferD3ScaleThresholdInput = // eslint-disable-next-line @typescript-eslint/no-explicit-any Scale extends D3Scale ? X : DefaultThresholdInput; + +/** Get type of scale input from D3 scale */ +export type ScaleInput = Parameters[0]; From 11e27a24b8356a52b5439d65d09fc17c99bf7a2c Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 10:13:01 -0700 Subject: [PATCH 17/23] refactor: orientation --- packages/vx-axis/src/axis/Axis.tsx | 14 +++++++------- packages/vx-axis/src/axis/AxisBottom.tsx | 4 ++-- packages/vx-axis/src/axis/AxisLeft.tsx | 4 ++-- packages/vx-axis/src/axis/AxisRenderer.tsx | 8 ++++---- packages/vx-axis/src/axis/AxisRight.tsx | 4 ++-- packages/vx-axis/src/axis/AxisTop.tsx | 4 ++-- packages/vx-axis/src/constants/orientation.ts | 10 ++++++---- packages/vx-axis/src/types.ts | 5 ++--- packages/vx-axis/src/utils/labelTransform.ts | 12 ++++++------ 9 files changed, 33 insertions(+), 32 deletions(-) diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index e84eb011d..8a69bb316 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -1,17 +1,17 @@ import React from 'react'; import cx from 'classnames'; import { Group } from '@vx/group'; -import ORIENT from '../constants/orientation'; -import { SharedAxisProps, AxisOrientation, AxisScale } from '../types'; +import { SharedAxisProps, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; import toNumberOrUndefined from '../utils/toNumberOrUndefined'; import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; import createPoint from '../utils/createPoint'; +import Orientation from '../constants/orientation'; export type AxisProps = SharedAxisProps & { - orientation?: AxisOrientation; + orientation?: Orientation; }; export default function Axis({ @@ -22,7 +22,7 @@ export default function Axis({ hideZero = false, left = 0, numTicks = 10, - orientation = ORIENT.bottom, + orientation = Orientation.bottom, rangePadding = 0, scale, tickFormat, @@ -33,9 +33,9 @@ export default function Axis({ }: AxisProps) { const format = tickFormat ?? getTickFormatter(scale); - const isLeft = orientation === ORIENT.left; - const isTop = orientation === ORIENT.top; - const horizontal = isTop || orientation === ORIENT.bottom; + const isLeft = orientation === Orientation.left; + const isTop = orientation === Orientation.top; + const horizontal = isTop || orientation === Orientation.bottom; const tickPosition = getTickPosition(scale); const tickSign = isLeft || isTop ? -1 : 1; diff --git a/packages/vx-axis/src/axis/AxisBottom.tsx b/packages/vx-axis/src/axis/AxisBottom.tsx index 1761bb5d9..9b3269034 100644 --- a/packages/vx-axis/src/axis/AxisBottom.tsx +++ b/packages/vx-axis/src/axis/AxisBottom.tsx @@ -1,7 +1,7 @@ import React from 'react'; import cx from 'classnames'; import Axis from './Axis'; -import ORIENT from '../constants/orientation'; +import Orientation from '../constants/orientation'; import { SharedAxisProps, AxisScale } from '../types'; export default function AxisBottom({ @@ -21,7 +21,7 @@ export default function AxisBottom({ ({ @@ -22,7 +22,7 @@ export default function AxisLeft({ = { textAnchor: 'middle', @@ -39,7 +39,7 @@ export default function AxisRenderer({ tickStroke = '#222', tickTransform, ticks, -}: ChildRenderProps) { +}: AxisRendererProps) { let tickLabelFontSize = 10; // track the max tick label size to compute label offset return ( @@ -52,7 +52,7 @@ export default function AxisRenderer({ ); const tickYCoord = - to.y + (horizontal && orientation !== ORIENT.top ? tickLabelFontSize : 0); + to.y + (horizontal && orientation !== Orientation.top ? tickLabelFontSize : 0); return ( = SharedAxisProps; @@ -24,7 +24,7 @@ export default function AxisRight({ = SharedAxisProps; @@ -23,7 +23,7 @@ export default function AxisTop({ = { +const Orientation = { top: 'top', left: 'left', right: 'right', bottom: 'bottom', -}; +} as const; -export default orientation; +type Orientation = ValueOf; + +export default Orientation; diff --git a/packages/vx-axis/src/types.ts b/packages/vx-axis/src/types.ts index c9b8a40ef..c9ed57652 100644 --- a/packages/vx-axis/src/types.ts +++ b/packages/vx-axis/src/types.ts @@ -1,5 +1,6 @@ import { D3Scale, NumberLike, ScaleInput } from '@vx/scale'; import { TextProps } from '@vx/text/lib/Text'; +import Orientation from './constants/orientation'; // In order to plot values on an axis, output of the scale must be number. // Some scales return undefined. @@ -10,8 +11,6 @@ export type AxisScale = // eslint-disable-next-line @typescript-eslint/no-explicit-any D3Scale; -export type AxisOrientation = 'top' | 'right' | 'bottom' | 'left'; - type FormattedValue = string | undefined; export type TickFormatter = (value: T, index: number) => FormattedValue; @@ -44,7 +43,7 @@ interface CommonProps { /** The number of ticks wanted for the axis (note this is approximate) */ numTicks: number; /** Placement of the axis */ - orientation: AxisOrientation; + orientation: Orientation; /** Pixel padding to apply to both sides of the axis. */ rangePadding: number; /** The color for the stroke of the lines. */ diff --git a/packages/vx-axis/src/utils/labelTransform.ts b/packages/vx-axis/src/utils/labelTransform.ts index 9b5048ffb..f1d1c2765 100644 --- a/packages/vx-axis/src/utils/labelTransform.ts +++ b/packages/vx-axis/src/utils/labelTransform.ts @@ -1,11 +1,11 @@ import { TextProps } from '@vx/text/lib/Text'; -import ORIENT from '../constants/orientation'; -import { AxisOrientation, AxisScaleOutput } from '../types'; +import Orientation from '../constants/orientation'; +import { AxisScaleOutput } from '../types'; export interface TransformArgs { labelOffset: number; labelProps: Partial; - orientation: AxisOrientation; + orientation: Orientation; range: AxisScaleOutput[]; tickLabelFontSize: number; tickLength: number; @@ -19,15 +19,15 @@ export default function labelTransform({ tickLabelFontSize, tickLength, }: TransformArgs) { - const sign = orientation === ORIENT.left || orientation === ORIENT.top ? -1 : 1; + const sign = orientation === Orientation.left || orientation === Orientation.top ? -1 : 1; let x; let y; let transform; - if (orientation === ORIENT.top || orientation === ORIENT.bottom) { + if (orientation === Orientation.top || orientation === Orientation.bottom) { const yBottomOffset = - orientation === ORIENT.bottom && typeof labelProps.fontSize === 'number' + orientation === Orientation.bottom && typeof labelProps.fontSize === 'number' ? labelProps.fontSize : 0; From 82422d78cafcb37d7eece69cdfd75fccda188d0e Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 10:43:39 -0700 Subject: [PATCH 18/23] fix: examples --- .../vx-demo/src/sandboxes/vx-axis/Example.tsx | 35 ++++++++----------- .../src/sandboxes/vx-brush/AreaChart.tsx | 7 ++-- 2 files changed, 18 insertions(+), 24 deletions(-) diff --git a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx index 50a7e77f1..452fb156d 100644 --- a/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx +++ b/packages/vx-demo/src/sandboxes/vx-axis/Example.tsx @@ -2,8 +2,8 @@ import React 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 } from '@vx/scale'; -import { AxisBottom } from '@vx/axis'; +import { scaleUtc, scaleLinear, scaleLog, scaleBand, ScaleInput } from '@vx/scale'; +import { AxisBottom, SharedAxisProps, AxisScale } from '@vx/axis'; import { LinearGradient } from '@vx/gradient'; import { timeFormat } from 'd3-time-format'; @@ -25,10 +25,6 @@ export type AxisProps = { height: number; }; -type Scale = any; - -type ScaleInput = any; - export default function Example({ width: outerWidth = 800, height: outerHeight = 800 }: AxisProps) { // in svg, margin is subtracted from total width/height const width = outerWidth - margin.left - margin.right; @@ -36,19 +32,18 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = if (width < 10) return null; - const scales: { - scale: Scale; - values: ScaleInput[]; - label: string; - tickFormat: (value: ScaleInput, idx: number) => string | number; - }[] = [ + interface AxisDemoProps extends SharedAxisProps { + values: ScaleInput[]; + } + + const axes: AxisDemoProps>[] = [ { scale: scaleLinear({ domain: [0, 10], range: [0, width], }), values: [0, 2, 4, 6, 8, 10], - tickFormat: (v: number) => (v === 10 ? 'last' : (v === 0 && 'first') || v), + tickFormat: (v: number) => (v === 10 ? 'last' : (v === 0 && 'first') || `${v}`), label: 'linear', }, { @@ -59,7 +54,7 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = paddingInner: 1, }), values: ['a', 'b', 'c', 'd'], - tickFormat: (v: number) => v, + tickFormat: (v: string) => v, label: 'categories', }, { @@ -78,13 +73,13 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = range: [0, width], }), values: [1, 10, 100, 1000, 10000], - tickFormat: (v: number) => (`${v}`[0] === '1' ? v : ''), + tickFormat: (v: number) => (`${v}`[0] === '1' ? `${v}` : ''), label: 'log', }, ]; const scalePadding = 50; - const scaleHeight = height / scales.length - scalePadding; + const scaleHeight = height / axes.length - scalePadding; const yScale = scaleLinear({ domain: [100, 0], @@ -108,11 +103,11 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = rx={14} /> - {scales.map(({ scale, values, label, tickFormat }, i) => ( + {axes.map(({ scale, values, label, tickFormat }, i) => ( [ - scale(x) + + (scale(x) ?? 0) + ('bandwidth' in scale && typeof scale!.bandwidth !== 'undefined' ? scale.bandwidth!() / 2 : 0), @@ -123,7 +118,7 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = fill={gridColor} fillOpacity={0.2} /> - + xScale={scale} yScale={yScale} stroke={gridColor} @@ -132,7 +127,7 @@ export default function Example({ width: outerWidth = 800, height: outerHeight = numTicksRows={2} numTicksColumns={numTickColumns} /> - + ; + yScale: AxisScale; width: number; yMax: number; margin: { top: number; right: number; bottom: number; left: number }; From 234cd614c4d87df9da19c1e14cc48affc1a3fd6f Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 11:04:40 -0700 Subject: [PATCH 19/23] refactor: move util files --- packages/vx-axis/src/axis/Axis.tsx | 5 ++--- packages/vx-axis/src/utils/getTickFormatter.ts | 2 +- packages/vx-axis/src/utils/toNumberOrUndefined.ts | 6 ------ packages/vx-axis/src/utils/toString.ts | 3 --- packages/vx-scale/src/index.ts | 4 ++++ packages/vx-scale/src/utils/coerceNumber.ts | 10 ++++++++++ packages/{vx-axis => vx-scale}/src/utils/getTicks.ts | 2 +- packages/vx-scale/src/utils/toString.ts | 5 +++++ 8 files changed, 23 insertions(+), 14 deletions(-) delete mode 100644 packages/vx-axis/src/utils/toNumberOrUndefined.ts delete mode 100644 packages/vx-axis/src/utils/toString.ts create mode 100644 packages/vx-scale/src/utils/coerceNumber.ts rename packages/{vx-axis => vx-scale}/src/utils/getTicks.ts (91%) create mode 100644 packages/vx-scale/src/utils/toString.ts diff --git a/packages/vx-axis/src/axis/Axis.tsx b/packages/vx-axis/src/axis/Axis.tsx index 8a69bb316..3571b8093 100644 --- a/packages/vx-axis/src/axis/Axis.tsx +++ b/packages/vx-axis/src/axis/Axis.tsx @@ -1,11 +1,10 @@ import React from 'react'; import cx from 'classnames'; import { Group } from '@vx/group'; +import { getTicks, coerceNumber } from '@vx/scale'; import { SharedAxisProps, AxisScale } from '../types'; import AxisRenderer from './AxisRenderer'; import getTickPosition from '../utils/getTickPosition'; -import toNumberOrUndefined from '../utils/toNumberOrUndefined'; -import getTicks from '../utils/getTicks'; import getTickFormatter from '../utils/getTickFormatter'; import createPoint from '../utils/createPoint'; import Orientation from '../constants/orientation'; @@ -51,7 +50,7 @@ export default function Axis({ .map((value, index) => ({ value, index })) .filter(({ value }) => !hideZero || (value !== 0 && value !== '0')) .map(({ value, index }) => { - const scaledValue = toNumberOrUndefined(tickPosition(value)); + const scaledValue = coerceNumber(tickPosition(value)); return { value, diff --git a/packages/vx-axis/src/utils/getTickFormatter.ts b/packages/vx-axis/src/utils/getTickFormatter.ts index acc7a5c70..9e4687397 100644 --- a/packages/vx-axis/src/utils/getTickFormatter.ts +++ b/packages/vx-axis/src/utils/getTickFormatter.ts @@ -1,4 +1,4 @@ -import { ScaleInput } from '@vx/scale'; +import { ScaleInput, toString } from '@vx/scale'; import { TickFormatter, AxisScale } from '../types'; /** diff --git a/packages/vx-axis/src/utils/toNumberOrUndefined.ts b/packages/vx-axis/src/utils/toNumberOrUndefined.ts deleted file mode 100644 index 46beaafe0..000000000 --- a/packages/vx-axis/src/utils/toNumberOrUndefined.ts +++ /dev/null @@ -1,6 +0,0 @@ -export default function toNumberOrUndefined( - val?: number | { valueOf(): number }, -): number | undefined { - if (typeof val === 'undefined') return val; - return Number(val); -} diff --git a/packages/vx-axis/src/utils/toString.ts b/packages/vx-axis/src/utils/toString.ts deleted file mode 100644 index aab868cc9..000000000 --- a/packages/vx-axis/src/utils/toString.ts +++ /dev/null @@ -1,3 +0,0 @@ -export default function toString(x?: T) { - return x && x.toString(); -} diff --git a/packages/vx-scale/src/index.ts b/packages/vx-scale/src/index.ts index aa0a70da5..2280d37b0 100644 --- a/packages/vx-scale/src/index.ts +++ b/packages/vx-scale/src/index.ts @@ -16,6 +16,10 @@ export { default as createScale } from './createScale'; export { default as updateScale } from './updateScale'; export { default as inferScaleType } from './utils/inferScaleType'; +export { default as coerceNumber } from './utils/coerceNumber'; +export { default as getTicks } from './utils/getTicks'; +export { default as toString } from './utils/toString'; + // export types export * from './types/Base'; export * from './types/Nice'; diff --git a/packages/vx-scale/src/utils/coerceNumber.ts b/packages/vx-scale/src/utils/coerceNumber.ts new file mode 100644 index 000000000..74e3c3362 --- /dev/null +++ b/packages/vx-scale/src/utils/coerceNumber.ts @@ -0,0 +1,10 @@ +import { NumberLike } from '../types/Base'; + +export default function coerceNumber(val: T | NumberLike): T | number { + if ('valueOf' in val) { + const num = val.valueOf(); + if (typeof num === 'number') return num; + } + + return val as T; +} diff --git a/packages/vx-axis/src/utils/getTicks.ts b/packages/vx-scale/src/utils/getTicks.ts similarity index 91% rename from packages/vx-axis/src/utils/getTicks.ts rename to packages/vx-scale/src/utils/getTicks.ts index 3b451dbac..96699030b 100644 --- a/packages/vx-axis/src/utils/getTicks.ts +++ b/packages/vx-scale/src/utils/getTicks.ts @@ -1,4 +1,4 @@ -import { AnyD3Scale, ScaleInput } from '@vx/scale'; +import { AnyD3Scale, ScaleInput } from '../types/Scale'; export default function getTicks( scale: Scale, diff --git a/packages/vx-scale/src/utils/toString.ts b/packages/vx-scale/src/utils/toString.ts new file mode 100644 index 000000000..e07e8f06e --- /dev/null +++ b/packages/vx-scale/src/utils/toString.ts @@ -0,0 +1,5 @@ +import { StringLike } from '../types/Base'; + +export default function toString(x?: T) { + return x?.toString(); +} From 6817e407384b6464a7373016b8d1940578cffcff Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 12:05:07 -0700 Subject: [PATCH 20/23] fix: demo --- packages/vx-axis/src/utils/createPoint.ts | 2 +- packages/vx-scale/src/utils/coerceNumber.ts | 2 +- .../vx-scale/test/utils/coerceNumber.test.ts | 16 ++++++++++++++++ packages/vx-scale/test/utils/getTicks.test.ts | 8 ++++++++ packages/vx-scale/test/utils/toString.test.ts | 14 ++++++++++++++ 5 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 packages/vx-scale/test/utils/coerceNumber.test.ts create mode 100644 packages/vx-scale/test/utils/getTicks.test.ts create mode 100644 packages/vx-scale/test/utils/toString.test.ts diff --git a/packages/vx-axis/src/utils/createPoint.ts b/packages/vx-axis/src/utils/createPoint.ts index a54b1ed74..4bb963b6e 100644 --- a/packages/vx-axis/src/utils/createPoint.ts +++ b/packages/vx-axis/src/utils/createPoint.ts @@ -1,5 +1,5 @@ import { Point } from '@vx/point'; export default function createPoint({ x, y }: Partial, horizontal: boolean) { - return new Point(horizontal ? { x, y } : { y, x }); + return new Point(horizontal ? { x, y } : { x: y, y: x }); } diff --git a/packages/vx-scale/src/utils/coerceNumber.ts b/packages/vx-scale/src/utils/coerceNumber.ts index 74e3c3362..a9387aabc 100644 --- a/packages/vx-scale/src/utils/coerceNumber.ts +++ b/packages/vx-scale/src/utils/coerceNumber.ts @@ -1,7 +1,7 @@ import { NumberLike } from '../types/Base'; export default function coerceNumber(val: T | NumberLike): T | number { - if ('valueOf' in val) { + if ((typeof val === 'function' || (typeof val === 'object' && !!val)) && 'valueOf' in val) { const num = val.valueOf(); if (typeof num === 'number') return num; } diff --git a/packages/vx-scale/test/utils/coerceNumber.test.ts b/packages/vx-scale/test/utils/coerceNumber.test.ts new file mode 100644 index 000000000..08fe28a90 --- /dev/null +++ b/packages/vx-scale/test/utils/coerceNumber.test.ts @@ -0,0 +1,16 @@ +import coerceNumber from '../../src/utils/coerceNumber'; + +describe('coerceNumber(mayBeNumberLike)', () => { + it('coerces NumberLike to number', () => { + expect(coerceNumber({ valueOf: () => 1 })).toEqual(1); + expect(coerceNumber(new Date(10))).toEqual(10); + }); + it('returns the same thing if not', () => { + expect(coerceNumber('x')).toEqual('x'); + expect(coerceNumber(2)).toEqual(2); + expect(coerceNumber(0)).toEqual(0); + expect(coerceNumber(null)).toBeNull(); + expect(coerceNumber(undefined)).toBeUndefined(); + expect(coerceNumber({ x: 1 })).toEqual({ x: 1 }); + }); +}); diff --git a/packages/vx-scale/test/utils/getTicks.test.ts b/packages/vx-scale/test/utils/getTicks.test.ts new file mode 100644 index 000000000..5c2a7e191 --- /dev/null +++ b/packages/vx-scale/test/utils/getTicks.test.ts @@ -0,0 +1,8 @@ +import getTicks from '../../src/utils/getTicks'; +import { scaleLinear } from '../../lib'; + +describe('getTicks(scale)', () => { + it('linear', () => { + expect(getTicks(scaleLinear(), 3)).toEqual([0, 0.5, 1]); + }); +}); diff --git a/packages/vx-scale/test/utils/toString.test.ts b/packages/vx-scale/test/utils/toString.test.ts new file mode 100644 index 000000000..09e794e3b --- /dev/null +++ b/packages/vx-scale/test/utils/toString.test.ts @@ -0,0 +1,14 @@ +import toString from '../../src/utils/toString'; + +describe('toString(mayBeStringLike)', () => { + it('converts StringLike to string', () => { + expect(toString({ toString: () => 'haha' })).toEqual('haha'); + expect(toString('x')).toEqual('x'); + expect(toString(2)).toEqual('2'); + expect(toString(0)).toEqual('0'); + expect(toString({ x: 1 })).toEqual('[object Object]'); + }); + it('returns the same thing if not', () => { + expect(toString(undefined)).toBeUndefined(); + }); +}); From 73e21747e980ec0ae107cdefb463a3d791e442a4 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 12:09:10 -0700 Subject: [PATCH 21/23] test: add unit tests --- packages/vx-scale/test/utils/getTicks.test.ts | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/vx-scale/test/utils/getTicks.test.ts b/packages/vx-scale/test/utils/getTicks.test.ts index 5c2a7e191..59fb4b13e 100644 --- a/packages/vx-scale/test/utils/getTicks.test.ts +++ b/packages/vx-scale/test/utils/getTicks.test.ts @@ -1,8 +1,19 @@ import getTicks from '../../src/utils/getTicks'; -import { scaleLinear } from '../../lib'; +import { scaleLinear, scaleBand } from '../../lib'; describe('getTicks(scale)', () => { it('linear', () => { - expect(getTicks(scaleLinear(), 3)).toEqual([0, 0.5, 1]); + const scale = scaleLinear(); + expect(getTicks(scale, 3)).toEqual([0, 0.5, 1]); + expect(getTicks(scale, 2)).toEqual([0, 0.5, 1]); + expect(getTicks(scale, 1)).toEqual([0, 1]); + }); + it('band', () => { + const scale = scaleBand({ + domain: ['a', 'b', 'c', 'd'], + }); + expect(getTicks(scale, 4)).toEqual(['a', 'b', 'c', 'd']); + expect(getTicks(scale, 3)).toEqual(['a', 'b', 'c', 'd']); + expect(getTicks(scale, 2)).toEqual(['a', 'c']); }); }); From 881d6e27d603b84d8e18a71eea048c11e757970c Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Thu, 30 Jul 2020 13:07:03 -0700 Subject: [PATCH 22/23] refactor: rename labelTransform to getLabelTransform --- packages/vx-axis/src/axis/AxisRenderer.tsx | 2 +- .../src/utils/{labelTransform.ts => getLabelTransform.ts} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename packages/vx-axis/src/utils/{labelTransform.ts => getLabelTransform.ts} (96%) diff --git a/packages/vx-axis/src/axis/AxisRenderer.tsx b/packages/vx-axis/src/axis/AxisRenderer.tsx index a7852b84e..594ad238d 100644 --- a/packages/vx-axis/src/axis/AxisRenderer.tsx +++ b/packages/vx-axis/src/axis/AxisRenderer.tsx @@ -6,7 +6,7 @@ import { Text } from '@vx/text'; import { TextProps } from '@vx/text/lib/Text'; import Orientation from '../constants/orientation'; -import getLabelTransform from '../utils/labelTransform'; +import getLabelTransform from '../utils/getLabelTransform'; import { AxisRendererProps, AxisScale } from '../types'; const defaultTextProps: Partial = { diff --git a/packages/vx-axis/src/utils/labelTransform.ts b/packages/vx-axis/src/utils/getLabelTransform.ts similarity index 96% rename from packages/vx-axis/src/utils/labelTransform.ts rename to packages/vx-axis/src/utils/getLabelTransform.ts index f1d1c2765..dd0f65f1c 100644 --- a/packages/vx-axis/src/utils/labelTransform.ts +++ b/packages/vx-axis/src/utils/getLabelTransform.ts @@ -11,7 +11,7 @@ export interface TransformArgs { tickLength: number; } -export default function labelTransform({ +export default function getLabelTransform({ labelOffset, labelProps, orientation, From e62f03d17ef8f20946f0dab520c72c9f1c462608 Mon Sep 17 00:00:00 2001 From: Krist Wongsuphasawat Date: Fri, 31 Jul 2020 11:54:57 -0700 Subject: [PATCH 23/23] feat: add align to getTickPosition --- packages/vx-axis/src/utils/getTickPosition.ts | 12 ++++-- .../test/utils/getTickPosition.test.ts | 42 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 packages/vx-axis/test/utils/getTickPosition.test.ts diff --git a/packages/vx-axis/src/utils/getTickPosition.ts b/packages/vx-axis/src/utils/getTickPosition.ts index cff2e6fe2..5d2006b54 100644 --- a/packages/vx-axis/src/utils/getTickPosition.ts +++ b/packages/vx-axis/src/utils/getTickPosition.ts @@ -4,14 +4,18 @@ import { AxisScale, AxisScaleOutput } from '../types'; /** * Create a function that returns a tick position for the given tick value */ -export default function getTickPosition(scale: Scale) { +export default function getTickPosition( + scale: Scale, + align: 'start' | 'center' | 'end' = 'center', +) { // Broaden type before using 'xxx' in s as typeguard. const s = scale as AxisScale; // For point or band scales, - // have to add offset to make the tick centered. - if ('bandwidth' in s) { - let offset = s.bandwidth() / 2; + // have to add offset to make the tick at center or end. + if (align !== 'start' && 'bandwidth' in s) { + let offset = s.bandwidth(); + if (align === 'center') offset /= 2; if (s.round()) offset = Math.round(offset); return (d: ScaleInput) => { const scaledValue = s(d); diff --git a/packages/vx-axis/test/utils/getTickPosition.test.ts b/packages/vx-axis/test/utils/getTickPosition.test.ts new file mode 100644 index 000000000..92d2701bb --- /dev/null +++ b/packages/vx-axis/test/utils/getTickPosition.test.ts @@ -0,0 +1,42 @@ +import { scaleLinear, scaleBand } from '@vx/scale'; +import getTickPosition from '../../src/utils/getTickPosition'; + +describe('getTickPosition(scale, align)', () => { + describe('scales without band', () => { + it('return center position', () => { + const position = getTickPosition(scaleLinear({ domain: [0, 10], range: [0, 100] })); + expect(position(5)).toEqual(50); + }); + }); + describe('scales with band', () => { + describe('align', () => { + const scale = scaleBand({ domain: ['a', 'b', 'c'], range: [0, 100] }); + + it('default to center', () => { + expect(getTickPosition(scale)('b')).toEqual(50); + }); + it('center', () => { + expect(getTickPosition(scale, 'center')('b')).toEqual(50); + }); + it('start', () => { + expect((getTickPosition(scale, 'start')('b') as number).toFixed(2)).toEqual('33.33'); + }); + it('end', () => { + expect((getTickPosition(scale, 'end')('b') as number).toFixed(2)).toEqual('66.67'); + }); + }); + describe('with rounding', () => { + const scale = scaleBand({ domain: ['a', 'b', 'c'], range: [0, 100], round: true }); + + it('center', () => { + expect(getTickPosition(scale, 'center')('b')).toEqual(51); + }); + it('start', () => { + expect(getTickPosition(scale, 'start')('b')).toEqual(34); + }); + it('end', () => { + expect(getTickPosition(scale, 'end')('b')).toEqual(67); + }); + }); + }); +});