@c2n/chart 0.0.7 → 0.0.9

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 (58) hide show
  1. package/README.md +44 -14
  2. package/dist/area-chart.js +2 -2
  3. package/dist/bar-chart.js +5 -4
  4. package/dist/candlestick-chart.js +139 -0
  5. package/dist/{chart-base-C4-1TyKq.js → chart-base-CEZvMKwW.js} +106 -13
  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-theme.js +4 -2
  12. package/dist/chart-tooltip-J-oC2DKZ.js +130 -0
  13. package/dist/chart-tooltip.js +2 -0
  14. package/dist/chart.js +11 -5
  15. package/dist/decorate-BkO8ArQg.js +9 -0
  16. package/dist/{echarts-chart-base-LogCQN10.js → echarts-chart-base-CImXMzVk.js} +8 -5
  17. package/dist/echarts-chart-base.js +1 -1
  18. package/dist/gauge-chart.js +111 -0
  19. package/dist/line-chart.js +4 -3
  20. package/dist/pie-chart.js +26 -3
  21. package/dist/radar-chart.js +109 -0
  22. package/dist/scatter-chart.js +40 -0
  23. package/dist/sparkline.js +4 -4
  24. package/dist/{uplot-chart-base-BWr0zJE2.js → uplot-chart-base-CLhgOeM_.js} +21 -5
  25. package/dist/uplot-chart-base.js +1 -1
  26. package/dist/{uplot-paths-ChKohvIX.js → uplot-paths-D5HCEKRV.js} +9 -4
  27. package/package.json +31 -3
  28. package/react.d.ts +12 -0
  29. package/types/src/candlestick-chart.d.ts +52 -0
  30. package/types/src/candlestick-chart.d.ts.map +1 -0
  31. package/types/src/chart-base.d.ts +41 -4
  32. package/types/src/chart-base.d.ts.map +1 -1
  33. package/types/src/chart-legend.d.ts +37 -0
  34. package/types/src/chart-legend.d.ts.map +1 -0
  35. package/types/src/chart-link.d.ts +26 -0
  36. package/types/src/chart-link.d.ts.map +1 -0
  37. package/types/src/chart-theme.d.ts +2 -0
  38. package/types/src/chart-theme.d.ts.map +1 -1
  39. package/types/src/chart-tooltip.d.ts +42 -0
  40. package/types/src/chart-tooltip.d.ts.map +1 -0
  41. package/types/src/chart.d.ts +13 -1
  42. package/types/src/chart.d.ts.map +1 -1
  43. package/types/src/echarts-chart-base.d.ts.map +1 -1
  44. package/types/src/engines/echarts-loader.d.ts +1 -1
  45. package/types/src/engines/echarts-loader.d.ts.map +1 -1
  46. package/types/src/engines/uplot-adapter.d.ts.map +1 -1
  47. package/types/src/engines/uplot-paths.d.ts +1 -1
  48. package/types/src/engines/uplot-paths.d.ts.map +1 -1
  49. package/types/src/gauge-chart.d.ts +54 -0
  50. package/types/src/gauge-chart.d.ts.map +1 -0
  51. package/types/src/pie-chart.d.ts +6 -0
  52. package/types/src/pie-chart.d.ts.map +1 -1
  53. package/types/src/radar-chart.d.ts +55 -0
  54. package/types/src/radar-chart.d.ts.map +1 -0
  55. package/types/src/scatter-chart.d.ts +41 -0
  56. package/types/src/scatter-chart.d.ts.map +1 -0
  57. package/vue.d.ts +104 -0
  58. 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-D5HCEKRV.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-CLhgOeM_.js";
4
+ import { t as barPaths } from "./uplot-paths-D5HCEKRV.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
@@ -16,7 +17,7 @@ var BarChart = class BarChart extends UplotChartBase {
16
17
  stroke: color,
17
18
  fill: color,
18
19
  width: 0,
19
- paths: barPaths(this.barWidth, this.barGap, index, context.series.length),
20
+ paths: barPaths(this.barWidth, this.barGap, index, context.series.length, context.theme.barRadius),
20
21
  points: { show: false }
21
22
  };
22
23
  }
@@ -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-CImXMzVk.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,11 +1,12 @@
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";
7
8
  //#region src/chart.scss?inline
