@@ -11,6 +11,18 @@ import {
1111 ScaleBand ,
1212} from 'd3-scale'
1313
14+ import {
15+ timeSecond ,
16+ timeMinute ,
17+ timeHour ,
18+ timeDay ,
19+ timeMonth ,
20+ timeWeek ,
21+ timeYear ,
22+ } from 'd3-time'
23+
24+ import { timeFormat } from 'd3-time-format'
25+
1426import {
1527 Axis ,
1628 AxisBand ,
@@ -73,16 +85,16 @@ export default function buildAxisLinear<TDatum>(
7385 // Give the scale a home
7486 return options . scaleType === 'time' || options . scaleType === 'localTime'
7587 ? buildTimeAxis (
76- isPrimary ,
77- options ,
78- series ,
79- allDatums ,
80- isVertical ,
81- range ,
82- outerRange
83- )
88+ isPrimary ,
89+ options ,
90+ series ,
91+ allDatums ,
92+ isVertical ,
93+ range ,
94+ outerRange
95+ )
8496 : options . scaleType === 'linear' || options . scaleType === 'log'
85- ? buildLinearAxis (
97+ ? buildLinearAxis (
8698 isPrimary ,
8799 options ,
88100 series ,
@@ -91,11 +103,11 @@ export default function buildAxisLinear<TDatum>(
91103 range ,
92104 outerRange
93105 )
94- : options . scaleType === 'band'
95- ? buildBandAxis ( isPrimary , options , series , isVertical , range , outerRange )
96- : ( ( ) => {
97- throw new Error ( 'Invalid scale type' )
98- } ) ( )
106+ : options . scaleType === 'band'
107+ ? buildBandAxis ( isPrimary , options , series , isVertical , range , outerRange )
108+ : ( ( ) => {
109+ throw new Error ( 'Invalid scale type' )
110+ } ) ( )
99111}
100112
101113function buildTimeAxis < TDatum > (
@@ -111,10 +123,13 @@ function buildTimeAxis<TDatum>(
111123
112124 let isInvalid = false
113125
114- series = isPrimary ? series : series
115- . filter ( s => s . secondaryAxisId === options . id )
126+ series = isPrimary
127+ ? series
128+ : series . filter ( s => s . secondaryAxisId === options . id )
116129
117- allDatums = isPrimary ? allDatums : allDatums . filter ( d => d . secondaryAxisId === options . id )
130+ allDatums = isPrimary
131+ ? allDatums
132+ : allDatums . filter ( d => d . secondaryAxisId === options . id )
118133
119134 // Now set the range
120135 const scale = scaleFn ( range )
@@ -125,6 +140,87 @@ function buildTimeAxis<TDatum>(
125140 return value
126141 } )
127142
143+ // Here, we find the maximum context (in descending order from year
144+ // down to millisecond) needed to understand the
145+ // dates in this dataset. If the min/max dates span multiples of
146+ // any of the time units OR if the max date resides in a different
147+ // unit boundary than today's, we use that unit as context.
148+
149+ const unitScale = [
150+ 'millisecond' ,
151+ 'second' ,
152+ 'minute' ,
153+ 'hour' ,
154+ 'day' ,
155+ 'month' ,
156+ 'year' ,
157+ ] as const
158+
159+ let autoFormatStr : string
160+
161+ if ( minValue && maxValue ) {
162+ if (
163+ timeYear . count ( minValue , maxValue ) > 0 ||
164+ + timeYear . floor ( maxValue ) < + timeYear ( )
165+ ) {
166+ autoFormatStr = '%b %-d, %Y %-I:%M:%S.%L %p'
167+ } else if (
168+ timeMonth . count ( minValue , maxValue ) > 0 ||
169+ + timeMonth . floor ( maxValue ) < + timeMonth ( )
170+ ) {
171+ autoFormatStr = '%b %-d, %-I:%M:%S.%L %p'
172+ } else if (
173+ timeDay . count ( minValue , maxValue ) > 0 ||
174+ + timeDay . floor ( maxValue ) < + timeDay ( )
175+ ) {
176+ autoFormatStr = '%b %-d, %-I:%M:%S.%L %p'
177+ } else if (
178+ timeHour . count ( minValue , maxValue ) > 0 ||
179+ + timeHour . floor ( maxValue ) < + timeHour ( )
180+ ) {
181+ autoFormatStr = '%-I:%M:%S.%L %p'
182+ } else if (
183+ timeMinute . count ( minValue , maxValue ) > 0 ||
184+ + timeMinute . floor ( maxValue ) < + timeMinute ( )
185+ ) {
186+ autoFormatStr = '%-I:%M:%S.%L'
187+ } else if (
188+ timeSecond . count ( minValue , maxValue ) > 0 ||
189+ + timeSecond . floor ( maxValue ) < + timeSecond ( )
190+ ) {
191+ autoFormatStr = '%L'
192+ }
193+ }
194+
195+ const contextFormat = ( format : string , date : Date ) => {
196+ if ( timeSecond ( date ) < date ) {
197+ // milliseconds - Do not remove any context
198+ return timeFormat ( format ) ( date )
199+ }
200+ if ( timeMinute ( date ) < date ) {
201+ // seconds - remove potential milliseconds
202+ return timeFormat ( format . replace ( / \. % L .* ?( \s | $ ) / , ' ' ) ) ( date )
203+ }
204+ if ( timeHour ( date ) < date ) {
205+ // minutes - remove potential seconds and milliseconds
206+ return timeFormat ( format . replace ( / : % S .* ?( \s | $ ) / , ' ' ) ) ( date )
207+ }
208+ if ( timeDay ( date ) < date ) {
209+ // hours - remove potential minutes and seconds and milliseconds
210+ return timeFormat ( format . replace ( / : % M .* ?( \s | $ ) / , ' ' ) ) ( date )
211+ }
212+ if ( timeMonth ( date ) < date ) {
213+ // days - remove potential hours, minutes, seconds and milliseconds
214+ return timeFormat ( format . replace ( / % I .* / , '' ) ) ( date )
215+ }
216+ if ( timeYear ( date ) < date ) {
217+ // months - remove potential days, hours, minutes, seconds and milliseconds
218+ return timeFormat ( format . replace ( / % e , .* ?( \s | $ ) / , '' ) ) ( date )
219+ }
220+ // years
221+ return timeFormat ( '%Y' ) ( date )
222+ }
223+
128224 let shouldNice = options . shouldNice
129225
130226 // see https://stackoverflow.com/a/2831422
@@ -195,13 +291,13 @@ function buildTimeAxis<TDatum>(
195291 ] )
196292 }
197293
198- const defaultFormat = scale . tickFormat ( )
199-
200294 const formatters = { } as AxisTime < TDatum > [ 'formatters' ]
201295
296+ const defaultFormat = scale . tickFormat ( )
297+
202298 const scaleFormat = ( value : Date ) =>
203299 options . formatters ?. scale ?.( value , { ...formatters , scale : undefined } ) ??
204- defaultFormat ( value )
300+ contextFormat ( autoFormatStr , value )
205301
206302 const tooltipFormat = ( value : Date ) =>
207303 options . formatters ?. tooltip ?.( value , {
@@ -211,7 +307,7 @@ function buildTimeAxis<TDatum>(
211307
212308 const cursorFormat = ( value : Date ) =>
213309 options . formatters ?. cursor ?.( value , { ...formatters , cursor : undefined } ) ??
214- tooltipFormat ( value )
310+ scaleFormat ( value )
215311
216312 Object . assign ( formatters , {
217313 default : defaultFormat ,
@@ -247,32 +343,35 @@ function buildLinearAxis<TDatum>(
247343
248344 let isInvalid = false
249345
250- series = isPrimary ? series : series
251- . filter ( s => s . secondaryAxisId === options . id )
346+ series = isPrimary
347+ ? series
348+ : series . filter ( s => s . secondaryAxisId === options . id )
252349
253- allDatums = isPrimary ? allDatums : allDatums . filter ( d => d . secondaryAxisId === options . id )
350+ allDatums = isPrimary
351+ ? allDatums
352+ : allDatums . filter ( d => d . secondaryAxisId === options . id )
254353
255354 if ( options . stacked ) {
256355 stackSeries ( series , options )
257356 }
258357
259358 let [ minValue , maxValue ] = options . stacked
260359 ? extent (
261- series
262- . map ( s =>
263- s . datums . map ( datum => {
264- const value = options . getValue ( datum . originalDatum )
265- datum [ isPrimary ? 'primaryValue' : 'secondaryValue' ] = value
266- return datum . stackData ?? [ ]
267- } )
268- )
269- . flat ( 2 ) as unknown as number [ ]
270- )
360+ series
361+ . map ( s =>
362+ s . datums . map ( datum => {
363+ const value = options . getValue ( datum . originalDatum )
364+ datum [ isPrimary ? 'primaryValue' : 'secondaryValue' ] = value
365+ return datum . stackData ?? [ ]
366+ } )
367+ )
368+ . flat ( 2 ) as unknown as number [ ]
369+ )
271370 : extent ( allDatums , datum => {
272- const value = options . getValue ( datum . originalDatum )
273- datum [ isPrimary ? 'primaryValue' : 'secondaryValue' ] = value
274- return value
275- } )
371+ const value = options . getValue ( datum . originalDatum )
372+ datum [ isPrimary ? 'primaryValue' : 'secondaryValue' ] = value
373+ return value
374+ } )
276375
277376 let shouldNice = options . shouldNice
278377
@@ -496,8 +595,8 @@ function stackSeries<TDatum>(
496595
497596 const stacked = stacker (
498597 Array . from ( {
499- length : series . sort ( ( a , b ) => b . datums . length - a . datums . length ) [ 0 ]
500- . datums . length ,
598+ length : series . sort ( ( a , b ) => b . datums . length - a . datums . length ) [ 0 ] . datums
599+ . length ,
501600 } )
502601 )
503602
@@ -578,11 +677,11 @@ function buildSeriesBandScale<TDatum>(
578677 . round ( false )
579678 . paddingOuter (
580679 options . outerSeriesBandPadding ??
581- ( options . outerBandPadding ? options . outerBandPadding / 2 : 0 )
680+ ( options . outerBandPadding ? options . outerBandPadding / 2 : 0 )
582681 )
583682 . paddingInner (
584683 options . innerSeriesBandPadding ??
585- ( options . innerBandPadding ? options . innerBandPadding / 2 : 0 )
684+ ( options . innerBandPadding ? options . innerBandPadding / 2 : 0 )
586685 )
587686
588687 const scale = ( seriesIndex : number ) =>
0 commit comments