@@ -11,6 +11,18 @@ import {
11
11
ScaleBand ,
12
12
} from 'd3-scale'
13
13
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
+
14
26
import {
15
27
Axis ,
16
28
AxisBand ,
@@ -73,16 +85,16 @@ export default function buildAxisLinear<TDatum>(
73
85
// Give the scale a home
74
86
return options . scaleType === 'time' || options . scaleType === 'localTime'
75
87
? 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
+ )
84
96
: options . scaleType === 'linear' || options . scaleType === 'log'
85
- ? buildLinearAxis (
97
+ ? buildLinearAxis (
86
98
isPrimary ,
87
99
options ,
88
100
series ,
@@ -91,11 +103,11 @@ export default function buildAxisLinear<TDatum>(
91
103
range ,
92
104
outerRange
93
105
)
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
+ } ) ( )
99
111
}
100
112
101
113
function buildTimeAxis < TDatum > (
@@ -111,10 +123,13 @@ function buildTimeAxis<TDatum>(
111
123
112
124
let isInvalid = false
113
125
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 )
116
129
117
- allDatums = isPrimary ? allDatums : allDatums . filter ( d => d . secondaryAxisId === options . id )
130
+ allDatums = isPrimary
131
+ ? allDatums
132
+ : allDatums . filter ( d => d . secondaryAxisId === options . id )
118
133
119
134
// Now set the range
120
135
const scale = scaleFn ( range )
@@ -125,6 +140,87 @@ function buildTimeAxis<TDatum>(
125
140
return value
126
141
} )
127
142
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
+
128
224
let shouldNice = options . shouldNice
129
225
130
226
// see https://stackoverflow.com/a/2831422
@@ -195,13 +291,13 @@ function buildTimeAxis<TDatum>(
195
291
] )
196
292
}
197
293
198
- const defaultFormat = scale . tickFormat ( )
199
-
200
294
const formatters = { } as AxisTime < TDatum > [ 'formatters' ]
201
295
296
+ const defaultFormat = scale . tickFormat ( )
297
+
202
298
const scaleFormat = ( value : Date ) =>
203
299
options . formatters ?. scale ?.( value , { ...formatters , scale : undefined } ) ??
204
- defaultFormat ( value )
300
+ contextFormat ( autoFormatStr , value )
205
301
206
302
const tooltipFormat = ( value : Date ) =>
207
303
options . formatters ?. tooltip ?.( value , {
@@ -211,7 +307,7 @@ function buildTimeAxis<TDatum>(
211
307
212
308
const cursorFormat = ( value : Date ) =>
213
309
options . formatters ?. cursor ?.( value , { ...formatters , cursor : undefined } ) ??
214
- tooltipFormat ( value )
310
+ scaleFormat ( value )
215
311
216
312
Object . assign ( formatters , {
217
313
default : defaultFormat ,
@@ -247,32 +343,35 @@ function buildLinearAxis<TDatum>(
247
343
248
344
let isInvalid = false
249
345
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 )
252
349
253
- allDatums = isPrimary ? allDatums : allDatums . filter ( d => d . secondaryAxisId === options . id )
350
+ allDatums = isPrimary
351
+ ? allDatums
352
+ : allDatums . filter ( d => d . secondaryAxisId === options . id )
254
353
255
354
if ( options . stacked ) {
256
355
stackSeries ( series , options )
257
356
}
258
357
259
358
let [ minValue , maxValue ] = options . stacked
260
359
? 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
+ )
271
370
: 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
+ } )
276
375
277
376
let shouldNice = options . shouldNice
278
377
@@ -496,8 +595,8 @@ function stackSeries<TDatum>(
496
595
497
596
const stacked = stacker (
498
597
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 ,
501
600
} )
502
601
)
503
602
@@ -578,11 +677,11 @@ function buildSeriesBandScale<TDatum>(
578
677
. round ( false )
579
678
. paddingOuter (
580
679
options . outerSeriesBandPadding ??
581
- ( options . outerBandPadding ? options . outerBandPadding / 2 : 0 )
680
+ ( options . outerBandPadding ? options . outerBandPadding / 2 : 0 )
582
681
)
583
682
. paddingInner (
584
683
options . innerSeriesBandPadding ??
585
- ( options . innerBandPadding ? options . innerBandPadding / 2 : 0 )
684
+ ( options . innerBandPadding ? options . innerBandPadding / 2 : 0 )
586
685
)
587
686
588
687
const scale = ( seriesIndex : number ) =>
0 commit comments