8
- var chart_default = "@charset \"UTF-8\";\n/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--c2-chart--width,100%);\n height: var(--c2-chart--height,320px);\n /*\n * The plot is absolutely positioned, so nothing inside the host contributes intrinsic width. As a flex\n * item — which is what every example frame in the docs site makes it — the host would otherwise shrink\n * to its legend, and to nothing at all when there is no legend. `min-width: 0` keeps it able to shrink\n * below its content when a layout genuinely needs that.\n */\n min-width: 0;\n padding: var(--c2-chart--padding,12px);\n background: var(--c2-chart--background,#ffffff);\n color: var(--c2-chart--color,#18181b);\n font-family: var(--c2-chart--font-family,inherit);\n font-size: var(--c2-chart--font-size,12px);\n border-top: var(--c2-chart--border-top,1px solid #e4e4e7);\n border-right: var(--c2-chart--border-right,1px solid #e4e4e7);\n border-bottom: var(--c2-chart--border-bottom,1px solid #e4e4e7);\n border-left: var(--c2-chart--border-left,1px solid #e4e4e7);\n border-top-left-radius: var(--c2-chart--border-top-left-radius,8px);\n border-top-right-radius: var(--c2-chart--border-top-right-radius,8px);\n border-bottom-left-radius: var(--c2-chart--border-bottom-left-radius,8px);\n border-bottom-right-radius: var(--c2-chart--border-bottom-right-radius,8px);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.frame {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/*\n * The plot area is the engine's territory. `contain: strict` plus absolute positioning means the canvas\n * the engine sizes inside it can never feed its own size back into the ResizeObserver watching this box,\n * which is what makes the resize loop structurally impossible rather than merely unlikely.\n */\n.plot-area {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n\n.plot {\n position: absolute;\n inset: 0;\n contain: strict;\n}\n\n.plot canvas {\n display: block;\n position: absolute;\n inset: 0;\n}\n\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.tooltip {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n pointer-events: none;\n will-change: transform;\n display: flex;\n flex-direction: column;\n gap: var(--c2-chart__tooltip--gap,6px);\n padding: var(--c2-chart__tooltip--padding,8px 10px);\n border-radius: var(--c2-chart__tooltip--border-radius,6px);\n background: var(--c2-chart__tooltip--background-color,#18181b);\n color: var(--c2-chart__tooltip--color,#fafafa);\n font-size: var(--c2-chart__tooltip--font-size,12px);\n box-shadow: var(--c2-chart__tooltip--box-shadow,0 8px 24px rgba(24, 24, 27, 0.08));\n white-space: nowrap;\n}\n\n.tooltip[hidden] {\n display: none;\n}\n\n.tooltip-row {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.tooltip-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}\n\n.tooltip-label {\n flex: 1 1 auto;\n}\n\n.tooltip-value {\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n}\n\n.actions {\n position: absolute;\n top: 0;\n right: 0;\n pointer-events: auto;\n display: flex;\n gap: 4px;\n}\n\n.state {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--c2-chart__state--color,#71717a);\n font-size: var(--c2-chart__state--font-size,14px);\n background: var(--c2-chart--background,#ffffff);\n}\n\n.legend {\n display: flex;\n flex-wrap: wrap;\n gap: var(--c2-chart__legend--gap,12px);\n padding: var(--c2-chart__legend--padding,8px 0 0);\n color: var(--c2-chart__legend--color,#71717a);\n font-size: var(--c2-chart__legend--font-size,12px);\n}\n\n.legend--start,\n.legend--end {\n flex-direction: column;\n align-content: flex-start;\n flex: 0 0 auto;\n align-self: center;\n max-height: 100%;\n overflow: auto;\n}\n\n/* The legend is on an inline edge, so the frame runs as a row. */\n.frame--side {\n flex-direction: row;\n align-items: stretch;\n}\n\n.legend-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.legend-item[aria-pressed=false] {\n opacity: var(--c2-chart__legend__disabled--opacity,0.38);\n}\n\n.legend-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}\n\n.definitions {\n display: none;\n}\n\n/*\n * Colour probes. `getComputedStyle().getPropertyValue()` hands back the author's text for a `var()` or\n * `color-mix()` chain, which no canvas engine can parse; the computed `color` of an element always\n * resolves to `rgb(…)`. `display: none` keeps them free.\n */\n.theme-probe {\n display: none;\n}\n.theme-probe i:nth-child(1) {\n color: var(--c2-chart__series-1--color,#0265dc);\n}\n.theme-probe i:nth-child(2) {\n color: var(--c2-chart__series-2--color,#ea580c);\n}\n.theme-probe i:nth-child(3) {\n color: var(--c2-chart__series-3--color,#0f766e);\n}\n.theme-probe i:nth-child(4) {\n color: var(--c2-chart__series-4--color,#db2777);\n}\n.theme-probe i:nth-child(5) {\n color: var(--c2-chart__series-5--color,#a16207);\n}\n.theme-probe i:nth-child(6) {\n color: var(--c2-chart__series-6--color,#7c3aed);\n}\n.theme-probe i:nth-child(7) {\n color: var(--c2-chart__series-7--color,#0891b2);\n}\n.theme-probe i:nth-child(8) {\n color: var(--c2-chart__series-8--color,#52525b);\n}\n.theme-probe i:nth-child(9) {\n color: var(--c2-chart__axis--color,#71717a);\n}\n.theme-probe i:nth-child(10) {\n color: var(--c2-chart__grid--color,#e4e4e7);\n}\n.theme-probe i:nth-child(11) {\n color: var(--c2-chart--color,#18181b);\n}\n.theme-probe i:nth-child(12) {\n color: var(--c2-chart__muted--color,#71717a);\n}\n.theme-probe i:nth-child(13) {\n color: var(--c2-chart__tooltip--background-color,#18181b);\n}\n.theme-probe i:nth-child(14) {\n color: var(--c2-chart__tooltip--color,#fafafa);\n}\n.theme-probe i:nth-child(15) {\n color: var(--c2-chart__crosshair--color,#a1a1aa);\n}\n.theme-probe i:nth-child(16) {\n color: var(--c2-chart__positive--color,#16a34a);\n}\n.theme-probe i:nth-child(17) {\n color: var(--c2-chart__negative--color,#dc2626);\n}\n.theme-probe i:nth-child(18) {\n color: var(--c2-chart__surface--color,#ffffff);\n}\n\n/*\n * uPlot's structural CSS, hand-ported.\n *\n * `uplot/dist/uPlot.min.css` is a global stylesheet and a shadow root cannot see it, so the rules the\n * engine's DOM depends on live here instead. Only the structural half is kept: the legend and title rules\n * are dropped because this component renders its own. Version-coupled to the `uplot` peer range — re-check\n * after an upgrade.\n */\n.uplot,\n.uplot *,\n.uplot *::before,\n.uplot *::after {\n box-sizing: border-box;\n}\n\n.uplot {\n font-family: var(--c2-chart--font-family,inherit);\n line-height: 1.5;\n width: min-content;\n}\n\n.u-wrap {\n position: relative;\n user-select: none;\n}\n\n.u-over,\n.u-under {\n position: absolute;\n}\n\n.u-under {\n overflow: hidden;\n}\n\n.uplot canvas {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.u-axis {\n position: absolute;\n}\n\n.u-select {\n position: absolute;\n pointer-events: none;\n background: rgba(0, 0, 0, 0.07);\n}\n\n.u-cursor-x,\n.u-cursor-y {\n position: absolute;\n left: 0;\n top: 0;\n pointer-events: none;\n will-change: transform;\n}\n\n.u-hz .u-cursor-x,\n.u-vt .u-cursor-y {\n height: 100%;\n border-right: var(--c2-chart__crosshair--width,1px) dashed var(--c2-chart__crosshair--color,#a1a1aa);\n}\n\n.u-hz .u-cursor-y,\n.u-vt .u-cursor-x {\n width: 100%;\n border-bottom: var(--c2-chart__crosshair--width,1px) dashed var(--c2-chart__crosshair--color,#a1a1aa);\n}\n\n.u-cursor-pt {\n position: absolute;\n top: 0;\n left: 0;\n border-radius: 50%;\n border: 0 solid;\n pointer-events: none;\n will-change: transform;\n /* Must stay !important: uPlot sets the `background` shorthand inline. */\n background-clip: padding-box !important;\n}\n\n.u-axis.u-off,\n.u-select.u-off,\n.u-cursor-x.u-off,\n.u-cursor-y.u-off,\n.u-cursor-pt.u-off {\n display: none;\n}";
9
+ var chart_default = "@charset \"UTF-8\";\n/* ex : var((width: 24px), width, c2-checkbox) returns var(--c2-checkbox-width, 24px) */\n:host {\n display: flex;\n flex-direction: column;\n box-sizing: border-box;\n width: var(--c2-chart--width,100%);\n height: var(--c2-chart--height,320px);\n /*\n * The plot is absolutely positioned, so nothing inside the host contributes intrinsic width. As a flex\n * item — which is what every example frame in the docs site makes it — the host would otherwise shrink\n * to its legend, and to nothing at all when there is no legend. `min-width: 0` keeps it able to shrink\n * below its content when a layout genuinely needs that.\n */\n min-width: 0;\n padding: var(--c2-chart--padding,12px);\n background: var(--c2-chart--background,#ffffff);\n color: var(--c2-chart--color,#18181b);\n font-family: var(--c2-chart--font-family,inherit);\n font-size: var(--c2-chart--font-size,12px);\n border-top: var(--c2-chart--border-top,1px solid #e4e4e7);\n border-right: var(--c2-chart--border-right,1px solid #e4e4e7);\n border-bottom: var(--c2-chart--border-bottom,1px solid #e4e4e7);\n border-left: var(--c2-chart--border-left,1px solid #e4e4e7);\n border-top-left-radius: var(--c2-chart--border-top-left-radius,8px);\n border-top-right-radius: var(--c2-chart--border-top-right-radius,8px);\n border-bottom-left-radius: var(--c2-chart--border-bottom-left-radius,8px);\n border-bottom-right-radius: var(--c2-chart--border-bottom-right-radius,8px);\n}\n\n:host([hidden]) {\n display: none;\n}\n\n.frame {\n display: flex;\n flex-direction: column;\n flex: 1 1 auto;\n min-height: 0;\n}\n\n/*\n * The plot area is the engine's territory. `contain: strict` plus absolute positioning means the canvas\n * the engine sizes inside it can never feed its own size back into the ResizeObserver watching this box,\n * which is what makes the resize loop structurally impossible rather than merely unlikely.\n */\n.plot-area {\n position: relative;\n flex: 1 1 auto;\n min-height: 0;\n overflow: hidden;\n}\n\n.plot {\n position: absolute;\n inset: 0;\n contain: strict;\n}\n\n/* uPlot has no entry animation. Reveal its already-rendered canvas once, when the adapter becomes ready. */\n:host([data-chart-engine=uplot][animation=auto]) .plot .u-under {\n animation: c2-chart-plot-reveal 500ms cubic-bezier(0.22, 1, 0.36, 1) both;\n}\n\n/* Bars enter from their baseline instead of using the line/area chart's horizontal reveal. */\n:host(c2-bar-chart[data-chart-engine=uplot][animation=auto]) .plot .u-under {\n animation-name: c2-chart-bar-grow;\n animation-duration: 600ms;\n}\n\n@keyframes c2-chart-plot-reveal {\n from {\n clip-path: inset(0 100% 0 0);\n opacity: 0.65;\n }\n to {\n clip-path: inset(0);\n opacity: 1;\n }\n}\n@keyframes c2-chart-bar-grow {\n from {\n clip-path: inset(100% 0 0);\n opacity: 0.65;\n }\n to {\n clip-path: inset(0);\n opacity: 1;\n }\n}\n@media (prefers-reduced-motion: reduce) {\n :host([data-chart-engine=uplot][animation=auto]) .plot .u-under,\n :host(c2-bar-chart[data-chart-engine=uplot][animation=auto]) .plot .u-under {\n animation: none;\n }\n}\n.plot canvas {\n display: block;\n position: absolute;\n inset: 0;\n}\n\n.overlay {\n position: absolute;\n inset: 0;\n pointer-events: none;\n}\n\n.tooltip {\n position: absolute;\n top: 0;\n left: 0;\n z-index: 2;\n pointer-events: none;\n will-change: transform;\n display: flex;\n flex-direction: column;\n gap: var(--c2-chart__tooltip--gap,6px);\n padding: var(--c2-chart__tooltip--padding,8px 10px);\n border-radius: var(--c2-chart__tooltip--border-radius,6px);\n background: var(--c2-chart__tooltip--background-color,#18181b);\n color: var(--c2-chart__tooltip--color,#fafafa);\n font-size: var(--c2-chart__tooltip--font-size,12px);\n box-shadow: var(--c2-chart__tooltip--box-shadow,0 8px 24px rgba(24, 24, 27, 0.08));\n white-space: nowrap;\n}\n\n.tooltip[hidden] {\n display: none;\n}\n\n.tooltip-row {\n display: flex;\n align-items: center;\n gap: 6px;\n}\n\n.tooltip-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}\n\n.tooltip-label {\n flex: 1 1 auto;\n}\n\n.tooltip-value {\n font-variant-numeric: tabular-nums;\n font-weight: 600;\n}\n\n.actions {\n position: absolute;\n top: 0;\n right: 0;\n pointer-events: auto;\n display: flex;\n gap: 4px;\n}\n\n.state {\n position: absolute;\n inset: 0;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--c2-chart__state--color,#71717a);\n font-size: var(--c2-chart__state--font-size,14px);\n background: var(--c2-chart--background,#ffffff);\n}\n\n.legend {\n display: flex;\n flex-wrap: wrap;\n gap: var(--c2-chart__legend--gap,12px);\n padding: var(--c2-chart__legend--padding,8px 0 0);\n color: var(--c2-chart__legend--color,#71717a);\n font-size: var(--c2-chart__legend--font-size,12px);\n}\n\n.legend--start,\n.legend--end {\n flex-direction: column;\n align-content: flex-start;\n flex: 0 0 auto;\n align-self: center;\n max-height: 100%;\n overflow: auto;\n}\n\n/* The legend is on an inline edge, so the frame runs as a row. */\n.frame--side {\n flex-direction: row;\n align-items: stretch;\n}\n\n.legend-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.legend-item[aria-pressed=false] {\n opacity: var(--c2-chart__legend__disabled--opacity,0.38);\n}\n\n.legend-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}\n\n.definitions {\n display: none;\n}\n\n/*\n * Colour probes. `getComputedStyle().getPropertyValue()` hands back the author's text for a `var()` or\n * `color-mix()` chain, which no canvas engine can parse; the computed `color` of an element always\n * resolves to `rgb(…)`. `display: none` keeps them free.\n */\n.theme-probe {\n display: none;\n}\n.theme-probe i:nth-child(1) {\n color: var(--c2-chart__series-1--color,#0265dc);\n}\n.theme-probe i:nth-child(2) {\n color: var(--c2-chart__series-2--color,#ea580c);\n}\n.theme-probe i:nth-child(3) {\n color: var(--c2-chart__series-3--color,#0f766e);\n}\n.theme-probe i:nth-child(4) {\n color: var(--c2-chart__series-4--color,#db2777);\n}\n.theme-probe i:nth-child(5) {\n color: var(--c2-chart__series-5--color,#a16207);\n}\n.theme-probe i:nth-child(6) {\n color: var(--c2-chart__series-6--color,#7c3aed);\n}\n.theme-probe i:nth-child(7) {\n color: var(--c2-chart__series-7--color,#0891b2);\n}\n.theme-probe i:nth-child(8) {\n color: var(--c2-chart__series-8--color,#52525b);\n}\n.theme-probe i:nth-child(9) {\n color: var(--c2-chart__axis--color,#71717a);\n}\n.theme-probe i:nth-child(10) {\n color: var(--c2-chart__grid--color,#e4e4e7);\n}\n.theme-probe i:nth-child(11) {\n color: var(--c2-chart--color,#18181b);\n}\n.theme-probe i:nth-child(12) {\n color: var(--c2-chart__muted--color,#71717a);\n}\n.theme-probe i:nth-child(13) {\n color: var(--c2-chart__tooltip--background-color,#18181b);\n}\n.theme-probe i:nth-child(14) {\n color: var(--c2-chart__tooltip--color,#fafafa);\n}\n.theme-probe i:nth-child(15) {\n color: var(--c2-chart__crosshair--color,#a1a1aa);\n}\n.theme-probe i:nth-child(16) {\n color: var(--c2-chart__positive--color,#16a34a);\n}\n.theme-probe i:nth-child(17) {\n color: var(--c2-chart__negative--color,#dc2626);\n}\n.theme-probe i:nth-child(18) {\n color: var(--c2-chart__surface--color,#ffffff);\n}\n\n/*\n * uPlot's structural CSS, hand-ported.\n *\n * `uplot/dist/uPlot.min.css` is a global stylesheet and a shadow root cannot see it, so the rules the\n * engine's DOM depends on live here instead. Only the structural half is kept: the legend and title rules\n * are dropped because this component renders its own. Version-coupled to the `uplot` peer range — re-check\n * after an upgrade.\n */\n.uplot,\n.uplot *,\n.uplot *::before,\n.uplot *::after {\n box-sizing: border-box;\n}\n\n.uplot {\n font-family: var(--c2-chart--font-family,inherit);\n line-height: 1.5;\n width: min-content;\n}\n\n.u-wrap {\n position: relative;\n user-select: none;\n}\n\n.u-over,\n.u-under {\n position: absolute;\n}\n\n.u-under {\n overflow: hidden;\n}\n\n.uplot canvas {\n display: block;\n position: relative;\n width: 100%;\n height: 100%;\n}\n\n.u-axis {\n position: absolute;\n}\n\n.u-select {\n position: absolute;\n pointer-events: none;\n background: rgba(0, 0, 0, 0.07);\n}\n\n.u-cursor-x,\n.u-cursor-y {\n position: absolute;\n left: 0;\n top: 0;\n pointer-events: none;\n will-change: transform;\n}\n\n.u-hz .u-cursor-x,\n.u-vt .u-cursor-y {\n height: 100%;\n border-right: var(--c2-chart__crosshair--width,1px) dashed var(--c2-chart__crosshair--color,#a1a1aa);\n}\n\n.u-hz .u-cursor-y,\n.u-vt .u-cursor-x {\n width: 100%;\n border-bottom: var(--c2-chart__crosshair--width,1px) dashed var(--c2-chart__crosshair--color,#a1a1aa);\n}\n\n.u-cursor-pt {\n position: absolute;\n top: 0;\n left: 0;\n border-radius: 50%;\n border: 0 solid;\n pointer-events: none;\n will-change: transform;\n /* Must stay !important: uPlot sets the `background` shorthand inline. */\n background-clip: padding-box !important;\n}\n\n.u-axis.u-off,\n.u-select.u-off,\n.u-cursor-x.u-off,\n.u-cursor-y.u-off,\n.u-cursor-pt.u-off {\n display: none;\n}";
9
10
  //#endregion
