@c2n/chart 0.0.7 → 0.0.8

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.
Files changed (49) hide show
  1. package/README.md +44 -14
  2. package/dist/area-chart.js +2 -2
  3. package/dist/bar-chart.js +4 -3
  4. package/dist/candlestick-chart.js +139 -0
  5. package/dist/{chart-base-C4-1TyKq.js → chart-base-BwPwRgHt.js} +89 -10
  6. package/dist/chart-base.js +1 -1
  7. package/dist/chart-legend-DJE3HO7C.js +49 -0
  8. package/dist/chart-legend.js +2 -0
  9. package/dist/chart-link-C9vN9Y6c.js +75 -0
  10. package/dist/chart-series.js +75 -1
  11. package/dist/chart-tooltip-J-oC2DKZ.js +130 -0
  12. package/dist/chart-tooltip.js +2 -0
  13. package/dist/chart.js +10 -5
  14. package/dist/decorate-BkO8ArQg.js +9 -0
  15. package/dist/{echarts-chart-base-LogCQN10.js → echarts-chart-base-6JsZbB05.js} +2 -4
  16. package/dist/echarts-chart-base.js +1 -1
  17. package/dist/gauge-chart.js +103 -0
  18. package/dist/line-chart.js +4 -3
  19. package/dist/pie-chart.js +11 -2
  20. package/dist/scatter-chart.js +40 -0
  21. package/dist/sparkline.js +4 -4
  22. package/dist/{uplot-chart-base-BWr0zJE2.js → uplot-chart-base-CDHzOLwa.js} +21 -5
  23. package/dist/uplot-chart-base.js +1 -1
  24. package/dist/{uplot-paths-ChKohvIX.js → uplot-paths-BRtF33Wh.js} +1 -1
  25. package/package.json +26 -3
  26. package/react.d.ts +10 -0
  27. package/types/src/candlestick-chart.d.ts +52 -0
  28. package/types/src/candlestick-chart.d.ts.map +1 -0
  29. package/types/src/chart-base.d.ts +31 -1
  30. package/types/src/chart-base.d.ts.map +1 -1
  31. package/types/src/chart-legend.d.ts +37 -0
  32. package/types/src/chart-legend.d.ts.map +1 -0
  33. package/types/src/chart-link.d.ts +26 -0
  34. package/types/src/chart-link.d.ts.map +1 -0
  35. package/types/src/chart-tooltip.d.ts +42 -0
  36. package/types/src/chart-tooltip.d.ts.map +1 -0
  37. package/types/src/chart.d.ts +11 -1
  38. package/types/src/chart.d.ts.map +1 -1
  39. package/types/src/engines/echarts-loader.d.ts +1 -1
  40. package/types/src/engines/echarts-loader.d.ts.map +1 -1
  41. package/types/src/engines/uplot-adapter.d.ts.map +1 -1
  42. package/types/src/gauge-chart.d.ts +52 -0
  43. package/types/src/gauge-chart.d.ts.map +1 -0
  44. package/types/src/pie-chart.d.ts +3 -0
  45. package/types/src/pie-chart.d.ts.map +1 -1
  46. package/types/src/scatter-chart.d.ts +41 -0
  47. package/types/src/scatter-chart.d.ts.map +1 -0
  48. package/vue.d.ts +81 -0
  49. package/dist/chart-series-BnpHO3lm.js +0 -83
package/README.md CHANGED
@@ -1,16 +1,20 @@
1
1
  # @c2n/chart
2
2
 
3
- Line, area, bar, sparkline and pie charts as custom elements, from one package.
3
+ Line, area, bar, sparkline, pie, gauge, scatter and candlestick charts as custom elements, from one package.
4
4
 
5
5
  ```bash
6
6
  npm install @c2n/chart uplot # line, area, bar, sparkline
7
- npm install @c2n/chart echarts # pie
7
+ npm install @c2n/chart echarts # pie, gauge, scatter, candlestick
8
8
  ```
9
9
 
10
10
  ```html
11
- <c2-line-chart x-field="month" legend="bottom" data='[{ "month": 1, "revenue": 128, "cost": 74 }]'>
12
- <c2-chart-series field="revenue" label="Revenue"></c2-chart-series>
13
- <c2-chart-series field="cost" label="Cost"></c2-chart-series>
11
+ <c2-line-chart
12
+ x-field="year"
13
+ data='[{"year":2022,"nuclear":294.73,"wind":38.2,"solar":19.34},{"year":2023,"nuclear":338.2,"wind":50.48,"solar":22.59},{"year":2024,"nuclear":380.45,"wind":45.43,"solar":24.87}]'
14
+ >
15
+ <c2-chart-series field="nuclear" label="Nuclear"></c2-chart-series>
16
+ <c2-chart-series field="wind" label="Wind"></c2-chart-series>
17
+ <c2-chart-series field="solar" label="Solar"></c2-chart-series>
14
18
  </c2-line-chart>
15
19
  ```
