@@ -129,7 +129,7 @@ function formatTime(unix: number, resolution: string, timezone: string): string
129129 if ( resolution === "daily" ) {
130130 return d . toLocaleDateString ( [ ] , { timeZone : timezone , month : "short" , day : "numeric" } ) ;
131131 }
132- return d . toLocaleDateString ( [ ] , { timeZone : timezone , month : "short" , year : "2-digit " } ) ;
132+ return d . toLocaleDateString ( [ ] , { timeZone : timezone , month : "short" , year : "numeric " } ) ;
133133}
134134
135135function isLeapYear ( year : number ) : boolean {
@@ -235,6 +235,72 @@ function withWeeklyDayXAxis(
235235 } ;
236236}
237237
238+ function stationMonthStartSplits (
239+ timezone : string ,
240+ scaleMin : number ,
241+ scaleMax : number ,
242+ ) : number [ ] {
243+ if ( ! Number . isFinite ( scaleMin ) || ! Number . isFinite ( scaleMax ) || scaleMin > scaleMax ) {
244+ return [ ] ;
245+ }
246+
247+ const startParts = getZonedParts ( timezone , new Date ( scaleMin * 1000 ) ) ;
248+ let date : CalendarDate = {
249+ year : startParts . year ,
250+ month : startParts . month ,
251+ day : 1 ,
252+ } ;
253+ const splits : number [ ] = [ ] ;
254+
255+ for ( let guard = 0 ; guard < 60 ; guard += 1 ) {
256+ const split =
257+ zonedMidnightToUtc ( timezone , date . year , date . month , date . day ) . getTime ( ) / 1000 ;
258+ if ( split >= scaleMin && split <= scaleMax ) {
259+ splits . push ( split ) ;
260+ }
261+ if ( split > scaleMax ) {
262+ break ;
263+ }
264+ const nextMonth = date . month === 12 ? 1 : date . month + 1 ;
265+ const nextYear = date . month === 12 ? date . year + 1 : date . year ;
266+ date = { year : nextYear , month : nextMonth , day : 1 } ;
267+ }
268+
269+ return splits ;
270+ }
271+
272+ function withMonthlyXAxis (
273+ opts : HistoryChartOptions ,
274+ enabled : boolean ,
275+ timezone : string ,
276+ ) : HistoryChartOptions {
277+ if ( ! enabled ) return opts ;
278+
279+ const splits : uPlot . Axis . Splits = ( _u , _axisIdx , scaleMin , scaleMax ) =>
280+ stationMonthStartSplits ( timezone , scaleMin , scaleMax ) ;
281+ const values : uPlot . Axis . Values = ( _u , axisSplits ) =>
282+ axisSplits . map ( ( split ) =>
283+ new Date ( split * 1000 ) . toLocaleDateString ( [ ] , {
284+ timeZone : timezone ,
285+ month : "short" ,
286+ year : "numeric" ,
287+ } ) ,
288+ ) ;
289+
290+ return {
291+ ...opts ,
292+ axes : opts . axes ?. map ( ( axis , index ) =>
293+ index === 0
294+ ? {
295+ ...axis ,
296+ splits,
297+ values,
298+ }
299+ : axis ,
300+ ) ,
301+ } ;
302+ }
303+
238304function ChartPanel ( {
239305 title,
240306 children,
@@ -496,43 +562,48 @@ function HistoryPageInner() {
496562 [ resolution , timezone ] ,
497563 ) ;
498564 const useWeeklyDayXAxis = resolution === "hourly" && range === "week" && ! activeZoomRange ;
499- const applyWeeklyDayXAxis = useCallback (
565+ const useMonthlyXAxis = resolution === "monthly" && ! activeZoomRange ;
566+ const applyHistoryXAxis = useCallback (
500567 ( opts : HistoryChartOptions ) =>
501- withWeeklyDayXAxis ( opts , useWeeklyDayXAxis , timezone ) ,
502- [ timezone , useWeeklyDayXAxis ] ,
568+ withMonthlyXAxis (
569+ withWeeklyDayXAxis ( opts , useWeeklyDayXAxis , timezone ) ,
570+ useMonthlyXAxis ,
571+ timezone ,
572+ ) ,
573+ [ timezone , useWeeklyDayXAxis , useMonthlyXAxis ] ,
503574 ) ;
504575
505576 // Chart options — rebuilt when resolution, units, or theme change
506577 const tempOpts = useMemo (
507- ( ) => applyWeeklyDayXAxis ( temperatureOpts ( colors , tickFmt , isRaw ) ) ,
508- [ applyWeeklyDayXAxis , colors , tickFmt , isRaw ] ,
578+ ( ) => applyHistoryXAxis ( temperatureOpts ( colors , tickFmt , isRaw ) ) ,
579+ [ applyHistoryXAxis , colors , tickFmt , isRaw ] ,
509580 ) ;
510581 const humOpts = useMemo (
511- ( ) => applyWeeklyDayXAxis ( humidityOpts ( colors , tickFmt ) ) ,
512- [ applyWeeklyDayXAxis , colors , tickFmt ] ,
582+ ( ) => applyHistoryXAxis ( humidityOpts ( colors , tickFmt ) ) ,
583+ [ applyHistoryXAxis , colors , tickFmt ] ,
513584 ) ;
514585 const presOpts = useMemo (
515- ( ) => applyWeeklyDayXAxis ( pressureOpts ( colors , tickFmt ) ) ,
516- [ applyWeeklyDayXAxis , colors , tickFmt ] ,
586+ ( ) => applyHistoryXAxis ( pressureOpts ( colors , tickFmt ) ) ,
587+ [ applyHistoryXAxis , colors , tickFmt ] ,
517588 ) ;
518589 const wndOpts = useMemo (
519590 ( ) =>
520- applyWeeklyDayXAxis (
591+ applyHistoryXAxis (
521592 useWindBars ? windOptsBucketed ( colors , tickFmt ) : windOpts ( colors , tickFmt ) ,
522593 ) ,
523- [ applyWeeklyDayXAxis , colors , tickFmt , useWindBars ] ,
594+ [ applyHistoryXAxis , colors , tickFmt , useWindBars ] ,
524595 ) ;
525596 const rainDecimals = system === "imperial" ? 3 : 1 ;
526597 const rnOpts = useMemo (
527- ( ) => applyWeeklyDayXAxis ( rainOpts ( colors , tickFmt , rainDecimals ) ) ,
528- [ applyWeeklyDayXAxis , colors , tickFmt , rainDecimals ] ,
598+ ( ) => applyHistoryXAxis ( rainOpts ( colors , tickFmt , rainDecimals ) ) ,
599+ [ applyHistoryXAxis , colors , tickFmt , rainDecimals ] ,
529600 ) ;
530601 const suvOpts = useMemo (
531602 ( ) =>
532- applyWeeklyDayXAxis (
603+ applyHistoryXAxis (
533604 useSolarBars ? solarUvOptsBucketed ( colors , tickFmt ) : solarUvOpts ( colors , tickFmt ) ,
534605 ) ,
535- [ applyWeeklyDayXAxis , colors , tickFmt , useSolarBars ] ,
606+ [ applyHistoryXAxis , colors , tickFmt , useSolarBars ] ,
536607 ) ;
537608
538609 // Zoom
0 commit comments