@aceshooting/lyra-ui 1.0.0 → 1.1.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/README.md +8 -9
- package/custom-elements.json +10463 -4519
- package/dist/components/chart/chart.d.ts +46 -1
- package/dist/components/chart/chart.js +148 -10
- package/dist/components/chart/chart.stories.d.ts +32 -0
- package/dist/components/chart/chart.stories.js +141 -0
- package/dist/components/chart/chart.styles.js +16 -0
- package/dist/components/chart/histogram.d.ts +2 -0
- package/dist/components/chart/histogram.js +18 -2
- package/dist/components/chart/line-chart.stories.d.ts +17 -0
- package/dist/components/chart/line-chart.stories.js +75 -0
- package/dist/components/chart/lite-chart.d.ts +74 -0
- package/dist/components/chart/lite-chart.js +384 -0
- package/dist/components/chart/lite-chart.stories.d.ts +12 -0
- package/dist/components/chart/lite-chart.stories.js +98 -0
- package/dist/components/chart/lite-chart.styles.d.ts +1 -0
- package/dist/components/chart/lite-chart.styles.js +83 -0
- package/dist/components/combobox/combobox.d.ts +9 -0
- package/dist/components/combobox/combobox.js +75 -12
- package/dist/components/combobox/combobox.stories.d.ts +18 -0
- package/dist/components/combobox/combobox.stories.js +75 -0
- package/dist/components/date-picker/calendar-core.js +13 -2
- package/dist/components/date-picker/date-input.d.ts +9 -1
- package/dist/components/date-picker/date-input.js +55 -7
- package/dist/components/date-picker/date-input.stories.d.ts +5 -0
- package/dist/components/date-picker/date-input.stories.js +36 -0
- package/dist/components/date-picker/date-picker.d.ts +11 -3
- package/dist/components/date-picker/date-picker.js +87 -37
- package/dist/components/date-picker/date-picker.stories.d.ts +4 -0
- package/dist/components/date-picker/date-picker.stories.js +12 -0
- package/dist/components/date-picker/date-picker.styles.js +1 -1
- package/dist/components/empty/empty.d.ts +12 -1
- package/dist/components/empty/empty.js +20 -3
- package/dist/components/empty/empty.stories.d.ts +2 -0
- package/dist/components/empty/empty.stories.js +20 -0
- package/dist/components/empty/empty.styles.js +11 -0
- package/dist/components/export-button/csv.js +6 -2
- package/dist/components/export-button/export-button.d.ts +15 -1
- package/dist/components/export-button/export-button.js +89 -12
- package/dist/components/export-button/export-button.stories.d.ts +1 -0
- package/dist/components/export-button/export-button.stories.js +5 -0
- package/dist/components/export-button/export-button.styles.js +11 -3
- package/dist/components/file-input/accept.d.ts +8 -2
- package/dist/components/file-input/accept.js +9 -3
- package/dist/components/file-input/file-input.d.ts +5 -2
- package/dist/components/file-input/file-input.js +23 -8
- package/dist/components/file-input/file-input.stories.d.ts +2 -0
- package/dist/components/file-input/file-input.stories.js +9 -0
- package/dist/components/flag/flag.d.ts +48 -3
- package/dist/components/flag/flag.js +65 -29
- package/dist/components/flag/flag.styles.js +1 -1
- package/dist/components/flag/language-map.d.ts +11 -0
- package/dist/components/flag/language-map.js +12 -1
- package/dist/components/gauge/gauge.d.ts +2 -0
- package/dist/components/gauge/gauge.js +19 -5
- package/dist/components/gauge/gauge.stories.d.ts +2 -0
- package/dist/components/gauge/gauge.stories.js +6 -0
- package/dist/components/graph/graph-loader.d.ts +28 -0
- package/dist/components/graph/graph-loader.js +44 -0
- package/dist/components/graph/graph.d.ts +41 -15
- package/dist/components/graph/graph.js +146 -71
- package/dist/components/graph/graph.stories.d.ts +3 -0
- package/dist/components/graph/graph.stories.js +38 -0
- package/dist/components/graph/graph.styles.js +6 -1
- package/dist/components/heatmap/calendar-grid.d.ts +7 -0
- package/dist/components/heatmap/calendar-grid.js +27 -10
- package/dist/components/heatmap/heatmap.d.ts +137 -12
- package/dist/components/heatmap/heatmap.js +427 -27
- package/dist/components/heatmap/heatmap.stories.d.ts +17 -0
- package/dist/components/heatmap/heatmap.stories.js +130 -0
- package/dist/components/heatmap/heatmap.styles.js +60 -2
- package/dist/components/map/map.d.ts +17 -1
- package/dist/components/map/map.js +68 -21
- package/dist/components/map/map.stories.d.ts +10 -0
- package/dist/components/map/map.stories.js +107 -12
- package/dist/components/map/map.styles.js +2 -2
- package/dist/components/playback/playback.d.ts +6 -2
- package/dist/components/playback/playback.js +36 -12
- package/dist/components/playback/playback.stories.d.ts +2 -0
- package/dist/components/playback/playback.stories.js +6 -0
- package/dist/components/playback/playback.styles.js +10 -2
- package/dist/components/select/select.d.ts +122 -0
- package/dist/components/select/select.js +516 -0
- package/dist/components/select/select.stories.d.ts +19 -0
- package/dist/components/select/select.stories.js +75 -0
- package/dist/components/select/select.styles.d.ts +1 -0
- package/dist/components/select/select.styles.js +188 -0
- package/dist/components/skeleton/skeleton.js +4 -7
- package/dist/components/sparkline/sparkline.d.ts +1 -1
- package/dist/components/sparkline/sparkline.js +40 -13
- package/dist/components/sparkline/sparkline.stories.d.ts +4 -0
- package/dist/components/sparkline/sparkline.stories.js +22 -0
- package/dist/components/split/split.d.ts +24 -3
- package/dist/components/split/split.js +155 -31
- package/dist/components/split/split.stories.d.ts +2 -0
- package/dist/components/split/split.stories.js +20 -0
- package/dist/components/stat/stat.d.ts +27 -1
- package/dist/components/stat/stat.js +56 -3
- package/dist/components/stat/stat.stories.d.ts +3 -0
- package/dist/components/stat/stat.stories.js +49 -1
- package/dist/components/stat/stat.styles.js +44 -0
- package/dist/components/table/table.d.ts +64 -2
- package/dist/components/table/table.js +213 -17
- package/dist/components/table/table.stories.d.ts +6 -0
- package/dist/components/table/table.stories.js +45 -0
- package/dist/components/table/table.styles.js +51 -4
- package/dist/components/time-range/time-range.d.ts +34 -5
- package/dist/components/time-range/time-range.js +217 -36
- package/dist/components/time-range/time-range.stories.d.ts +2 -0
- package/dist/components/time-range/time-range.stories.js +13 -0
- package/dist/components/time-range/time-range.styles.js +44 -2
- package/dist/components/toast/toast-item.d.ts +14 -2
- package/dist/components/toast/toast-item.js +134 -17
- package/dist/components/toast/toast-item.styles.js +28 -2
- package/dist/components/toast/toast.stories.d.ts +3 -0
- package/dist/components/toast/toast.stories.js +41 -1
- package/dist/components/toast/toast.styles.js +11 -11
- package/dist/components/toast/toaster.d.ts +1 -1
- package/dist/components/toast/toaster.js +13 -6
- package/dist/components/tree/tree-node.d.ts +13 -7
- package/dist/components/tree/tree-node.js +24 -10
- package/dist/components/tree/tree.d.ts +64 -15
- package/dist/components/tree/tree.js +113 -22
- package/dist/components/tree/tree.stories.d.ts +4 -0
- package/dist/components/tree/tree.stories.js +31 -1
- package/dist/components/tree/update-cascade.d.ts +13 -0
- package/dist/components/tree/update-cascade.js +13 -0
- package/dist/components/widget/widget.d.ts +15 -1
- package/dist/components/widget/widget.js +140 -5
- package/dist/components/widget/widget.stories.d.ts +1 -0
- package/dist/components/widget/widget.stories.js +10 -0
- package/dist/components/widget/widget.styles.js +11 -1
- package/dist/components/word-cloud/word-cloud-layout.d.ts +63 -0
- package/dist/components/word-cloud/word-cloud-layout.js +145 -0
- package/dist/components/word-cloud/word-cloud.d.ts +65 -0
- package/dist/components/word-cloud/word-cloud.js +255 -0
- package/dist/components/word-cloud/word-cloud.stories.d.ts +10 -0
- package/dist/components/word-cloud/word-cloud.stories.js +55 -0
- package/dist/components/word-cloud/word-cloud.styles.d.ts +1 -0
- package/dist/components/word-cloud/word-cloud.styles.js +54 -0
- package/dist/internal/a11y.js +2 -1
- package/dist/internal/rtl.d.ts +7 -0
- package/dist/internal/rtl.js +9 -0
- package/dist/lyra.d.ts +9 -1
- package/dist/lyra.js +6 -0
- package/llms-full.txt +892 -276
- package/llms.txt +12 -9
- package/package.json +6 -4
|
@@ -42,6 +42,10 @@ export type LyraChartType = 'line' | 'bar' | 'scatter' | 'pie' | 'doughnut' | 'r
|
|
|
42
42
|
*
|
|
43
43
|
* @customElement lyra-chart
|
|
44
44
|
* @event lyra-zoom - `detail: { zoomed }`.
|
|
45
|
+
* @event lyra-point-click - Fired when a click lands on (or nearest,
|
|
46
|
+
* intersect-only — see `handlePointClick()`) a data point/segment.
|
|
47
|
+
* `detail: { datasetIndex: number, index: number, label: string |
|
|
48
|
+
* undefined, value: unknown }`.
|
|
45
49
|
* @csspart base, canvas, reset-zoom-button
|
|
46
50
|
*/
|
|
47
51
|
export declare class LyraChart extends LyraElement {
|
|
@@ -57,6 +61,10 @@ export declare class LyraChart extends LyraElement {
|
|
|
57
61
|
yLabel: string;
|
|
58
62
|
y2Label: string;
|
|
59
63
|
beginAtZero: boolean;
|
|
64
|
+
/** Sets `options.indexAxis = 'y'`, Chart.js's own mechanism for horizontal bars (also applies to line/area types). */
|
|
65
|
+
horizontal: boolean;
|
|
66
|
+
/** Stacks the `x`/`y`(/`y2`) scale entries `buildScales()` returns; only meaningful for `bar` and `line` types. */
|
|
67
|
+
stacked: boolean;
|
|
60
68
|
/**
|
|
61
69
|
* Raw Chart.js configuration passthrough — mirrors `wa-chart`'s `config`
|
|
62
70
|
* property. Recursively deep-merged over the `Series`-derived config in
|
|
@@ -73,21 +81,58 @@ export declare class LyraChart extends LyraElement {
|
|
|
73
81
|
private chart?;
|
|
74
82
|
private chartJsModule?;
|
|
75
83
|
private builtType?;
|
|
84
|
+
private suppressZoomComplete;
|
|
76
85
|
connectedCallback(): void;
|
|
77
86
|
disconnectedCallback(): void;
|
|
78
87
|
protected updated(changed: PropertyValues): void;
|
|
79
88
|
private seriesToDataset;
|
|
89
|
+
/**
|
|
90
|
+
* Resolves the `--lyra-chart-*` theme tokens (declared in
|
|
91
|
+
* `chart.styles.ts`, each layered over an existing semantic token) via
|
|
92
|
+
* `getComputedStyle`. Chart.js renders to canvas, not the DOM, so it can't
|
|
93
|
+
* consume CSS `var()` directly — same constraint documented on
|
|
94
|
+
* `heatmap.ts`'s `labelColor()`/`noDataFill()`/`scaleEndpoints()` — so this
|
|
95
|
+
* is called fresh from `buildConfig()` on every draw rather than cached.
|
|
96
|
+
*/
|
|
97
|
+
private themeColors;
|
|
80
98
|
/**
|
|
81
99
|
* Builds `options.scales` for the effective chart type: no scale at all for
|
|
82
100
|
* pie/doughnut (a proportional-area chart has no axis), the single radial
|
|
83
101
|
* `r` scale Chart.js v4 uses for radar/polarArea, and the cartesian
|
|
84
|
-
* `x`/`y`(/`y2`) block for every other (line/bar/scatter/bubble) type
|
|
102
|
+
* `x`/`y`(/`y2`) block for every other (line/bar/scatter/bubble) type — with
|
|
103
|
+
* `x` itself further split within that block: scatter and bubble datasets
|
|
104
|
+
* carry raw numeric `{x, y(, r)}` points (via `Series.points`) and need a
|
|
105
|
+
* linear `x` scale, while line/bar plot against `labels` and need the
|
|
106
|
+
* default categorical one. `theme` (from `themeColors()`) drives every
|
|
107
|
+
* scale's `ticks.color`/`grid.color`/axis `title.color` so grid lines and
|
|
108
|
+
* labels retheme instead of sitting at Chart.js's own hardcoded defaults.
|
|
85
109
|
*/
|
|
86
110
|
private buildScales;
|
|
111
|
+
/**
|
|
112
|
+
* `options.onClick` handler wired in `buildConfig()`. The chart-wide
|
|
113
|
+
* `interaction` mode above (`'index'`/`'nearest'`, `intersect: false`) is
|
|
114
|
+
* tuned for hover tooltips — resolving which single point/segment was
|
|
115
|
+
* actually clicked needs its own `getElementsAtEventForMode('nearest', {
|
|
116
|
+
* intersect: true }, true)` lookup instead, so a click landing off any
|
|
117
|
+
* point/segment reports nothing (`elements` empty) rather than firing for
|
|
118
|
+
* whatever's nearest. Covers the per-bar/per-segment click ask for any
|
|
119
|
+
* chart type (bar/line/pie/doughnut/etc.), not just bars.
|
|
120
|
+
*/
|
|
121
|
+
private handlePointClick;
|
|
87
122
|
private buildConfig;
|
|
88
123
|
private draw;
|
|
89
124
|
/** Reset any active zoom/pan back to the original view. */
|
|
90
125
|
resetZoom(): void;
|
|
126
|
+
/**
|
|
127
|
+
* Forces a redraw so `themeColors()` re-reads the `--lyra-chart-*` custom
|
|
128
|
+
* properties from the current computed style. No global theme-broadcast
|
|
129
|
+
* event exists anywhere in lyra-ui (nothing to subscribe to here) — this
|
|
130
|
+
* is the escape hatch for a consumer's own theme-toggle handler to call
|
|
131
|
+
* directly when it flips e.g. a `data-theme` attribute upstream that
|
|
132
|
+
* doesn't otherwise change any `lyra-chart` property, so Lit's reactive
|
|
133
|
+
* update loop has nothing of its own to trigger `draw()` on.
|
|
134
|
+
*/
|
|
135
|
+
refreshTheme(): void;
|
|
91
136
|
render(): TemplateResult;
|
|
92
137
|
}
|
|
93
138
|
declare global {
|
|
@@ -27,6 +27,18 @@ function isPlainObject(value) {
|
|
|
27
27
|
// response) reach up through the merge and mutate `Object.prototype` —
|
|
28
28
|
// skipped unconditionally regardless of `base`'s own shape.
|
|
29
29
|
const UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype']);
|
|
30
|
+
function prefersReducedMotion() {
|
|
31
|
+
return typeof window !== 'undefined' && !!window.matchMedia?.('(prefers-reduced-motion: reduce)').matches;
|
|
32
|
+
}
|
|
33
|
+
// Defensive JS-side fallbacks for themeColors() below, mirroring the
|
|
34
|
+
// light-mode default of each `--lyra-chart-*` token's own fallback chain
|
|
35
|
+
// (see chart.styles.ts) — only reached if getComputedStyle somehow can't
|
|
36
|
+
// resolve the custom property at all (e.g. host detached from the document).
|
|
37
|
+
const FALLBACK_GRID_COLOR = '#8a8a90';
|
|
38
|
+
const FALLBACK_TICK_COLOR = '#6b7280';
|
|
39
|
+
const FALLBACK_LEGEND_COLOR = '#1a1a1a';
|
|
40
|
+
const FALLBACK_TOOLTIP_BG = '#fff';
|
|
41
|
+
const FALLBACK_TOOLTIP_TEXT = '#1a1a1a';
|
|
30
42
|
function deepMerge(base, override) {
|
|
31
43
|
if (!isPlainObject(base) || !isPlainObject(override)) {
|
|
32
44
|
return (override === undefined ? base : override);
|
|
@@ -60,6 +72,10 @@ function deepMerge(base, override) {
|
|
|
60
72
|
*
|
|
61
73
|
* @customElement lyra-chart
|
|
62
74
|
* @event lyra-zoom - `detail: { zoomed }`.
|
|
75
|
+
* @event lyra-point-click - Fired when a click lands on (or nearest,
|
|
76
|
+
* intersect-only — see `handlePointClick()`) a data point/segment.
|
|
77
|
+
* `detail: { datasetIndex: number, index: number, label: string |
|
|
78
|
+
* undefined, value: unknown }`.
|
|
63
79
|
* @csspart base, canvas, reset-zoom-button
|
|
64
80
|
*/
|
|
65
81
|
export class LyraChart extends LyraElement {
|
|
@@ -76,9 +92,19 @@ export class LyraChart extends LyraElement {
|
|
|
76
92
|
this.yLabel = '';
|
|
77
93
|
this.y2Label = '';
|
|
78
94
|
this.beginAtZero = true;
|
|
95
|
+
/** Sets `options.indexAxis = 'y'`, Chart.js's own mechanism for horizontal bars (also applies to line/area types). */
|
|
96
|
+
this.horizontal = false;
|
|
97
|
+
/** Stacks the `x`/`y`(/`y2`) scale entries `buildScales()` returns; only meaningful for `bar` and `line` types. */
|
|
98
|
+
this.stacked = false;
|
|
79
99
|
/** True until the lazy-loaded `chart.js` peer dependency has settled (success or failure). */
|
|
80
100
|
this.loading = true;
|
|
81
101
|
this.zoomed = false;
|
|
102
|
+
// `chartjs-plugin-zoom`'s own `resetZoom()` synchronously re-invokes the
|
|
103
|
+
// `onZoomComplete` callback below as part of its reset, which would emit a
|
|
104
|
+
// stale `{zoomed: true}` right before `resetZoom()` emits the real
|
|
105
|
+
// `{zoomed: false}`. Set while this component's own `resetZoom()` is
|
|
106
|
+
// driving the plugin so that re-entrant callback is ignored.
|
|
107
|
+
this.suppressZoomComplete = false;
|
|
82
108
|
}
|
|
83
109
|
static { this.styles = [LyraElement.styles, styles]; }
|
|
84
110
|
connectedCallback() {
|
|
@@ -130,41 +156,111 @@ export class LyraChart extends LyraElement {
|
|
|
130
156
|
tooltip: s.noTooltip ? { enabled: false } : undefined,
|
|
131
157
|
};
|
|
132
158
|
}
|
|
159
|
+
/**
|
|
160
|
+
* Resolves the `--lyra-chart-*` theme tokens (declared in
|
|
161
|
+
* `chart.styles.ts`, each layered over an existing semantic token) via
|
|
162
|
+
* `getComputedStyle`. Chart.js renders to canvas, not the DOM, so it can't
|
|
163
|
+
* consume CSS `var()` directly — same constraint documented on
|
|
164
|
+
* `heatmap.ts`'s `labelColor()`/`noDataFill()`/`scaleEndpoints()` — so this
|
|
165
|
+
* is called fresh from `buildConfig()` on every draw rather than cached.
|
|
166
|
+
*/
|
|
167
|
+
themeColors() {
|
|
168
|
+
const cs = getComputedStyle(this);
|
|
169
|
+
return {
|
|
170
|
+
grid: cs.getPropertyValue('--lyra-chart-grid-color').trim() || FALLBACK_GRID_COLOR,
|
|
171
|
+
tick: cs.getPropertyValue('--lyra-chart-tick-color').trim() || FALLBACK_TICK_COLOR,
|
|
172
|
+
legend: cs.getPropertyValue('--lyra-chart-legend-color').trim() || FALLBACK_LEGEND_COLOR,
|
|
173
|
+
tooltipBg: cs.getPropertyValue('--lyra-chart-tooltip-bg').trim() || FALLBACK_TOOLTIP_BG,
|
|
174
|
+
tooltipText: cs.getPropertyValue('--lyra-chart-tooltip-text').trim() || FALLBACK_TOOLTIP_TEXT,
|
|
175
|
+
};
|
|
176
|
+
}
|
|
133
177
|
/**
|
|
134
178
|
* Builds `options.scales` for the effective chart type: no scale at all for
|
|
135
179
|
* pie/doughnut (a proportional-area chart has no axis), the single radial
|
|
136
180
|
* `r` scale Chart.js v4 uses for radar/polarArea, and the cartesian
|
|
137
|
-
* `x`/`y`(/`y2`) block for every other (line/bar/scatter/bubble) type
|
|
181
|
+
* `x`/`y`(/`y2`) block for every other (line/bar/scatter/bubble) type — with
|
|
182
|
+
* `x` itself further split within that block: scatter and bubble datasets
|
|
183
|
+
* carry raw numeric `{x, y(, r)}` points (via `Series.points`) and need a
|
|
184
|
+
* linear `x` scale, while line/bar plot against `labels` and need the
|
|
185
|
+
* default categorical one. `theme` (from `themeColors()`) drives every
|
|
186
|
+
* scale's `ticks.color`/`grid.color`/axis `title.color` so grid lines and
|
|
187
|
+
* labels retheme instead of sitting at Chart.js's own hardcoded defaults.
|
|
138
188
|
*/
|
|
139
|
-
buildScales() {
|
|
189
|
+
buildScales(theme) {
|
|
140
190
|
if (this.type === 'pie' || this.type === 'doughnut')
|
|
141
191
|
return {};
|
|
142
192
|
if (this.type === 'radar' || this.type === 'polarArea') {
|
|
143
193
|
return {
|
|
144
194
|
r: {
|
|
145
195
|
beginAtZero: this.beginAtZero,
|
|
196
|
+
ticks: { color: theme.tick },
|
|
197
|
+
grid: { color: theme.grid },
|
|
198
|
+
angleLines: { color: theme.grid },
|
|
199
|
+
pointLabels: { color: theme.tick },
|
|
146
200
|
},
|
|
147
201
|
};
|
|
148
202
|
}
|
|
149
203
|
const hasY2 = this.datasets.some((s) => s.axis === 'y2');
|
|
204
|
+
// `stacked` only applies to bar/line-family charts sharing a categorical
|
|
205
|
+
// axis, per the design spec — scatter/bubble's linear x scale and the
|
|
206
|
+
// radial r scale above are out of scope.
|
|
207
|
+
const stacked = this.stacked && (this.type === 'bar' || this.type === 'line');
|
|
150
208
|
return {
|
|
151
209
|
x: {
|
|
152
|
-
type: this.type === 'scatter' ? 'linear' : 'category',
|
|
153
|
-
title: { display: !!this.xLabel, text: this.xLabel },
|
|
210
|
+
type: this.type === 'scatter' || this.type === 'bubble' ? 'linear' : 'category',
|
|
211
|
+
title: { display: !!this.xLabel, text: this.xLabel, color: theme.tick },
|
|
212
|
+
ticks: { color: theme.tick },
|
|
213
|
+
grid: { color: theme.grid },
|
|
214
|
+
stacked,
|
|
215
|
+
},
|
|
216
|
+
y: {
|
|
217
|
+
beginAtZero: this.beginAtZero,
|
|
218
|
+
title: { display: !!this.yLabel, text: this.yLabel, color: theme.tick },
|
|
219
|
+
ticks: { color: theme.tick },
|
|
220
|
+
grid: { color: theme.grid },
|
|
221
|
+
stacked,
|
|
154
222
|
},
|
|
155
|
-
y: { beginAtZero: this.beginAtZero, title: { display: !!this.yLabel, text: this.yLabel } },
|
|
156
223
|
...(hasY2
|
|
157
224
|
? {
|
|
158
225
|
y2: {
|
|
159
226
|
position: 'right',
|
|
160
|
-
grid: { drawOnChartArea: false },
|
|
161
|
-
title: { display: !!this.y2Label, text: this.y2Label },
|
|
227
|
+
grid: { drawOnChartArea: false, color: theme.grid },
|
|
228
|
+
title: { display: !!this.y2Label, text: this.y2Label, color: theme.tick },
|
|
229
|
+
ticks: { color: theme.tick },
|
|
230
|
+
stacked,
|
|
162
231
|
},
|
|
163
232
|
}
|
|
164
233
|
: {}),
|
|
165
234
|
};
|
|
166
235
|
}
|
|
236
|
+
/**
|
|
237
|
+
* `options.onClick` handler wired in `buildConfig()`. The chart-wide
|
|
238
|
+
* `interaction` mode above (`'index'`/`'nearest'`, `intersect: false`) is
|
|
239
|
+
* tuned for hover tooltips — resolving which single point/segment was
|
|
240
|
+
* actually clicked needs its own `getElementsAtEventForMode('nearest', {
|
|
241
|
+
* intersect: true }, true)` lookup instead, so a click landing off any
|
|
242
|
+
* point/segment reports nothing (`elements` empty) rather than firing for
|
|
243
|
+
* whatever's nearest. Covers the per-bar/per-segment click ask for any
|
|
244
|
+
* chart type (bar/line/pie/doughnut/etc.), not just bars.
|
|
245
|
+
*/
|
|
246
|
+
handlePointClick(event, chart) {
|
|
247
|
+
// Chart.js's own `onClick` handler hands us its `ChartEvent` wrapper, but
|
|
248
|
+
// `getElementsAtEventForMode()`'s .d.ts (inaccurately) types its first
|
|
249
|
+
// param as a DOM `Event` — at runtime Chart.js only reads `.x`/`.y` off
|
|
250
|
+
// whatever is passed (see chart.js/src/helpers/helpers.dom.ts
|
|
251
|
+
// `getRelativePosition()`), which `ChartEvent` already has, so the cast
|
|
252
|
+
// here is a type-only correction, not a behavior change.
|
|
253
|
+
const elements = chart.getElementsAtEventForMode(event, 'nearest', { intersect: true }, true);
|
|
254
|
+
const hit = elements[0];
|
|
255
|
+
if (!hit)
|
|
256
|
+
return;
|
|
257
|
+
const { datasetIndex, index } = hit;
|
|
258
|
+
const label = chart.data.labels?.[index];
|
|
259
|
+
const value = chart.data.datasets[datasetIndex]?.data?.[index] ?? null;
|
|
260
|
+
this.emit('lyra-point-click', { datasetIndex, index, label, value });
|
|
261
|
+
}
|
|
167
262
|
buildConfig() {
|
|
263
|
+
const theme = this.themeColors();
|
|
168
264
|
const generated = {
|
|
169
265
|
type: this.type,
|
|
170
266
|
data: {
|
|
@@ -174,9 +270,25 @@ export class LyraChart extends LyraElement {
|
|
|
174
270
|
options: {
|
|
175
271
|
responsive: true,
|
|
176
272
|
maintainAspectRatio: false,
|
|
273
|
+
// Chart.js's own mechanism for horizontal bars (also flips line/area
|
|
274
|
+
// types onto a horizontal category axis).
|
|
275
|
+
indexAxis: this.horizontal ? 'y' : undefined,
|
|
276
|
+
// Chart.js's own default ~1000ms draw-in animation only ever fires
|
|
277
|
+
// from `new Chart()` (chart/type-change construction) — every
|
|
278
|
+
// in-place update in `draw()` already passes `'none'` to
|
|
279
|
+
// `Chart#update()`. A CSS media query can't reach that
|
|
280
|
+
// canvas-internal animation loop, so `prefersReducedMotion()` is
|
|
281
|
+
// checked here instead and fed into `options.animation`.
|
|
282
|
+
animation: prefersReducedMotion() ? false : undefined,
|
|
177
283
|
interaction: { intersect: false, mode: this.type === 'scatter' ? 'nearest' : 'index' },
|
|
284
|
+
onClick: (event, _elements, chart) => this.handlePointClick(event, chart),
|
|
178
285
|
plugins: {
|
|
179
|
-
legend: { display: this.legend },
|
|
286
|
+
legend: { display: this.legend, labels: { color: theme.legend } },
|
|
287
|
+
tooltip: {
|
|
288
|
+
backgroundColor: theme.tooltipBg,
|
|
289
|
+
titleColor: theme.tooltipText,
|
|
290
|
+
bodyColor: theme.tooltipText,
|
|
291
|
+
},
|
|
180
292
|
zoom: this.zoom
|
|
181
293
|
? {
|
|
182
294
|
pan: { enabled: false },
|
|
@@ -186,6 +298,8 @@ export class LyraChart extends LyraElement {
|
|
|
186
298
|
pinch: { enabled: true },
|
|
187
299
|
mode: 'x',
|
|
188
300
|
onZoomComplete: () => {
|
|
301
|
+
if (this.suppressZoomComplete)
|
|
302
|
+
return;
|
|
189
303
|
this.zoomed = true;
|
|
190
304
|
this.emit('lyra-zoom', { zoomed: true });
|
|
191
305
|
},
|
|
@@ -194,7 +308,7 @@ export class LyraChart extends LyraElement {
|
|
|
194
308
|
}
|
|
195
309
|
: undefined,
|
|
196
310
|
},
|
|
197
|
-
scales: this.buildScales(),
|
|
311
|
+
scales: this.buildScales(theme),
|
|
198
312
|
},
|
|
199
313
|
};
|
|
200
314
|
if (!this.config)
|
|
@@ -223,10 +337,28 @@ export class LyraChart extends LyraElement {
|
|
|
223
337
|
}
|
|
224
338
|
/** Reset any active zoom/pan back to the original view. */
|
|
225
339
|
resetZoom() {
|
|
226
|
-
this.
|
|
340
|
+
this.suppressZoomComplete = true;
|
|
341
|
+
try {
|
|
342
|
+
this.chart?.resetZoom?.();
|
|
343
|
+
}
|
|
344
|
+
finally {
|
|
345
|
+
this.suppressZoomComplete = false;
|
|
346
|
+
}
|
|
227
347
|
this.zoomed = false;
|
|
228
348
|
this.emit('lyra-zoom', { zoomed: false });
|
|
229
349
|
}
|
|
350
|
+
/**
|
|
351
|
+
* Forces a redraw so `themeColors()` re-reads the `--lyra-chart-*` custom
|
|
352
|
+
* properties from the current computed style. No global theme-broadcast
|
|
353
|
+
* event exists anywhere in lyra-ui (nothing to subscribe to here) — this
|
|
354
|
+
* is the escape hatch for a consumer's own theme-toggle handler to call
|
|
355
|
+
* directly when it flips e.g. a `data-theme` attribute upstream that
|
|
356
|
+
* doesn't otherwise change any `lyra-chart` property, so Lit's reactive
|
|
357
|
+
* update loop has nothing of its own to trigger `draw()` on.
|
|
358
|
+
*/
|
|
359
|
+
refreshTheme() {
|
|
360
|
+
this.draw();
|
|
361
|
+
}
|
|
230
362
|
render() {
|
|
231
363
|
if (this.loading) {
|
|
232
364
|
return html `
|
|
@@ -281,6 +413,12 @@ __decorate([
|
|
|
281
413
|
__decorate([
|
|
282
414
|
property({ type: Boolean, attribute: 'begin-at-zero' })
|
|
283
415
|
], LyraChart.prototype, "beginAtZero", void 0);
|
|
416
|
+
__decorate([
|
|
417
|
+
property({ type: Boolean })
|
|
418
|
+
], LyraChart.prototype, "horizontal", void 0);
|
|
419
|
+
__decorate([
|
|
420
|
+
property({ type: Boolean })
|
|
421
|
+
], LyraChart.prototype, "stacked", void 0);
|
|
284
422
|
__decorate([
|
|
285
423
|
property({ attribute: false })
|
|
286
424
|
], LyraChart.prototype, "config", void 0);
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/web-components-vite';
|
|
2
|
+
declare const meta: Meta;
|
|
3
|
+
export default meta;
|
|
4
|
+
type Story = StoryObj;
|
|
5
|
+
export declare const Default: Story;
|
|
6
|
+
/**
|
|
7
|
+
* The `config` property is the raw Chart.js passthrough escape hatch — it is
|
|
8
|
+
* deep-merged over the `Series`-generated config, so a nested key like
|
|
9
|
+
* `options.plugins.title` can be set without discarding the rest of the
|
|
10
|
+
* generated config the `type`/`labels`/`datasets` attributes produce.
|
|
11
|
+
*/
|
|
12
|
+
export declare const ConfigPassthrough: Story;
|
|
13
|
+
/** `horizontal` sets Chart.js's `indexAxis: 'y'`, flipping bars onto a horizontal axis. */
|
|
14
|
+
export declare const Horizontal: Story;
|
|
15
|
+
/** `stacked` stacks every dataset sharing an axis on top of each other instead of side by side. */
|
|
16
|
+
export declare const Stacked: Story;
|
|
17
|
+
/**
|
|
18
|
+
* The `--lyra-chart-grid-color`/`-tick-color`/`-legend-color`/`-tooltip-bg`/
|
|
19
|
+
* `-tooltip-text` custom properties retheme Chart.js's canvas-drawn chrome
|
|
20
|
+
* (grid lines, axis ticks/titles, legend labels, tooltip). Chart.js can't
|
|
21
|
+
* consume `var()` directly, so these are resolved via `getComputedStyle`
|
|
22
|
+
* once per draw — see `themeColors()` in `chart.ts`.
|
|
23
|
+
*/
|
|
24
|
+
export declare const ThemedTokens: Story;
|
|
25
|
+
/**
|
|
26
|
+
* `lyra-point-click` fires whenever a click lands on (or nearest,
|
|
27
|
+
* intersect-only) a data point/segment — covers any `type`, not just bars.
|
|
28
|
+
* `refreshTheme()` forces a redraw so an out-of-band theme change (e.g. a
|
|
29
|
+
* host-level dark-mode toggle that doesn't touch any `lyra-chart` property)
|
|
30
|
+
* is picked up immediately, rather than waiting for the next reactive update.
|
|
31
|
+
*/
|
|
32
|
+
export declare const PointClickAndRefreshTheme: Story;
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { html } from 'lit';
|
|
2
|
+
const meta = {
|
|
3
|
+
title: 'Charts/Chart',
|
|
4
|
+
component: 'lyra-chart',
|
|
5
|
+
tags: ['autodocs'],
|
|
6
|
+
};
|
|
7
|
+
export default meta;
|
|
8
|
+
export const Default = {
|
|
9
|
+
render: () => {
|
|
10
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
11
|
+
return html `
|
|
12
|
+
<lyra-chart
|
|
13
|
+
type="bar"
|
|
14
|
+
height="16rem"
|
|
15
|
+
style="width: 22rem"
|
|
16
|
+
legend
|
|
17
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
18
|
+
.datasets=${series}
|
|
19
|
+
></lyra-chart>
|
|
20
|
+
`;
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* The `config` property is the raw Chart.js passthrough escape hatch — it is
|
|
25
|
+
* deep-merged over the `Series`-generated config, so a nested key like
|
|
26
|
+
* `options.plugins.title` can be set without discarding the rest of the
|
|
27
|
+
* generated config the `type`/`labels`/`datasets` attributes produce.
|
|
28
|
+
*/
|
|
29
|
+
export const ConfigPassthrough = {
|
|
30
|
+
render: () => {
|
|
31
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
32
|
+
return html `
|
|
33
|
+
<lyra-chart
|
|
34
|
+
type="bar"
|
|
35
|
+
height="16rem"
|
|
36
|
+
style="width: 22rem"
|
|
37
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
38
|
+
.datasets=${series}
|
|
39
|
+
.config=${{
|
|
40
|
+
options: { plugins: { title: { display: true, text: 'Quarterly revenue' } } },
|
|
41
|
+
}}
|
|
42
|
+
></lyra-chart>
|
|
43
|
+
`;
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
/** `horizontal` sets Chart.js's `indexAxis: 'y'`, flipping bars onto a horizontal axis. */
|
|
47
|
+
export const Horizontal = {
|
|
48
|
+
render: () => {
|
|
49
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
50
|
+
return html `
|
|
51
|
+
<lyra-chart
|
|
52
|
+
type="bar"
|
|
53
|
+
horizontal
|
|
54
|
+
height="16rem"
|
|
55
|
+
style="width: 22rem"
|
|
56
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
57
|
+
.datasets=${series}
|
|
58
|
+
></lyra-chart>
|
|
59
|
+
`;
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
/** `stacked` stacks every dataset sharing an axis on top of each other instead of side by side. */
|
|
63
|
+
export const Stacked = {
|
|
64
|
+
render: () => {
|
|
65
|
+
const series = [
|
|
66
|
+
{ label: 'Product A', data: [12, 19, 14, 22] },
|
|
67
|
+
{ label: 'Product B', data: [8, 11, 9, 15] },
|
|
68
|
+
];
|
|
69
|
+
return html `
|
|
70
|
+
<lyra-chart
|
|
71
|
+
type="bar"
|
|
72
|
+
stacked
|
|
73
|
+
legend
|
|
74
|
+
height="16rem"
|
|
75
|
+
style="width: 22rem"
|
|
76
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
77
|
+
.datasets=${series}
|
|
78
|
+
></lyra-chart>
|
|
79
|
+
`;
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
/**
|
|
83
|
+
* The `--lyra-chart-grid-color`/`-tick-color`/`-legend-color`/`-tooltip-bg`/
|
|
84
|
+
* `-tooltip-text` custom properties retheme Chart.js's canvas-drawn chrome
|
|
85
|
+
* (grid lines, axis ticks/titles, legend labels, tooltip). Chart.js can't
|
|
86
|
+
* consume `var()` directly, so these are resolved via `getComputedStyle`
|
|
87
|
+
* once per draw — see `themeColors()` in `chart.ts`.
|
|
88
|
+
*/
|
|
89
|
+
export const ThemedTokens = {
|
|
90
|
+
render: () => {
|
|
91
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
92
|
+
return html `
|
|
93
|
+
<lyra-chart
|
|
94
|
+
type="bar"
|
|
95
|
+
legend
|
|
96
|
+
height="16rem"
|
|
97
|
+
style="
|
|
98
|
+
width: 22rem;
|
|
99
|
+
--lyra-chart-grid-color: #e5484d;
|
|
100
|
+
--lyra-chart-tick-color: #e5484d;
|
|
101
|
+
--lyra-chart-legend-color: #e5484d;
|
|
102
|
+
--lyra-chart-tooltip-bg: #1a1a1a;
|
|
103
|
+
--lyra-chart-tooltip-text: #fff;
|
|
104
|
+
"
|
|
105
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
106
|
+
.datasets=${series}
|
|
107
|
+
></lyra-chart>
|
|
108
|
+
`;
|
|
109
|
+
},
|
|
110
|
+
};
|
|
111
|
+
/**
|
|
112
|
+
* `lyra-point-click` fires whenever a click lands on (or nearest,
|
|
113
|
+
* intersect-only) a data point/segment — covers any `type`, not just bars.
|
|
114
|
+
* `refreshTheme()` forces a redraw so an out-of-band theme change (e.g. a
|
|
115
|
+
* host-level dark-mode toggle that doesn't touch any `lyra-chart` property)
|
|
116
|
+
* is picked up immediately, rather than waiting for the next reactive update.
|
|
117
|
+
*/
|
|
118
|
+
export const PointClickAndRefreshTheme = {
|
|
119
|
+
render: () => {
|
|
120
|
+
const series = [{ label: 'Revenue', data: [12, 19, 14, 22] }];
|
|
121
|
+
return html `
|
|
122
|
+
<lyra-chart
|
|
123
|
+
type="bar"
|
|
124
|
+
height="16rem"
|
|
125
|
+
style="width: 22rem"
|
|
126
|
+
.labels=${['Q1', 'Q2', 'Q3', 'Q4']}
|
|
127
|
+
.datasets=${series}
|
|
128
|
+
@lyra-point-click=${(e) => console.log('lyra-point-click', e.detail)}
|
|
129
|
+
></lyra-chart>
|
|
130
|
+
<button
|
|
131
|
+
type="button"
|
|
132
|
+
@click=${(e) => {
|
|
133
|
+
const chart = e.target.previousElementSibling;
|
|
134
|
+
chart.refreshTheme();
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
refreshTheme()
|
|
138
|
+
</button>
|
|
139
|
+
`;
|
|
140
|
+
},
|
|
141
|
+
};
|
|
@@ -4,6 +4,18 @@ export const styles = css `
|
|
|
4
4
|
display: block;
|
|
5
5
|
position: relative;
|
|
6
6
|
block-size: var(--lyra-chart-height, 280px);
|
|
7
|
+
/* Chart.js renders to canvas, not the DOM, so it can't consume CSS
|
|
8
|
+
var() directly — chart.ts's themeColors() resolves these once per
|
|
9
|
+
draw() via getComputedStyle, same pattern as heatmap.ts's scale-lo/-hi.
|
|
10
|
+
Each is its own token (rather than reusing the semantic ones directly)
|
|
11
|
+
so a host can retheme just the chart's grid/ticks/legend/tooltip
|
|
12
|
+
without affecting unrelated text/border/surface colors elsewhere in
|
|
13
|
+
the component, while still defaulting to those semantic tokens. */
|
|
14
|
+
--lyra-chart-grid-color: var(--lyra-color-border);
|
|
15
|
+
--lyra-chart-tick-color: var(--lyra-color-text-quiet);
|
|
16
|
+
--lyra-chart-legend-color: var(--lyra-color-text);
|
|
17
|
+
--lyra-chart-tooltip-bg: var(--lyra-color-surface);
|
|
18
|
+
--lyra-chart-tooltip-text: var(--lyra-color-text);
|
|
7
19
|
}
|
|
8
20
|
[part='base'] {
|
|
9
21
|
position: relative;
|
|
@@ -30,4 +42,8 @@ export const styles = css `
|
|
|
30
42
|
color: var(--lyra-color-text);
|
|
31
43
|
cursor: pointer;
|
|
32
44
|
}
|
|
45
|
+
[part='reset-zoom-button']:focus-visible {
|
|
46
|
+
outline: var(--lyra-focus-ring-width) solid var(--lyra-focus-ring-color);
|
|
47
|
+
outline-offset: var(--lyra-focus-ring-offset);
|
|
48
|
+
}
|
|
33
49
|
`;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LyraChart } from './chart.js';
|
|
2
|
+
import { type HistogramBucket } from './histogram-bin.js';
|
|
2
3
|
/**
|
|
3
4
|
* `<lyra-histogram>` — bins `values` into `bins` equal-width buckets and
|
|
4
5
|
* renders them as a bar chart. Chart.js has no built-in histogram
|
|
@@ -13,6 +14,7 @@ export declare class LyraHistogram extends LyraChart {
|
|
|
13
14
|
values: number[];
|
|
14
15
|
label: string;
|
|
15
16
|
}
|
|
17
|
+
export declare function binnedBuckets(el: LyraHistogram): HistogramBucket[];
|
|
16
18
|
declare global {
|
|
17
19
|
interface HTMLElementTagNameMap {
|
|
18
20
|
'lyra-histogram': LyraHistogram;
|
|
@@ -40,6 +40,22 @@ __decorate([
|
|
|
40
40
|
__decorate([
|
|
41
41
|
property()
|
|
42
42
|
], LyraHistogram.prototype, "label", void 0);
|
|
43
|
+
// Both the `labels` and `datasets` accessors below derive from the same
|
|
44
|
+
// `binValues(values, bins)` pass, and `LyraChart` reads `datasets` more than
|
|
45
|
+
// once per `draw()`/`render()` — memoize the last bucketing result per
|
|
46
|
+
// instance (keyed by reference equality on `values`/`bins`, matching Lit's
|
|
47
|
+
// own change detection for the `values` array) so an unrelated property
|
|
48
|
+
// change doesn't re-run the O(n) bucketing loop from scratch on every access.
|
|
49
|
+
const bucketCache = new WeakMap();
|
|
50
|
+
export function binnedBuckets(el) {
|
|
51
|
+
const cached = bucketCache.get(el);
|
|
52
|
+
if (cached && cached.values === el.values && cached.bins === el.bins) {
|
|
53
|
+
return cached.buckets;
|
|
54
|
+
}
|
|
55
|
+
const buckets = binValues(el.values, el.bins);
|
|
56
|
+
bucketCache.set(el, { values: el.values, bins: el.bins, buckets });
|
|
57
|
+
return buckets;
|
|
58
|
+
}
|
|
43
59
|
// `labels`/`datasets` are computed from `values`/`bins` rather than settable
|
|
44
60
|
// props. `LyraChart` declares both as plain (decorator-managed) class
|
|
45
61
|
// fields, and TypeScript forbids a subclass from re-declaring a base field
|
|
@@ -51,7 +67,7 @@ Object.defineProperty(LyraHistogram.prototype, 'labels', {
|
|
|
51
67
|
configurable: true,
|
|
52
68
|
enumerable: true,
|
|
53
69
|
get() {
|
|
54
|
-
return
|
|
70
|
+
return binnedBuckets(this).map((b) => b.label);
|
|
55
71
|
},
|
|
56
72
|
set(_v) {
|
|
57
73
|
/* derived from `values`/`bins`; direct writes are ignored */
|
|
@@ -61,7 +77,7 @@ Object.defineProperty(LyraHistogram.prototype, 'datasets', {
|
|
|
61
77
|
configurable: true,
|
|
62
78
|
enumerable: true,
|
|
63
79
|
get() {
|
|
64
|
-
return [{ label: this.label, data:
|
|
80
|
+
return [{ label: this.label, data: binnedBuckets(this).map((b) => b.count) }];
|
|
65
81
|
},
|
|
66
82
|
set(_v) {
|
|
67
83
|
/* derived from `values`/`bins`; direct writes are ignored */
|
|
@@ -3,3 +3,20 @@ declare const meta: Meta;
|
|
|
3
3
|
export default meta;
|
|
4
4
|
type Story = StoryObj;
|
|
5
5
|
export declare const Default: Story;
|
|
6
|
+
/**
|
|
7
|
+
* `area` fills every series lacking its own per-series `fill` (unlike
|
|
8
|
+
* `Default`'s "Sessions" series, which opts into a fill via its own
|
|
9
|
+
* `fill: true` instead of the chart-wide default).
|
|
10
|
+
*/
|
|
11
|
+
export declare const AreaFill: Story;
|
|
12
|
+
/** `zoom` enables wheel/drag/pinch zoom; a reset-zoom button appears once zoomed. */
|
|
13
|
+
export declare const WithZoom: Story;
|
|
14
|
+
/** A second series on `axis: "y2"` plots against its own right-side scale, labelled by `y2-label`. */
|
|
15
|
+
export declare const DualAxis: Story;
|
|
16
|
+
/**
|
|
17
|
+
* `beginAtZero` lets the y axis start near the data's own minimum instead of
|
|
18
|
+
* 0 — set via the `.beginAtZero` property, since the `begin-at-zero`
|
|
19
|
+
* *attribute*'s presence (not its string value) is what Lit's boolean
|
|
20
|
+
* converter reads, so `begin-at-zero="false"` would not turn it off.
|
|
21
|
+
*/
|
|
22
|
+
export declare const WithoutBeginAtZero: Story;
|