16
20
 
@@ -21,14 +25,34 @@ import '@c2n/chart' // all of them
21
25
 
22
26
  ## Elements
23
27
 
24
- | Tag | Engine | For |
25
- | ----------------- | ------- | ----------------------------------------------- |
26
- | `c2-line-chart` | uPlot | Time or numeric trends |
27
- | `c2-area-chart` | uPlot | The same, with the region under the line filled |
28
- | `c2-bar-chart` | uPlot | Categorical or time-bucketed values |
29
- | `c2-sparkline` | uPlot | A chromeless trend for a table cell or KPI row |
30
- | `c2-pie-chart` | ECharts | Parts of a whole, with a donut mode |
31
- | `c2-chart-series` | | A series definition; renders nothing |
28
+ | Tag | Engine | For |
29
+ | ---------------------- | ------- | ----------------------------------------------- |
30
+ | `c2-line-chart` | uPlot | Time or numeric trends |
31
+ | `c2-area-chart` | uPlot | The same, with the region under the line filled |
32
+ | `c2-bar-chart` | uPlot | Categorical or time-bucketed values |
33
+ | `c2-sparkline` | uPlot | A chromeless trend for a table cell or KPI row |
34
+ | `c2-pie-chart` | ECharts | Parts of a whole, with a donut mode |
35
+ | `c2-gauge-chart` | ECharts | A bounded current value or target |
36
+ | `c2-scatter-chart` | ECharts | Relationships, clusters and outliers |
37
+ | `c2-candlestick-chart` | ECharts | Open-high-low-close financial sessions |
38
+ | `c2-chart-series` | — | A series definition; renders nothing |
39
+ | `c2-chart-legend` | — | A linked legend placed anywhere in the layout |
40
+ | `c2-chart-tooltip` | — | A linked floating or inline tooltip |
41
+
42
+ ## Independent legend and tooltip
43
+
44
+ The chart renders built-in chrome by default. To own its layout, link companion elements to the chart by id;
45
+ each companion automatically replaces its corresponding built-in display:
46
+
47
+ ```html
48
+ <c2-line-chart id="sales" data="..."></c2-line-chart>
49
+ <c2-chart-legend for="sales"></c2-chart-legend>
50
+ <c2-chart-tooltip for="sales"></c2-chart-tooltip>
51
+ ```
52
+
53
+ The legend occupies its normal HTML position. The tooltip floats beside the hovered point by default and
54
+ flips at viewport edges; `position="inline"` makes it occupy its authored position. Assign `renderLegend`,
55
+ `renderLegendItem` or `renderTooltip` for data-driven custom Lit templates.
32
56
 
33
57
  ## Engines
34
58
 
@@ -42,6 +66,11 @@ whichever engines your charts need.
42
66
  Internally everything becomes one columnar frame of `Float64Array`s, which is the shape uPlot draws
43
67
  natively and what makes appending O(1).
44
68
 