10
11
  //#region src/chart-base.ts
11
12
  /** Properties that only ever move data. A change limited to these takes the fast path. */
@@ -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
  *
@@ -112,6 +117,7 @@ var RESIZE_EPSILON = 1;
112
117
  * @cssproperty {pixel} [--c2-chart__line--width=2px] - Stroke width of a line series.
113
118
  * @cssproperty {pixel} [--c2-chart__point--radius=2.5px] - Radius of a data point marker.
114
119
  * @cssproperty {opacity} [--c2-chart__area--opacity=0.15] - Opacity of the fill under an area series.
120
+ * @cssproperty {number} [--c2-chart__bar--border-radius=0] - Roundedness of a bar's value end, from 0 (square) to 0.5 (fully rounded).
115
121
  *
116
122
  * @cssproperty {color} [--c2-chart__axis--color=#71717a] - Colour of the axis lines and tick labels.
117
123
  * @cssproperty {font-size} [--c2-chart__axis--font-size=12px] - Font size of the tick labels.
@@ -153,7 +159,7 @@ var ChartBase = class extends LitElement {
153
159
  this.xType = "linear";
154
160
  this.legend = "bottom";
155
161
  this.tooltip = "axis";
156
- this.animation = "none";
162
+ this.animation = "auto";
157
163
  this.loading = false;
158
164
  this.error = "";
159
165
  this.emptyMessage = "No data";
@@ -163,6 +169,8 @@ var ChartBase = class extends LitElement {
163
169
  this.hiddenSeries = /* @__PURE__ */ new Set();
164
170
  this.hoverContext = null;
165
171
  this.engineFailed = false;
172
+ this.hasLinkedLegend = false;
173
+ this.hasLinkedTooltip = false;
166
174
  this.themeController = new ChartThemeController(this, () => this.handleThemeChange());
167
175
  this.frameBuilder = new ChartFrameBuilder();
168
176
  this.#creating = false;
@@ -175,6 +183,9 @@ var ChartBase = class extends LitElement {
175
183
  height: 0
176
184
  };
177
185
  this.#visible = false;
186
+ this.#tooltipContext = null;
187
+ this.#legendPresenters = /* @__PURE__ */ new Set();
188
+ this.#tooltipPresenters = /* @__PURE__ */ new Set();
178
189
  this.#handleSeriesChange = (event) => {
179
190
  event.stopPropagation();
180
191
  this.#collectSeries();
@@ -194,6 +205,17 @@ var ChartBase = class extends LitElement {
194
205
  #intersectionObserver;
195
206
  #visible;
196
207
  #unsubscribe;
208
+ #tooltipContext;
209
+ #legendPresenters;
210
+ #tooltipPresenters;
211
+ /** Restores interaction state after an engine instance is created or replaced. */
212
+ restoreVisibility(adapter) {
213
+ for (const index of this.hiddenSeries) adapter.setSeriesVisibility(index, false);
214
+ }
215
+ /** Whether changing chart data can change the legend model. Pie charts use row labels as entries. */
216
+ get legendDependsOnData() {
217
+ return false;
218
+ }
197
219
  connectedCallback() {
198
220
  super.connectedCallback();
199
221
  this.addEventListener(SERIES_CHANGE_EVENT, this.#handleSeriesChange);
@@ -210,6 +232,14 @@ var ChartBase = class extends LitElement {
210
232
  this.#intersectionObserver.observe(this);
211
233
  } else this.#visible = true;
212
234
  }
235
+ /**
236
+ * Client-rendered charts connect before Lit creates their plot node. Observe it after the first render as
237
+ * well, so a chart whose initial layout is zero-sized still draws when its container becomes measurable.
238
+ */
239
+ firstUpdated() {
240
+ if (this.plotElement) this.#resizeObserver?.observe(this.plotElement);
241
+ this.themeController.invalidate();
242
+ }
213
243
  disconnectedCallback() {
214
244
  super.disconnectedCallback();
215
245
  this.removeEventListener(SERIES_CHANGE_EVENT, this.#handleSeriesChange);
@@ -219,6 +249,7 @@ var ChartBase = class extends LitElement {
219
249
  this.#unsubscribe = void 0;
220
250
  this.adapter?.destroy();
221
251
  this.adapter = void 0;
252
+ this.removeAttribute("data-chart-engine");
222
253
  this.removeAttribute("data-chart-ready");
223
254
  }
224
255
  /**
@@ -251,6 +282,12 @@ var ChartBase = class extends LitElement {
251
282
  super.updated(changed);
252
283
  if (isServer) return;
253
284
  this.#sync();
285
+ if (changed.has("tooltip") && this.tooltip === "none") {
286
+ this.#tooltipContext = null;
287
+ this.#setHover(null);
288
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: null }));
289
+ }
290
+ if ([...changed.keys()].some((key) => key !== "hoverContext")) this.notifyLegendChange();
254
291
  }
255
292
  /**
256
293
  * Replaces the data without going through the update queue. Equivalent to assigning `data`, but skips
@@ -281,6 +318,7 @@ var ChartBase = class extends LitElement {
281
318
  else next.add(index);
282
319
  this.hiddenSeries = next;
283
320
  this.adapter?.setSeriesVisibility(index, visible);
321
+ this.notifyLegendChange();
284
322
  const series = this.resolvedSeries[index];
285
323
  if (!series) return;
286
324
  this.dispatchEvent(new CustomEvent("series-toggle", { detail: {
@@ -290,6 +328,42 @@ var ChartBase = class extends LitElement {
290
328
  hidden: [...next]
291
329
  } }));
292
330
  }
331
+ /** Current entries for a linked `c2-chart-legend`. */
332
+ getLegendItems() {
333
+ return this.legendItems();
334
+ }
335
+ /** Current hover model for a linked `c2-chart-tooltip`. */
336
+ getTooltipContext() {
337
+ return this.#tooltipContext;
338
+ }
339
+ /** Plot bounds used to place a linked floating tooltip in viewport coordinates. */
340
+ getPlotBounds() {
341
+ return (this.plotElement ?? this).getBoundingClientRect();
342
+ }
343
+ /** Registers an external legend and suppresses the built-in legend until it disconnects. */
344
+ registerLegendPresenter(presenter) {
345
+ this.#legendPresenters.add(presenter);
346
+ this.hasLinkedLegend = this.#legendPresenters.size > 0;
347
+ }
348
+ /** Removes an external legend registration. */
349
+ unregisterLegendPresenter(presenter) {
350
+ this.#legendPresenters.delete(presenter);
351
+ this.hasLinkedLegend = this.#legendPresenters.size > 0;
352
+ }
353
+ /** Registers an external tooltip and suppresses the built-in tooltip until it disconnects. */
354
+ registerTooltipPresenter(presenter) {
355
+ this.#tooltipPresenters.add(presenter);
356
+ this.hasLinkedTooltip = this.#tooltipPresenters.size > 0;
357
+ }
358
+ /** Removes an external tooltip registration. */
359
+ unregisterTooltipPresenter(presenter) {
360
+ this.#tooltipPresenters.delete(presenter);
361
+ this.hasLinkedTooltip = this.#tooltipPresenters.size > 0;
362
+ }
363
+ /** Notifies linked legends after a subclass changes a data-shaped legend such as pie slices. */
364
+ notifyLegendChange() {
365
+ this.dispatchEvent(new CustomEvent("legend-change", { detail: { items: this.getLegendItems() } }));
366
+ }
293
367
  /** The series in draw order: `c2-chart-series` children when present, otherwise the `series` property. */
294
368
  get resolvedSeries() {
295
369
  if (this.seriesElements.length > 0) return this.seriesElements.filter((element) => !element.hidden).map((element) => element.toConfig());
@@ -359,13 +433,17 @@ var ChartBase = class extends LitElement {
359
433
  await this.#createChart();
360
434
  return;
361
435
  }
436
+ let restoreAfterData = false;
362
437
  if (this.#optionsDirty) {
363
438
  this.#optionsDirty = false;
364
- this.adapter.setOptions(this.buildOptions(this.buildContext()), this.#optionsMode);
439
+ const mode = this.#optionsMode;
440
+ this.adapter.setOptions(this.buildOptions(this.buildContext()), mode);
365
441
  this.#optionsMode = "merge";
442
+ restoreAfterData = mode === "replace";
366
443
  this.#dataDirty = true;
367
444
  }
368
445
  if (this.#dataDirty) this.#flushData();
446
+ if (restoreAfterData) this.restoreVisibility(this.adapter);
369
447
  }
370
448
  async #createChart() {
371
449
  if (this.#creating || !this.plotElement || !this.frame) return;
@@ -385,7 +463,8 @@ var ChartBase = class extends LitElement {
385
463
  this.adapter = adapter;
386
464
  this.#optionsDirty = false;
387
465
  this.#dataDirty = false;
388
- for (const index of this.hiddenSeries) adapter.setSeriesVisibility(index, false);
466
+ this.restoreVisibility(adapter);
467
+ this.setAttribute("data-chart-engine", this.engineName);
389
468
  this.setAttribute("data-chart-ready", "true");
390
469
  this.dispatchEvent(new CustomEvent("chart-ready", { detail: { engine: this.engineName } }));
391
470
  } catch (error) {
@@ -402,6 +481,7 @@ var ChartBase = class extends LitElement {
402
481
  if (this.#appended > 0 && this.adapter.appendData) this.adapter.appendData(projected, this.#appended);
403
482
  else this.adapter.setData(projected);
404
483
  this.#appended = 0;
484
+ if (this.legendDependsOnData) this.notifyLegendChange();
405
485
  }
406
486
  #handleResize(entries) {
407
487
  const rect = entries[entries.length - 1]?.contentRect;
@@ -451,12 +531,18 @@ var ChartBase = class extends LitElement {
451
531
  } else this.#unsubscribe = () => controller.abort();
452
532
  }
453
533
  #handleHover(detail) {
454
- if (!detail || !this.frame || this.tooltip === "none") {
534
+ if (!detail || !this.frame) {
535
+ this.#tooltipContext = null;
455
536
  this.#setHover(null);
537
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: null }));
538
+ this.dispatchEvent(new CustomEvent("point-hover", { detail: null }));
456
539
  return;
457
540
  }
458
541
  const context = this.#tooltipContextAt(detail);
459
- this.#setHover(context);
542
+ const tooltipContext = this.tooltip === "none" ? null : context;
543
+ this.#tooltipContext = tooltipContext;
544
+ this.#setHover(this.hasLinkedTooltip ? null : tooltipContext);
545
+ this.dispatchEvent(new CustomEvent("tooltip-change", { detail: tooltipContext }));
460
546
  const point = this.#pointAt(detail.index, detail.seriesIndex);
461
547
  if (point) this.dispatchEvent(new CustomEvent("point-hover", { detail: point }));
462
548
  }
@@ -468,7 +554,7 @@ var ChartBase = class extends LitElement {
468
554
  #setHover(context) {
469
555
  const previous = this.hoverContext;
470
556
  if (context && this.tooltipElement) this.tooltipElement.style.transform = `translate(${context.px}px, ${context.py}px)`;
471
- if (previous && context && previous.index === context.index) return;
557
+ if (previous && context && previous.index === context.index && (this.tooltip !== "item" || previous.entries[0]?.seriesIndex === context.entries[0]?.seriesIndex)) return;
472
558
  this.hoverContext = context;
473
559
  }
474
560
  #tooltipContextAt(detail) {
@@ -541,8 +627,8 @@ var ChartBase = class extends LitElement {
541
627
  return new Intl.NumberFormat(this.locale, { maximumFractionDigits: 2 }).format(value);
542
628
  }
543
629
  render() {
544
- const legendBefore = this.legend === "top" || this.legend === "start";
545
- const legendAfter = this.legend === "bottom" || this.legend === "end";
630
+ const legendBefore = !this.hasLinkedLegend && (this.legend === "top" || this.legend === "start");
631
+ const legendAfter = !this.hasLinkedLegend && (this.legend === "bottom" || this.legend === "end");
546
632
  const side = this.legend === "start" || this.legend === "end";
547
633
  return html`
548
634
  <div class="frame ${side ? "frame--side" : ""}" part="frame">
@@ -551,7 +637,9 @@ var ChartBase = class extends LitElement {
551
637
  <!-- No bindings inside: Lit never patches this node, so the engine's canvas survives updates. -->
552
638
  <div class="plot" part="plot"></div>
553
639
  <div class="overlay" part="overlay">
554
- <div class="tooltip" part="tooltip" role="tooltip" ?hidden=${!this.hoverContext || this.tooltip === "none"}>${this.renderTooltipBody()}</div>
640
+ <div class="tooltip" part="tooltip" role="tooltip" ?hidden=${!this.hoverContext || this.tooltip === "none" || this.hasLinkedTooltip}>
641
+ ${this.renderTooltipBody()}
642
+ </div>
555
643
  <div class="actions" part="actions"><slot name="actions"></slot></div>
556
644
  </div>
557
645
  ${this.renderState()}
@@ -637,7 +725,10 @@ __decorate([property({
637
725
  })], ChartBase.prototype, "xType", void 0);
638
726
  __decorate([property({ type: String })], ChartBase.prototype, "legend", void 0);
639
727
  __decorate([property({ type: String })], ChartBase.prototype, "tooltip", void 0);
640
- __decorate([property({ type: String })], ChartBase.prototype, "animation", void 0);
728
+ __decorate([property({
729
+ type: String,
730
+ reflect: true
731
+ })], ChartBase.prototype, "animation", void 0);
641
732
  __decorate([property({ type: String })], ChartBase.prototype, "locale", void 0);
642
733
  __decorate([property({
643
734
  type: Boolean,
@@ -662,5 +753,7 @@ __decorate([state()], ChartBase.prototype, "seriesElements", void 0);
662
753
  __decorate([state()], ChartBase.prototype, "hiddenSeries", void 0);
663
754
  __decorate([state()], ChartBase.prototype, "hoverContext", void 0);
664
755
  __decorate([state()], ChartBase.prototype, "engineFailed", void 0);
756
+ __decorate([state()], ChartBase.prototype, "hasLinkedLegend", void 0);
757
+ __decorate([state()], ChartBase.prototype, "hasLinkedTooltip", void 0);
665
758
  //#endregion
666
759
  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-CEZvMKwW.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 };