@particle-academy/fancy-echarts 2.0.1 → 2.0.3

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 (2) hide show
  1. package/README.md +177 -6
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -6,18 +6,18 @@ React component library wrapping [Apache ECharts](https://echarts.apache.org/) w
6
6
 
7
7
  ```bash
8
8
  # npm
9
- npm install @particle-academy/fancy-echarts echarts
9
+ npm install @particle-academy/fancy-echarts
10
10
 
11
11
  # pnpm
12
- pnpm add @particle-academy/fancy-echarts echarts
12
+ pnpm add @particle-academy/fancy-echarts
13
13
 
14
14
  # yarn
15
- yarn add @particle-academy/fancy-echarts echarts
15
+ yarn add @particle-academy/fancy-echarts
16
16
  ```
17
17
 
18
- `echarts` is a peer dependency install it alongside fancy-echarts so you control the version and avoid bundling two copies if other parts of your app already use echarts.
18
+ `echarts` is a peer dependency. **npm 7+** and **yarn 3+** install peer deps automatically nothing extra needed. **pnpm** needs `auto-install-peers=true` in `.npmrc`, or run `pnpm add echarts` once.
19
19
 
20
- For 3D charts (globe, surface, scatter3D, bar3D), also install `echarts-gl`:
20
+ 3D charts (globe, surface, scatter3D, bar3D) need the optional `echarts-gl` peer dep:
21
21
 
22
22
  ```bash
23
23
  npm install echarts-gl
@@ -25,7 +25,7 @@ npm install echarts-gl
25
25
 
26
26
  **Peer dependencies:** `react >= 18`, `react-dom >= 18`, `echarts >= 5.5`, `echarts-gl >= 2.0` (optional, only needed for 3D)
27
27
 
28
- > **Breaking change in 2.0** — `echarts` and `echarts-gl` moved from regular dependencies to peer dependencies. Upgrading from 1.x? Run `npm install echarts` (and `echarts-gl` if you use 3D charts) once.
28
+ > **Breaking change in 2.0** — `echarts` and `echarts-gl` moved from regular dependencies to peer dependencies. If you're on npm 7+ or yarn 3+ the upgrade is transparent. On older tooling or pnpm without auto-install, run `npm install echarts` (and `echarts-gl` if you use 3D charts) once.
29
29
 
30
30
  ## Quick Start
31
31
 
@@ -73,6 +73,177 @@ option={{
73
73
 
74
74
  This is consumer responsibility — the wrapper does not introspect `option` to identify HTML-bearing fields.
75
75
 
76
+ ## Recipes: ECharts × react-fancy
77
+
78
+ `fancy-echarts` is a thin wrapper — every interaction surface is reachable through `onEvents`, `useECharts().instance`, and `tooltip.formatter`. These recipes cover the patterns that hold up across charts.
79
+
80
+ ### Wrap any chart with a Popover, ContextMenu, and Action button
81
+
82
+ The pattern that scales: a single `ChartFrame` wrapper that gives every chart an info popover next to the title, action buttons in the header, and a right-click context menu on the body. Build it once, reuse for every chart type:
83
+
84
+ ```tsx
85
+ import { Card, Popover, Action, Badge, ContextMenu, Icon, useToast } from "@particle-academy/react-fancy";
86
+
87
+ function ChartFrame({ title, info, actions = [], onExport, extraMenu, children }) {
88
+ const { toast } = useToast();
89
+ return (
90
+ <Card>
91
+ <Card.Header>
92
+ <div className="flex items-center justify-between">
93
+ <div className="flex items-center gap-2">
94
+ <h3 className="font-semibold">{title}</h3>
95
+ <Popover hover placement="right">
96
+ <Popover.Trigger>
97
+ <button><Icon name="info" size="sm" /></button>
98
+ </Popover.Trigger>
99
+ <Popover.Content>
100
+ <p className="w-64 text-sm text-zinc-500">{info}</p>
101
+ </Popover.Content>
102
+ </Popover>
103
+ </div>
104
+ <div className="flex gap-2">
105
+ {actions.map((a) => <Action key={a.label} size="sm" onClick={a.onClick}>{a.label}</Action>)}
106
+ </div>
107
+ </div>
108
+ </Card.Header>
109
+ <Card.Body>
110
+ <ContextMenu>
111
+ <ContextMenu.Trigger><div>{children}</div></ContextMenu.Trigger>
112
+ <ContextMenu.Content>
113
+ <ContextMenu.Item onClick={onExport}>Export CSV</ContextMenu.Item>
114
+ <ContextMenu.Item onClick={() => toast({ title: "Exported PNG" })}>Export PNG</ContextMenu.Item>
115
+ {extraMenu && <ContextMenu.Separator />}
116
+ {extraMenu}
117
+ </ContextMenu.Content>
118
+ </ContextMenu>
119
+ </Card.Body>
120
+ </Card>
121
+ );
122
+ }
123
+
124
+ <ChartFrame title="Revenue" info="..." actions={[{ label: "Forecast", onClick: ... }]}>
125
+ <EChart option={lineOption} style={{ height: 320 }} />
126
+ </ChartFrame>
127
+ ```
128
+
129
+ Three reasons this works: `<ContextMenu.Trigger>` wraps the chart canvas without injecting DOM into ECharts, `Popover hover` is independent of any chart event, and the chart inside is unaware of the frame.
130
+
131
+ ### Right-click a data point → open a Modal drill-down
132
+
133
+ `onEvents.contextmenu` fires per-datum with the same `params` shape as `click`. Call `params.event.event.preventDefault()` to suppress the browser menu, then drive your own state.
134
+
135
+ ```tsx
136
+ <EChart
137
+ option={barOption}
138
+ onEvents={{
139
+ contextmenu: (params) => {
140
+ params.event.event.preventDefault();
141
+ const region = regions.find((r) => r.name === params.name);
142
+ setDrill(region); // opens a <Modal>
143
+ },
144
+ }}
145
+ />
146
+ ```
147
+
148
+ If you also wrap the chart in `<ContextMenu>` (chart-level actions), this still works — the per-bar `contextmenu` event fires *and* the wrapper menu opens. Suppress one or the other based on which event the cursor was over.
149
+
150
+ ### Click-to-toast and hover-popover on a slice
151
+
152
+ `onEvents.click` for actions, native ECharts `tooltip` for hover details — they don't conflict.
153
+
154
+ ```tsx
155
+ <EChart
156
+ option={{
157
+ tooltip: { trigger: "item", formatter: "{b}<br/>${c}k ({d}%)" },
158
+ series: [{ type: "pie", data: categories }],
159
+ }}
160
+ onEvents={{
161
+ click: (p) => toast({ title: `${p.name}: ${p.percent}%`, variant: "info" }),
162
+ }}
163
+ />
164
+ ```
165
+
166
+ ### Rich HTML tooltips with sanitized data
167
+
168
+ `tooltip.formatter` accepts a function returning an HTML string. Compose colored arrows, badges, and metadata — but always escape user input.
169
+
170
+ ```tsx
171
+ tooltip: {
172
+ trigger: "axis",
173
+ formatter: (params) => {
174
+ const p = params[0];
175
+ const region = regions.find((r) => r.name === p.name);
176
+ const arrow = region.growth >= 0 ? "▲" : "▼";
177
+ const color = region.growth >= 15 ? "#10b981" : "#3b82f6";
178
+ return `<div style="font-weight:600">${escape(p.name)}</div>
179
+ <div>Revenue: <b>$${p.value.toLocaleString()}k</b></div>
180
+ <div style="color:${color}">${arrow} ${region.growth}% YoY</div>`;
181
+ },
182
+ }
183
+ ```
184
+
185
+ For interactive content inside a tooltip (buttons that fire React state), use a `Popover` keyed off `onEvents.mouseover` instead — ECharts tooltips are detached HTML and lose React handlers.
186
+
187
+ ### Per-datum styling from an array of objects
188
+
189
+ Pass `data` as `{ value, itemStyle }[]` to color each bar/slice individually based on a property of the underlying record:
190
+
191
+ ```tsx
192
+ series: [{
193
+ type: "bar",
194
+ data: regions.map((r) => ({
195
+ value: r.value,
196
+ itemStyle: {
197
+ color: r.growth >= 15 ? "#10b981" : "#3b82f6",
198
+ borderRadius: [0, 6, 6, 0],
199
+ },
200
+ })),
201
+ }]
202
+ ```
203
+
204
+ ### Wrap the page in `<Toast.Provider>` for toast feedback
205
+
206
+ `useToast()` only works inside a provider. Wrap the demo's outermost element so chart-event toasts have somewhere to render:
207
+
208
+ ```tsx
209
+ export function Showcase() {
210
+ return (
211
+ <Toast.Provider position="bottom-right">
212
+ <ShowcaseInner />
213
+ </Toast.Provider>
214
+ );
215
+ }
216
+ ```
217
+
218
+ ### Keep the chart `option` memoized
219
+
220
+ Re-creating the option object on every render forces ECharts to diff and reapply. `useMemo` keeps the reference stable so the chart only updates when its inputs actually change:
221
+
222
+ ```tsx
223
+ const lineOption = useMemo(() => ({ /* ... */ }), [revenue, expenses]);
224
+ <EChart option={lineOption} />
225
+ ```
226
+
227
+ ### Theme-toggling is a prop swap
228
+
229
+ Pass `theme="dark-preset"` (after `registerBuiltinThemes()`) or `theme="light"` and re-render — the wrapper rebuilds the chart with the new theme automatically. No `dispose()` calls needed.
230
+
231
+ ```tsx
232
+ const [theme, setTheme] = useState<"light" | "dark-preset">("light");
233
+
234
+ <Action onClick={() => setTheme((t) => t === "light" ? "dark-preset" : "light")}>
235
+ Toggle theme
236
+ </Action>
237
+ <EChart theme={theme} option={option} />
238
+ ```
239
+
240
+ ### Pitfalls
241
+
242
+ - **Badge takes `color`, not `variant`.** Valid colors: `zinc | red | blue | green | amber | violet | rose`. The `variant` prop selects the *style* (`soft | solid | outline`), not the semantic intent. Don't confuse this with `Toast.toast({ variant: "success" })`, which uses semantic names.
243
+ - **`<ContextMenu.Trigger>` needs a single DOM child.** Wrap `<EChart>` in a plain `<div>` if you have additional siblings (or none — Trigger forwards refs through the wrapper).
244
+ - **`params.event` vs `params.event.event`.** ECharts wraps the native event. Call `params.event.event.preventDefault()` to stop the browser context menu, not `params.event.preventDefault()`.
245
+ - **Don't render React inside `tooltip.formatter`.** Returning an HTML string is fine; expecting React state or handlers to attach to that HTML is not. For interactive tooltips, use `Popover` driven by `onEvents.mouseover` / `mouseout` instead.
246
+
76
247
  ## Documentation
77
248
 
78
249
  Full component documentation is available in the [docs/](docs/) folder:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@particle-academy/fancy-echarts",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "React component library wrapping Apache ECharts with typed components for every chart type",
5
5
  "repository": {
6
6
  "type": "git",