69
+ The documentation examples share a France 2015–2024 extract from the public
70
+ [Our World in Data Energy dataset](https://github.com/owid/energy-data), measured in terawatt-hours. Line,
71
+ area, bar, scatter and sparkline charts read the annual rows directly; radial charts derive a focused
72
+ metric or one slice per energy source from a selected year. Candlesticks use public OHLC observations.
73
+
45
74
  ```js
46
75
  chart.data = rows // full replace
47
76
  chart.revision++ // after mutating the array in place
@@ -75,7 +104,8 @@ messages) is ordinary DOM and does expose parts.
75
104
 
76
105
  ## Events
77
106
 
78
- `chart-ready`, `chart-error`, `point-click`, `point-hover`, `range-change` and `series-toggle`. The
107
+ `chart-ready`, `chart-error`, `point-click`, `point-hover`, `range-change`, `series-toggle`, `legend-change`
108
+ and `tooltip-change`. The
79
109
  semantic ones do not bubble: several components fire similarly named events, so a listener belongs on the
80
110
  element itself. The host also carries `data-chart-ready` once the first frame is drawn, which is the
81
111
  signal to wait on in a test.
@@ -1,5 +1,5 @@
1
- import { i as __decorate } from "./chart-series-BnpHO3lm.js";
2
- import { r as withAlpha } from "./uplot-paths-ChKohvIX.js";
1
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
2
+ import { r as withAlpha } from "./uplot-paths-BRtF33Wh.js";
3
3
  import { LineChart } from "./line-chart.js";
4
4
  import { property } from "lit/decorators.js";
5
5
  import { customElement } from "@c2n/core/element-helper.js";
package/dist/bar-chart.js CHANGED
@@ -1,6 +1,7 @@
1
- import { i as __decorate } from "./chart-series-BnpHO3lm.js";
2
- import { t as UplotChartBase } from "./uplot-chart-base-BWr0zJE2.js";
3
- import { t as barPaths } from "./uplot-paths-ChKohvIX.js";
1
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
2
+ import "./chart-series.js";
3
+ import { t as UplotChartBase } from "./uplot-chart-base-CDHzOLwa.js";
4
+ import { t as barPaths } from "./uplot-paths-BRtF33Wh.js";
4
5
  import { property } from "lit/decorators.js";
5
6
  import { customElement } from "@c2n/core/element-helper.js";
6
7
  //#region src/bar-chart.ts
@@ -0,0 +1,139 @@
1
+ import { columnValue } from "./chart-data.js";
2
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
3
+ import "./chart-series.js";
4
+ import { t as EchartsChartBase } from "./echarts-chart-base-6JsZbB05.js";
5
+ import { property } from "lit/decorators.js";
6
+ import { customElement } from "@c2n/core/element-helper.js";
7
+ //#region src/candlestick-chart.ts
8
+ var CandlestickChart = class CandlestickChart extends EchartsChartBase {
9
+ constructor() {
10
+ super();
11
+ this.features = ["candlestick", "grid"];
12
+ this.openField = "open";
13
+ this.closeField = "close";
14
+ this.lowField = "low";
15
+ this.highField = "high";
16
+ this.name = "Price";
17
+ this.xType = "category";
18
+ this.tooltip = "axis";
19
+ this.legend = "none";
20
+ }
21
+ inferredSeries() {
22
+ return [
23
+ {
24
+ field: this.openField,
25
+ label: "Open"
26
+ },
27
+ {
28
+ field: this.closeField,
29
+ label: "Close"
30
+ },
31
+ {
32
+ field: this.lowField,
33
+ label: "Low"
34
+ },
35
+ {
36
+ field: this.highField,
37
+ label: "High"
38
+ }
39
+ ];
40
+ }
41
+ coordinateSystem(context) {
42
+ const axisLine = { lineStyle: { color: context.theme.gridColor } };
43
+ const axisLabel = {
44
+ color: context.theme.axisColor,
45
+ fontSize: context.theme.fontSize
46
+ };
47
+ return {
48
+ grid: {
49
+ left: 48,
50
+ right: 16,
51
+ top: 16,
52
+ bottom: 32,
53
+ containLabel: false
54
+ },
55
+ xAxis: {
56
+ type: "category",
57
+ data: this.xLabels(),
58
+ boundaryGap: true,
59
+ axisLine,
60
+ axisLabel
61
+ },
62
+ yAxis: {
63
+ type: "value",
64
+ scale: true,
65
+ axisLine,
66
+ axisLabel,
67
+ splitLine: { lineStyle: { color: context.theme.gridColor } }
68
+ }
69
+ };
70
+ }
71
+ projectData(frame) {
72
+ const [open, close, low, high] = frame.columns;
73
+ return [Array.from({ length: frame.length }, (_item, index) => [
74
+ open ? columnValue(open, index) : null,
75
+ close ? columnValue(close, index) : null,
76
+ low ? columnValue(low, index) : null,
77
+ high ? columnValue(high, index) : null
78
+ ])];
79
+ }
80
+ buildOptions(context) {
81
+ return {
82
+ ...super.buildOptions(context),
83
+ series: [{
84
+ name: this.name,
85
+ ...this.seriesOption(0, context)
86
+ }]
87
+ };
88
+ }
89
+ seriesOption(_index, context) {
90
+ return {
91
+ type: "candlestick",
92
+ itemStyle: {
93
+ color: context.theme.positive,
94
+ color0: context.theme.negative,
95
+ borderColor: context.theme.positive,
96
+ borderColor0: context.theme.negative
97
+ }
98
+ };
99
+ }
100
+ legendItems() {
101
+ const visible = !this.hiddenSeries.has(0);
102
+ return [{
103
+ label: this.name,
104
+ color: this.themeController.theme.positive,
105
+ visible,
106
+ toggle: () => this.setSeriesVisible(0, !visible),
107
+ series: {
108
+ field: this.openField,
109
+ label: this.name
110
+ },
111
+ index: 0
112
+ }];
113
+ }
114
+ xLabels() {
115
+ const frame = this.frame;
116
+ if (!frame) return [];
117
+ return Array.from({ length: frame.length }, (_item, index) => frame.labels?.[index] ?? this.formatX(columnValue(frame.x, index) ?? index));
118
+ }
119
+ };
120
+ __decorate([property({
121
+ type: String,
122
+ attribute: "open-field"
123
+ })], CandlestickChart.prototype, "openField", void 0);
124
+ __decorate([property({
125
+ type: String,
126
+ attribute: "close-field"
127
+ })], CandlestickChart.prototype, "closeField", void 0);
128
+ __decorate([property({
129
+ type: String,
130
+ attribute: "low-field"
131
+ })], CandlestickChart.prototype, "lowField", void 0);
132
+ __decorate([property({
133
+ type: String,
134
+ attribute: "high-field"
135
+ })], CandlestickChart.prototype, "highField", void 0);
136
+ __decorate([property({ type: String })], CandlestickChart.prototype, "name", void 0);
137
+ CandlestickChart = __decorate([customElement("c2-candlestick-chart")], CandlestickChart);
138
+ //#endregion
139
+ export { CandlestickChart };
@@ -1,6 +1,7 @@
1
1
  import { ChartFrameBuilder, columnValue } from "./chart-data.js";
2
2
  import { ChartThemeController, PROBE_COLORS } from "./chart-theme.js";
3
- import { i as __decorate, n as SERIES_CHANGE_EVENT, r as SERIES_TAG } from "./chart-series-BnpHO3lm.js";
3
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
4
+ import { SERIES_CHANGE_EVENT, SERIES_TAG } from "./chart-series.js";
4
5
  import { property, query, state } from "lit/decorators.js";
5
6
  import { LitElement, html, isServer, nothing, unsafeCSS } from "lit";
6
7
  import { jsonPropertyConverter } from "@c2n/core/lit-helper.js";
@@ -31,6 +32,8 @@ var VIEW_ONLY_KEYS = /* @__PURE__ */ new Set([
31
32
  "lazyRender",
32
33
  "renderTooltip",
33
34
  "renderLegendItem",
35
+ "hasLinkedLegend",
36
+ "hasLinkedTooltip",
34
37
  "dataSource"
35
38
  ]);
36
39
  /** `stats.cpu` -> `Cpu`, `unit_price` -> `Unit price`: a readable label for an inferred field. */
@@ -68,7 +71,9 @@ var RESIZE_EPSILON = 1;
68
71
  * @event {CustomEvent<{ engine: string }>} chart-ready - Fired after the engine has loaded and drawn for the first time. The host also gains `data-chart-ready`, which is what a test should wait on.
69
72
  * @event {CustomEvent<{ error: unknown }>} chart-error - Fired when the engine fails to load or to draw. `detail.error` is the underlying failure.
70
73
  * @event {CustomEvent<ChartPointEventDetail>} point-click - Fired when a datum is clicked. Does not bubble: several components fire point events, so a listener belongs on the element itself.
71
- * @event {CustomEvent<ChartPointEventDetail>} point-hover - Fired as the pointer moves between data points, and with a `null` detail when it leaves the plot. Does not bubble.
74
+ * @event {CustomEvent<ChartPointEventDetail | null>} point-hover - Fired as the pointer moves between data points, and with a `null` detail when it leaves the plot. Does not bubble.
75
+ * @event {CustomEvent<ChartTooltipContext | null>} tooltip-change - Fired with the complete tooltip model as the pointer moves, and with `null` when it leaves. Used by `c2-chart-tooltip`.
76
+ * @event {CustomEvent<ChartLegendChangeEventDetail>} legend-change - Fired when legend entries or visibility change. Used by `c2-chart-legend`.
72
77
  * @event {CustomEvent<ChartRangeEventDetail>} range-change - Fired after the user zooms or brushes. `detail.min` and `detail.max` are the new x bounds. Does not bubble.
73
78
  * @event {CustomEvent<ChartSeriesToggleEventDetail>} series-toggle - Fired when a legend entry is toggled. Does not bubble.
74
79
  *
@@ -163,6 +168,8 @@ var ChartBase = class extends LitElement {
163
168
  this.hiddenSeries = /* @__PURE__ */ new Set();
164
169
  this.hoverContext = null;
165
170
  this.engineFailed = false;
171
+ this.hasLinkedLegend = false;
172
+ this.hasLinkedTooltip = false;
166
173
  this.themeController = new ChartThemeController(this, () => this.handleThemeChange());
167
174
  this.frameBuilder = new ChartFrameBuilder();
168
175
  this.#creating = false;
@@ -175,6 +182,9 @@ var ChartBase = class extends LitElement {
175
182
  height: 0
176
183
  };
177
184
  this.#visible = false;
185
+ this.#tooltipContext = null;
186
+ this.#legendPresenters = /* @__PURE__ */ new Set();
187
+ this.#tooltipPresenters = /* @__PURE__ */ new Set();
178
188
  this.#handleSeriesChange = (event) => {
179
189
  event.stopPropagation();
180
190
  this.#collectSeries();
@@ -194,6 +204,17 @@ var ChartBase = class extends LitElement {
194
204
  #intersectionObserver;
195
205
  #visible;
196
206
  #unsubscribe;
207
+ #tooltipContext;
208
+ #legendPresenters;
209
+ #tooltipPresenters;
210
+ /** Restores interaction state after an engine instance is created or replaced. */
211
+ restoreVisibility(adapter) {
212
+ for (const index of this.hiddenSeries) adapter.setSeriesVisibility(index, false);
213
+ }
214
+ /** Whether changing chart data can change the legend model. Pie charts use row labels as entries. */
215
+ get legendDependsOnData() {
216
+ return false;
217
+ }
197
218
  connectedCallback() {
198
219
  super.connectedCallback();
199
220
  this.addEventListener(SERIES_CHANGE_EVENT, this.#handleSeriesChange);
@@ -251,6 +272,12 @@ var ChartBase = class extends LitElement {
251
272
  super.updated(changed);
252
273
  if (isServer) return;
253
274
  this.#sync();
275
+ if (changed.has("tooltip") && this.tooltip === "none") {
276
+ this.#tooltipContext = null;
277
+ this.#setHover(null);
278
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: null }));
279
+ }
280
+ if ([...changed.keys()].some((key) => key !== "hoverContext")) this.notifyLegendChange();
254
281
  }
255
282
  /**
256
283
  * Replaces the data without going through the update queue. Equivalent to assigning `data`, but skips
@@ -281,6 +308,7 @@ var ChartBase = class extends LitElement {
281
308
  else next.add(index);
282
309
  this.hiddenSeries = next;
283
310
  this.adapter?.setSeriesVisibility(index, visible);
311
+ this.notifyLegendChange();
284
312
  const series = this.resolvedSeries[index];
285
313
  if (!series) return;
286
314
  this.dispatchEvent(new CustomEvent("series-toggle", { detail: {
@@ -290,6 +318,42 @@ var ChartBase = class extends LitElement {
290
318
  hidden: [...next]
291
319
  } }));
292
320
  }
321
+ /** Current entries for a linked `c2-chart-legend`. */
322
+ getLegendItems() {
323
+ return this.legendItems();
324
+ }
325
+ /** Current hover model for a linked `c2-chart-tooltip`. */
326
+ getTooltipContext() {
327
+ return this.#tooltipContext;
328
+ }
329
+ /** Plot bounds used to place a linked floating tooltip in viewport coordinates. */
330
+ getPlotBounds() {
331
+ return (this.plotElement ?? this).getBoundingClientRect();
332
+ }
333
+ /** Registers an external legend and suppresses the built-in legend until it disconnects. */
334
+ registerLegendPresenter(presenter) {
335
+ this.#legendPresenters.add(presenter);
336
+ this.hasLinkedLegend = this.#legendPresenters.size > 0;
337
+ }
338
+ /** Removes an external legend registration. */
339
+ unregisterLegendPresenter(presenter) {
340
+ this.#legendPresenters.delete(presenter);
341
+ this.hasLinkedLegend = this.#legendPresenters.size > 0;
342
+ }
343
+ /** Registers an external tooltip and suppresses the built-in tooltip until it disconnects. */
344
+ registerTooltipPresenter(presenter) {
345
+ this.#tooltipPresenters.add(presenter);
346
+ this.hasLinkedTooltip = this.#tooltipPresenters.size > 0;
347
+ }
348
+ /** Removes an external tooltip registration. */
349
+ unregisterTooltipPresenter(presenter) {
350
+ this.#tooltipPresenters.delete(presenter);
351
+ this.hasLinkedTooltip = this.#tooltipPresenters.size > 0;
352
+ }
353
+ /** Notifies linked legends after a subclass changes a data-shaped legend such as pie slices. */
354
+ notifyLegendChange() {
355
+ this.dispatchEvent(new CustomEvent("legend-change", { detail: { items: this.getLegendItems() } }));
356
+ }
293
357
  /** The series in draw order: `c2-chart-series` children when present, otherwise the `series` property. */
294
358
  get resolvedSeries() {
295
359
  if (this.seriesElements.length > 0) return this.seriesElements.filter((element) => !element.hidden).map((element) => element.toConfig());
@@ -359,13 +423,17 @@ var ChartBase = class extends LitElement {
359
423
  await this.#createChart();
360
424
  return;
361
425
  }
426
+ let restoreAfterData = false;
362
427
  if (this.#optionsDirty) {
363
428
  this.#optionsDirty = false;
364
- this.adapter.setOptions(this.buildOptions(this.buildContext()), this.#optionsMode);
429
+ const mode = this.#optionsMode;
430
+ this.adapter.setOptions(this.buildOptions(this.buildContext()), mode);
365
431
  this.#optionsMode = "merge";
432
+ restoreAfterData = mode === "replace";
366
433
  this.#dataDirty = true;
367
434
  }
368
435
  if (this.#dataDirty) this.#flushData();
436
+ if (restoreAfterData) this.restoreVisibility(this.adapter);
369
437
  }
370
438
  async #createChart() {
371
439
  if (this.#creating || !this.plotElement || !this.frame) return;
@@ -385,7 +453,7 @@ var ChartBase = class extends LitElement {
385
453
  this.adapter = adapter;
386
454
  this.#optionsDirty = false;
387
455
  this.#dataDirty = false;
388
- for (const index of this.hiddenSeries) adapter.setSeriesVisibility(index, false);
456
+ this.restoreVisibility(adapter);
389
457
  this.setAttribute("data-chart-ready", "true");
390
458
  this.dispatchEvent(new CustomEvent("chart-ready", { detail: { engine: this.engineName } }));
391
459
  } catch (error) {
@@ -402,6 +470,7 @@ var ChartBase = class extends LitElement {
402
470
  if (this.#appended > 0 && this.adapter.appendData) this.adapter.appendData(projected, this.#appended);
403
471
  else this.adapter.setData(projected);
404
472
  this.#appended = 0;
473
+ if (this.legendDependsOnData) this.notifyLegendChange();
405
474
  }
406
475
  #handleResize(entries) {
407
476
  const rect = entries[entries.length - 1]?.contentRect;
@@ -451,12 +520,18 @@ var ChartBase = class extends LitElement {
451
520
  } else this.#unsubscribe = () => controller.abort();
452
521
  }
453
522
  #handleHover(detail) {
454
- if (!detail || !this.frame || this.tooltip === "none") {
523
+ if (!detail || !this.frame) {
524
+ this.#tooltipContext = null;
455
525
  this.#setHover(null);
526
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: null }));
527
+ this.dispatchEvent(new CustomEvent("point-hover", { detail: null }));
456
528
  return;
457
529
  }
458
530
  const context = this.#tooltipContextAt(detail);
459
- this.#setHover(context);
531
+ const tooltipContext = this.tooltip === "none" ? null : context;
532
+ this.#tooltipContext = tooltipContext;
533
+ this.#setHover(this.hasLinkedTooltip ? null : tooltipContext);
534
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: tooltipContext }));
460
535
  const point = this.#pointAt(detail.index, detail.seriesIndex);
461
536
  if (point) this.dispatchEvent(new CustomEvent("point-hover", { detail: point }));
462
537
  }
@@ -468,7 +543,7 @@ var ChartBase = class extends LitElement {
468
543
  #setHover(context) {
469
544
  const previous = this.hoverContext;
470
545
  if (context && this.tooltipElement) this.tooltipElement.style.transform = `translate(${context.px}px, ${context.py}px)`;
471
- if (previous && context && previous.index === context.index) return;
546
+ if (previous && context && previous.index === context.index && (this.tooltip !== "item" || previous.entries[0]?.seriesIndex === context.entries[0]?.seriesIndex)) return;
472
547
  this.hoverContext = context;
473
548
  }
474
549
  #tooltipContextAt(detail) {
@@ -541,8 +616,8 @@ var ChartBase = class extends LitElement {
541
616
  return new Intl.NumberFormat(this.locale, { maximumFractionDigits: 2 }).format(value);
542
617
  }
543
618
  render() {
544
- const legendBefore = this.legend === "top" || this.legend === "start";
545
- const legendAfter = this.legend === "bottom" || this.legend === "end";
619
+ const legendBefore = !this.hasLinkedLegend && (this.legend === "top" || this.legend === "start");
620
+ const legendAfter = !this.hasLinkedLegend && (this.legend === "bottom" || this.legend === "end");
546
621
  const side = this.legend === "start" || this.legend === "end";
547
622
  return html`
548
623
  <div class="frame ${side ? "frame--side" : ""}" part="frame">
@@ -551,7 +626,9 @@ var ChartBase = class extends LitElement {
551
626
  <!-- No bindings inside: Lit never patches this node, so the engine's canvas survives updates. -->
552
627
  <div class="plot" part="plot"></div>
553
628
  <div class="overlay" part="overlay">
554
- <div class="tooltip" part="tooltip" role="tooltip" ?hidden=${!this.hoverContext || this.tooltip === "none"}>${this.renderTooltipBody()}</div>
629
+ <div class="tooltip" part="tooltip" role="tooltip" ?hidden=${!this.hoverContext || this.tooltip === "none" || this.hasLinkedTooltip}>
630
+ ${this.renderTooltipBody()}
631
+ </div>
555
632
  <div class="actions" part="actions"><slot name="actions"></slot></div>
556
633
  </div>
557
634
  ${this.renderState()}
@@ -662,5 +739,7 @@ __decorate([state()], ChartBase.prototype, "seriesElements", void 0);
662
739
  __decorate([state()], ChartBase.prototype, "hiddenSeries", void 0);
663
740
  __decorate([state()], ChartBase.prototype, "hoverContext", void 0);
664
741
  __decorate([state()], ChartBase.prototype, "engineFailed", void 0);
742
+ __decorate([state()], ChartBase.prototype, "hasLinkedLegend", void 0);
743
+ __decorate([state()], ChartBase.prototype, "hasLinkedTooltip", void 0);
665
744
  //#endregion
666
745
  export { chart_default as n, ChartBase as t };
@@ -1,2 +1,2 @@
1
- import { t as ChartBase } from "./chart-base-C4-1TyKq.js";
1
+ import { t as ChartBase } from "./chart-base-BwPwRgHt.js";
2
2
  export { ChartBase };
@@ -0,0 +1,49 @@
1
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
2
+ import { t as ChartLinkedElement } from "./chart-link-C9vN9Y6c.js";
3
+ import { property, state } from "lit/decorators.js";
4
+ import { customElement } from "@c2n/core/element-helper.js";
5
+ import { html, unsafeCSS } from "lit";
6
+ //#region src/chart-legend.scss?inline
7
+ var chart_legend_default = "/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--c2-chart__legend--gap,12px);\n padding: var(--c2-chart__legend--padding,0);\n color: var(--c2-chart__legend--color,#71717a);\n font-size: var(--c2-chart__legend--font-size,12px);\n}\n\n.item {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 0;\n border: 0;\n background: none;\n color: inherit;\n font: inherit;\n cursor: pointer;\n}\n\n.item[aria-pressed=false] {\n opacity: var(--c2-chart__legend__disabled--opacity,0.38);\n}\n\n.marker {\n width: var(--c2-chart__legend-marker--size,10px);\n height: var(--c2-chart__legend-marker--size,10px);\n border-radius: var(--c2-chart__legend-marker--border-radius,999px);\n flex: none;\n}";
8
+ //#endregion
9
+ //#region src/chart-legend.ts
10
+ var ChartLegend = class ChartLegend extends ChartLinkedElement {
11
+ constructor(..._args) {
12
+ super(..._args);
13
+ this.items = [];
14
+ this.#handleLegendChange = (event) => {
15
+ this.items = event.detail.items;
16
+ };
17
+ }
18
+ static {
19
+ this.styles = unsafeCSS(chart_legend_default);
20
+ }
21
+ #handleLegendChange;
22
+ chartConnected(chart) {
23
+ chart.registerLegendPresenter(this);
24
+ chart.addEventListener("legend-change", this.#handleLegendChange);
25
+ this.items = chart.getLegendItems();
26
+ }
27
+ chartDisconnected(chart) {
28
+ chart.removeEventListener("legend-change", this.#handleLegendChange);
29
+ chart.unregisterLegendPresenter(this);
30
+ this.items = [];
31
+ }
32
+ render() {
33
+ const content = this.renderLegend?.(this.items) ?? this.items.map((item) => html`
34
+ <button class="item" part="item" type="button" aria-pressed=${item.visible ? "true" : "false"} @click=${() => item.toggle()}>
35
+ ${this.renderLegendItem?.(item) ?? html`
36
+ <span class="marker" part="marker" style="background:${item.color}"></span>
37
+ <span>${item.label}</span>
38
+ `}
39
+ </button>
40
+ `);
41
+ return html`<slot>${content}</slot>`;
42
+ }
43
+ };
44
+ __decorate([property({ attribute: false })], ChartLegend.prototype, "renderLegend", void 0);
45
+ __decorate([property({ attribute: false })], ChartLegend.prototype, "renderLegendItem", void 0);
46
+ __decorate([state()], ChartLegend.prototype, "items", void 0);
47
+ ChartLegend = __decorate([customElement("c2-chart-legend")], ChartLegend);
48
+ //#endregion
49
+ export { ChartLegend as t };
@@ -0,0 +1,2 @@
1
+ import { t as ChartLegend } from "./chart-legend-DJE3HO7C.js";
2
+ export { ChartLegend };
@@ -0,0 +1,75 @@
1
+ import { t as __decorate } from "./decorate-BkO8ArQg.js";
2
+ import { property } from "lit/decorators.js";
3
+ import { LitElement, isServer } from "lit";
4
+ //#region src/chart-link.ts
5
+ /** Resolves and follows a chart by id within the companion's document or shadow root. */
6
+ var ChartLinkedElement = class ChartLinkedElement extends LitElement {
7
+ constructor(..._args) {
8
+ super(..._args);
9
+ this.for = "";
10
+ }
11
+ static #watchers = /* @__PURE__ */ new WeakMap();
12
+ #watchedRoot;
13
+ connectedCallback() {
14
+ super.connectedCallback();
15
+ if (isServer) return;
16
+ queueMicrotask(() => this.#resolveChart());
17
+ const root = this.getRootNode();
18
+ const observed = root instanceof Document ? root.documentElement : root;
19
+ let watcher = ChartLinkedElement.#watchers.get(root);
20
+ if (!watcher) {
21
+ const elements = /* @__PURE__ */ new Set();
22
+ const observer = new MutationObserver(() => elements.forEach((element) => element.#resolveChart()));
23
+ observer.observe(observed, {
24
+ childList: true,
25
+ subtree: true,
26
+ attributes: true,
27
+ attributeFilter: ["id"]
28
+ });
29
+ watcher = {
30
+ elements,
31
+ observer
32
+ };
33
+ ChartLinkedElement.#watchers.set(root, watcher);
34
+ }
35
+ watcher.elements.add(this);
36
+ this.#watchedRoot = root;
37
+ }
38
+ disconnectedCallback() {
39
+ super.disconnectedCallback();
40
+ if (this.#watchedRoot) {
41
+ const watcher = ChartLinkedElement.#watchers.get(this.#watchedRoot);
42
+ watcher?.elements.delete(this);
43
+ if (watcher?.elements.size === 0) {
44
+ watcher.observer.disconnect();
45
+ ChartLinkedElement.#watchers.delete(this.#watchedRoot);
46
+ }
47
+ }
48
+ this.#watchedRoot = void 0;
49
+ this.#setChart(void 0);
50
+ }
51
+ willUpdate(changed) {
52
+ if (changed.has("for") && !isServer) queueMicrotask(() => this.#resolveChart());
53
+ }
54
+ chartConnected(_chart) {}
55
+ chartDisconnected(_chart) {}
56
+ #resolveChart() {
57
+ const root = this.getRootNode();
58
+ const candidate = this.for && (root instanceof Document || root instanceof ShadowRoot) ? root.getElementById(this.for) : null;
59
+ const chart = candidate && "getLegendItems" in candidate && "getTooltipContext" in candidate && "getPlotBounds" in candidate && "registerLegendPresenter" in candidate && "unregisterLegendPresenter" in candidate && "registerTooltipPresenter" in candidate && "unregisterTooltipPresenter" in candidate ? candidate : void 0;
60
+ this.#setChart(chart);
61
+ }
62
+ #setChart(chart) {
63
+ if (chart === this.chart) return;
64
+ if (this.chart) this.chartDisconnected(this.chart);
65
+ this.chart = chart;
66
+ if (chart) this.chartConnected(chart);
67
+ this.requestUpdate();
68
+ }
69
+ };
70
+ __decorate([property({
71
+ type: String,
72
+ reflect: true
73
+ })], ChartLinkedElement.prototype, "for", void 0);
74
+ //#endregion
75
+ export { ChartLinkedElement as t };