ml-time-graph 1.0.0 → 1.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/API_DESIGN.md +1211 -0
- package/README.de.md +12 -0
- package/README.md +12 -0
- package/dist/analyze/index.d.ts +272 -46
- package/dist/analyze/index.js +11 -570
- package/dist/index.d.ts +346 -9
- package/dist/index.js +57 -3420
- package/dist/interaction/index.d.ts +1 -1
- package/dist/interaction/index.js +2 -435
- package/dist/internals.d.ts +20 -5
- package/dist/internals.js +40 -2455
- package/dist/{layout-Sc5UkC0r.d.ts → layout-BOYtrsZa.d.ts} +1 -1
- package/dist/{scale-Cbr0KpPz.d.ts → scale-BRE_QhbZ.d.ts} +38 -15
- package/package.json +65 -80
- package/dist/aggregated_subtypes-DZNZyFTX.d.ts +0 -43
- /package/{MKT_AGGREGATE.md → docs/MKT_AGGREGATE.md} +0 -0
package/dist/internals.js
CHANGED
|
@@ -1,2465 +1,50 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
}
|
|
4
|
-
|
|
5
|
-
// src/analyze/processor.ts
|
|
6
|
-
var SeriesProcessor = class {
|
|
7
|
-
/**
|
|
8
|
-
* Standard interpolation for scalar DataPoints.
|
|
9
|
-
*/
|
|
10
|
-
static interpolateDataPoint(p1, p2, t) {
|
|
11
|
-
return {
|
|
12
|
-
time: p1.time + t * (p2.time - p1.time),
|
|
13
|
-
value: (p1.value ?? 0) + t * ((p2.value ?? 0) - (p1.value ?? 0))
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
/**
|
|
17
|
-
* Standard interpolation for AggregatedPoints (interpolates min, max, avg and count).
|
|
18
|
-
*/
|
|
19
|
-
static interpolateAggregatedPoint(p1, p2, t) {
|
|
20
|
-
const lerp = (v1, v2) => v1 !== null && v2 !== null ? v1 + t * (v2 - v1) : null;
|
|
21
|
-
return {
|
|
22
|
-
time: p1.time + t * (p2.time - p1.time),
|
|
23
|
-
min: lerp(p1.min, p2.min),
|
|
24
|
-
max: lerp(p1.max, p2.max),
|
|
25
|
-
avg: lerp(p1.avg, p2.avg),
|
|
26
|
-
count: Math.round(p1.count + t * (p2.count - p1.count))
|
|
27
|
-
};
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* Splits a data array into contiguous runs based on null values or time jumps.
|
|
31
|
-
*
|
|
32
|
-
* @param data The raw data points.
|
|
33
|
-
* @param isNull A predicate to identify "gap" points (e.g. value === null).
|
|
34
|
-
* @param gapThreshold Max time distance between points before a new run starts.
|
|
35
|
-
*/
|
|
36
|
-
static getRuns(data, isNull, gapThreshold = 0) {
|
|
37
|
-
const sorted = [...data].sort((a, b) => a.time - b.time);
|
|
38
|
-
const runs = [];
|
|
39
|
-
let current = [];
|
|
40
|
-
let prev = null;
|
|
41
|
-
for (const p of sorted) {
|
|
42
|
-
const isPointNull = isNull(p);
|
|
43
|
-
const isJump = gapThreshold > 0 && prev && p.time - prev.time > gapThreshold;
|
|
44
|
-
if (isPointNull || isJump) {
|
|
45
|
-
if (current.length) {
|
|
46
|
-
runs.push(current);
|
|
47
|
-
current = [];
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (!isPointNull) {
|
|
51
|
-
current.push(p);
|
|
52
|
-
}
|
|
53
|
-
prev = p;
|
|
54
|
-
}
|
|
55
|
-
if (current.length) {
|
|
56
|
-
runs.push(current);
|
|
57
|
-
}
|
|
58
|
-
return runs;
|
|
59
|
-
}
|
|
60
|
-
/**
|
|
61
|
-
* Splits a contiguous run into sub-segments at the given boundary values.
|
|
62
|
-
* Inserts interpolated points at every boundary crossing so segments
|
|
63
|
-
* meet exactly at the boundary.
|
|
64
|
-
*
|
|
65
|
-
* @param run A gap-free array of points.
|
|
66
|
-
* @param boundaries Values at which to split the run.
|
|
67
|
-
* @param getValue Function to extract the numeric value used for splitting.
|
|
68
|
-
* @param interpolate Function to create an interpolated point between p1 and p2 at factor t [0..1].
|
|
69
|
-
*/
|
|
70
|
-
static splitByBoundaries(run, boundaries, getValue, interpolate) {
|
|
71
|
-
if (run.length === 0) return [];
|
|
72
|
-
if (boundaries.length === 0) {
|
|
73
|
-
return [{ data: run, zoneIndex: 0 }];
|
|
74
|
-
}
|
|
75
|
-
const bs = [...boundaries].sort((a, b) => a - b);
|
|
76
|
-
const out = [];
|
|
77
|
-
const getZone = (v) => {
|
|
78
|
-
let idx = 0;
|
|
79
|
-
for (let i = 0; i < bs.length; i++) {
|
|
80
|
-
if (v >= bs[i]) idx = i + 1;
|
|
81
|
-
else break;
|
|
82
|
-
}
|
|
83
|
-
return idx;
|
|
84
|
-
};
|
|
85
|
-
let currentSeg = [run[0]];
|
|
86
|
-
for (let i = 1; i < run.length; i++) {
|
|
87
|
-
const p1 = run[i - 1];
|
|
88
|
-
const p2 = run[i];
|
|
89
|
-
const v1 = getValue(p1);
|
|
90
|
-
const v2 = getValue(p2);
|
|
91
|
-
let crossed;
|
|
92
|
-
if (v2 > v1) {
|
|
93
|
-
crossed = bs.filter((b) => b > v1 && b <= v2);
|
|
94
|
-
} else if (v2 < v1) {
|
|
95
|
-
crossed = bs.filter((b) => b >= v2 && b < v1).reverse();
|
|
96
|
-
} else {
|
|
97
|
-
crossed = [];
|
|
98
|
-
}
|
|
99
|
-
for (const b of crossed) {
|
|
100
|
-
const t = (b - v1) / (v2 - v1);
|
|
101
|
-
const pInt = interpolate(p1, p2, t);
|
|
102
|
-
currentSeg.push(pInt);
|
|
103
|
-
out.push({ data: currentSeg, zoneIndex: getZone((v1 + b) / 2) });
|
|
104
|
-
currentSeg = [pInt];
|
|
105
|
-
}
|
|
106
|
-
currentSeg.push(p2);
|
|
107
|
-
}
|
|
108
|
-
if (currentSeg.length > 0) {
|
|
109
|
-
const vStart = getValue(currentSeg[0]);
|
|
110
|
-
const vEnd = getValue(currentSeg[currentSeg.length - 1]);
|
|
111
|
-
out.push({ data: currentSeg, zoneIndex: getZone((vStart + vEnd) / 2) });
|
|
112
|
-
}
|
|
113
|
-
return out;
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Splits a contiguous run into two groups: those below and those at/above a threshold.
|
|
117
|
-
* Internally uses splitByBoundaries to ensure exact intersection points.
|
|
118
|
-
*/
|
|
119
|
-
static splitByThreshold(run, threshold, getValue, interpolate) {
|
|
120
|
-
const segments = this.splitByBoundaries(run, [threshold], getValue, interpolate);
|
|
121
|
-
const result = { above: [], below: [] };
|
|
122
|
-
for (const seg of segments) {
|
|
123
|
-
if (seg.zoneIndex === 0) result.below.push(seg.data);
|
|
124
|
-
else result.above.push(seg.data);
|
|
125
|
-
}
|
|
126
|
-
return result;
|
|
127
|
-
}
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
// src/theme/defaults.ts
|
|
131
|
-
var theme = {
|
|
132
|
-
// ── Series ──
|
|
133
|
-
/** Default stroke color for series lines */
|
|
134
|
-
stroke: "#4285f4",
|
|
135
|
-
/** Default line width in pixels */
|
|
136
|
-
strokeWidth: 2,
|
|
137
|
-
/** Default point marker size (radius / half-width) */
|
|
138
|
-
pointSize: 4,
|
|
139
|
-
/** Max data points before point markers are suppressed */
|
|
140
|
-
pointThreshold: 100,
|
|
141
|
-
/** Default max time gap (ms) before the line breaks; 0 = off */
|
|
142
|
-
gapThreshold: 0,
|
|
143
|
-
/** Default series type */
|
|
144
|
-
fill: "none",
|
|
145
|
-
hatch: null,
|
|
146
|
-
// ── Aggregated series ──
|
|
147
|
-
/** Default band fill color */
|
|
148
|
-
bandFill: "#4285f4",
|
|
149
|
-
/** Default band opacity (when countOpacity is disabled) */
|
|
150
|
-
bandOpacity: 0.6,
|
|
151
|
-
/** Default avg line color for bands */
|
|
152
|
-
bandAvgLine: "#e53e3e",
|
|
153
|
-
/** Default min color for minmaxavg series */
|
|
154
|
-
minColor: "#3b82f6",
|
|
155
|
-
/** Default max color for minmaxavg series */
|
|
156
|
-
maxColor: "#ef4444",
|
|
157
|
-
/** Default avg color for minmaxavg series */
|
|
158
|
-
avgColor: "#64748b",
|
|
159
|
-
/** Area fill alpha suffix (hex) for zoned areas — default 20% opacity */
|
|
160
|
-
areaFillAlpha: "4285f433",
|
|
161
|
-
// ── Axis ──
|
|
162
|
-
/** Default axis baseline color */
|
|
163
|
-
axisColor: "#ccc",
|
|
164
|
-
/** Default tick mark color */
|
|
165
|
-
tickColor: "#ddd",
|
|
166
|
-
/** Default axis label text color */
|
|
167
|
-
textColor: "#777",
|
|
168
|
-
/** Default axis text size (axis labels, tick labels) */
|
|
169
|
-
textSize: 11,
|
|
170
|
-
/** Axis label (rotated title next to axis) fill color */
|
|
171
|
-
axisLabelColor: "#444",
|
|
172
|
-
/** Axis label font size */
|
|
173
|
-
axisLabelSize: 12,
|
|
174
|
-
// ── Grid ──
|
|
175
|
-
/** Default grid line stroke */
|
|
176
|
-
gridStroke: "#e2e8f0",
|
|
177
|
-
/** Default grid line stroke width */
|
|
178
|
-
gridStrokeWidth: 1,
|
|
179
|
-
/** Default grid opacity */
|
|
180
|
-
gridOpacity: 1,
|
|
181
|
-
// ── Legend ──
|
|
182
|
-
/** Legend swatch stroke */
|
|
183
|
-
legendStroke: "#ccc",
|
|
184
|
-
/** Legend text fill */
|
|
185
|
-
legendText: "#333",
|
|
186
|
-
/** Legend font size */
|
|
187
|
-
legendFont: 11,
|
|
188
|
-
// ── Annotations ──
|
|
189
|
-
/** Default annotation color */
|
|
190
|
-
annotationColor: "#334155",
|
|
191
|
-
/** Default annotation line width */
|
|
192
|
-
annotationWidth: 1.5,
|
|
193
|
-
/** Default annotation arrow head size */
|
|
194
|
-
annotationHead: 9,
|
|
195
|
-
/** Default annotation point radius */
|
|
196
|
-
annotationRadius: 4,
|
|
197
|
-
/** Default annotation text font size */
|
|
198
|
-
annotationFontSize: 11,
|
|
199
|
-
// ── Thresholds ──
|
|
200
|
-
/** Default threshold line color */
|
|
201
|
-
thresholdColor: "#666",
|
|
202
|
-
/** Default threshold line style */
|
|
203
|
-
thresholdLine: "dashed",
|
|
204
|
-
/** Default threshold fill opacity */
|
|
205
|
-
thresholdFillOpacity: 0.12,
|
|
206
|
-
/** Default threshold label font size */
|
|
207
|
-
thresholdFontSize: 10,
|
|
208
|
-
// ── Highlights ──
|
|
209
|
-
/** Default highlight fill color */
|
|
210
|
-
highlightColor: "#fbbf24",
|
|
211
|
-
/** Default highlight box opacity */
|
|
212
|
-
highlightOpacity: 0.2,
|
|
213
|
-
/** Default highlight label text color */
|
|
214
|
-
highlightLabelColor: "#92400e",
|
|
215
|
-
// ── Markers ──
|
|
216
|
-
/** Default marker color */
|
|
217
|
-
markerColor: "#f59e0b",
|
|
218
|
-
/** Default marker point size (cross / circle radius) */
|
|
219
|
-
markerSize: 5,
|
|
220
|
-
// ── Gaps ──
|
|
221
|
-
/** Gap region background fill */
|
|
222
|
-
gapFill: "#fff",
|
|
223
|
-
/** Gap border stroke */
|
|
224
|
-
gapStroke: "#ccc",
|
|
225
|
-
/** Gap border stroke width */
|
|
226
|
-
gapStrokeWidth: 1,
|
|
227
|
-
/** Gap label text color */
|
|
228
|
-
gapFontColor: "#999",
|
|
229
|
-
/** Gap label font size */
|
|
230
|
-
gapFontSize: 10,
|
|
231
|
-
gapFillOpacity: 0.15,
|
|
232
|
-
// ── Tooltip ──
|
|
233
|
-
/** Tooltip box background */
|
|
234
|
-
tooltipBg: "#fff",
|
|
235
|
-
/** Tooltip border */
|
|
236
|
-
tooltipBorder: "#cbd5e1",
|
|
237
|
-
/** Tooltip text color */
|
|
238
|
-
tooltipText: "#1e293b",
|
|
239
|
-
/** Tooltip value label color */
|
|
240
|
-
tooltipValue: "#3b82f6",
|
|
241
|
-
/** Tooltip crosshair stroke */
|
|
242
|
-
tooltipCrosshair: "#94a3b8",
|
|
243
|
-
/** Tooltip snap radius in pixels */
|
|
244
|
-
tooltipSnapRadius: 20,
|
|
245
|
-
// ── Statistics ──
|
|
246
|
-
/** Stats overlay line color */
|
|
247
|
-
statsLineColor: "#94a3b8",
|
|
248
|
-
/** Stats label color */
|
|
249
|
-
statsLabelColor: "#64748b",
|
|
250
|
-
// ── Minimap ──
|
|
251
|
-
/** Minimap overview line stroke */
|
|
252
|
-
minimapStroke: "#94a3b8",
|
|
253
|
-
/** Minimap background */
|
|
254
|
-
minimapBg: "#f8f9fa",
|
|
255
|
-
/** Minimap brush (viewport) fill */
|
|
256
|
-
minimapBrush: "#3b82f644",
|
|
257
|
-
// ── Palette ──
|
|
258
|
-
/** Default colour palette for enum categories and multi-series. */
|
|
259
|
-
palette: [
|
|
260
|
-
"#4285f4",
|
|
261
|
-
"#ea4335",
|
|
262
|
-
"#22c55e",
|
|
263
|
-
"#fbbc05",
|
|
264
|
-
"#9334ea",
|
|
265
|
-
"#12b5e5",
|
|
266
|
-
"#fb923c",
|
|
267
|
-
"#6366f1"
|
|
268
|
-
]
|
|
269
|
-
};
|
|
270
|
-
|
|
271
|
-
// src/patterns/hatch.ts
|
|
272
|
-
function getHatch(id, variant = "classic-diagonal", fillcolor = "rgba(200, 220, 255, 0.3)", linecolor = "#4D88FF", strokewidth = 2) {
|
|
273
|
-
if (variant === "none") {
|
|
274
|
-
return `
|
|
275
|
-
<pattern id="${id}" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
276
|
-
<rect width="10" height="10" fill="${fillcolor}" />
|
|
1
|
+
var E=class{};var x=class{static interpolateDataPoint(a,e,t){return {time:a.time+t*(e.time-a.time),value:(a.value??0)+t*((e.value??0)-(a.value??0))}}static interpolateAggregatedPoint(a,e,t){let r=(o,n)=>o!==null&&n!==null?o+t*(n-o):null;return {time:a.time+t*(e.time-a.time),min:r(a.min,e.min),max:r(a.max,e.max),avg:r(a.avg,e.avg),count:Math.round(a.count+t*(e.count-a.count))}}static getRuns(a,e,t=0){let r=[...a].sort((m,s)=>m.time-s.time),o=[],n=[],i=null;for(let m of r){let s=e(m),l=t>0&&i&&m.time-i.time>t;(s||l)&&n.length&&(o.push(n),n=[]),s||n.push(m),i=m;}return n.length&&o.push(n),o}static splitByBoundaries(a,e,t,r){if(a.length===0)return [];if(e.length===0)return [{data:a,zoneIndex:0}];let o=[...e].sort((s,l)=>s-l),n=[],i=s=>{let l=0;for(let u=0;u<o.length&&s>=o[u];u++)l=u+1;return l},m=[a[0]];for(let s=1;s<a.length;s++){let l=a[s-1],u=a[s],d=t(l),c=t(u),g;c>d?g=o.filter(p=>p>d&&p<=c):c<d?g=o.filter(p=>p>=c&&p<d).reverse():g=[];for(let p of g){let b=(p-d)/(c-d),y=r(l,u,b);m.push(y),n.push({data:m,zoneIndex:i((d+p)/2)}),m=[y];}m.push(u);}if(m.length>0){let s=t(m[0]),l=t(m[m.length-1]);n.push({data:m,zoneIndex:i((s+l)/2)});}return n}static splitByThreshold(a,e,t,r){let o=this.splitByBoundaries(a,[e],t,r),n={above:[],below:[]};for(let i of o)i.zoneIndex===0?n.below.push(i.data):n.above.push(i.data);return n}};var me=class{static compute(a){let e=a.map(m=>m.value).filter(m=>m!==null).sort((m,s)=>m-s);if(e.length===0)return {min:NaN,max:NaN,avg:NaN,mean:NaN,median:NaN,stdDev:NaN,count:0};let t=e.length,r=e.reduce((m,s)=>m+s,0)/t,o=t%2===1?e[Math.floor(t/2)]:(e[t/2-1]+e[t/2])/2,n=e.reduce((m,s)=>m+Math.pow(s-r,2),0)/t,i=Math.sqrt(n);return {min:e[0],max:e[t-1],avg:r,mean:r,median:o,stdDev:i,count:t}}static computeInRange(a,e,t){let r=a.filter(o=>o.time>=e&&o.time<=t);return this.compute(r)}};var h={stroke:"#4285f4",strokeWidth:2,pointSize:4,pointThreshold:100,gapThreshold:0,fill:"none",hatch:null,bandFill:"#4285f4",bandOpacity:.6,bandAvgLine:"#e53e3e",minColor:"#3b82f6",maxColor:"#ef4444",avgColor:"#64748b",areaFillAlpha:"4285f433",axisColor:"#ccc",tickColor:"#ddd",textColor:"#777",textSize:11,axisLabelColor:"#444",axisLabelSize:12,gridStroke:"#e2e8f0",gridStrokeWidth:1,gridOpacity:1,legendStroke:"#ccc",legendText:"#333",legendFont:11,annotationColor:"#334155",annotationWidth:1.5,annotationHead:9,annotationRadius:4,annotationFontSize:11,thresholdColor:"#666",thresholdLine:"dashed",thresholdFillOpacity:.12,thresholdFontSize:10,highlightColor:"#fbbf24",highlightOpacity:.2,highlightLabelColor:"#92400e",markerColor:"#f59e0b",markerSize:5,gapFill:"#fff",gapStroke:"#ccc",gapStrokeWidth:1,gapFontColor:"#999",gapFontSize:10,gapFillOpacity:.15,tooltipBg:"#fff",tooltipBorder:"#cbd5e1",tooltipText:"#1e293b",tooltipValue:"#3b82f6",tooltipCrosshair:"#94a3b8",tooltipSnapRadius:20,statsLineColor:"#94a3b8",statsLabelColor:"#64748b",minimapStroke:"#94a3b8",minimapBg:"#f8f9fa",minimapBrush:"#3b82f644",palette:["#4285f4","#ea4335","#22c55e","#fbbc05","#9334ea","#12b5e5","#fb923c","#6366f1"]};function ue(a,e="classic-diagonal",t="rgba(200, 220, 255, 0.3)",r="#4D88FF",o=2){if(e==="none")return `
|
|
2
|
+
<pattern id="${a}" width="10" height="10" patternUnits="userSpaceOnUse">
|
|
3
|
+
<rect width="10" height="10" fill="${t}" />
|
|
277
4
|
</pattern>
|
|
278
|
-
`.trim();
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
break;
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
break
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
transform = "rotate(45)";
|
|
301
|
-
patternContent = `
|
|
302
|
-
<line x1="0" y1="0" x2="0" y2="${height}" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linecap="square" />
|
|
303
|
-
<line x1="0" y1="0" x2="${width}" y2="0" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linecap="square" />
|
|
304
|
-
`;
|
|
305
|
-
break;
|
|
306
|
-
case "dots":
|
|
307
|
-
width = 12;
|
|
308
|
-
height = 12;
|
|
309
|
-
patternContent = `<circle cx="${width / 2}" cy="${height / 2}" r="${strokewidth * 1.2}" fill="${linecolor}" />`;
|
|
310
|
-
break;
|
|
311
|
-
case "waves":
|
|
312
|
-
width = 16;
|
|
313
|
-
height = 16;
|
|
314
|
-
patternContent = `
|
|
315
|
-
<path d="M 0 ${height / 2} Q ${width / 4} 0, ${width / 2} ${height / 2} T ${width} ${height / 2}"
|
|
316
|
-
fill="none" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linecap="round" />
|
|
317
|
-
`;
|
|
318
|
-
break;
|
|
319
|
-
case "dashed":
|
|
320
|
-
width = 12;
|
|
321
|
-
height = 12;
|
|
322
|
-
transform = "rotate(45)";
|
|
323
|
-
patternContent = `<line x1="0" y1="0" x2="0" y2="${height}" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-dasharray="3,3" />`;
|
|
324
|
-
break;
|
|
325
|
-
case "herringbone":
|
|
326
|
-
width = 16;
|
|
327
|
-
height = 16;
|
|
328
|
-
patternContent = `
|
|
329
|
-
<path d="M 0 0 L ${width / 2} ${height / 2} L 0 ${height} M ${width} 0 L ${width / 2} ${height / 2} L ${width} ${height}"
|
|
330
|
-
fill="none" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linejoin="round" stroke-linecap="round" />
|
|
331
|
-
`;
|
|
332
|
-
break;
|
|
333
|
-
case "brick":
|
|
334
|
-
width = 20;
|
|
335
|
-
height = 20;
|
|
336
|
-
patternContent = `
|
|
337
|
-
<path d="M 0 ${height / 2} L ${width} ${height / 2} M 0 ${height} L ${width} ${height} M ${width / 2} 0 L ${width / 2} ${height / 2} M 0 ${height / 2} L 0 ${height}"
|
|
338
|
-
fill="none" stroke="${linecolor}" stroke-width="${strokewidth}" />
|
|
339
|
-
`;
|
|
340
|
-
break;
|
|
341
|
-
case "double-stripe":
|
|
342
|
-
width = 16;
|
|
343
|
-
height = 16;
|
|
344
|
-
transform = "rotate(45)";
|
|
345
|
-
patternContent = `
|
|
346
|
-
<line x1="0" y1="0" x2="0" y2="${height}" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linecap="square" />
|
|
347
|
-
<line x1="${width / 2}" y1="0" x2="${width / 2}" y2="${height}" stroke="${linecolor}" stroke-width="${strokewidth / 2}" stroke-linecap="square" />
|
|
348
|
-
`;
|
|
349
|
-
break;
|
|
350
|
-
case "honeycomb":
|
|
351
|
-
width = 18;
|
|
352
|
-
height = 32;
|
|
353
|
-
patternContent = `
|
|
354
|
-
<path d="M 0 0 L ${width / 2} 5 L ${width} 0 M 0 16 L ${width / 2} 11 L ${width} 16 M 0 16 L 0 32 M ${width / 2} 5 L ${width / 2} 11 M ${width} 16 L ${width} 32 M 0 32 L ${width / 2} 27 L ${width} 32 M ${width / 2} 27 L ${width / 2} 32"
|
|
355
|
-
fill="none" stroke="${linecolor}" stroke-width="${strokewidth}" stroke-linejoin="round" stroke-linecap="round" />
|
|
356
|
-
`;
|
|
357
|
-
break;
|
|
358
|
-
}
|
|
359
|
-
const bgRect = `<rect width="${width}" height="${height}" fill="${fillcolor}" />`;
|
|
360
|
-
return `
|
|
361
|
-
<pattern id="${id}" width="${width}" height="${height}" patternTransform="${transform}" patternUnits="userSpaceOnUse">
|
|
362
|
-
${bgRect}
|
|
363
|
-
${patternContent}
|
|
5
|
+
`.trim();let n=12,i=12,m="rotate(0)",s="";switch(e){case "classic-diagonal":n=12,i=12,m="rotate(45)",s=`<line x1="0" y1="0" x2="0" y2="${i}" stroke="${r}" stroke-width="${o}" stroke-linecap="square" />`;break;case "dense-steep":n=6,i=6,m="rotate(30)",s=`<line x1="0" y1="0" x2="0" y2="${i}" stroke="${r}" stroke-width="${o}" stroke-linecap="square" />`;break;case "crosshatch":n=14,i=14,m="rotate(45)",s=`
|
|
6
|
+
<line x1="0" y1="0" x2="0" y2="${i}" stroke="${r}" stroke-width="${o}" stroke-linecap="square" />
|
|
7
|
+
<line x1="0" y1="0" x2="${n}" y2="0" stroke="${r}" stroke-width="${o}" stroke-linecap="square" />
|
|
8
|
+
`;break;case "dots":n=12,i=12,s=`<circle cx="${n/2}" cy="${i/2}" r="${o*1.2}" fill="${r}" />`;break;case "waves":n=16,i=16,s=`
|
|
9
|
+
<path d="M 0 ${i/2} Q ${n/4} 0, ${n/2} ${i/2} T ${n} ${i/2}"
|
|
10
|
+
fill="none" stroke="${r}" stroke-width="${o}" stroke-linecap="round" />
|
|
11
|
+
`;break;case "dashed":n=12,i=12,m="rotate(45)",s=`<line x1="0" y1="0" x2="0" y2="${i}" stroke="${r}" stroke-width="${o}" stroke-dasharray="3,3" />`;break;case "herringbone":n=16,i=16,s=`
|
|
12
|
+
<path d="M 0 0 L ${n/2} ${i/2} L 0 ${i} M ${n} 0 L ${n/2} ${i/2} L ${n} ${i}"
|
|
13
|
+
fill="none" stroke="${r}" stroke-width="${o}" stroke-linejoin="round" stroke-linecap="round" />
|
|
14
|
+
`;break;case "brick":n=20,i=20,s=`
|
|
15
|
+
<path d="M 0 ${i/2} L ${n} ${i/2} M 0 ${i} L ${n} ${i} M ${n/2} 0 L ${n/2} ${i/2} M 0 ${i/2} L 0 ${i}"
|
|
16
|
+
fill="none" stroke="${r}" stroke-width="${o}" />
|
|
17
|
+
`;break;case "double-stripe":n=16,i=16,m="rotate(45)",s=`
|
|
18
|
+
<line x1="0" y1="0" x2="0" y2="${i}" stroke="${r}" stroke-width="${o}" stroke-linecap="square" />
|
|
19
|
+
<line x1="${n/2}" y1="0" x2="${n/2}" y2="${i}" stroke="${r}" stroke-width="${o/2}" stroke-linecap="square" />
|
|
20
|
+
`;break;case "honeycomb":n=18,i=32,s=`
|
|
21
|
+
<path d="M 0 0 L ${n/2} 5 L ${n} 0 M 0 16 L ${n/2} 11 L ${n} 16 M 0 16 L 0 32 M ${n/2} 5 L ${n/2} 11 M ${n} 16 L ${n} 32 M 0 32 L ${n/2} 27 L ${n} 32 M ${n/2} 27 L ${n/2} 32"
|
|
22
|
+
fill="none" stroke="${r}" stroke-width="${o}" stroke-linejoin="round" stroke-linecap="round" />
|
|
23
|
+
`;break}let l=`<rect width="${n}" height="${i}" fill="${t}" />`;return `
|
|
24
|
+
<pattern id="${a}" width="${n}" height="${i}" patternTransform="${m}" patternUnits="userSpaceOnUse">
|
|
25
|
+
${l}
|
|
26
|
+
${s}
|
|
364
27
|
</pattern>
|
|
365
|
-
`.trim();
|
|
366
|
-
}
|
|
367
|
-
|
|
368
|
-
// src/renderer/series_renderer.ts
|
|
369
|
-
function resolveStyle(style) {
|
|
370
|
-
return {
|
|
371
|
-
id: style.id,
|
|
372
|
-
line: {
|
|
373
|
-
stroke: style.stroke ?? theme.stroke,
|
|
374
|
-
strokeWidth: style.strokeWidth ?? theme.strokeWidth,
|
|
375
|
-
smoothing: style.smoothing ?? false,
|
|
376
|
-
dashed: style.dashed ?? false
|
|
377
|
-
},
|
|
378
|
-
fill: style.fill ?? theme.areaFillAlpha,
|
|
379
|
-
markers: {
|
|
380
|
-
type: style.pointStyle ?? "none",
|
|
381
|
-
size: style.pointSize ?? theme.pointSize,
|
|
382
|
-
stroke: style.stroke ?? theme.stroke,
|
|
383
|
-
fill: "#ffffff"
|
|
384
|
-
},
|
|
385
|
-
shadow: {
|
|
386
|
-
color: style.shadowColor ?? "transparent",
|
|
387
|
-
blur: style.shadowBlur ?? 0,
|
|
388
|
-
offsetX: style.shadowOffsetX ?? 0,
|
|
389
|
-
offsetY: style.shadowOffsetY ?? 0
|
|
390
|
-
}
|
|
391
|
-
};
|
|
392
|
-
}
|
|
393
|
-
function renderLine(segments, ctx, style) {
|
|
394
|
-
const commands = [];
|
|
395
|
-
const totalPoints = segments.reduce((sum, seg) => sum + seg.data.length, 0);
|
|
396
|
-
const s = resolveStyle(style);
|
|
397
|
-
for (let si = 0; si < segments.length; si++) {
|
|
398
|
-
const seg = segments[si];
|
|
399
|
-
if (seg.data.length >= 2) {
|
|
400
|
-
commands.push({
|
|
401
|
-
type: "path",
|
|
402
|
-
id: style.id ? `${style.id}-line-${si}` : void 0,
|
|
403
|
-
points: seg.data.map((p) => ({
|
|
404
|
-
x: ctx.timeScale.map(p.time),
|
|
405
|
-
y: ctx.valueScale.map(p.value)
|
|
406
|
-
})),
|
|
407
|
-
stroke: seg.color ?? s.line.stroke,
|
|
408
|
-
strokeWidth: s.line.strokeWidth,
|
|
409
|
-
smoothing: s.line.smoothing,
|
|
410
|
-
dashed: s.line.dashed,
|
|
411
|
-
shadowColor: s.shadow.color,
|
|
412
|
-
shadowBlur: s.shadow.blur,
|
|
413
|
-
shadowOffsetX: s.shadow.offsetX,
|
|
414
|
-
shadowOffsetY: s.shadow.offsetY,
|
|
415
|
-
fill: "none"
|
|
416
|
-
});
|
|
417
|
-
} else if (seg.data.length === 1 && totalPoints === 1) {
|
|
418
|
-
commands.push({
|
|
419
|
-
type: "circle",
|
|
420
|
-
cx: ctx.timeScale.map(seg.data[0].time),
|
|
421
|
-
cy: ctx.valueScale.map(seg.data[0].value),
|
|
422
|
-
r: Math.max(s.markers.size, s.line.strokeWidth),
|
|
423
|
-
fill: seg.color ?? s.line.stroke,
|
|
424
|
-
shadowColor: s.shadow.color,
|
|
425
|
-
shadowBlur: s.shadow.blur
|
|
426
|
-
});
|
|
427
|
-
}
|
|
428
|
-
}
|
|
429
|
-
return commands;
|
|
430
|
-
}
|
|
431
|
-
function renderStep(segments, ctx, style) {
|
|
432
|
-
const commands = [];
|
|
433
|
-
const s = resolveStyle(style);
|
|
434
|
-
for (let si = 0; si < segments.length; si++) {
|
|
435
|
-
const seg = segments[si];
|
|
436
|
-
if (seg.data.length < 2) continue;
|
|
437
|
-
const pts = [];
|
|
438
|
-
for (let i = 0; i < seg.data.length; i++) {
|
|
439
|
-
const px = ctx.timeScale.map(seg.data[i].time);
|
|
440
|
-
const py = ctx.valueScale.map(seg.data[i].value);
|
|
441
|
-
if (i === 0) {
|
|
442
|
-
pts.push({ x: px, y: py });
|
|
443
|
-
} else {
|
|
444
|
-
pts.push({ x: px, y: pts[pts.length - 1].y });
|
|
445
|
-
pts.push({ x: px, y: py });
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
commands.push({
|
|
449
|
-
type: "path",
|
|
450
|
-
id: style.id ? `${style.id}-line-${si}` : void 0,
|
|
451
|
-
points: pts,
|
|
452
|
-
stroke: seg.color ?? s.line.stroke,
|
|
453
|
-
strokeWidth: s.line.strokeWidth,
|
|
454
|
-
smoothing: false,
|
|
455
|
-
fill: "none"
|
|
456
|
-
});
|
|
457
|
-
}
|
|
458
|
-
return commands;
|
|
459
|
-
}
|
|
460
|
-
function renderArea(segments, ctx, style, yLow, yHigh) {
|
|
461
|
-
const commands = [];
|
|
462
|
-
const s = resolveStyle(style);
|
|
463
|
-
for (let si = 0; si < segments.length; si++) {
|
|
464
|
-
const seg = segments[si];
|
|
465
|
-
if (seg.data.length < 2) continue;
|
|
466
|
-
const ptsLow = seg.data.map((p) => ({
|
|
467
|
-
x: ctx.timeScale.map(p.time),
|
|
468
|
-
y: ctx.valueScale.map(yLow(p))
|
|
469
|
-
}));
|
|
470
|
-
const ptsHigh = seg.data.map((p) => ({
|
|
471
|
-
x: ctx.timeScale.map(p.time),
|
|
472
|
-
y: ctx.valueScale.map(yHigh(p))
|
|
473
|
-
})).reverse();
|
|
474
|
-
commands.push({
|
|
475
|
-
type: "path",
|
|
476
|
-
id: style.id ? `${style.id}-fill-${si}` : void 0,
|
|
477
|
-
points: [...ptsLow, ...ptsHigh],
|
|
478
|
-
fill: seg.color ?? s.fill,
|
|
479
|
-
hatch: style.hatch,
|
|
480
|
-
stroke: "none"
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
return commands;
|
|
484
|
-
}
|
|
485
|
-
function renderZonedArea(run, ctx, options, style) {
|
|
486
|
-
if (run.length < 2) return [];
|
|
487
|
-
const segments = SeriesProcessor.splitByBoundaries(
|
|
488
|
-
run,
|
|
489
|
-
options.boundaries,
|
|
490
|
-
options.getValue,
|
|
491
|
-
options.interpolate
|
|
492
|
-
);
|
|
493
|
-
const commands = [];
|
|
494
|
-
for (let si = 0; si < segments.length; si++) {
|
|
495
|
-
const seg = segments[si];
|
|
496
|
-
if (seg.data.length < 2) continue;
|
|
497
|
-
const color = options.getColor(seg.zoneIndex);
|
|
498
|
-
if (!color) continue;
|
|
499
|
-
const ptsLow = seg.data.map((p) => ({
|
|
500
|
-
x: ctx.timeScale.map(p.time),
|
|
501
|
-
y: ctx.valueScale.map(options.yLow(p))
|
|
502
|
-
}));
|
|
503
|
-
const ptsHigh = seg.data.map((p) => ({
|
|
504
|
-
x: ctx.timeScale.map(p.time),
|
|
505
|
-
y: ctx.valueScale.map(options.yHigh(p))
|
|
506
|
-
})).reverse();
|
|
507
|
-
commands.push({
|
|
508
|
-
type: "path",
|
|
509
|
-
id: style?.id ? `${style.id}-fill-${si}` : void 0,
|
|
510
|
-
points: [...ptsLow, ...ptsHigh],
|
|
511
|
-
fill: color,
|
|
512
|
-
hatch: options.getHatch?.(seg.zoneIndex),
|
|
513
|
-
stroke: "none"
|
|
514
|
-
});
|
|
515
|
-
}
|
|
516
|
-
return commands;
|
|
517
|
-
}
|
|
518
|
-
function renderSplitLine(run, threshold, ctx, styles) {
|
|
519
|
-
const split = SeriesProcessor.splitByThreshold(
|
|
520
|
-
run,
|
|
521
|
-
threshold,
|
|
522
|
-
(p) => p.value,
|
|
523
|
-
SeriesProcessor.interpolateDataPoint
|
|
524
|
-
);
|
|
525
|
-
const commands = [];
|
|
526
|
-
if (styles.below) {
|
|
527
|
-
commands.push(...renderLine(split.below.map((data) => ({ data })), ctx, styles.below));
|
|
528
|
-
}
|
|
529
|
-
if (styles.above) {
|
|
530
|
-
commands.push(...renderLine(split.above.map((data) => ({ data })), ctx, styles.above));
|
|
531
|
-
}
|
|
532
|
-
return commands;
|
|
533
|
-
}
|
|
534
|
-
function renderMarkers(points, ctx, style, getColor) {
|
|
535
|
-
const s = resolveStyle(style);
|
|
536
|
-
if (!s.markers.type || s.markers.type === "none") return [];
|
|
537
|
-
const commands = [];
|
|
538
|
-
for (let mi = 0; mi < points.length; mi++) {
|
|
539
|
-
const p = points[mi];
|
|
540
|
-
const x = ctx.timeScale.map(p.time);
|
|
541
|
-
const y = ctx.valueScale.map(p.value);
|
|
542
|
-
const pointColor = getColor(p);
|
|
543
|
-
const stroke = style.pointStroke ?? pointColor;
|
|
544
|
-
const fill = style.pointFill ?? pointColor;
|
|
545
|
-
const strokeWidth = style.pointStrokeWidth ?? 1.5;
|
|
546
|
-
const id = style.id ? `${style.id}-marker-${mi}` : void 0;
|
|
547
|
-
drawMarker(commands, id, s.markers.type, x, y, s.markers.size, stroke, fill, strokeWidth);
|
|
548
|
-
}
|
|
549
|
-
return commands;
|
|
550
|
-
}
|
|
551
|
-
function drawMarker(commands, id, shape, x, y, size, stroke, fill, sw) {
|
|
552
|
-
switch (shape) {
|
|
553
|
-
case "circle":
|
|
554
|
-
commands.push({ type: "circle", cx: x, cy: y, r: size, fill, stroke, strokeWidth: sw, id });
|
|
555
|
-
break;
|
|
556
|
-
case "square":
|
|
557
|
-
commands.push({ type: "rect", x: x - size, y: y - size, w: size * 2, h: size * 2, fill, stroke, strokeWidth: sw, id });
|
|
558
|
-
break;
|
|
559
|
-
case "cross":
|
|
560
|
-
commands.push({ type: "line", x1: x - size, y1: y - size, x2: x + size, y2: y + size, stroke, strokeWidth: sw, id });
|
|
561
|
-
commands.push({ type: "line", x1: x - size, y1: y + size, x2: x + size, y2: y - size, stroke, strokeWidth: sw, id });
|
|
562
|
-
break;
|
|
563
|
-
case "diamond":
|
|
564
|
-
commands.push({ type: "path", points: [{ x, y: y - size }, { x: x + size, y }, { x, y: y + size }, { x: x - size, y }], fill, stroke, strokeWidth: sw, id });
|
|
565
|
-
break;
|
|
566
|
-
case "triangle":
|
|
567
|
-
commands.push({ type: "path", points: [{ x, y: y - size }, { x: x + size, y: y + size }, { x: x - size, y: y + size }], fill, stroke, strokeWidth: sw, id });
|
|
568
|
-
break;
|
|
569
|
-
case "star": {
|
|
570
|
-
const pts = [];
|
|
571
|
-
for (let i = 0; i < 10; i++) {
|
|
572
|
-
const r = i % 2 === 0 ? size : size * 0.5;
|
|
573
|
-
const a = Math.PI / 2 * 3 + i * Math.PI / 5;
|
|
574
|
-
pts.push({ x: x + r * Math.cos(a), y: y + r * Math.sin(a) });
|
|
575
|
-
}
|
|
576
|
-
commands.push({ type: "path", points: pts, fill, stroke, strokeWidth: sw, id });
|
|
577
|
-
break;
|
|
578
|
-
}
|
|
579
|
-
case "arrow":
|
|
580
|
-
commands.push({ type: "path", points: [{ x: x - size, y: y + size }, { x, y: y - size }, { x: x + size, y: y + size }], stroke, strokeWidth: sw, fill: "none", id });
|
|
581
|
-
break;
|
|
582
|
-
default:
|
|
583
|
-
commands.push({ type: "circle", cx: x, cy: y, r: size, fill, stroke, strokeWidth: sw, id });
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// src/renderer/legend_renderer.ts
|
|
588
|
-
var SWATCH = 12;
|
|
589
|
-
var GAP = 8;
|
|
590
|
-
var ITEM_H = 20;
|
|
591
|
-
var FONT = 11;
|
|
592
|
-
var H_GAP = 18;
|
|
593
|
-
var labelWidth = (s) => s.length * FONT * 0.6;
|
|
594
|
-
function measureLegend(items, orientation = "vertical") {
|
|
595
|
-
if (orientation === "horizontal") {
|
|
596
|
-
let w = 0;
|
|
597
|
-
for (const it of items) w += SWATCH + GAP + labelWidth(it.name) + H_GAP;
|
|
598
|
-
return { width: Math.max(0, w - H_GAP), height: ITEM_H };
|
|
599
|
-
}
|
|
600
|
-
let maxLabel = 0;
|
|
601
|
-
for (const it of items) maxLabel = Math.max(maxLabel, labelWidth(it.name));
|
|
602
|
-
return { width: SWATCH + GAP + maxLabel, height: items.length * ITEM_H };
|
|
603
|
-
}
|
|
604
|
-
function renderLegend(config) {
|
|
605
|
-
const { items, x, y, orientation = "vertical" } = config;
|
|
606
|
-
const commands = [];
|
|
607
|
-
let cursorX = x;
|
|
608
|
-
items.forEach((item, i) => {
|
|
609
|
-
const sx = orientation === "horizontal" ? cursorX : x;
|
|
610
|
-
const sy = orientation === "horizontal" ? y : y + i * ITEM_H;
|
|
611
|
-
commands.push({
|
|
612
|
-
type: "rect",
|
|
613
|
-
x: sx,
|
|
614
|
-
y: sy,
|
|
615
|
-
w: SWATCH,
|
|
616
|
-
h: SWATCH,
|
|
617
|
-
fill: item.color,
|
|
618
|
-
stroke: theme.legendStroke,
|
|
619
|
-
strokeWidth: 1
|
|
620
|
-
});
|
|
621
|
-
commands.push({
|
|
622
|
-
type: "text",
|
|
623
|
-
content: item.name,
|
|
624
|
-
x: sx + SWATCH + GAP,
|
|
625
|
-
y: sy + SWATCH - 2,
|
|
626
|
-
fontSize: theme.legendFont,
|
|
627
|
-
fill: theme.legendText
|
|
628
|
-
});
|
|
629
|
-
if (orientation === "horizontal") cursorX += SWATCH + GAP + labelWidth(item.name) + H_GAP;
|
|
630
|
-
});
|
|
631
|
-
return { type: "group", cssClass: "chart-legend", commands };
|
|
632
|
-
}
|
|
633
|
-
|
|
634
|
-
// src/renderer/grid_renderer.ts
|
|
635
|
-
function renderGrid(config) {
|
|
636
|
-
const {
|
|
637
|
-
xTicks,
|
|
638
|
-
yTicks,
|
|
639
|
-
xRange,
|
|
640
|
-
yRange,
|
|
641
|
-
stroke = theme.gridStroke,
|
|
642
|
-
strokeWidth = theme.gridStrokeWidth,
|
|
643
|
-
dashed = false,
|
|
644
|
-
opacity = theme.gridOpacity
|
|
645
|
-
} = config;
|
|
646
|
-
const commands = [];
|
|
647
|
-
if (yTicks) {
|
|
648
|
-
for (const y of yTicks) {
|
|
649
|
-
commands.push({ type: "line", x1: xRange[0], y1: y, x2: xRange[1], y2: y, stroke, strokeWidth, dashed, opacity });
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (xTicks) {
|
|
653
|
-
for (const x of xTicks) {
|
|
654
|
-
commands.push({ type: "line", x1: x, y1: yRange[0], x2: x, y2: yRange[1], stroke, strokeWidth, dashed, opacity });
|
|
655
|
-
}
|
|
656
|
-
}
|
|
657
|
-
return commands;
|
|
658
|
-
}
|
|
659
|
-
|
|
660
|
-
// src/core/scale.ts
|
|
661
|
-
var LinearScale = class {
|
|
662
|
-
#domain;
|
|
663
|
-
#range;
|
|
664
|
-
constructor(config) {
|
|
665
|
-
this.#domain = [...config.domain];
|
|
666
|
-
this.#range = [...config.range];
|
|
667
|
-
}
|
|
668
|
-
map(value) {
|
|
669
|
-
const v = Number(value);
|
|
670
|
-
const [d0, d1] = this.#domain;
|
|
671
|
-
const [r0, r1] = this.#range;
|
|
672
|
-
if (d1 === d0) return r0;
|
|
673
|
-
return r0 + (v - d0) / (d1 - d0) * (r1 - r0);
|
|
674
|
-
}
|
|
675
|
-
invert(pixel) {
|
|
676
|
-
const [d0, d1] = this.#domain;
|
|
677
|
-
const [r0, r1] = this.#range;
|
|
678
|
-
if (r1 === r0) return d0;
|
|
679
|
-
return d0 + (pixel - r0) / (r1 - r0) * (d1 - d0);
|
|
680
|
-
}
|
|
681
|
-
domain() {
|
|
682
|
-
return [...this.#domain];
|
|
683
|
-
}
|
|
684
|
-
range() {
|
|
685
|
-
return [...this.#range];
|
|
686
|
-
}
|
|
687
|
-
};
|
|
688
|
-
var TIME_INTERVALS = [
|
|
689
|
-
{ label: "second", ms: 1e3 },
|
|
690
|
-
{ label: "2_seconds", ms: 2e3 },
|
|
691
|
-
{ label: "5_seconds", ms: 5e3 },
|
|
692
|
-
{ label: "10_seconds", ms: 1e4 },
|
|
693
|
-
{ label: "30_seconds", ms: 3e4 },
|
|
694
|
-
{ label: "minute", ms: 6e4 },
|
|
695
|
-
{ label: "5_minutes", ms: 3e5 },
|
|
696
|
-
{ label: "15_minutes", ms: 9e5 },
|
|
697
|
-
{ label: "30_minutes", ms: 18e5 },
|
|
698
|
-
{ label: "hour", ms: 36e5 },
|
|
699
|
-
{ label: "3_hours", ms: 108e5 },
|
|
700
|
-
{ label: "6_hours", ms: 216e5 },
|
|
701
|
-
{ label: "day", ms: 864e5 },
|
|
702
|
-
{ label: "week", ms: 6048e5 },
|
|
703
|
-
{ label: "month", ms: 2592e6 },
|
|
704
|
-
{ label: "3_months", ms: 7776e6 },
|
|
705
|
-
{ label: "6_months", ms: 15552e6 },
|
|
706
|
-
{ label: "year", ms: 31536e6 },
|
|
707
|
-
{ label: "2_years", ms: 63072e6 },
|
|
708
|
-
{ label: "5_years", ms: 15768e7 }
|
|
709
|
-
];
|
|
710
|
-
var TimeScale = class {
|
|
711
|
-
#linear;
|
|
712
|
-
#locale;
|
|
713
|
-
constructor(config) {
|
|
714
|
-
this.#linear = new LinearScale({
|
|
715
|
-
domain: config.domain,
|
|
716
|
-
range: config.range
|
|
717
|
-
});
|
|
718
|
-
this.#locale = config.locale || (typeof navigator !== "undefined" ? navigator.language : "en-US");
|
|
719
|
-
}
|
|
720
|
-
map(value) {
|
|
721
|
-
return this.#linear.map(Number(value));
|
|
722
|
-
}
|
|
723
|
-
invert(pixel) {
|
|
724
|
-
return this.#linear.invert(pixel);
|
|
725
|
-
}
|
|
726
|
-
domain() {
|
|
727
|
-
return this.#linear.domain();
|
|
728
|
-
}
|
|
729
|
-
range() {
|
|
730
|
-
return this.#linear.range();
|
|
731
|
-
}
|
|
732
|
-
get locale() {
|
|
733
|
-
return this.#locale;
|
|
734
|
-
}
|
|
735
|
-
/**
|
|
736
|
-
* Pick the "nicest" time interval that yields roughly `targetTicks` ticks
|
|
737
|
-
* across the visible range. Clamps to minTicks / maxTicks bounds.
|
|
738
|
-
*/
|
|
739
|
-
tickInterval(targetTicks, minTicks = 3, maxTicks = 12) {
|
|
740
|
-
const [d0, d1] = this.#linear.domain();
|
|
741
|
-
const totalMs = d1 - d0;
|
|
742
|
-
if (totalMs <= 0) return { interval: TIME_INTERVALS[0].ms };
|
|
743
|
-
const ideal = totalMs / targetTicks;
|
|
744
|
-
let picked = TIME_INTERVALS[0].ms;
|
|
745
|
-
for (const t of TIME_INTERVALS) {
|
|
746
|
-
if (t.ms >= ideal) {
|
|
747
|
-
picked = t.ms;
|
|
748
|
-
break;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
let candidate = picked;
|
|
752
|
-
let count = Math.round(totalMs / candidate);
|
|
753
|
-
while (count > maxTicks && candidate < TIME_INTERVALS[TIME_INTERVALS.length - 1].ms) {
|
|
754
|
-
const idx = TIME_INTERVALS.findIndex((t) => t.ms === candidate);
|
|
755
|
-
candidate = TIME_INTERVALS[Math.min(idx + 1, TIME_INTERVALS.length - 1)].ms;
|
|
756
|
-
count = Math.round(totalMs / candidate);
|
|
757
|
-
}
|
|
758
|
-
while (count < minTicks && candidate > TIME_INTERVALS[0].ms) {
|
|
759
|
-
const idx = TIME_INTERVALS.findIndex((t) => t.ms === candidate);
|
|
760
|
-
candidate = TIME_INTERVALS[Math.max(idx - 1, 0)].ms;
|
|
761
|
-
count = Math.round(totalMs / candidate);
|
|
762
|
-
}
|
|
763
|
-
return { interval: candidate };
|
|
764
|
-
}
|
|
765
|
-
/**
|
|
766
|
-
* Generate tick positions (timestamps) across the domain.
|
|
767
|
-
*/
|
|
768
|
-
ticks(opts) {
|
|
769
|
-
const minT = opts?.minTicks ?? 5;
|
|
770
|
-
const maxT = opts?.maxTicks ?? 12;
|
|
771
|
-
const { interval } = this.tickInterval(
|
|
772
|
-
(minT + maxT) / 2,
|
|
773
|
-
minT,
|
|
774
|
-
maxT
|
|
775
|
-
);
|
|
776
|
-
const [d0, d1] = this.#linear.domain();
|
|
777
|
-
const result = [];
|
|
778
|
-
const start = Math.ceil(d0 / interval) * interval;
|
|
779
|
-
for (let t = start; t <= d1; t += interval) {
|
|
780
|
-
result.push(t);
|
|
781
|
-
}
|
|
782
|
-
return result;
|
|
783
|
-
}
|
|
784
|
-
/** Format a timestamp using Intl.DateTimeFormat */
|
|
785
|
-
format(timestamp, formatOpts) {
|
|
786
|
-
return new Intl.DateTimeFormat(this.#locale, formatOpts).format(
|
|
787
|
-
new Date(timestamp)
|
|
788
|
-
);
|
|
789
|
-
}
|
|
790
|
-
};
|
|
791
|
-
var BandScale = class {
|
|
792
|
-
#domain;
|
|
793
|
-
#range;
|
|
794
|
-
#paddingInner;
|
|
795
|
-
#paddingOuter;
|
|
796
|
-
constructor(config) {
|
|
797
|
-
this.#domain = [...config.domain];
|
|
798
|
-
this.#range = [...config.range];
|
|
799
|
-
this.#paddingInner = config.paddingInner ?? 0.1;
|
|
800
|
-
this.#paddingOuter = config.paddingOuter ?? 0.05;
|
|
801
|
-
}
|
|
802
|
-
/** Get the pixel width of each band (including padding) */
|
|
803
|
-
get step() {
|
|
804
|
-
const [r0, r1] = this.#range;
|
|
805
|
-
const n = this.#domain.length;
|
|
806
|
-
if (n <= 1) return Math.abs(r1 - r0);
|
|
807
|
-
return Math.abs(r1 - r0) * (1 - this.#paddingOuter * 2) / n + Math.abs(r1 - r0) * this.#paddingInner * 2 / n;
|
|
808
|
-
}
|
|
809
|
-
/** Get the pixel width of each band's content area */
|
|
810
|
-
get bandwidth() {
|
|
811
|
-
const [r0, r1] = this.#range;
|
|
812
|
-
const n = this.#domain.length;
|
|
813
|
-
if (n <= 1) return Math.abs(r1 - r0) * (1 - this.#paddingOuter * 2);
|
|
814
|
-
const totalPaddingOuter = this.#paddingOuter * 2 * Math.abs(r1 - r0);
|
|
815
|
-
const usable = Math.abs(r1 - r0) - totalPaddingOuter;
|
|
816
|
-
const step = usable / n;
|
|
817
|
-
return step * (1 - this.#paddingInner);
|
|
818
|
-
}
|
|
819
|
-
map(value) {
|
|
820
|
-
const idx = this.#domain.findIndex((d) => String(d) === String(value));
|
|
821
|
-
if (idx === -1) return this.#range[0];
|
|
822
|
-
const [r0, r1] = this.#range;
|
|
823
|
-
const n = this.#domain.length;
|
|
824
|
-
if (n <= 1) return (r0 + r1) / 2;
|
|
825
|
-
const totalPaddingOuter = this.#paddingOuter * 2 * Math.abs(r1 - r0);
|
|
826
|
-
const usable = Math.abs(r1 - r0) - totalPaddingOuter;
|
|
827
|
-
const direction = r1 >= r0 ? 1 : -1;
|
|
828
|
-
const step = usable / n;
|
|
829
|
-
const start = r0 + this.#paddingOuter * Math.abs(r1 - r0) * direction;
|
|
830
|
-
return start + idx * step;
|
|
831
|
-
}
|
|
832
|
-
invert(pixel) {
|
|
833
|
-
let bestIdx = 0;
|
|
834
|
-
let bestDist = Infinity;
|
|
835
|
-
for (let i = 0; i < this.#domain.length; i++) {
|
|
836
|
-
const pos = this.map(this.#domain[i]);
|
|
837
|
-
const dist = Math.abs(pixel - pos);
|
|
838
|
-
if (dist < bestDist) {
|
|
839
|
-
bestDist = dist;
|
|
840
|
-
bestIdx = i;
|
|
841
|
-
}
|
|
842
|
-
}
|
|
843
|
-
return this.#domain[bestIdx];
|
|
844
|
-
}
|
|
845
|
-
domain() {
|
|
846
|
-
if (this.#domain.length === 0) return ["", ""];
|
|
847
|
-
return [this.#domain[0], this.#domain[this.#domain.length - 1]];
|
|
848
|
-
}
|
|
849
|
-
range() {
|
|
850
|
-
return [...this.#range];
|
|
851
|
-
}
|
|
852
|
-
/** Get all band positions */
|
|
853
|
-
positions() {
|
|
854
|
-
const m = /* @__PURE__ */ new Map();
|
|
855
|
-
for (const d of this.#domain) {
|
|
856
|
-
m.set(d, this.map(d));
|
|
857
|
-
}
|
|
858
|
-
return m;
|
|
859
|
-
}
|
|
860
|
-
};
|
|
861
|
-
|
|
862
|
-
// src/series/threshold_renderer.ts
|
|
863
|
-
function dashFor(line) {
|
|
864
|
-
if (line === "dotted") return { dash: "dotted" };
|
|
865
|
-
if (line === "dashed") return { dash: "dashed" };
|
|
866
|
-
return {};
|
|
867
|
-
}
|
|
868
|
-
function renderThresholds(config) {
|
|
869
|
-
const { thresholds, valueScale, xRange } = config;
|
|
870
|
-
const [x0, x1] = xRange;
|
|
871
|
-
const [r0, r1] = valueScale.range();
|
|
872
|
-
const top = Math.min(r0, r1);
|
|
873
|
-
const bottom = Math.max(r0, r1);
|
|
874
|
-
const commands = [];
|
|
875
|
-
for (const t of thresholds) {
|
|
876
|
-
const color = t.color ?? theme.thresholdColor;
|
|
877
|
-
const y = valueScale.map(t.value);
|
|
878
|
-
if (t.fill === "above") {
|
|
879
|
-
commands.push({
|
|
880
|
-
type: "rect",
|
|
881
|
-
x: x0,
|
|
882
|
-
y: top,
|
|
883
|
-
w: x1 - x0,
|
|
884
|
-
h: Math.max(0, y - top),
|
|
885
|
-
fill: color,
|
|
886
|
-
hatch: t.fillHatch,
|
|
887
|
-
opacity: t.fillOpacity ?? 0.12,
|
|
888
|
-
id: t.id ? `${t.id}-fill` : void 0
|
|
889
|
-
});
|
|
890
|
-
} else if (t.fill === "below") {
|
|
891
|
-
commands.push({
|
|
892
|
-
type: "rect",
|
|
893
|
-
x: x0,
|
|
894
|
-
y,
|
|
895
|
-
w: x1 - x0,
|
|
896
|
-
h: Math.max(0, bottom - y),
|
|
897
|
-
fill: color,
|
|
898
|
-
hatch: t.fillHatch,
|
|
899
|
-
opacity: t.fillOpacity ?? 0.12,
|
|
900
|
-
id: t.id ? `${t.id}-fill` : void 0
|
|
901
|
-
});
|
|
902
|
-
}
|
|
903
|
-
const line = t.line ?? theme.thresholdLine;
|
|
904
|
-
if (line !== "none") {
|
|
905
|
-
const dash = dashFor(line);
|
|
906
|
-
const lineCmd = {
|
|
907
|
-
type: "line",
|
|
908
|
-
x1: x0,
|
|
909
|
-
y1: y,
|
|
910
|
-
x2: x1,
|
|
911
|
-
y2: y,
|
|
912
|
-
stroke: color,
|
|
913
|
-
strokeWidth: 1,
|
|
914
|
-
...dash,
|
|
915
|
-
id: t.id ? `${t.id}-line` : void 0
|
|
916
|
-
};
|
|
917
|
-
if (t.shadowColor) {
|
|
918
|
-
lineCmd.shadowColor = t.shadowColor;
|
|
919
|
-
lineCmd.shadowBlur = t.shadowBlur ?? 4;
|
|
920
|
-
lineCmd.shadowOffsetX = t.shadowOffsetX ?? 0;
|
|
921
|
-
lineCmd.shadowOffsetY = t.shadowOffsetY ?? 2;
|
|
922
|
-
}
|
|
923
|
-
commands.push(lineCmd);
|
|
924
|
-
}
|
|
925
|
-
if (t.label !== false) {
|
|
926
|
-
const labelObj = t.label && typeof t.label === "object" ? t.label : void 0;
|
|
927
|
-
const text = typeof t.label === "string" ? t.label : labelObj?.text ?? t.name;
|
|
928
|
-
const position = labelObj?.position ?? "right";
|
|
929
|
-
commands.push({
|
|
930
|
-
...thresholdLabel(text, position, x0, x1, y, color, labelObj),
|
|
931
|
-
id: t.id ? `${t.id}-label` : void 0
|
|
932
|
-
});
|
|
933
|
-
}
|
|
934
|
-
}
|
|
935
|
-
return commands;
|
|
936
|
-
}
|
|
937
|
-
function thresholdLabel(text, pos, x0, x1, y, color, labelObj) {
|
|
938
|
-
const mid = (x0 + x1) / 2;
|
|
939
|
-
const base = {
|
|
940
|
-
type: "text",
|
|
941
|
-
content: text,
|
|
942
|
-
fontSize: theme.thresholdFontSize,
|
|
943
|
-
fill: color
|
|
944
|
-
};
|
|
945
|
-
const extras = labelObj ? {
|
|
946
|
-
...labelObj.rotate !== void 0 && { rotate: labelObj.rotate },
|
|
947
|
-
...labelObj.textBaseline !== void 0 && {
|
|
948
|
-
textBaseline: labelObj.textBaseline
|
|
949
|
-
}
|
|
950
|
-
} : {};
|
|
951
|
-
switch (pos) {
|
|
952
|
-
case "left":
|
|
953
|
-
return { ...base, ...extras, x: x0 + 4, y: y - 4, anchor: "start" };
|
|
954
|
-
case "above":
|
|
955
|
-
return { ...base, ...extras, x: mid, y: y - 6, anchor: "middle" };
|
|
956
|
-
case "below":
|
|
957
|
-
return { ...base, ...extras, x: mid, y: y + 14, anchor: "middle" };
|
|
958
|
-
case "center":
|
|
959
|
-
return { ...base, ...extras, x: mid, y: y - 4, anchor: "middle" };
|
|
960
|
-
case "right":
|
|
961
|
-
default:
|
|
962
|
-
return { ...base, ...extras, x: x1 - 4, y: y - 4, anchor: "end" };
|
|
963
|
-
}
|
|
964
|
-
}
|
|
965
|
-
|
|
966
|
-
// src/series/gap_renderer.ts
|
|
967
|
-
function renderGaps(config) {
|
|
968
|
-
const {
|
|
969
|
-
gaps,
|
|
970
|
-
timeScale,
|
|
971
|
-
yRange,
|
|
972
|
-
fill = theme.gapFill,
|
|
973
|
-
hatch,
|
|
974
|
-
fillOpacity = theme.gapFillOpacity ?? 0.15,
|
|
975
|
-
stroke = theme.gapStroke,
|
|
976
|
-
strokeWidth = theme.gapStrokeWidth,
|
|
977
|
-
dashed = true,
|
|
978
|
-
fontSize = theme.gapFontSize,
|
|
979
|
-
fontFill = theme.gapFontColor,
|
|
980
|
-
labelBaseline: defaultBaseline = "middle",
|
|
981
|
-
labelRotate: defaultRotate
|
|
982
|
-
} = config;
|
|
983
|
-
const [y0, y1] = yRange;
|
|
984
|
-
const commands = [];
|
|
985
|
-
for (const gap of gaps) {
|
|
986
|
-
const x1 = timeScale.map(gap.startTime);
|
|
987
|
-
const x2 = timeScale.map(gap.endTime);
|
|
988
|
-
const gapFill = gap.fill ?? fill;
|
|
989
|
-
const gapHatch = gap.hatch ?? hatch;
|
|
990
|
-
const gapOpacity = gap.fillOpacity ?? fillOpacity;
|
|
991
|
-
const gapLabel = gap.label ?? "";
|
|
992
|
-
const gapRotate = gap.rotate ?? defaultRotate;
|
|
993
|
-
const baseline = gap.labelBaseline ?? defaultBaseline;
|
|
994
|
-
if (gap.style === "dashed_border" || !gap.style) {
|
|
995
|
-
commands.push({
|
|
996
|
-
type: "rect",
|
|
997
|
-
x: x1,
|
|
998
|
-
y: y0,
|
|
999
|
-
w: x2 - x1,
|
|
1000
|
-
h: y1 - y0,
|
|
1001
|
-
fill: gapFill,
|
|
1002
|
-
hatch: gapHatch,
|
|
1003
|
-
opacity: gapOpacity,
|
|
1004
|
-
stroke,
|
|
1005
|
-
strokeWidth,
|
|
1006
|
-
dashed
|
|
1007
|
-
});
|
|
1008
|
-
} else if (gap.style === "empty") {
|
|
1009
|
-
commands.push({
|
|
1010
|
-
type: "rect",
|
|
1011
|
-
x: x1,
|
|
1012
|
-
y: y0,
|
|
1013
|
-
w: x2 - x1,
|
|
1014
|
-
h: y1 - y0,
|
|
1015
|
-
fill: gapFill,
|
|
1016
|
-
hatch: gapHatch,
|
|
1017
|
-
opacity: gapOpacity
|
|
1018
|
-
});
|
|
1019
|
-
}
|
|
1020
|
-
if (gapLabel) {
|
|
1021
|
-
const labelY = gapLabelY(y0, y1, baseline);
|
|
1022
|
-
const svgBaseline = baseline === "above" ? "top" : baseline === "below" ? "bottom" : "middle";
|
|
1023
|
-
commands.push({
|
|
1024
|
-
type: "text",
|
|
1025
|
-
content: gapLabel,
|
|
1026
|
-
x: (x1 + x2) / 2,
|
|
1027
|
-
y: labelY,
|
|
1028
|
-
anchor: "middle",
|
|
1029
|
-
fontSize,
|
|
1030
|
-
fill: fontFill,
|
|
1031
|
-
textBaseline: svgBaseline,
|
|
1032
|
-
rotate: gapRotate
|
|
1033
|
-
});
|
|
1034
|
-
}
|
|
1035
|
-
}
|
|
1036
|
-
return commands;
|
|
1037
|
-
}
|
|
1038
|
-
function gapLabelY(y0, y1, baseline) {
|
|
1039
|
-
switch (baseline) {
|
|
1040
|
-
case "above":
|
|
1041
|
-
return y0 - 12;
|
|
1042
|
-
case "below":
|
|
1043
|
-
return y1 + 4;
|
|
1044
|
-
case "middle":
|
|
1045
|
-
default:
|
|
1046
|
-
return (y0 + y1) / 2;
|
|
1047
|
-
}
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
// src/annotation/marker.ts
|
|
1051
|
-
function renderMarkers2(config) {
|
|
1052
|
-
const { markers, timeScale, valueScale, yRange = [0, 300] } = config;
|
|
1053
|
-
const [yTop, yBottom] = yRange;
|
|
1054
|
-
const commands = [];
|
|
1055
|
-
for (const marker of markers) {
|
|
1056
|
-
const x = timeScale.map(marker.time);
|
|
1057
|
-
const color = marker.color ?? theme.markerColor;
|
|
1058
|
-
const pointStyle = marker.pointStyle ?? (marker.value !== void 0 ? "circle" : "none");
|
|
1059
|
-
const lineStyle = marker.lineStyle ?? "full";
|
|
1060
|
-
if (marker.value !== void 0) {
|
|
1061
|
-
const y = valueScale.map(marker.value);
|
|
1062
|
-
if (lineStyle === "to-value") {
|
|
1063
|
-
commands.push({ type: "line", x1: x, y1: yBottom, x2: x, y2: y, stroke: color, strokeWidth: 1, dashed: true });
|
|
1064
|
-
} else if (lineStyle === "to-top") {
|
|
1065
|
-
commands.push({ type: "line", x1: x, y1: yTop, x2: x, y2: y, stroke: color, strokeWidth: 1, dashed: true });
|
|
1066
|
-
} else if (lineStyle === "full") {
|
|
1067
|
-
commands.push({ type: "line", x1: x, y1: yTop, x2: x, y2: yBottom, stroke: color, strokeWidth: 1 });
|
|
1068
|
-
}
|
|
1069
|
-
if (pointStyle !== "none") {
|
|
1070
|
-
drawMarkerPoint(commands, x, y, color, pointStyle);
|
|
1071
|
-
}
|
|
1072
|
-
if (marker.label) {
|
|
1073
|
-
const labelY = lineStyle === "to-value" ? y - 10 : yTop - 6;
|
|
1074
|
-
commands.push({ type: "text", content: marker.label, x, y: labelY, anchor: "middle", fontSize: 11, fill: color });
|
|
1075
|
-
}
|
|
1076
|
-
} else {
|
|
1077
|
-
commands.push({ type: "line", x1: x, y1: yTop, x2: x, y2: yBottom, stroke: color, strokeWidth: 1 });
|
|
1078
|
-
if (marker.label) {
|
|
1079
|
-
commands.push({ type: "text", content: marker.label, x, y: yTop - 6, anchor: "middle", fontSize: 11, fill: color });
|
|
1080
|
-
}
|
|
1081
|
-
}
|
|
1082
|
-
}
|
|
1083
|
-
return commands;
|
|
1084
|
-
}
|
|
1085
|
-
function drawMarkerPoint(commands, x, y, color, style) {
|
|
1086
|
-
const s = theme.markerSize;
|
|
1087
|
-
switch (style) {
|
|
1088
|
-
case "circle":
|
|
1089
|
-
commands.push({ type: "circle", cx: x, cy: y, r: s, fill: color });
|
|
1090
|
-
break;
|
|
1091
|
-
case "square":
|
|
1092
|
-
commands.push({ type: "rect", x: x - s, y: y - s, w: s * 2, h: s * 2, fill: color });
|
|
1093
|
-
break;
|
|
1094
|
-
case "cross":
|
|
1095
|
-
commands.push({ type: "line", x1: x - s, y1: y - s, x2: x + s, y2: y + s, stroke: color, strokeWidth: 2 });
|
|
1096
|
-
commands.push({ type: "line", x1: x - s, y1: y + s, x2: x + s, y2: y - s, stroke: color, strokeWidth: 2 });
|
|
1097
|
-
break;
|
|
1098
|
-
case "arrow":
|
|
1099
|
-
commands.push({
|
|
1100
|
-
type: "path",
|
|
1101
|
-
points: [{ x: x - s, y: y + s }, { x, y: y - s }, { x: x + s, y: y + s }],
|
|
1102
|
-
stroke: color,
|
|
1103
|
-
strokeWidth: 2,
|
|
1104
|
-
fill: "none"
|
|
1105
|
-
});
|
|
1106
|
-
break;
|
|
1107
|
-
case "diamond":
|
|
1108
|
-
commands.push({
|
|
1109
|
-
type: "path",
|
|
1110
|
-
points: [{ x, y: y - s }, { x: x + s, y }, { x, y: y + s }, { x: x - s, y }],
|
|
1111
|
-
fill: color,
|
|
1112
|
-
stroke: "none"
|
|
1113
|
-
});
|
|
1114
|
-
break;
|
|
1115
|
-
case "triangle":
|
|
1116
|
-
commands.push({
|
|
1117
|
-
type: "path",
|
|
1118
|
-
points: [{ x, y: y - s }, { x: x + s, y: y + s }, { x: x - s, y: y + s }],
|
|
1119
|
-
fill: color,
|
|
1120
|
-
stroke: "none"
|
|
1121
|
-
});
|
|
1122
|
-
break;
|
|
1123
|
-
case "star": {
|
|
1124
|
-
const pts = [];
|
|
1125
|
-
const innerRadius = s * 0.4;
|
|
1126
|
-
for (let i = 0; i < 10; i++) {
|
|
1127
|
-
const r = i % 2 === 0 ? s : innerRadius;
|
|
1128
|
-
const angle = Math.PI / 2 * 3 + i * Math.PI / 5;
|
|
1129
|
-
pts.push({ x: x + r * Math.cos(angle), y: y + r * Math.sin(angle) });
|
|
1130
|
-
}
|
|
1131
|
-
commands.push({ type: "path", points: pts, fill: color, stroke: "none" });
|
|
1132
|
-
break;
|
|
1133
|
-
}
|
|
1134
|
-
case "plus":
|
|
1135
|
-
commands.push({ type: "line", x1: x - s, y1: y, x2: x + s, y2: y, stroke: color, strokeWidth: 2 });
|
|
1136
|
-
commands.push({ type: "line", x1: x, y1: y - s, x2: x, y2: y + s, stroke: color, strokeWidth: 2 });
|
|
1137
|
-
break;
|
|
1138
|
-
case "triangle-down":
|
|
1139
|
-
commands.push({
|
|
1140
|
-
type: "path",
|
|
1141
|
-
points: [{ x, y: y + s }, { x: x + s, y: y - s }, { x: x - s, y: y - s }],
|
|
1142
|
-
fill: color,
|
|
1143
|
-
stroke: "none"
|
|
1144
|
-
});
|
|
1145
|
-
break;
|
|
1146
|
-
case "hexagon": {
|
|
1147
|
-
const pts = [];
|
|
1148
|
-
for (let i = 0; i < 6; i++) {
|
|
1149
|
-
const angle = i * (Math.PI / 3);
|
|
1150
|
-
pts.push({ x: x + s * Math.cos(angle), y: y + s * Math.sin(angle) });
|
|
1151
|
-
}
|
|
1152
|
-
commands.push({ type: "path", points: pts, fill: color, stroke: "none" });
|
|
1153
|
-
break;
|
|
1154
|
-
}
|
|
1155
|
-
case "hourglass":
|
|
1156
|
-
commands.push({
|
|
1157
|
-
type: "path",
|
|
1158
|
-
points: [{ x: x - s, y: y - s }, { x: x + s, y: y - s }, { x: x - s, y: y + s }, { x: x + s, y: y + s }],
|
|
1159
|
-
fill: color,
|
|
1160
|
-
stroke: "none"
|
|
1161
|
-
});
|
|
1162
|
-
break;
|
|
1163
|
-
case "line-horizontal":
|
|
1164
|
-
commands.push({ type: "line", x1: x - s, y1: y, x2: x + s, y2: y, stroke: color, strokeWidth: 2 });
|
|
1165
|
-
break;
|
|
1166
|
-
}
|
|
1167
|
-
}
|
|
1168
|
-
|
|
1169
|
-
// src/annotation/highlight.ts
|
|
1170
|
-
function renderHighlights(config) {
|
|
1171
|
-
const { highlights, timeScale, yRange, height } = config;
|
|
1172
|
-
const [y0, y1] = yRange;
|
|
1173
|
-
const commands = [];
|
|
1174
|
-
for (const h of highlights) {
|
|
1175
|
-
const x1 = timeScale.map(h.startTime);
|
|
1176
|
-
const x2 = timeScale.map(h.endTime);
|
|
1177
|
-
commands.push({
|
|
1178
|
-
type: "rect",
|
|
1179
|
-
x: x1,
|
|
1180
|
-
y: y0,
|
|
1181
|
-
w: x2 - x1,
|
|
1182
|
-
h: y1 - y0,
|
|
1183
|
-
fill: h.color ?? theme.highlightColor,
|
|
1184
|
-
opacity: h.opacity ?? theme.highlightOpacity
|
|
1185
|
-
});
|
|
1186
|
-
if (h.label) {
|
|
1187
|
-
commands.push({
|
|
1188
|
-
type: "text",
|
|
1189
|
-
content: h.label,
|
|
1190
|
-
x: (x1 + x2) / 2,
|
|
1191
|
-
y: highlightLabelY(h.labelPosition ?? "top", y0, y1, height),
|
|
1192
|
-
anchor: "middle",
|
|
1193
|
-
fontSize: theme.annotationFontSize,
|
|
1194
|
-
fill: h.color ?? theme.highlightLabelColor
|
|
1195
|
-
});
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
return commands;
|
|
1199
|
-
}
|
|
1200
|
-
function highlightLabelY(pos, y0, y1, height) {
|
|
1201
|
-
switch (pos) {
|
|
1202
|
-
case "above":
|
|
1203
|
-
return y0 - 5;
|
|
1204
|
-
case "below":
|
|
1205
|
-
return height !== void 0 ? height - 5 : y1 + 14;
|
|
1206
|
-
case "center":
|
|
1207
|
-
return (y0 + y1) / 2 + 4;
|
|
1208
|
-
case "bottom":
|
|
1209
|
-
return y1 - 6;
|
|
1210
|
-
case "top":
|
|
1211
|
-
default:
|
|
1212
|
-
return y0 + 14;
|
|
1213
|
-
}
|
|
1214
|
-
}
|
|
1215
|
-
|
|
1216
|
-
// src/annotation/annotation_renderer.ts
|
|
1217
|
-
function renderAnnotations(config) {
|
|
1218
|
-
const { annotations, timeScale, valueScales } = config;
|
|
1219
|
-
const cmds = [];
|
|
1220
|
-
const project = (ref) => {
|
|
1221
|
-
const scale = valueScales.get(ref.axis ?? 0) ?? valueScales.values().next().value;
|
|
1222
|
-
return { x: timeScale.map(ref.time), y: scale ? scale.map(ref.value) : 0 };
|
|
1223
|
-
};
|
|
1224
|
-
for (const a of annotations) {
|
|
1225
|
-
switch (a.type) {
|
|
1226
|
-
case "line": {
|
|
1227
|
-
const p1 = project(a.from);
|
|
1228
|
-
const p2 = project(a.to);
|
|
1229
|
-
cmds.push({ type: "line", x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, stroke: a.color ?? theme.annotationColor, strokeWidth: a.width ?? theme.annotationWidth, dash: a.dash });
|
|
1230
|
-
break;
|
|
1231
|
-
}
|
|
1232
|
-
case "arrow": {
|
|
1233
|
-
const p1 = project(a.from);
|
|
1234
|
-
const p2 = project(a.to);
|
|
1235
|
-
const color = a.color ?? theme.annotationColor;
|
|
1236
|
-
const h = a.headSize ?? theme.annotationHead;
|
|
1237
|
-
cmds.push({ type: "line", x1: p1.x, y1: p1.y, x2: p2.x, y2: p2.y, stroke: color, strokeWidth: a.width ?? theme.annotationWidth });
|
|
1238
|
-
const len = Math.hypot(p2.x - p1.x, p2.y - p1.y) || 1;
|
|
1239
|
-
const ux = (p2.x - p1.x) / len;
|
|
1240
|
-
const uy = (p2.y - p1.y) / len;
|
|
1241
|
-
const baseX = p2.x - ux * h;
|
|
1242
|
-
const baseY = p2.y - uy * h;
|
|
1243
|
-
cmds.push({
|
|
1244
|
-
type: "path",
|
|
1245
|
-
points: [
|
|
1246
|
-
{ x: p2.x, y: p2.y },
|
|
1247
|
-
{ x: baseX - uy * h * 0.5, y: baseY + ux * h * 0.5 },
|
|
1248
|
-
{ x: baseX + uy * h * 0.5, y: baseY - ux * h * 0.5 }
|
|
1249
|
-
],
|
|
1250
|
-
fill: color,
|
|
1251
|
-
stroke: "none"
|
|
1252
|
-
});
|
|
1253
|
-
break;
|
|
1254
|
-
}
|
|
1255
|
-
case "rect": {
|
|
1256
|
-
const p1 = project(a.from);
|
|
1257
|
-
const p2 = project(a.to);
|
|
1258
|
-
cmds.push({ type: "rect", x: Math.min(p1.x, p2.x), y: Math.min(p1.y, p2.y), w: Math.abs(p2.x - p1.x), h: Math.abs(p2.y - p1.y), fill: a.fill ?? "none", stroke: a.stroke, opacity: a.opacity });
|
|
1259
|
-
break;
|
|
1260
|
-
}
|
|
1261
|
-
case "point": {
|
|
1262
|
-
const p = project(a.at);
|
|
1263
|
-
const color = a.color ?? "#334155";
|
|
1264
|
-
const r = a.radius ?? theme.annotationRadius;
|
|
1265
|
-
const shape = a.shape ?? "circle";
|
|
1266
|
-
if (shape === "circle") {
|
|
1267
|
-
cmds.push({ type: "circle", cx: p.x, cy: p.y, r, fill: color });
|
|
1268
|
-
} else if (shape === "square") {
|
|
1269
|
-
cmds.push({ type: "rect", x: p.x - r, y: p.y - r, w: r * 2, h: r * 2, fill: color });
|
|
1270
|
-
} else {
|
|
1271
|
-
cmds.push({ type: "line", x1: p.x - r, y1: p.y - r, x2: p.x + r, y2: p.y + r, stroke: color, strokeWidth: 1.5 });
|
|
1272
|
-
cmds.push({ type: "line", x1: p.x - r, y1: p.y + r, x2: p.x + r, y2: p.y - r, stroke: color, strokeWidth: 1.5 });
|
|
1273
|
-
}
|
|
1274
|
-
break;
|
|
1275
|
-
}
|
|
1276
|
-
case "label": {
|
|
1277
|
-
const p = project(a.at);
|
|
1278
|
-
cmds.push({ type: "text", content: a.text, x: p.x + (a.dx ?? 0), y: p.y + (a.dy ?? 0), anchor: a.anchor ?? "middle", fontSize: theme.annotationFontSize, fill: a.color ?? theme.annotationColor, rotate: a.rotate });
|
|
1279
|
-
break;
|
|
1280
|
-
}
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
return cmds;
|
|
1284
|
-
}
|
|
1285
|
-
|
|
1286
|
-
// src/core/layout.ts
|
|
1287
|
-
var Layout = class _Layout {
|
|
1288
|
-
_config;
|
|
1289
|
-
constructor(config) {
|
|
1290
|
-
this._config = config;
|
|
1291
|
-
}
|
|
1292
|
-
/** Calculate layout dimensions */
|
|
1293
|
-
compute() {
|
|
1294
|
-
const { width, height, margin } = this._config;
|
|
1295
|
-
return {
|
|
1296
|
-
totalWidth: width,
|
|
1297
|
-
totalHeight: height,
|
|
1298
|
-
chartWidth: width - margin.left - margin.right,
|
|
1299
|
-
chartHeight: height - margin.top - margin.bottom,
|
|
1300
|
-
chartX: margin.left,
|
|
1301
|
-
chartY: margin.top,
|
|
1302
|
-
margin
|
|
1303
|
-
};
|
|
1304
|
-
}
|
|
1305
|
-
/** Default layout for standard charts */
|
|
1306
|
-
static default(width = 800, height = 400) {
|
|
1307
|
-
return new _Layout({
|
|
1308
|
-
width,
|
|
1309
|
-
height,
|
|
1310
|
-
margin: { top: 20, right: 20, bottom: 40, left: 60 }
|
|
1311
|
-
});
|
|
1312
|
-
}
|
|
1313
|
-
};
|
|
1314
|
-
|
|
1315
|
-
// src/core/clip.ts
|
|
1316
|
-
var Clip = class {
|
|
1317
|
-
#regions;
|
|
1318
|
-
constructor() {
|
|
1319
|
-
this.#regions = [];
|
|
1320
|
-
}
|
|
1321
|
-
/** Add a clipping region */
|
|
1322
|
-
add(region) {
|
|
1323
|
-
this.#regions.push(region);
|
|
1324
|
-
}
|
|
1325
|
-
/** Clear all clipping regions */
|
|
1326
|
-
clear() {
|
|
1327
|
-
this.#regions = [];
|
|
1328
|
-
}
|
|
1329
|
-
/** Check if a point is within the clipping regions */
|
|
1330
|
-
isInside(x, y) {
|
|
1331
|
-
if (this.#regions.length === 0) return true;
|
|
1332
|
-
return this.#regions.some(
|
|
1333
|
-
(r) => x >= r.x && x <= r.x + r.width && y >= r.y && y <= r.y + r.height
|
|
1334
|
-
);
|
|
1335
|
-
}
|
|
1336
|
-
/** Generate SVG clipPath element */
|
|
1337
|
-
toSVGClipPath(id = "clip") {
|
|
1338
|
-
if (this.#regions.length === 0) return "";
|
|
1339
|
-
const rects = this.#regions.map((r) => `<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" />`).join("\n ");
|
|
1340
|
-
return `<clipPath id="${id}">
|
|
1341
|
-
${rects}
|
|
1342
|
-
</clipPath>`;
|
|
1343
|
-
}
|
|
1344
|
-
/** Get current clipping regions */
|
|
1345
|
-
get regions() {
|
|
1346
|
-
return [...this.#regions];
|
|
1347
|
-
}
|
|
1348
|
-
};
|
|
1349
|
-
|
|
1350
|
-
// src/axis/time_axis.ts
|
|
1351
|
-
var DEFAULT_COLORS = {
|
|
1352
|
-
axisColor: theme.axisColor,
|
|
1353
|
-
tickColor: theme.tickColor,
|
|
1354
|
-
textColor: theme.textColor,
|
|
1355
|
-
textSize: theme.textSize
|
|
1356
|
-
};
|
|
1357
|
-
var TimeAxis = class {
|
|
1358
|
-
#scale;
|
|
1359
|
-
#config;
|
|
1360
|
-
constructor(config) {
|
|
1361
|
-
this.#scale = new TimeScale({
|
|
1362
|
-
domain: config.domain,
|
|
1363
|
-
range: config.xRange,
|
|
1364
|
-
locale: config.locale
|
|
1365
|
-
});
|
|
1366
|
-
this.#config = config;
|
|
1367
|
-
}
|
|
1368
|
-
get scale() {
|
|
1369
|
-
return this.#scale;
|
|
1370
|
-
}
|
|
1371
|
-
get axisColor() {
|
|
1372
|
-
return (this.#config.colors ?? DEFAULT_COLORS).axisColor;
|
|
1373
|
-
}
|
|
1374
|
-
get tickColor() {
|
|
1375
|
-
return (this.#config.colors ?? DEFAULT_COLORS).tickColor;
|
|
1376
|
-
}
|
|
1377
|
-
get textColor() {
|
|
1378
|
-
return (this.#config.colors ?? DEFAULT_COLORS).textColor;
|
|
1379
|
-
}
|
|
1380
|
-
get textSize() {
|
|
1381
|
-
return (this.#config.colors ?? DEFAULT_COLORS).textSize;
|
|
1382
|
-
}
|
|
1383
|
-
/**
|
|
1384
|
-
* Generate properly spaced, formatted ticks for the time axis.
|
|
1385
|
-
* Applies anti-overlap: if ticks are too close, every-other is skipped.
|
|
1386
|
-
*/
|
|
1387
|
-
generateTicks() {
|
|
1388
|
-
const minTicks = this.#config.minTicks ?? 5;
|
|
1389
|
-
const maxTicks = this.#config.maxTicks ?? 12;
|
|
1390
|
-
const timestamps = this.#scale.ticks({ minTicks, maxTicks });
|
|
1391
|
-
const ticks = timestamps.map((time) => ({
|
|
1392
|
-
time,
|
|
1393
|
-
x: this.#scale.map(time),
|
|
1394
|
-
label: this.tickLabel(time)
|
|
1395
|
-
}));
|
|
1396
|
-
return this.antiOverlap(ticks);
|
|
1397
|
-
}
|
|
1398
|
-
/** Pick the right date format based on the tick interval. */
|
|
1399
|
-
tickLabel(time) {
|
|
1400
|
-
if (this.#config.format) return this.#config.format(new Date(time));
|
|
1401
|
-
const minTicks = this.#config.minTicks ?? 5;
|
|
1402
|
-
const maxTicks = this.#config.maxTicks ?? 12;
|
|
1403
|
-
const { interval } = this.#scale.tickInterval(
|
|
1404
|
-
(minTicks + maxTicks) / 2,
|
|
1405
|
-
minTicks,
|
|
1406
|
-
maxTicks
|
|
1407
|
-
);
|
|
1408
|
-
const opts = {};
|
|
1409
|
-
if (interval < 6e4) {
|
|
1410
|
-
opts.hour = "2-digit";
|
|
1411
|
-
opts.minute = "2-digit";
|
|
1412
|
-
opts.second = "2-digit";
|
|
1413
|
-
} else if (interval < 36e5) {
|
|
1414
|
-
opts.hour = "2-digit";
|
|
1415
|
-
opts.minute = "2-digit";
|
|
1416
|
-
} else if (interval < 864e5) {
|
|
1417
|
-
opts.hour = "2-digit";
|
|
1418
|
-
opts.minute = "2-digit";
|
|
1419
|
-
} else if (interval < 31536e6) {
|
|
1420
|
-
opts.day = "numeric";
|
|
1421
|
-
opts.month = "short";
|
|
1422
|
-
if (interval >= 2592e6) {
|
|
1423
|
-
opts.day = void 0;
|
|
1424
|
-
opts.month = "long";
|
|
1425
|
-
}
|
|
1426
|
-
} else {
|
|
1427
|
-
opts.year = "numeric";
|
|
1428
|
-
if (interval < 2 * 31536e6) opts.month = "short";
|
|
1429
|
-
}
|
|
1430
|
-
return this.#scale.format(time, opts);
|
|
1431
|
-
}
|
|
1432
|
-
/** Remove ticks that would overlap (minimum 60px spacing). */
|
|
1433
|
-
antiOverlap(ticks) {
|
|
1434
|
-
if (ticks.length <= 1) return ticks;
|
|
1435
|
-
const minGap = 60;
|
|
1436
|
-
const result = [ticks[0]];
|
|
1437
|
-
for (let i = 1; i < ticks.length; i++) {
|
|
1438
|
-
const lastX = result[result.length - 1].x;
|
|
1439
|
-
if (Math.abs(ticks[i].x - lastX) >= minGap) {
|
|
1440
|
-
result.push(ticks[i]);
|
|
1441
|
-
}
|
|
1442
|
-
}
|
|
1443
|
-
return result;
|
|
1444
|
-
}
|
|
1445
|
-
/** Render axis baseline + tick marks as draw commands. */
|
|
1446
|
-
render() {
|
|
1447
|
-
const ticks = this.generateTicks();
|
|
1448
|
-
const colors = this.#config.colors ?? DEFAULT_COLORS;
|
|
1449
|
-
const y = this.#config.y ?? 0;
|
|
1450
|
-
const commands = [];
|
|
1451
|
-
const [x0] = this.#scale.range();
|
|
1452
|
-
commands.push({
|
|
1453
|
-
type: "line",
|
|
1454
|
-
x1: x0,
|
|
1455
|
-
y1: y,
|
|
1456
|
-
x2: ticks[ticks.length - 1]?.x ?? x0,
|
|
1457
|
-
y2: y,
|
|
1458
|
-
stroke: colors.axisColor,
|
|
1459
|
-
strokeWidth: colors.axisWidth
|
|
1460
|
-
});
|
|
1461
|
-
for (const tick of ticks) {
|
|
1462
|
-
commands.push({
|
|
1463
|
-
type: "line",
|
|
1464
|
-
x1: tick.x,
|
|
1465
|
-
y1: y,
|
|
1466
|
-
x2: tick.x,
|
|
1467
|
-
y2: y + 6,
|
|
1468
|
-
stroke: colors.tickColor,
|
|
1469
|
-
strokeWidth: colors.axisWidth
|
|
1470
|
-
});
|
|
1471
|
-
commands.push({
|
|
1472
|
-
type: "text",
|
|
1473
|
-
content: tick.label,
|
|
1474
|
-
x: tick.x,
|
|
1475
|
-
y: y + colors.textSize + 6,
|
|
1476
|
-
anchor: "middle",
|
|
1477
|
-
fontSize: colors.textSize,
|
|
1478
|
-
fill: colors.textColor
|
|
1479
|
-
});
|
|
1480
|
-
}
|
|
1481
|
-
return commands;
|
|
1482
|
-
}
|
|
1483
|
-
};
|
|
1484
|
-
|
|
1485
|
-
// src/axis/value_axis.ts
|
|
1486
|
-
var DEFAULT_COLORS2 = {
|
|
1487
|
-
axisColor: "#ccc",
|
|
1488
|
-
tickColor: "#ddd",
|
|
1489
|
-
textColor: "#777",
|
|
1490
|
-
textSize: 12
|
|
1491
|
-
};
|
|
1492
|
-
function defaultFormat(value) {
|
|
1493
|
-
if (Math.abs(value) >= 1e6) return `${(value / 1e6).toFixed(1)}M`;
|
|
1494
|
-
if (Math.abs(value) >= 1e3) return `${(value / 1e3).toFixed(1)}k`;
|
|
1495
|
-
if (Number.isInteger(value)) return String(value);
|
|
1496
|
-
return value.toFixed(1);
|
|
1497
|
-
}
|
|
1498
|
-
var ValueAxis = class {
|
|
1499
|
-
#scale;
|
|
1500
|
-
#config;
|
|
1501
|
-
constructor(config) {
|
|
1502
|
-
this.#scale = new LinearScale({ domain: config.domain, range: config.range });
|
|
1503
|
-
this.#config = config;
|
|
1504
|
-
}
|
|
1505
|
-
get scale() {
|
|
1506
|
-
return this.#scale;
|
|
1507
|
-
}
|
|
1508
|
-
get axisColor() {
|
|
1509
|
-
return (this.#config.colors ?? DEFAULT_COLORS2).axisColor;
|
|
1510
|
-
}
|
|
1511
|
-
get tickColor() {
|
|
1512
|
-
return (this.#config.colors ?? DEFAULT_COLORS2).tickColor;
|
|
1513
|
-
}
|
|
1514
|
-
get textColor() {
|
|
1515
|
-
return (this.#config.colors ?? DEFAULT_COLORS2).textColor;
|
|
1516
|
-
}
|
|
1517
|
-
get textSize() {
|
|
1518
|
-
return (this.#config.colors ?? DEFAULT_COLORS2).textSize;
|
|
1519
|
-
}
|
|
1520
|
-
/** Generate nicely-spaced tick values. */
|
|
1521
|
-
generateTicks() {
|
|
1522
|
-
const format = this.#config.format ?? defaultFormat;
|
|
1523
|
-
const numTicks = 6;
|
|
1524
|
-
const [d0, d1] = this.#scale.domain();
|
|
1525
|
-
const range = d1 - d0;
|
|
1526
|
-
if (range === 0) {
|
|
1527
|
-
return [{ value: d0, position: this.#scale.map(d0), label: format(d0) }];
|
|
1528
|
-
}
|
|
1529
|
-
const rough = range / numTicks;
|
|
1530
|
-
const magnitude = Math.pow(10, Math.floor(Math.log10(rough)));
|
|
1531
|
-
const residual = rough / magnitude;
|
|
1532
|
-
let step;
|
|
1533
|
-
if (residual <= 1.5) step = magnitude;
|
|
1534
|
-
else if (residual <= 3) step = 2 * magnitude;
|
|
1535
|
-
else if (residual <= 7) step = 5 * magnitude;
|
|
1536
|
-
else step = 10 * magnitude;
|
|
1537
|
-
const ticks = [];
|
|
1538
|
-
const start = Math.ceil(d0 / step) * step;
|
|
1539
|
-
for (let v = start; v <= d1; v += step) {
|
|
1540
|
-
ticks.push({ value: v, position: this.#scale.map(v), label: format(v) });
|
|
1541
|
-
}
|
|
1542
|
-
return ticks;
|
|
1543
|
-
}
|
|
1544
|
-
/** Render axis as draw commands. */
|
|
1545
|
-
render() {
|
|
1546
|
-
const ticks = this.generateTicks();
|
|
1547
|
-
const colors = this.#config.colors ?? DEFAULT_COLORS2;
|
|
1548
|
-
const x = this.#config.x ?? 0;
|
|
1549
|
-
const orientation = this.#config.orientation ?? "vertical";
|
|
1550
|
-
const position = this.#config.position ?? "left";
|
|
1551
|
-
const commands = [];
|
|
1552
|
-
if (orientation === "vertical") {
|
|
1553
|
-
const [r0, r1] = this.#scale.range();
|
|
1554
|
-
commands.push({
|
|
1555
|
-
type: "line",
|
|
1556
|
-
x1: x,
|
|
1557
|
-
y1: r0,
|
|
1558
|
-
x2: x,
|
|
1559
|
-
y2: r1,
|
|
1560
|
-
stroke: colors.axisColor,
|
|
1561
|
-
strokeWidth: colors.axisWidth
|
|
1562
|
-
});
|
|
1563
|
-
for (const tick of ticks) {
|
|
1564
|
-
if (position === "left") {
|
|
1565
|
-
commands.push({
|
|
1566
|
-
type: "line",
|
|
1567
|
-
x1: x - 4,
|
|
1568
|
-
y1: tick.position,
|
|
1569
|
-
x2: x,
|
|
1570
|
-
y2: tick.position,
|
|
1571
|
-
stroke: colors.tickColor,
|
|
1572
|
-
strokeWidth: colors.axisWidth
|
|
1573
|
-
});
|
|
1574
|
-
commands.push({
|
|
1575
|
-
type: "text",
|
|
1576
|
-
content: tick.label,
|
|
1577
|
-
x: x - 8,
|
|
1578
|
-
y: tick.position + 4,
|
|
1579
|
-
anchor: "end",
|
|
1580
|
-
fontSize: 11,
|
|
1581
|
-
fill: colors.textColor
|
|
1582
|
-
});
|
|
1583
|
-
} else {
|
|
1584
|
-
commands.push({
|
|
1585
|
-
type: "line",
|
|
1586
|
-
x1: x,
|
|
1587
|
-
y1: tick.position,
|
|
1588
|
-
x2: x + 4,
|
|
1589
|
-
y2: tick.position,
|
|
1590
|
-
stroke: colors.tickColor,
|
|
1591
|
-
strokeWidth: colors.axisWidth
|
|
1592
|
-
});
|
|
1593
|
-
commands.push({
|
|
1594
|
-
type: "text",
|
|
1595
|
-
content: tick.label,
|
|
1596
|
-
x: x + 8,
|
|
1597
|
-
y: tick.position + 4,
|
|
1598
|
-
anchor: "start",
|
|
1599
|
-
fontSize: 11,
|
|
1600
|
-
fill: colors.textColor
|
|
1601
|
-
});
|
|
1602
|
-
}
|
|
1603
|
-
}
|
|
1604
|
-
} else {
|
|
1605
|
-
const [r0, r1] = this.#scale.range();
|
|
1606
|
-
commands.push({
|
|
1607
|
-
type: "line",
|
|
1608
|
-
x1: r0,
|
|
1609
|
-
y1: x,
|
|
1610
|
-
x2: r1,
|
|
1611
|
-
y2: x,
|
|
1612
|
-
stroke: colors.axisColor,
|
|
1613
|
-
strokeWidth: colors.axisWidth
|
|
1614
|
-
});
|
|
1615
|
-
for (const tick of ticks) {
|
|
1616
|
-
commands.push({
|
|
1617
|
-
type: "line",
|
|
1618
|
-
x1: tick.position,
|
|
1619
|
-
y1: x,
|
|
1620
|
-
x2: tick.position,
|
|
1621
|
-
y2: x + 6,
|
|
1622
|
-
stroke: colors.tickColor,
|
|
1623
|
-
strokeWidth: colors.axisWidth
|
|
1624
|
-
});
|
|
1625
|
-
commands.push({
|
|
1626
|
-
type: "text",
|
|
1627
|
-
content: tick.label,
|
|
1628
|
-
x: tick.position,
|
|
1629
|
-
y: x + 18,
|
|
1630
|
-
anchor: "middle",
|
|
1631
|
-
fontSize: colors.textSize,
|
|
1632
|
-
fill: colors.textColor
|
|
1633
|
-
});
|
|
1634
|
-
}
|
|
1635
|
-
}
|
|
1636
|
-
return commands;
|
|
1637
|
-
}
|
|
1638
|
-
};
|
|
1639
|
-
|
|
1640
|
-
// src/axis/enum_axis.ts
|
|
1641
|
-
var EnumAxis = class {
|
|
1642
|
-
#scale;
|
|
1643
|
-
/** All enum keys (as strings), sorted numerically — the full category list. */
|
|
1644
|
-
#keys;
|
|
1645
|
-
#config;
|
|
1646
|
-
constructor(config) {
|
|
1647
|
-
const domain = Object.keys(config.enumMap).map(Number).sort((a, b) => a - b).map(String);
|
|
1648
|
-
this.#keys = domain;
|
|
1649
|
-
this.#scale = new BandScale({ domain, range: config.range });
|
|
1650
|
-
this.#config = config;
|
|
1651
|
-
}
|
|
1652
|
-
get scale() {
|
|
1653
|
-
return this.#scale;
|
|
1654
|
-
}
|
|
1655
|
-
/** Get all enum ticks with labels, positions, colors. */
|
|
1656
|
-
generateTicks() {
|
|
1657
|
-
const { enumMap, showLabels = true, autoColor = true, gapValues } = this.#config;
|
|
1658
|
-
const gaps = new Set(gapValues ?? []);
|
|
1659
|
-
return this.#keys.map((key, idx) => {
|
|
1660
|
-
const value = Number(key);
|
|
1661
|
-
const label = showLabels ? enumMap[value] ?? String(value) : String(value);
|
|
1662
|
-
const y = this.#scale.map(value);
|
|
1663
|
-
const color = autoColor ? theme.palette[idx % theme.palette.length] : void 0;
|
|
1664
|
-
return { value, label, y, color, isGap: gaps.has(value) };
|
|
1665
|
-
});
|
|
1666
|
-
}
|
|
1667
|
-
/** Get color for a specific enum value. */
|
|
1668
|
-
colorFor(value) {
|
|
1669
|
-
const idx = this.#keys.indexOf(String(value));
|
|
1670
|
-
if (idx === -1 || !(this.#config.autoColor ?? true)) return void 0;
|
|
1671
|
-
return theme.palette[idx % theme.palette.length];
|
|
1672
|
-
}
|
|
1673
|
-
/** Render enum axis labels as draw commands. */
|
|
1674
|
-
render() {
|
|
1675
|
-
const ticks = this.generateTicks();
|
|
1676
|
-
const x = this.#config.x ?? 0;
|
|
1677
|
-
const commands = [];
|
|
1678
|
-
for (const tick of ticks) {
|
|
1679
|
-
if (tick.isGap) continue;
|
|
1680
|
-
commands.push({
|
|
1681
|
-
type: "text",
|
|
1682
|
-
content: tick.label,
|
|
1683
|
-
x,
|
|
1684
|
-
y: tick.y + 4,
|
|
1685
|
-
anchor: "end",
|
|
1686
|
-
fontSize: 11,
|
|
1687
|
-
fill: tick.color ?? theme.textColor
|
|
1688
|
-
});
|
|
1689
|
-
}
|
|
1690
|
-
return commands;
|
|
1691
|
-
}
|
|
1692
|
-
/** Map an enum value to its pixel position. */
|
|
1693
|
-
map(value) {
|
|
1694
|
-
return this.#scale.map(value);
|
|
1695
|
-
}
|
|
1696
|
-
/** Check if a value should be treated as a gap. */
|
|
1697
|
-
isGap(value) {
|
|
1698
|
-
return new Set(this.#config.gapValues ?? []).has(value);
|
|
1699
|
-
}
|
|
1700
|
-
};
|
|
1701
|
-
|
|
1702
|
-
// src/series/series.ts
|
|
1703
|
-
var Series = class _Series {
|
|
1704
|
-
/** SVG id prefix for elements. Generated ids: `<id>-slot-<index>`, `<id>-avg-<index>`. */
|
|
1705
|
-
#id;
|
|
1706
|
-
#timeScale;
|
|
1707
|
-
static uidcnt = 0;
|
|
1708
|
-
#data;
|
|
1709
|
-
constructor(config, data = []) {
|
|
1710
|
-
this.#id = config.id ?? "id" + Date.now + ++_Series.uidcnt;
|
|
1711
|
-
this.#timeScale = config.timeScale;
|
|
1712
|
-
this.#data = data;
|
|
1713
|
-
}
|
|
1714
|
-
get id() {
|
|
1715
|
-
return this.#id;
|
|
1716
|
-
}
|
|
1717
|
-
get timeScale() {
|
|
1718
|
-
return this.#timeScale;
|
|
1719
|
-
}
|
|
1720
|
-
get data() {
|
|
1721
|
-
return this.#data;
|
|
1722
|
-
}
|
|
1723
|
-
};
|
|
1724
|
-
|
|
1725
|
-
// src/series/line_series.ts
|
|
1726
|
-
var LineSeries = class extends Series {
|
|
1727
|
-
#config;
|
|
1728
|
-
constructor(config) {
|
|
1729
|
-
super(config, config.data);
|
|
1730
|
-
this.#config = config;
|
|
1731
|
-
}
|
|
1732
|
-
/** Convert non-null data points to pixel coordinates (sorted by X). */
|
|
1733
|
-
points() {
|
|
1734
|
-
const valueScale = this.#config.valueScale;
|
|
1735
|
-
return this.data.filter((dp) => dp.value !== null).map((dp) => ({
|
|
1736
|
-
x: this.timeScale.map(dp.time),
|
|
1737
|
-
y: valueScale.map(dp.value),
|
|
1738
|
-
time: dp.time
|
|
1739
|
-
})).sort((a, b) => a.x - b.x);
|
|
1740
|
-
}
|
|
1741
|
-
render() {
|
|
1742
|
-
const c = this.#config;
|
|
1743
|
-
const style = {
|
|
1744
|
-
smoothing: c.smoothing ?? false,
|
|
1745
|
-
stroke: c.stroke ?? theme.stroke,
|
|
1746
|
-
strokeWidth: c.strokeWidth ?? theme.strokeWidth,
|
|
1747
|
-
dashed: c.dashed ?? false,
|
|
1748
|
-
pointStyle: c.pointStyle ?? "none",
|
|
1749
|
-
pointSize: c.pointSize ?? theme.pointSize,
|
|
1750
|
-
shadowColor: c.shadowColor,
|
|
1751
|
-
shadowBlur: c.shadowBlur,
|
|
1752
|
-
shadowOffsetX: c.shadowOffsetX,
|
|
1753
|
-
shadowOffsetY: c.shadowOffsetY
|
|
1754
|
-
};
|
|
1755
|
-
const pointThreshold = c.pointThreshold ?? theme.pointThreshold;
|
|
1756
|
-
const gapThreshold = c.gapThreshold ?? theme.gapThreshold;
|
|
1757
|
-
const id = c.id;
|
|
1758
|
-
const runs = SeriesProcessor.getRuns(
|
|
1759
|
-
this.data,
|
|
1760
|
-
(p) => p.value === null,
|
|
1761
|
-
gapThreshold
|
|
1762
|
-
);
|
|
1763
|
-
const totalPoints = runs.reduce((sum, run) => sum + run.length, 0);
|
|
1764
|
-
if (totalPoints === 0) return [];
|
|
1765
|
-
const ctx = { timeScale: this.timeScale, valueScale: c.valueScale };
|
|
1766
|
-
const segments = runs.map((run) => ({ data: run }));
|
|
1767
|
-
const commands = [];
|
|
1768
|
-
commands.push(
|
|
1769
|
-
...renderLine(segments, ctx, {
|
|
1770
|
-
...style,
|
|
1771
|
-
id: id ? `${id}-line` : void 0
|
|
1772
|
-
})
|
|
1773
|
-
);
|
|
1774
|
-
if (style.pointStyle && style.pointStyle !== "none" && totalPoints <= pointThreshold) {
|
|
1775
|
-
for (const run of runs) {
|
|
1776
|
-
commands.push(
|
|
1777
|
-
...renderMarkers(
|
|
1778
|
-
run,
|
|
1779
|
-
ctx,
|
|
1780
|
-
{ ...style, id: id ? `${id}-marker` : void 0 },
|
|
1781
|
-
() => style.stroke
|
|
1782
|
-
)
|
|
1783
|
-
);
|
|
1784
|
-
}
|
|
1785
|
-
}
|
|
1786
|
-
return commands;
|
|
1787
|
-
}
|
|
1788
|
-
};
|
|
1789
|
-
|
|
1790
|
-
// src/series/step_series.ts
|
|
1791
|
-
var StepSeries = class extends Series {
|
|
1792
|
-
#config;
|
|
1793
|
-
constructor(config) {
|
|
1794
|
-
super(config, config.data);
|
|
1795
|
-
this.#config = config;
|
|
1796
|
-
}
|
|
1797
|
-
/**
|
|
1798
|
-
* Convert (non-null) data points to step-style pixel coordinates.
|
|
1799
|
-
* Each data point generates two corners (horizontal then vertical).
|
|
1800
|
-
*/
|
|
1801
|
-
points() {
|
|
1802
|
-
const valueScale = this.#config.valueScale;
|
|
1803
|
-
const sorted = [...this.data].filter((p) => p.value !== null).sort((a, b) => a.time - b.time);
|
|
1804
|
-
const pts = [];
|
|
1805
|
-
for (let i = 0; i < sorted.length; i++) {
|
|
1806
|
-
const px = this.timeScale.map(sorted[i].time);
|
|
1807
|
-
const py = valueScale.map(sorted[i].value);
|
|
1808
|
-
if (i === 0) {
|
|
1809
|
-
pts.push({ x: px, y: py });
|
|
1810
|
-
} else {
|
|
1811
|
-
pts.push({ x: px, y: pts[pts.length - 1].y });
|
|
1812
|
-
pts.push({ x: px, y: py });
|
|
1813
|
-
}
|
|
1814
|
-
}
|
|
1815
|
-
return pts;
|
|
1816
|
-
}
|
|
1817
|
-
render() {
|
|
1818
|
-
const runs = SeriesProcessor.getRuns(this.data, (p) => p.value === null);
|
|
1819
|
-
if (runs.length === 0) return [];
|
|
1820
|
-
const ctx = { timeScale: this.timeScale, valueScale: this.#config.valueScale };
|
|
1821
|
-
const segments = runs.map((run) => ({ data: run }));
|
|
1822
|
-
return renderStep(segments, ctx, {
|
|
1823
|
-
stroke: this.#config.stroke ?? theme.stroke,
|
|
1824
|
-
strokeWidth: this.#config.strokeWidth ?? theme.strokeWidth,
|
|
1825
|
-
id: this.id ? `${this.id}-line` : void 0
|
|
1826
|
-
});
|
|
1827
|
-
}
|
|
1828
|
-
};
|
|
1829
|
-
|
|
1830
|
-
// src/series/band_series.ts
|
|
1831
|
-
var BandSeries = class extends Series {
|
|
1832
|
-
#config;
|
|
1833
|
-
constructor(config) {
|
|
1834
|
-
super(config, config.data);
|
|
1835
|
-
this.#config = config;
|
|
1836
|
-
}
|
|
1837
|
-
/** Calculate opacity from count (normalized 0.2-1.0) */
|
|
1838
|
-
opacity(count) {
|
|
1839
|
-
if (!(this.#config.countOpacity ?? false)) return 0.6;
|
|
1840
|
-
const maxCount = Math.max(...this.data.map((d) => d.count));
|
|
1841
|
-
if (maxCount === 0) return 0.2;
|
|
1842
|
-
return 0.2 + 0.8 * count / maxCount;
|
|
1843
|
-
}
|
|
1844
|
-
/** Render bands as draw commands */
|
|
1845
|
-
render() {
|
|
1846
|
-
const c = this.#config;
|
|
1847
|
-
const fill = c.fill ?? theme.bandFill;
|
|
1848
|
-
const hatch = c.hatch;
|
|
1849
|
-
const avgLine = c.avgLine ?? false;
|
|
1850
|
-
const avgLineColor = c.avgLineColor ?? theme.bandAvgLine;
|
|
1851
|
-
const bandWidth = c.bandWidth ?? 10;
|
|
1852
|
-
const commands = [];
|
|
1853
|
-
for (const dp of this.data) {
|
|
1854
|
-
if (dp.min === null || dp.max === null) continue;
|
|
1855
|
-
const x = this.timeScale.map(dp.time);
|
|
1856
|
-
const yMin = c.valueScale.map(dp.max);
|
|
1857
|
-
const yMax = c.valueScale.map(dp.min);
|
|
1858
|
-
const w = bandWidth;
|
|
1859
|
-
const idx = this.data.indexOf(dp);
|
|
1860
|
-
commands.push({
|
|
1861
|
-
type: "rect",
|
|
1862
|
-
x: x - w / 2,
|
|
1863
|
-
y: yMin,
|
|
1864
|
-
w,
|
|
1865
|
-
h: yMax - yMin,
|
|
1866
|
-
fill,
|
|
1867
|
-
hatch,
|
|
1868
|
-
opacity: this.opacity(dp.count),
|
|
1869
|
-
id: this.id ? `${this.id}-slot-${idx}` : void 0
|
|
1870
|
-
});
|
|
1871
|
-
if (avgLine && dp.avg !== null) {
|
|
1872
|
-
const yAvg = c.valueScale.map(dp.avg);
|
|
1873
|
-
commands.push({
|
|
1874
|
-
type: "line",
|
|
1875
|
-
x1: x - w / 2,
|
|
1876
|
-
y1: yAvg,
|
|
1877
|
-
x2: x + w / 2,
|
|
1878
|
-
y2: yAvg,
|
|
1879
|
-
stroke: avgLineColor,
|
|
1880
|
-
strokeWidth: 1,
|
|
1881
|
-
id: this.id ? `${this.id}-avg-${idx}` : void 0
|
|
1882
|
-
});
|
|
1883
|
-
}
|
|
1884
|
-
}
|
|
1885
|
-
return commands;
|
|
1886
|
-
}
|
|
1887
|
-
};
|
|
1888
|
-
|
|
1889
|
-
// src/series/minmaxavg_series.ts
|
|
1890
|
-
var MinMaxAvgSeries = class extends Series {
|
|
1891
|
-
#config;
|
|
1892
|
-
constructor(config) {
|
|
1893
|
-
super(config, config.data);
|
|
1894
|
-
this.#config = config;
|
|
1895
|
-
}
|
|
1896
|
-
render() {
|
|
1897
|
-
const c = this.#config;
|
|
1898
|
-
const minColor = c.minColor ?? theme.minColor;
|
|
1899
|
-
const maxColor = c.maxColor ?? theme.maxColor;
|
|
1900
|
-
const avgColor = c.avgColor ?? theme.avgColor;
|
|
1901
|
-
const avgDashed = c.avgDashed ?? true;
|
|
1902
|
-
const smoothing = c.smoothing ?? false;
|
|
1903
|
-
const strokeWidth = c.strokeWidth ?? theme.strokeWidth;
|
|
1904
|
-
const runs = SeriesProcessor.getRuns(
|
|
1905
|
-
this.data,
|
|
1906
|
-
(p) => p.min === null || p.max === null || p.avg === null
|
|
1907
|
-
);
|
|
1908
|
-
if (runs.length === 0) return [];
|
|
1909
|
-
const ctx = { timeScale: this.timeScale, valueScale: c.valueScale };
|
|
1910
|
-
const commands = [];
|
|
1911
|
-
for (const run of runs) {
|
|
1912
|
-
if (run.length < 2) continue;
|
|
1913
|
-
if (c.fillToMax) {
|
|
1914
|
-
commands.push(
|
|
1915
|
-
...renderZonedArea(
|
|
1916
|
-
run,
|
|
1917
|
-
ctx,
|
|
1918
|
-
{
|
|
1919
|
-
boundaries: [],
|
|
1920
|
-
yLow: (p) => p.avg,
|
|
1921
|
-
yHigh: (p) => p.max,
|
|
1922
|
-
getValue: (p) => p.avg,
|
|
1923
|
-
interpolate: SeriesProcessor.interpolateAggregatedPoint,
|
|
1924
|
-
getColor: () => c.fillToMax,
|
|
1925
|
-
getHatch: () => c.fillToMaxHatch
|
|
1926
|
-
},
|
|
1927
|
-
{ id: this.id ? `${this.id}-fillToMax` : void 0 }
|
|
1928
|
-
)
|
|
1929
|
-
);
|
|
1930
|
-
}
|
|
1931
|
-
if (c.fillToMin) {
|
|
1932
|
-
commands.push(
|
|
1933
|
-
...renderZonedArea(
|
|
1934
|
-
run,
|
|
1935
|
-
ctx,
|
|
1936
|
-
{
|
|
1937
|
-
boundaries: [],
|
|
1938
|
-
yLow: (p) => p.avg,
|
|
1939
|
-
yHigh: (p) => p.min,
|
|
1940
|
-
getValue: (p) => p.avg,
|
|
1941
|
-
interpolate: SeriesProcessor.interpolateAggregatedPoint,
|
|
1942
|
-
getColor: () => c.fillToMin,
|
|
1943
|
-
getHatch: () => c.fillToMinHatch
|
|
1944
|
-
},
|
|
1945
|
-
{ id: this.id ? `${this.id}-fillToMin` : void 0 }
|
|
1946
|
-
)
|
|
1947
|
-
);
|
|
1948
|
-
}
|
|
1949
|
-
commands.push(
|
|
1950
|
-
...renderLine(
|
|
1951
|
-
[{ data: run.map((p) => ({ time: p.time, value: p.max })) }],
|
|
1952
|
-
ctx,
|
|
1953
|
-
{ stroke: maxColor, strokeWidth, smoothing, id: this.id ? `${this.id}-max` : void 0 }
|
|
1954
|
-
),
|
|
1955
|
-
...renderLine(
|
|
1956
|
-
[{ data: run.map((p) => ({ time: p.time, value: p.min })) }],
|
|
1957
|
-
ctx,
|
|
1958
|
-
{ stroke: minColor, strokeWidth, smoothing, id: this.id ? `${this.id}-min` : void 0 }
|
|
1959
|
-
),
|
|
1960
|
-
...renderLine(
|
|
1961
|
-
[{ data: run.map((p) => ({ time: p.time, value: p.avg })) }],
|
|
1962
|
-
ctx,
|
|
1963
|
-
{ stroke: avgColor, strokeWidth, smoothing, dashed: avgDashed, id: this.id ? `${this.id}-avg` : void 0 }
|
|
1964
|
-
)
|
|
1965
|
-
);
|
|
1966
|
-
}
|
|
1967
|
-
return commands;
|
|
1968
|
-
}
|
|
1969
|
-
};
|
|
1970
|
-
|
|
1971
|
-
// src/series/zoned_line_series.ts
|
|
1972
|
-
var ZonedLineSeries = class extends Series {
|
|
1973
|
-
#config;
|
|
1974
|
-
#sortedZones;
|
|
1975
|
-
constructor(config) {
|
|
1976
|
-
super(config, config.data);
|
|
1977
|
-
this.#config = config;
|
|
1978
|
-
this.#sortedZones = [...config.zones ?? []].sort((a, b) => a.value - b.value);
|
|
1979
|
-
}
|
|
1980
|
-
/** Colour for a given zone index (0 = base, 1..N = zones). */
|
|
1981
|
-
#colorAtZone(zoneIndex) {
|
|
1982
|
-
if (zoneIndex === 0) return this.#config.baseColor ?? theme.stroke;
|
|
1983
|
-
return this.#sortedZones[zoneIndex - 1].color;
|
|
1984
|
-
}
|
|
1985
|
-
render() {
|
|
1986
|
-
const c = this.#config;
|
|
1987
|
-
const baseColor = c.baseColor ?? theme.stroke;
|
|
1988
|
-
const style = {
|
|
1989
|
-
stroke: baseColor,
|
|
1990
|
-
strokeWidth: c.strokeWidth ?? theme.strokeWidth,
|
|
1991
|
-
smoothing: c.smoothing ?? false,
|
|
1992
|
-
pointStyle: c.pointStyle ?? "none",
|
|
1993
|
-
pointSize: c.pointSize ?? theme.pointSize
|
|
1994
|
-
};
|
|
1995
|
-
const gapThreshold = c.gapThreshold ?? theme.gapThreshold;
|
|
1996
|
-
const pointThreshold = c.pointThreshold ?? theme.pointThreshold;
|
|
1997
|
-
const runs = SeriesProcessor.getRuns(this.data, (p) => p.value === null, gapThreshold);
|
|
1998
|
-
const totalPoints = runs.reduce((sum, run) => sum + run.length, 0);
|
|
1999
|
-
if (totalPoints === 0) return [];
|
|
2000
|
-
const ctx = { timeScale: this.timeScale, valueScale: c.valueScale };
|
|
2001
|
-
const commands = [];
|
|
2002
|
-
for (const run of runs) {
|
|
2003
|
-
if (run.length < 2) continue;
|
|
2004
|
-
if (c.fill) {
|
|
2005
|
-
const fillVal = c.fill.value;
|
|
2006
|
-
const fillSegments = SeriesProcessor.splitByBoundaries(
|
|
2007
|
-
run,
|
|
2008
|
-
[fillVal],
|
|
2009
|
-
(p) => p.value,
|
|
2010
|
-
SeriesProcessor.interpolateDataPoint
|
|
2011
|
-
);
|
|
2012
|
-
const yFill = c.valueScale.map(fillVal);
|
|
2013
|
-
for (let fi = 0; fi < fillSegments.length; fi++) {
|
|
2014
|
-
const seg = fillSegments[fi];
|
|
2015
|
-
const rep = (seg.data[0].value + seg.data[seg.data.length - 1].value) / 2;
|
|
2016
|
-
if (c.fill.side === "above" === rep >= fillVal) {
|
|
2017
|
-
const pts = seg.data.map((p) => ({
|
|
2018
|
-
x: this.timeScale.map(p.time),
|
|
2019
|
-
y: c.valueScale.map(p.value)
|
|
2020
|
-
}));
|
|
2021
|
-
commands.push({
|
|
2022
|
-
type: "path",
|
|
2023
|
-
id: this.id ? `${this.id}-fill-${fi}` : void 0,
|
|
2024
|
-
points: [...pts, { x: pts[pts.length - 1].x, y: yFill }, { x: pts[0].x, y: yFill }],
|
|
2025
|
-
fill: c.fill.color,
|
|
2026
|
-
hatch: c.fill.hatch,
|
|
2027
|
-
stroke: "none"
|
|
2028
|
-
});
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
2031
|
-
}
|
|
2032
|
-
const boundaries = this.#sortedZones.map((z) => z.value);
|
|
2033
|
-
const segments = SeriesProcessor.splitByBoundaries(
|
|
2034
|
-
run,
|
|
2035
|
-
boundaries,
|
|
2036
|
-
(p) => p.value,
|
|
2037
|
-
SeriesProcessor.interpolateDataPoint
|
|
2038
|
-
);
|
|
2039
|
-
const zonedSegments = segments.map((seg) => ({
|
|
2040
|
-
data: seg.data,
|
|
2041
|
-
color: this.#colorAtZone(seg.zoneIndex)
|
|
2042
|
-
}));
|
|
2043
|
-
commands.push(
|
|
2044
|
-
...renderLine(zonedSegments, ctx, { ...style, id: this.id ? `${this.id}-line` : void 0 })
|
|
2045
|
-
);
|
|
2046
|
-
if (style.pointStyle && style.pointStyle !== "none" && totalPoints <= pointThreshold) {
|
|
2047
|
-
commands.push(
|
|
2048
|
-
...renderMarkers(
|
|
2049
|
-
run,
|
|
2050
|
-
ctx,
|
|
2051
|
-
{ ...style, id: this.id ? `${this.id}-marker` : void 0 },
|
|
2052
|
-
(p) => {
|
|
2053
|
-
let color = baseColor;
|
|
2054
|
-
for (const z of this.#sortedZones) {
|
|
2055
|
-
if (p.value >= z.value) color = z.color;
|
|
2056
|
-
}
|
|
2057
|
-
return color;
|
|
2058
|
-
}
|
|
2059
|
-
)
|
|
2060
|
-
);
|
|
2061
|
-
}
|
|
2062
|
-
}
|
|
2063
|
-
return commands;
|
|
2064
|
-
}
|
|
2065
|
-
};
|
|
2066
|
-
|
|
2067
|
-
// src/series/annotation_band.ts
|
|
2068
|
-
var AnnotationBandSeries = class {
|
|
2069
|
-
#config;
|
|
2070
|
-
constructor(config, xRange, y, height) {
|
|
2071
|
-
this.#config = { ...config, xRange, y, height };
|
|
2072
|
-
}
|
|
2073
|
-
/** Render the band as colored rects with labels, optionally with a time axis. */
|
|
2074
|
-
render() {
|
|
2075
|
-
const { items, timeScale, background, hatch: bandHatch, showAxis, xRange, y, height } = this.#config;
|
|
2076
|
-
const commands = [];
|
|
2077
|
-
if (background) {
|
|
2078
|
-
commands.push({
|
|
2079
|
-
type: "rect",
|
|
2080
|
-
x: xRange[0],
|
|
2081
|
-
y,
|
|
2082
|
-
w: xRange[1] - xRange[0],
|
|
2083
|
-
h: height,
|
|
2084
|
-
fill: background,
|
|
2085
|
-
opacity: 0.04,
|
|
2086
|
-
stroke: "#ddd",
|
|
2087
|
-
strokeWidth: 0.25
|
|
2088
|
-
});
|
|
2089
|
-
}
|
|
2090
|
-
for (const item of items) {
|
|
2091
|
-
const x1 = timeScale.map(item.startTime);
|
|
2092
|
-
const x2 = timeScale.map(item.endTime);
|
|
2093
|
-
if (x2 - x1 < 1) continue;
|
|
2094
|
-
commands.push({
|
|
2095
|
-
type: "rect",
|
|
2096
|
-
x: x1,
|
|
2097
|
-
y,
|
|
2098
|
-
w: x2 - x1,
|
|
2099
|
-
h: height,
|
|
2100
|
-
hatch: item.hatch ?? bandHatch,
|
|
2101
|
-
fill: item.fill ?? "#6b728044",
|
|
2102
|
-
stroke: item.stroke,
|
|
2103
|
-
strokeWidth: item.strokeWidth ?? 0
|
|
2104
|
-
});
|
|
2105
|
-
if (item.label) {
|
|
2106
|
-
commands.push({
|
|
2107
|
-
type: "text",
|
|
2108
|
-
content: item.label,
|
|
2109
|
-
x: (x1 + x2) / 2,
|
|
2110
|
-
y: this.#labelY(item.labelBaseline),
|
|
2111
|
-
anchor: "middle",
|
|
2112
|
-
fontSize: item.labelFontSize ?? 10,
|
|
2113
|
-
fill: item.labelFill ?? "#333",
|
|
2114
|
-
textBaseline: item.labelBaseline ?? "middle"
|
|
2115
|
-
});
|
|
2116
|
-
}
|
|
2117
|
-
}
|
|
2118
|
-
if (showAxis) {
|
|
2119
|
-
const timeAxis = new TimeAxis({
|
|
2120
|
-
domain: timeScale.domain(),
|
|
2121
|
-
xRange,
|
|
2122
|
-
y: y + height + 4
|
|
2123
|
-
});
|
|
2124
|
-
commands.push({
|
|
2125
|
-
type: "group",
|
|
2126
|
-
cssClass: "annotation-band-axis",
|
|
2127
|
-
commands: timeAxis.render()
|
|
2128
|
-
});
|
|
2129
|
-
}
|
|
2130
|
-
return commands;
|
|
2131
|
-
}
|
|
2132
|
-
#labelY(baseline) {
|
|
2133
|
-
const { y, height } = this.#config;
|
|
2134
|
-
switch (baseline) {
|
|
2135
|
-
case "top":
|
|
2136
|
-
return y + 1;
|
|
2137
|
-
case "bottom":
|
|
2138
|
-
return y + height - 1;
|
|
2139
|
-
default:
|
|
2140
|
-
return y + height / 2;
|
|
2141
|
-
}
|
|
2142
|
-
}
|
|
2143
|
-
};
|
|
2144
|
-
|
|
2145
|
-
// src/analyze/stats.ts
|
|
2146
|
-
var StatsAggregator = class {
|
|
2147
|
-
/** Compute statistics from data points */
|
|
2148
|
-
static compute(data) {
|
|
2149
|
-
const values = data.map((d) => d.value).filter((v) => v !== null).sort((a, b) => a - b);
|
|
2150
|
-
if (values.length === 0) {
|
|
2151
|
-
return { min: NaN, max: NaN, avg: NaN, mean: NaN, median: NaN, stdDev: NaN, count: 0 };
|
|
2152
|
-
}
|
|
2153
|
-
const count = values.length;
|
|
2154
|
-
const sum = values.reduce((a, b) => a + b, 0);
|
|
2155
|
-
const mean = sum / count;
|
|
2156
|
-
const median = count % 2 === 1 ? values[Math.floor(count / 2)] : (values[count / 2 - 1] + values[count / 2]) / 2;
|
|
2157
|
-
const variance = values.reduce((a, b) => a + Math.pow(b - mean, 2), 0) / count;
|
|
2158
|
-
const stdDev = Math.sqrt(variance);
|
|
2159
|
-
return {
|
|
2160
|
-
min: values[0],
|
|
2161
|
-
max: values[count - 1],
|
|
2162
|
-
avg: mean,
|
|
2163
|
-
mean,
|
|
2164
|
-
median,
|
|
2165
|
-
stdDev,
|
|
2166
|
-
count
|
|
2167
|
-
};
|
|
2168
|
-
}
|
|
2169
|
-
/** Compute stats for a specific time range (viewport-scoped) */
|
|
2170
|
-
static computeInRange(data, startTime, endTime) {
|
|
2171
|
-
const filtered = data.filter((d) => d.time >= startTime && d.time <= endTime);
|
|
2172
|
-
return this.compute(filtered);
|
|
2173
|
-
}
|
|
2174
|
-
};
|
|
2175
|
-
|
|
2176
|
-
// src/series/stats_overlay.ts
|
|
2177
|
-
var StatsOverlay = class {
|
|
2178
|
-
#config;
|
|
2179
|
-
constructor(config) {
|
|
2180
|
-
this.#config = config;
|
|
2181
|
-
}
|
|
2182
|
-
/** Render stats as horizontal lines with labels. */
|
|
2183
|
-
render() {
|
|
2184
|
-
const {
|
|
2185
|
-
data,
|
|
2186
|
-
valueScale,
|
|
2187
|
-
xRange,
|
|
2188
|
-
showMin = false,
|
|
2189
|
-
showMax = false,
|
|
2190
|
-
showAvg = true,
|
|
2191
|
-
showMedian = false,
|
|
2192
|
-
labelPosition = "end",
|
|
2193
|
-
lineColor = theme.statsLineColor,
|
|
2194
|
-
labelColor = theme.statsLabelColor
|
|
2195
|
-
} = this.#config;
|
|
2196
|
-
const stats = StatsAggregator.compute(data);
|
|
2197
|
-
const lines = [
|
|
2198
|
-
{ name: "min", value: stats.min, enabled: showMin },
|
|
2199
|
-
{ name: "max", value: stats.max, enabled: showMax },
|
|
2200
|
-
{ name: "avg", value: stats.avg, enabled: showAvg },
|
|
2201
|
-
{ name: "median", value: stats.median, enabled: showMedian }
|
|
2202
|
-
];
|
|
2203
|
-
const commands = [];
|
|
2204
|
-
const [x0, x1] = xRange;
|
|
2205
|
-
for (const stat of lines.filter((s) => s.enabled)) {
|
|
2206
|
-
const y = valueScale.map(stat.value);
|
|
2207
|
-
commands.push({ type: "line", x1: x0, y1: y, x2: x1, y2: y, stroke: lineColor, strokeWidth: 1 });
|
|
2208
|
-
const label = `${stat.name}: ${stat.value.toFixed(1)}`;
|
|
2209
|
-
if (labelPosition === "start" || labelPosition === "both") {
|
|
2210
|
-
commands.push({ type: "text", content: label, x: x0 + 4, y: y - 4, anchor: "start", fontSize: 10, fill: labelColor });
|
|
2211
|
-
}
|
|
2212
|
-
if (labelPosition === "end" || labelPosition === "both") {
|
|
2213
|
-
commands.push({ type: "text", content: label, x: x1 - 4, y: y - 4, anchor: "end", fontSize: 10, fill: labelColor });
|
|
2214
|
-
}
|
|
2215
|
-
if (labelPosition === "center") {
|
|
2216
|
-
commands.push({ type: "text", content: label, x: (x0 + x1) / 2, y: y - 4, anchor: "middle", fontSize: 10, fill: labelColor });
|
|
2217
|
-
}
|
|
2218
|
-
}
|
|
2219
|
-
return commands;
|
|
2220
|
-
}
|
|
2221
|
-
};
|
|
2222
|
-
|
|
2223
|
-
// src/formatter/time_formatter.ts
|
|
2224
|
-
var TimeFormatter = class {
|
|
2225
|
-
#locale;
|
|
2226
|
-
#fallbackLocales;
|
|
2227
|
-
#autoFormat;
|
|
2228
|
-
constructor(opts) {
|
|
2229
|
-
this.#locale = opts?.locale || (typeof navigator !== "undefined" ? navigator.language : "en-US");
|
|
2230
|
-
this.#fallbackLocales = opts?.fallbackLocales ?? ["en-US"];
|
|
2231
|
-
this.#autoFormat = opts?.autoFormat ?? true;
|
|
2232
|
-
}
|
|
2233
|
-
/**
|
|
2234
|
-
* Format a timestamp.
|
|
2235
|
-
* Auto-selects format options based on the time difference if autoFormat is enabled.
|
|
2236
|
-
*/
|
|
2237
|
-
format(timestamp, opts, referenceTime) {
|
|
2238
|
-
try {
|
|
2239
|
-
const options = this.#autoFormat && referenceTime ? this.#autoOptions(timestamp, referenceTime, opts) : opts;
|
|
2240
|
-
return new Intl.DateTimeFormat(this.#locale, options).format(
|
|
2241
|
-
new Date(timestamp)
|
|
2242
|
-
);
|
|
2243
|
-
} catch {
|
|
2244
|
-
for (const locale of this.#fallbackLocales) {
|
|
2245
|
-
try {
|
|
2246
|
-
return new Intl.DateTimeFormat(locale, opts).format(
|
|
2247
|
-
new Date(timestamp)
|
|
2248
|
-
);
|
|
2249
|
-
} catch {
|
|
2250
|
-
}
|
|
2251
|
-
}
|
|
2252
|
-
return new Date(timestamp).toISOString();
|
|
2253
|
-
}
|
|
2254
|
-
}
|
|
2255
|
-
/** Format a time range display */
|
|
2256
|
-
formatRange(start, end) {
|
|
2257
|
-
const startStr = this.format(start);
|
|
2258
|
-
const endStr = this.format(end);
|
|
2259
|
-
return `${startStr} \u2014 ${endStr}`;
|
|
2260
|
-
}
|
|
2261
|
-
#autoOptions(timestamp, referenceTime, base) {
|
|
2262
|
-
const diff = Math.abs(timestamp - referenceTime);
|
|
2263
|
-
const day = 864e5;
|
|
2264
|
-
const hour = 36e5;
|
|
2265
|
-
const minute = 6e4;
|
|
2266
|
-
const opts = {};
|
|
2267
|
-
if (diff < minute) {
|
|
2268
|
-
opts.second = "2-digit";
|
|
2269
|
-
opts.minute = "2-digit";
|
|
2270
|
-
opts.hour = "2-digit";
|
|
2271
|
-
} else if (diff < hour) {
|
|
2272
|
-
opts.minute = "2-digit";
|
|
2273
|
-
opts.hour = "2-digit";
|
|
2274
|
-
} else if (diff < day) {
|
|
2275
|
-
opts.hour = "2-digit";
|
|
2276
|
-
opts.minute = "2-digit";
|
|
2277
|
-
} else if (diff < 7 * day) {
|
|
2278
|
-
opts.weekday = "short";
|
|
2279
|
-
opts.day = "numeric";
|
|
2280
|
-
} else if (diff < 365 * day) {
|
|
2281
|
-
opts.month = "short";
|
|
2282
|
-
opts.day = "numeric";
|
|
2283
|
-
} else {
|
|
2284
|
-
opts.month = "short";
|
|
2285
|
-
opts.year = "numeric";
|
|
2286
|
-
}
|
|
2287
|
-
return { ...base, ...opts };
|
|
2288
|
-
}
|
|
2289
|
-
};
|
|
2290
|
-
|
|
2291
|
-
// src/formatter/value_formatter.ts
|
|
2292
|
-
var ValueFormatter = class _ValueFormatter {
|
|
2293
|
-
#unit;
|
|
2294
|
-
#decimals;
|
|
2295
|
-
_compact;
|
|
2296
|
-
#custom;
|
|
2297
|
-
constructor(opts) {
|
|
2298
|
-
this.#unit = opts?.unit ?? "";
|
|
2299
|
-
this.#decimals = opts?.decimals ?? 1;
|
|
2300
|
-
this._compact = opts?.compact ?? false;
|
|
2301
|
-
this.#custom = opts?.custom;
|
|
2302
|
-
}
|
|
2303
|
-
/** Format a single value */
|
|
2304
|
-
format(value) {
|
|
2305
|
-
if (this.#custom) {
|
|
2306
|
-
return this.#custom(value);
|
|
2307
|
-
}
|
|
2308
|
-
let formatted;
|
|
2309
|
-
if (this._compact && Math.abs(value) >= 1e6) {
|
|
2310
|
-
formatted = `${(value / 1e6).toFixed(this.#decimals)}M`;
|
|
2311
|
-
} else if (this._compact && Math.abs(value) >= 1e3) {
|
|
2312
|
-
formatted = `${(value / 1e3).toFixed(this.#decimals)}k`;
|
|
2313
|
-
} else {
|
|
2314
|
-
formatted = value.toFixed(this.#decimals);
|
|
2315
|
-
}
|
|
2316
|
-
return this.#unit ? `${formatted}${this.#unit}` : formatted;
|
|
2317
|
-
}
|
|
2318
|
-
/** Format a range of values */
|
|
2319
|
-
formatRange(min, max) {
|
|
2320
|
-
return `${this.format(min)} \u2014 ${this.format(max)}`;
|
|
2321
|
-
}
|
|
2322
|
-
/** Create a formatter for a specific unit */
|
|
2323
|
-
static unit(unit, decimals = 1) {
|
|
2324
|
-
return new _ValueFormatter({ unit, decimals });
|
|
2325
|
-
}
|
|
2326
|
-
/** Temperature formatter */
|
|
2327
|
-
static temperature(unit = "\xB0C") {
|
|
2328
|
-
return new _ValueFormatter({ unit, decimals: 1 });
|
|
2329
|
-
}
|
|
2330
|
-
/** Percentage formatter */
|
|
2331
|
-
static percentage() {
|
|
2332
|
-
return new _ValueFormatter({ unit: "%", decimals: 0 });
|
|
2333
|
-
}
|
|
2334
|
-
};
|
|
2335
|
-
|
|
2336
|
-
// src/formatter/enum_formatter.ts
|
|
2337
|
-
var EnumFormatter = class _EnumFormatter {
|
|
2338
|
-
#map;
|
|
2339
|
-
#fallback;
|
|
2340
|
-
#showValue;
|
|
2341
|
-
constructor(opts) {
|
|
2342
|
-
this.#map = opts?.map ?? {};
|
|
2343
|
-
this.#fallback = opts?.fallback ?? ((v) => String(v));
|
|
2344
|
-
this.#showValue = opts?.showValue ?? false;
|
|
2345
|
-
}
|
|
2346
|
-
/** Format an enum value */
|
|
2347
|
-
format(value) {
|
|
2348
|
-
const label = this.#map[value] ?? this.#fallback(value);
|
|
2349
|
-
return this.#showValue ? `(${value}) ${label}` : label;
|
|
2350
|
-
}
|
|
2351
|
-
/** Check if value exists in map */
|
|
2352
|
-
has(value) {
|
|
2353
|
-
return value in this.#map;
|
|
2354
|
-
}
|
|
2355
|
-
/** Get all mapped labels */
|
|
2356
|
-
labels() {
|
|
2357
|
-
return Object.values(this.#map);
|
|
2358
|
-
}
|
|
2359
|
-
/** Get all mapped values */
|
|
2360
|
-
values() {
|
|
2361
|
-
return Object.keys(this.#map).map(Number);
|
|
2362
|
-
}
|
|
2363
|
-
/** Create formatter from label config strings */
|
|
2364
|
-
static fromLabels(labels, showValue = false) {
|
|
2365
|
-
const map = {};
|
|
2366
|
-
labels.forEach((label, idx) => {
|
|
2367
|
-
map[idx] = label;
|
|
2368
|
-
});
|
|
2369
|
-
return new _EnumFormatter({ map, showValue });
|
|
2370
|
-
}
|
|
2371
|
-
};
|
|
2372
|
-
|
|
2373
|
-
// src/formatter/index.ts
|
|
2374
|
-
var Formatters = {
|
|
2375
|
-
time: new TimeFormatter(),
|
|
2376
|
-
value: new ValueFormatter(),
|
|
2377
|
-
enum: new EnumFormatter()
|
|
2378
|
-
};
|
|
2379
|
-
|
|
2380
|
-
// src/theme/runtime.ts
|
|
2381
|
-
var currentDefault = { ...theme };
|
|
2382
|
-
function getDefaultTheme() {
|
|
2383
|
-
return currentDefault;
|
|
2384
|
-
}
|
|
2385
|
-
|
|
2386
|
-
// src/style/resolver.ts
|
|
2387
|
-
function mergeLine(line, series) {
|
|
2388
|
-
const theme2 = getDefaultTheme();
|
|
2389
|
-
return {
|
|
2390
|
-
color: line.color ?? theme2.stroke,
|
|
2391
|
-
width: line.width ?? theme2.strokeWidth,
|
|
2392
|
-
style: line.style ?? "solid",
|
|
2393
|
-
smoothing: line.smoothing ?? false,
|
|
2394
|
-
shape: line.shape ?? (series.seriesType === "step" ? "step" : "line"),
|
|
2395
|
-
gapThreshold: line.gapThreshold ?? theme2.gapThreshold,
|
|
2396
|
-
opacity: line.opacity ?? 1
|
|
2397
|
-
};
|
|
2398
|
-
}
|
|
2399
|
-
function resolveLines(series) {
|
|
2400
|
-
const styleLine = series.style?.line;
|
|
2401
|
-
if (styleLine === false) return [];
|
|
2402
|
-
if (styleLine === void 0) return [mergeLine({}, series)];
|
|
2403
|
-
if (Array.isArray(styleLine)) return styleLine.map((l) => mergeLine(l, series));
|
|
2404
|
-
return [mergeLine(styleLine, series)];
|
|
2405
|
-
}
|
|
2406
|
-
function resolveMarkers(series) {
|
|
2407
|
-
const m = series.style?.markers;
|
|
2408
|
-
if (!m?.type || m.type === "none") return void 0;
|
|
2409
|
-
const theme2 = getDefaultTheme();
|
|
2410
|
-
const lineColor = series.style?.line && !Array.isArray(series.style.line) ? series.style.line.color : void 0;
|
|
2411
|
-
const seriesColor = lineColor ?? theme2.stroke;
|
|
2412
|
-
return {
|
|
2413
|
-
type: m.type,
|
|
2414
|
-
size: m.size ?? theme2.pointSize,
|
|
2415
|
-
stroke: m.stroke ?? seriesColor,
|
|
2416
|
-
fill: m.fill ?? "#ffffff",
|
|
2417
|
-
strokeWidth: m.strokeWidth ?? 1,
|
|
2418
|
-
threshold: m.threshold
|
|
2419
|
-
};
|
|
2420
|
-
}
|
|
2421
|
-
function resolveShadow(series) {
|
|
2422
|
-
const s = series.style?.shadow;
|
|
2423
|
-
if (!s?.color || s.color === "transparent" || s.color === "none") return void 0;
|
|
2424
|
-
return {
|
|
2425
|
-
color: s.color,
|
|
2426
|
-
blur: s.blur ?? 4,
|
|
2427
|
-
offsetX: s.offsetX ?? 0,
|
|
2428
|
-
offsetY: s.offsetY ?? 0
|
|
2429
|
-
};
|
|
2430
|
-
}
|
|
2431
|
-
function resolveGap(series) {
|
|
2432
|
-
return series.style?.gap;
|
|
2433
|
-
}
|
|
2434
|
-
function resolveFill(series) {
|
|
2435
|
-
return series.style?.fill;
|
|
2436
|
-
}
|
|
2437
|
-
function resolveSeriesStyle(series) {
|
|
2438
|
-
return {
|
|
2439
|
-
lines: resolveLines(series),
|
|
2440
|
-
fill: resolveFill(series),
|
|
2441
|
-
markers: resolveMarkers(series),
|
|
2442
|
-
shadow: resolveShadow(series),
|
|
2443
|
-
gap: resolveGap(series),
|
|
2444
|
-
id: series.style?.id ?? series.id
|
|
2445
|
-
};
|
|
2446
|
-
}
|
|
2447
|
-
|
|
2448
|
-
// src/core/slug.ts
|
|
2449
|
-
function slugify(s) {
|
|
2450
|
-
if (!s) return "unnamed";
|
|
2451
|
-
const slug = s.toString().normalize("NFKD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 50);
|
|
2452
|
-
return slug || "unnamed";
|
|
2453
|
-
}
|
|
2454
|
-
/*!
|
|
28
|
+
`.trim()}function I(a){return {id:a.id,line:{stroke:a.stroke??h.stroke,strokeWidth:a.strokeWidth??h.strokeWidth,smoothing:a.smoothing??false,dashed:a.dashed??false},fill:a.fill??h.areaFillAlpha,markers:{type:a.pointStyle??"none",size:a.pointSize??h.pointSize,stroke:a.stroke??h.stroke,fill:"#ffffff"},shadow:{color:a.shadowColor??"transparent",blur:a.shadowBlur??0,offsetX:a.shadowOffsetX??0,offsetY:a.shadowOffsetY??0}}}function w(a,e,t){let r=[],o=a.reduce((i,m)=>i+m.data.length,0),n=I(t);for(let i=0;i<a.length;i++){let m=a[i];m.data.length>=2?r.push({type:"path",id:t.id?`${t.id}-line-${i}`:void 0,points:m.data.map(s=>({x:e.timeScale.map(s.time),y:e.valueScale.map(s.value)})),stroke:m.color??n.line.stroke,strokeWidth:n.line.strokeWidth,smoothing:n.line.smoothing,dashed:n.line.dashed,shadowColor:n.shadow.color,shadowBlur:n.shadow.blur,shadowOffsetX:n.shadow.offsetX,shadowOffsetY:n.shadow.offsetY,fill:"none"}):m.data.length===1&&o===1&&r.push({type:"circle",cx:e.timeScale.map(m.data[0].time),cy:e.valueScale.map(m.data[0].value),r:Math.max(n.markers.size,n.line.strokeWidth),fill:m.color??n.line.stroke,shadowColor:n.shadow.color,shadowBlur:n.shadow.blur});}return r}function V(a,e,t){let r=[],o=I(t);for(let n=0;n<a.length;n++){let i=a[n];if(i.data.length<2)continue;let m=[];for(let s=0;s<i.data.length;s++){let l=e.timeScale.map(i.data[s].time),u=e.valueScale.map(i.data[s].value);s===0?m.push({x:l,y:u}):(m.push({x:l,y:m[m.length-1].y}),m.push({x:l,y:u}));}r.push({type:"path",id:t.id?`${t.id}-line-${n}`:void 0,points:m,stroke:i.color??o.line.stroke,strokeWidth:o.line.strokeWidth,smoothing:false,fill:"none"});}return r}function xe(a,e,t,r,o){let n=[],i=I(t);for(let m=0;m<a.length;m++){let s=a[m];if(s.data.length<2)continue;let l=s.data.map(d=>({x:e.timeScale.map(d.time),y:e.valueScale.map(r(d))})),u=s.data.map(d=>({x:e.timeScale.map(d.time),y:e.valueScale.map(o(d))})).reverse();n.push({type:"path",id:t.id?`${t.id}-fill-${m}`:void 0,points:[...l,...u],fill:s.color??i.fill,hatch:t.hatch,stroke:"none"});}return n}function B(a,e,t,r){if(a.length<2)return [];let o=x.splitByBoundaries(a,t.boundaries,t.getValue,t.interpolate),n=[];for(let i=0;i<o.length;i++){let m=o[i];if(m.data.length<2)continue;let s=t.getColor(m.zoneIndex);if(!s)continue;let l=m.data.map(d=>({x:e.timeScale.map(d.time),y:e.valueScale.map(t.yLow(d))})),u=m.data.map(d=>({x:e.timeScale.map(d.time),y:e.valueScale.map(t.yHigh(d))})).reverse();n.push({type:"path",id:r?.id?`${r.id}-fill-${i}`:void 0,points:[...l,...u],fill:s,hatch:t.getHatch?.(m.zoneIndex),stroke:"none"});}return n}function Se(a,e,t,r){let o=x.splitByThreshold(a,e,i=>i.value,x.interpolateDataPoint),n=[];return r.below&&n.push(...w(o.below.map(i=>({data:i})),t,r.below)),r.above&&n.push(...w(o.above.map(i=>({data:i})),t,r.above)),n}function P(a,e,t,r){let o=I(t);if(!o.markers.type||o.markers.type==="none")return [];let n=[];for(let i=0;i<a.length;i++){let m=a[i],s=e.timeScale.map(m.time),l=e.valueScale.map(m.value),u=r(m),d=t.pointStroke??u,c=t.pointFill??u,g=t.pointStrokeWidth??1.5,p=t.id?`${t.id}-marker-${i}`:void 0;ke(n,p,o.markers.type,s,l,o.markers.size,d,c,g);}return n}function ke(a,e,t,r,o,n,i,m,s){switch(t){case "circle":a.push({type:"circle",cx:r,cy:o,r:n,fill:m,stroke:i,strokeWidth:s,id:e});break;case "square":a.push({type:"rect",x:r-n,y:o-n,w:n*2,h:n*2,fill:m,stroke:i,strokeWidth:s,id:e});break;case "cross":a.push({type:"line",x1:r-n,y1:o-n,x2:r+n,y2:o+n,stroke:i,strokeWidth:s,id:e}),a.push({type:"line",x1:r-n,y1:o+n,x2:r+n,y2:o-n,stroke:i,strokeWidth:s,id:e});break;case "diamond":a.push({type:"path",points:[{x:r,y:o-n},{x:r+n,y:o},{x:r,y:o+n},{x:r-n,y:o}],fill:m,stroke:i,strokeWidth:s,id:e});break;case "triangle":a.push({type:"path",points:[{x:r,y:o-n},{x:r+n,y:o+n},{x:r-n,y:o+n}],fill:m,stroke:i,strokeWidth:s,id:e});break;case "star":{let l=[];for(let u=0;u<10;u++){let d=u%2===0?n:n*.5,c=Math.PI/2*3+u*Math.PI/5;l.push({x:r+d*Math.cos(c),y:o+d*Math.sin(c)});}a.push({type:"path",points:l,fill:m,stroke:i,strokeWidth:s,id:e});break}case "arrow":a.push({type:"path",points:[{x:r-n,y:o+n},{x:r,y:o-n},{x:r+n,y:o+n}],stroke:i,strokeWidth:s,fill:"none",id:e});break;default:a.push({type:"circle",cx:r,cy:o,r:n,fill:m,stroke:i,strokeWidth:s,id:e});}}var T=12,H=8,N=20,ve=11,G=18,Y=a=>a.length*ve*.6;function we(a,e="vertical"){if(e==="horizontal"){let r=0;for(let o of a)r+=T+H+Y(o.name)+G;return {width:Math.max(0,r-G),height:N}}let t=0;for(let r of a)t=Math.max(t,Y(r.name));return {width:T+H+t,height:a.length*N}}function Ce(a){let{items:e,x:t,y:r,orientation:o="vertical"}=a,n=[],i=t;return e.forEach((m,s)=>{let l=o==="horizontal"?i:t,u=o==="horizontal"?r:r+s*N;n.push({type:"rect",x:l,y:u,w:T,h:T,fill:m.color,stroke:h.legendStroke,strokeWidth:1}),n.push({type:"text",content:m.name,x:l+T+H,y:u+T-2,fontSize:h.legendFont,fill:h.legendText}),o==="horizontal"&&(i+=T+H+Y(m.name)+G);}),{type:"group",cssClass:"chart-legend",commands:n}}function Te(a){let{xTicks:e,yTicks:t,xRange:r,yRange:o,stroke:n=h.gridStroke,strokeWidth:i=h.gridStrokeWidth,dashed:m=false,opacity:s=h.gridOpacity}=a,l=[];if(t)for(let u of t)l.push({type:"line",x1:r[0],y1:u,x2:r[1],y2:u,stroke:n,strokeWidth:i,dashed:m,opacity:s});if(e)for(let u of e)l.push({type:"line",x1:u,y1:o[0],x2:u,y2:o[1],stroke:n,strokeWidth:i,dashed:m,opacity:s});return l}var L=class{#e;#t;constructor(e){this.#e=[...e.domain],this.#t=[...e.range];}map(e){let t=Number(e),[r,o]=this.#e,[n,i]=this.#t;return o===r?n:n+(t-r)/(o-r)*(i-n)}invert(e){let[t,r]=this.#e,[o,n]=this.#t;return n===o?t:t+(e-o)/(n-o)*(r-t)}domain(){return [...this.#e]}range(){return [...this.#t]}},k=[{label:"second",ms:1e3},{label:"2_seconds",ms:2e3},{label:"5_seconds",ms:5e3},{label:"10_seconds",ms:1e4},{label:"30_seconds",ms:3e4},{label:"minute",ms:6e4},{label:"5_minutes",ms:3e5},{label:"15_minutes",ms:9e5},{label:"30_minutes",ms:18e5},{label:"hour",ms:36e5},{label:"3_hours",ms:108e5},{label:"6_hours",ms:216e5},{label:"day",ms:864e5},{label:"week",ms:6048e5},{label:"month",ms:2592e6},{label:"3_months",ms:7776e6},{label:"6_months",ms:15552e6},{label:"year",ms:31536e6},{label:"2_years",ms:63072e6},{label:"5_years",ms:15768e7}],R=class{#e;#t;constructor(e){this.#e=new L({domain:e.domain,range:e.range}),this.#t=e.locale||(typeof navigator<"u"?navigator.language:"en-US");}map(e){return this.#e.map(Number(e))}invert(e){return this.#e.invert(e)}domain(){return this.#e.domain()}range(){return this.#e.range()}get locale(){return this.#t}tickInterval(e,t=3,r=12){let[o,n]=this.#e.domain(),i=n-o;if(i<=0)return {interval:k[0].ms};let m=i/e,s=k[0].ms;for(let d of k)if(d.ms>=m){s=d.ms;break}let l=s,u=Math.round(i/l);for(;u>r&&l<k[k.length-1].ms;){let d=k.findIndex(c=>c.ms===l);l=k[Math.min(d+1,k.length-1)].ms,u=Math.round(i/l);}for(;u<t&&l>k[0].ms;){let d=k.findIndex(c=>c.ms===l);l=k[Math.max(d-1,0)].ms,u=Math.round(i/l);}return {interval:l}}ticks(e){let t=e?.minTicks??5,r=e?.maxTicks??12,{interval:o}=this.tickInterval((t+r)/2,t,r),[n,i]=this.#e.domain(),m=[],s=Math.ceil(n/o)*o;for(let l=s;l<=i;l+=o)m.push(l);return m}format(e,t){return new Intl.DateTimeFormat(this.#t,t).format(new Date(e))}},W=class{#e;#t;#n;#r;constructor(e){this.#e=[...e.domain],this.#t=[...e.range],this.#n=e.paddingInner??.1,this.#r=e.paddingOuter??.05;}get step(){let[e,t]=this.#t,r=this.#e.length;return r<=1?Math.abs(t-e):Math.abs(t-e)*(1-this.#r*2)/r+Math.abs(t-e)*this.#n*2/r}get bandwidth(){let[e,t]=this.#t,r=this.#e.length;if(r<=1)return Math.abs(t-e)*(1-this.#r*2);let o=this.#r*2*Math.abs(t-e);return (Math.abs(t-e)-o)/r*(1-this.#n)}map(e){let t=this.#e.findIndex(d=>String(d)===String(e));if(t===-1)return this.#t[0];let[r,o]=this.#t,n=this.#e.length;if(n<=1)return (r+o)/2;let i=this.#r*2*Math.abs(o-r),m=Math.abs(o-r)-i,s=o>=r?1:-1,l=m/n;return r+this.#r*Math.abs(o-r)*s+t*l}invert(e){let t=0,r=1/0;for(let o=0;o<this.#e.length;o++){let n=this.map(this.#e[o]),i=Math.abs(e-n);i<r&&(r=i,t=o);}return this.#e[t]}domain(){return this.#e.length===0?["",""]:[this.#e[0],this.#e[this.#e.length-1]]}range(){return [...this.#t]}positions(){let e=new Map;for(let t of this.#e)e.set(t,this.map(t));return e}};function Le(a){return a==="dotted"?{dash:"dotted"}:a==="dashed"?{dash:"dashed"}:{}}function Me(a){let{thresholds:e,valueScale:t,xRange:r}=a,[o,n]=r,[i,m]=t.range(),s=Math.min(i,m),l=Math.max(i,m),u=[],d=[];for(let c of e){let g=c.color??h.thresholdColor,p=t.map(c.value);c.fill==="above"?u.push({type:"rect",x:o,y:s,w:n-o,h:Math.max(0,p-s),fill:g,hatch:c.fillHatch,opacity:c.fillOpacity??.12,id:c.id?`${c.id}-fill`:void 0}):c.fill==="below"&&u.push({type:"rect",x:o,y:p,w:n-o,h:Math.max(0,l-p),fill:g,hatch:c.fillHatch,opacity:c.fillOpacity??.12,id:c.id?`${c.id}-fill`:void 0});let b=c.line??h.thresholdLine;if(b!=="none"){let y=Le(b),f={type:"line",x1:o,y1:p,x2:n,y2:p,stroke:g,strokeWidth:1,...y,id:c.id?`${c.id}-line`:void 0};c.shadowColor&&(f.shadowColor=c.shadowColor,f.shadowBlur=c.shadowBlur??4,f.shadowOffsetX=c.shadowOffsetX??0,f.shadowOffsetY=c.shadowOffsetY??2),u.push(f);}if(c.label!==false){let y=c.label&&typeof c.label=="object"?c.label:void 0,f=typeof c.label=="string"?c.label:y?.text??c.name,S=y?.position??"right";d.push({..._e(f,S,o,n,p,g,y),id:c.id?`${c.id}-label`:void 0});}}return {inside:u,labels:d}}function De(a){let e=Me(a);return [...e.inside,...e.labels]}function _e(a,e,t,r,o,n,i){let m=(t+r)/2,s={type:"text",content:a,fontSize:h.thresholdFontSize,fill:n},l=i?{...i.rotate!==void 0&&{rotate:i.rotate},...i.textBaseline!==void 0&&{textBaseline:i.textBaseline}}:{};switch(e){case "left":return {...s,...l,x:t+4,y:o-4,anchor:"start"};case "above":return {...s,...l,x:m,y:o-6,anchor:"middle"};case "below":return {...s,...l,x:m,y:o+14,anchor:"middle"};case "center":return {...s,...l,x:m,y:o-4,anchor:"middle"};case "outside-left":return {...s,...l,x:t-6,y:o+3,anchor:"end",textBaseline:l.textBaseline??"middle"};case "outside-right":return {...s,...l,x:r+6,y:o+3,anchor:"start",textBaseline:l.textBaseline??"middle"};default:return {...s,...l,x:r-4,y:o-4,anchor:"end"}}}function $e(a){let{gaps:e,timeScale:t,yRange:r,fill:o=h.gapFill,hatch:n,fillOpacity:i=h.gapFillOpacity??.15,stroke:m=h.gapStroke,strokeWidth:s=h.gapStrokeWidth,dashed:l=true,fontSize:u=h.gapFontSize,fontFill:d=h.gapFontColor,labelBaseline:c="middle",labelRotate:g}=a,[p,b]=r,y=[];for(let f of e){let S=t.map(f.startTime),$=t.map(f.endTime),C=f.fill??o,F=f.hatch??n,se=f.fillOpacity??i,le=f.label??"",ge=f.rotate??g,z=f.labelBaseline??c;if(f.style==="dashed_border"||!f.style?y.push({type:"rect",x:S,y:p,w:$-S,h:b-p,fill:C,hatch:F,opacity:se,stroke:m,strokeWidth:s,dashed:l}):f.style==="empty"&&y.push({type:"rect",x:S,y:p,w:$-S,h:b-p,fill:C,hatch:F,opacity:se}),le){let ye=Fe(p,b,z),be=z==="above"?"top":z==="below"?"bottom":"middle";y.push({type:"text",content:le,x:(S+$)/2,y:ye,anchor:"middle",fontSize:u,fill:d,textBaseline:be,rotate:ge});}}return y}function Fe(a,e,t){switch(t){case "above":return a-12;case "below":return e+4;default:return (a+e)/2}}function Pe(a){let{markers:e,timeScale:t,valueScale:r,yRange:o=[0,300]}=a,[n,i]=o,m=[];for(let s of e){let l=t.map(s.time),u=s.color??h.markerColor,d=s.pointStyle??(s.value!==void 0?"circle":"none"),c=s.lineStyle??"full";if(s.value!==void 0){let g=r.map(s.value);if(c==="to-value"?m.push({type:"line",x1:l,y1:i,x2:l,y2:g,stroke:u,strokeWidth:1,dashed:true}):c==="to-top"?m.push({type:"line",x1:l,y1:n,x2:l,y2:g,stroke:u,strokeWidth:1,dashed:true}):c==="full"&&m.push({type:"line",x1:l,y1:n,x2:l,y2:i,stroke:u,strokeWidth:1}),d!=="none"&&Re(m,l,g,u,d),s.label){let p=c==="to-value"?g-10:n-6;m.push({type:"text",content:s.label,x:l,y:p,anchor:"middle",fontSize:11,fill:u});}}else m.push({type:"line",x1:l,y1:n,x2:l,y2:i,stroke:u,strokeWidth:1}),s.label&&m.push({type:"text",content:s.label,x:l,y:n-6,anchor:"middle",fontSize:11,fill:u});}return m}function Re(a,e,t,r,o){let n=h.markerSize;switch(o){case "circle":a.push({type:"circle",cx:e,cy:t,r:n,fill:r});break;case "square":a.push({type:"rect",x:e-n,y:t-n,w:n*2,h:n*2,fill:r});break;case "cross":a.push({type:"line",x1:e-n,y1:t-n,x2:e+n,y2:t+n,stroke:r,strokeWidth:2}),a.push({type:"line",x1:e-n,y1:t+n,x2:e+n,y2:t-n,stroke:r,strokeWidth:2});break;case "arrow":a.push({type:"path",points:[{x:e-n,y:t+n},{x:e,y:t-n},{x:e+n,y:t+n}],stroke:r,strokeWidth:2,fill:"none"});break;case "diamond":a.push({type:"path",points:[{x:e,y:t-n},{x:e+n,y:t},{x:e,y:t+n},{x:e-n,y:t}],fill:r,stroke:"none"});break;case "triangle":a.push({type:"path",points:[{x:e,y:t-n},{x:e+n,y:t+n},{x:e-n,y:t+n}],fill:r,stroke:"none"});break;case "star":{let i=[],m=n*.4;for(let s=0;s<10;s++){let l=s%2===0?n:m,u=Math.PI/2*3+s*Math.PI/5;i.push({x:e+l*Math.cos(u),y:t+l*Math.sin(u)});}a.push({type:"path",points:i,fill:r,stroke:"none"});break}case "plus":a.push({type:"line",x1:e-n,y1:t,x2:e+n,y2:t,stroke:r,strokeWidth:2}),a.push({type:"line",x1:e,y1:t-n,x2:e,y2:t+n,stroke:r,strokeWidth:2});break;case "triangle-down":a.push({type:"path",points:[{x:e,y:t+n},{x:e+n,y:t-n},{x:e-n,y:t-n}],fill:r,stroke:"none"});break;case "hexagon":{let i=[];for(let m=0;m<6;m++){let s=m*(Math.PI/3);i.push({x:e+n*Math.cos(s),y:t+n*Math.sin(s)});}a.push({type:"path",points:i,fill:r,stroke:"none"});break}case "hourglass":a.push({type:"path",points:[{x:e-n,y:t-n},{x:e+n,y:t-n},{x:e-n,y:t+n},{x:e+n,y:t+n}],fill:r,stroke:"none"});break;case "line-horizontal":a.push({type:"line",x1:e-n,y1:t,x2:e+n,y2:t,stroke:r,strokeWidth:2});break}}function We(a){let{highlights:e,timeScale:t,yRange:r,height:o}=a,[n,i]=r,m=[];for(let s of e){let l=t.map(s.startTime),u=t.map(s.endTime);m.push({type:"rect",x:l,y:n,w:u-l,h:i-n,fill:s.color??h.highlightColor,opacity:s.opacity??h.highlightOpacity}),s.label&&m.push({type:"text",content:s.label,x:(l+u)/2,y:Ae(s.labelPosition??"top",n,i,o),anchor:"middle",fontSize:h.annotationFontSize,fill:s.color??h.highlightLabelColor,rotate:s.rotate});}return m}function Ae(a,e,t,r){switch(a){case "above":return e-5;case "below":return r!==void 0?r-5:t+14;case "center":return (e+t)/2+4;case "bottom":return t-6;default:return e+14}}function X(a){return a&&a.toString().normalize("NFKD").replace(/[̀-ͯ]/g,"").toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"").slice(0,50)||"unnamed"}function Oe(a){let{annotations:e,timeScale:t,valueScales:r}=a,o=[],n=i=>{let m=r.get(i.axis??0)??r.values().next().value;return {x:t.map(i.time),y:m?m.map(i.value):0}};for(let i of e){let m=[],s=l=>m.push(l);switch(i.type){case "line":{let l=n(i.from),u=n(i.to);s({type:"line",x1:l.x,y1:l.y,x2:u.x,y2:u.y,stroke:i.color??h.annotationColor,strokeWidth:i.width??h.annotationWidth,dash:i.dash});break}case "arrow":{let l=n(i.from),u=n(i.to),d=i.color??h.annotationColor,c=i.headSize??h.annotationHead;s({type:"line",x1:l.x,y1:l.y,x2:u.x,y2:u.y,stroke:d,strokeWidth:i.width??h.annotationWidth});let g=Math.hypot(u.x-l.x,u.y-l.y)||1,p=(u.x-l.x)/g,b=(u.y-l.y)/g,y=u.x-p*c,f=u.y-b*c;s({type:"path",points:[{x:u.x,y:u.y},{x:y-b*c*.5,y:f+p*c*.5},{x:y+b*c*.5,y:f-p*c*.5}],fill:d,stroke:"none"});break}case "rect":{let l=n(i.from),u=n(i.to);s({type:"rect",x:Math.min(l.x,u.x),y:Math.min(l.y,u.y),w:Math.abs(u.x-l.x),h:Math.abs(u.y-l.y),fill:i.fill??"none",stroke:i.stroke,opacity:i.opacity});break}case "point":{let l=n(i.at),u=i.color??"#334155",d=i.radius??h.annotationRadius,c=i.shape??"circle";c==="circle"?s({type:"circle",cx:l.x,cy:l.y,r:d,fill:u}):c==="square"?s({type:"rect",x:l.x-d,y:l.y-d,w:d*2,h:d*2,fill:u}):(s({type:"line",x1:l.x-d,y1:l.y-d,x2:l.x+d,y2:l.y+d,stroke:u,strokeWidth:1.5}),s({type:"line",x1:l.x-d,y1:l.y+d,x2:l.x+d,y2:l.y-d,stroke:u,strokeWidth:1.5}));break}case "label":{let l=n(i.at);s({type:"text",content:i.text,x:l.x+(i.dx??0),y:l.y+(i.dy??0),anchor:i.anchor??"middle",fontSize:h.annotationFontSize,fill:i.color??h.annotationColor,rotate:i.rotate});break}}if(i.id&&m.length>0){let l=X(i.id);o.push({type:"group",cssClass:`annotation annotation--${l}`,commands:m});}else o.push(...m);}return o}var U=class a{_config;constructor(e){this._config=e;}compute(){let{width:e,height:t,margin:r}=this._config;return {totalWidth:e,totalHeight:t,chartWidth:e-r.left-r.right,chartHeight:t-r.top-r.bottom,chartX:r.left,chartY:r.top,margin:r}}static default(e=800,t=400){return new a({width:e,height:t,margin:{top:20,right:20,bottom:40,left:60}})}};var Z=class{#e;constructor(){this.#e=[];}add(e){this.#e.push(e);}clear(){this.#e=[];}isInside(e,t){return this.#e.length===0?true:this.#e.some(r=>e>=r.x&&e<=r.x+r.width&&t>=r.y&&t<=r.y+r.height)}toSVGClipPath(e="clip"){if(this.#e.length===0)return "";let t=this.#e.map(r=>`<rect x="${r.x}" y="${r.y}" width="${r.width}" height="${r.height}" />`).join(`
|
|
29
|
+
`);return `<clipPath id="${e}">
|
|
30
|
+
${t}
|
|
31
|
+
</clipPath>`}get regions(){return [...this.#e]}};var A={axisColor:h.axisColor,tickColor:h.tickColor,textColor:h.textColor,textSize:h.textSize},O=class{#e;#t;constructor(e){this.#e=new R({domain:e.domain,range:e.xRange,locale:e.locale}),this.#t=e;}get scale(){return this.#e}get axisColor(){return (this.#t.colors??A).axisColor}get tickColor(){return (this.#t.colors??A).tickColor}get textColor(){return (this.#t.colors??A).textColor}get textSize(){return (this.#t.colors??A).textSize}generateTicks(){let e=this.#t.minTicks??5,t=this.#t.maxTicks??12,o=this.#e.ticks({minTicks:e,maxTicks:t}).map(n=>({time:n,x:this.#e.map(n),label:this.tickLabel(n)}));return this.antiOverlap(o)}tickLabel(e){if(this.#t.format)return this.#t.format(new Date(e));let t=this.#t.minTicks??5,r=this.#t.maxTicks??12,{interval:o}=this.#e.tickInterval((t+r)/2,t,r),n={};return o<6e4?(n.hour="2-digit",n.minute="2-digit",n.second="2-digit"):o<36e5||o<864e5?(n.hour="2-digit",n.minute="2-digit"):o<31536e6?(n.day="numeric",n.month="short",o>=2592e6&&(n.day=void 0,n.month="long")):(n.year="numeric",o<2*31536e6&&(n.month="short")),this.#e.format(e,n)}antiOverlap(e){if(e.length<=1)return e;let t=60,r=[e[0]];for(let o=1;o<e.length;o++){let n=r[r.length-1].x;Math.abs(e[o].x-n)>=t&&r.push(e[o]);}return r}render(){let e=this.generateTicks(),t=this.#t.colors??A,r=this.#t.y??0,o=[],[n]=this.#e.range();o.push({type:"line",x1:n,y1:r,x2:e[e.length-1]?.x??n,y2:r,stroke:t.axisColor,strokeWidth:t.axisWidth});for(let i of e)o.push({type:"line",x1:i.x,y1:r,x2:i.x,y2:r+6,stroke:t.tickColor,strokeWidth:t.axisWidth}),o.push({type:"text",content:i.label,x:i.x,y:r+t.textSize+6,anchor:"middle",fontSize:t.textSize,fill:t.textColor});return o}};var j={axisColor:"#ccc",tickColor:"#ddd",textColor:"#777",textSize:12};function je(a){return Math.abs(a)>=1e6?`${(a/1e6).toFixed(1)}M`:Math.abs(a)>=1e3?`${(a/1e3).toFixed(1)}k`:Number.isInteger(a)?String(a):a.toFixed(1)}var q=class{#e;#t;constructor(e){this.#e=new L({domain:e.domain,range:e.range}),this.#t=e;}get scale(){return this.#e}get axisColor(){return (this.#t.colors??j).axisColor}get tickColor(){return (this.#t.colors??j).tickColor}get textColor(){return (this.#t.colors??j).textColor}get textSize(){return (this.#t.colors??j).textSize}generateTicks(){let e=this.#t.format??je,t=6,[r,o]=this.#e.domain(),n=o-r;if(n===0)return [{value:r,position:this.#e.map(r),label:e(r)}];let i=n/t,m=Math.pow(10,Math.floor(Math.log10(i))),s=i/m,l;s<=1.5?l=m:s<=3?l=2*m:s<=7?l=5*m:l=10*m;let u=[],d=Math.ceil(r/l)*l;for(let c=d;c<=o;c+=l)u.push({value:c,position:this.#e.map(c),label:e(c)});return u}render(){let e=this.generateTicks(),t=this.#t.colors??j,r=this.#t.x??0,o=this.#t.orientation??"vertical",n=this.#t.position??"left",i=this.#t.suppressLabelsNear??[],m=this.#t.suppressTolerancePx??8,s=u=>i.some(d=>Math.abs(d-u)<=m),l=[];if(o==="vertical"){let[u,d]=this.#e.range();l.push({type:"line",x1:r,y1:u,x2:r,y2:d,stroke:t.axisColor,strokeWidth:t.axisWidth});for(let c of e){let g=s(c.position);n==="left"?(l.push({type:"line",x1:r-4,y1:c.position,x2:r,y2:c.position,stroke:t.tickColor,strokeWidth:t.axisWidth}),g||l.push({type:"text",content:c.label,x:r-8,y:c.position+4,anchor:"end",fontSize:11,fill:t.textColor})):(l.push({type:"line",x1:r,y1:c.position,x2:r+4,y2:c.position,stroke:t.tickColor,strokeWidth:t.axisWidth}),g||l.push({type:"text",content:c.label,x:r+8,y:c.position+4,anchor:"start",fontSize:11,fill:t.textColor}));}}else {let[u,d]=this.#e.range();l.push({type:"line",x1:u,y1:r,x2:d,y2:r,stroke:t.axisColor,strokeWidth:t.axisWidth});for(let c of e)l.push({type:"line",x1:c.position,y1:r,x2:c.position,y2:r+6,stroke:t.tickColor,strokeWidth:t.axisWidth}),l.push({type:"text",content:c.label,x:c.position,y:r+18,anchor:"middle",fontSize:t.textSize,fill:t.textColor});}return l}};var K=class{#e;#t;#n;constructor(e){let t=Object.keys(e.enumMap).map(Number).sort((r,o)=>r-o).map(String);this.#t=t,this.#e=new W({domain:t,range:e.range}),this.#n=e;}get scale(){return this.#e}generateTicks(){let{enumMap:e,showLabels:t=true,autoColor:r=true,gapValues:o}=this.#n,n=new Set(o??[]);return this.#t.map((i,m)=>{let s=Number(i),l=t?e[s]??String(s):String(s),u=this.#e.map(s),d=r?h.palette[m%h.palette.length]:void 0;return {value:s,label:l,y:u,color:d,isGap:n.has(s)}})}colorFor(e){let t=this.#t.indexOf(String(e));if(!(t===-1||!(this.#n.autoColor??true)))return h.palette[t%h.palette.length]}render(){let e=this.generateTicks(),t=this.#n.x??0,r=[];for(let o of e)o.isGap||r.push({type:"text",content:o.label,x:t,y:o.y+4,anchor:"end",fontSize:11,fill:o.color??h.textColor});return r}map(e){return this.#e.map(e)}isGap(e){return new Set(this.#n.gapValues??[]).has(e)}};var v=class a{#e;#t;static uidcnt=0;#n;constructor(e,t=[]){this.#e=e.id??"id"+Date.now+ ++a.uidcnt,this.#t=e.timeScale,this.#n=t;}get id(){return this.#e}get timeScale(){return this.#t}get data(){return this.#n}};var Q=class extends v{#e;constructor(e){super(e,e.data),this.#e=e;}points(){let e=this.#e.valueScale;return this.data.filter(t=>t.value!==null).map(t=>({x:this.timeScale.map(t.time),y:e.map(t.value),time:t.time})).sort((t,r)=>t.x-r.x)}render(){let e=this.#e,t={smoothing:e.smoothing??false,stroke:e.stroke??h.stroke,strokeWidth:e.strokeWidth??h.strokeWidth,dashed:e.dashed??false,pointStyle:e.pointStyle??"none",pointSize:e.pointSize??h.pointSize,shadowColor:e.shadowColor,shadowBlur:e.shadowBlur,shadowOffsetX:e.shadowOffsetX,shadowOffsetY:e.shadowOffsetY},r=e.pointThreshold??h.pointThreshold,o=e.gapThreshold??h.gapThreshold,n=e.id,i=x.getRuns(this.data,d=>d.value===null,o),m=i.reduce((d,c)=>d+c.length,0);if(m===0)return [];let s={timeScale:this.timeScale,valueScale:e.valueScale},l=i.map(d=>({data:d})),u=[];if(u.push(...w(l,s,{...t,id:n?`${n}-line`:void 0})),t.pointStyle&&t.pointStyle!=="none"&&m<=r)for(let d of i)u.push(...P(d,s,{...t,id:n?`${n}-marker`:void 0},()=>t.stroke));return u}};var J=class extends v{#e;constructor(e){super(e,e.data),this.#e=e;}points(){let e=this.#e.valueScale,t=[...this.data].filter(o=>o.value!==null).sort((o,n)=>o.time-n.time),r=[];for(let o=0;o<t.length;o++){let n=this.timeScale.map(t[o].time),i=e.map(t[o].value);o===0?r.push({x:n,y:i}):(r.push({x:n,y:r[r.length-1].y}),r.push({x:n,y:i}));}return r}render(){let e=x.getRuns(this.data,o=>o.value===null);if(e.length===0)return [];let t={timeScale:this.timeScale,valueScale:this.#e.valueScale},r=e.map(o=>({data:o}));return V(r,t,{stroke:this.#e.stroke??h.stroke,strokeWidth:this.#e.strokeWidth??h.strokeWidth,id:this.id?`${this.id}-line`:void 0})}};var ee=class extends v{#e;constructor(e){super(e,e.data),this.#e=e;}opacity(e){if(!(this.#e.countOpacity??false))return .6;let t=Math.max(...this.data.map(r=>r.count));return t===0?.2:.2+.8*e/t}render(){let e=this.#e,t=e.fill??h.bandFill,r=e.hatch,o=e.avgLine??false,n=e.avgLineColor??h.bandAvgLine,i=e.bandWidth??10,m=[];for(let s of this.data){if(s.min===null||s.max===null)continue;let l=this.timeScale.map(s.time),u=e.valueScale.map(s.max),d=e.valueScale.map(s.min),c=i,g=this.data.indexOf(s);if(m.push({type:"rect",x:l-c/2,y:u,w:c,h:d-u,fill:t,hatch:r,opacity:this.opacity(s.count),id:this.id?`${this.id}-slot-${g}`:void 0}),o&&s.avg!==null){let p=e.valueScale.map(s.avg);m.push({type:"line",x1:l-c/2,y1:p,x2:l+c/2,y2:p,stroke:n,strokeWidth:1,id:this.id?`${this.id}-avg-${g}`:void 0});}}return m}};var te=class extends v{#e;constructor(e){super(e,e.data),this.#e=e;}render(){let e=this.#e,t=e.minColor??h.minColor,r=e.maxColor??h.maxColor,o=e.avgColor??h.avgColor,n=e.avgDashed??true,i=e.smoothing??false,m=e.strokeWidth??h.strokeWidth,s=x.getRuns(this.data,d=>d.min===null||d.max===null||d.avg===null);if(s.length===0)return [];let l={timeScale:this.timeScale,valueScale:e.valueScale},u=[];for(let d of s)d.length<2||(e.fillToMax&&u.push(...B(d,l,{boundaries:[],yLow:c=>c.avg,yHigh:c=>c.max,getValue:c=>c.avg,interpolate:x.interpolateAggregatedPoint,getColor:()=>e.fillToMax,getHatch:()=>e.fillToMaxHatch},{id:this.id?`${this.id}-fillToMax`:void 0})),e.fillToMin&&u.push(...B(d,l,{boundaries:[],yLow:c=>c.avg,yHigh:c=>c.min,getValue:c=>c.avg,interpolate:x.interpolateAggregatedPoint,getColor:()=>e.fillToMin,getHatch:()=>e.fillToMinHatch},{id:this.id?`${this.id}-fillToMin`:void 0})),u.push(...w([{data:d.map(c=>({time:c.time,value:c.max}))}],l,{stroke:r,strokeWidth:m,smoothing:i,id:this.id?`${this.id}-max`:void 0}),...w([{data:d.map(c=>({time:c.time,value:c.min}))}],l,{stroke:t,strokeWidth:m,smoothing:i,id:this.id?`${this.id}-min`:void 0}),...w([{data:d.map(c=>({time:c.time,value:c.avg}))}],l,{stroke:o,strokeWidth:m,smoothing:i,dashed:n,id:this.id?`${this.id}-avg`:void 0})));return u}};var ne=class extends v{#e;#t;constructor(e){super(e,e.data),this.#e=e,this.#t=[...e.zones??[]].sort((t,r)=>t.value-r.value);}#n(e){return e===0?this.#e.baseColor??h.stroke:this.#t[e-1].color}render(){let e=this.#e,t=e.baseColor??h.stroke,r={stroke:t,strokeWidth:e.strokeWidth??h.strokeWidth,smoothing:e.smoothing??false,pointStyle:e.pointStyle??"none",pointSize:e.pointSize??h.pointSize},o=e.gapThreshold??h.gapThreshold,n=e.pointThreshold??h.pointThreshold,i=x.getRuns(this.data,u=>u.value===null,o),m=i.reduce((u,d)=>u+d.length,0);if(m===0)return [];let s={timeScale:this.timeScale,valueScale:e.valueScale},l=[];for(let u of i){if(u.length<2)continue;if(e.fill){let p=e.fill.value,b=x.splitByBoundaries(u,[p],f=>f.value,x.interpolateDataPoint),y=e.valueScale.map(p);for(let f=0;f<b.length;f++){let S=b[f],$=(S.data[0].value+S.data[S.data.length-1].value)/2;if(e.fill.side==="above"==$>=p){let C=S.data.map(F=>({x:this.timeScale.map(F.time),y:e.valueScale.map(F.value)}));l.push({type:"path",id:this.id?`${this.id}-fill-${f}`:void 0,points:[...C,{x:C[C.length-1].x,y},{x:C[0].x,y}],fill:e.fill.color,hatch:e.fill.hatch,stroke:"none"});}}}let d=this.#t.map(p=>p.value),g=x.splitByBoundaries(u,d,p=>p.value,x.interpolateDataPoint).map(p=>({data:p.data,color:this.#n(p.zoneIndex)}));l.push(...w(g,s,{...r,id:this.id?`${this.id}-line`:void 0})),r.pointStyle&&r.pointStyle!=="none"&&m<=n&&l.push(...P(u,s,{...r,id:this.id?`${this.id}-marker`:void 0},p=>{let b=t;for(let y of this.#t)p.value>=y.value&&(b=y.color);return b}));}return l}};var re=class{#e;constructor(e,t,r,o){this.#e={...e,xRange:t,y:r,height:o};}render(){let{items:e,timeScale:t,background:r,hatch:o,showAxis:n,xRange:i,y:m,height:s}=this.#e,l=[];r&&l.push({type:"rect",x:i[0],y:m,w:i[1]-i[0],h:s,fill:r,opacity:.04,stroke:"#ddd",strokeWidth:.25});for(let u of e){let d=t.map(u.startTime),c=t.map(u.endTime);c-d<1||(l.push({type:"rect",x:d,y:m,w:c-d,h:s,hatch:u.hatch??o,fill:u.fill??"#6b728044",stroke:u.stroke,strokeWidth:u.strokeWidth??0}),u.label&&l.push({type:"text",content:u.label,x:(d+c)/2,y:this.#t(u.labelBaseline),anchor:"middle",fontSize:u.labelFontSize??10,fill:u.labelFill??"#333",textBaseline:u.labelBaseline??"middle"}));}if(n){let u=new O({domain:t.domain(),xRange:i,y:m+s+4});l.push({type:"group",cssClass:"annotation-band-axis",commands:u.render()});}return l}#t(e){let{y:t,height:r}=this.#e;switch(e){case "top":return t+1;case "bottom":return t+r-1;default:return t+r/2}}};var oe=class{#e;constructor(e){this.#e=e;}render(){let{data:e,valueScale:t,xRange:r,showMin:o=false,showMax:n=false,showAvg:i=true,showMedian:m=false,labelPosition:s="end",lineColor:l=h.statsLineColor,labelColor:u=h.statsLabelColor}=this.#e,d=me.compute(e),c=[{name:"min",value:d.min,enabled:o},{name:"max",value:d.max,enabled:n},{name:"avg",value:d.avg,enabled:i},{name:"median",value:d.median,enabled:m}],g=[],[p,b]=r;for(let y of c.filter(f=>f.enabled)){let f=t.map(y.value);g.push({type:"line",x1:p,y1:f,x2:b,y2:f,stroke:l,strokeWidth:1});let S=`${y.name}: ${y.value.toFixed(1)}`;(s==="start"||s==="both")&&g.push({type:"text",content:S,x:p+4,y:f-4,anchor:"start",fontSize:10,fill:u}),(s==="end"||s==="both")&&g.push({type:"text",content:S,x:b-4,y:f-4,anchor:"end",fontSize:10,fill:u}),s==="center"&&g.push({type:"text",content:S,x:(p+b)/2,y:f-4,anchor:"middle",fontSize:10,fill:u});}return g}};var M=class{#e;#t;#n;constructor(e){this.#e=e?.locale||(typeof navigator<"u"?navigator.language:"en-US"),this.#t=e?.fallbackLocales??["en-US"],this.#n=e?.autoFormat??true;}format(e,t,r){try{let o=this.#n&&r?this.#r(e,r,t):t;return new Intl.DateTimeFormat(this.#e,o).format(new Date(e))}catch{for(let o of this.#t)try{return new Intl.DateTimeFormat(o,t).format(new Date(e))}catch{}return new Date(e).toISOString()}}formatRange(e,t){let r=this.format(e),o=this.format(t);return `${r} \u2014 ${o}`}#r(e,t,r){let o=Math.abs(e-t),n=864e5,i=36e5,m=6e4,s={};return o<m?(s.second="2-digit",s.minute="2-digit",s.hour="2-digit"):o<i?(s.minute="2-digit",s.hour="2-digit"):o<n?(s.hour="2-digit",s.minute="2-digit"):o<7*n?(s.weekday="short",s.day="numeric"):o<365*n?(s.month="short",s.day="numeric"):(s.month="short",s.year="numeric"),{...r,...s}}};var D=class a{#e;#t;_compact;#n;constructor(e){this.#e=e?.unit??"",this.#t=e?.decimals??1,this._compact=e?.compact??false,this.#n=e?.custom;}format(e){if(this.#n)return this.#n(e);let t;return this._compact&&Math.abs(e)>=1e6?t=`${(e/1e6).toFixed(this.#t)}M`:this._compact&&Math.abs(e)>=1e3?t=`${(e/1e3).toFixed(this.#t)}k`:t=e.toFixed(this.#t),this.#e?`${t}${this.#e}`:t}formatRange(e,t){return `${this.format(e)} \u2014 ${this.format(t)}`}static unit(e,t=1){return new a({unit:e,decimals:t})}static temperature(e="\xB0C"){return new a({unit:e,decimals:1})}static percentage(){return new a({unit:"%",decimals:0})}};var _=class a{#e;#t;#n;constructor(e){this.#e=e?.map??{},this.#t=e?.fallback??(t=>String(t)),this.#n=e?.showValue??false;}format(e){let t=this.#e[e]??this.#t(e);return this.#n?`(${e}) ${t}`:t}has(e){return e in this.#e}labels(){return Object.values(this.#e)}values(){return Object.keys(this.#e).map(Number)}static fromLabels(e,t=false){let r={};return e.forEach((o,n)=>{r[n]=o;}),new a({map:r,showValue:t})}};var Ie={time:new M,value:new D,enum:new _};var Be={...h};function ie(){return Be}function ae(a,e){let t=ie();return {color:a.color??t.stroke,width:a.width??t.strokeWidth,style:a.style??"solid",smoothing:a.smoothing??false,shape:a.shape??(e.seriesType==="step"?"step":"line"),gapThreshold:a.gapThreshold??t.gapThreshold,opacity:a.opacity??1}}function ce(a){let e=a.style?.line;return e===false?[]:e===void 0?[ae({},a)]:Array.isArray(e)?e.map(t=>ae(t,a)):[ae(e,a)]}function de(a){let e=a.style?.markers;if(!e?.type||e.type==="none")return;let t=ie(),o=(a.style?.line&&!Array.isArray(a.style.line)?a.style.line.color:void 0)??t.stroke;return {type:e.type,size:e.size??t.pointSize,stroke:e.stroke??o,fill:e.fill??"#ffffff",strokeWidth:e.strokeWidth??1,threshold:e.threshold}}function he(a){let e=a.style?.shadow;if(!(!e?.color||e.color==="transparent"||e.color==="none"))return {color:e.color,blur:e.blur??4,offsetX:e.offsetX??0,offsetY:e.offsetY??0}}function pe(a){return a.style?.gap}function fe(a){return a.style?.fill}function He(a){return {lines:ce(a),fill:fe(a),markers:de(a),shadow:he(a),gap:pe(a),id:a.style?.id??a.id}}/*!
|
|
2455
32
|
* MLTimeGraph — Copyright (c) 2026 Michael Lechner
|
|
2456
33
|
* MIT with Attribution: free use incl. commercial requires visible credit to
|
|
2457
34
|
* "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
|
|
2458
35
|
*/
|
|
36
|
+
/*!
|
|
37
|
+
* MLTimeGraph — Copyright (c) 2026 Michael Lechner
|
|
38
|
+
* MIT with Attribution: free use incl. commercial requires visible credit to
|
|
39
|
+
* "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
|
|
40
|
+
*/
|
|
41
|
+
/*!
|
|
42
|
+
* ml-time-analyze — Copyright (c) 2026 Michael Lechner
|
|
43
|
+
* MIT with Attribution: free use incl. commercial requires visible credit to
|
|
44
|
+
* "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
|
|
45
|
+
*/
|
|
2459
46
|
/*!
|
|
2460
47
|
* MLTimeGraph — Copyright (c) 2026 Michael Lechner
|
|
2461
48
|
* MIT with Attribution: free use incl. commercial must have visible credit to
|
|
2462
49
|
* "Michael Lechner". Commercial license (no attribution) on request. See LICENSE.
|
|
2463
|
-
*/
|
|
2464
|
-
|
|
2465
|
-
export { AnnotationBandSeries, BandScale, BandSeries, Clip, EnumAxis, EnumFormatter, Formatters, Layout, LineSeries, LinearScale, MinMaxAvgSeries, Renderer, StatsOverlay, StepSeries, TIME_INTERVALS, TimeAxis, TimeFormatter, TimeScale, ValueAxis, ValueFormatter, ZonedLineSeries, getHatch, measureLegend, renderAnnotations, renderArea, renderGaps, renderGrid, renderHighlights, renderLegend, renderLine, renderMarkers2 as renderMarkers, renderMarkers as renderSeriesMarkers, renderSplitLine, renderStep, renderThresholds, renderZonedArea, resolveFill, resolveGap, resolveLines, resolveMarkers, resolveSeriesStyle, resolveShadow, slugify, theme };
|
|
50
|
+
*/export{re as AnnotationBandSeries,W as BandScale,ee as BandSeries,Z as Clip,K as EnumAxis,_ as EnumFormatter,Ie as Formatters,U as Layout,Q as LineSeries,L as LinearScale,te as MinMaxAvgSeries,E as Renderer,oe as StatsOverlay,J as StepSeries,k as TIME_INTERVALS,O as TimeAxis,M as TimeFormatter,R as TimeScale,q as ValueAxis,D as ValueFormatter,ne as ZonedLineSeries,ue as getHatch,we as measureLegend,Oe as renderAnnotations,xe as renderArea,$e as renderGaps,Te as renderGrid,We as renderHighlights,Ce as renderLegend,w as renderLine,Pe as renderMarkers,P as renderSeriesMarkers,Se as renderSplitLine,V as renderStep,De as renderThresholds,B as renderZonedArea,fe as resolveFill,pe as resolveGap,ce as resolveLines,de as resolveMarkers,He as resolveSeriesStyle,he as resolveShadow,X as slugify,h as theme};
|