diff --git a/packages/scales/src/compute.ts b/packages/scales/src/compute.ts index 9a49edc60..b5c995554 100644 --- a/packages/scales/src/compute.ts +++ b/packages/scales/src/compute.ts @@ -4,56 +4,93 @@ import sortBy from 'lodash/sortBy' import last from 'lodash/last' import isDate from 'lodash/isDate' import { createDateNormalizer } from './timeHelpers' -import { ScaleAxis, ScaleSpec, Series, ScaleValue, SerieAxis, OtherScaleAxis } from './types' +import { ScaleAxis, ScaleSpec, Series, ScaleValue, SerieAxis } from './types' import { computeScale } from './computeScale' -export const getOtherAxis = (axis: ScaleAxis): OtherScaleAxis => - axis === 'x' ? 'y' : 'x' +type XY = ReturnType -const a = getOtherAxis('x') -if (a === 'x') { - console.log('crap') +type StackedXY = { + [K in keyof XY]: XY[K] & { + maxStacked: number + minStacked: number + } +} + +type InputXYSeries = Record<'x' | 'y', number | string | Date | null> + +interface Data { + x: number + xStacked: number | null + y: number + yStacked: number | null + + // Allow template literal `xStacked/yStacked` to be set on line 213 + [key: string]: number | null +} + +type XYSeries = InputXYSeries & { + data: Data[] +} + +interface ComputedXYSeries extends InputXYSeries { + data: Array<{ + data: Data + position: { + x: ScaleValue | null + y: ScaleValue | null + } + }> } +type Compare = (a: T, b: T) => boolean + +export const getOtherAxis = (axis: ScaleAxis): ScaleAxis => (axis === 'x' ? 'y' : 'x') + export const compareValues = (a: string | number, b: string | number) => a === b export const compareDateValues = (a: Date, b: Date) => a.getTime() === b.getTime() -export const computeXYScalesForSeries = (_series, xScaleSpec, yScaleSpec, width, height) => { +export const computeXYScalesForSeries = ( + _series: XYSeries[], + xScaleSpec: ScaleSpec, + yScaleSpec: ScaleSpec, + width: number, + height: number +) => { const series = _series.map(serie => ({ ...serie, data: serie.data.map(d => ({ data: { ...d } })), - })) + })) as ComputedXYSeries[] - let xy = generateSeriesXY(series, xScaleSpec, yScaleSpec) - if (xScaleSpec.stacked === true) { - stackX(yScaleSpec.type, xy, series) + const xy = generateSeriesXY(series, xScaleSpec, yScaleSpec) + if ('stacked' in xScaleSpec && xScaleSpec.stacked === true) { + stackX(xy as StackedXY, series) } - if (yScaleSpec.stacked === true) { - stackY(xScaleSpec.type, xy, series) + if ('stacked' in yScaleSpec && yScaleSpec.stacked === true) { + stackY(xy as StackedXY, series) } - const xScale = computeScale({ ...xScaleSpec, axis: 'x' }, xy.x, width, 'x') - const yScale = computeScale({ ...yScaleSpec, axis: 'y' }, xy.y, height, 'y') + const xScale = computeScale(xScaleSpec, xy.x, width, 'x') + const yScale = computeScale(yScaleSpec, xy.y, height, 'y') series.forEach(serie => { serie.data.forEach(d => { d.position = { x: - xScale.stacked === true + 'stacked' in xScale && xScale.stacked === true ? d.data.xStacked === null ? null : xScale(d.data.xStacked) : d.data.x === null ? null - : xScale(d.data.x), + : xScale(d.data.x) ?? null, y: - yScale.stacked === true + 'stacked' in yScale && yScale.stacked === true ? d.data.yStacked === null ? null : yScale(d.data.yStacked) : d.data.y === null ? null - : yScale(d.data.y), + : yScale(d.data.y) ?? null, } }) }) @@ -97,8 +134,9 @@ export const generateSeriesAxis = { serie.data.forEach(d => { const value = getValue(d) - if (value !== null) { - setValue(d, parseFloat(value)) + + if (value) { + setValue(d, (parseFloat(String(value)) as unknown) as Value) } }) }) @@ -106,55 +144,61 @@ export const generateSeriesAxis = { serie.data.forEach(d => { const value = getValue(d) - setValue(d, getValue(d) === null ? null : parseTime(getValue(d))) + + if (value) { + setValue(d, (parseTime(value as Date) as unknown) as Value) + } }) }) } - let all = [] + const values: unknown[] = [] + series.forEach(serie => { serie.data.forEach(d => { - all.push(getValue(d)) + values.push(getValue(d)) }) }) - let min, max - if (scaleSpec.type === 'linear') { - all = uniq(all) - all = sortBy(all, v => v) - min = Math.min(...all) - max = Math.max(...all) - } else if (scaleSpec.type === 'time') { - all = uniqBy(all, v => v.getTime()) - all = all - .slice(0) - .sort((a, b) => b - a) - .reverse() - min = all[0] - max = last(all) - } else { - all = uniq(all) - min = all[0] - max = last(all) - } + switch (scaleSpec.type) { + case 'linear': { + const all = sortBy(uniq(values as number[]), v => v) + + return { all, min: Math.min(...all), max: Math.max(...all) } + } + case 'time': { + const all = uniqBy(values as Date[], v => v.getTime()) + .slice(0) + .sort((a, b) => b.getTime() - a.getTime()) + .reverse() + + return { all, min: all[0], max: last(all) } + } + default: { + const all = uniq(values) - return { all, min, max } + return { all, min: all[0], max: last(all) } + } + } } -export const stackAxis = (axis: ScaleAxis, otherType: ScaleAxis, xy: any, series: any) => { +export const stackAxis = (axis: ScaleAxis, xy: StackedXY, series: ComputedXYSeries[]) => { const otherAxis = getOtherAxis(axis) + const all: number[] = [] - let all = [] xy[otherAxis].all.forEach(v => { - const compare = isDate(v) ? compareDateValues : compareValues - const stack = [] + const compare = (isDate(v) ? compareDateValues : compareValues) as Compare + const stack: Array = [] + series.forEach(serie => { const datum = serie.data.find(d => compare(d.data[otherAxis], v)) let value = null let stackValue = null + if (datum !== undefined) { value = datum.data[axis] if (value !== null) { @@ -165,45 +209,21 @@ export const stackAxis = (axis: ScaleAxis, otherType: ScaleAxis, xy: any, series stackValue = head + value } } + datum.data[`${axis}Stacked`] = stackValue } + stack.push(stackValue) - all.push(stackValue) + + if (stackValue !== null) { + all.push(stackValue) + } }) }) - all = all.filter(v => v !== null) xy[axis].minStacked = Math.min(...all) xy[axis].maxStacked = Math.max(...all) } -export const stackX = (xy, otherType: ScaleAxis, series) => stackAxis('x', xy, otherType, series) -export const stackY = (xy, otherType: ScaleAxis, series) => stackAxis('y', xy, otherType, series) - -export const computeAxisSlices = (axis: ScaleAxis, data) => { - const otherAxis = getOtherAxis(axis) - - return data[otherAxis].all.map(v => { - const slice = { - id: v, - [otherAxis]: data[`${otherAxis}Scale`](v), - data: [], - } - const compare = isDate(v) ? compareDateValues : compareValues - data.series.forEach(serie => { - const datum = serie.data.find(d => compare(d.data[otherAxis], v)) - if (datum !== undefined) { - slice.data.push({ - ...datum, - serie, - }) - } - }) - slice.data.reverse() - - return slice - }) -} - -export const computeXSlices = data => computeAxisSlices('x', data) -export const computeYSlices = data => computeAxisSlices('y', data) +const stackX = (xy: StackedXY, series: ComputedXYSeries[]) => stackAxis('x', xy, series) +const stackY = (xy: StackedXY, series: ComputedXYSeries[]) => stackAxis('y', xy, series) diff --git a/packages/scales/src/computeScale.ts b/packages/scales/src/computeScale.ts index a96c44018..be57af625 100644 --- a/packages/scales/src/computeScale.ts +++ b/packages/scales/src/computeScale.ts @@ -1,20 +1,4 @@ -import { - ScaleAxis, - ScaleBand, - ScaleBandSpec, - ScalePoint, - ScalePointSpec, - ScaleSpec, - ComputedSerieAxis, - ScaleValue, - StringValue, - NumericValue, - ScaleLinear, - ScaleLinearSpec, - ScaleLogSpec, - ScaleLog, - ScaleSymLogSpec, -} from './types' +import { ScaleAxis, ScaleSpec, ComputedSerieAxis, ScaleValue } from './types' import { createLinearScale } from './linearScale' import { createPointScale } from './pointScale' import { createBandScale } from './bandScale' @@ -22,65 +6,26 @@ import { createTimeScale } from './timeScale' import { createLogScale } from './logScale' import { createSymLogScale } from './symLogScale' -// override for linear scale -export function computeScale( - spec: ScaleLinearSpec, - data: ComputedSerieAxis, - size: number, - axis: ScaleAxis -): ScaleLinear - -// override for point scale -export function computeScale( - spec: ScalePointSpec, - data: ComputedSerieAxis, - size: number, - axis: ScaleAxis -): ScalePoint - -// override for band scale -export function computeScale( - spec: ScaleBandSpec, - data: ComputedSerieAxis, - size: number, - axis: ScaleAxis -): ScaleBand - -// override for log scale -export function computeScale( - spec: ScaleLogSpec, - data: ComputedSerieAxis, - size: number, - axis: ScaleAxis -): ScaleLog - -// override for symlog scale -export function computeScale( - spec: ScaleSymLogSpec, - data: ComputedSerieAxis, - size: number, - axis: ScaleAxis -): ScaleLog - -export function computeScale( +export function computeScale( spec: ScaleSpec, - data: ComputedSerieAxis, + data: ComputedSerieAxis, size: number, axis: ScaleAxis ) { - if (spec.type === 'linear') { - return createLinearScale(spec, data, size, axis) - } else if (spec.type === 'point') { - return createPointScale(spec, data, size) - } else if (spec.type === 'band') { - return createBandScale(spec, data, size) - } else if (spec.type === 'time') { - return createTimeScale(spec, data, size) - } else if (spec.type === 'log') { - return createLogScale(spec, data, size, axis) - } else if (spec.type === 'symlog') { - return createSymLogScale(spec, data, size, axis) + switch (spec.type) { + case 'linear': + return createLinearScale(spec, data, size, axis) + case 'point': + return createPointScale(spec, data, size) + case 'band': + return createBandScale(spec, data, size) + case 'time': + return createTimeScale(spec, data, size) + case 'log': + return createLogScale(spec, data, size, axis) + case 'symlog': + return createSymLogScale(spec, data, size, axis) + default: + throw new Error('invalid scale spec') } - - throw new Error('invalid scale spec') } diff --git a/packages/scales/src/timeScale.ts b/packages/scales/src/timeScale.ts index 4fa720f11..4a14878e4 100644 --- a/packages/scales/src/timeScale.ts +++ b/packages/scales/src/timeScale.ts @@ -2,7 +2,7 @@ import { NumberValue, scaleTime, scaleUtc } from 'd3-scale' import { createDateNormalizer } from './timeHelpers' import { ComputedSerieAxis, ScaleTime, ScaleTimeSpec } from './types' -export const createTimeScale = ( +export const createTimeScale = ( { format = 'native', precision = 'millisecond', @@ -41,7 +41,7 @@ export const createTimeScale = ( if (nice === true) scale.nice() else if (typeof nice === 'object' || typeof nice === 'number') scale.nice(nice) - const typedScale = (scale as unknown) as ScaleTime + const typedScale = (scale as unknown) as ScaleTime typedScale.type = 'time' typedScale.useUTC = useUTC diff --git a/packages/scales/src/types.ts b/packages/scales/src/types.ts index abe9619cf..2a07fc128 100644 --- a/packages/scales/src/types.ts +++ b/packages/scales/src/types.ts @@ -117,8 +117,7 @@ export type ScaleTimeSpec = { nice?: boolean } -export interface ScaleTime - extends D3ScaleTime { +export interface ScaleTime extends D3ScaleTime { type: 'time' useUTC: boolean } @@ -141,7 +140,7 @@ export type SerieAxis = { export type ComputedSerieAxis = { all: Value[] min: Value - minStacked: Value + minStacked?: Value max: Value - maxStacked: Value + maxStacked?: Value }