@dschz/solid-uplot 0.1.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/LICENSE +21 -0
- package/README.md +771 -0
- package/dist/chunk/3TJS44N7.js +15 -0
- package/dist/chunk/A3AZKFSW.jsx +27 -0
- package/dist/chunk/ZISGD6FJ.js +25 -0
- package/dist/createPluginBus-B_Gp5BCB.d.ts +21 -0
- package/dist/getCursorData-2qxURGuZ.d.ts +44 -0
- package/dist/getSeriesData-D1zBqQ9Y.d.ts +37 -0
- package/dist/index/index.d.ts +44 -0
- package/dist/index/index.js +117 -0
- package/dist/index/index.jsx +135 -0
- package/dist/plugins/index.d.ts +443 -0
- package/dist/plugins/index.js +342 -0
- package/dist/plugins/index.jsx +347 -0
- package/dist/utils/index.d.ts +24 -0
- package/dist/utils/index.js +26 -0
- package/package.json +116 -0
package/README.md
ADDED
|
@@ -0,0 +1,771 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="https://assets.solidjs.com/banner?project=solid-uplot&type=Ecosystem&background=tiles" alt="@dschz/solid-uplot banner" />
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
# @dschz/solid-uplot
|
|
6
|
+
|
|
7
|
+
[](https://www.solidjs.com)
|
|
8
|
+
[](https://github.com/leeoniya/uPlot)
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[](https://www.typescriptlang.org)
|
|
11
|
+
[](https://bundlephobia.com/package/@dschz/solid-uplot)
|
|
12
|
+
[](https://www.npmjs.com/package/@dschz/solid-uplot)
|
|
13
|
+
|
|
14
|
+
> 💹 SolidJS wrapper for [uPlot](https://github.com/leeoniya/uPlot) — an ultra-fast, small footprint charting library for time-series data.
|
|
15
|
+
|
|
16
|
+
## ✨ Features
|
|
17
|
+
|
|
18
|
+
- ✅ Fully reactive SolidJS wrapper around uPlot
|
|
19
|
+
- 🔌 Plugin system support with inter-plugin communication
|
|
20
|
+
- 🎯 Fine-grained control over chart lifecycle
|
|
21
|
+
- 💡 Lightweight and fast
|
|
22
|
+
- 💻 TypeScript support out of the box
|
|
23
|
+
- 🎨 Built-in plugins for tooltips, legends, cursor tracking, and series focusing
|
|
24
|
+
- 📱 Responsive sizing support with auto-resize capabilities
|
|
25
|
+
|
|
26
|
+
## 📦 Installation
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
npm install solid-js uplot @dschz/solid-uplot
|
|
30
|
+
pnpm install solid-js uplot @dschz/solid-uplot
|
|
31
|
+
yarn install solid-js uplot @dschz/solid-uplot
|
|
32
|
+
bun install solid-js uplot @dschz/solid-uplot
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 📁 Package Structure
|
|
36
|
+
|
|
37
|
+
This package provides three main export paths for different functionality:
|
|
38
|
+
|
|
39
|
+
### `@dschz/solid-uplot`
|
|
40
|
+
|
|
41
|
+
Core components and plugin system:
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
44
|
+
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
|
|
45
|
+
import type { PluginFactory, SolidUplotPluginBus } from "@dschz/solid-uplot";
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### `@dschz/solid-uplot/plugins`
|
|
49
|
+
|
|
50
|
+
This export path provides four plugins (three of which can be considered primitives).
|
|
51
|
+
|
|
52
|
+
- `cursor`: transmits cursor position data
|
|
53
|
+
- `focusSeries`: transmits which series are visually emphasized
|
|
54
|
+
- `tooltip`: plugin that allows you to present a custom JSX tooltip around the cursor
|
|
55
|
+
- `legend`: plugin that allows you to present a custom JSX component as your legend over the canvas drawing area.
|
|
56
|
+
|
|
57
|
+
```tsx
|
|
58
|
+
import { cursor, tooltip, legend, focusSeries } from "@dschz/solid-uplot/plugins";
|
|
59
|
+
import type {
|
|
60
|
+
CursorPluginMessageBus,
|
|
61
|
+
FocusSeriesPluginMessageBus,
|
|
62
|
+
TooltipProps,
|
|
63
|
+
LegendProps,
|
|
64
|
+
} from "@dschz/solid-uplot/plugins";
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### `@dschz/solid-uplot/utils`
|
|
68
|
+
|
|
69
|
+
Some convenience utility functions for getting certain bits of data from a `uPlot` instance (except for `getColorString` which translates a series' `stroke` or `fill` into a color value).
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
import {
|
|
73
|
+
getSeriesData,
|
|
74
|
+
getCursorData,
|
|
75
|
+
getColorString,
|
|
76
|
+
getNewCalendarDayIndices,
|
|
77
|
+
} from "@dschz/solid-uplot/utils";
|
|
78
|
+
import type { SeriesDatum, CursorData } from "@dschz/solid-uplot/utils";
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## 🚀 Quick Start
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
|
|
85
|
+
import { cursor, tooltip, legend } from "@dschz/solid-uplot/plugins";
|
|
86
|
+
import type { CursorPluginMessageBus, TooltipProps, LegendProps } from "@dschz/solid-uplot/plugins";
|
|
87
|
+
|
|
88
|
+
// Create a tooltip component
|
|
89
|
+
const MyTooltip = (props: TooltipProps) => (
|
|
90
|
+
<div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
|
|
91
|
+
<div>X: {props.cursor.xValue}</div>
|
|
92
|
+
<For each={props.seriesData}>
|
|
93
|
+
{(series) => {
|
|
94
|
+
const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
|
|
95
|
+
return (
|
|
96
|
+
<div>
|
|
97
|
+
{series.label}: {value}
|
|
98
|
+
</div>
|
|
99
|
+
);
|
|
100
|
+
}}
|
|
101
|
+
</For>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
// Create a legend component
|
|
106
|
+
const MyLegend = (props: LegendProps) => (
|
|
107
|
+
<div style={{ background: "rgba(255,255,255,0.9)", padding: "8px" }}>
|
|
108
|
+
<For each={props.seriesData}>
|
|
109
|
+
{(series) => (
|
|
110
|
+
<div style={{ display: "flex", "align-items": "center", gap: "4px" }}>
|
|
111
|
+
<div
|
|
112
|
+
style={{
|
|
113
|
+
width: "12px",
|
|
114
|
+
height: "12px",
|
|
115
|
+
background: series.stroke,
|
|
116
|
+
}}
|
|
117
|
+
/>
|
|
118
|
+
<span>{series.label}</span>
|
|
119
|
+
</div>
|
|
120
|
+
)}
|
|
121
|
+
</For>
|
|
122
|
+
</div>
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
const MyChart = () => {
|
|
126
|
+
const bus = createPluginBus<CursorPluginMessageBus>();
|
|
127
|
+
|
|
128
|
+
return (
|
|
129
|
+
<SolidUplot
|
|
130
|
+
data={[
|
|
131
|
+
[0, 1, 2, 3], // x values
|
|
132
|
+
[10, 20, 30, 40], // y values for series 1
|
|
133
|
+
[15, 25, 35, 45], // y values for series 2
|
|
134
|
+
]}
|
|
135
|
+
width={600}
|
|
136
|
+
height={400}
|
|
137
|
+
series={[
|
|
138
|
+
{},
|
|
139
|
+
{ label: "Series 1", stroke: "#1f77b4" },
|
|
140
|
+
{ label: "Series 2", stroke: "#ff7f0e" },
|
|
141
|
+
]}
|
|
142
|
+
plugins={[
|
|
143
|
+
cursor(),
|
|
144
|
+
tooltip(MyTooltip),
|
|
145
|
+
legend(MyLegend, { placement: "top-right", pxOffset: 12 }),
|
|
146
|
+
]}
|
|
147
|
+
pluginBus={bus}
|
|
148
|
+
/>
|
|
149
|
+
);
|
|
150
|
+
};
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
## 📏 Responsive Sizing
|
|
154
|
+
|
|
155
|
+
For responsive charts that automatically adapt to container size changes, use the `autoResize` prop:
|
|
156
|
+
|
|
157
|
+
```tsx
|
|
158
|
+
<div style={{ width: "100%", height: "400px" }}>
|
|
159
|
+
<SolidUplot
|
|
160
|
+
autoResize={true}
|
|
161
|
+
data={data}
|
|
162
|
+
series={series}
|
|
163
|
+
// Chart will automatically resize to fill the container
|
|
164
|
+
/>
|
|
165
|
+
</div>
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
For more advanced responsive patterns, you can pair this library with [@dschz/solid-auto-sizer](https://github.com/dsnchz/solid-auto-sizer):
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm install @dschz/solid-auto-sizer
|
|
172
|
+
pnpm install @dschz/solid-auto-sizer
|
|
173
|
+
yarn install @dschz/solid-auto-sizer
|
|
174
|
+
bun install @dschz/solid-auto-sizer
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
```tsx
|
|
178
|
+
import { AutoSizer } from "@dschz/solid-auto-sizer";
|
|
179
|
+
|
|
180
|
+
<AutoSizer>
|
|
181
|
+
{({ width, height }) => <SolidUplot width={width} height={height} data={data} />}
|
|
182
|
+
</AutoSizer>;
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
## 🔌 Enhanced Plugin System
|
|
186
|
+
|
|
187
|
+
The cornerstone feature of `SolidUplot` is its refined plugin system that enables extensible functionality and inter-plugin communication through a reactive message bus.
|
|
188
|
+
|
|
189
|
+
### Plugin Bus Architecture
|
|
190
|
+
|
|
191
|
+
The Plugin Bus System enables plugins to communicate with each other and external components through a reactive store. This architecture provides:
|
|
192
|
+
|
|
193
|
+
- **Type-safe communication**: All plugin messages are fully typed
|
|
194
|
+
- **Reactive updates**: Changes in plugin state automatically trigger updates
|
|
195
|
+
- **Decoupled components**: Plugins can interact without direct dependencies
|
|
196
|
+
- **Extensible**: Easy to add new plugins that integrate with existing ones
|
|
197
|
+
|
|
198
|
+
### Built-in Plugins
|
|
199
|
+
|
|
200
|
+
#### Cursor Plugin
|
|
201
|
+
|
|
202
|
+
Tracks cursor position and interaction state across charts:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
import { cursor } from "@dschz/solid-uplot/plugins";
|
|
206
|
+
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";
|
|
207
|
+
|
|
208
|
+
const cursorPlugin = cursor();
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
The cursor plugin provides cursor position data that other plugins can consume through the bus.
|
|
212
|
+
|
|
213
|
+
#### Focus Series Plugin
|
|
214
|
+
|
|
215
|
+
Highlights series based on cursor proximity:
|
|
216
|
+
|
|
217
|
+
```tsx
|
|
218
|
+
import { focusSeries } from "@dschz/solid-uplot/plugins";
|
|
219
|
+
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";
|
|
220
|
+
|
|
221
|
+
const focusPlugin = focusSeries({
|
|
222
|
+
pxThreshold: 15, // Distance threshold for focusing (default: 15)
|
|
223
|
+
});
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
#### Tooltip Plugin
|
|
227
|
+
|
|
228
|
+
Renders custom tooltips with automatic positioning and overflow handling:
|
|
229
|
+
|
|
230
|
+
```tsx
|
|
231
|
+
import { tooltip } from "@dschz/solid-uplot/plugins";
|
|
232
|
+
import type { TooltipProps } from "@dschz/solid-uplot/plugins";
|
|
233
|
+
|
|
234
|
+
const MyTooltip: Component<TooltipProps> = (props) => {
|
|
235
|
+
return (
|
|
236
|
+
<div
|
|
237
|
+
style={{
|
|
238
|
+
background: "white",
|
|
239
|
+
border: "1px solid #ccc",
|
|
240
|
+
padding: "8px",
|
|
241
|
+
"border-radius": "4px",
|
|
242
|
+
"box-shadow": "0 2px 4px rgba(0,0,0,0.1)",
|
|
243
|
+
}}
|
|
244
|
+
>
|
|
245
|
+
<div style={{ "font-weight": "bold", "margin-bottom": "8px" }}>X: {props.cursor.xValue}</div>
|
|
246
|
+
<For each={props.seriesData}>
|
|
247
|
+
{(series) => {
|
|
248
|
+
const value = () => props.u.data[series.seriesIdx]?.[props.cursor.idx];
|
|
249
|
+
return (
|
|
250
|
+
<Show when={series.visible}>
|
|
251
|
+
<div style={{ display: "flex", "align-items": "center", "margin-bottom": "4px" }}>
|
|
252
|
+
<div
|
|
253
|
+
style={{
|
|
254
|
+
width: "10px",
|
|
255
|
+
height: "10px",
|
|
256
|
+
"border-radius": "50%",
|
|
257
|
+
"background-color": series.stroke,
|
|
258
|
+
"margin-right": "6px",
|
|
259
|
+
}}
|
|
260
|
+
/>
|
|
261
|
+
<span style={{ color: series.stroke }}>
|
|
262
|
+
{series.label}: {value()?.toFixed(2)}
|
|
263
|
+
</span>
|
|
264
|
+
</div>
|
|
265
|
+
</Show>
|
|
266
|
+
);
|
|
267
|
+
}}
|
|
268
|
+
</For>
|
|
269
|
+
</div>
|
|
270
|
+
);
|
|
271
|
+
};
|
|
272
|
+
|
|
273
|
+
const tooltipPlugin = tooltip(MyTooltip, {
|
|
274
|
+
placement: "top-left", // "top-left" | "top-right" | "bottom-left" | "bottom-right"
|
|
275
|
+
zIndex: 20,
|
|
276
|
+
});
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
#### Legend Plugin
|
|
280
|
+
|
|
281
|
+
Adds customizable legends with smart positioning and interactive features:
|
|
282
|
+
|
|
283
|
+
```tsx
|
|
284
|
+
import { legend } from "@dschz/solid-uplot/plugins";
|
|
285
|
+
import type { LegendProps } from "@dschz/solid-uplot/plugins";
|
|
286
|
+
|
|
287
|
+
const MyLegend: Component<LegendProps> = (props) => {
|
|
288
|
+
// Access cursor data for interactive features
|
|
289
|
+
const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;
|
|
290
|
+
|
|
291
|
+
return (
|
|
292
|
+
<div
|
|
293
|
+
style={{
|
|
294
|
+
background: "white",
|
|
295
|
+
border: "1px solid #ddd",
|
|
296
|
+
"border-radius": "4px",
|
|
297
|
+
padding: "8px",
|
|
298
|
+
"box-shadow": "0 2px 4px rgba(0,0,0,0.1)",
|
|
299
|
+
// Dim when tooltip is active
|
|
300
|
+
opacity: cursorVisible() ? 0.6 : 1,
|
|
301
|
+
transition: "opacity 200ms",
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
<div style={{ "font-weight": "bold", "margin-bottom": "8px" }}>Legend</div>
|
|
305
|
+
<For each={props.seriesData}>
|
|
306
|
+
{(series) => (
|
|
307
|
+
<Show when={series.visible}>
|
|
308
|
+
<div
|
|
309
|
+
style={{
|
|
310
|
+
display: "flex",
|
|
311
|
+
"align-items": "center",
|
|
312
|
+
gap: "6px",
|
|
313
|
+
"margin-bottom": "4px",
|
|
314
|
+
}}
|
|
315
|
+
>
|
|
316
|
+
<div
|
|
317
|
+
style={{
|
|
318
|
+
width: "12px",
|
|
319
|
+
height: "12px",
|
|
320
|
+
"background-color": series.stroke,
|
|
321
|
+
"border-radius": "2px",
|
|
322
|
+
}}
|
|
323
|
+
/>
|
|
324
|
+
<span style={{ "font-size": "14px" }}>{series.label}</span>
|
|
325
|
+
</div>
|
|
326
|
+
</Show>
|
|
327
|
+
)}
|
|
328
|
+
</For>
|
|
329
|
+
</div>
|
|
330
|
+
);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
const legendPlugin = legend(MyLegend, {
|
|
334
|
+
placement: "top-left", // "top-left" | "top-right"
|
|
335
|
+
pxOffset: 8, // Distance from chart edges (default: 8)
|
|
336
|
+
zIndex: 10,
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
**Legend Plugin Features:**
|
|
341
|
+
|
|
342
|
+
- **Simple positioning**: Only top-left or top-right corners to avoid axis conflicts
|
|
343
|
+
- **Size-constrained**: Legend cannot exceed chart drawing area dimensions
|
|
344
|
+
- **Layout-agnostic**: You control internal layout and styling
|
|
345
|
+
- **Non-interfering**: Designed to work harmoniously with chart interactions
|
|
346
|
+
- **Plugin bus integration**: Access cursor and focus data for smart interactions
|
|
347
|
+
- **Automatic cleanup**: Proper memory management and DOM cleanup
|
|
348
|
+
|
|
349
|
+
### Plugin Bus Type Safety
|
|
350
|
+
|
|
351
|
+
When using multiple plugins, ensure type safety by properly typing the plugin bus:
|
|
352
|
+
|
|
353
|
+
```tsx
|
|
354
|
+
import { createPluginBus } from "@dschz/solid-uplot";
|
|
355
|
+
import type {
|
|
356
|
+
CursorPluginMessageBus,
|
|
357
|
+
FocusSeriesPluginMessageBus,
|
|
358
|
+
} from "@dschz/solid-uplot/plugins";
|
|
359
|
+
|
|
360
|
+
// Create a bus that includes all plugin message types
|
|
361
|
+
const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();
|
|
362
|
+
|
|
363
|
+
const MyChart = () => {
|
|
364
|
+
return (
|
|
365
|
+
<SolidUplot
|
|
366
|
+
data={data}
|
|
367
|
+
pluginBus={bus}
|
|
368
|
+
plugins={[cursor(), focusSeries(), tooltip(MyTooltip), legend(MyLegend)]}
|
|
369
|
+
/>
|
|
370
|
+
);
|
|
371
|
+
};
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
### Creating Custom Plugins
|
|
375
|
+
|
|
376
|
+
The plugin system is open to extension. When authoring plugins for public consumption, follow this pattern:
|
|
377
|
+
|
|
378
|
+
```tsx
|
|
379
|
+
import type { PluginFactory } from "@dschz/solid-uplot";
|
|
380
|
+
import type { CursorPluginMessageBus } from "@dschz/solid-uplot/plugins";
|
|
381
|
+
|
|
382
|
+
// 1. Define your plugin's message type
|
|
383
|
+
export type MyPluginMessage = {
|
|
384
|
+
value: number;
|
|
385
|
+
timestamp: number;
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
// 2. Define your plugin's message bus
|
|
389
|
+
export type MyPluginMessageBus = {
|
|
390
|
+
myPlugin?: MyPluginMessage;
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
// 3. Export your plugin factory
|
|
394
|
+
export const myPlugin = (
|
|
395
|
+
options = {},
|
|
396
|
+
): PluginFactory<CursorPluginMessageBus & MyPluginMessageBus> => {
|
|
397
|
+
return ({ bus }) => {
|
|
398
|
+
if (!bus) {
|
|
399
|
+
console.warn("[my-plugin]: A plugin bus is required");
|
|
400
|
+
return { hooks: {} };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
return {
|
|
404
|
+
hooks: {
|
|
405
|
+
ready: (u) => {
|
|
406
|
+
// Initialize plugin state
|
|
407
|
+
bus.setData("myPlugin", {
|
|
408
|
+
value: 0,
|
|
409
|
+
timestamp: Date.now(),
|
|
410
|
+
});
|
|
411
|
+
},
|
|
412
|
+
setData: (u) => {
|
|
413
|
+
// Update plugin state
|
|
414
|
+
bus.setData("myPlugin", "value", (prev) => prev + 1);
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
};
|
|
418
|
+
};
|
|
419
|
+
};
|
|
420
|
+
```
|
|
421
|
+
|
|
422
|
+
### External Component Integration
|
|
423
|
+
|
|
424
|
+
The plugin bus enables powerful integrations between charts and external components:
|
|
425
|
+
|
|
426
|
+
```tsx
|
|
427
|
+
import { createPluginBus } from "@dschz/solid-uplot";
|
|
428
|
+
import type { FocusSeriesPluginMessageBus } from "@dschz/solid-uplot/plugins";
|
|
429
|
+
|
|
430
|
+
const bus = createPluginBus<FocusSeriesPluginMessageBus>();
|
|
431
|
+
|
|
432
|
+
// External component that can trigger series focus
|
|
433
|
+
const DataGrid = (props: { bus: typeof bus }) => {
|
|
434
|
+
const handleRowHover = (seriesLabel: string) => {
|
|
435
|
+
props.bus.setData("focusSeries", {
|
|
436
|
+
sourceId: "data-grid",
|
|
437
|
+
targets: [{ label: seriesLabel }],
|
|
438
|
+
});
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
return <table>{/* Grid implementation */}</table>;
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
// Chart and grid interact through shared bus
|
|
445
|
+
const MyDashboard = () => {
|
|
446
|
+
return (
|
|
447
|
+
<div>
|
|
448
|
+
<DataGrid bus={bus} />
|
|
449
|
+
<SolidUplot data={data} pluginBus={bus} plugins={[focusSeries()]} />
|
|
450
|
+
</div>
|
|
451
|
+
);
|
|
452
|
+
};
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
## 🔧 API Reference
|
|
456
|
+
|
|
457
|
+
### SolidUplot Component
|
|
458
|
+
|
|
459
|
+
```tsx
|
|
460
|
+
type SolidUplotProps<T extends VoidStruct = VoidStruct> = {
|
|
461
|
+
// Chart data (required)
|
|
462
|
+
data: uPlot.AlignedData;
|
|
463
|
+
|
|
464
|
+
// Chart dimensions
|
|
465
|
+
width?: number; // default: 600
|
|
466
|
+
height?: number; // default: 300
|
|
467
|
+
|
|
468
|
+
// Responsive sizing
|
|
469
|
+
autoResize?: boolean; // default: false
|
|
470
|
+
|
|
471
|
+
// Plugin configuration
|
|
472
|
+
plugins?: SolidUplotPlugin<T>[];
|
|
473
|
+
pluginBus?: SolidUplotPluginBus<T>;
|
|
474
|
+
|
|
475
|
+
// Chart options (all uPlot.Options except plugins, width, height)
|
|
476
|
+
series?: uPlot.Series[];
|
|
477
|
+
scales?: uPlot.Scales;
|
|
478
|
+
axes?: uPlot.Axis[];
|
|
479
|
+
// ... other uPlot options
|
|
480
|
+
|
|
481
|
+
// Chart behavior
|
|
482
|
+
resetScales?: boolean; // default: true
|
|
483
|
+
|
|
484
|
+
// Callbacks
|
|
485
|
+
onCreate?: (u: uPlot, meta: { seriesData: SeriesDatum[] }) => void;
|
|
486
|
+
|
|
487
|
+
// Container styling
|
|
488
|
+
style?: JSX.CSSProperties;
|
|
489
|
+
class?: string;
|
|
490
|
+
id?: string;
|
|
491
|
+
ref?: (el: HTMLDivElement) => void;
|
|
492
|
+
|
|
493
|
+
// Children placement
|
|
494
|
+
childrenPlacement?: "top" | "bottom"; // default: "top"
|
|
495
|
+
};
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### Plugin Bus
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
type SolidUplotPluginBus<T extends VoidStruct = VoidStruct> = {
|
|
502
|
+
data: T;
|
|
503
|
+
setData: <K extends keyof T>(key: K, value: T[K]) => void;
|
|
504
|
+
setData: <K extends keyof T, P extends keyof T[K]>(
|
|
505
|
+
key: K,
|
|
506
|
+
path: P,
|
|
507
|
+
value: T[K][P]
|
|
508
|
+
) => void;
|
|
509
|
+
};
|
|
510
|
+
|
|
511
|
+
// Create a plugin bus
|
|
512
|
+
const createPluginBus = <T extends VoidStruct = VoidStruct>(
|
|
513
|
+
initialData?: Partial<T>
|
|
514
|
+
): SolidUplotPluginBus<T>;
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
### Built-in Plugin Options
|
|
518
|
+
|
|
519
|
+
```tsx
|
|
520
|
+
// Cursor Plugin
|
|
521
|
+
const cursor = (): PluginFactory<CursorPluginMessageBus>;
|
|
522
|
+
|
|
523
|
+
// Focus Series Plugin
|
|
524
|
+
const focusSeries = (options?: {
|
|
525
|
+
pxThreshold?: number; // default: 15
|
|
526
|
+
}): PluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
|
|
527
|
+
|
|
528
|
+
// Tooltip Plugin
|
|
529
|
+
const tooltip = (
|
|
530
|
+
Component: Component<TooltipProps>,
|
|
531
|
+
options?: {
|
|
532
|
+
placement?: "top-left" | "top-right" | "bottom-left" | "bottom-right";
|
|
533
|
+
id?: string;
|
|
534
|
+
class?: string;
|
|
535
|
+
style?: JSX.CSSProperties;
|
|
536
|
+
zIndex?: number; // default: 20
|
|
537
|
+
}
|
|
538
|
+
): PluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
|
|
539
|
+
|
|
540
|
+
// Legend Plugin
|
|
541
|
+
const legend = (
|
|
542
|
+
Component: Component<LegendProps>,
|
|
543
|
+
options?: {
|
|
544
|
+
placement?: "top-left" | "top-right"; // default: "top-left"
|
|
545
|
+
pxOffset?: number; // default: 8
|
|
546
|
+
id?: string;
|
|
547
|
+
class?: string;
|
|
548
|
+
style?: JSX.CSSProperties;
|
|
549
|
+
zIndex?: number; // default: 10
|
|
550
|
+
}
|
|
551
|
+
): PluginFactory<CursorPluginMessageBus & FocusSeriesPluginMessageBus>;
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## 📚 Examples
|
|
555
|
+
|
|
556
|
+
### Basic Chart
|
|
557
|
+
|
|
558
|
+
```tsx
|
|
559
|
+
import { SolidUplot } from "@dschz/solid-uplot";
|
|
560
|
+
|
|
561
|
+
const BasicChart = () => {
|
|
562
|
+
return (
|
|
563
|
+
<SolidUplot
|
|
564
|
+
data={[
|
|
565
|
+
[0, 1, 2, 3],
|
|
566
|
+
[10, 20, 30, 40],
|
|
567
|
+
[15, 25, 35, 45],
|
|
568
|
+
]}
|
|
569
|
+
width={600}
|
|
570
|
+
height={400}
|
|
571
|
+
scales={{
|
|
572
|
+
x: { time: false },
|
|
573
|
+
}}
|
|
574
|
+
series={[
|
|
575
|
+
{},
|
|
576
|
+
{ label: "Series 1", stroke: "#1f77b4" },
|
|
577
|
+
{ label: "Series 2", stroke: "#ff7f0e" },
|
|
578
|
+
]}
|
|
579
|
+
/>
|
|
580
|
+
);
|
|
581
|
+
};
|
|
582
|
+
```
|
|
583
|
+
|
|
584
|
+
### Chart with All Plugins
|
|
585
|
+
|
|
586
|
+
```tsx
|
|
587
|
+
import { SolidUplot, createPluginBus } from "@dschz/solid-uplot";
|
|
588
|
+
import { cursor, tooltip, legend, focusSeries } from "@dschz/solid-uplot/plugins";
|
|
589
|
+
import type {
|
|
590
|
+
CursorPluginMessageBus,
|
|
591
|
+
FocusSeriesPluginMessageBus,
|
|
592
|
+
TooltipProps,
|
|
593
|
+
LegendProps,
|
|
594
|
+
} from "@dschz/solid-uplot/plugins";
|
|
595
|
+
|
|
596
|
+
const MyTooltip: Component<TooltipProps> = (props) => (
|
|
597
|
+
<div style={{ background: "white", padding: "8px", border: "1px solid #ccc" }}>
|
|
598
|
+
<div>Time: {new Date(props.cursor.xValue * 1000).toLocaleTimeString()}</div>
|
|
599
|
+
<For each={props.seriesData}>
|
|
600
|
+
{(series) => {
|
|
601
|
+
const value = props.u.data[series.seriesIdx]?.[props.cursor.idx];
|
|
602
|
+
return (
|
|
603
|
+
<div style={{ color: series.stroke }}>
|
|
604
|
+
{series.label}: {value?.toFixed(2)}
|
|
605
|
+
</div>
|
|
606
|
+
);
|
|
607
|
+
}}
|
|
608
|
+
</For>
|
|
609
|
+
</div>
|
|
610
|
+
);
|
|
611
|
+
|
|
612
|
+
const MyLegend: Component<LegendProps> = (props) => {
|
|
613
|
+
const cursorVisible = () => props.bus.data.cursor?.state[props.u.root.id]?.visible;
|
|
614
|
+
|
|
615
|
+
return (
|
|
616
|
+
<div
|
|
617
|
+
style={{
|
|
618
|
+
background: "white",
|
|
619
|
+
border: "1px solid #ddd",
|
|
620
|
+
padding: "8px",
|
|
621
|
+
opacity: cursorVisible() ? 0.6 : 1,
|
|
622
|
+
transition: "opacity 200ms",
|
|
623
|
+
}}
|
|
624
|
+
>
|
|
625
|
+
<For each={props.seriesData}>
|
|
626
|
+
{(series) => (
|
|
627
|
+
<div style={{ display: "flex", "align-items": "center", gap: "6px" }}>
|
|
628
|
+
<div
|
|
629
|
+
style={{
|
|
630
|
+
width: "12px",
|
|
631
|
+
height: "12px",
|
|
632
|
+
background: series.stroke,
|
|
633
|
+
}}
|
|
634
|
+
/>
|
|
635
|
+
<span>{series.label}</span>
|
|
636
|
+
</div>
|
|
637
|
+
)}
|
|
638
|
+
</For>
|
|
639
|
+
</div>
|
|
640
|
+
);
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
const FullFeaturedChart = () => {
|
|
644
|
+
const bus = createPluginBus<CursorPluginMessageBus & FocusSeriesPluginMessageBus>();
|
|
645
|
+
|
|
646
|
+
return (
|
|
647
|
+
<SolidUplot
|
|
648
|
+
data={[
|
|
649
|
+
[0, 1, 2, 3, 4, 5],
|
|
650
|
+
[10, 20, 30, 40, 50, 60],
|
|
651
|
+
[15, 25, 35, 45, 55, 65],
|
|
652
|
+
[5, 15, 25, 35, 45, 55],
|
|
653
|
+
]}
|
|
654
|
+
width={800}
|
|
655
|
+
height={500}
|
|
656
|
+
series={[
|
|
657
|
+
{},
|
|
658
|
+
{ label: "Revenue", stroke: "#1f77b4" },
|
|
659
|
+
{ label: "Profit", stroke: "#ff7f0e" },
|
|
660
|
+
{ label: "Expenses", stroke: "#2ca02c" },
|
|
661
|
+
]}
|
|
662
|
+
plugins={[
|
|
663
|
+
cursor(),
|
|
664
|
+
focusSeries({ pxThreshold: 20 }),
|
|
665
|
+
tooltip(MyTooltip, { placement: "top-right" }),
|
|
666
|
+
legend(MyLegend, { placement: "top-left", pxOffset: 12 }),
|
|
667
|
+
]}
|
|
668
|
+
pluginBus={bus}
|
|
669
|
+
/>
|
|
670
|
+
);
|
|
671
|
+
};
|
|
672
|
+
```
|
|
673
|
+
|
|
674
|
+
### Responsive Chart
|
|
675
|
+
|
|
676
|
+
```tsx
|
|
677
|
+
const ResponsiveChart = () => {
|
|
678
|
+
return (
|
|
679
|
+
<div style={{ width: "100%", height: "400px", border: "1px solid #ccc" }}>
|
|
680
|
+
<SolidUplot
|
|
681
|
+
autoResize={true}
|
|
682
|
+
data={data}
|
|
683
|
+
series={series}
|
|
684
|
+
plugins={[cursor(), tooltip(MyTooltip)]}
|
|
685
|
+
/>
|
|
686
|
+
</div>
|
|
687
|
+
);
|
|
688
|
+
};
|
|
689
|
+
```
|
|
690
|
+
|
|
691
|
+
### External Integration
|
|
692
|
+
|
|
693
|
+
```tsx
|
|
694
|
+
const Dashboard = () => {
|
|
695
|
+
const bus = createPluginBus<FocusSeriesPluginMessageBus>();
|
|
696
|
+
|
|
697
|
+
const handleSeriesToggle = (seriesLabel: string) => {
|
|
698
|
+
bus.setData("focusSeries", {
|
|
699
|
+
sourceId: "external-control",
|
|
700
|
+
targets: [{ label: seriesLabel }],
|
|
701
|
+
});
|
|
702
|
+
};
|
|
703
|
+
|
|
704
|
+
return (
|
|
705
|
+
<div>
|
|
706
|
+
<div>
|
|
707
|
+
<button onClick={() => handleSeriesToggle("Series 1")}>Focus Series 1</button>
|
|
708
|
+
<button onClick={() => handleSeriesToggle("Series 2")}>Focus Series 2</button>
|
|
709
|
+
</div>
|
|
710
|
+
<SolidUplot data={data} plugins={[focusSeries()]} pluginBus={bus} />
|
|
711
|
+
</div>
|
|
712
|
+
);
|
|
713
|
+
};
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
## 🎮 Interactive Playground
|
|
717
|
+
|
|
718
|
+
This library includes a comprehensive playground application that demonstrates all features and provides interactive examples. The playground showcases:
|
|
719
|
+
|
|
720
|
+
- **Basic Charts**: Simple line charts with different configurations
|
|
721
|
+
- **Plugin Examples**: All built-in plugins working together
|
|
722
|
+
- **Legend Showcase**: Various legend patterns and interactions
|
|
723
|
+
- **Responsive Sizing**: Auto-resize and manual sizing examples
|
|
724
|
+
- **Custom Plugins**: Examples of creating your own plugins
|
|
725
|
+
- **External Integration**: Charts interacting with external components
|
|
726
|
+
|
|
727
|
+
### Running the Playground Locally
|
|
728
|
+
|
|
729
|
+
To explore the playground and see the library in action:
|
|
730
|
+
|
|
731
|
+
```bash
|
|
732
|
+
# Clone the repository
|
|
733
|
+
git clone https://github.com/dsnchz/solid-uplot.git
|
|
734
|
+
cd solid-uplot
|
|
735
|
+
|
|
736
|
+
# Install dependencies
|
|
737
|
+
npm install
|
|
738
|
+
# or
|
|
739
|
+
pnpm install
|
|
740
|
+
# or
|
|
741
|
+
yarn install
|
|
742
|
+
# or
|
|
743
|
+
bun install
|
|
744
|
+
|
|
745
|
+
# Start the playground development server
|
|
746
|
+
npm run start
|
|
747
|
+
# or
|
|
748
|
+
pnpm start
|
|
749
|
+
# or
|
|
750
|
+
yarn start
|
|
751
|
+
# or
|
|
752
|
+
bun start
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
The playground will be available at `http://localhost:3000` and includes:
|
|
756
|
+
|
|
757
|
+
- **Live code examples** with syntax highlighting
|
|
758
|
+
- **Interactive demos** you can modify in real-time
|
|
759
|
+
- **Performance comparisons** between different configurations
|
|
760
|
+
- **Best practices** and common patterns
|
|
761
|
+
- **Plugin development examples** with step-by-step guides
|
|
762
|
+
|
|
763
|
+
The playground source code also serves as a comprehensive reference for implementing various chart patterns and plugin combinations.
|
|
764
|
+
|
|
765
|
+
## 🤝 Contributing
|
|
766
|
+
|
|
767
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
768
|
+
|
|
769
|
+
## 📄 License
|
|
770
|
+
|
|
771
|
+
MIT
|