@aparte/plugin-echarts 0.0.0-stage → 0.29.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 aparté
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,201 @@
1
- # Temporary Holding Version
1
+ # @aparte/plugin-echarts
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Charts for [aparté](https://github.com/apartejs/aparte) via [Apache ECharts](https://echarts.apache.org).
4
+ Every ```` ```echarts ```` fence in a reply — an ECharts `option` as JSON — is drawn as a chart, as
5
+ it streams, whichever Markdown provider is installed, or none. A tool whose result is an option
6
+ can be drawn the same way, in place of its row.
7
+
8
+ ```bash
9
+ npm install @aparte/plugin-echarts @aparte/core echarts
10
+ ```
11
+
12
+ ```ts
13
+ import { setupECharts } from '@aparte/plugin-echarts';
14
+
15
+ setupECharts();
16
+ ```
17
+
18
+ `@aparte/core` and `echarts` (6.0 or later) are **peer dependencies**. ECharts is loaded on the
19
+ first chart, not before, and a chart is drawn only once it comes near the screen, so reopening a
20
+ long conversation stays fast.
21
+
22
+ ## The fence
23
+
24
+ The body is ECharts' own `option`, as JSON — nothing new to learn, and what a model already
25
+ knows from ECharts' documentation. `echart` works as the language too.
26
+
27
+ ````md
28
+ ```echarts
29
+ {
30
+ "title": { "text": "Sales by month" },
31
+ "tooltip": { "trigger": "axis" },
32
+ "xAxis": { "type": "category", "data": ["Jan", "Feb", "Mar"] },
33
+ "yAxis": { "type": "value" },
34
+ "series": [{ "name": "Sales", "type": "bar", "data": [120, 200, 150] }]
35
+ }
36
+ ```
37
+ ````
38
+
39
+ While the reply streams, the chart is drawn from the part of the JSON that has arrived — its
40
+ last whole value, brackets closed — at most every 300 ms, so the bars appear as their numbers
41
+ do; each frame updates the same chart. Once the fence closes, one that is not valid JSON — or is,
42
+ but has no `series` and no `dataset` to draw — shows a note and its source.
43
+
44
+ ## A tool's result
45
+
46
+ ```ts
47
+ setupECharts({
48
+ tool: 'plot_sales', // a name, a RegExp, or an array of them
49
+ map: (result, input) => ({ // optional: your tool's data → an ECharts option
50
+ title: { text: String(input.metric) },
51
+ xAxis: { type: 'category', data: result.months },
52
+ yAxis: {},
53
+ series: [{ type: 'line', data: result.values }],
54
+ }),
55
+ });
56
+ ```
57
+
58
+ Without `map`, the result itself is the option: its `structuredContent` when it has one, else its
59
+ text read as JSON. While the tool runs, a quiet box stands where the chart will be; waiting for
60
+ approval, refused, stopped or failed, a one-line note says so.
61
+
62
+ ## The chat's colours
63
+
64
+ The chart takes its look from the chat's tokens — text (`--aparte-text`), axis labels
65
+ (`--aparte-text-muted`), axis and grid lines (`--aparte-border`), the tooltip's ground
66
+ (`--aparte-surface-1`), the font (`--aparte-font-family`), a transparent background — as an
67
+ ECharts **theme**, so anything the option sets itself wins over it. A light/dark switch recolours
68
+ the chart in place (`setTheme`), without drawing it again.
69
+
70
+ The series colours start with `--aparte-primary`, then eight hues validated for colour-vision
71
+ deficiency (each adjacent pair at least 8 ΔE in OKLab ×100 under protanopia, deuteranopia and
72
+ tritanopia, 15 for full colour vision), stepped once for a light ground and once for a dark one
73
+ so a series keeps its hue across the switch. The primary takes the place of the hue nearest to
74
+ it — a brass primary replaces the yellow — so no two look alike side by side. They are
75
+ `ECHARTS_SERIES_HUES`; `echartsPalette(primary, dark)` gives the order.
76
+
77
+ `echartsTheme(tokens)` is the theme itself, pure — the same tokens make the same theme, in Node
78
+ as in a browser — for a chart of your own that should look like the chat's:
79
+
80
+ ```ts
81
+ import * as echarts from 'echarts';
82
+ import { echartsTheme, type EChartsTokens } from '@aparte/plugin-echarts';
83
+
84
+ const tokens: EChartsTokens = {
85
+ text: '#241f2b', muted: '#6d6479', border: '#e5ddce',
86
+ surface: '#fffdf8', background: '#f6f2ea', primary: '#b07d33',
87
+ };
88
+ echarts.init(document.getElementById('my-chart')!, echartsTheme(tokens), { renderer: 'svg' });
89
+ ```
90
+
91
+ ## Size, motion, screen readers
92
+
93
+ The chart fills the bubble's width, up to 40rem, at `height` (18rem by default), and follows its
94
+ box as it resizes. It is drawn with ECharts' SVG renderer: crisp at any zoom, and its text is
95
+ real text. Under `prefers-reduced-motion` nothing moves: animation is off at the root, on every
96
+ series, on `graphic` and in a `baseOption`, a timeline does not play itself, an `effectScatter`
97
+ ripples only on hover and a `lines` trail is not drawn.
98
+
99
+ It is a `figure` named by the option's `title.text`, else "Chart". Inside it, ECharts' own
100
+ description is on (`aria.enabled`, in the chat's language when that language is registered with
101
+ ECharts, English otherwise) in the plugin's own settings — an option cannot turn its label off or
102
+ replace it with a `description` of its own; only its `aria.decal` is kept. A visually hidden table
103
+ holds the numbers when the option says plainly what they are: a `dataset` (its first row a header
104
+ the way ECharts reads `sourceHeader`), a category axis with its series, or a pie's named values. A
105
+ series with no data has no column, and the table stops at 1000 rows, 50 columns and 10 000 cells,
106
+ whatever the option asks — a last line, hidden too, says when it lists only part of the data.
107
+
108
+ Its words follow the chat's language (ten of them built in) and can be set with `extendLocale()`:
109
+
110
+ ```ts
111
+ aparteGlobalConfig.extendLocale({
112
+ echartsChart: 'Chart', // the name of an untitled chart
113
+ echartsError: 'This chart could not be drawn.',
114
+ echartsLoading: 'Loading chart',
115
+ echartsWaiting: 'Waiting for your approval',
116
+ echartsNotRun: 'No chart: the tool did not run.',
117
+ echartsFailed: 'No chart: the tool failed.',
118
+ echartsTableCut: 'Only part of the data is listed.', // the hidden table's last line, when it is cut
119
+ });
120
+ ```
121
+
122
+ ## Options
123
+
124
+ `setupECharts(options?, config?)` takes `EChartsOptions`:
125
+
126
+ | option | default | |
127
+ | --- | --- | --- |
128
+ | `progressive` | `true` | `false` draws a chart once its fence has closed |
129
+ | `height` | `'18rem'` | pixels, or a CSS length |
130
+ | `renderer` | `'svg'` | `'canvas'` for a chart of tens of thousands of points |
131
+ | `tool` | — | the tool(s) whose result is an option |
132
+ | `map` | — | `(result, input) => option` |
133
+
134
+ Both setup functions return a disposer that unregisters the renderers, stops watching the theme
135
+ and disposes every chart they drew.
136
+
137
+ ## What a model's option cannot do
138
+
139
+ The option comes from a model or a tool, so `sanitizeEChartsOption(option)` takes out whatever
140
+ ECharts would turn into HTML, a request or a navigation before it is drawn — and only that:
141
+
142
+ - **Tooltips are drawn, never written as HTML.** Every `tooltip` (the root's, a series', a data
143
+ item's) is forced to `renderMode: 'richText'`, and the HTML-only `extraCssText`, `className`,
144
+ `appendTo` and `appendToBody` go. A string `formatter` stays: in rich-text mode ECharts draws it
145
+ as text, so `<b>` shows as `<b>`.
146
+ - **No download, no HTML panel.** `toolbox.feature.saveAsImage` (a download, or a new tab) and
147
+ `toolbox.feature.dataView` (an HTML table with a textarea) go.
148
+ - **No pictures.** A `graphic` element of `type: 'image'`, any value with an `image` (a picture
149
+ fill, a rich-text background) or an `svgElement` (an SVG pattern ECharts builds node by node —
150
+ a `<script>` would run), any `image://…` string (a symbol, a legend or handle icon), and any
151
+ string holding a CSS `url(…)` (a fill pointing at another document) go — read with its CSS
152
+ escapes decoded, since a fill takes `u\rl(` as `url(`; any string pairing a backslash with a
153
+ bracket goes too.
154
+ - **Only colours where a colour goes.** ECharts copies a `color`, `fill`, `stroke` or any `…Color`
155
+ into an SVG attribute as it is, so there only colour syntax stays: a name, `#hex`, or `rgb()` /
156
+ `rgba()` / `hsl()` / `hsla()` of numbers. A gradient is an object ECharts builds itself; its
157
+ stops are checked the same way.
158
+ - **No regular expression.** A dataset `filter` transform's `reg` is compiled and run on every
159
+ value, and a pattern can backtrack for minutes (`^(a+)+$`), so every `reg` goes.
160
+ - **No links.** A string `link` or `sublink` (a title, a treemap or sunburst node — ECharts opens
161
+ it on a click, and `javascript:` would run in the page) goes.
162
+ - **No CSS through a font.** ECharts writes `fontFamily`, `fontSize`, `fontStyle`, `fontWeight`
163
+ and `font` into a `style` attribute; a value with anything beyond a font name in it goes.
164
+ - `__proto__`, `constructor` and `prototype` keys go.
165
+
166
+ Functions cannot arrive in JSON, so a `formatter` or `renderItem` callback never does. The option
167
+ reaches the page's markup once, as the escaped value of an attribute on the chart's element (a fence
168
+ hands core markup, not a node); ECharts draws it into that element, and the table's cells are
169
+ written as text.
170
+
171
+ ## Two entry points
172
+
173
+ `@aparte/plugin-echarts` imports the full ECharts build on the first chart. To hand over your
174
+ own — a smaller build with only the charts you use — import from the entry that imports nothing
175
+ from `echarts`:
176
+
177
+ ```ts
178
+ import * as echarts from 'echarts/core';
179
+ import { BarChart, LineChart, PieChart } from 'echarts/charts';
180
+ import {
181
+ AriaComponent, DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent,
182
+ } from 'echarts/components';
183
+ import { SVGRenderer } from 'echarts/renderers';
184
+ import { setupEChartsWith } from '@aparte/plugin-echarts/core';
185
+
186
+ // `AriaComponent` and `SVGRenderer` are what the plugin itself uses.
187
+ echarts.use([
188
+ BarChart, LineChart, PieChart,
189
+ AriaComponent, DatasetComponent, GridComponent, LegendComponent, TitleComponent, TooltipComponent,
190
+ SVGRenderer,
191
+ ]);
192
+ setupEChartsWith(echarts);
193
+ ```
194
+
195
+ `setupEChartsWith` takes the module, or a function returning a promise of it, and any object
196
+ shaped like `EChartsLike` (`init`, returning an `EChartsInstanceLike`: `setOption`, `setTheme`,
197
+ `resize`, `dispose`). A chart whose series type your build lacks shows the note. The `/core`
198
+ entry exports the same helpers and types: `sanitizeEChartsOption`, `echartsTheme`,
199
+ `echartsPalette`, `ECHARTS_SERIES_HUES`, `EChartsOption`, `EChartsTokens`, `EChartsOptions`.
200
+
201
+ ESM only.
package/dist/core.d.ts ADDED
@@ -0,0 +1,64 @@
1
+ import { type AparteConfig, type AparteToolMatcher } from '@aparte/core';
2
+ export { sanitizeEChartsOption, type EChartsOption } from './option.js';
3
+ export { echartsTheme, echartsPalette, ECHARTS_SERIES_HUES, type EChartsTokens } from './theme.js';
4
+ /** The part of a chart ECharts hands back from `init` that this plugin calls. */
5
+ export interface EChartsInstanceLike {
6
+ setOption(option: Record<string, unknown>, opts?: {
7
+ notMerge?: boolean;
8
+ }): void;
9
+ setTheme(theme: Record<string, unknown>): void;
10
+ resize(): void;
11
+ dispose(): void;
12
+ }
13
+ /**
14
+ * The part of ECharts this plugin calls. Structural, so this module imports nothing from
15
+ * `echarts` — a bundle that hands over its own build (`echarts/core` with the charts it
16
+ * uses) carries one copy.
17
+ */
18
+ export interface EChartsLike {
19
+ init(dom: HTMLElement, theme?: Record<string, unknown> | string | null, opts?: {
20
+ renderer?: 'svg' | 'canvas';
21
+ locale?: string;
22
+ }): EChartsInstanceLike;
23
+ }
24
+ export interface EChartsOptions {
25
+ /**
26
+ * Redraw a chart while its fence arrives, from the part of its JSON that has arrived
27
+ * (default `true`). `false` draws it once the fence has closed.
28
+ */
29
+ progressive?: boolean;
30
+ /** The chart's height: pixels, or a CSS length (`'18rem'`, the default). It fills the width, up to 40rem. */
31
+ height?: number | string;
32
+ /**
33
+ * ECharts' renderer (default `'svg'`): crisp at any zoom, sized by the page like any
34
+ * element, and its text is real text. `'canvas'` for a chart with tens of thousands of
35
+ * points, where an SVG node per mark costs more than a bitmap.
36
+ */
37
+ renderer?: 'svg' | 'canvas';
38
+ /**
39
+ * The tool (or tools) whose result IS an ECharts option: it is drawn as the chart in
40
+ * place of the tool's row. Its `structuredContent` when it has one, else its text read
41
+ * as JSON — or what `map` makes of it.
42
+ */
43
+ tool?: AparteToolMatcher | AparteToolMatcher[];
44
+ /**
45
+ * Turn a tool's own result (and the arguments it was called with) into an ECharts option.
46
+ * The result is `any`, as in `@aparte/plugin-cards`: its shape is your tool's, and `map` is
47
+ * where you read it.
48
+ */
49
+ map?: (result: any, input: Record<string, unknown>) => unknown;
50
+ }
51
+ /**
52
+ * Draw charts with an ECharts you provide — the module, or a function that loads it on the
53
+ * first chart. The entry point for a bundle that controls what it ships:
54
+ * `@aparte/plugin-echarts` itself is this, with `() => import('echarts')`.
55
+ *
56
+ * @returns a disposer that unregisters the renderers, stops watching the theme and
57
+ * disposes every chart on the page.
58
+ * @example
59
+ * import * as echarts from 'echarts';
60
+ * import { setupEChartsWith } from '@aparte/plugin-echarts/core';
61
+ * setupEChartsWith(echarts);
62
+ */
63
+ export declare function setupEChartsWith(echarts: EChartsLike | (() => Promise<EChartsLike>), options?: EChartsOptions, config?: AparteConfig): () => void;
64
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AAAA,OAAO,EAGH,KAAK,YAAY,EAGjB,KAAK,iBAAiB,EAEzB,MAAM,cAAc,CAAC;AAKtB,OAAO,EAAE,qBAAqB,EAAE,KAAK,aAAa,EAAE,MAAM,aAAa,CAAC;AACxE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,mBAAmB,EAAE,KAAK,aAAa,EAAE,MAAM,YAAY,CAAC;AAEnG,iFAAiF;AACjF,MAAM,WAAW,mBAAmB;IAChC,SAAS,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAA;KAAE,GAAG,IAAI,CAAC;IAChF,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,IAAI,CAAC;IAC/C,MAAM,IAAI,IAAI,CAAC;IACf,OAAO,IAAI,IAAI,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,WAAW;IACxB,IAAI,CAAC,GAAG,EAAE,WAAW,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,GAAG,IAAI,EAAE,IAAI,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,mBAAmB,CAAC;CACzJ;AAED,MAAM,WAAW,cAAc;IAC3B;;;OAGG;IACH,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,6GAA6G;IAC7G,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB;;;;OAIG;IACH,QAAQ,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC5B;;;;OAIG;IACH,IAAI,CAAC,EAAE,iBAAiB,GAAG,iBAAiB,EAAE,CAAC;IAC/C;;;;OAIG;IAEH,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;CAClE;AAmDD;;;;;;;;;;;GAWG;AACH,wBAAgB,gBAAgB,CAC5B,OAAO,EAAE,WAAW,GAAG,CAAC,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,EACnD,OAAO,GAAE,cAAmB,EAC5B,MAAM,GAAE,YAAiC,GAC1C,MAAM,IAAI,CAgBZ"}