goro-charts 1.0.0
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/CHANGELOG.md +33 -0
- package/LICENSE +21 -0
- package/README.md +530 -0
- package/dist/charts/area-chart.d.ts +13 -0
- package/dist/charts/chart-base.d.ts +248 -0
- package/dist/charts/line-chart.d.ts +13 -0
- package/dist/charts/scatter-chart.d.ts +13 -0
- package/dist/data/monotonic-extent.d.ts +39 -0
- package/dist/data/ring-buffer.d.ts +44 -0
- package/dist/data/series-store.d.ts +65 -0
- package/dist/defaults.d.ts +13 -0
- package/dist/goro-charts.js +990 -0
- package/dist/goro-charts.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/math/format.d.ts +16 -0
- package/dist/math/scale.d.ts +33 -0
- package/dist/math/ticks.d.ts +16 -0
- package/dist/presets.d.ts +17 -0
- package/dist/render/area.d.ts +15 -0
- package/dist/render/axes.d.ts +14 -0
- package/dist/render/crosshair.d.ts +36 -0
- package/dist/render/legend.d.ts +10 -0
- package/dist/render/line.d.ts +20 -0
- package/dist/render/scatter.d.ts +11 -0
- package/dist/render/shape.d.ts +5 -0
- package/dist/render/stacked-band.d.ts +40 -0
- package/dist/render/surface.d.ts +32 -0
- package/dist/types.d.ts +142 -0
- package/package.json +78 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [1.0.0]
|
|
9
|
+
|
|
10
|
+
Initial stable release.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **LineChart** — batched polyline with per-pixel-column min/max decimation.
|
|
15
|
+
- **AreaChart** — filled region below the line, same decimation path, separate fill/stroke.
|
|
16
|
+
- **ScatterChart** — stride-thinned scatter plot, one circle per sampled point.
|
|
17
|
+
- **Multi-series** — one chart, many series, each with its own colour, width, fill, dash, and Y-axis.
|
|
18
|
+
- **Dual Y-axis** — independent left and right domains, per-series axis assignment.
|
|
19
|
+
- **Stacked area** — series sharing a `stack` id render cumulatively (AreaChart),
|
|
20
|
+
with the same per-pixel-column decimation as line/area so large windows stay
|
|
21
|
+
cheap (flat draw cost regardless of window size).
|
|
22
|
+
- **Fixed Y range** — lock the grid globally with `yMin`/`yMax`, or per-series overrides.
|
|
23
|
+
- **Streaming ring mode** — O(1) append and O(1) sliding-window min/max via monotonic
|
|
24
|
+
deques. The grid Y domain tracks the sliding window (grows and shrinks) so bands
|
|
25
|
+
never drift past the frame; snapshot mode keeps the expand-only anchor.
|
|
26
|
+
- **Snapshot mode** — full-series replacement via `setData` with columnar `Float64Array` data.
|
|
27
|
+
- **Crosshair** — multi-series interpolated tooltip card, keyboard navigation, chart-to-chart sync.
|
|
28
|
+
- **Accessibility** — `role="img"`, dynamic `aria-label`, `aria-live` announcements,
|
|
29
|
+
`prefers-reduced-motion`, `prefers-contrast`, and `forced-colors` support.
|
|
30
|
+
- **Rendering** — `devicePixelRatio`-aware, offscreen static layer with 1:1 blit,
|
|
31
|
+
`ResizeObserver` auto-sizing, `rAF`-coalesced auto-draw.
|
|
32
|
+
- **DARK** / **LIGHT** colour presets.
|
|
33
|
+
- **PNG export** via `toImage()`.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
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
ADDED
|
@@ -0,0 +1,530 @@
|
|
|
1
|
+
# Goro Charts
|
|
2
|
+
|
|
3
|
+
[](https://github.com/stefanelloisaac/goro-charts/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/goro-charts)
|
|
5
|
+
[](https://bundlephobia.com/package/goro-charts)
|
|
6
|
+
[](./LICENSE)
|
|
7
|
+
|
|
8
|
+
**[Live demo →](https://stefanelloisaac.github.io/goro-charts/)**
|
|
9
|
+
|
|
10
|
+
Minimal high-performance chart engine. **Canvas 2D only. Zero runtime dependencies. Framework-agnostic.** Inspired by [uPlot](https://github.com/leeoniya/uPlot) — small, fast, and covers only what you need.
|
|
11
|
+
|
|
12
|
+
- **LineChart** — batched polyline with per-pixel-column min/max decimation
|
|
13
|
+
- **AreaChart** — filled region below the line with the same decimation path
|
|
14
|
+
- **ScatterChart** — stride-thinned scatter plot, one circle per sampled point
|
|
15
|
+
- **Multi-series** — one chart, many series, each with its own colour, width, fill, dash, and Y-axis
|
|
16
|
+
- **Dual Y-axis** — left and right domains, independent scales per series
|
|
17
|
+
- **Fixed Y range** — lock the grid with `yMin`/`yMax` (ideal for 0–100 % dashboards)
|
|
18
|
+
- Columnar `Float64Array` data per series (no object-per-point overhead)
|
|
19
|
+
- Streaming ring mode with O(1) append and O(1) sliding-window min/max via monotonic deques
|
|
20
|
+
- Dashed, boxed grid with a locked domain that expands but never shrinks — a real visual anchor
|
|
21
|
+
- `devicePixelRatio`-aware for sharp retina rendering
|
|
22
|
+
- Offscreen static layer — crosshair repaint is instant
|
|
23
|
+
- Built-in legend and multi-series crosshair with an interpolated tooltip card
|
|
24
|
+
- `ResizeObserver` auto-sizing, `rAF`-coalesced auto-draw
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install goro-charts
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
```ts
|
|
35
|
+
import { LineChart } from 'goro-charts';
|
|
36
|
+
|
|
37
|
+
const canvas = document.querySelector('canvas') as HTMLCanvasElement;
|
|
38
|
+
|
|
39
|
+
const chart = new LineChart(canvas, {
|
|
40
|
+
series: [
|
|
41
|
+
{ name: 'CPU', color: '#4ea8ff' },
|
|
42
|
+
{ name: 'Temp', color: '#ffb454', lineWidth: 1.2 },
|
|
43
|
+
],
|
|
44
|
+
maxPoints: 2000,
|
|
45
|
+
autoDraw: true,
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
chart.append(0, 0, 52); // CPU @ x=0
|
|
49
|
+
chart.append(1, 0, 38); // Temp @ x=0
|
|
50
|
+
|
|
51
|
+
chart.appendBatch(0, [1, 2, 3], [55, 58, 57]);
|
|
52
|
+
chart.appendBatch(1, [1, 2, 3], [39, 40, 39]);
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Series model
|
|
58
|
+
|
|
59
|
+
Every chart holds one or more series. Each series owns its visual identity:
|
|
60
|
+
|
|
61
|
+
```ts
|
|
62
|
+
import type { SeriesConfig } from 'goro-charts';
|
|
63
|
+
|
|
64
|
+
interface SeriesConfig {
|
|
65
|
+
/** Display name for the legend and crosshair tooltip. */
|
|
66
|
+
name: string;
|
|
67
|
+
/** Line stroke, crosshair dot, and legend swatch colour. */
|
|
68
|
+
color: string;
|
|
69
|
+
/** Line stroke width (AreaChart: top stroke width). Falls back to ChartOpts.lineWidth. */
|
|
70
|
+
lineWidth?: number;
|
|
71
|
+
/** Line dash pattern, e.g. [8, 4] for dashed lines. */
|
|
72
|
+
dash?: number[];
|
|
73
|
+
/** Area fill colour (meaningful only on AreaChart). */
|
|
74
|
+
fillColor?: string;
|
|
75
|
+
/** Area fill opacity 0–1 (meaningful only on AreaChart). */
|
|
76
|
+
fillOpacity?: number;
|
|
77
|
+
/** Which Y axis this series maps to. Default 'left'. */
|
|
78
|
+
yAxis?: 'left' | 'right';
|
|
79
|
+
/** Stack group id — series sharing one id render cumulatively (AreaChart). */
|
|
80
|
+
stack?: string;
|
|
81
|
+
/** Fixed Y lower bound for this series only (overrides the grid domain). */
|
|
82
|
+
yMin?: number;
|
|
83
|
+
/** Fixed Y upper bound for this series only (overrides the grid domain). */
|
|
84
|
+
yMax?: number;
|
|
85
|
+
}
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
When `series` is omitted from `ChartOpts` a single default series is created automatically.
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Chart types
|
|
93
|
+
|
|
94
|
+
### `LineChart`
|
|
95
|
+
|
|
96
|
+
```ts
|
|
97
|
+
new LineChart(canvas, opts?: ChartOpts)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Each series is drawn as a single batched polyline. When the dataset is dense (more than 2× the plot width in samples) the renderer auto-switches to per-pixel-column min/max decimation — the visual envelope of the signal — so 500k points render as ~2×width segments with no aliasing. Multiple series are drawn in config order, each with its own colour.
|
|
101
|
+
|
|
102
|
+
### `AreaChart`
|
|
103
|
+
|
|
104
|
+
```ts
|
|
105
|
+
new AreaChart(canvas, opts?: ChartOpts)
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Each series is drawn as a filled region below the line plus a top stroke. The area fill and the stroke are separate paths; only the visible top line is stroked (the bottom and side closure edges are never painted). `fillColor` and `fillOpacity` are per-series. Set `fillOpacity: 0` on a series to render it as a plain line overlay inside an area chart.
|
|
109
|
+
|
|
110
|
+
```ts
|
|
111
|
+
const chart = new AreaChart(canvas, {
|
|
112
|
+
series: [
|
|
113
|
+
{ name: 'Req/s', color: '#52d4a0', fillColor: '#52d4a0', fillOpacity: 0.08 },
|
|
114
|
+
{ name: 'Baseline', color: '#c792ff', lineWidth: 1, fillOpacity: 0 },
|
|
115
|
+
],
|
|
116
|
+
maxPoints: 2000,
|
|
117
|
+
autoDraw: true,
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
### `ScatterChart`
|
|
124
|
+
|
|
125
|
+
```ts
|
|
126
|
+
new ScatterChart(canvas, opts?: ChartOpts)
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Each series is drawn as filled circles, one per sampled point. When the dataset exceeds `maxDots` (default 2000) the renderer switches to **stride thinning** — it draws every `floor(n / maxDots)`-th point so the chart stays responsive at any data volume. Individual series can be dashed via `SeriesConfig.dash`.
|
|
130
|
+
|
|
131
|
+
```ts
|
|
132
|
+
const chart = new ScatterChart(canvas, {
|
|
133
|
+
series: [
|
|
134
|
+
{ name: 'Packets', color: '#f07167', pointRadius: 3.5 },
|
|
135
|
+
{ name: 'Errors', color: '#ffb454', dash: [6, 3] },
|
|
136
|
+
],
|
|
137
|
+
maxPoints: 5000,
|
|
138
|
+
autoDraw: true,
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## Data API
|
|
145
|
+
|
|
146
|
+
Every data method takes a **series index** as the first argument. `setMaxPoints()`, `clear()`, and `draw()` operate on all series.
|
|
147
|
+
|
|
148
|
+
| Method | Signature | Description |
|
|
149
|
+
| -------------- | ------------------------------------------------------------- | ------------------------------------------------------------------ |
|
|
150
|
+
| `setData` | `(seriesIndex, x: Float64Array, y: Float64Array)` | Snapshot: replace a series. O(n) extent. |
|
|
151
|
+
| `append` | `(seriesIndex, x: number, y: number)` | Ring: append one point. O(1) amortized. |
|
|
152
|
+
| `appendBatch` | `(seriesIndex, xs: ArrayLike<number>, ys: ArrayLike<number>)` | Ring: append a batch. O(k). |
|
|
153
|
+
| `setMaxPoints` | `(maxPoints: number)` | Resize the streaming window for all series. |
|
|
154
|
+
| `clear` | `()` | Empty all series and reset the grid domain. |
|
|
155
|
+
| `draw` | `()` | Manual paint. No-op when clean and no crosshair. |
|
|
156
|
+
| `suspendDraw` | `()` | Pause rAF-coalesced drawing. Nestable — pair with `resumeDraw()`. |
|
|
157
|
+
| `resumeDraw` | `()` | Resume after matching `suspendDraw()`. Draws immediately if dirty. |
|
|
158
|
+
| `toImage` | `()` | Export the canvas as a PNG data URL. |
|
|
159
|
+
| `destroy` | `()` | Detach observers, release buffers. |
|
|
160
|
+
|
|
161
|
+
### Read-only properties
|
|
162
|
+
|
|
163
|
+
| Property | Type | Description |
|
|
164
|
+
| ------------------- | -------- | ---------------------------------- |
|
|
165
|
+
| `seriesCount` | `number` | Number of configured series |
|
|
166
|
+
| `pointCount(index)` | `number` | Samples in the window for a series |
|
|
167
|
+
| `lastValue(index)` | `number` | Most recent y (NaN if empty) |
|
|
168
|
+
| `extentMin(index)` | `number` | Window y minimum (O(1)) |
|
|
169
|
+
| `extentMax(index)` | `number` | Window y maximum (O(1)) |
|
|
170
|
+
|
|
171
|
+
---
|
|
172
|
+
|
|
173
|
+
## Snapshot mode
|
|
174
|
+
|
|
175
|
+
Replace entire series at once. Can be mixed with ring mode on different series.
|
|
176
|
+
|
|
177
|
+
```ts
|
|
178
|
+
const x = new Float64Array([0, 1, 2, 3, 4]);
|
|
179
|
+
const cpu = new Float64Array([50, 53, 55, 52, 51]);
|
|
180
|
+
const temp = new Float64Array([36, 37, 38, 37, 36]);
|
|
181
|
+
|
|
182
|
+
chart.setData(0, x, cpu);
|
|
183
|
+
chart.setData(1, x, temp);
|
|
184
|
+
chart.draw();
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Streaming mode
|
|
190
|
+
|
|
191
|
+
Requires `maxPoints` at construction. Each series gets its own ring buffer. `setMaxPoints()` resizes all windows atomically. `autoDraw` coalesces rapid appends into a single `requestAnimationFrame` paint.
|
|
192
|
+
|
|
193
|
+
```ts
|
|
194
|
+
const chart = new LineChart(canvas, {
|
|
195
|
+
series: [{ name: 'CPU', color: '#4ea8ff' }],
|
|
196
|
+
maxPoints: 2000,
|
|
197
|
+
autoDraw: true,
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
let t = 0;
|
|
201
|
+
setInterval(() => {
|
|
202
|
+
chart.append(0, t, Math.random() * 100);
|
|
203
|
+
t++;
|
|
204
|
+
}, 1000);
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Options
|
|
210
|
+
|
|
211
|
+
### `ChartOpts` (constructor)
|
|
212
|
+
|
|
213
|
+
| Option | Default | Description |
|
|
214
|
+
| ---------------- | ------------------------------------------ | ------------------------------------------------------ |
|
|
215
|
+
| `series` | `[{ name: 'Series 0', color: '#4ea8ff' }]` | Array of per-series visual configs |
|
|
216
|
+
| `padding` | `[16, 16, 32, 56]` | `[top, right, bottom, left]` in CSS pixels |
|
|
217
|
+
| `gridColor` | `rgba(255,255,255,0.08)` | Dashed internal grid line colour |
|
|
218
|
+
| `axisColor` | `rgba(255,255,255,0.25)` | Grid frame stroke colour |
|
|
219
|
+
| `textColor` | `rgba(255,255,255,0.5)` | Tick labels, legend text, tooltip labels |
|
|
220
|
+
| `fontSize` | `11` | Base font size for all text |
|
|
221
|
+
| `fontFamily` | `system-ui, …` | Font stack for all text |
|
|
222
|
+
| `bgColor` | `'#111'` | Canvas background fill |
|
|
223
|
+
| `crosshairColor` | `rgba(255,255,255,0.3)` | Crosshair guide line colour |
|
|
224
|
+
| `crosshairWidth` | `1` | Crosshair guide line width |
|
|
225
|
+
| `pointRadius` | `4` | Crosshair marker dot radius |
|
|
226
|
+
| `xTicks` | `8` | Approximate X-axis tick count |
|
|
227
|
+
| `yTicks` | `6` | Approximate Y-axis tick count |
|
|
228
|
+
| `maxPoints` | `0` | Activate ring streaming mode (0 = off) |
|
|
229
|
+
| `autoDraw` | `false` | Coalesce data changes into one rAF draw |
|
|
230
|
+
| `yMin` | `0` | Fixed Y-axis lower bound (0 = auto). Pair with `yMax`. |
|
|
231
|
+
| `yMax` | `0` | Fixed Y-axis upper bound (0 = auto). Pair with `yMin`. |
|
|
232
|
+
| `maxDots` | `2000` | Max dots before scatter chart stride-thinning kicks in |
|
|
233
|
+
| `lineColor` | `#4ea8ff` | Fallback line colour |
|
|
234
|
+
| `lineWidth` | `1.5` | Fallback line width |
|
|
235
|
+
| `fillColor` | `#4ea8ff` | Fallback area fill |
|
|
236
|
+
| `fillOpacity` | `0.15` | Fallback area fill opacity |
|
|
237
|
+
| `pointColor` | `#4ea8ff` | Fallback crosshair dot colour |
|
|
238
|
+
|
|
239
|
+
### `SeriesConfig` (per series)
|
|
240
|
+
|
|
241
|
+
| Field | Required | Default | Description |
|
|
242
|
+
| ------------- | -------- | ----------------------- | -------------------------------------------------------- |
|
|
243
|
+
| `name` | yes | — | Legend and tooltip label |
|
|
244
|
+
| `color` | yes | — | Line, dot, and legend swatch colour |
|
|
245
|
+
| `lineWidth` | no | `ChartOpts.lineWidth` | Stroke width |
|
|
246
|
+
| `dash` | no | — | Dash pattern, e.g. `[8, 4]` for dashed lines |
|
|
247
|
+
| `fillColor` | no | `ChartOpts.fillColor` | Area fill colour |
|
|
248
|
+
| `fillOpacity` | no | `ChartOpts.fillOpacity` | Area fill opacity |
|
|
249
|
+
| `yAxis` | no | `'left'` | Which Y axis this series maps to (`'left'` \| `'right'`) |
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Grid & axes
|
|
254
|
+
|
|
255
|
+
The grid is intentionally stable — it does not recompute a tight Y range on every append. Instead the grid domain locks to the data extent on the first draw and only expands when the data exceeds it (with a 10 % margin). The grid **never shrinks**, keeping horizontal reference lines stationary so the eye tracks movement inside a stable frame.
|
|
256
|
+
|
|
257
|
+
The grid itself is a dashed internal lattice plus a closed rectangular frame drawn via `strokeRect`. The frame replaces old-style axis strokes — tick labels sit outside the frame with no extra axis lines.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## Crosshair
|
|
262
|
+
|
|
263
|
+
- A dashed vertical guide line is always drawn.
|
|
264
|
+
- A dashed horizontal guide appears only when exactly one series is visible.
|
|
265
|
+
- Marker dots sit at each series' interpolated position with a dark halo for readability over filled areas.
|
|
266
|
+
- Y values are linearly interpolated between the two samples bracketing the cursor — the readout slides smoothly, never jumping by whole samples.
|
|
267
|
+
|
|
268
|
+
The tooltip is a rounded card:
|
|
269
|
+
|
|
270
|
+
```
|
|
271
|
+
┌──────────────────────────┐
|
|
272
|
+
│ x 42.6│ header row
|
|
273
|
+
│ ──────────────────────── │ divider
|
|
274
|
+
│ ● CPU 67.5% │ series dot + name + value
|
|
275
|
+
│ ● Temp 48.2° │
|
|
276
|
+
└──────────────────────────┘
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
281
|
+
## Legend
|
|
282
|
+
|
|
283
|
+
Rendered automatically when two or more series are configured. Placed in the top-right corner of the plot. Laid out horizontally in a rounded pill; falls back to a vertical stack if the row would overflow the plot width.
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## Dual Y-axis
|
|
288
|
+
|
|
289
|
+
When a series declares `yAxis: 'right'` the chart maintains a separate Y domain for it, with tick labels rendered on the right side of the frame. Each series uses its own domain for scale mapping; the crosshair reads the correct axis per series.
|
|
290
|
+
|
|
291
|
+
```ts
|
|
292
|
+
const chart = new LineChart(canvas, {
|
|
293
|
+
series: [
|
|
294
|
+
{ name: 'Temp (°C)', color: '#ffb454', yAxis: 'left' },
|
|
295
|
+
{ name: 'Humidity (%)', color: '#4ea8ff', yAxis: 'right' },
|
|
296
|
+
],
|
|
297
|
+
maxPoints: 2000,
|
|
298
|
+
autoDraw: true,
|
|
299
|
+
});
|
|
300
|
+
```
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Stacked area
|
|
305
|
+
|
|
306
|
+
Series that share the same `stack` id render cumulatively: each layer's Y
|
|
307
|
+
values are added on top of the previous layer's accumulated Y within the
|
|
308
|
+
group, so the bands sit flush against each other. Meaningful only on an
|
|
309
|
+
`AreaChart`. The crosshair dots follow the accumulated band edges.
|
|
310
|
+
|
|
311
|
+
```ts
|
|
312
|
+
const chart = new AreaChart(canvas, {
|
|
313
|
+
series: [
|
|
314
|
+
{ name: 'System', color: '#4ea8ff', stack: 'cpu' },
|
|
315
|
+
{ name: 'User', color: '#52d4a0', stack: 'cpu' },
|
|
316
|
+
{ name: 'IO Wait', color: '#ffb454', stack: 'cpu' },
|
|
317
|
+
],
|
|
318
|
+
maxPoints: 2000,
|
|
319
|
+
autoDraw: true,
|
|
320
|
+
});
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
A `stack` group with fewer than two populated series falls back to a normal
|
|
324
|
+
(non-stacked) area render.
|
|
325
|
+
|
|
326
|
+
Like `LineChart` / `AreaChart`, stacked bands auto-switch to per-pixel-column
|
|
327
|
+
min/max decimation once a layer is denser than 2× the plot width, so large
|
|
328
|
+
windows (tens of thousands of points per series) stay cheap — the draw cost is
|
|
329
|
+
flat regardless of window size.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Presets
|
|
334
|
+
|
|
335
|
+
Pre-built `DARK` and `LIGHT` colour presets ready to spread over constructor options.
|
|
336
|
+
|
|
337
|
+
```ts
|
|
338
|
+
import { LineChart, DARK, LIGHT } from 'goro-charts'
|
|
339
|
+
|
|
340
|
+
// Dark (default) — explicit
|
|
341
|
+
new LineChart(canvas, { ...DARK, series: [...] })
|
|
342
|
+
|
|
343
|
+
// Light theme
|
|
344
|
+
new LineChart(canvas, { ...LIGHT, series: [...] })
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
---
|
|
348
|
+
|
|
349
|
+
## Bulk loading
|
|
350
|
+
|
|
351
|
+
`suspendDraw()` pauses the rAF-coalesced draw scheduler. Pair it with `resumeDraw()` to batch many append / setData calls without intermediate paints. Nestable — pause N times, resume N times.
|
|
352
|
+
|
|
353
|
+
```ts
|
|
354
|
+
chart.suspendDraw();
|
|
355
|
+
for (const batch of batches) {
|
|
356
|
+
chart.appendBatch(0, batch.x, batch.y);
|
|
357
|
+
}
|
|
358
|
+
chart.resumeDraw(); // one draw, then normal scheduling resumes
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
---
|
|
362
|
+
|
|
363
|
+
## Fixed Y range
|
|
364
|
+
|
|
365
|
+
Set `yMin` and `yMax` to pin the grid domain to a known range. The grid won't auto-expand — ideal for dashboards where the scale is fixed (e.g. always 0–100 %).
|
|
366
|
+
|
|
367
|
+
```ts
|
|
368
|
+
new LineChart(canvas, {
|
|
369
|
+
yMin: 0,
|
|
370
|
+
yMax: 100,
|
|
371
|
+
series: [{ name: 'CPU', color: '#4ea8ff' }],
|
|
372
|
+
});
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
When both are `0` (the default) the grid domain expands automatically from data.
|
|
376
|
+
|
|
377
|
+
---
|
|
378
|
+
|
|
379
|
+
## Chart sync
|
|
380
|
+
|
|
381
|
+
Bidirectionally sync the crosshair between two or more charts. When the mouse
|
|
382
|
+
moves on one chart, the crosshair guide, marker dots, and tooltip card appear on
|
|
383
|
+
all synced charts at the matching x coordinate — ideal for multi-panel dashboards
|
|
384
|
+
where you want to compare the same time window across views.
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
chart1.sync(chart2);
|
|
388
|
+
chart1.sync(chart3);
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
Calling `sync()` pairs charts in both directions — there is no master/slave
|
|
392
|
+
relationship. The crosshair position on one chart is mirrored on every synced
|
|
393
|
+
target.
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Custom tooltips (DOM)
|
|
398
|
+
|
|
399
|
+
The `onHover` callback fires on every `mousemove` with the interpolated data
|
|
400
|
+
for every visible series at the cursor position. Use it to build DOM-based
|
|
401
|
+
tooltips, update framework state, or pipe values into external widgets.
|
|
402
|
+
|
|
403
|
+
```ts
|
|
404
|
+
chart.onHover = (hits) => {
|
|
405
|
+
myTooltipEl.innerHTML = hits
|
|
406
|
+
.map((h) => `${h.label}: ${h.yVal.toFixed(1)}`)
|
|
407
|
+
.join('<br>');
|
|
408
|
+
};
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
Each hit contains the series name, colour, interpolated (x, y) value, and the
|
|
412
|
+
pixel position. See the `SeriesHit` type for the full shape.
|
|
413
|
+
|
|
414
|
+
---
|
|
415
|
+
|
|
416
|
+
## Export (PNG)
|
|
417
|
+
|
|
418
|
+
Call `toImage()` to export the current canvas as a PNG data URL. Useful for
|
|
419
|
+
reports, screenshots, or image-generation pipelines.
|
|
420
|
+
|
|
421
|
+
```ts
|
|
422
|
+
const url = chart.toImage();
|
|
423
|
+
// e.g. <img src="..."> or download link
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
---
|
|
427
|
+
|
|
428
|
+
## Accessibility
|
|
429
|
+
|
|
430
|
+
Goro Charts renders to Canvas 2D, which is not natively accessible to screen
|
|
431
|
+
readers. The library compensates with several built-in features:
|
|
432
|
+
|
|
433
|
+
- **`role="img"`** + dynamic **`aria-label`** on the canvas element, updated
|
|
434
|
+
each draw with a summary of visible series values.
|
|
435
|
+
- **Keyboard navigation** — when the canvas is focused (Tab key), use:
|
|
436
|
+
- ← / → to move the crosshair 1 data point
|
|
437
|
+
- Shift + ← / → to move 10 data points
|
|
438
|
+
- Escape to hide the crosshair
|
|
439
|
+
- **`aria-live` region** — crosshair values are announced to screen readers
|
|
440
|
+
via a hidden `aria-live="polite"` element inserted next to the canvas.
|
|
441
|
+
- **`prefers-reduced-motion`** — when the user's system preference is set,
|
|
442
|
+
the chart disables `requestAnimationFrame` coalescing and draws synchronously.
|
|
443
|
+
- **`prefers-contrast: more`** — grid and text colours are boosted for
|
|
444
|
+
readability when the user requests higher contrast.
|
|
445
|
+
- **`forced-colors: active`** — the chart uses CSS system colours (`Canvas`,
|
|
446
|
+
`CanvasText`, `GrayText`) when Windows High Contrast Mode is active.
|
|
447
|
+
|
|
448
|
+
The canvas element receives `tabindex="0"` so it can receive keyboard focus.
|
|
449
|
+
For optimal accessibility, pair the chart with a data table fallback rendered
|
|
450
|
+
outside the canvas.
|
|
451
|
+
|
|
452
|
+
---
|
|
453
|
+
|
|
454
|
+
## Troubleshooting
|
|
455
|
+
|
|
456
|
+
### Canvas is blank / black
|
|
457
|
+
- Ensure the canvas element has non-zero CSS width and height.
|
|
458
|
+
- Check that `chart.draw()` is being called after `setData()` (or enable
|
|
459
|
+
`autoDraw` for streaming mode).
|
|
460
|
+
- If using `ResizeObserver` in a container with `display: none`, the canvas
|
|
461
|
+
may have zero dimensions. Call `chart.draw()` after the container becomes
|
|
462
|
+
visible.
|
|
463
|
+
|
|
464
|
+
### "Canvas 2D context not available"
|
|
465
|
+
- This error means `canvas.getContext('2d')` returned `null`. Common causes:
|
|
466
|
+
- The canvas element does not exist in the DOM at construction time.
|
|
467
|
+
- The canvas was already claimed by a WebGL context.
|
|
468
|
+
- You are running in a server-side environment (Node.js without `node-canvas`).
|
|
469
|
+
|
|
470
|
+
### "append() requires the chart to be created with { maxPoints }"
|
|
471
|
+
- Streaming methods (`append`, `appendBatch`) require ring mode, which is
|
|
472
|
+
activated by passing `maxPoints` (a positive number) to the constructor.
|
|
473
|
+
- Use `setData()` for snapshot mode (full replacement).
|
|
474
|
+
|
|
475
|
+
### Crosshair does not appear
|
|
476
|
+
- The crosshair only shows when the mouse is inside the plot area (the inner
|
|
477
|
+
rectangle after padding is applied).
|
|
478
|
+
- Check that `onHover` or mouse events are not being consumed by an overlay
|
|
479
|
+
element above the canvas.
|
|
480
|
+
|
|
481
|
+
### Performance is slow with many points
|
|
482
|
+
- Verify that decimation is working: the line/area renderers auto-switch to
|
|
483
|
+
per-pixel-column decimation when `count > 2 × plotWidth`.
|
|
484
|
+
- For scatter charts, reduce `maxDots` (default 2000) or set it to a lower
|
|
485
|
+
value to increase stride thinning.
|
|
486
|
+
- Use `suspendDraw()` / `resumeDraw()` when batch-loading large datasets.
|
|
487
|
+
|
|
488
|
+
---
|
|
489
|
+
|
|
490
|
+
## Architecture
|
|
491
|
+
|
|
492
|
+
```
|
|
493
|
+
src/
|
|
494
|
+
index.ts Public barrel
|
|
495
|
+
types.ts ChartOpts, SeriesConfig, PlotRect, Domain, SeriesView
|
|
496
|
+
defaults.ts Baseline options
|
|
497
|
+
presets.ts DARK / LIGHT colour presets
|
|
498
|
+
|
|
499
|
+
charts/
|
|
500
|
+
chart-base.ts Abstract orchestrator (multi-store, locked grid, dirty/rAF, interaction)
|
|
501
|
+
line-chart.ts LineChart — delegates to renderLine
|
|
502
|
+
area-chart.ts AreaChart — delegates to renderArea
|
|
503
|
+
scatter-chart.ts ScatterChart — delegates to renderScatter
|
|
504
|
+
|
|
505
|
+
data/
|
|
506
|
+
monotonic-extent.ts O(1) sliding min/max via dual monotonic deques
|
|
507
|
+
ring-buffer.ts O(1) append ring, no memmove
|
|
508
|
+
series-store.ts Unified snapshot + ring data store
|
|
509
|
+
|
|
510
|
+
render/
|
|
511
|
+
axes.ts Dashed grid + tick labels, dual-Y support
|
|
512
|
+
line.ts Batched line with per-pixel decimation
|
|
513
|
+
area.ts Batched area fill + stroke (same decimation, separate paths)
|
|
514
|
+
scatter.ts Stride-thinned scatter plot
|
|
515
|
+
crosshair.ts Multi-series interpolated crosshair + tooltip card
|
|
516
|
+
legend.ts Horizontal compact legend pill
|
|
517
|
+
shape.ts Canvas path helper (roundedRect)
|
|
518
|
+
surface.ts DPR, offscreen buffer, 1:1 blit
|
|
519
|
+
|
|
520
|
+
math/
|
|
521
|
+
scale.ts Data ↔ pixel transforms
|
|
522
|
+
ticks.ts Nice tick generation (1, 2, 5 × 10ⁿ)
|
|
523
|
+
format.ts Numeric label formatting
|
|
524
|
+
```
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## License
|
|
529
|
+
|
|
530
|
+
MIT
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file Area chart — filled region below the series line.
|
|
3
|
+
*
|
|
4
|
+
* Same batched path + decimation as {@link LineChart}, but the path is closed
|
|
5
|
+
* to the plot bottom so a single `fill()` paints the area, with the stroke
|
|
6
|
+
* line drawn on top for readability even at low fill opacity.
|
|
7
|
+
*/
|
|
8
|
+
import { ChartBase } from './chart-base.js';
|
|
9
|
+
import type { SeriesView, PlotRect, ResolvedOpts } from '../types.js';
|
|
10
|
+
/** High-performance Canvas 2D area chart. */
|
|
11
|
+
export declare class AreaChart extends ChartBase {
|
|
12
|
+
protected renderSeries(ctx: CanvasRenderingContext2D, view: SeriesView, plot: PlotRect, opts: ResolvedOpts): void;
|
|
13
|
+
}
|