diff --git a/packages/vx-scale/src/operators/reverse.ts b/packages/vx-scale/src/operators/reverse.ts new file mode 100644 index 000000000..28762740f --- /dev/null +++ b/packages/vx-scale/src/operators/reverse.ts @@ -0,0 +1,26 @@ +import { DefaultThresholdInput, D3Scale } from '../types/Scale'; +import { StringLike } from '../types/Base'; +import { ScaleConfigWithoutType } from '../types/ScaleConfig'; + +export default function applyReverse< + Output, + DiscreteInput extends StringLike, + ThresholdInput extends DefaultThresholdInput +>( + scale: D3Scale, + config: ScaleConfigWithoutType, +) { + if (config.reverse) { + const reversedRange = scale + .range() + .slice() + .reverse(); + if ('padding' in scale) { + // point and band scales + scale.range(reversedRange as [number, number]); + } else { + // the rest + scale.range(reversedRange as Output[]); + } + } +} diff --git a/packages/vx-scale/src/operators/scaleOperator.ts b/packages/vx-scale/src/operators/scaleOperator.ts index 96a057371..013dc17f3 100644 --- a/packages/vx-scale/src/operators/scaleOperator.ts +++ b/packages/vx-scale/src/operators/scaleOperator.ts @@ -11,6 +11,7 @@ import exponent from './exponent'; import interpolate from './interpolate'; import nice from './nice'; import padding from './padding'; +import reverse from './reverse'; import round from './round'; import unknown from './unknown'; import zero from './zero'; @@ -28,6 +29,10 @@ export const ALL_OPERATORS = [ 'interpolate', 'round', + // set range then reverse + 'range', + 'reverse', + // Order does not matter for these operators 'align', 'base', @@ -35,7 +40,6 @@ export const ALL_OPERATORS = [ 'constant', 'exponent', 'padding', - 'range', 'unknown', ] as const; @@ -55,6 +59,7 @@ const operators: Record = { exponent, padding, range, + reverse, unknown, }; diff --git a/packages/vx-scale/src/scales/band.ts b/packages/vx-scale/src/scales/band.ts index bdb0d1089..3c04da37b 100644 --- a/packages/vx-scale/src/scales/band.ts +++ b/packages/vx-scale/src/scales/band.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateBandScale = scaleOperator<'band'>( 'domain', 'range', + 'reverse', 'align', 'padding', 'round', diff --git a/packages/vx-scale/src/scales/linear.ts b/packages/vx-scale/src/scales/linear.ts index d56f690a7..343fc8606 100644 --- a/packages/vx-scale/src/scales/linear.ts +++ b/packages/vx-scale/src/scales/linear.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateLinearScale = scaleOperator<'linear'>( 'domain', 'range', + 'reverse', 'clamp', 'interpolate', 'nice', diff --git a/packages/vx-scale/src/scales/log.ts b/packages/vx-scale/src/scales/log.ts index 6e68139c7..03e2559c1 100644 --- a/packages/vx-scale/src/scales/log.ts +++ b/packages/vx-scale/src/scales/log.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateLogScale = scaleOperator<'log'>( 'domain', 'range', + 'reverse', 'base', 'clamp', 'interpolate', diff --git a/packages/vx-scale/src/scales/ordinal.ts b/packages/vx-scale/src/scales/ordinal.ts index ea91e0310..9f999ece6 100644 --- a/packages/vx-scale/src/scales/ordinal.ts +++ b/packages/vx-scale/src/scales/ordinal.ts @@ -3,7 +3,7 @@ import { DefaultOutput, StringLike } from '../types/Base'; import { PickScaleConfigWithoutType } from '../types/ScaleConfig'; import scaleOperator from '../operators/scaleOperator'; -export const updateOrdinalScale = scaleOperator<'ordinal'>('domain', 'range', 'unknown'); +export const updateOrdinalScale = scaleOperator<'ordinal'>('domain', 'range', 'reverse', 'unknown'); export default function createOrdinalScale< DiscreteInput extends StringLike = StringLike, diff --git a/packages/vx-scale/src/scales/point.ts b/packages/vx-scale/src/scales/point.ts index 9ab16a39e..58d5d84d8 100644 --- a/packages/vx-scale/src/scales/point.ts +++ b/packages/vx-scale/src/scales/point.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updatePointScale = scaleOperator<'point'>( 'domain', 'range', + 'reverse', 'align', 'padding', 'round', diff --git a/packages/vx-scale/src/scales/power.ts b/packages/vx-scale/src/scales/power.ts index fd5fb2596..f1878f6cd 100644 --- a/packages/vx-scale/src/scales/power.ts +++ b/packages/vx-scale/src/scales/power.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updatePowScale = scaleOperator<'pow'>( 'domain', 'range', + 'reverse', 'clamp', 'exponent', 'interpolate', diff --git a/packages/vx-scale/src/scales/quantile.ts b/packages/vx-scale/src/scales/quantile.ts index 27b73d3eb..87838c7fb 100644 --- a/packages/vx-scale/src/scales/quantile.ts +++ b/packages/vx-scale/src/scales/quantile.ts @@ -3,7 +3,7 @@ import { DefaultOutput } from '../types/Base'; import { PickScaleConfigWithoutType } from '../types/ScaleConfig'; import scaleOperator from '../operators/scaleOperator'; -export const updateQuantileScale = scaleOperator<'quantile'>('domain', 'range'); +export const updateQuantileScale = scaleOperator<'quantile'>('domain', 'range', 'reverse'); export default function createQuantileScale( config?: PickScaleConfigWithoutType<'quantile', Output>, diff --git a/packages/vx-scale/src/scales/quantize.ts b/packages/vx-scale/src/scales/quantize.ts index 42d21dd73..ddca2c5f3 100644 --- a/packages/vx-scale/src/scales/quantize.ts +++ b/packages/vx-scale/src/scales/quantize.ts @@ -3,7 +3,13 @@ import { DefaultOutput } from '../types/Base'; import { PickScaleConfigWithoutType } from '../types/ScaleConfig'; import scaleOperator from '../operators/scaleOperator'; -export const updateQuantizeScale = scaleOperator<'quantize'>('domain', 'range', 'nice', 'zero'); +export const updateQuantizeScale = scaleOperator<'quantize'>( + 'domain', + 'range', + 'reverse', + 'nice', + 'zero', +); export default function createQuantizeScale( config?: PickScaleConfigWithoutType<'quantize', Output>, diff --git a/packages/vx-scale/src/scales/squareRoot.ts b/packages/vx-scale/src/scales/squareRoot.ts index 187ba9be1..81a619c37 100644 --- a/packages/vx-scale/src/scales/squareRoot.ts +++ b/packages/vx-scale/src/scales/squareRoot.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateSqrtScale = scaleOperator<'sqrt'>( 'domain', 'range', + 'reverse', 'clamp', 'interpolate', 'nice', diff --git a/packages/vx-scale/src/scales/symlog.ts b/packages/vx-scale/src/scales/symlog.ts index bf47a2ff4..9b155d560 100644 --- a/packages/vx-scale/src/scales/symlog.ts +++ b/packages/vx-scale/src/scales/symlog.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateSymlogScale = scaleOperator<'symlog'>( 'domain', 'range', + 'reverse', 'clamp', 'constant', 'nice', diff --git a/packages/vx-scale/src/scales/threshold.ts b/packages/vx-scale/src/scales/threshold.ts index 8023f8d51..f6bc13256 100644 --- a/packages/vx-scale/src/scales/threshold.ts +++ b/packages/vx-scale/src/scales/threshold.ts @@ -4,7 +4,7 @@ import { PickScaleConfigWithoutType } from '../types/ScaleConfig'; import { DefaultThresholdInput } from '../types/Scale'; import scaleOperator from '../operators/scaleOperator'; -export const updateThresholdScale = scaleOperator<'threshold'>('domain', 'range'); +export const updateThresholdScale = scaleOperator<'threshold'>('domain', 'range', 'reverse'); export default function createThresholdScale< ThresholdInput extends DefaultThresholdInput = DefaultThresholdInput, diff --git a/packages/vx-scale/src/scales/time.ts b/packages/vx-scale/src/scales/time.ts index ae9937489..82c915741 100644 --- a/packages/vx-scale/src/scales/time.ts +++ b/packages/vx-scale/src/scales/time.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateTimeScale = scaleOperator<'time'>( 'domain', 'range', + 'reverse', 'clamp', 'interpolate', 'nice', diff --git a/packages/vx-scale/src/scales/utc.ts b/packages/vx-scale/src/scales/utc.ts index 846c11a19..4810e05f2 100644 --- a/packages/vx-scale/src/scales/utc.ts +++ b/packages/vx-scale/src/scales/utc.ts @@ -6,6 +6,7 @@ import scaleOperator from '../operators/scaleOperator'; export const updateUtcScale = scaleOperator<'utc'>( 'domain', 'range', + 'reverse', 'clamp', 'interpolate', 'nice', diff --git a/packages/vx-scale/src/types/ScaleConfig.ts b/packages/vx-scale/src/types/ScaleConfig.ts index ff5312e00..df992006c 100644 --- a/packages/vx-scale/src/types/ScaleConfig.ts +++ b/packages/vx-scale/src/types/ScaleConfig.ts @@ -17,7 +17,7 @@ export type ContinuousDomain = ContinuousInput[]; // and add `type` property as discriminant of union type. type CreateScaleConfig = 'type'> = Pick< BaseScaleConfig, - 'type' | 'domain' | 'range' | Fields + 'type' | 'domain' | 'range' | 'reverse' | Fields >; export type LinearScaleConfig = CreateScaleConfig< diff --git a/packages/vx-scale/test/scaleLinear.test.ts b/packages/vx-scale/test/scaleLinear.test.ts index 39cb53f2c..a044c5774 100644 --- a/packages/vx-scale/test/scaleLinear.test.ts +++ b/packages/vx-scale/test/scaleLinear.test.ts @@ -13,6 +13,10 @@ describe('scaleLinear()', () => { const range = [1, 2]; expect(scaleLinear({ range: [1, 2] }).range()).toEqual(range); }); + it('set reverse', () => { + expect(scaleLinear({ reverse: true }).range()).toEqual([1, 0]); + expect(scaleLinear({ range: [1, 2], reverse: true }).range()).toEqual([2, 1]); + }); describe('set clamp', () => { it('true', () => { const scale = scaleLinear({ clamp: true }); diff --git a/packages/vx-scale/test/scalePoint.test.ts b/packages/vx-scale/test/scalePoint.test.ts index 3a239a442..a73f9401a 100644 --- a/packages/vx-scale/test/scalePoint.test.ts +++ b/packages/vx-scale/test/scalePoint.test.ts @@ -13,6 +13,10 @@ describe('scalePoint', () => { const scale = scalePoint({ range: [2, 3] }); expect(scale.range()).toEqual([2, 3]); }); + it('set reverse', () => { + expect(scalePoint({ reverse: true }).range()).toEqual([1, 0]); + expect(scalePoint({ range: [1, 2], reverse: true }).range()).toEqual([2, 1]); + }); it('set align', () => { expect(scalePoint({ align: 0.5 }).align()).toEqual(0.5); });