@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.94 → 2.0.0-next.95
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/bundle/openbridge-webcomponents.bundle.js +153 -23
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +66 -49
- package/dist/building-blocks/chart-line/chart-line-base.d.ts +95 -33
- package/dist/building-blocks/chart-line/chart-line-base.d.ts.map +1 -1
- package/dist/building-blocks/chart-line/chart-line-base.js +88 -23
- package/dist/building-blocks/chart-line/chart-line-base.js.map +1 -1
- package/dist/charthelpers/index.d.ts +1 -0
- package/dist/charthelpers/index.d.ts.map +1 -1
- package/dist/charthelpers/index.js +4 -0
- package/dist/charthelpers/index.js.map +1 -1
- package/dist/charthelpers/x-value.d.ts +72 -0
- package/dist/charthelpers/x-value.d.ts.map +1 -0
- package/dist/charthelpers/x-value.js +60 -0
- package/dist/charthelpers/x-value.js.map +1 -0
- package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts +16 -1
- package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts.map +1 -1
- package/dist/navigation-instruments/gauge-trend/gauge-trend.js +12 -0
- package/dist/navigation-instruments/gauge-trend/gauge-trend.js.map +1 -1
- package/package.json +1 -1
- package/src/bars-graphs/area-graph/area-graph.stories.ts +83 -1
- package/src/bars-graphs/line-graph/line-graph.stories.ts +148 -1
- package/src/building-blocks/chart-line/chart-line-base.stories.ts +1 -1
- package/src/building-blocks/chart-line/chart-line-base.ts +198 -70
- package/src/charthelpers/index.ts +1 -0
- package/src/charthelpers/x-value.spec.ts +102 -0
- package/src/charthelpers/x-value.ts +134 -0
- package/src/navigation-instruments/gauge-trend/gauge-trend.stories.ts +42 -0
- package/src/navigation-instruments/gauge-trend/gauge-trend.ts +38 -1
|
@@ -93,7 +93,7 @@ const meta: Meta = {
|
|
|
93
93
|
// Axis and layout
|
|
94
94
|
xAxisType: {
|
|
95
95
|
control: {type: 'radio'},
|
|
96
|
-
options: [XAxisType.category, XAxisType.time],
|
|
96
|
+
options: [XAxisType.category, XAxisType.time, XAxisType.number],
|
|
97
97
|
},
|
|
98
98
|
yAxisPosition: {
|
|
99
99
|
control: {type: 'radio'},
|
|
@@ -188,6 +188,153 @@ export const SingleSeries: Story = {
|
|
|
188
188
|
`,
|
|
189
189
|
};
|
|
190
190
|
|
|
191
|
+
const UNEVEN_TIME_DATA = [
|
|
192
|
+
{x: '2026-07-06T10:00:00Z', value: 10},
|
|
193
|
+
{x: '2026-07-06T10:02:00Z', value: 14},
|
|
194
|
+
{x: '2026-07-06T10:03:00Z', value: 12},
|
|
195
|
+
{x: '2026-07-06T10:10:00Z', value: 18},
|
|
196
|
+
{x: '2026-07-06T10:30:00Z', value: 8},
|
|
197
|
+
{x: '2026-07-06T11:00:00Z', value: 15},
|
|
198
|
+
];
|
|
199
|
+
|
|
200
|
+
export const UnevenTimeIntervals: Story = {
|
|
201
|
+
name: 'Uneven Time Intervals (Time Axis)',
|
|
202
|
+
play: async ({canvasElement}) => {
|
|
203
|
+
await document.fonts.ready;
|
|
204
|
+
const chart = canvasElement.querySelector('obc-line-graph') as
|
|
205
|
+
| (HTMLElement & {chart?: {update(): void}})
|
|
206
|
+
| null;
|
|
207
|
+
chart?.chart?.update();
|
|
208
|
+
},
|
|
209
|
+
args: {
|
|
210
|
+
xAxisType: XAxisType.time,
|
|
211
|
+
timeDisplay: TimeDisplay.minutes,
|
|
212
|
+
},
|
|
213
|
+
render: (_args) => html`
|
|
214
|
+
<obc-line-graph
|
|
215
|
+
.data=${UNEVEN_TIME_DATA}
|
|
216
|
+
.lineMode=${_args.lineMode}
|
|
217
|
+
.xAxisType=${_args.xAxisType}
|
|
218
|
+
.timeDisplay=${_args.timeDisplay}
|
|
219
|
+
.showGrid=${_args.showGrid}
|
|
220
|
+
.showGridX=${_args.showGridX}
|
|
221
|
+
.showGridY=${_args.showGridY}
|
|
222
|
+
.showTickMarks=${_args.showTickMarks}
|
|
223
|
+
.showPoints=${_args.showPoints}
|
|
224
|
+
.priority=${_args.priority}
|
|
225
|
+
.width=${_args.width}
|
|
226
|
+
.height=${_args.height}
|
|
227
|
+
></obc-line-graph>
|
|
228
|
+
`,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
const DATE_OBJECT_TIME_DATA = [
|
|
232
|
+
{x: new Date('2026-07-06T10:00:00Z'), value: 10},
|
|
233
|
+
{x: new Date('2026-07-06T10:04:00Z'), value: 14},
|
|
234
|
+
{x: '2026-07-06T10:12:00Z', value: 12},
|
|
235
|
+
{x: Date.parse('2026-07-06T10:30:00Z'), value: 18},
|
|
236
|
+
];
|
|
237
|
+
|
|
238
|
+
export const DateObjectsTimeAxis: Story = {
|
|
239
|
+
name: 'Date Objects (Time Axis)',
|
|
240
|
+
play: async ({canvasElement}) => {
|
|
241
|
+
await document.fonts.ready;
|
|
242
|
+
const chart = canvasElement.querySelector('obc-line-graph') as
|
|
243
|
+
| (HTMLElement & {chart?: {update(): void}})
|
|
244
|
+
| null;
|
|
245
|
+
chart?.chart?.update();
|
|
246
|
+
},
|
|
247
|
+
args: {
|
|
248
|
+
xAxisType: XAxisType.time,
|
|
249
|
+
timeDisplay: TimeDisplay.minutes,
|
|
250
|
+
},
|
|
251
|
+
render: (_args) => html`
|
|
252
|
+
<obc-line-graph
|
|
253
|
+
.data=${DATE_OBJECT_TIME_DATA}
|
|
254
|
+
.lineMode=${_args.lineMode}
|
|
255
|
+
.xAxisType=${_args.xAxisType}
|
|
256
|
+
.timeDisplay=${_args.timeDisplay}
|
|
257
|
+
.showGrid=${_args.showGrid}
|
|
258
|
+
.showGridX=${_args.showGridX}
|
|
259
|
+
.showGridY=${_args.showGridY}
|
|
260
|
+
.showTickMarks=${_args.showTickMarks}
|
|
261
|
+
.showPoints=${_args.showPoints}
|
|
262
|
+
.priority=${_args.priority}
|
|
263
|
+
.width=${_args.width}
|
|
264
|
+
.height=${_args.height}
|
|
265
|
+
></obc-line-graph>
|
|
266
|
+
`,
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const NUMBER_AXIS_DATA = [
|
|
270
|
+
{x: 0, value: 2},
|
|
271
|
+
{x: 1, value: 5},
|
|
272
|
+
{x: 2.5, value: 3},
|
|
273
|
+
{x: 7, value: 9},
|
|
274
|
+
{x: 10, value: 6},
|
|
275
|
+
];
|
|
276
|
+
|
|
277
|
+
export const NumberAxis: Story = {
|
|
278
|
+
name: 'Number X-Axis',
|
|
279
|
+
play: async ({canvasElement}) => {
|
|
280
|
+
await document.fonts.ready;
|
|
281
|
+
const chart = canvasElement.querySelector('obc-line-graph') as
|
|
282
|
+
| (HTMLElement & {chart?: {update(): void}})
|
|
283
|
+
| null;
|
|
284
|
+
chart?.chart?.update();
|
|
285
|
+
},
|
|
286
|
+
args: {
|
|
287
|
+
xAxisType: XAxisType.number,
|
|
288
|
+
},
|
|
289
|
+
render: (_args) => html`
|
|
290
|
+
<obc-line-graph
|
|
291
|
+
.data=${NUMBER_AXIS_DATA}
|
|
292
|
+
.lineMode=${_args.lineMode}
|
|
293
|
+
.xAxisType=${_args.xAxisType}
|
|
294
|
+
.showGrid=${_args.showGrid}
|
|
295
|
+
.showGridX=${_args.showGridX}
|
|
296
|
+
.showGridY=${_args.showGridY}
|
|
297
|
+
.showTickMarks=${_args.showTickMarks}
|
|
298
|
+
.showPoints=${_args.showPoints}
|
|
299
|
+
.priority=${_args.priority}
|
|
300
|
+
.width=${_args.width}
|
|
301
|
+
.height=${_args.height}
|
|
302
|
+
></obc-line-graph>
|
|
303
|
+
`,
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
export const TemporalInput: Story = {
|
|
307
|
+
name: 'Temporal API Input',
|
|
308
|
+
tags: ['skip-test'],
|
|
309
|
+
render: () => {
|
|
310
|
+
const T = (
|
|
311
|
+
globalThis as {
|
|
312
|
+
Temporal?: {Instant: {from(s: string): {epochMilliseconds: number}}};
|
|
313
|
+
}
|
|
314
|
+
).Temporal;
|
|
315
|
+
if (!T) {
|
|
316
|
+
return html`<p>
|
|
317
|
+
Temporal API is not available in this browser — pass ISO strings, Date
|
|
318
|
+
objects or epoch milliseconds instead.
|
|
319
|
+
</p>`;
|
|
320
|
+
}
|
|
321
|
+
const chart = document.createElement('obc-line-graph');
|
|
322
|
+
chart.xAxisType = XAxisType.time;
|
|
323
|
+
chart.timeDisplay = TimeDisplay.minutes;
|
|
324
|
+
chart.showGrid = true;
|
|
325
|
+
chart.showGridX = true;
|
|
326
|
+
chart.showGridY = true;
|
|
327
|
+
chart.showTickMarks = true;
|
|
328
|
+
chart.data = [
|
|
329
|
+
{x: T.Instant.from('2026-07-06T10:00:00Z'), value: 10},
|
|
330
|
+
{x: T.Instant.from('2026-07-06T10:04:00Z'), value: 14},
|
|
331
|
+
{x: T.Instant.from('2026-07-06T10:05:00Z'), value: 12},
|
|
332
|
+
{x: T.Instant.from('2026-07-06T10:20:00Z'), value: 18},
|
|
333
|
+
];
|
|
334
|
+
return chart;
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
|
|
191
338
|
export const SingleSeriesExternalScales: Story = {
|
|
192
339
|
name: 'Single-Series Line Graph (with external scales)',
|
|
193
340
|
tags: ['skip-test'],
|
|
@@ -210,7 +210,7 @@ Abstract base class for line and area chart components built on Chart.js.
|
|
|
210
210
|
// Axis and layout
|
|
211
211
|
xAxisType: {
|
|
212
212
|
control: {type: 'radio'},
|
|
213
|
-
options: [XAxisType.category, XAxisType.time],
|
|
213
|
+
options: [XAxisType.category, XAxisType.time, XAxisType.number],
|
|
214
214
|
},
|
|
215
215
|
yAxisPosition: {
|
|
216
216
|
control: {type: 'radio'},
|
|
@@ -38,7 +38,11 @@ import {
|
|
|
38
38
|
getChartTooltipOptions,
|
|
39
39
|
generateLegendHTML,
|
|
40
40
|
applyAlphaToColor,
|
|
41
|
+
normalizeXValue,
|
|
42
|
+
formatXValue,
|
|
43
|
+
XValueMode,
|
|
41
44
|
} from '../../charthelpers/index.js';
|
|
45
|
+
import type {ChartXValue} from '../../charthelpers/x-value.js';
|
|
42
46
|
import {
|
|
43
47
|
EXTERNAL_SCALE_BORDER_RADIUS_CSS_VAR,
|
|
44
48
|
readExternalScaleBorderRadiusPx,
|
|
@@ -94,9 +98,12 @@ interface ExternalScaleElement extends HTMLElement {
|
|
|
94
98
|
borderRadiusPosition?: BorderRadiusPosition;
|
|
95
99
|
}
|
|
96
100
|
|
|
101
|
+
export type {ChartXValue, TemporalLike} from '../../charthelpers/x-value.js';
|
|
102
|
+
|
|
97
103
|
export enum XAxisType {
|
|
98
104
|
category = 'category',
|
|
99
105
|
time = 'time',
|
|
106
|
+
number = 'number',
|
|
100
107
|
}
|
|
101
108
|
|
|
102
109
|
export enum YAxisPosition {
|
|
@@ -115,8 +122,17 @@ export enum TimeDisplay {
|
|
|
115
122
|
date = 'date',
|
|
116
123
|
}
|
|
117
124
|
|
|
125
|
+
export type ChartLinePoint = number | {x: ChartXValue; y: number};
|
|
126
|
+
|
|
118
127
|
export type ChartLineDataItem = {
|
|
119
|
-
label
|
|
128
|
+
/** Category label. Used when `xAxisType='category'` (the default). */
|
|
129
|
+
label?: string;
|
|
130
|
+
/**
|
|
131
|
+
* X-coordinate for `xAxisType='time'` (epoch ms, ISO string, Date, or
|
|
132
|
+
* Temporal object) or `xAxisType='number'` (plain number). When absent,
|
|
133
|
+
* `label` is parsed as a fallback.
|
|
134
|
+
*/
|
|
135
|
+
x?: ChartXValue;
|
|
120
136
|
value: number;
|
|
121
137
|
};
|
|
122
138
|
|
|
@@ -170,7 +186,9 @@ const LINE_GRAPH_RECREATE_PROP_NAMES = [
|
|
|
170
186
|
*
|
|
171
187
|
* ## Features
|
|
172
188
|
* - **Single or multi-series**: Use `data` for simple single-series or `datasets` for multi-series charts
|
|
173
|
-
* - **
|
|
189
|
+
* - **Category, time and number axes**: `category` (labels, evenly spaced), `time` (epoch ms,
|
|
190
|
+
* ISO strings, `Date` or Temporal objects — positioned proportionally, so uneven intervals
|
|
191
|
+
* render unevenly) and `number` (plain numeric x-values on a linear scale)
|
|
174
192
|
* - **Line styles**: Choose `smooth` (curved), `straight`, or `stepped` line rendering
|
|
175
193
|
* - **Fill modes**: Area fills with `semitransparent`, `solid`, or `threshold` (red/blue above/below midpoint)
|
|
176
194
|
* - **Stacked charts**: Enable `stacked` for multi-series datasets to stack values on y-axis
|
|
@@ -219,6 +237,36 @@ const LINE_GRAPH_RECREATE_PROP_NAMES = [
|
|
|
219
237
|
* </script>
|
|
220
238
|
* ```
|
|
221
239
|
*
|
|
240
|
+
* Single-series with time axis (uneven intervals position proportionally;
|
|
241
|
+
* x accepts epoch ms, ISO strings, Date or Temporal objects):
|
|
242
|
+
* ```html
|
|
243
|
+
* <obc-line-graph></obc-line-graph>
|
|
244
|
+
* <script>
|
|
245
|
+
* const chart = document.querySelector('obc-line-graph');
|
|
246
|
+
* chart.xAxisType = 'time';
|
|
247
|
+
* chart.timeDisplay = 'minutes';
|
|
248
|
+
* chart.data = [
|
|
249
|
+
* {x: '2026-07-06T10:00:00Z', value: 10},
|
|
250
|
+
* {x: new Date('2026-07-06T10:03:00Z'), value: 14},
|
|
251
|
+
* {x: Temporal.Instant.from('2026-07-06T10:15:00Z'), value: 12}
|
|
252
|
+
* ];
|
|
253
|
+
* </script>
|
|
254
|
+
* ```
|
|
255
|
+
*
|
|
256
|
+
* Single-series with numeric x-axis:
|
|
257
|
+
* ```html
|
|
258
|
+
* <obc-line-graph></obc-line-graph>
|
|
259
|
+
* <script>
|
|
260
|
+
* const chart = document.querySelector('obc-line-graph');
|
|
261
|
+
* chart.xAxisType = 'number';
|
|
262
|
+
* chart.data = [
|
|
263
|
+
* {x: 0, value: 2},
|
|
264
|
+
* {x: 2.5, value: 3},
|
|
265
|
+
* {x: 10, value: 6}
|
|
266
|
+
* ];
|
|
267
|
+
* </script>
|
|
268
|
+
* ```
|
|
269
|
+
*
|
|
222
270
|
* Stacked area chart with solid fill:
|
|
223
271
|
* ```html
|
|
224
272
|
* <obc-line-graph></obc-line-graph>
|
|
@@ -263,12 +311,12 @@ const LINE_GRAPH_RECREATE_PROP_NAMES = [
|
|
|
263
311
|
* </script>
|
|
264
312
|
* ```
|
|
265
313
|
*
|
|
266
|
-
* @property {Array<{label
|
|
267
|
-
* @property {ChartDataset<'line', (number | {x: string|
|
|
314
|
+
* @property {Array<{label?: string, x?: number|string|Date|TemporalLike, value: number}>} data - Single-series data array. In `category` mode each item needs `label`; in `time`/`number` mode each item needs `x` (epoch ms, ISO string, Date, or Temporal object — `label` is parsed as a fallback). Used when `datasets` is not provided. Points are drawn in array order (no sorting); Temporal Plain* values are interpreted in the system time zone.
|
|
315
|
+
* @property {ChartDataset<'line', (number | {x: number|string|Date|TemporalLike; y: number})[]>[]} datasets - Multi-series Chart.js datasets. Takes precedence over `data`. Each dataset can have `label`, `data` (numeric array or `{x, y}` points), and visual properties like `borderColor`, `backgroundColor`, `fill`, etc. In `time`/`number` mode point x-values are normalized like single-series `x`.
|
|
268
316
|
* @property {(string|number)[]} labels - Explicit labels for category x-axis. If omitted, labels are derived from `data` property or dataset x-values.
|
|
269
317
|
* @property {string[]} colors - Custom color palette (CSS variable names or color strings). Falls back to theme default colors if not provided.
|
|
270
|
-
* @property {'category'|'time'} xAxisType - X-axis mode. `'category'` for labeled data points
|
|
271
|
-
* @property {'minutes'|'date'} timeDisplay - Time axis label format when `xAxisType='time'`. `'date'` shows
|
|
318
|
+
* @property {'category'|'time'|'number'} xAxisType - X-axis mode. `'category'` for labeled, evenly spaced data points; `'time'` for time-based data positioned proportionally (numbers are always epoch ms — `xStepSize`/`xTicksLimit` operate in ms); `'number'` for plain numeric x-values. Default: `'category'`.
|
|
319
|
+
* @property {'minutes'|'date'} timeDisplay - Time axis label format when `xAxisType='time'`. `'date'` shows a locale date, `'minutes'` shows minutes relative to the latest data point. Default: `'date'`.
|
|
272
320
|
* @property {'left'|'right'} yAxisPosition - Single y-axis position. Use this for simple charts with one y-axis. For multiple y-axes, use `yAxes` property instead. Default: `'left'`.
|
|
273
321
|
* @property {Array<{id?: string; position?: 'left'|'right'; min?: number; max?: number; grid?: boolean}>} yAxes - Multiple y-axis definitions for complex charts. Each axis can specify `id` (referenced by dataset `yAxisID`), `position`, `min`/`max` range, and `grid` visibility.
|
|
274
322
|
* @property {boolean} showGrid - Show vertical grid lines (x-axis). When combined with `showGridX` and `showGridY`, controls full grid visibility. Default: `false`.
|
|
@@ -293,16 +341,17 @@ const LINE_GRAPH_RECREATE_PROP_NAMES = [
|
|
|
293
341
|
* @experimental
|
|
294
342
|
*/
|
|
295
343
|
export class ObcChartLineBase extends LitElement {
|
|
296
|
-
/**
|
|
344
|
+
/**
|
|
345
|
+
* Simple single-series data. `{label, value}` items for the category axis;
|
|
346
|
+
* `{x, value}` items for time/number axes (x: epoch ms, ISO string, Date,
|
|
347
|
+
* or Temporal object). Points are drawn in array order (no sorting).
|
|
348
|
+
*/
|
|
297
349
|
@property({type: Array, attribute: false})
|
|
298
350
|
data: ChartLineDataItem[] = [];
|
|
299
351
|
|
|
300
352
|
/** Chart.js-style datasets for multi-series use. If provided, takes precedence over `data`. */
|
|
301
353
|
@property({type: Array, attribute: false})
|
|
302
|
-
datasets?: ChartDataset<
|
|
303
|
-
'line',
|
|
304
|
-
(number | {x: string | number | Date; y: number})[]
|
|
305
|
-
>[] = undefined;
|
|
354
|
+
datasets?: ChartDataset<'line', ChartLinePoint[]>[] = undefined;
|
|
306
355
|
|
|
307
356
|
/** Optional explicit labels for the x-axis (category mode). If omitted labels are derived from `data` */
|
|
308
357
|
@property({type: Array, attribute: false})
|
|
@@ -346,7 +395,11 @@ export class ObcChartLineBase extends LitElement {
|
|
|
346
395
|
@property({type: Number})
|
|
347
396
|
scaleReferenceSize = 384;
|
|
348
397
|
|
|
349
|
-
/**
|
|
398
|
+
/**
|
|
399
|
+
* X-axis mode: 'category' for labeled, evenly spaced data points; 'time'
|
|
400
|
+
* for time-based data positioned proportionally; 'number' for plain
|
|
401
|
+
* numeric x-values.
|
|
402
|
+
*/
|
|
350
403
|
@property({type: String})
|
|
351
404
|
xAxisType: XAxisType = XAxisType.category;
|
|
352
405
|
|
|
@@ -400,6 +453,33 @@ export class ObcChartLineBase extends LitElement {
|
|
|
400
453
|
@property({type: Boolean, attribute: false})
|
|
401
454
|
hasLabelPadding = true;
|
|
402
455
|
|
|
456
|
+
/** @internal - True when the x-axis positions points by numeric value. */
|
|
457
|
+
protected get isNumericXAxis(): boolean {
|
|
458
|
+
return (
|
|
459
|
+
this.xAxisType === XAxisType.time || this.xAxisType === XAxisType.number
|
|
460
|
+
);
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/** @internal - Normalization mode for the current x-axis type. */
|
|
464
|
+
protected get xValueMode(): XValueMode {
|
|
465
|
+
return this.xAxisType === XAxisType.number
|
|
466
|
+
? XValueMode.number
|
|
467
|
+
: XValueMode.time;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** @internal - Last data/datasets reference already warned about. */
|
|
471
|
+
private lastWarnedXSource?: unknown;
|
|
472
|
+
|
|
473
|
+
/** @internal - Warn once per data assignment about unparseable x-values. */
|
|
474
|
+
private warnOnInvalidX(xValues: number[], sourceRef: unknown) {
|
|
475
|
+
const invalid = xValues.filter((x) => !Number.isFinite(x)).length;
|
|
476
|
+
if (invalid === 0 || this.lastWarnedXSource === sourceRef) return;
|
|
477
|
+
this.lastWarnedXSource = sourceRef;
|
|
478
|
+
console.warn(
|
|
479
|
+
`[obc-chart] ${invalid} x value(s) could not be parsed for xAxisType='${this.xAxisType}'; the points render as gaps.`
|
|
480
|
+
);
|
|
481
|
+
}
|
|
482
|
+
|
|
403
483
|
// Internal default tension used when `lineMode` is 'smooth'. Not exposed as a property.
|
|
404
484
|
private readonly DEFAULT_TENSION = 0.4;
|
|
405
485
|
|
|
@@ -1825,28 +1905,21 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1825
1905
|
protected buildDataset(
|
|
1826
1906
|
data:
|
|
1827
1907
|
| number[]
|
|
1828
|
-
|
|
|
1829
|
-
|
|
1830
|
-
(number | {x: string | number | Date; y: number})[]
|
|
1831
|
-
>,
|
|
1908
|
+
| {x: number; y: number}[]
|
|
1909
|
+
| ChartDataset<'line', ChartLinePoint[]>,
|
|
1832
1910
|
index: number,
|
|
1833
1911
|
chartColors: string[],
|
|
1834
1912
|
totalCount = 1
|
|
1835
|
-
): ChartDataset<
|
|
1836
|
-
'line',
|
|
1837
|
-
number[] | (number | {x: string | number | Date; y: number})[]
|
|
1838
|
-
> {
|
|
1913
|
+
): ChartDataset<'line', ChartLinePoint[]> {
|
|
1839
1914
|
const currentColor = chartColors[index % chartColors.length];
|
|
1840
1915
|
|
|
1841
|
-
//
|
|
1842
|
-
const existingDataset =
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
: null;
|
|
1849
|
-
const values = existingDataset ? null : (data as number[]);
|
|
1916
|
+
// Raw input is an array (values or points); anything else is an existing dataset
|
|
1917
|
+
const existingDataset = Array.isArray(data)
|
|
1918
|
+
? null
|
|
1919
|
+
: (data as ChartDataset<'line', ChartLinePoint[]>);
|
|
1920
|
+
const values = Array.isArray(data)
|
|
1921
|
+
? (data as number[] | {x: number; y: number}[])
|
|
1922
|
+
: null;
|
|
1850
1923
|
|
|
1851
1924
|
const borderColor = existingDataset?.borderColor ?? currentColor;
|
|
1852
1925
|
const fillFlag = existingDataset?.fill ?? this.shouldApplyFill();
|
|
@@ -1905,33 +1978,34 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1905
1978
|
}),
|
|
1906
1979
|
};
|
|
1907
1980
|
|
|
1908
|
-
return result as ChartDataset<
|
|
1909
|
-
'line',
|
|
1910
|
-
number[] | (number | {x: string | number | Date; y: number})[]
|
|
1911
|
-
>;
|
|
1981
|
+
return result as ChartDataset<'line', ChartLinePoint[]>;
|
|
1912
1982
|
}
|
|
1913
1983
|
|
|
1914
1984
|
/**
|
|
1915
|
-
* Create threshold mode datasets: invisible baseline + main dataset with
|
|
1985
|
+
* Create threshold mode datasets: invisible baseline + main dataset with
|
|
1986
|
+
* above/below fills. Accepts plain values (category mode) or {x, y} points
|
|
1987
|
+
* (time/number mode); the baseline mirrors the input x-positions.
|
|
1916
1988
|
*/
|
|
1917
1989
|
protected createThresholdDatasets(
|
|
1918
|
-
values: number[],
|
|
1990
|
+
values: number[] | {x: number; y: number}[],
|
|
1919
1991
|
chartColors: string[]
|
|
1920
|
-
): ChartDataset<'line',
|
|
1921
|
-
const
|
|
1922
|
-
.map((v) =>
|
|
1992
|
+
): ChartDataset<'line', ChartLinePoint[]>[] {
|
|
1993
|
+
const yValues = values
|
|
1994
|
+
.map((v) => (typeof v === 'number' ? v : v.y))
|
|
1923
1995
|
.filter((n) => Number.isFinite(n));
|
|
1924
|
-
const minV =
|
|
1925
|
-
const maxV =
|
|
1996
|
+
const minV = yValues.length ? Math.min(...yValues) : 0;
|
|
1997
|
+
const maxV = yValues.length ? Math.max(...yValues) : 100;
|
|
1926
1998
|
const threshold = (minV + maxV) / 2;
|
|
1927
|
-
const baselineData =
|
|
1999
|
+
const baselineData: ChartLinePoint[] = values.map((v) =>
|
|
2000
|
+
typeof v === 'number' ? threshold : {x: v.x, y: threshold}
|
|
2001
|
+
);
|
|
1928
2002
|
|
|
1929
2003
|
const lowRaw = LINE_GRAPH_GRID_CONFIG.thresholdLowColorVar;
|
|
1930
2004
|
const highRaw = LINE_GRAPH_GRID_CONFIG.thresholdHighColorVar;
|
|
1931
2005
|
const highFill = applyAlphaToColor(this, highRaw, 0.35);
|
|
1932
2006
|
const lowFill = applyAlphaToColor(this, lowRaw, 0.35);
|
|
1933
2007
|
|
|
1934
|
-
const baselineDataset: ChartDataset<'line',
|
|
2008
|
+
const baselineDataset: ChartDataset<'line', ChartLinePoint[]> = {
|
|
1935
2009
|
label: 'threshold-baseline',
|
|
1936
2010
|
data: baselineData,
|
|
1937
2011
|
borderColor: 'transparent',
|
|
@@ -1943,10 +2017,7 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1943
2017
|
spanGaps: true,
|
|
1944
2018
|
};
|
|
1945
2019
|
|
|
1946
|
-
const main = this.buildDataset(values, 0, chartColors)
|
|
1947
|
-
'line',
|
|
1948
|
-
number[]
|
|
1949
|
-
>;
|
|
2020
|
+
const main = this.buildDataset(values, 0, chartColors);
|
|
1950
2021
|
(main as unknown as Record<string, unknown>).fill = {
|
|
1951
2022
|
target: 0,
|
|
1952
2023
|
above: highFill,
|
|
@@ -1959,7 +2030,9 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1959
2030
|
}
|
|
1960
2031
|
|
|
1961
2032
|
/**
|
|
1962
|
-
* Prepare normalized datasets for multi-series charts
|
|
2033
|
+
* Prepare normalized datasets for multi-series charts.
|
|
2034
|
+
* In time/number mode every point's x is normalized (epoch ms / number)
|
|
2035
|
+
* so strings, Dates and Temporal objects position correctly.
|
|
1963
2036
|
*/
|
|
1964
2037
|
protected prepareMultiSeriesDatasets() {
|
|
1965
2038
|
const defaultPalette =
|
|
@@ -1972,19 +2045,48 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1972
2045
|
defaultPalette
|
|
1973
2046
|
);
|
|
1974
2047
|
|
|
2048
|
+
// Normalize first, then warn once per assignment with the aggregate
|
|
2049
|
+
// count across ALL series — warning inside the per-dataset loop would
|
|
2050
|
+
// either flood the console or (with ref-based dedup) silently swallow
|
|
2051
|
+
// failures in every series after the first.
|
|
2052
|
+
const normalized = this.datasets!.map((ds) => this.normalizeDatasetX(ds));
|
|
2053
|
+
if (this.isNumericXAxis) {
|
|
2054
|
+
this.warnOnInvalidX(
|
|
2055
|
+
normalized.flatMap((ds) =>
|
|
2056
|
+
(ds.data ?? []).map((pt) =>
|
|
2057
|
+
pt && typeof pt === 'object' ? (pt.x as number) : 0
|
|
2058
|
+
)
|
|
2059
|
+
),
|
|
2060
|
+
this.datasets
|
|
2061
|
+
);
|
|
2062
|
+
}
|
|
2063
|
+
|
|
1975
2064
|
const totalCount = this.datasets!.length;
|
|
1976
|
-
return
|
|
2065
|
+
return normalized.map((ds, i) =>
|
|
1977
2066
|
this.buildDataset(ds, i, chartColors, totalCount)
|
|
1978
2067
|
);
|
|
1979
2068
|
}
|
|
1980
2069
|
|
|
2070
|
+
/** @internal - Return a copy of the dataset with normalized point x-values. */
|
|
2071
|
+
private normalizeDatasetX(
|
|
2072
|
+
ds: ChartDataset<'line', ChartLinePoint[]>
|
|
2073
|
+
): ChartDataset<'line', ChartLinePoint[]> {
|
|
2074
|
+
if (!this.isNumericXAxis || !ds.data) return ds;
|
|
2075
|
+
const data = ds.data.map((pt) =>
|
|
2076
|
+
pt && typeof pt === 'object' && 'x' in pt
|
|
2077
|
+
? {...pt, x: normalizeXValue(pt.x, this.xValueMode)}
|
|
2078
|
+
: pt
|
|
2079
|
+
);
|
|
2080
|
+
return {...ds, data};
|
|
2081
|
+
}
|
|
2082
|
+
|
|
1981
2083
|
/**
|
|
1982
|
-
* Prepare datasets for single-series charts
|
|
1983
|
-
*
|
|
2084
|
+
* Prepare datasets for single-series charts.
|
|
2085
|
+
* Category mode: labels + numeric values (unchanged legacy path).
|
|
2086
|
+
* Time/number mode: normalized {x, y} points on a linear scale.
|
|
2087
|
+
* Handles both regular and threshold fill modes.
|
|
1984
2088
|
*/
|
|
1985
2089
|
protected prepareSingleSeriesDatasets() {
|
|
1986
|
-
const values = this.data.map((d) => d.value);
|
|
1987
|
-
const labels = this.data.map((d) => d.label);
|
|
1988
2090
|
const defaultPalette =
|
|
1989
2091
|
this.priority === Priority.enhanced
|
|
1990
2092
|
? CHART_SECTOR_ENHANCED_COLORS
|
|
@@ -1997,6 +2099,25 @@ export class ObcChartLineBase extends LitElement {
|
|
|
1997
2099
|
const fill = this.shouldApplyFill();
|
|
1998
2100
|
const fillMode = this.getFillMode();
|
|
1999
2101
|
|
|
2102
|
+
if (this.isNumericXAxis) {
|
|
2103
|
+
const points = this.data.map((d) => ({
|
|
2104
|
+
x: normalizeXValue(d.x ?? d.label ?? NaN, this.xValueMode),
|
|
2105
|
+
y: d.value,
|
|
2106
|
+
}));
|
|
2107
|
+
this.warnOnInvalidX(
|
|
2108
|
+
points.map((p) => p.x),
|
|
2109
|
+
this.data
|
|
2110
|
+
);
|
|
2111
|
+
const datasets =
|
|
2112
|
+
fill && fillMode === 'threshold'
|
|
2113
|
+
? this.createThresholdDatasets(points, chartColors)
|
|
2114
|
+
: [this.buildDataset(points, 0, chartColors)];
|
|
2115
|
+
return {datasets, labels: [] as (string | number)[]};
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
const values = this.data.map((d) => d.value);
|
|
2119
|
+
const labels = this.data.map((d) => d.label ?? String(d.x ?? ''));
|
|
2120
|
+
|
|
2000
2121
|
const datasets =
|
|
2001
2122
|
fill && fillMode === 'threshold'
|
|
2002
2123
|
? this.createThresholdDatasets(values, chartColors)
|
|
@@ -2118,13 +2239,22 @@ export class ObcChartLineBase extends LitElement {
|
|
|
2118
2239
|
callbacks: {
|
|
2119
2240
|
title: () => '',
|
|
2120
2241
|
label: (context) => {
|
|
2121
|
-
const label = context.label ?? '';
|
|
2122
2242
|
const value =
|
|
2123
2243
|
typeof context.parsed === 'object' && context.parsed !== null
|
|
2124
2244
|
? (context.parsed as {y: number}).y
|
|
2125
2245
|
: (context.parsed as number);
|
|
2126
2246
|
const numericValue = formatNumericValue(value, 1, false, 0);
|
|
2127
2247
|
const unit = this.unit ? `${this.unit}` : '';
|
|
2248
|
+
let label = context.label ?? '';
|
|
2249
|
+
if (this.isNumericXAxis) {
|
|
2250
|
+
const x =
|
|
2251
|
+
typeof context.parsed === 'object' && context.parsed !== null
|
|
2252
|
+
? (context.parsed as {x: number}).x
|
|
2253
|
+
: NaN;
|
|
2254
|
+
const relativeTo =
|
|
2255
|
+
this.timeDisplay === TimeDisplay.minutes ? refTs : undefined;
|
|
2256
|
+
label = formatXValue(x, this.xValueMode, relativeTo);
|
|
2257
|
+
}
|
|
2128
2258
|
return `${label} ${numericValue}${unit}`;
|
|
2129
2259
|
},
|
|
2130
2260
|
},
|
|
@@ -2140,23 +2270,27 @@ export class ObcChartLineBase extends LitElement {
|
|
|
2140
2270
|
* Returns earliest timestamp for 'date' mode, latest for 'minutes' mode.
|
|
2141
2271
|
*/
|
|
2142
2272
|
private computeTimeReference(): number | undefined {
|
|
2273
|
+
if (this.xAxisType !== XAxisType.time) return undefined;
|
|
2274
|
+
|
|
2143
2275
|
const timestamps: number[] = [];
|
|
2144
2276
|
|
|
2145
2277
|
// Collect timestamps from datasets
|
|
2146
2278
|
if (this.datasets?.length) {
|
|
2147
2279
|
this.datasets.forEach((ds) => {
|
|
2148
2280
|
if (!ds.data) return;
|
|
2149
|
-
|
|
2281
|
+
ds.data.forEach((pt) => {
|
|
2150
2282
|
if (pt && typeof pt === 'object' && 'x' in pt) {
|
|
2151
|
-
const
|
|
2152
|
-
const ts =
|
|
2153
|
-
typeof xVal === 'string'
|
|
2154
|
-
? new Date(String(xVal)).getTime()
|
|
2155
|
-
: Number(xVal);
|
|
2283
|
+
const ts = normalizeXValue(pt.x, XValueMode.time);
|
|
2156
2284
|
if (Number.isFinite(ts)) timestamps.push(ts);
|
|
2157
2285
|
}
|
|
2158
2286
|
});
|
|
2159
2287
|
});
|
|
2288
|
+
} else if (this.data?.length) {
|
|
2289
|
+
// Collect timestamps from single-series data items
|
|
2290
|
+
this.data.forEach((d) => {
|
|
2291
|
+
const ts = normalizeXValue(d.x ?? d.label ?? NaN, XValueMode.time);
|
|
2292
|
+
if (Number.isFinite(ts)) timestamps.push(ts);
|
|
2293
|
+
});
|
|
2160
2294
|
}
|
|
2161
2295
|
|
|
2162
2296
|
// Collect timestamps from labels if no dataset timestamps found
|
|
@@ -2228,7 +2362,7 @@ export class ObcChartLineBase extends LitElement {
|
|
|
2228
2362
|
const fontConfig = {family: fontFamily, size: fontSize, weight: fontWeight};
|
|
2229
2363
|
|
|
2230
2364
|
const x = {
|
|
2231
|
-
type: this.xAxisType ===
|
|
2365
|
+
type: this.xAxisType === XAxisType.category ? 'category' : 'linear',
|
|
2232
2366
|
offset: false, // Always edge-to-edge (no padding on x-axis)
|
|
2233
2367
|
grace: 0, // No extra margin
|
|
2234
2368
|
bounds: 'data', // Use data bounds for edge-to-edge rendering
|
|
@@ -2247,18 +2381,12 @@ export class ObcChartLineBase extends LitElement {
|
|
|
2247
2381
|
maxTicksLimit: this.xTicksLimit,
|
|
2248
2382
|
stepSize: this.xStepSize,
|
|
2249
2383
|
callback: (value: unknown) => {
|
|
2250
|
-
if (this.
|
|
2384
|
+
if (!this.isNumericXAxis) return String(value);
|
|
2251
2385
|
const n = Number(value);
|
|
2252
2386
|
if (!Number.isFinite(n)) return String(value);
|
|
2253
|
-
|
|
2254
|
-
this.timeDisplay ===
|
|
2255
|
-
|
|
2256
|
-
Number.isFinite(minX)
|
|
2257
|
-
) {
|
|
2258
|
-
const minutes = Math.round((n - minX) / 60000);
|
|
2259
|
-
return `${minutes}min`;
|
|
2260
|
-
}
|
|
2261
|
-
return new Date(n).toLocaleDateString();
|
|
2387
|
+
const relativeTo =
|
|
2388
|
+
this.timeDisplay === TimeDisplay.minutes ? minX : undefined;
|
|
2389
|
+
return formatXValue(n, this.xValueMode, relativeTo);
|
|
2262
2390
|
},
|
|
2263
2391
|
},
|
|
2264
2392
|
border: {
|