@design.estate/dees-catalog 12.5.0 → 13.0.0
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/dist_bundle/bundle.js +149 -69
- package/dist_bundle/bundle.js.map +1 -1
- package/dist_ts_web/00_commitinfo_data.js +1 -1
- package/dist_ts_web/elements/00group-chart/dees-chart-area/component.d.ts +26 -16
- package/dist_ts_web/elements/00group-chart/dees-chart-area/component.js +161 -72
- package/dist_ts_web/elements/00group-chart/dees-chart-area/realtime-updates.d.ts +2 -1
- package/dist_ts_web/elements/00group-chart/dees-chart-area/realtime-updates.js +3 -2
- package/dist_ts_web/elements/00group-chart/dees-chart-area/template.js +4 -3
- package/package.json +1 -1
- package/readme.md +11 -1
- package/ts_web/00_commitinfo_data.ts +1 -1
- package/ts_web/elements/00group-chart/dees-chart-area/component.ts +195 -79
- package/ts_web/elements/00group-chart/dees-chart-area/realtime-updates.ts +4 -2
- package/ts_web/elements/00group-chart/dees-chart-area/template.ts +3 -2
|
@@ -52,7 +52,8 @@ export const planRealtimeChartUpdates = (renderedData, canonicalData) => {
|
|
|
52
52
|
updates.push({ point: canonicalPoint, historicalUpdate: false });
|
|
53
53
|
continue;
|
|
54
54
|
}
|
|
55
|
-
if (renderedPoint.value !== canonicalPoint.value
|
|
55
|
+
if (renderedPoint.value !== canonicalPoint.value
|
|
56
|
+
|| ('value' in renderedPoint) !== ('value' in canonicalPoint)) {
|
|
56
57
|
updates.push({
|
|
57
58
|
point: canonicalPoint,
|
|
58
59
|
historicalUpdate: canonicalPoint.time < renderedLastTime,
|
|
@@ -76,4 +77,4 @@ export const planRealtimeChartUpdates = (renderedData, canonicalData) => {
|
|
|
76
77
|
};
|
|
77
78
|
export const haveSameSeriesLayout = (renderedNames, canonicalNames) => renderedNames.length === canonicalNames.length
|
|
78
79
|
&& renderedNames.every((name, index) => name === canonicalNames[index]);
|
|
79
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
80
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoicmVhbHRpbWUtdXBkYXRlcy5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uLy4uL3RzX3dlYi9lbGVtZW50cy8wMGdyb3VwLWNoYXJ0L2RlZXMtY2hhcnQtYXJlYS9yZWFsdGltZS11cGRhdGVzLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQWtCQTs7OztHQUlHO0FBQ0gsTUFBTSxDQUFDLE1BQU0sd0JBQXdCLEdBQUcsQ0FDdEMsWUFBNEMsRUFDNUMsYUFBNkMsRUFDbkIsRUFBRTtJQUM1QixJQUFJLGFBQWEsQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDL0IsT0FBTztZQUNMLG1CQUFtQixFQUFFLFlBQVksQ0FBQyxNQUFNLEtBQUssQ0FBQztZQUM5QyxPQUFPLEVBQUUsRUFBRTtZQUNYLGdCQUFnQixFQUFFLEtBQUs7WUFDdkIsYUFBYSxFQUFFLEtBQUs7U0FDckIsQ0FBQztJQUNKLENBQUM7SUFDRCxJQUFJLFlBQVksQ0FBQyxNQUFNLEtBQUssQ0FBQyxFQUFFLENBQUM7UUFDOUIsT0FBTztZQUNMLG1CQUFtQixFQUFFLEtBQUs7WUFDMUIsT0FBTyxFQUFFLGFBQWEsQ0FBQyxHQUFHLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLENBQUMsRUFBRSxLQUFLLEVBQUUsZ0JBQWdCLEVBQUUsS0FBSyxFQUFFLENBQUMsQ0FBQztZQUMzRSxnQkFBZ0IsRUFBRSxLQUFLO1lBQ3ZCLGFBQWEsRUFBRSxLQUFLO1NBQ3JCLENBQUM7SUFDSixDQUFDO0lBRUQsTUFBTSxjQUFjLEdBQUcsSUFBSSxHQUFHLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxDQUFDLEtBQUssRUFBRSxFQUFFLENBQUMsQ0FBQyxLQUFLLENBQUMsSUFBSSxFQUFFLEtBQUssQ0FBQyxDQUFDLENBQUMsQ0FBQztJQUNqRixNQUFNLGVBQWUsR0FBRyxJQUFJLEdBQUcsQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsS0FBSyxFQUFFLEVBQUUs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
|
|
@@ -12,7 +12,8 @@ export const renderChartArea = (component) => {
|
|
|
12
12
|
<div class="chartContainer">
|
|
13
13
|
<div class="rangeSelectionOverlay" aria-hidden="true"></div>
|
|
14
14
|
</div>
|
|
15
|
-
${!component.chartSeries.some(series => series.data.
|
|
15
|
+
${!component.chartSeries.some(series => series.data.some(point => point.y !== null))
|
|
16
|
+
? html `<div class="chartState" role="status">No data for this period</div>` : ''}
|
|
16
17
|
${component.seriesStats.length > 0 && !component.hideLegend ? html `
|
|
17
18
|
<div slot="footer" class="statsBar">
|
|
18
19
|
${component.seriesStats.map(s => html `
|
|
@@ -22,7 +23,7 @@ export const renderChartArea = (component) => {
|
|
|
22
23
|
${component.legendStats
|
|
23
24
|
.filter(key => key === 'latest' || key === 'min' || key === 'max' || key === 'avg')
|
|
24
25
|
.map(key => html `
|
|
25
|
-
<span class="statsItem">${key} <strong>${component.yAxisFormatter(s[key])}</strong></span>
|
|
26
|
+
<span class="statsItem">${key} <strong>${s[key] === null ? '—' : component.yAxisFormatter(s[key])}</strong></span>
|
|
26
27
|
`)}
|
|
27
28
|
</div>
|
|
28
29
|
`)}
|
|
@@ -31,4 +32,4 @@ export const renderChartArea = (component) => {
|
|
|
31
32
|
</dees-tile>
|
|
32
33
|
`;
|
|
33
34
|
};
|
|
34
|
-
//# sourceMappingURL=data:application/json;base64,
|
|
35
|
+
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoidGVtcGxhdGUuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90c193ZWIvZWxlbWVudHMvMDBncm91cC1jaGFydC9kZWVzLWNoYXJ0LWFyZWEvdGVtcGxhdGUudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLElBQUksRUFBdUIsTUFBTSw2QkFBNkIsQ0FBQztBQUV4RSxPQUFPLDhDQUE4QyxDQUFDO0FBRXRELE1BQU0sQ0FBQyxNQUFNLGVBQWUsR0FBRyxDQUFDLFNBQXdCLEVBQWtCLEVBQUU7SUFDdEUsT0FBTyxJQUFJLENBQUE7OztxQ0FHb0IsU0FBUyxDQUFDLEtBQUs7K0NBQ0wsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsZ0JBQWdCLENBQUMsQ0FBQyxDQUFDLGNBQWMsNkJBQTZCLEdBQUcsRUFBRSxDQUFDLFNBQVMsQ0FBQyxjQUFjLEVBQUUsV0FBVyxTQUFTLENBQUMsVUFBVSxDQUFDLENBQUMsQ0FBQyxnQkFBZ0IsQ0FBQyxDQUFDLENBQUMsV0FBVztpQ0FDbk0sU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsa0JBQWtCLENBQUMsQ0FBQyxDQUFDLGtCQUFrQixjQUFjLEVBQUU7Ozs7OztZQU1uRyxDQUFDLFNBQVMsQ0FBQyxXQUFXLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsS0FBSyxDQUFDLEVBQUUsQ0FBQyxLQUFLLENBQUMsQ0FBQyxLQUFLLElBQUksQ0FBQyxDQUFDO1FBQ2xGLENBQUMsQ0FBQyxJQUFJLENBQUEscUVBQXFFLENBQUMsQ0FBQyxDQUFDLEVBQUU7WUFDaEYsU0FBUyxDQUFDLFdBQVcsQ0FBQyxNQUFNLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLFVBQVUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFBOztnQkFFNUQsU0FBUyxDQUFDLFdBQVcsQ0FBQyxHQUFHLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUE7OytEQUVZLENBQUMsQ0FBQyxLQUFLOzRDQUMxQixDQUFDLENBQUMsSUFBSTtvQkFDOUIsU0FBUyxDQUFDLFdBQVc7U0FDcEIsTUFBTSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUMsR0FBRyxLQUFLLFFBQVEsSUFBSSxHQUFHLEtBQUssS0FBSyxJQUFJLEdBQUcsS0FBSyxLQUFLLElBQUksR0FBRyxLQUFLLEtBQUssQ0FBQztTQUNsRixHQUFHLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUE7Z0RBQ1ksR0FBRyxZQUFZLENBQUMsQ0FBQyxHQUFHLENBQUMsS0FBSyxJQUFJLENBQUMsQ0FBQyxDQUFDLEdBQUcsQ0FBQyxDQUFDLENBQUMsU0FBUyxDQUFDLGNBQWMsQ0FBQyxDQUFDLENBQUMsR0FBRyxDQUFDLENBQUM7cUJBQ2xHLENBQUM7O2VBRVAsQ0FBQzs7V0FFTCxDQUFDLENBQUMsQ0FBQyxFQUFFOztPQUVULENBQUM7QUFDUixDQUFDLENBQUMifQ==
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@design.estate/dees-catalog",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "13.0.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.",
|
|
6
6
|
"main": "dist_ts_web/index.js",
|
package/readme.md
CHANGED
|
@@ -2152,6 +2152,16 @@ bubbling `range-change` event carries `{ from, to }` as Unix epoch
|
|
|
2152
2152
|
milliseconds. Apply that range back through `selectedRange` to keep the
|
|
2153
2153
|
selection visible; set it to `null` to clear the overlay.
|
|
2154
2154
|
|
|
2155
|
+
Use `y: null` for an unavailable reading. Its timestamp remains on the time
|
|
2156
|
+
axis, and the area line visibly stops until the next numeric reading. Supply
|
|
2157
|
+
one null point for each missing sampling interval when the width of an outage
|
|
2158
|
+
should reflect its duration. The chart keeps one legend entry per named input
|
|
2159
|
+
series. Legend statistics omit null readings; `latest` is unavailable when the
|
|
2160
|
+
newest reading is null, while `min`, `max`, and `avg` describe available
|
|
2161
|
+
readings in the selected window. An unavailable statistic is displayed as `—`.
|
|
2162
|
+
With `realtimeMode=true` and `rollingWindow=3_600_000`, the time axis follows
|
|
2163
|
+
the last hour even while the latest reading is unavailable.
|
|
2164
|
+
|
|
2155
2165
|
```typescript
|
|
2156
2166
|
<dees-chart-area
|
|
2157
2167
|
label="System Usage"
|
|
@@ -2162,7 +2172,7 @@ selection visible; set it to `null` to clear the overlay.
|
|
|
2162
2172
|
name: 'CPU',
|
|
2163
2173
|
data: [
|
|
2164
2174
|
{ x: '2025-01-15T03:00:00', y: 25 },
|
|
2165
|
-
{ x: '2025-01-15T07:00:00', y:
|
|
2175
|
+
{ x: '2025-01-15T07:00:00', y: null }, // unavailable
|
|
2166
2176
|
{ x: '2025-01-15T11:00:00', y: 20 }
|
|
2167
2177
|
]
|
|
2168
2178
|
}
|
|
@@ -3,6 +3,6 @@
|
|
|
3
3
|
*/
|
|
4
4
|
export const commitinfo = {
|
|
5
5
|
name: '@design.estate/dees-catalog',
|
|
6
|
-
version: '
|
|
6
|
+
version: '13.0.0',
|
|
7
7
|
description: 'A comprehensive library that provides dynamic web components for building sophisticated and modern web applications using JavaScript and TypeScript.'
|
|
8
8
|
}
|
|
@@ -19,7 +19,14 @@ import { getEchartsSeriesColors, hexToRgba, getThemeColors } from '../dees-chart
|
|
|
19
19
|
|
|
20
20
|
import { systemFontFamily } from '../../00fonts.js';
|
|
21
21
|
|
|
22
|
-
import type {
|
|
22
|
+
import type {
|
|
23
|
+
AreaData,
|
|
24
|
+
IChartApi,
|
|
25
|
+
ISeriesApi,
|
|
26
|
+
MouseEventParams,
|
|
27
|
+
UTCTimestamp,
|
|
28
|
+
WhitespaceData,
|
|
29
|
+
} from 'lightweight-charts';
|
|
23
30
|
import { DeesServiceLibLoader, type ILightweightChartsBundle } from '../../../services/index.js';
|
|
24
31
|
import '../../00group-layout/dees-tile/dees-tile.js';
|
|
25
32
|
|
|
@@ -27,9 +34,28 @@ export type ChartSeriesConfig = {
|
|
|
27
34
|
name?: string;
|
|
28
35
|
/** Explicit series color (hex or hsl). Falls back to the shared catalog palette by index. */
|
|
29
36
|
color?: string;
|
|
30
|
-
|
|
37
|
+
/** A null reading keeps its timestamp and breaks the drawn area line. */
|
|
38
|
+
data: Array<{ x: any; y: number | null }>;
|
|
31
39
|
}[];
|
|
32
40
|
|
|
41
|
+
export interface IChartSeriesStats {
|
|
42
|
+
name: string;
|
|
43
|
+
/** Null when the newest sample is unavailable, even if older samples exist. */
|
|
44
|
+
latest: number | null;
|
|
45
|
+
min: number | null;
|
|
46
|
+
max: number | null;
|
|
47
|
+
avg: number | null;
|
|
48
|
+
color: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
type TChartAreaData = AreaData<UTCTimestamp> | WhitespaceData<UTCTimestamp>;
|
|
52
|
+
|
|
53
|
+
interface IChartRenderedSegment {
|
|
54
|
+
key: string;
|
|
55
|
+
logicalIndex: number;
|
|
56
|
+
data: TChartAreaData[];
|
|
57
|
+
}
|
|
58
|
+
|
|
33
59
|
export type TChartLegendStat = 'latest' | 'min' | 'max' | 'avg';
|
|
34
60
|
export type TChartPriceLine = 'avg' | 'max';
|
|
35
61
|
|
|
@@ -55,7 +81,7 @@ export class DeesChartArea extends DeesElement {
|
|
|
55
81
|
accessor chart: IChartApi | null = null;
|
|
56
82
|
|
|
57
83
|
@state()
|
|
58
|
-
accessor seriesStats:
|
|
84
|
+
accessor seriesStats: IChartSeriesStats[] = [];
|
|
59
85
|
|
|
60
86
|
@state()
|
|
61
87
|
accessor isFullPage: boolean = false;
|
|
@@ -117,6 +143,9 @@ export class DeesChartArea extends DeesElement {
|
|
|
117
143
|
private autoScrollTimer: number | null = null;
|
|
118
144
|
private lcBundle: ILightweightChartsBundle | null = null;
|
|
119
145
|
private seriesApis: Map<string, ISeriesApi<any>> = new Map();
|
|
146
|
+
/** LC's data() omits whitespace; this mirrors the exact points sent to each series API. */
|
|
147
|
+
private renderedSeriesData: Map<string, IRealtimeChartPoint[]> = new Map();
|
|
148
|
+
private renderedSegments: IChartRenderedSegment[] = [];
|
|
120
149
|
private priceLines: Map<string, any[]> = new Map();
|
|
121
150
|
private tooltipEl: HTMLDivElement | null = null;
|
|
122
151
|
private crosshairActive = false;
|
|
@@ -219,6 +248,8 @@ export class DeesChartArea extends DeesElement {
|
|
|
219
248
|
console.error('Error destroying chart:', error);
|
|
220
249
|
} finally {
|
|
221
250
|
this.seriesApis.clear();
|
|
251
|
+
this.renderedSeriesData.clear();
|
|
252
|
+
this.renderedSegments = [];
|
|
222
253
|
this.priceLines.clear();
|
|
223
254
|
}
|
|
224
255
|
});
|
|
@@ -316,15 +347,17 @@ export class DeesChartArea extends DeesElement {
|
|
|
316
347
|
this.paintRangeOverlay(fromCoordinate, toCoordinate);
|
|
317
348
|
}
|
|
318
349
|
|
|
319
|
-
private convertDataToLC(data:
|
|
320
|
-
const pointsByTime = new Map<number,
|
|
350
|
+
private convertDataToLC(data: ChartSeriesConfig[number]['data']): TChartAreaData[] {
|
|
351
|
+
const pointsByTime = new Map<number, TChartAreaData>();
|
|
321
352
|
for (const point of data) {
|
|
322
353
|
const ms = typeof point.x === 'number' ? point.x : new Date(point.x).getTime();
|
|
323
354
|
const time = Math.floor(ms / 1000);
|
|
324
|
-
if (!Number.isFinite(time) || !Number.isFinite(point.y)) continue;
|
|
355
|
+
if (!Number.isFinite(time) || (point.y !== null && !Number.isFinite(point.y))) continue;
|
|
325
356
|
// Lightweight Charts requires unique ordered timestamps. The latest
|
|
326
|
-
// sample
|
|
327
|
-
pointsByTime.set(time,
|
|
357
|
+
// sample, including an unavailable one, wins within the same second.
|
|
358
|
+
pointsByTime.set(time, point.y === null
|
|
359
|
+
? { time: time as UTCTimestamp }
|
|
360
|
+
: { time: time as UTCTimestamp, value: point.y });
|
|
328
361
|
}
|
|
329
362
|
return [...pointsByTime.values()]
|
|
330
363
|
.sort((a, b) => (a.time as number) - (b.time as number));
|
|
@@ -338,6 +371,45 @@ export class DeesChartArea extends DeesElement {
|
|
|
338
371
|
return `${index}:${this.getSeriesName(series, index)}`;
|
|
339
372
|
}
|
|
340
373
|
|
|
374
|
+
/**
|
|
375
|
+
* Lightweight Charts joins Area points across whitespace. Render each
|
|
376
|
+
* uninterrupted numeric run as its own Area series instead. All missing
|
|
377
|
+
* timestamps stay on the first real series so the time scale keeps them;
|
|
378
|
+
* there is no synthetic numeric value or extra public legend series.
|
|
379
|
+
*/
|
|
380
|
+
private getRenderedSegments(chartSeries: ChartSeriesConfig): IChartRenderedSegment[] {
|
|
381
|
+
const renderedSegments: IChartRenderedSegment[] = [];
|
|
382
|
+
chartSeries.forEach((series, logicalIndex) => {
|
|
383
|
+
const points = this.convertDataToLC(series.data);
|
|
384
|
+
const missingPoints = points.filter((point) => !('value' in point));
|
|
385
|
+
const numericRuns: TChartAreaData[][] = [];
|
|
386
|
+
let currentRun: TChartAreaData[] = [];
|
|
387
|
+
for (const point of points) {
|
|
388
|
+
if ('value' in point) {
|
|
389
|
+
currentRun.push(point);
|
|
390
|
+
} else if (currentRun.length > 0) {
|
|
391
|
+
numericRuns.push(currentRun);
|
|
392
|
+
currentRun = [];
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
if (currentRun.length > 0) numericRuns.push(currentRun);
|
|
396
|
+
if (numericRuns.length === 0) numericRuns.push([]);
|
|
397
|
+
|
|
398
|
+
const baseKey = this.getSeriesKey(series, logicalIndex);
|
|
399
|
+
numericRuns.forEach((run, segmentIndex) => {
|
|
400
|
+
const data = segmentIndex === 0
|
|
401
|
+
? [...run, ...missingPoints].sort((left, right) => left.time - right.time)
|
|
402
|
+
: run;
|
|
403
|
+
renderedSegments.push({
|
|
404
|
+
key: segmentIndex === 0 ? baseKey : `${baseKey}#${segmentIndex}`,
|
|
405
|
+
logicalIndex,
|
|
406
|
+
data,
|
|
407
|
+
});
|
|
408
|
+
});
|
|
409
|
+
});
|
|
410
|
+
return renderedSegments;
|
|
411
|
+
}
|
|
412
|
+
|
|
341
413
|
private getCanonicalChartSeries(chartSeries: ChartSeriesConfig): ChartSeriesConfig {
|
|
342
414
|
const cutoffTime = Date.now() - this.rollingWindow;
|
|
343
415
|
return chartSeries.map((series) => ({
|
|
@@ -348,15 +420,34 @@ export class DeesChartArea extends DeesElement {
|
|
|
348
420
|
return ms > cutoffTime;
|
|
349
421
|
})).map((point) => ({
|
|
350
422
|
x: (point.time as number) * 1000,
|
|
351
|
-
y: point.value,
|
|
423
|
+
y: 'value' in point ? point.value : null,
|
|
352
424
|
})),
|
|
353
425
|
}));
|
|
354
426
|
}
|
|
355
427
|
|
|
356
|
-
private getRenderedSeriesData(
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
428
|
+
private getRenderedSeriesData(segmentKey: string): IRealtimeChartPoint[] {
|
|
429
|
+
const renderedData = this.renderedSeriesData.get(segmentKey);
|
|
430
|
+
if (!renderedData) throw new Error(`Missing rendered chart data for ${segmentKey}`);
|
|
431
|
+
return renderedData;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
private rememberRenderedSeriesData(segmentKey: string, data: readonly TChartAreaData[]): void {
|
|
435
|
+
this.renderedSeriesData.set(segmentKey, data
|
|
436
|
+
.map((point) => 'value' in point
|
|
437
|
+
? { time: point.time as number, value: point.value }
|
|
438
|
+
: { time: point.time as number }));
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
private rememberIncrementalUpdate(segmentKey: string, point: IRealtimeChartPoint): void {
|
|
442
|
+
const renderedData = this.getRenderedSeriesData(segmentKey);
|
|
443
|
+
const existingIndex = renderedData.findIndex((entry) => entry.time === point.time);
|
|
444
|
+
if (existingIndex >= 0) renderedData[existingIndex] = point;
|
|
445
|
+
else renderedData.push(point);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
private toLcPoint(point: IRealtimeChartPoint): TChartAreaData {
|
|
449
|
+
const time = point.time as UTCTimestamp;
|
|
450
|
+
return point.value === undefined ? { time } : { time, value: point.value };
|
|
360
451
|
}
|
|
361
452
|
|
|
362
453
|
private colorToRgba(color: string, alpha: number): string {
|
|
@@ -423,19 +514,21 @@ export class DeesChartArea extends DeesElement {
|
|
|
423
514
|
},
|
|
424
515
|
} as any);
|
|
425
516
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
517
|
+
for (const segment of this.renderedSegments) {
|
|
518
|
+
const api = this.seriesApis.get(segment.key);
|
|
519
|
+
if (!api) continue;
|
|
520
|
+
const color = this.resolveSeriesColor(
|
|
521
|
+
this.chartSeries[segment.logicalIndex], segment.logicalIndex, isDark,
|
|
522
|
+
);
|
|
429
523
|
api.applyOptions({
|
|
430
524
|
topColor: this.colorToRgba(color, isDark ? 0.4 : 0.5),
|
|
431
525
|
bottomColor: this.colorToRgba(color, 0),
|
|
432
526
|
lineColor: color,
|
|
433
527
|
});
|
|
434
|
-
idx++;
|
|
435
528
|
}
|
|
436
529
|
}
|
|
437
530
|
|
|
438
|
-
private recreateSeries(chartSeries: ChartSeriesConfig) {
|
|
531
|
+
private recreateSeries(chartSeries: ChartSeriesConfig, renderedSegments = this.getRenderedSegments(chartSeries)) {
|
|
439
532
|
if (!this.chart || !this.lcBundle) return;
|
|
440
533
|
const isDark = !this.goBright;
|
|
441
534
|
|
|
@@ -443,10 +536,13 @@ export class DeesChartArea extends DeesElement {
|
|
|
443
536
|
this.chart.removeSeries(api);
|
|
444
537
|
}
|
|
445
538
|
this.seriesApis.clear();
|
|
539
|
+
this.renderedSeriesData.clear();
|
|
446
540
|
this.priceLines.clear();
|
|
541
|
+
this.renderedSegments = renderedSegments;
|
|
447
542
|
|
|
448
|
-
|
|
449
|
-
const
|
|
543
|
+
renderedSegments.forEach((segment) => {
|
|
544
|
+
const series = chartSeries[segment.logicalIndex];
|
|
545
|
+
const color = this.resolveSeriesColor(series, segment.logicalIndex, isDark);
|
|
450
546
|
const api = this.chart!.addSeries(this.lcBundle!.AreaSeries, {
|
|
451
547
|
topColor: this.colorToRgba(color, isDark ? 0.4 : 0.5),
|
|
452
548
|
bottomColor: this.colorToRgba(color, 0),
|
|
@@ -459,10 +555,12 @@ export class DeesChartArea extends DeesElement {
|
|
|
459
555
|
crosshairMarkerRadius: 4,
|
|
460
556
|
});
|
|
461
557
|
|
|
462
|
-
api.setData(
|
|
463
|
-
this.
|
|
464
|
-
|
|
465
|
-
|
|
558
|
+
api.setData(segment.data);
|
|
559
|
+
this.seriesApis.set(segment.key, api);
|
|
560
|
+
this.rememberRenderedSeriesData(segment.key, segment.data);
|
|
561
|
+
if (segment.key === this.getSeriesKey(series, segment.logicalIndex)) {
|
|
562
|
+
this.updatePriceLines(segment.key, api, series.data, color);
|
|
563
|
+
}
|
|
466
564
|
});
|
|
467
565
|
this.applyYAxisScale(chartSeries);
|
|
468
566
|
this.computeStats(chartSeries);
|
|
@@ -477,17 +575,19 @@ export class DeesChartArea extends DeesElement {
|
|
|
477
575
|
|
|
478
576
|
private updateSeriesPresentation(chartSeries: ChartSeriesConfig) {
|
|
479
577
|
const isDark = !this.goBright;
|
|
480
|
-
|
|
481
|
-
const
|
|
482
|
-
const api = this.seriesApis.get(
|
|
578
|
+
this.renderedSegments.forEach((segment) => {
|
|
579
|
+
const series = chartSeries[segment.logicalIndex];
|
|
580
|
+
const api = this.seriesApis.get(segment.key);
|
|
483
581
|
if (!api) return;
|
|
484
|
-
const color = this.resolveSeriesColor(series,
|
|
582
|
+
const color = this.resolveSeriesColor(series, segment.logicalIndex, isDark);
|
|
485
583
|
api.applyOptions({
|
|
486
584
|
topColor: this.colorToRgba(color, isDark ? 0.4 : 0.5),
|
|
487
585
|
bottomColor: this.colorToRgba(color, 0),
|
|
488
586
|
lineColor: color,
|
|
489
587
|
});
|
|
490
|
-
this.
|
|
588
|
+
if (segment.key === this.getSeriesKey(series, segment.logicalIndex)) {
|
|
589
|
+
this.updatePriceLines(segment.key, api, series.data, color);
|
|
590
|
+
}
|
|
491
591
|
});
|
|
492
592
|
this.computeStats(chartSeries);
|
|
493
593
|
|
|
@@ -500,7 +600,7 @@ export class DeesChartArea extends DeesElement {
|
|
|
500
600
|
if (this.yAxisScaling === 'dynamic') {
|
|
501
601
|
maxValue = 1;
|
|
502
602
|
for (const entry of series) for (const point of entry.data) {
|
|
503
|
-
if (!Number.isFinite(point.y)) continue;
|
|
603
|
+
if (point.y === null || !Number.isFinite(point.y)) continue;
|
|
504
604
|
minValue = Math.min(minValue, Math.floor(point.y * 1.1));
|
|
505
605
|
maxValue = Math.max(maxValue, Math.ceil(point.y * 1.1));
|
|
506
606
|
}
|
|
@@ -533,22 +633,39 @@ export class DeesChartArea extends DeesElement {
|
|
|
533
633
|
) return;
|
|
534
634
|
|
|
535
635
|
const canonicalSeries = this.getCanonicalChartSeries(this.internalChartData);
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
636
|
+
const canonicalSegments = this.getRenderedSegments(canonicalSeries);
|
|
637
|
+
if (!haveSameSeriesLayout(
|
|
638
|
+
[...this.seriesApis.keys()], canonicalSegments.map((segment) => segment.key),
|
|
639
|
+
)) {
|
|
640
|
+
this.clearCrosshairForDataReplacement();
|
|
641
|
+
this.internalChartData = canonicalSeries;
|
|
642
|
+
this.recreateSeries(canonicalSeries, canonicalSegments);
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
canonicalSegments.forEach((segment) => {
|
|
646
|
+
const api = this.seriesApis.get(segment.key);
|
|
647
|
+
if (api) {
|
|
648
|
+
api.setData(segment.data);
|
|
649
|
+
this.rememberRenderedSeriesData(segment.key, segment.data);
|
|
650
|
+
}
|
|
539
651
|
});
|
|
652
|
+
this.internalChartData = canonicalSeries;
|
|
653
|
+
this.renderedSegments = canonicalSegments;
|
|
540
654
|
this.pendingRealtimePrune = false;
|
|
655
|
+
this.updateSeriesPresentation(canonicalSeries);
|
|
541
656
|
}
|
|
542
657
|
|
|
543
658
|
private computeStats(chartSeries: ChartSeriesConfig) {
|
|
544
659
|
const isDark = !this.goBright;
|
|
545
660
|
this.seriesStats = chartSeries.map((s, index) => {
|
|
546
661
|
const color = this.resolveSeriesColor(s, index, isDark);
|
|
547
|
-
const values = s.data
|
|
662
|
+
const values = s.data
|
|
663
|
+
.map((point) => point.y)
|
|
664
|
+
.filter((value): value is number => value !== null && Number.isFinite(value));
|
|
665
|
+
const latest = s.data.length > 0 ? s.data[s.data.length - 1].y : null;
|
|
548
666
|
if (values.length === 0) {
|
|
549
|
-
return { name: s.name || `series-${index}`, latest:
|
|
667
|
+
return { name: s.name || `series-${index}`, latest: null, min: null, max: null, avg: null, color };
|
|
550
668
|
}
|
|
551
|
-
const latest = values[values.length - 1];
|
|
552
669
|
const min = Math.min(...values);
|
|
553
670
|
const max = Math.max(...values);
|
|
554
671
|
const avg = Math.round((values.reduce((sum, v) => sum + v, 0) / values.length) * 100) / 100;
|
|
@@ -556,7 +673,7 @@ export class DeesChartArea extends DeesElement {
|
|
|
556
673
|
});
|
|
557
674
|
}
|
|
558
675
|
|
|
559
|
-
private updatePriceLines(name: string, api: ISeriesApi<any>, data:
|
|
676
|
+
private updatePriceLines(name: string, api: ISeriesApi<any>, data: ChartSeriesConfig[number]['data'], color: string) {
|
|
560
677
|
// Remove existing price lines for this series
|
|
561
678
|
const existing = this.priceLines.get(name);
|
|
562
679
|
if (existing) {
|
|
@@ -565,12 +682,14 @@ export class DeesChartArea extends DeesElement {
|
|
|
565
682
|
}
|
|
566
683
|
}
|
|
567
684
|
|
|
568
|
-
|
|
685
|
+
const values = data
|
|
686
|
+
.map((point) => point.y)
|
|
687
|
+
.filter((value): value is number => value !== null && Number.isFinite(value));
|
|
688
|
+
if (values.length === 0) {
|
|
569
689
|
this.priceLines.set(name, []);
|
|
570
690
|
return;
|
|
571
691
|
}
|
|
572
692
|
|
|
573
|
-
const values = data.map(d => d.y);
|
|
574
693
|
const avg = values.reduce((sum, v) => sum + v, 0) / values.length;
|
|
575
694
|
const max = Math.max(...values);
|
|
576
695
|
|
|
@@ -627,23 +746,24 @@ export class DeesChartArea extends DeesElement {
|
|
|
627
746
|
const borderColor = isDark ? 'hsl(0 0% 14.9%)' : 'hsl(0 0% 89.8%)';
|
|
628
747
|
|
|
629
748
|
let html = '';
|
|
630
|
-
let idx = 0;
|
|
631
749
|
let hasData = false;
|
|
632
|
-
for (const
|
|
750
|
+
for (const segment of this.renderedSegments) {
|
|
751
|
+
const api = this.seriesApis.get(segment.key);
|
|
752
|
+
if (!api) continue;
|
|
633
753
|
const data = param.seriesData.get(api);
|
|
634
|
-
if (data && 'value' in data &&
|
|
754
|
+
if (data && 'value' in data && typeof data.value === 'number') {
|
|
755
|
+
const marginTop = hasData ? '6px' : '0';
|
|
635
756
|
hasData = true;
|
|
636
|
-
const series = this.internalChartData[
|
|
637
|
-
const name =
|
|
638
|
-
const color = this.resolveSeriesColor(series,
|
|
639
|
-
const formatted = this.yAxisFormatter(
|
|
640
|
-
html += `<div style="display:flex;align-items:center;gap:8px;margin:${
|
|
757
|
+
const series = this.internalChartData[segment.logicalIndex];
|
|
758
|
+
const name = this.getSeriesName(series, segment.logicalIndex);
|
|
759
|
+
const color = this.resolveSeriesColor(series, segment.logicalIndex, isDark);
|
|
760
|
+
const formatted = this.yAxisFormatter(data.value);
|
|
761
|
+
html += `<div style="display:flex;align-items:center;gap:8px;margin:${marginTop} 0;">
|
|
641
762
|
<span style="display:inline-block;width:10px;height:10px;background:${color};border-radius:2px;"></span>
|
|
642
763
|
<span style="font-weight:500;">${name}:</span>
|
|
643
764
|
<span style="margin-left:auto;font-weight:600;">${formatted}</span>
|
|
644
765
|
</div>`;
|
|
645
766
|
}
|
|
646
|
-
idx++;
|
|
647
767
|
}
|
|
648
768
|
|
|
649
769
|
if (!hasData) {
|
|
@@ -786,55 +906,55 @@ export class DeesChartArea extends DeesElement {
|
|
|
786
906
|
try {
|
|
787
907
|
void animate;
|
|
788
908
|
const canonicalSeries = this.getCanonicalChartSeries(newSeries);
|
|
909
|
+
const renderedSegments = this.getRenderedSegments(canonicalSeries);
|
|
789
910
|
this.internalChartData = canonicalSeries;
|
|
790
911
|
const renderedKeys = [...this.seriesApis.keys()];
|
|
791
|
-
const canonicalKeys =
|
|
912
|
+
const canonicalKeys = renderedSegments.map((segment) => segment.key);
|
|
792
913
|
|
|
793
914
|
if (!haveSameSeriesLayout(renderedKeys, canonicalKeys)) {
|
|
794
915
|
this.clearCrosshairForDataReplacement();
|
|
795
|
-
this.recreateSeries(canonicalSeries);
|
|
916
|
+
this.recreateSeries(canonicalSeries, renderedSegments);
|
|
796
917
|
this.updateSeriesPresentation(canonicalSeries);
|
|
797
918
|
return;
|
|
798
919
|
}
|
|
920
|
+
this.renderedSegments = renderedSegments;
|
|
799
921
|
|
|
800
922
|
if (!this.realtimeMode || this.rollingWindow <= 0) {
|
|
801
923
|
this.clearCrosshairForDataReplacement();
|
|
802
|
-
|
|
803
|
-
this.seriesApis.get(
|
|
804
|
-
|
|
924
|
+
renderedSegments.forEach((segment) => {
|
|
925
|
+
this.seriesApis.get(segment.key)!.setData(segment.data);
|
|
926
|
+
this.rememberRenderedSeriesData(segment.key, segment.data);
|
|
805
927
|
});
|
|
806
928
|
this.pendingRealtimePrune = false;
|
|
807
929
|
this.updateSeriesPresentation(canonicalSeries);
|
|
808
930
|
return;
|
|
809
931
|
}
|
|
810
932
|
|
|
811
|
-
const plannedUpdates =
|
|
812
|
-
const api = this.seriesApis.get(
|
|
813
|
-
const canonicalData =
|
|
814
|
-
|
|
933
|
+
const plannedUpdates = renderedSegments.map((segment) => {
|
|
934
|
+
const api = this.seriesApis.get(segment.key)!;
|
|
935
|
+
const canonicalData = segment.data.map((point) => 'value' in point
|
|
936
|
+
? { time: point.time as number, value: point.value }
|
|
937
|
+
: { time: point.time as number });
|
|
815
938
|
return {
|
|
939
|
+
key: segment.key,
|
|
816
940
|
api,
|
|
817
941
|
canonicalData,
|
|
818
|
-
plan: planRealtimeChartUpdates(this.getRenderedSeriesData(
|
|
942
|
+
plan: planRealtimeChartUpdates(this.getRenderedSeriesData(segment.key), canonicalData),
|
|
819
943
|
};
|
|
820
944
|
});
|
|
821
945
|
|
|
822
946
|
if (plannedUpdates.some(({ plan }) => plan.replacementRequired)) {
|
|
823
947
|
this.clearCrosshairForDataReplacement();
|
|
824
|
-
for (const { api, canonicalData } of plannedUpdates) {
|
|
825
|
-
api.setData(canonicalData.map((point) => (
|
|
826
|
-
|
|
827
|
-
value: point.value,
|
|
828
|
-
})));
|
|
948
|
+
for (const { key, api, canonicalData } of plannedUpdates) {
|
|
949
|
+
api.setData(canonicalData.map((point) => this.toLcPoint(point)));
|
|
950
|
+
this.renderedSeriesData.set(key, [...canonicalData]);
|
|
829
951
|
}
|
|
830
952
|
this.pendingRealtimePrune = false;
|
|
831
953
|
} else {
|
|
832
|
-
for (const { api, plan } of plannedUpdates) {
|
|
954
|
+
for (const { key, api, plan } of plannedUpdates) {
|
|
833
955
|
for (const update of plan.updates) {
|
|
834
|
-
api.update(
|
|
835
|
-
|
|
836
|
-
value: update.point.value,
|
|
837
|
-
}, update.historicalUpdate);
|
|
956
|
+
api.update(this.toLcPoint(update.point), update.historicalUpdate);
|
|
957
|
+
this.rememberIncrementalUpdate(key, update.point);
|
|
838
958
|
}
|
|
839
959
|
}
|
|
840
960
|
|
|
@@ -842,20 +962,16 @@ export class DeesChartArea extends DeesElement {
|
|
|
842
962
|
const pruneRecommended = plannedUpdates.some(({ plan }) => plan.pruneRecommended);
|
|
843
963
|
if (pruneRequired) {
|
|
844
964
|
this.clearCrosshairForDataReplacement();
|
|
845
|
-
for (const { api, canonicalData } of plannedUpdates) {
|
|
846
|
-
api.setData(canonicalData.map((point) => (
|
|
847
|
-
|
|
848
|
-
value: point.value,
|
|
849
|
-
})));
|
|
965
|
+
for (const { key, api, canonicalData } of plannedUpdates) {
|
|
966
|
+
api.setData(canonicalData.map((point) => this.toLcPoint(point)));
|
|
967
|
+
this.renderedSeriesData.set(key, [...canonicalData]);
|
|
850
968
|
}
|
|
851
969
|
} else if (pruneRecommended && this.crosshairActive) {
|
|
852
970
|
this.pendingRealtimePrune = true;
|
|
853
971
|
} else if (pruneRecommended) {
|
|
854
|
-
for (const { api, canonicalData } of plannedUpdates) {
|
|
855
|
-
api.setData(canonicalData.map((point) => (
|
|
856
|
-
|
|
857
|
-
value: point.value,
|
|
858
|
-
})));
|
|
972
|
+
for (const { key, api, canonicalData } of plannedUpdates) {
|
|
973
|
+
api.setData(canonicalData.map((point) => this.toLcPoint(point)));
|
|
974
|
+
this.renderedSeriesData.set(key, [...canonicalData]);
|
|
859
975
|
}
|
|
860
976
|
this.pendingRealtimePrune = false;
|
|
861
977
|
}
|
|
@@ -879,7 +995,7 @@ export class DeesChartArea extends DeesElement {
|
|
|
879
995
|
} catch (e) { /* range may be invalid */ }
|
|
880
996
|
}
|
|
881
997
|
|
|
882
|
-
public async appendData(newData:
|
|
998
|
+
public async appendData(newData: ChartSeriesConfig) {
|
|
883
999
|
if (!this.chart) return;
|
|
884
1000
|
const nextSeries = this.internalChartData.map((series) => ({
|
|
885
1001
|
...series,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export interface IRealtimeChartPoint {
|
|
2
2
|
time: number;
|
|
3
|
-
|
|
3
|
+
/** Omitted for a time point where this series has no reading. */
|
|
4
|
+
value?: number;
|
|
4
5
|
}
|
|
5
6
|
|
|
6
7
|
export interface IRealtimeChartUpdate {
|
|
@@ -78,7 +79,8 @@ export const planRealtimeChartUpdates = (
|
|
|
78
79
|
continue;
|
|
79
80
|
}
|
|
80
81
|
|
|
81
|
-
if (renderedPoint.value !== canonicalPoint.value
|
|
82
|
+
if (renderedPoint.value !== canonicalPoint.value
|
|
83
|
+
|| ('value' in renderedPoint) !== ('value' in canonicalPoint)) {
|
|
82
84
|
updates.push({
|
|
83
85
|
point: canonicalPoint,
|
|
84
86
|
historicalUpdate: canonicalPoint.time < renderedLastTime,
|
|
@@ -14,7 +14,8 @@ export const renderChartArea = (component: DeesChartArea): TemplateResult => {
|
|
|
14
14
|
<div class="chartContainer">
|
|
15
15
|
<div class="rangeSelectionOverlay" aria-hidden="true"></div>
|
|
16
16
|
</div>
|
|
17
|
-
${!component.chartSeries.some(series => series.data.
|
|
17
|
+
${!component.chartSeries.some(series => series.data.some(point => point.y !== null))
|
|
18
|
+
? html`<div class="chartState" role="status">No data for this period</div>` : ''}
|
|
18
19
|
${component.seriesStats.length > 0 && !component.hideLegend ? html`
|
|
19
20
|
<div slot="footer" class="statsBar">
|
|
20
21
|
${component.seriesStats.map(s => html`
|
|
@@ -24,7 +25,7 @@ export const renderChartArea = (component: DeesChartArea): TemplateResult => {
|
|
|
24
25
|
${component.legendStats
|
|
25
26
|
.filter(key => key === 'latest' || key === 'min' || key === 'max' || key === 'avg')
|
|
26
27
|
.map(key => html`
|
|
27
|
-
<span class="statsItem">${key} <strong>${component.yAxisFormatter(s[key])}</strong></span>
|
|
28
|
+
<span class="statsItem">${key} <strong>${s[key] === null ? '—' : component.yAxisFormatter(s[key])}</strong></span>
|
|
28
29
|
`)}
|
|
29
30
|
</div>
|
|
30
31
|
`)}
|