diff --git a/packages/axes/src/components/Grid.tsx b/packages/axes/src/components/Grid.tsx index 0c39a4293a..759230aa2e 100644 --- a/packages/axes/src/components/Grid.tsx +++ b/packages/axes/src/components/Grid.tsx @@ -1,7 +1,7 @@ import React, { useMemo, memo } from 'react' import { GridLines } from './GridLines' import { computeGridLines } from '../compute' -import { AnyScale, AxisValue } from '../types' +import { AnyScale, AxisValue, TicksSpec } from '../types' export const Grid = memo( ({ @@ -15,9 +15,9 @@ export const Grid = memo( width: number height: number xScale?: AnyScale - xValues?: number | X[] + xValues?: TicksSpec yScale?: AnyScale - yValues?: number | Y[] + yValues?: TicksSpec }) => { const xLines = useMemo(() => { if (!xScale) return false diff --git a/packages/axes/src/compute.ts b/packages/axes/src/compute.ts index e76a6443db..eedd29786c 100644 --- a/packages/axes/src/compute.ts +++ b/packages/axes/src/compute.ts @@ -8,8 +8,6 @@ import { utcMinute, timeHour, utcHour, - timeDay, - utcDay, timeWeek, utcWeek, timeSunday, @@ -30,6 +28,7 @@ import { utcMonth, timeYear, utcYear, + timeInterval, } from 'd3-time' import { timeFormat } from 'd3-time-format' import { format as d3Format } from 'd3-format' @@ -58,6 +57,20 @@ export const centerScale = (scale: ScaleWithBandwidth) => { return (d: T) => (scale(d) ?? 0) + offset } +const timeDay = timeInterval( + date => date.setHours(0, 0, 0, 0), + (date, step) => date.setDate(date.getDate() + step), + (start, end) => (end.getTime() - start.getTime()) / 864e5, + date => Math.floor(date.getTime() / 864e5) +) + +const utcDay = timeInterval( + date => date.setUTCHours(0, 0, 0, 0), + (date, step) => date.setUTCDate(date.getUTCDate() + step), + (start, end) => (end.getTime() - start.getTime()) / 864e5, + date => Math.floor(date.getTime() / 864e5) +) + const timeByType: Record = { millisecond: [timeMillisecond, utcMillisecond], second: [timeSecond, utcSecond], @@ -93,6 +106,41 @@ export const getScaleTicks = ( return spec } + if (typeof spec === 'string' && 'useUTC' in scale) { + // time interval + const matches = spec.match(timeIntervalRegexp) + + if (matches) { + const [, amount, type] = matches + // UTC is used as it's more predictible + // however local time could be used too + // let's see how it fits users' requirements + const timeType = timeByType[type][scale.useUTC ? 1 : 0] + + if (type === 'day') { + const [start, originalStop] = scale.domain() + const stop = new Date(originalStop) + + // Set range to include last day in the domain since `interval.range` function is exclusive stop + stop.setDate(stop.getDate() + 1) + + return timeType.every(Number(amount ?? 1))?.range(start, stop) ?? [] + } + + if (amount === undefined) { + return scale.ticks(timeType) + } + + const interval = timeType.every(Number(amount)) + + if (interval) { + return scale.ticks(interval) + } + } + + throw new Error(`Invalid tickValues: ${spec}`) + } + // continuous scales if ('ticks' in scale) { // default behaviour @@ -104,29 +152,6 @@ export const getScaleTicks = ( if (isInteger(spec)) { return scale.ticks(spec) } - - if (typeof spec === 'string' && 'useUTC' in scale) { - // time interval - const matches = spec.match(timeIntervalRegexp) - if (matches) { - // UTC is used as it's more predictible - // however local time could be used too - // let's see how it fits users' requirements - const timeType = timeByType[matches[2]][scale.useUTC ? 1 : 0] - - if (matches[1] === undefined) { - return scale.ticks(timeType) - } - - const interval = timeType.every(Number(matches[1])) - - if (interval) { - return scale.ticks(interval) - } - } - - throw new Error(`Invalid tickValues: ${spec}`) - } } // non linear scale default @@ -249,10 +274,7 @@ export const computeGridLines = ({ values?: TicksSpec }) => { const lineValues = isArray(_values) ? _values : undefined - const lineCount = isInteger(_values) ? _values : undefined - - const values = lineValues || getScaleTicks(scale, lineCount) - + const values = lineValues || getScaleTicks(scale, _values) const position = 'bandwidth' in scale ? centerScale(scale) : scale const lines: Line[] = diff --git a/packages/axes/src/types.ts b/packages/axes/src/types.ts index 7f73745fe8..6a25bfcf2b 100644 --- a/packages/axes/src/types.ts +++ b/packages/axes/src/types.ts @@ -34,7 +34,7 @@ export type ScaleWithBandwidth = export type AnyScale = | (ScaleLinear & { type: 'linear' }) | (ScaleOrdinal & { type: 'ordinal' }) - | (ScaleTime & { useUTC: boolean; type: 'time' }) + | (ScaleTime & { format: string; useUTC: boolean; type: 'time' }) | (ScaleSymLog & { type: 'symlog' }) | (ScaleLogarithmic & { type: 'log' }) | ScaleWithBandwidth diff --git a/packages/line/index.d.ts b/packages/line/index.d.ts index a25c1e0799..9db7d0d96a 100644 --- a/packages/line/index.d.ts +++ b/packages/line/index.d.ts @@ -20,7 +20,7 @@ import { import { OrdinalColorScaleConfig } from '@nivo/colors' import { LegendProps } from '@nivo/legends' import { Scale, ScaleFunc } from '@nivo/scales' -import { AxisProps, GridValues } from '@nivo/axes' +import { AxisProps, TicksSpec } from '@nivo/axes' import { CrosshairType } from '@nivo/tooltip' import { Line as D3Line } from 'd3-shape' @@ -164,9 +164,9 @@ declare module '@nivo/line' { axisLeft?: AxisProps | null enableGridX?: boolean - gridXValues?: GridValues + gridXValues?: TicksSpec enableGridY?: boolean - gridYValues?: GridValues + gridYValues?: TicksSpec enablePoints?: boolean pointSymbol?: (props: Readonly) => React.ReactNode diff --git a/yarn.lock b/yarn.lock index 112f49a8d6..adc45c75e9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -17231,11 +17231,6 @@ lodash.hasin@4.5.2: resolved "https://registry.yarnpkg.com/lodash.hasin/-/lodash.hasin-4.5.2.tgz#f91e352378d21ef7090b9e7687c2ca35c5b4d52a" integrity sha1-+R41I3jSHvcJC552h8LKNcW01So= -lodash.isdate@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/lodash.isdate/-/lodash.isdate-4.0.1.tgz#35a543673b9d76110de4114b32cc577048a7f366" - integrity sha1-NaVDZzuddhEN5BFLMsxXcEin82Y= - lodash.isempty@4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e"