Skip to content

Commit d05aacc

Browse files
jeclrsgGordonSmith
authored andcommitted
fix(eclwatch): add a multi-scale 24-hour tick formatter (#4482)
adds a 24h multiscale tick formatting function to chart/Axis, exposes a tickFormatFunc property in timeline/ReactTimelineSeries, and uses both in eclwatch/WUTimeline Signed-off-by: Jeremy Clements <79224539+jeclrsg@users.noreply.github.com>
1 parent 8f321c3 commit d05aacc

File tree

5 files changed

+64
-3
lines changed

5 files changed

+64
-3
lines changed

packages/chart/src/Axis.ts

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,14 +22,15 @@ export class Axis extends SVGWidget {
2222

2323
protected parser;
2424
protected parserInvert;
25-
protected formatter: (date: Date) => string;
25+
protected formatter: ((d: Date | any) => string) | null;
2626
protected d3Scale;
2727
protected d3Axis;
2828
protected d3Guides;
2929
protected _guideElement;
3030
protected svg;
3131
protected svgAxis;
3232
protected svgGuides;
33+
protected _tickFormatFunc?: (d: any) => string;
3334

3435
constructor(drawStartPosition: "origin" | "center" = "origin") {
3536
super();
@@ -91,6 +92,15 @@ export class Axis extends SVGWidget {
9192
return this.format(this.parse(d));
9293
}
9394

95+
tickFormatFunc(fn: (d: any) => string): this;
96+
tickFormatFunc(): ((d: any) => string) | undefined;
97+
tickFormatFunc(fn?: (d: any) => string): this | ((d: any) => string) | undefined {
98+
if (!arguments.length) return this._tickFormatFunc;
99+
this._tickFormatFunc = fn;
100+
this.updateScale();
101+
return this;
102+
}
103+
94104
scalePos(d) {
95105
let retVal = this.d3Scale(this.parse(d));
96106
if (this.type() === "ordinal") {
@@ -211,7 +221,11 @@ export class Axis extends SVGWidget {
211221
}
212222
this.parser = this.timePattern_exists() ? d3TimeParse(this.timePattern()) : null;
213223
this.parserInvert = this.timePattern_exists() ? d3TimeFormat(this.timePattern()) : null;
214-
this.formatter = this.tickFormat_exists() ? d3TimeFormat(this.tickFormat()) : null;
224+
if (this._tickFormatFunc) {
225+
this.formatter = this._tickFormatFunc;
226+
} else {
227+
this.formatter = this.tickFormat_exists() ? d3TimeFormat(this.tickFormat()) : null;
228+
}
215229
break;
216230
default:
217231
}
@@ -677,6 +691,8 @@ export interface Axis {
677691
tickFormat(_: string): this;
678692
tickFormat_exists(): boolean;
679693
tickFormat_reset(): void;
694+
tickFormatFunc(fn: (d: any) => string): this;
695+
tickFormatFunc(): ((d: any) => string) | undefined;
680696
tickLength(): number;
681697
tickLength(_: number): this;
682698
tickLength_exists(): boolean;

packages/chart/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ export * from "./Summary.ts";
2525
export * from "./SummaryC.ts";
2626
export * from "./WordCloud.ts";
2727
export * from "./XYAxis.ts";
28+
export * from "./timeFormats.ts";

packages/chart/src/timeFormats.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { timeFormat } from "d3-time-format";
2+
3+
/**
4+
* Adaptive 24-hour multi-scale tick formatter.
5+
* Order of precedence (first predicate that matches):
6+
* milliseconds -> seconds -> minutes -> hours -> day -> month -> year.
7+
*/
8+
export function multiScale24Hours(): (d: Date) => string {
9+
const fmtMs = timeFormat(".%L");
10+
const fmtSec = timeFormat(":%S");
11+
const fmtMin = timeFormat("%H:%M");
12+
const fmtHour = timeFormat("%H:00");
13+
const fmtDay = timeFormat("%b %d");
14+
const fmtMonth = timeFormat("%b");
15+
const fmtYear = timeFormat("%Y");
16+
17+
return (d: Date): string => {
18+
if (d.getMilliseconds() !== 0) return fmtMs(d);
19+
if (d.getSeconds() !== 0) return fmtSec(d);
20+
if (d.getMinutes() !== 0) return fmtMin(d);
21+
if (d.getHours() !== 0) return fmtHour(d);
22+
if (d.getDate() !== 1) return fmtDay(d);
23+
if (d.getMonth() !== 0) return fmtMonth(d);
24+
return fmtYear(d);
25+
};
26+
}

packages/eclwatch/src/WUTimeline.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { Palette } from "@hpcc-js/common";
22
import { Scope, Workunit, WsWorkunits } from "@hpcc-js/comms";
33
import { ReactTimelineSeries } from "@hpcc-js/timeline";
4+
import { multiScale24Hours } from "@hpcc-js/chart";
45
import { hashSum, RecursivePartial } from "@hpcc-js/util";
56

67
import "../src/WUGraph.css";
@@ -20,7 +21,7 @@ export class WUTimeline extends ReactTimelineSeries {
2021
.colorColumn("color")
2122
.seriesColumn("series")
2223
.timePattern("%Y-%m-%dT%H:%M:%S.%LZ")
23-
.tickFormat("%H:%M")
24+
.tickFormatFunc(multiScale24Hours())
2425
.tooltipTimeFormat("%H:%M:%S.%L")
2526
.tooltipHTML(d => {
2627
return d[columns.length].calcTooltip();

packages/timeline/src/ReactTimelineSeries.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,21 @@ export class ReactTimelineSeries extends ReactAxisGanttSeries {
7373
}
7474
}
7575

76+
tickFormatFunc(fn: (d: any) => string): this;
77+
tickFormatFunc(): ((d: any) => string) | undefined;
78+
tickFormatFunc(fn?: (d: any) => string): this | ((d: any) => string) | undefined {
79+
if (!arguments.length) {
80+
return this._axisLabelFormatter;
81+
}
82+
this._axisLabelFormatter = fn;
83+
84+
// Delegate to underlying Axis instances using the proper method
85+
this._topAxis.tickFormatFunc(fn);
86+
this._bottomAxis.tickFormatFunc(fn);
87+
88+
return this;
89+
}
90+
7691
tooltipHTML(callback) {
7792
this._tooltipHTML = callback;
7893
this.tooltip().tooltipHTML(this._tooltipHTML);
@@ -118,6 +133,8 @@ export interface ReactTimelineSeries {
118133
timePattern_exists(): boolean;
119134
tooltipTimeFormat(): string;
120135
tooltipTimeFormat(_: string): this;
136+
tickFormatFunc(fn: (d: any) => string): this;
137+
tickFormatFunc(): ((d: any) => string) | undefined;
121138
}
122139
ReactTimelineSeries.prototype.publish("timePattern", "%Y-%m-%d", "string", "Time pattern used for parsing datetime strings on each data row", null, { optional: true });
123140
ReactTimelineSeries.prototype.publish("tooltipTimeFormat", "%Y-%m-%d", "string", "Time format used in the default html tooltip");

0 commit comments

Comments
 (0)