@oicl/openbridge-webcomponents-full-bundle 2.0.0-next.151 → 2.0.0-next.152
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/.storybook/vitest.setup.ts +12 -1
- package/bundle/openbridge-webcomponents.bundle.js +989 -319
- package/bundle/openbridge-webcomponents.bundle.js.map +1 -1
- package/custom-elements.json +1514 -77
- package/dist/alert-severity.d.ts +12 -9
- package/dist/alert-severity.d.ts.map +1 -1
- package/dist/alert-severity.js +30 -38
- package/dist/alert-severity.js.map +1 -1
- package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts +6 -0
- package/dist/building-blocks/bar-horizontal/bar-horizontal.d.ts.map +1 -1
- package/dist/building-blocks/bar-horizontal/bar-horizontal.js +6 -1
- package/dist/building-blocks/bar-horizontal/bar-horizontal.js.map +1 -1
- package/dist/building-blocks/bar-vertical/bar-vertical.d.ts +6 -0
- package/dist/building-blocks/bar-vertical/bar-vertical.d.ts.map +1 -1
- package/dist/building-blocks/bar-vertical/bar-vertical.js +6 -1
- package/dist/building-blocks/bar-vertical/bar-vertical.js.map +1 -1
- package/dist/building-blocks/chart-line/chart-line-base.d.ts +129 -0
- package/dist/building-blocks/chart-line/chart-line-base.d.ts.map +1 -1
- package/dist/building-blocks/chart-line/chart-line-base.js +464 -79
- package/dist/building-blocks/chart-line/chart-line-base.js.map +1 -1
- package/dist/building-blocks/external-scale/external-scale.d.ts +11 -0
- package/dist/building-blocks/external-scale/external-scale.d.ts.map +1 -1
- package/dist/building-blocks/external-scale/external-scale.js +45 -12
- package/dist/building-blocks/external-scale/external-scale.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 +7 -1
- package/dist/charthelpers/index.js.map +1 -1
- package/dist/charthelpers/range-labels.d.ts +55 -0
- package/dist/charthelpers/range-labels.d.ts.map +1 -0
- package/dist/charthelpers/range-labels.js +36 -0
- package/dist/charthelpers/range-labels.js.map +1 -0
- package/dist/components/alert-button/alert-button.css.js +13 -21
- package/dist/components/alert-button/alert-button.css.js.map +1 -1
- package/dist/components/alert-button/alert-button.d.ts +9 -4
- package/dist/components/alert-button/alert-button.d.ts.map +1 -1
- package/dist/components/alert-button/alert-button.js +24 -15
- package/dist/components/alert-button/alert-button.js.map +1 -1
- package/dist/components/alert-frame/alert-frame.css.js +109 -33
- package/dist/components/alert-frame/alert-frame.css.js.map +1 -1
- package/dist/components/alert-frame/alert-frame.d.ts +34 -7
- package/dist/components/alert-frame/alert-frame.d.ts.map +1 -1
- package/dist/components/alert-frame/alert-frame.js +134 -30
- package/dist/components/alert-frame/alert-frame.js.map +1 -1
- package/dist/components/alert-icon/alert-icon.d.ts +10 -7
- package/dist/components/alert-icon/alert-icon.d.ts.map +1 -1
- package/dist/components/alert-icon/alert-icon.js +37 -60
- package/dist/components/alert-icon/alert-icon.js.map +1 -1
- package/dist/components/alert-menu-item/alert-menu-item.css.js +1 -4
- package/dist/components/alert-menu-item/alert-menu-item.css.js.map +1 -1
- package/dist/components/date-item/date-item.css.js +3 -3
- package/dist/components/keyboard-full/keyboard-full.css.js +4 -4
- package/dist/components/message-menu-item/message-menu-item.css.js +2 -6
- package/dist/components/message-menu-item/message-menu-item.css.js.map +1 -1
- package/dist/components/sequence-toolbar/sequence-toolbar.css.js +1 -2
- package/dist/components/sequence-toolbar/sequence-toolbar.css.js.map +1 -1
- package/dist/index.js +2 -1
- package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts +6 -0
- package/dist/navigation-instruments/gauge-trend/gauge-trend.d.ts.map +1 -1
- package/dist/navigation-instruments/gauge-trend/gauge-trend.js +8 -0
- package/dist/navigation-instruments/gauge-trend/gauge-trend.js.map +1 -1
- package/dist/navigation-instruments/instrument-field/instrument-field.css.js +2 -2
- package/dist/navigation-instruments/readout/readout.d.ts.map +1 -1
- package/dist/navigation-instruments/readout/readout.js +2 -1
- package/dist/navigation-instruments/readout/readout.js.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.d.ts.map +1 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js +2 -1
- package/dist/navigation-instruments/readout-list-item/readout-list-item.js.map +1 -1
- package/dist/openbridge.css +19 -4
- package/dist/palettes/blinking.d.ts +14 -4
- package/dist/palettes/blinking.d.ts.map +1 -1
- package/dist/palettes/blinking.js +41 -35
- package/dist/palettes/blinking.js.map +1 -1
- package/dist/palettes/flashing-controller.d.ts +21 -0
- package/dist/palettes/flashing-controller.d.ts.map +1 -0
- package/dist/palettes/flashing-controller.js +45 -0
- package/dist/palettes/flashing-controller.js.map +1 -0
- package/dist/svghelpers/index.d.ts +1 -0
- package/dist/svghelpers/index.d.ts.map +1 -1
- package/dist/svghelpers/index.js +2 -0
- package/dist/svghelpers/index.js.map +1 -1
- package/dist/svghelpers/rounded-rect.d.ts +15 -0
- package/dist/svghelpers/rounded-rect.d.ts.map +1 -0
- package/dist/svghelpers/rounded-rect.js +29 -0
- package/dist/svghelpers/rounded-rect.js.map +1 -0
- package/dist/types.d.ts +14 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
- package/script/check-css-variables.ts +2 -15
- package/src/alert-severity.spec.ts +56 -0
- package/src/alert-severity.ts +45 -43
- package/src/bars-graphs/area-graph/area-graph.stories.ts +235 -1
- package/src/bars-graphs/line-graph/line-graph.stories.ts +24 -0
- package/src/building-blocks/bar-horizontal/bar-horizontal.ts +7 -0
- package/src/building-blocks/bar-vertical/bar-vertical.ts +7 -0
- package/src/building-blocks/chart-line/chart-line-base.spec.ts +767 -0
- package/src/building-blocks/chart-line/chart-line-base.stories.ts +124 -0
- package/src/building-blocks/chart-line/chart-line-base.ts +671 -97
- package/src/building-blocks/chart-line/chart-sizing-battleground.stories.ts +17 -0
- package/src/building-blocks/external-scale/external-scale.spec.ts +154 -0
- package/src/building-blocks/external-scale/external-scale.ts +78 -20
- package/src/charthelpers/index.ts +1 -0
- package/src/charthelpers/range-labels.spec.ts +46 -0
- package/src/charthelpers/range-labels.ts +93 -0
- package/src/components/alert-button/alert-button.css +10 -23
- package/src/components/alert-button/alert-button.spec.ts +56 -15
- package/src/components/alert-button/alert-button.stories.ts +5 -1
- package/src/components/alert-button/alert-button.ts +33 -24
- package/src/components/alert-frame/alert-flash-scenario.stories.ts +669 -0
- package/src/components/alert-frame/alert-frame.css +106 -33
- package/src/components/alert-frame/alert-frame.spec.ts +176 -17
- package/src/components/alert-frame/alert-frame.stories.ts +61 -0
- package/src/components/alert-frame/alert-frame.ts +179 -41
- package/src/components/alert-icon/alert-icon.spec.ts +82 -27
- package/src/components/alert-icon/alert-icon.stories.ts +7 -1
- package/src/components/alert-icon/alert-icon.ts +48 -74
- package/src/components/alert-menu-item/alert-menu-item.css +1 -4
- package/src/components/date-item/date-item.css +3 -3
- package/src/components/keyboard-full/keyboard-full.css +4 -4
- package/src/components/message-menu-item/message-menu-item.css +2 -6
- package/src/components/sequence-toolbar/sequence-toolbar.css +1 -2
- package/src/navigation-instruments/gauge-trend/gauge-trend.stories.ts +29 -0
- package/src/navigation-instruments/gauge-trend/gauge-trend.ts +11 -0
- package/src/navigation-instruments/instrument-field/instrument-field.css +2 -2
- package/src/navigation-instruments/readout/readout.ts +2 -1
- package/src/navigation-instruments/readout-list-item/readout-list-item.ts +2 -1
- package/src/palettes/blinking.spec.ts +83 -0
- package/src/palettes/blinking.ts +47 -35
- package/src/palettes/flashing-controller.ts +62 -0
- package/src/palettes/manual.css +18 -4
- package/src/svghelpers/index.ts +1 -0
- package/src/svghelpers/rounded-rect.spec.ts +47 -0
- package/src/svghelpers/rounded-rect.ts +42 -0
- package/src/types.ts +20 -0
|
@@ -6,14 +6,15 @@ import chartDebugStyle from "../../charthelpers/chart-debug.css.js";
|
|
|
6
6
|
import chartLegendStyle from "../../charthelpers/chart-legend.css.js";
|
|
7
7
|
import { Chart, LineController, LineElement, PointElement, ArcElement, CategoryScale, LinearScale, TimeScale, Filler, Tooltip } from "chart.js";
|
|
8
8
|
import { InstrumentState, Priority, FrameStyle, BorderRadiusPosition } from "../../navigation-instruments/types.js";
|
|
9
|
-
import {
|
|
9
|
+
import { RECTANGULAR_CHART_DIMENSIONS, CHART_DIMENSIONS, CHART_SECTOR_ENHANCED_COLORS, CHART_SECTOR_DEFAULT_COLORS, LINE_GRAPH_LABEL_CONFIG, CHART_AREA_BACKGROUND_COLOR_VAR, LINE_GRAPH_GRID_CONFIG } from "../../charthelpers/constants.js";
|
|
10
10
|
import { getCssVariableValue, applyAlphaToColor, getChartColorsOrDefault } from "../../charthelpers/colors.js";
|
|
11
11
|
import { observeThemeChanges } from "../../charthelpers/theme.js";
|
|
12
12
|
import { formatChartNumber } from "../../charthelpers/canvas-layout.js";
|
|
13
13
|
import { getChartTooltipOptions } from "../../charthelpers/tooltip.js";
|
|
14
14
|
import { generateLegendHTML } from "../../charthelpers/legend.js";
|
|
15
|
-
import { XValueMode,
|
|
15
|
+
import { XValueMode, formatXValue, normalizeXValue } from "../../charthelpers/x-value.js";
|
|
16
16
|
import { observeLabelThreshold } from "../../charthelpers/label-threshold.js";
|
|
17
|
+
import { yRangeLabelValues, measureRangeLabelGutters, formatRangeValue, rangeLabelLineHeight, rangeLabelFontString } from "../../charthelpers/range-labels.js";
|
|
17
18
|
import { readExternalScaleBorderRadiusPx, ScaleType, startExternalScaleBorderRadiusObserver, EXTERNAL_SCALE_BORDER_RADIUS_CSS_VAR } from "../external-scale/external-scale.js";
|
|
18
19
|
var __defProp = Object.defineProperty;
|
|
19
20
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -57,6 +58,13 @@ var TimeDisplay = /* @__PURE__ */ ((TimeDisplay2) => {
|
|
|
57
58
|
TimeDisplay2["date"] = "date";
|
|
58
59
|
return TimeDisplay2;
|
|
59
60
|
})(TimeDisplay || {});
|
|
61
|
+
var RangeLabels = /* @__PURE__ */ ((RangeLabels2) => {
|
|
62
|
+
RangeLabels2["none"] = "none";
|
|
63
|
+
RangeLabels2["y"] = "y";
|
|
64
|
+
RangeLabels2["x"] = "x";
|
|
65
|
+
RangeLabels2["xy"] = "xy";
|
|
66
|
+
return RangeLabels2;
|
|
67
|
+
})(RangeLabels || {});
|
|
60
68
|
const LINE_GRAPH_WATCHED_PROP_NAMES = [
|
|
61
69
|
"data",
|
|
62
70
|
"datasets",
|
|
@@ -65,6 +73,7 @@ const LINE_GRAPH_WATCHED_PROP_NAMES = [
|
|
|
65
73
|
"xAxisType",
|
|
66
74
|
"yAxisPosition",
|
|
67
75
|
"yAxes",
|
|
76
|
+
"xAxis",
|
|
68
77
|
"showGrid",
|
|
69
78
|
"showGridX",
|
|
70
79
|
"showGridY",
|
|
@@ -89,14 +98,17 @@ const LINE_GRAPH_WATCHED_PROP_NAMES = [
|
|
|
89
98
|
"height",
|
|
90
99
|
"fixedAspectRatioScaling",
|
|
91
100
|
// Triggers responsive mode change
|
|
92
|
-
"hasLabelPadding"
|
|
101
|
+
"hasLabelPadding",
|
|
93
102
|
// Toggles edge-to-edge rendering and label visibility
|
|
103
|
+
"rangeLabels"
|
|
94
104
|
];
|
|
95
105
|
const LINE_GRAPH_RECREATE_PROP_NAMES = [
|
|
96
106
|
"showDebugOverlay",
|
|
97
107
|
"width",
|
|
98
108
|
"height",
|
|
99
|
-
"fixedAspectRatioScaling"
|
|
109
|
+
"fixedAspectRatioScaling",
|
|
110
|
+
"rangeLabels"
|
|
111
|
+
// Adds or removes the range-labels plugin
|
|
100
112
|
];
|
|
101
113
|
const LINE_GRAPH_DIMENSION_PROP_NAMES = [
|
|
102
114
|
"width",
|
|
@@ -119,11 +131,14 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
119
131
|
this.xAxisType = "category";
|
|
120
132
|
this.yAxisPosition = "left";
|
|
121
133
|
this.yAxes = void 0;
|
|
134
|
+
this.xAxis = void 0;
|
|
122
135
|
this.showGrid = false;
|
|
123
136
|
this.showGridX = false;
|
|
124
137
|
this.showGridY = false;
|
|
125
138
|
this.showTickMarks = false;
|
|
126
139
|
this.hasLabelPadding = true;
|
|
140
|
+
this.rangeLabels = "none";
|
|
141
|
+
this.warnedDateDisplayScale = false;
|
|
127
142
|
this.DEFAULT_TENSION = 0.4;
|
|
128
143
|
this.POINT_RADIUS = 3;
|
|
129
144
|
this.showPoints = false;
|
|
@@ -146,6 +161,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
146
161
|
this.currentBorderRadiusPx = 8;
|
|
147
162
|
this.computedWidth = 480;
|
|
148
163
|
this.computedHeight = 320;
|
|
164
|
+
this.savedLabelThickness = /* @__PURE__ */ new WeakMap();
|
|
165
|
+
this.savedMainTickmarkLabels = /* @__PURE__ */ new WeakMap();
|
|
166
|
+
this.lastRangeLabels = [];
|
|
167
|
+
this.rangeLabelCache = { y: [], x: [] };
|
|
149
168
|
this.updateBorderRadius = () => {
|
|
150
169
|
if (!this.canvasEl) return;
|
|
151
170
|
let next;
|
|
@@ -185,10 +204,25 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
185
204
|
}, 16);
|
|
186
205
|
};
|
|
187
206
|
}
|
|
207
|
+
/** @internal - The y side wants range labels; an edge-to-edge chart never does. */
|
|
208
|
+
get rangeLabelsY() {
|
|
209
|
+
return this.hasLabelPadding && (this.rangeLabels === "y" || this.rangeLabels === "xy");
|
|
210
|
+
}
|
|
211
|
+
/** @internal - The x side wants range labels; an edge-to-edge chart never does. */
|
|
212
|
+
get rangeLabelsX() {
|
|
213
|
+
return this.hasLabelPadding && (this.rangeLabels === "x" || this.rangeLabels === "xy");
|
|
214
|
+
}
|
|
188
215
|
/** @internal - True when the x-axis positions points by numeric value. */
|
|
189
216
|
get isNumericXAxis() {
|
|
190
217
|
return this.xAxisType === "time" || this.xAxisType === "number";
|
|
191
218
|
}
|
|
219
|
+
/**
|
|
220
|
+
* Id of the y scale a dataset lands on when it names none. Chart.js would
|
|
221
|
+
* otherwise add a default `y` scale next to the configured ones and draw it.
|
|
222
|
+
*/
|
|
223
|
+
get primaryYAxisId() {
|
|
224
|
+
return this.yAxes?.length ? this.yAxes[0].id ?? "y0" : "y";
|
|
225
|
+
}
|
|
192
226
|
/** @internal - Normalization mode for the current x-axis type. */
|
|
193
227
|
get xValueMode() {
|
|
194
228
|
return this.xAxisType === "number" ? XValueMode.number : XValueMode.time;
|
|
@@ -227,37 +261,221 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
227
261
|
throw new Error("shouldStack() must be implemented in subclass");
|
|
228
262
|
}
|
|
229
263
|
/**
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
* Logic:
|
|
234
|
-
* - Opposite scales (left+right OR top+bottom) → no rounding (middleChild behavior)
|
|
235
|
-
* - Perpendicular scales (e.g., right+bottom):
|
|
236
|
-
* - innerFirstChild → round corner adjacent to both scales
|
|
237
|
-
* - outerLastChild → round free corner opposite to scales
|
|
238
|
-
* - Single-side scales:
|
|
239
|
-
* - When external scale is on RIGHT, chart is on LEFT
|
|
240
|
-
* - innerFirstChild → round LEFT corners (top-left + bottom-left)
|
|
241
|
-
* - outerLastChild → round RIGHT corners (top-right + bottom-right)
|
|
242
|
-
* - When external scale is on LEFT, chart is on RIGHT (opposite)
|
|
243
|
-
* - When external scale is on TOP, chart is on BOTTOM
|
|
244
|
-
* - innerFirstChild → round BOTTOM corners (bottom-left + bottom-right)
|
|
245
|
-
* - outerLastChild → round TOP corners (top-left + top-right)
|
|
246
|
-
* - When external scale is on BOTTOM, chart is on TOP (opposite)
|
|
247
|
-
* - middleChild → no rounding
|
|
248
|
-
*/
|
|
249
|
-
/**
|
|
250
|
-
* Check if a slotted scale element is actually visible (renders content).
|
|
251
|
-
* For bar-vertical/bar-horizontal elements, checks hasBar and hasScale properties.
|
|
264
|
+
* Label band a slotted scale needs for its compact labels, in the scale's
|
|
265
|
+
* own viewBox units. The scale prints the raw values, so those are what is
|
|
266
|
+
* measured; the x band is one text line.
|
|
252
267
|
*/
|
|
253
|
-
|
|
254
|
-
const
|
|
268
|
+
compactLabelThickness(axis) {
|
|
269
|
+
const context = this.canvasEl?.getContext("2d");
|
|
270
|
+
if (!context) return void 0;
|
|
271
|
+
const font = this.rangeLabelFont();
|
|
272
|
+
let visual;
|
|
273
|
+
if (axis === "y") {
|
|
274
|
+
const bounds = this.rangeLabelCache.bounds;
|
|
275
|
+
if (!bounds) return void 0;
|
|
276
|
+
const texts = yRangeLabelValues(bounds.min, bounds.max).map(String);
|
|
277
|
+
visual = measureRangeLabelGutters(context, font, texts, []).side + 4;
|
|
278
|
+
} else {
|
|
279
|
+
visual = measureRangeLabelGutters(context, font, [], ["x"]).bottom + 8;
|
|
280
|
+
}
|
|
281
|
+
if (!this.fixedAspectRatioScaling) return Math.ceil(visual);
|
|
282
|
+
const effectiveLength = axis === "y" ? this.getEffectiveHeight() : this.getEffectiveWidth();
|
|
283
|
+
return Math.ceil(visual * this.scaleReferenceSize / effectiveLength);
|
|
284
|
+
}
|
|
285
|
+
slotFor(side) {
|
|
286
|
+
return {
|
|
255
287
|
left: this.leftScaleSlot,
|
|
256
288
|
right: this.rightScaleSlot,
|
|
257
289
|
top: this.topScaleSlot,
|
|
258
290
|
bottom: this.bottomScaleSlot
|
|
291
|
+
}[side];
|
|
292
|
+
}
|
|
293
|
+
hasSlottedScale(side) {
|
|
294
|
+
return (this.slotFor(side)?.assignedElements().length ?? 0) > 0;
|
|
295
|
+
}
|
|
296
|
+
/** @internal - Below the label threshold on either dimension. */
|
|
297
|
+
isBelowThreshold() {
|
|
298
|
+
return this.getEffectiveWidth() < RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS || this.getEffectiveHeight() < RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS;
|
|
299
|
+
}
|
|
300
|
+
get ySide() {
|
|
301
|
+
const first = this.yAxes?.[0]?.position;
|
|
302
|
+
return (first ?? this.yAxisPosition) === "right" ? "right" : "left";
|
|
303
|
+
}
|
|
304
|
+
rangeLabelFont() {
|
|
305
|
+
const sizePx = Number.parseFloat(
|
|
306
|
+
getCssVariableValue(this, LINE_GRAPH_LABEL_CONFIG.fontSizeVar)
|
|
307
|
+
);
|
|
308
|
+
return {
|
|
309
|
+
family: getCssVariableValue(this, LINE_GRAPH_LABEL_CONFIG.fontFamily),
|
|
310
|
+
sizePx: Number.isFinite(sizePx) ? sizePx : 12,
|
|
311
|
+
weight: getCssVariableValue(this, LINE_GRAPH_LABEL_CONFIG.fontWeightVar)
|
|
259
312
|
};
|
|
260
|
-
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Extent of the prepared data: one for x, one per y scale id. Each y axis
|
|
316
|
+
* ranges from its own datasets, so a merged extent would label one axis
|
|
317
|
+
* with another's values.
|
|
318
|
+
*/
|
|
319
|
+
dataExtents(prepared) {
|
|
320
|
+
const x = { min: Infinity, max: -Infinity };
|
|
321
|
+
const y = /* @__PURE__ */ new Map();
|
|
322
|
+
const take = (extent, v) => {
|
|
323
|
+
if (!Number.isFinite(v)) return;
|
|
324
|
+
extent.min = Math.min(extent.min, v);
|
|
325
|
+
extent.max = Math.max(extent.max, v);
|
|
326
|
+
};
|
|
327
|
+
prepared.datasets.forEach((dataset) => {
|
|
328
|
+
const id = dataset.yAxisID ?? this.primaryYAxisId;
|
|
329
|
+
const extent = y.get(id) ?? { min: Infinity, max: -Infinity };
|
|
330
|
+
y.set(id, extent);
|
|
331
|
+
dataset.data.forEach((point) => {
|
|
332
|
+
if (typeof point === "number") {
|
|
333
|
+
take(extent, point);
|
|
334
|
+
} else if (point) {
|
|
335
|
+
take(x, point.x);
|
|
336
|
+
take(extent, point.y);
|
|
337
|
+
}
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
y.forEach((extent, id) => {
|
|
341
|
+
if (!(extent.min <= extent.max)) y.delete(id);
|
|
342
|
+
});
|
|
343
|
+
return { x: x.min <= x.max ? x : void 0, y };
|
|
344
|
+
}
|
|
345
|
+
/**
|
|
346
|
+
* Range the y labels describe. Below the threshold the axis runs on data
|
|
347
|
+
* bounds, so its datasets' extent is exact and needs no chart to read from.
|
|
348
|
+
*/
|
|
349
|
+
yRangeBounds(extents) {
|
|
350
|
+
let axis;
|
|
351
|
+
let id = "y";
|
|
352
|
+
if (this.yAxes?.length) {
|
|
353
|
+
const match = this.yAxes.findIndex(
|
|
354
|
+
(a) => (a.position ?? "left") === this.ySide
|
|
355
|
+
);
|
|
356
|
+
const index = match === -1 ? 0 : match;
|
|
357
|
+
axis = this.yAxes[index];
|
|
358
|
+
id = axis.id ?? `y${index}`;
|
|
359
|
+
}
|
|
360
|
+
const extent = extents.get(id);
|
|
361
|
+
const stackedScale = this.shouldStack() ? this.chart?.scales[id] : void 0;
|
|
362
|
+
const min = axis?.min ?? stackedScale?.min ?? extent?.min;
|
|
363
|
+
const max = axis?.max ?? stackedScale?.max ?? extent?.max;
|
|
364
|
+
return min !== void 0 && max !== void 0 ? { min, max } : void 0;
|
|
365
|
+
}
|
|
366
|
+
/** First and last x label, or nothing when there is no span to label. */
|
|
367
|
+
xEdgeLabelTexts(prepared, extent) {
|
|
368
|
+
if (!this.isNumericXAxis) {
|
|
369
|
+
const labels = prepared.labels.map(String);
|
|
370
|
+
const first = labels[0];
|
|
371
|
+
const last = labels[labels.length - 1];
|
|
372
|
+
return labels.length > 1 && first !== last ? [first, last] : [];
|
|
373
|
+
}
|
|
374
|
+
const min = this.xAxis?.min ?? extent?.min;
|
|
375
|
+
const max = this.xAxis?.max ?? extent?.max;
|
|
376
|
+
if (min === void 0 || max === void 0 || min === max) return [];
|
|
377
|
+
const reference = this.timeDisplay === "minutes" ? this.computeTimeReference() : void 0;
|
|
378
|
+
return [
|
|
379
|
+
formatXValue(min, this.xValueMode, reference),
|
|
380
|
+
formatXValue(max, this.xValueMode, reference)
|
|
381
|
+
];
|
|
382
|
+
}
|
|
383
|
+
/**
|
|
384
|
+
* Recompute the range labels. A side with a slotted scale is labelled by
|
|
385
|
+
* the scale, so it gets no text here; the y bounds are kept regardless,
|
|
386
|
+
* since the scale's compact band is measured from them.
|
|
387
|
+
*/
|
|
388
|
+
refreshRangeLabels() {
|
|
389
|
+
const cache = { y: [], x: [] };
|
|
390
|
+
if (this.isBelowThreshold() && (this.rangeLabelsY || this.rangeLabelsX)) {
|
|
391
|
+
const prepared = this.prepareChartDataAndLabels();
|
|
392
|
+
const extents = this.dataExtents(prepared);
|
|
393
|
+
if (this.rangeLabelsY) {
|
|
394
|
+
cache.bounds = this.yRangeBounds(extents.y);
|
|
395
|
+
if (cache.bounds && !this.hasSlottedScale("left") && !this.hasSlottedScale("right")) {
|
|
396
|
+
const { min, max } = cache.bounds;
|
|
397
|
+
cache.y = yRangeLabelValues(min, max).map(
|
|
398
|
+
(v) => formatRangeValue(v, min, max)
|
|
399
|
+
);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
if (this.rangeLabelsX && !this.hasSlottedScale("top") && !this.hasSlottedScale("bottom")) {
|
|
403
|
+
cache.x = this.xEdgeLabelTexts(prepared, extents.x);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
this.rangeLabelCache = cache;
|
|
407
|
+
return cache;
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Paints min / 0 / max and first / last once Chart.js has drawn, inside the
|
|
411
|
+
* gutters `getChartOptions()` reserved. The end labels sit flush with the
|
|
412
|
+
* plot edges so no vertical padding is needed for them.
|
|
413
|
+
*/
|
|
414
|
+
createRangeLabelsPlugin() {
|
|
415
|
+
return {
|
|
416
|
+
id: "rangeLabels",
|
|
417
|
+
afterDraw: (chart) => {
|
|
418
|
+
this.lastRangeLabels = [];
|
|
419
|
+
const { y: yTexts, x: xTexts, bounds } = this.rangeLabelCache;
|
|
420
|
+
if (!yTexts.length && !xTexts.length) return;
|
|
421
|
+
const area = chart.chartArea;
|
|
422
|
+
const ctx = chart.ctx;
|
|
423
|
+
const font = this.rangeLabelFont();
|
|
424
|
+
const lineHeight = rangeLabelLineHeight(font);
|
|
425
|
+
ctx.save();
|
|
426
|
+
ctx.font = rangeLabelFontString(font);
|
|
427
|
+
ctx.fillStyle = getCssVariableValue(
|
|
428
|
+
this,
|
|
429
|
+
LINE_GRAPH_LABEL_CONFIG.fontColorVar
|
|
430
|
+
);
|
|
431
|
+
const scale = chart.scales[this.primaryYAxisId];
|
|
432
|
+
if (yTexts.length && bounds && scale) {
|
|
433
|
+
const onRight = this.ySide === "right";
|
|
434
|
+
const x = onRight ? area.right + 8 : area.left - 8;
|
|
435
|
+
ctx.textAlign = onRight ? "left" : "right";
|
|
436
|
+
const values = yRangeLabelValues(bounds.min, bounds.max);
|
|
437
|
+
for (let i = values.length - 1; i >= 0; i--) {
|
|
438
|
+
const isMax = i === values.length - 1;
|
|
439
|
+
const isMin = i === 0;
|
|
440
|
+
const y = isMax ? area.top : isMin ? area.bottom : scale.getPixelForValue(values[i]);
|
|
441
|
+
if (!isMax && !isMin && (y - area.top < lineHeight || area.bottom - y < lineHeight)) {
|
|
442
|
+
continue;
|
|
443
|
+
}
|
|
444
|
+
ctx.textBaseline = isMax ? "top" : isMin ? "bottom" : "middle";
|
|
445
|
+
ctx.fillText(yTexts[i], x, y);
|
|
446
|
+
this.lastRangeLabels.push({ axis: "y", text: yTexts[i], x, y });
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (xTexts.length === 2) {
|
|
450
|
+
const y = area.bottom + 4;
|
|
451
|
+
ctx.textBaseline = "top";
|
|
452
|
+
ctx.textAlign = "left";
|
|
453
|
+
ctx.fillText(xTexts[0], area.left, y);
|
|
454
|
+
this.lastRangeLabels.push({
|
|
455
|
+
axis: "x",
|
|
456
|
+
text: xTexts[0],
|
|
457
|
+
x: area.left,
|
|
458
|
+
y
|
|
459
|
+
});
|
|
460
|
+
ctx.textAlign = "right";
|
|
461
|
+
ctx.fillText(xTexts[1], area.right, y);
|
|
462
|
+
this.lastRangeLabels.push({
|
|
463
|
+
axis: "x",
|
|
464
|
+
text: xTexts[1],
|
|
465
|
+
x: area.right,
|
|
466
|
+
y
|
|
467
|
+
});
|
|
468
|
+
}
|
|
469
|
+
ctx.restore();
|
|
470
|
+
}
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Check if a slotted scale element is actually visible (renders content).
|
|
475
|
+
* For bar-vertical/bar-horizontal elements, checks hasBar and hasScale properties.
|
|
476
|
+
*/
|
|
477
|
+
hasVisibleScale(side) {
|
|
478
|
+
const slot = this.slotFor(side);
|
|
261
479
|
const elements = slot?.assignedElements() ?? [];
|
|
262
480
|
return elements.some((el) => {
|
|
263
481
|
const barEl = el;
|
|
@@ -637,13 +855,13 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
637
855
|
if (this.isUpdatingScales) return false;
|
|
638
856
|
this.isUpdatingScales = true;
|
|
639
857
|
try {
|
|
640
|
-
const padding = this.
|
|
641
|
-
const effectiveWidth = this.
|
|
642
|
-
const effectiveHeight = this.
|
|
858
|
+
const padding = this.computeChartPadding();
|
|
859
|
+
const effectiveWidth = this.getEffectiveWidth() - padding.left - padding.right;
|
|
860
|
+
const effectiveHeight = this.getEffectiveHeight() - padding.top - padding.bottom;
|
|
643
861
|
if (effectiveWidth <= 0 || effectiveHeight <= 0) {
|
|
644
862
|
console.warn("[chart-line-base] Invalid effective dimensions", {
|
|
645
|
-
width: this.
|
|
646
|
-
height: this.
|
|
863
|
+
width: this.getEffectiveWidth(),
|
|
864
|
+
height: this.getEffectiveHeight(),
|
|
647
865
|
padding,
|
|
648
866
|
effectiveWidth,
|
|
649
867
|
effectiveHeight
|
|
@@ -680,47 +898,139 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
680
898
|
};
|
|
681
899
|
return padding;
|
|
682
900
|
}
|
|
901
|
+
/**
|
|
902
|
+
* Range a slotted scale on the given side has to cover.
|
|
903
|
+
*
|
|
904
|
+
* A range pinned in the axis configuration is used as is: it is the source
|
|
905
|
+
* of truth, and on the first sync there is no chart to read yet. Otherwise
|
|
906
|
+
* the live Chart.js scale is read under the id `buildScalesConfig()` gives
|
|
907
|
+
* it. A side with no axis of its own follows the first configured one.
|
|
908
|
+
*/
|
|
909
|
+
resolveAxisRange(side) {
|
|
910
|
+
let id = side === "x" ? "x" : "y";
|
|
911
|
+
let configured;
|
|
912
|
+
if (side === "x") {
|
|
913
|
+
configured = this.isNumericXAxis ? this.xAxis : void 0;
|
|
914
|
+
} else if (this.yAxes?.length) {
|
|
915
|
+
const match = this.yAxes.findIndex(
|
|
916
|
+
(axis2) => (axis2.position ?? "left") === side
|
|
917
|
+
);
|
|
918
|
+
const index = match === -1 ? 0 : match;
|
|
919
|
+
const axis = this.yAxes[index];
|
|
920
|
+
id = axis.id ?? `y${index}`;
|
|
921
|
+
configured = axis;
|
|
922
|
+
}
|
|
923
|
+
if (configured?.min !== void 0 && configured.max !== void 0) {
|
|
924
|
+
return { min: configured.min, max: configured.max };
|
|
925
|
+
}
|
|
926
|
+
const scale = this.chart?.scales[id];
|
|
927
|
+
return {
|
|
928
|
+
min: scale?.min ?? configured?.min ?? 0,
|
|
929
|
+
max: scale?.max ?? configured?.max ?? 100
|
|
930
|
+
};
|
|
931
|
+
}
|
|
932
|
+
/**
|
|
933
|
+
* Range pushed to the top and bottom scales, or `undefined` to leave them
|
|
934
|
+
* alone. A scale prints its values verbatim, and on a `time` axis the raw
|
|
935
|
+
* range is epoch milliseconds. In `minutes` display the range is converted
|
|
936
|
+
* to the minutes the chart's own labels show; `date` display has no numeric
|
|
937
|
+
* equivalent, so the scale keeps its own range.
|
|
938
|
+
*/
|
|
939
|
+
resolveSlottedXRange() {
|
|
940
|
+
const range = this.resolveAxisRange("x");
|
|
941
|
+
if (this.xAxisType !== "time") return range;
|
|
942
|
+
if (this.timeDisplay !== "minutes") {
|
|
943
|
+
if (!this.warnedDateDisplayScale) {
|
|
944
|
+
this.warnedDateDisplayScale = true;
|
|
945
|
+
console.warn(
|
|
946
|
+
"[obc-chart] a slotted horizontal scale cannot label a time axis in 'date' display and keeps its own range; use timeDisplay='minutes' or a number axis."
|
|
947
|
+
);
|
|
948
|
+
}
|
|
949
|
+
return void 0;
|
|
950
|
+
}
|
|
951
|
+
const pinned = this.xAxis?.min !== void 0 && this.xAxis?.max !== void 0;
|
|
952
|
+
const reference = this.computeTimeReference();
|
|
953
|
+
if (reference === void 0 || !this.chart && !pinned) return void 0;
|
|
954
|
+
const minute = 6e4;
|
|
955
|
+
return {
|
|
956
|
+
min: (range.min - reference) / minute,
|
|
957
|
+
max: (range.max - reference) / minute
|
|
958
|
+
};
|
|
959
|
+
}
|
|
683
960
|
/**
|
|
684
961
|
* Update properties on slotted scale elements
|
|
685
962
|
*/
|
|
686
963
|
updateScaleProperties(padding) {
|
|
687
|
-
const
|
|
688
|
-
const
|
|
689
|
-
const
|
|
690
|
-
const xMax = this.chart?.scales["x"]?.max ?? 100;
|
|
964
|
+
const leftRange = this.resolveAxisRange("left");
|
|
965
|
+
const rightRange = this.resolveAxisRange("right");
|
|
966
|
+
const xRange = this.resolveSlottedXRange();
|
|
691
967
|
const effectiveWidth = this.getEffectiveWidth();
|
|
692
968
|
const effectiveHeight = this.getEffectiveHeight();
|
|
693
|
-
const
|
|
969
|
+
const aboveThreshold = effectiveWidth >= RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS && effectiveHeight >= RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS;
|
|
970
|
+
const showLabels = this.hasLabelPadding && aboveThreshold;
|
|
971
|
+
const compactY = !aboveThreshold && this.rangeLabelsY;
|
|
972
|
+
const compactX = !aboveThreshold && this.rangeLabelsX;
|
|
973
|
+
const compactYThickness = compactY ? this.compactLabelThickness("y") : void 0;
|
|
974
|
+
const compactXThickness = compactX ? this.compactLabelThickness("x") : void 0;
|
|
975
|
+
const mainLabelsFor = (scale, compact) => {
|
|
976
|
+
if (compact) {
|
|
977
|
+
if (!this.savedMainTickmarkLabels.has(scale)) {
|
|
978
|
+
this.savedMainTickmarkLabels.set(
|
|
979
|
+
scale,
|
|
980
|
+
scale.showMainTickmarkLabels ?? false
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
return true;
|
|
984
|
+
}
|
|
985
|
+
if (!this.savedMainTickmarkLabels.has(scale)) return void 0;
|
|
986
|
+
const saved = this.savedMainTickmarkLabels.get(scale);
|
|
987
|
+
this.savedMainTickmarkLabels.delete(scale);
|
|
988
|
+
return saved;
|
|
989
|
+
};
|
|
990
|
+
const bandFor = (scale, compact, thickness) => {
|
|
991
|
+
if (compact) {
|
|
992
|
+
if (thickness === void 0 || typeof scale.labelThickness !== "number") {
|
|
993
|
+
return void 0;
|
|
994
|
+
}
|
|
995
|
+
if (!this.savedLabelThickness.has(scale)) {
|
|
996
|
+
this.savedLabelThickness.set(scale, scale.labelThickness);
|
|
997
|
+
}
|
|
998
|
+
return thickness;
|
|
999
|
+
}
|
|
1000
|
+
const saved = this.savedLabelThickness.get(scale);
|
|
1001
|
+
this.savedLabelThickness.delete(scale);
|
|
1002
|
+
return saved;
|
|
1003
|
+
};
|
|
694
1004
|
const verticalViewBoxPadding = this.fixedAspectRatioScaling ? {
|
|
695
1005
|
top: Math.round(
|
|
696
|
-
padding.top * this.scaleReferenceSize /
|
|
1006
|
+
padding.top * this.scaleReferenceSize / effectiveHeight
|
|
697
1007
|
),
|
|
698
1008
|
bottom: Math.round(
|
|
699
|
-
padding.bottom * this.scaleReferenceSize /
|
|
1009
|
+
padding.bottom * this.scaleReferenceSize / effectiveHeight
|
|
700
1010
|
)
|
|
701
1011
|
} : { top: padding.top, bottom: padding.bottom };
|
|
702
1012
|
const horizontalViewBoxPadding = this.fixedAspectRatioScaling ? {
|
|
703
1013
|
left: Math.round(
|
|
704
|
-
padding.left * this.scaleReferenceSize /
|
|
1014
|
+
padding.left * this.scaleReferenceSize / effectiveWidth
|
|
705
1015
|
),
|
|
706
1016
|
right: Math.round(
|
|
707
|
-
padding.right * this.scaleReferenceSize /
|
|
1017
|
+
padding.right * this.scaleReferenceSize / effectiveWidth
|
|
708
1018
|
)
|
|
709
1019
|
} : { left: padding.left, right: padding.right };
|
|
710
1020
|
const updates = [];
|
|
1021
|
+
const rangeProps = (side, range) => this.ownsSlottedScaleRange(side) ? { minValue: range.min, maxValue: range.max } : {};
|
|
711
1022
|
if (this.leftScaleSlot) {
|
|
712
1023
|
const scales = this.leftScaleSlot.assignedElements();
|
|
713
1024
|
scales.forEach((scale) => {
|
|
714
1025
|
const props = {
|
|
715
|
-
|
|
716
|
-
maxValue: yMax,
|
|
1026
|
+
...rangeProps("left", leftRange),
|
|
717
1027
|
height: effectiveHeight,
|
|
718
1028
|
// Use effective height for proper sizing
|
|
719
1029
|
paddingTop: verticalViewBoxPadding.top,
|
|
720
1030
|
paddingBottom: verticalViewBoxPadding.bottom,
|
|
721
1031
|
paddingStart: verticalViewBoxPadding.top,
|
|
722
1032
|
paddingEnd: verticalViewBoxPadding.bottom,
|
|
723
|
-
showLabels,
|
|
1033
|
+
showLabels: showLabels || compactY,
|
|
724
1034
|
fixedAspectRatio: this.fixedAspectRatioScaling,
|
|
725
1035
|
// Use chart's scaleReferenceSize property for proportional scaling
|
|
726
1036
|
scaleReferenceSize: this.scaleReferenceSize,
|
|
@@ -732,6 +1042,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
732
1042
|
if (this.yStepSize !== void 0) {
|
|
733
1043
|
props.primaryTickmarkInterval = this.yStepSize;
|
|
734
1044
|
}
|
|
1045
|
+
const band = bandFor(scale, compactY, compactYThickness);
|
|
1046
|
+
const mainLabels = mainLabelsFor(scale, compactY);
|
|
1047
|
+
if (mainLabels !== void 0) props.showMainTickmarkLabels = mainLabels;
|
|
1048
|
+
if (band !== void 0) props.labelThickness = band;
|
|
735
1049
|
updates.push([this.leftScaleSlot, scale, props]);
|
|
736
1050
|
});
|
|
737
1051
|
}
|
|
@@ -739,15 +1053,14 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
739
1053
|
const scales = this.rightScaleSlot.assignedElements();
|
|
740
1054
|
scales.forEach((scale) => {
|
|
741
1055
|
const props = {
|
|
742
|
-
|
|
743
|
-
maxValue: yMax,
|
|
1056
|
+
...rangeProps("right", rightRange),
|
|
744
1057
|
height: effectiveHeight,
|
|
745
1058
|
// Use effective height for proper sizing
|
|
746
1059
|
paddingTop: verticalViewBoxPadding.top,
|
|
747
1060
|
paddingBottom: verticalViewBoxPadding.bottom,
|
|
748
1061
|
paddingStart: verticalViewBoxPadding.top,
|
|
749
1062
|
paddingEnd: verticalViewBoxPadding.bottom,
|
|
750
|
-
showLabels,
|
|
1063
|
+
showLabels: showLabels || compactY,
|
|
751
1064
|
fixedAspectRatio: this.fixedAspectRatioScaling,
|
|
752
1065
|
// Use chart's scaleReferenceSize property for proportional scaling
|
|
753
1066
|
scaleReferenceSize: this.scaleReferenceSize,
|
|
@@ -759,6 +1072,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
759
1072
|
if (this.yStepSize !== void 0) {
|
|
760
1073
|
props.primaryTickmarkInterval = this.yStepSize;
|
|
761
1074
|
}
|
|
1075
|
+
const band = bandFor(scale, compactY, compactYThickness);
|
|
1076
|
+
const mainLabels = mainLabelsFor(scale, compactY);
|
|
1077
|
+
if (mainLabels !== void 0) props.showMainTickmarkLabels = mainLabels;
|
|
1078
|
+
if (band !== void 0) props.labelThickness = band;
|
|
762
1079
|
updates.push([this.rightScaleSlot, scale, props]);
|
|
763
1080
|
});
|
|
764
1081
|
}
|
|
@@ -766,15 +1083,14 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
766
1083
|
const scales = this.topScaleSlot.assignedElements();
|
|
767
1084
|
scales.forEach((scale) => {
|
|
768
1085
|
const props = {
|
|
769
|
-
|
|
770
|
-
maxValue: xMax,
|
|
1086
|
+
...xRange,
|
|
771
1087
|
width: effectiveWidth,
|
|
772
1088
|
// Use effective width for proper sizing
|
|
773
1089
|
paddingLeft: horizontalViewBoxPadding.left,
|
|
774
1090
|
paddingRight: horizontalViewBoxPadding.right,
|
|
775
1091
|
paddingStart: horizontalViewBoxPadding.left,
|
|
776
1092
|
paddingEnd: horizontalViewBoxPadding.right,
|
|
777
|
-
showLabels,
|
|
1093
|
+
showLabels: showLabels || compactX,
|
|
778
1094
|
fixedAspectRatio: this.fixedAspectRatioScaling,
|
|
779
1095
|
// Use chart's scaleReferenceSize property for proportional scaling
|
|
780
1096
|
scaleReferenceSize: this.scaleReferenceSize,
|
|
@@ -786,6 +1102,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
786
1102
|
if (this.xStepSize !== void 0) {
|
|
787
1103
|
props.primaryTickmarkInterval = this.xStepSize;
|
|
788
1104
|
}
|
|
1105
|
+
const band = bandFor(scale, compactX, compactXThickness);
|
|
1106
|
+
const mainLabels = mainLabelsFor(scale, compactX);
|
|
1107
|
+
if (mainLabels !== void 0) props.showMainTickmarkLabels = mainLabels;
|
|
1108
|
+
if (band !== void 0) props.labelThickness = band;
|
|
789
1109
|
updates.push([this.topScaleSlot, scale, props]);
|
|
790
1110
|
});
|
|
791
1111
|
}
|
|
@@ -793,15 +1113,14 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
793
1113
|
const scales = this.bottomScaleSlot.assignedElements();
|
|
794
1114
|
scales.forEach((scale) => {
|
|
795
1115
|
const props = {
|
|
796
|
-
|
|
797
|
-
maxValue: xMax,
|
|
1116
|
+
...xRange,
|
|
798
1117
|
width: effectiveWidth,
|
|
799
1118
|
// Use effective width for proper sizing
|
|
800
1119
|
paddingLeft: horizontalViewBoxPadding.left,
|
|
801
1120
|
paddingRight: horizontalViewBoxPadding.right,
|
|
802
1121
|
paddingStart: horizontalViewBoxPadding.left,
|
|
803
1122
|
paddingEnd: horizontalViewBoxPadding.right,
|
|
804
|
-
showLabels,
|
|
1123
|
+
showLabels: showLabels || compactX,
|
|
805
1124
|
fixedAspectRatio: this.fixedAspectRatioScaling,
|
|
806
1125
|
// Use chart's scaleReferenceSize property for proportional scaling
|
|
807
1126
|
scaleReferenceSize: this.scaleReferenceSize,
|
|
@@ -813,6 +1132,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
813
1132
|
if (this.xStepSize !== void 0) {
|
|
814
1133
|
props.primaryTickmarkInterval = this.xStepSize;
|
|
815
1134
|
}
|
|
1135
|
+
const band = bandFor(scale, compactX, compactXThickness);
|
|
1136
|
+
const mainLabels = mainLabelsFor(scale, compactX);
|
|
1137
|
+
if (mainLabels !== void 0) props.showMainTickmarkLabels = mainLabels;
|
|
1138
|
+
if (band !== void 0) props.labelThickness = band;
|
|
816
1139
|
updates.push([this.bottomScaleSlot, scale, props]);
|
|
817
1140
|
});
|
|
818
1141
|
}
|
|
@@ -820,6 +1143,34 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
820
1143
|
Object.assign(scale, props);
|
|
821
1144
|
});
|
|
822
1145
|
}
|
|
1146
|
+
/**
|
|
1147
|
+
* Whether the chart's axis range is what a slotted scale on this side should
|
|
1148
|
+
* describe. A subclass that slots a scale of its own and gives it a separate
|
|
1149
|
+
* range returns false for that side, otherwise the sync below overwrites it.
|
|
1150
|
+
*/
|
|
1151
|
+
ownsSlottedScaleRange(_side) {
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Push the current axis ranges to the slotted scales. The full
|
|
1156
|
+
* `updateScaleProperties()` cascade runs before the chart is (re)built and
|
|
1157
|
+
* so reads the previous chart's ranges; this closes that gap once the new
|
|
1158
|
+
* chart exists, and keeps auto-ranged scales in step with the data.
|
|
1159
|
+
*/
|
|
1160
|
+
syncSlottedScaleRanges() {
|
|
1161
|
+
const apply = (side, slot, range) => {
|
|
1162
|
+
if (!slot || !range || !this.ownsSlottedScaleRange(side)) return;
|
|
1163
|
+
slot.assignedElements().forEach((scale) => {
|
|
1164
|
+
scale.minValue = range.min;
|
|
1165
|
+
scale.maxValue = range.max;
|
|
1166
|
+
});
|
|
1167
|
+
};
|
|
1168
|
+
apply("left", this.leftScaleSlot, this.resolveAxisRange("left"));
|
|
1169
|
+
apply("right", this.rightScaleSlot, this.resolveAxisRange("right"));
|
|
1170
|
+
const xRange = this.resolveSlottedXRange();
|
|
1171
|
+
apply("top", this.topScaleSlot, xRange);
|
|
1172
|
+
apply("bottom", this.bottomScaleSlot, xRange);
|
|
1173
|
+
}
|
|
823
1174
|
hasAnyChanged(changed, props) {
|
|
824
1175
|
return props.some((prop) => changed.has(prop));
|
|
825
1176
|
}
|
|
@@ -893,7 +1244,10 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
893
1244
|
if (this.hasAnyChanged(changed, LINE_GRAPH_DIMENSION_PROP_NAMES)) {
|
|
894
1245
|
if (this.updateComputedDimensions()) return;
|
|
895
1246
|
}
|
|
896
|
-
|
|
1247
|
+
const belowThreshold = this.isBelowThreshold();
|
|
1248
|
+
const crossedThreshold = this.wasBelowThreshold !== void 0 && this.wasBelowThreshold !== belowThreshold;
|
|
1249
|
+
this.wasBelowThreshold = belowThreshold;
|
|
1250
|
+
if ((changed.has("hasLabelPadding") || changed.has("rangeLabels") || crossedThreshold) && this.hasExternalScales()) {
|
|
897
1251
|
this.syncScalesAndChart();
|
|
898
1252
|
return;
|
|
899
1253
|
}
|
|
@@ -948,7 +1302,7 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
948
1302
|
if (!this.canvasEl) return;
|
|
949
1303
|
this.resizeObserver = observeLabelThreshold(
|
|
950
1304
|
this.canvasEl,
|
|
951
|
-
() => (this.canvasEl?.clientHeight ?? 0) >= RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS,
|
|
1305
|
+
() => (this.canvasEl?.clientHeight ?? 0) >= RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS && (this.canvasEl?.clientWidth ?? 0) >= RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS,
|
|
952
1306
|
{
|
|
953
1307
|
rebuild: () => {
|
|
954
1308
|
this.chart?.destroy();
|
|
@@ -1069,6 +1423,7 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1069
1423
|
const result = {
|
|
1070
1424
|
...existingDataset || {},
|
|
1071
1425
|
data: existingDataset?.data ?? values,
|
|
1426
|
+
yAxisID: existingDataset?.yAxisID ?? this.primaryYAxisId,
|
|
1072
1427
|
borderColor,
|
|
1073
1428
|
backgroundColor,
|
|
1074
1429
|
borderWidth: existingDataset?.borderWidth ?? 2,
|
|
@@ -1115,6 +1470,7 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1115
1470
|
const baselineDataset = {
|
|
1116
1471
|
label: "threshold-baseline",
|
|
1117
1472
|
data: baselineData,
|
|
1473
|
+
yAxisID: this.primaryYAxisId,
|
|
1118
1474
|
borderColor: "transparent",
|
|
1119
1475
|
backgroundColor: "transparent",
|
|
1120
1476
|
borderWidth: 0,
|
|
@@ -1200,6 +1556,36 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1200
1556
|
const datasets = fill && fillMode === "threshold" ? this.createThresholdDatasets(values, chartColors) : [this.buildDataset(values, 0, chartColors)];
|
|
1201
1557
|
return { datasets, labels };
|
|
1202
1558
|
}
|
|
1559
|
+
/**
|
|
1560
|
+
* One padding source for the Chart.js layout and the slotted scales: two
|
|
1561
|
+
* sources painted the canvas over a scale and inset a scale inside a chart.
|
|
1562
|
+
*/
|
|
1563
|
+
computeChartPadding() {
|
|
1564
|
+
const isTooSmall = this.isBelowThreshold();
|
|
1565
|
+
const scaleFactor = this.getScaleFactor();
|
|
1566
|
+
const defaultPaddingScaled = !this.hasLabelPadding ? 0 : this.fixedAspectRatioScaling ? Math.round(CHART_DIMENSIONS.CANVAS_PADDING * scaleFactor) : CHART_DIMENSIONS.CANVAS_PADDING;
|
|
1567
|
+
const { y: yTexts, x: xTexts } = this.refreshRangeLabels();
|
|
1568
|
+
const context = this.canvasEl?.getContext("2d");
|
|
1569
|
+
const gutters = (yTexts.length || xTexts.length) && context ? measureRangeLabelGutters(
|
|
1570
|
+
context,
|
|
1571
|
+
this.rangeLabelFont(),
|
|
1572
|
+
yTexts,
|
|
1573
|
+
xTexts
|
|
1574
|
+
) : { side: 0, bottom: 0 };
|
|
1575
|
+
const scalePadding = this.calculatePaddingFromScales();
|
|
1576
|
+
const freeSide = (side) => {
|
|
1577
|
+
if (!isTooSmall) return defaultPaddingScaled;
|
|
1578
|
+
if (side === "bottom") return gutters.bottom;
|
|
1579
|
+
return side === this.ySide ? gutters.side : 0;
|
|
1580
|
+
};
|
|
1581
|
+
const padding = {
|
|
1582
|
+
top: this.externalScaleDimensions.has("top") ? scalePadding.top : freeSide("top"),
|
|
1583
|
+
right: this.externalScaleDimensions.has("right") ? scalePadding.right : freeSide("right"),
|
|
1584
|
+
bottom: this.externalScaleDimensions.has("bottom") ? scalePadding.bottom : freeSide("bottom"),
|
|
1585
|
+
left: this.externalScaleDimensions.has("left") ? scalePadding.left : freeSide("left")
|
|
1586
|
+
};
|
|
1587
|
+
return padding;
|
|
1588
|
+
}
|
|
1203
1589
|
/**
|
|
1204
1590
|
* Get Chart.js options with dynamic sizing and padding
|
|
1205
1591
|
*/
|
|
@@ -1207,27 +1593,7 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1207
1593
|
const effectiveWidth = this.getEffectiveWidth();
|
|
1208
1594
|
const effectiveHeight = this.getEffectiveHeight();
|
|
1209
1595
|
const isTooSmall = effectiveWidth < RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS || effectiveHeight < RECTANGULAR_CHART_DIMENSIONS.MIN_HEIGHT_WITH_LABELS;
|
|
1210
|
-
const
|
|
1211
|
-
let padding;
|
|
1212
|
-
const defaultPaddingScaled = !this.hasLabelPadding ? 0 : this.fixedAspectRatioScaling ? Math.round(CHART_DIMENSIONS.CANVAS_PADDING * scaleFactor) : CHART_DIMENSIONS.CANVAS_PADDING;
|
|
1213
|
-
if (isTooSmall && this.hasLabelPadding) {
|
|
1214
|
-
padding = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
1215
|
-
} else if (this.hasExternalScales()) {
|
|
1216
|
-
const scalePadding = this.calculatePaddingFromScales();
|
|
1217
|
-
padding = {
|
|
1218
|
-
top: this.externalScaleDimensions.has("top") ? scalePadding.top : defaultPaddingScaled,
|
|
1219
|
-
right: this.externalScaleDimensions.has("right") ? scalePadding.right : defaultPaddingScaled,
|
|
1220
|
-
bottom: this.externalScaleDimensions.has("bottom") ? scalePadding.bottom : defaultPaddingScaled,
|
|
1221
|
-
left: this.externalScaleDimensions.has("left") ? scalePadding.left : defaultPaddingScaled
|
|
1222
|
-
};
|
|
1223
|
-
} else {
|
|
1224
|
-
padding = {
|
|
1225
|
-
top: defaultPaddingScaled,
|
|
1226
|
-
right: defaultPaddingScaled,
|
|
1227
|
-
bottom: defaultPaddingScaled,
|
|
1228
|
-
left: defaultPaddingScaled
|
|
1229
|
-
};
|
|
1230
|
-
}
|
|
1596
|
+
const padding = this.computeChartPadding();
|
|
1231
1597
|
if (this.fixedAspectRatioScaling) {
|
|
1232
1598
|
this.style.setProperty("--chart-width", "100%");
|
|
1233
1599
|
this.style.setProperty("--chart-height", `${effectiveHeight}px`);
|
|
@@ -1282,9 +1648,13 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1282
1648
|
/**
|
|
1283
1649
|
* Compute reference timestamp for time axis formatting.
|
|
1284
1650
|
* Returns earliest timestamp for 'date' mode, latest for 'minutes' mode.
|
|
1651
|
+
* A pinned x range supplies the reference instead, so the axis edge stays
|
|
1652
|
+
* the reference while the data has not reached it yet.
|
|
1285
1653
|
*/
|
|
1286
1654
|
computeTimeReference() {
|
|
1287
1655
|
if (this.xAxisType !== "time") return void 0;
|
|
1656
|
+
const pinned = this.timeDisplay === "minutes" ? this.xAxis?.max : this.xAxis?.min;
|
|
1657
|
+
if (pinned !== void 0 && Number.isFinite(pinned)) return pinned;
|
|
1288
1658
|
const timestamps = [];
|
|
1289
1659
|
if (this.datasets?.length) {
|
|
1290
1660
|
this.datasets.forEach((ds) => {
|
|
@@ -1357,6 +1727,9 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1357
1727
|
// No extra margin
|
|
1358
1728
|
bounds: "data",
|
|
1359
1729
|
// Use data bounds for edge-to-edge rendering
|
|
1730
|
+
// A category axis has no numeric range to pin.
|
|
1731
|
+
min: this.isNumericXAxis ? this.xAxis?.min : void 0,
|
|
1732
|
+
max: this.isNumericXAxis ? this.xAxis?.max : void 0,
|
|
1360
1733
|
grid: {
|
|
1361
1734
|
display: this.showGrid && this.showGridX,
|
|
1362
1735
|
color: gridColor,
|
|
@@ -1457,10 +1830,14 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1457
1830
|
type: "line",
|
|
1458
1831
|
data: { labels, datasets },
|
|
1459
1832
|
options: this.getChartOptions(),
|
|
1460
|
-
plugins:
|
|
1833
|
+
plugins: [
|
|
1834
|
+
...this.borderRadiusPosition ? [this.createBorderPlugin()] : [],
|
|
1835
|
+
...this.rangeLabels !== "none" ? [this.createRangeLabelsPlugin()] : []
|
|
1836
|
+
]
|
|
1461
1837
|
});
|
|
1462
1838
|
requestAnimationFrame(() => this.updateLegend());
|
|
1463
1839
|
this.applyFillModes();
|
|
1840
|
+
this.syncSlottedScaleRanges();
|
|
1464
1841
|
}
|
|
1465
1842
|
updateChart() {
|
|
1466
1843
|
if (!this.chart || !this.canvasEl || !this.canvasEl.isConnected) return;
|
|
@@ -1470,6 +1847,7 @@ const _ObcChartLineBase = class _ObcChartLineBase extends LitElement {
|
|
|
1470
1847
|
Object.assign(this.chart.options, this.getChartOptions());
|
|
1471
1848
|
this.applyFillModes();
|
|
1472
1849
|
this.chart.update();
|
|
1850
|
+
this.syncSlottedScaleRanges();
|
|
1473
1851
|
requestAnimationFrame(() => this.updateLegend());
|
|
1474
1852
|
}
|
|
1475
1853
|
/**
|
|
@@ -1595,6 +1973,9 @@ __decorateClass([
|
|
|
1595
1973
|
__decorateClass([
|
|
1596
1974
|
property({ type: Array, attribute: false })
|
|
1597
1975
|
], ObcChartLineBase.prototype, "yAxes");
|
|
1976
|
+
__decorateClass([
|
|
1977
|
+
property({ type: Object, attribute: false })
|
|
1978
|
+
], ObcChartLineBase.prototype, "xAxis");
|
|
1598
1979
|
__decorateClass([
|
|
1599
1980
|
property({ type: Boolean })
|
|
1600
1981
|
], ObcChartLineBase.prototype, "showGrid");
|
|
@@ -1610,6 +1991,9 @@ __decorateClass([
|
|
|
1610
1991
|
__decorateClass([
|
|
1611
1992
|
property({ type: Boolean, attribute: false })
|
|
1612
1993
|
], ObcChartLineBase.prototype, "hasLabelPadding");
|
|
1994
|
+
__decorateClass([
|
|
1995
|
+
property({ type: String })
|
|
1996
|
+
], ObcChartLineBase.prototype, "rangeLabels");
|
|
1613
1997
|
__decorateClass([
|
|
1614
1998
|
property({ type: Boolean })
|
|
1615
1999
|
], ObcChartLineBase.prototype, "showPoints");
|
|
@@ -1676,6 +2060,7 @@ __decorateClass([
|
|
|
1676
2060
|
export {
|
|
1677
2061
|
LineMode,
|
|
1678
2062
|
ObcChartLineBase,
|
|
2063
|
+
RangeLabels,
|
|
1679
2064
|
TimeDisplay,
|
|
1680
2065
|
XAxisType,
|
|
1681
2066
|
YAxisPosition
|