Skip to content
This repository was archived by the owner on Mar 26, 2026. It is now read-only.

Commit ccbf5ec

Browse files
authored
Merge pull request #7 from pyreon/docs/add-charts-feature
docs: add charts and feature documentation
2 parents 2311031 + c65898b commit ccbf5ec

2 files changed

Lines changed: 940 additions & 0 deletions

File tree

content/docs/charts/index.mdx

Lines changed: 270 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,270 @@
1+
---
2+
title: Charts
3+
description: Reactive ECharts bridge with lazy loading, auto-detection, and typed options for Pyreon.
4+
---
5+
6+
`@pyreon/charts` provides a reactive bridge to [Apache ECharts](https://echarts.apache.org/) for Pyreon applications. Chart modules are lazy-loaded on demand -- zero bundle cost until a chart actually renders. The Canvas renderer is used by default, with SVG available as an option.
7+
8+
<PackageBadge name="@pyreon/charts" href="/docs/charts" />
9+
10+
## Installation
11+
12+
```package-install
13+
@pyreon/charts
14+
```
15+
16+
## Quick Start
17+
18+
Use the `<Chart />` component to render a chart. Pass an options function that returns a standard ECharts configuration -- signal reads inside the function are tracked for reactivity.
19+
20+
```tsx
21+
import { signal } from '@pyreon/reactivity'
22+
import { Chart } from '@pyreon/charts'
23+
24+
function SalesChart() {
25+
const months = signal(['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun'])
26+
const revenue = signal([120, 200, 150, 80, 70, 110])
27+
28+
return (
29+
<Chart
30+
options={() => ({
31+
xAxis: { type: 'category', data: months() },
32+
yAxis: { type: 'value' },
33+
tooltip: { trigger: 'axis' },
34+
series: [{ name: 'Revenue', type: 'bar', data: revenue() }],
35+
})}
36+
style="height: 400px"
37+
/>
38+
)
39+
}
40+
```
41+
42+
The `options` prop accepts a function (not a plain object) so that signal reads are tracked. When any signal inside the function changes, the chart re-renders automatically.
43+
44+
## API Reference
45+
46+
### `<Chart />`
47+
48+
The primary component for rendering charts.
49+
50+
| Prop | Type | Description |
51+
|------|------|-------------|
52+
| `options` | `() => EChartsOption` | Function returning ECharts configuration. Signal reads are tracked for reactivity. |
53+
| `style` | `string` | Inline style string. Must include a height (ECharts requires a sized container). |
54+
| `class` | `string` | CSS class name for the container element. |
55+
| `renderer` | `'canvas' \| 'svg'` | Rendering mode. Defaults to `'canvas'`. |
56+
| `onChartReady` | `(instance: ECharts) => void` | Callback fired after the chart instance is initialized. |
57+
| `on*` | Event handlers | ECharts event bindings, e.g. `onClick`, `onMouseover`, `onLegendSelectChanged`. |
58+
59+
```tsx
60+
<Chart
61+
options={() => ({ /* ... */ })}
62+
style="height: 300px"
63+
renderer="svg"
64+
onClick={(params) => console.log('Clicked:', params.name)}
65+
onChartReady={(instance) => console.log('Chart ready:', instance)}
66+
/>
67+
```
68+
69+
### `useChart<TOption>(optionsFn, config?)`
70+
71+
A lower-level hook for programmatic control. Returns reactive signals for the chart instance and error state.
72+
73+
```tsx
74+
import { useChart } from '@pyreon/charts'
75+
76+
function MyChart() {
77+
const { containerRef, instance, error } = useChart(() => ({
78+
xAxis: { type: 'category', data: ['A', 'B', 'C'] },
79+
yAxis: { type: 'value' },
80+
series: [{ type: 'bar', data: [10, 20, 30] }],
81+
}))
82+
83+
return (
84+
<div>
85+
{() => error() ? <p class="error">{error()!.message}</p> : null}
86+
<div ref={(el) => containerRef.set(el)} style="height: 400px" />
87+
</div>
88+
)
89+
}
90+
```
91+
92+
**Config options:**
93+
94+
| Option | Type | Default | Description |
95+
|--------|------|---------|-------------|
96+
| `renderer` | `'canvas' \| 'svg'` | `'canvas'` | Rendering mode |
97+
| `notMerge` | `boolean` | `false` | Replace options entirely instead of merging |
98+
| `lazyUpdate` | `boolean` | `false` | Defer chart update to next frame |
99+
100+
**Return value:**
101+
102+
| Property | Type | Description |
103+
|----------|------|-------------|
104+
| `containerRef` | `Signal<HTMLElement \| null>` | Bind to a DOM element via `ref` |
105+
| `instance` | `Signal<ECharts \| null>` | The underlying ECharts instance (available after init) |
106+
| `error` | `Signal<Error \| null>` | Error signal for init or setOption failures |
107+
108+
### Types
109+
110+
`@pyreon/charts` re-exports all ECharts option types for strict typing:
111+
112+
```tsx
113+
import type {
114+
ComposeOption,
115+
BarSeriesOption,
116+
LineSeriesOption,
117+
PieSeriesOption,
118+
ScatterSeriesOption,
119+
RadarSeriesOption,
120+
HeatmapSeriesOption,
121+
TreemapSeriesOption,
122+
SankeySeriesOption,
123+
GaugeSeriesOption,
124+
FunnelSeriesOption,
125+
CandlestickSeriesOption,
126+
GraphSeriesOption,
127+
} from '@pyreon/charts'
128+
```
129+
130+
## Auto-Detection
131+
132+
When you provide an options function, `@pyreon/charts` inspects the configuration to determine which ECharts modules are needed:
133+
134+
- **Series types** (`bar`, `line`, `pie`, etc.) are detected from `series[].type`
135+
- **Components** (`tooltip`, `legend`, `dataZoom`, etc.) are detected from top-level keys
136+
- **Axis types** (`category`, `value`, `time`, `log`) are detected from axis config
137+
138+
Only the required modules are dynamically imported. A chart with `type: 'bar'` and `tooltip` will only load the bar series renderer and tooltip component -- not the full ECharts bundle.
139+
140+
```tsx
141+
// Only loads: BarChart, TooltipComponent, GridComponent, CanvasRenderer
142+
<Chart
143+
options={() => ({
144+
tooltip: { trigger: 'axis' },
145+
xAxis: { type: 'category', data: labels() },
146+
yAxis: { type: 'value' },
147+
series: [{ type: 'bar', data: values() }],
148+
})}
149+
style="height: 300px"
150+
/>
151+
```
152+
153+
## Strict Typing with ComposeOption
154+
155+
For type-safe chart configurations, use `ComposeOption<>` to narrow the options type to only the series types you use:
156+
157+
```tsx
158+
import { useChart } from '@pyreon/charts'
159+
import type { ComposeOption, BarSeriesOption, LineSeriesOption } from '@pyreon/charts'
160+
161+
type DashboardOption = ComposeOption<BarSeriesOption | LineSeriesOption>
162+
163+
function Dashboard() {
164+
const chart = useChart<DashboardOption>(() => ({
165+
xAxis: { type: 'category', data: ['Q1', 'Q2', 'Q3', 'Q4'] },
166+
yAxis: { type: 'value' },
167+
series: [
168+
{ type: 'bar', data: [100, 200, 150, 300] },
169+
{ type: 'line', data: [80, 170, 130, 280] },
170+
],
171+
}))
172+
173+
return <div ref={(el) => chart.containerRef.set(el)} style="height: 400px" />
174+
}
175+
```
176+
177+
This gives you autocomplete and type checking for the specific series options you declared.
178+
179+
## Manual Registration
180+
181+
For maximum tree-shaking control, use the `@pyreon/charts/manual` entry point. This disables auto-detection and requires you to register ECharts modules explicitly:
182+
183+
```tsx
184+
import { useChart, registerModules } from '@pyreon/charts/manual'
185+
import { BarChart, LineChart } from 'echarts/charts'
186+
import { TooltipComponent, GridComponent, LegendComponent } from 'echarts/components'
187+
import { CanvasRenderer } from 'echarts/renderers'
188+
189+
// Register once at app startup
190+
registerModules([
191+
BarChart,
192+
LineChart,
193+
TooltipComponent,
194+
GridComponent,
195+
LegendComponent,
196+
CanvasRenderer,
197+
])
198+
199+
// Then use useChart / <Chart /> as normal
200+
function MyChart() {
201+
const chart = useChart(() => ({
202+
series: [{ type: 'bar', data: [1, 2, 3] }],
203+
}))
204+
205+
return <div ref={(el) => chart.containerRef.set(el)} style="height: 300px" />
206+
}
207+
```
208+
209+
Use manual registration when you need deterministic bundle sizes or are building a library that should not auto-import ECharts modules.
210+
211+
## Bundle Size
212+
213+
| Import | Approximate Size (gzipped) |
214+
|--------|---------------------------|
215+
| `@pyreon/charts` (wrapper only) | ~2 KB |
216+
| + Bar chart | ~15 KB |
217+
| + Line chart | ~18 KB |
218+
| + Pie chart | ~12 KB |
219+
| + Tooltip + Legend | ~8 KB |
220+
| Full ECharts (all modules) | ~300 KB |
221+
222+
Auto-detection ensures you only pay for what you use. A typical dashboard with 2-3 chart types loads ~40-50 KB of ECharts code.
223+
224+
## Error Handling
225+
226+
Both `<Chart />` and `useChart()` expose an `error` signal that captures initialization and rendering failures:
227+
228+
```tsx
229+
import { Chart } from '@pyreon/charts'
230+
231+
function SafeChart() {
232+
return (
233+
<Chart
234+
options={() => ({
235+
series: [{ type: 'bar', data: chartData() }],
236+
})}
237+
style="height: 300px"
238+
onError={(err) => console.error('Chart error:', err)}
239+
/>
240+
)
241+
}
242+
```
243+
244+
With `useChart()`, check the error signal directly:
245+
246+
```tsx
247+
import { useChart } from '@pyreon/charts'
248+
249+
function SafeChart() {
250+
const { containerRef, error } = useChart(() => ({
251+
series: [{ type: 'bar', data: chartData() }],
252+
}))
253+
254+
return (
255+
<div>
256+
{() => error() ? (
257+
<div class="chart-error">
258+
<p>Failed to render chart: {error()!.message}</p>
259+
</div>
260+
) : null}
261+
<div ref={(el) => containerRef.set(el)} style="height: 300px" />
262+
</div>
263+
)
264+
}
265+
```
266+
267+
Common error scenarios:
268+
- Container element has zero height (ECharts requires a sized container)
269+
- Invalid option structure passed to `setOption`
270+
- Network failure when lazy-loading ECharts modules

0 commit comments

Comments
 (0)