@vuu-ui/vuu-chart 4.0.0-alpha.20 → 4.0.0-alpha.22
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/README.md +324 -0
- package/package.json +14 -17
- package/src/Chart.css.js +22 -0
- package/src/Chart.js +4 -4
- package/src/ChartSeries.js +20 -3
- package/src/chart-data/ChartDataStore.js +131 -0
- package/src/chart-data/projection-utils.js +119 -0
- package/src/charts/CandlestickChart.js +31 -0
- package/src/charts/CartesianChart.js +28 -0
- package/src/charts/DepthChart.js +28 -0
- package/src/charts/HeatmapChart.js +31 -0
- package/src/charts/HierarchyChart.js +41 -0
- package/src/charts/candlestick-model.js +373 -0
- package/src/charts/cartesian-model.js +234 -0
- package/src/charts/depth-model.js +159 -0
- package/src/charts/heatmap-model.js +243 -0
- package/src/charts/hierarchy-model.js +224 -0
- package/src/echarts.js +26 -0
- package/src/engine/ChartModel.js +2 -0
- package/src/engine/VuuChartContainer.js +196 -0
- package/src/engine/useStructuralMemo.js +18 -0
- package/src/index.js +8 -0
- package/src/options/common.js +181 -0
- package/src/options/mergeOptions.js +37 -0
- package/src/theme/chartTheme.js +74 -0
- package/src/useChartOptions.js +25 -2
- package/src/vuu-chart-types.js +0 -0
- package/types/Chart.d.ts +0 -28
- package/types/ChartSeries.d.ts +0 -32
- package/types/DataRowMovingWindow.d.ts +0 -23
- package/types/chart-types.d.ts +0 -42
- package/types/chartTooltip.d.ts +0 -12
- package/types/index.d.ts +0 -5
- package/types/src/Chart.d.ts +0 -28
- package/types/src/ChartSeries.d.ts +0 -32
- package/types/src/DataRowMovingWindow.d.ts +0 -23
- package/types/src/chart-types.d.ts +0 -42
- package/types/src/chartTooltip.d.ts +0 -12
- package/types/src/index.d.ts +0 -5
- package/types/src/useChartContextMenu.d.ts +0 -16
- package/types/src/useChartOptions.d.ts +0 -28
- package/types/src/useChartSelection.d.ts +0 -14
- package/types/useChartContextMenu.d.ts +0 -16
- package/types/useChartOptions.d.ts +0 -28
- package/types/useChartSelection.d.ts +0 -14
package/README.md
CHANGED
|
@@ -0,0 +1,324 @@
|
|
|
1
|
+
# @vuu-ui/vuu-chart
|
|
2
|
+
|
|
3
|
+
React chart components driven by Vuu `DataSource`s, rendered with
|
|
4
|
+
[Apache ECharts](https://echarts.apache.org).
|
|
5
|
+
|
|
6
|
+
Every chart is declarative and configured by **column mapping**. You tell
|
|
7
|
+
the chart which columns hold the x values, the series values, the OHLC values
|
|
8
|
+
and so on. The chart subscribes to the DataSource and keeps itself up to date
|
|
9
|
+
as rows are inserted, updated or removed.
|
|
10
|
+
|
|
11
|
+
| Component | Use for |
|
|
12
|
+
| ------------------------------------------- | ---------------------------------------------------------------------- |
|
|
13
|
+
| `CartesianChart` | line, area (incl. stepped), bar (vertical/horizontal/stacked), scatter |
|
|
14
|
+
| `CandlestickChart` | OHLC bars, optional volume pane and line/scatter overlays |
|
|
15
|
+
| `DepthChart` | market depth (cumulative bid/ask stepped areas) |
|
|
16
|
+
| `HeatmapChart` | value by two dimensions, e.g. price level x time |
|
|
17
|
+
| `TreemapChart`, `SunburstChart`, `PieChart` | breakdowns grouped hierarchically by columns |
|
|
18
|
+
| `Chart` | the original line/scatter chart (unchanged API) |
|
|
19
|
+
|
|
20
|
+
Showcase examples for every chart type are in `showcase/src/examples/Chart`.
|
|
21
|
+
|
|
22
|
+
## Common props
|
|
23
|
+
|
|
24
|
+
All of the new chart components accept these props (`VuuChartBaseProps`),
|
|
25
|
+
plus the standard `className`, `style`, `id` etc.
|
|
26
|
+
|
|
27
|
+
| Prop | Type | Notes |
|
|
28
|
+
| ------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
29
|
+
| `echartsOptions` | `EChartsCoreOption \| (generated) => EChartsCoreOption` | Escape hatch: raw ECharts options, deep merged over the generated options (see below) |
|
|
30
|
+
| `maxRows` | `number` | Maximum rows read from each DataSource. Default `1000` |
|
|
31
|
+
| `renderer` | `"canvas" \| "svg"` | Default `"canvas"`, which is best for streaming data |
|
|
32
|
+
| `showTooltip` | `boolean` | Default `true` |
|
|
33
|
+
| `theme` | `Partial<ChartTheme>` | Override colours resolved from css tokens |
|
|
34
|
+
| `onChartReady` | `(chart: ECharts) => void` | Access to the ECharts instance |
|
|
35
|
+
| `onItemClick` | `(evt: ChartItemEvent) => void` | `evt.row` is the `DataSourceRow` behind the item. For aggregated items, `evt.rows` and `evt.groupPath` are also set |
|
|
36
|
+
| `onItemContextMenu` | `(evt: ChartItemEvent) => void` | As above |
|
|
37
|
+
|
|
38
|
+
Cartesian, candlestick and heatmap charts also accept
|
|
39
|
+
`dataZoom?: boolean | "inside" | "slider" | "both"`. `true` is the same as
|
|
40
|
+
`"inside"`. Most charts accept `legend?: "top" | "bottom" | "none"`.
|
|
41
|
+
|
|
42
|
+
Mark lines (`markLines?: MarkLineDef[]`) can be added to series and to the
|
|
43
|
+
candlestick price axis:
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
interface MarkLineDef {
|
|
47
|
+
value: number | "last" | "min" | "max" | "average" | { weightedAverage: string };
|
|
48
|
+
label?: string;
|
|
49
|
+
color?: string; // css colour, or "up" | "down" | "palette-1".."palette-8"
|
|
50
|
+
lineType?: "solid" | "dashed" | "dotted";
|
|
51
|
+
labelPosition?: MarkLineLabelPosition; // default "insideEndTop"
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Rows used by a series can be restricted with `where`, either as
|
|
56
|
+
`{ side: "BUY" }` (all entries must match) or as a predicate
|
|
57
|
+
`(row, columnMap) => boolean`. Points can be coloured by the value in a column
|
|
58
|
+
with `colorBy: { columnName: "side", colors: { BUY: "up", SELL: "down" } }`.
|
|
59
|
+
|
|
60
|
+
## CartesianChart
|
|
61
|
+
|
|
62
|
+
```tsx
|
|
63
|
+
<CartesianChart
|
|
64
|
+
dataSource={tradesDataSource}
|
|
65
|
+
xAxis={{ columnName: "time", type: "time" }}
|
|
66
|
+
yAxes={[{ scale: true }, { position: "right", name: "Qty" }]}
|
|
67
|
+
series={[
|
|
68
|
+
{ type: "line", columnName: "price", markLines: [{ value: "last" }] },
|
|
69
|
+
{ type: "bar", columnName: "quantity", yAxisIndex: 1 },
|
|
70
|
+
{
|
|
71
|
+
type: "scatter",
|
|
72
|
+
columnName: "price",
|
|
73
|
+
id: "fills",
|
|
74
|
+
dataSource: fillsDataSource, // overlay from a second DataSource
|
|
75
|
+
colorBy: { columnName: "side", colors: { BUY: "up", SELL: "down" } },
|
|
76
|
+
},
|
|
77
|
+
]}
|
|
78
|
+
dataZoom="both"
|
|
79
|
+
/>
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
- `xAxis: { columnName, type?: "time" | "category" | "value", name? }`.
|
|
83
|
+
`"time"` expects epoch millisecond timestamps. The default, `"category"`,
|
|
84
|
+
uses distinct values in DataSource order.
|
|
85
|
+
- `series: CartesianSeriesDef[]`. Each has a `type` of
|
|
86
|
+
`"line" | "area" | "bar" | "scatter"`, a `columnName`, and optionally:
|
|
87
|
+
- `dataSource` and `xColumnName`, to read from a different DataSource
|
|
88
|
+
- `where`, `yAxisIndex`, `stack`
|
|
89
|
+
- `step: "start" | "middle" | "end"` and `smooth`
|
|
90
|
+
- `color`, `colorBy` and `areaOpacity`
|
|
91
|
+
- `showSymbol`, `symbolSize` and `markLines`
|
|
92
|
+
- `yAxes: CartesianYAxisDef[]`. Each takes `name`, `position`, `scale`,
|
|
93
|
+
`type: "value" | "log"`, `min` and `max`.
|
|
94
|
+
- `orientation: "horizontal"` gives horizontal bars.
|
|
95
|
+
- `aggregation: "sum" | "avg" | "min" | "max" | "count" | "last"` combines
|
|
96
|
+
rows that share the same category, e.g. notional summed by sector.
|
|
97
|
+
|
|
98
|
+
## CandlestickChart
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
<CandlestickChart
|
|
102
|
+
dataSource={barsDataSource} // bars for one ric and interval
|
|
103
|
+
volumeColumnName="volume"
|
|
104
|
+
markLines={[{ value: "last", label: "Last" }]}
|
|
105
|
+
overlays={[
|
|
106
|
+
{ type: "line", columnName: "vwap", name: "VWAP" },
|
|
107
|
+
{
|
|
108
|
+
type: "scatter",
|
|
109
|
+
columnName: "price",
|
|
110
|
+
dataSource: fillsDataSource,
|
|
111
|
+
timeColumnName: "time",
|
|
112
|
+
colorBy: { columnName: "side", colors: { BUY: "up", SELL: "down" } },
|
|
113
|
+
markLines: [{ value: { weightedAverage: "quantity" }, label: "Avg fill" }],
|
|
114
|
+
},
|
|
115
|
+
]}
|
|
116
|
+
/>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- `timeColumnName` defaults to `"start"`.
|
|
120
|
+
- `ohlcColumnNames` defaults to `open`, `high`, `low` and `close`.
|
|
121
|
+
- `volumeColumnName` adds a volume pane, sized by `volumePaneHeight` (% of the
|
|
122
|
+
chart, default 20). The volume bars are coloured up/down.
|
|
123
|
+
- `xAxisType`: the default, `"category"`, spaces bars evenly with no gaps.
|
|
124
|
+
`"time"` uses a continuous time axis.
|
|
125
|
+
- `overlays` take `type: "line" | "scatter"`, `columnName`, and optionally
|
|
126
|
+
`dataSource`, `timeColumnName`, `where`, `color`, `colorBy`, `symbol`,
|
|
127
|
+
`symbolSize` and `markLines`. On a category axis, overlay points from
|
|
128
|
+
another DataSource are placed on the bar whose interval contains their
|
|
129
|
+
timestamp. Points earlier than the first bar are dropped.
|
|
130
|
+
- `dataZoom` defaults to `"both"`, and the zoom is shared by the price and
|
|
131
|
+
volume panes.
|
|
132
|
+
- `formatTime` formats axis labels.
|
|
133
|
+
|
|
134
|
+
## DepthChart
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
<DepthChart dataSource={depthDataSource} cumulativeSizeColumnName="cumSize" />
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Renders cumulative bids (stepped area, `step: "start"`) and asks
|
|
141
|
+
(`step: "end"`) over a value price axis, with a mid price mark line.
|
|
142
|
+
|
|
143
|
+
Columns default to `priceColumnName="price"`, `sideColumnName="side"` and
|
|
144
|
+
`sizeColumnName="size"`. The side values default to `bidValue="BUY"` and
|
|
145
|
+
`askValue="SELL"`. If `cumulativeSizeColumnName` is not given, cumulative size
|
|
146
|
+
is computed from `sizeColumnName`. Set `showMidPrice={false}` to hide the mid
|
|
147
|
+
price line.
|
|
148
|
+
|
|
149
|
+
## HeatmapChart
|
|
150
|
+
|
|
151
|
+
```tsx
|
|
152
|
+
<HeatmapChart
|
|
153
|
+
dataSource={tradesDataSource}
|
|
154
|
+
xAxis={{ columnName: "time", type: "time", bucketSize: 60_000 }}
|
|
155
|
+
yAxis={{ columnName: "price", type: "value", bucketSize: 0.05 }}
|
|
156
|
+
valueColumnName="quantity"
|
|
157
|
+
aggregation="sum"
|
|
158
|
+
/>
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
- Both axes are category axes built from the distinct (bucketed) values.
|
|
162
|
+
`bucketSize` rounds numeric values down to a multiple of the bucket size.
|
|
163
|
+
- `aggregation` defaults to `"sum"`.
|
|
164
|
+
- `colorScale`: `"sequential"` (the default) uses the theme heatmap colours.
|
|
165
|
+
`"diverging"` maps negative values to down and positive values to up,
|
|
166
|
+
symmetric around zero, e.g. for pnl.
|
|
167
|
+
- `min` and `max` fix the colour range. By default the range is recomputed
|
|
168
|
+
from the data on each update.
|
|
169
|
+
- `showVisualMap` controls the colour scale legend (default `true`).
|
|
170
|
+
- Also accepts `where` and `formatTime`.
|
|
171
|
+
|
|
172
|
+
## TreemapChart, SunburstChart, PieChart
|
|
173
|
+
|
|
174
|
+
```tsx
|
|
175
|
+
<TreemapChart
|
|
176
|
+
dataSource={positionsDataSource}
|
|
177
|
+
groupByColumnNames={["sector", "ric"]}
|
|
178
|
+
valueColumnName="pnl"
|
|
179
|
+
colorMode="sign"
|
|
180
|
+
onItemClick={({ groupPath, rows }) => console.log(groupPath, rows?.length)}
|
|
181
|
+
/>
|
|
182
|
+
|
|
183
|
+
<PieChart
|
|
184
|
+
dataSource={positionsDataSource}
|
|
185
|
+
groupByColumnNames={["currency"]}
|
|
186
|
+
valueColumnName="notional"
|
|
187
|
+
donut
|
|
188
|
+
legend="bottom"
|
|
189
|
+
/>
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
- `groupByColumnNames` lists the grouping columns, outermost first. Pie charts
|
|
193
|
+
use only the first column.
|
|
194
|
+
- `aggregation` defaults to `"sum"`.
|
|
195
|
+
- `colorMode`:
|
|
196
|
+
- `"palette"` (the default) gives each top level group its own colour.
|
|
197
|
+
- `"sign"` sizes nodes by absolute value and colours them up or down by
|
|
198
|
+
sign. A parent is sized as the sum of its children, while labels and
|
|
199
|
+
tooltips show the net value.
|
|
200
|
+
- Also accepts `formatValue`, `where` and `donut` (pie only).
|
|
201
|
+
- `HierarchyChart` with a `type` prop is the underlying component.
|
|
202
|
+
|
|
203
|
+
## Streaming model
|
|
204
|
+
|
|
205
|
+
Each chart separates **structure** from **data**:
|
|
206
|
+
|
|
207
|
+
- The **base option** holds axes, grids, series definitions, theme, legends,
|
|
208
|
+
dataZoom and `echartsOptions`. It is rebuilt with
|
|
209
|
+
`setOption(..., { notMerge: true })` only when props (compared
|
|
210
|
+
structurally), theme mode or the set of DataSources change.
|
|
211
|
+
- **Data** is held per DataSource in a `ChartDataStore`. The store subscribes
|
|
212
|
+
to a viewport of `maxRows` rows and applies snapshots, inserts, updates and
|
|
213
|
+
deletes by row key. Updates set a dirty flag. At most once per animation
|
|
214
|
+
frame, the chart builds a data-only partial option (series `data`, category
|
|
215
|
+
axis `data`, visualMap range, mark line values). That option is merged by
|
|
216
|
+
component id, so axes, zoom state and legend selection are preserved.
|
|
217
|
+
|
|
218
|
+
A bar that ticks in place, or a depth level that updates, therefore costs one
|
|
219
|
+
partial `setOption` per frame, however many updates arrive. `appendData` is not
|
|
220
|
+
used because Vuu rows also update and delete in place, which `appendData`
|
|
221
|
+
cannot express.
|
|
222
|
+
|
|
223
|
+
The original `Chart` component is also throttled to animation frames.
|
|
224
|
+
|
|
225
|
+
## DataSource ownership and teardown
|
|
226
|
+
|
|
227
|
+
The new charts own the DataSource subscriptions they create:
|
|
228
|
+
|
|
229
|
+
- On unmount, and when a `dataSource` prop is replaced, the chart unsubscribes
|
|
230
|
+
from the DataSource.
|
|
231
|
+
- On unmount, the ECharts instance is disposed and any pending animation frame
|
|
232
|
+
is cancelled.
|
|
233
|
+
|
|
234
|
+
So give each chart its own DataSource instance rather than sharing one with
|
|
235
|
+
another component, such as a table. Filters, sorts and link groups apply as
|
|
236
|
+
normal.
|
|
237
|
+
|
|
238
|
+
## Theming
|
|
239
|
+
|
|
240
|
+
Colours are read from css custom properties on the chart element. Defaults
|
|
241
|
+
map to Salt tokens, so light and dark modes work automatically. The theme is
|
|
242
|
+
re-read when the Vuu `ThemeContext` mode changes.
|
|
243
|
+
|
|
244
|
+
```css
|
|
245
|
+
.my-charts {
|
|
246
|
+
--vuuChart-upColor: #26a69a;
|
|
247
|
+
--vuuChart-downColor: #ef5350;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
The tokens are:
|
|
252
|
+
|
|
253
|
+
- `--vuuChart-foreground` and `--vuuChart-secondaryForeground`
|
|
254
|
+
- `--vuuChart-background`
|
|
255
|
+
- `--vuuChart-axisLineColor` and `--vuuChart-splitLineColor`
|
|
256
|
+
- `--vuuChart-crosshairColor`
|
|
257
|
+
- `--vuuChart-fontFamily`
|
|
258
|
+
- `--vuuChart-tooltip-background` and `--vuuChart-tooltip-borderColor`
|
|
259
|
+
- `--vuuChart-upColor` and `--vuuChart-downColor`
|
|
260
|
+
- `--vuuChart-heatmap-lowColor` and `--vuuChart-heatmap-highColor`
|
|
261
|
+
- `--vuuChart-color-1` … `--vuuChart-color-8`, the series palette
|
|
262
|
+
|
|
263
|
+
The `theme` prop overrides any resolved value, e.g.
|
|
264
|
+
`theme={{ upColor: "#26a69a" }}`.
|
|
265
|
+
|
|
266
|
+
## Escape hatch: `echartsOptions`
|
|
267
|
+
|
|
268
|
+
Anything not modelled can be passed as raw ECharts options. They are deep
|
|
269
|
+
merged over the generated option:
|
|
270
|
+
|
|
271
|
+
- Objects merge recursively.
|
|
272
|
+
- Arrays of components (series, xAxis, yAxis, grid, dataZoom …) merge item
|
|
273
|
+
by item, matched by `id`, or by index where there is no id.
|
|
274
|
+
- An object given for an array component merges into the first item.
|
|
275
|
+
- Arrays of primitives are replaced.
|
|
276
|
+
|
|
277
|
+
A function receives the generated option and returns the option to use.
|
|
278
|
+
Overrides are applied when the structure is built, not on each data update.
|
|
279
|
+
`mergeEChartsOptions` is exported if you need the same merge elsewhere.
|
|
280
|
+
|
|
281
|
+
```tsx
|
|
282
|
+
<CandlestickChart
|
|
283
|
+
dataSource={barsDataSource}
|
|
284
|
+
echartsOptions={{
|
|
285
|
+
title: { text: "VOD.L 5s" },
|
|
286
|
+
series: [{ id: "candles", itemStyle: { color: "#26a69a", color0: "#ef5350" } }],
|
|
287
|
+
}}
|
|
288
|
+
/>
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
Generated component ids:
|
|
292
|
+
|
|
293
|
+
| Chart | Ids |
|
|
294
|
+
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------- |
|
|
295
|
+
| Cartesian | grid `main`; xAxis `x`; yAxes `y0`, `y1` …; series `id ?? columnName` |
|
|
296
|
+
| Candlestick | grids `price`, `volume`; xAxes `x`, `x-volume`; yAxes `price`, `volume`; series `candles`, `volume`, `overlay-{index}-{id ?? columnName}` |
|
|
297
|
+
| Depth | grid `main`; xAxis `x`; series `bids`, `asks` |
|
|
298
|
+
| Heatmap | grid `main`; xAxis `x`; series `heatmap`; visualMap `vm` |
|
|
299
|
+
| Hierarchy | series `treemap` / `sunburst` / `pie` |
|
|
300
|
+
| dataZoom | `zoom-inside`, `zoom-slider` |
|
|
301
|
+
|
|
302
|
+
## Bundle size
|
|
303
|
+
|
|
304
|
+
ECharts is imported via `echarts/core`, registering only the chart types and
|
|
305
|
+
components used (see `src/echarts.ts`). Measured with esbuild, minified:
|
|
306
|
+
|
|
307
|
+
| Import | min | gzip |
|
|
308
|
+
| ----------------------------------------------- | -------- | ------ |
|
|
309
|
+
| `import * as echarts from "echarts"` (previous) | 1,142 KB | 383 KB |
|
|
310
|
+
| vuu-chart registered set (all chart types) | 733 KB | 248 KB |
|
|
311
|
+
| line + scatter + grid/tooltip/legend only | 548 KB | 187 KB |
|
|
312
|
+
|
|
313
|
+
So the package now adds about 135 KB less (gzipped) to an application than
|
|
314
|
+
before, even with the new chart types.
|
|
315
|
+
|
|
316
|
+
## Limitations
|
|
317
|
+
|
|
318
|
+
- At most `maxRows` (default 1000) rows are read from each DataSource. Use
|
|
319
|
+
DataSource filters, or the bars interval, to keep data within range.
|
|
320
|
+
- On a data update, a treemap or sunburst may reset to the root after the
|
|
321
|
+
user has drilled down.
|
|
322
|
+
- Heatmap axes are category axes (bucketed values), not continuous axes.
|
|
323
|
+
- `echartsOptions` given as a function are applied on structural rebuilds
|
|
324
|
+
only.
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "4.0.0-alpha.
|
|
2
|
+
"version": "4.0.0-alpha.22",
|
|
3
3
|
"description": "support for apache echarts using vuu data",
|
|
4
4
|
"main": "./src/index.js",
|
|
5
5
|
"type": "module",
|
|
@@ -10,35 +10,32 @@
|
|
|
10
10
|
"type-defs": "node ../../scripts/build-type-defs.mjs"
|
|
11
11
|
},
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@vuu-ui/vuu-data-types": "4.0.0-alpha.
|
|
14
|
-
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.
|
|
15
|
-
"@vuu-ui/vuu-data-local": "4.0.0-alpha.
|
|
13
|
+
"@vuu-ui/vuu-data-types": "4.0.0-alpha.22",
|
|
14
|
+
"@vuu-ui/vuu-protocol-types": "4.0.0-alpha.22",
|
|
15
|
+
"@vuu-ui/vuu-data-local": "4.0.0-alpha.22"
|
|
16
16
|
},
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@salt-ds/styles": "0.
|
|
19
|
-
"@salt-ds/window": "0.1.
|
|
20
|
-
"@vuu-ui/vuu-context-menu": "4.0.0-alpha.
|
|
21
|
-
"@vuu-ui/vuu-utils": "4.0.0-alpha.
|
|
22
|
-
"clsx": "^2.0.0"
|
|
18
|
+
"@salt-ds/styles": "0.4.1",
|
|
19
|
+
"@salt-ds/window": "0.1.2",
|
|
20
|
+
"@vuu-ui/vuu-context-menu": "4.0.0-alpha.22",
|
|
21
|
+
"@vuu-ui/vuu-utils": "4.0.0-alpha.22",
|
|
22
|
+
"clsx": "^2.0.0",
|
|
23
|
+
"echarts": "6.1.0",
|
|
24
|
+
"@vuu-ui/vuu-ui-controls": "4.0.0-alpha.22"
|
|
23
25
|
},
|
|
24
26
|
"peerDependencies": {
|
|
25
|
-
"echarts": "6.0.0",
|
|
26
27
|
"react": "^19.2.3",
|
|
27
28
|
"react-dom": "^19.2.3"
|
|
28
29
|
},
|
|
29
30
|
"files": [
|
|
30
31
|
"README.md",
|
|
31
|
-
"src"
|
|
32
|
-
"/types"
|
|
32
|
+
"src"
|
|
33
33
|
],
|
|
34
34
|
"exports": {
|
|
35
35
|
".": {
|
|
36
|
-
"
|
|
37
|
-
"import": "./src/index.js",
|
|
38
|
-
"default": "./src/index.js"
|
|
36
|
+
"import": "./src/index.js"
|
|
39
37
|
}
|
|
40
38
|
},
|
|
41
39
|
"module": "./src/index.js",
|
|
42
|
-
"name": "@vuu-ui/vuu-chart"
|
|
43
|
-
"types": "types/index.d.ts"
|
|
40
|
+
"name": "@vuu-ui/vuu-chart"
|
|
44
41
|
}
|
package/src/Chart.css.js
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
const css = `
|
|
2
2
|
.vuuChart {
|
|
3
|
+
--vuuChart-foreground: var(--salt-content-primary-foreground);
|
|
4
|
+
--vuuChart-secondaryForeground: var(--salt-content-secondary-foreground);
|
|
5
|
+
--vuuChart-background: transparent;
|
|
6
|
+
--vuuChart-axisLineColor: var(--salt-separable-primary-borderColor);
|
|
7
|
+
--vuuChart-splitLineColor: var(--salt-separable-tertiary-borderColor);
|
|
8
|
+
--vuuChart-crosshairColor: var(--salt-content-secondary-foreground);
|
|
9
|
+
--vuuChart-fontFamily: var(--salt-text-fontFamily);
|
|
10
|
+
--vuuChart-tooltip-background: var(--salt-container-primary-background);
|
|
11
|
+
--vuuChart-tooltip-borderColor: var(--salt-selectable-borderColor-selected);
|
|
12
|
+
--vuuChart-upColor: var(--salt-sentiment-positive-foreground-decorative);
|
|
13
|
+
--vuuChart-downColor: var(--salt-sentiment-negative-foreground-decorative);
|
|
14
|
+
--vuuChart-heatmap-lowColor: var(--salt-container-secondary-background);
|
|
15
|
+
--vuuChart-heatmap-highColor: var(--salt-accent-background, #1b4f9c);
|
|
16
|
+
--vuuChart-color-1: #4676bf;
|
|
17
|
+
--vuuChart-color-2: #ab6528;
|
|
18
|
+
--vuuChart-color-3: #9f55c2;
|
|
19
|
+
--vuuChart-color-4: #2a8285;
|
|
20
|
+
--vuuChart-color-5: #697694;
|
|
21
|
+
--vuuChart-color-6: #b0549d;
|
|
22
|
+
--vuuChart-color-7: #c69d26;
|
|
23
|
+
--vuuChart-color-8: #5c8a3a;
|
|
24
|
+
|
|
3
25
|
& .vuuChartTooltip {
|
|
4
26
|
background-color: var(--salt-container-primary-background) !important;
|
|
5
27
|
border-color: var(--salt-selectable-borderColor-selected) !important;
|
package/src/Chart.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
3
3
|
import { useWindow } from "@salt-ds/window";
|
|
4
|
+
import { MeasuredContainer } from "@vuu-ui/vuu-ui-controls";
|
|
5
|
+
import { buildColumnMap } from "@vuu-ui/vuu-utils";
|
|
4
6
|
import clsx from "clsx";
|
|
5
|
-
import { getInstanceByDom, init } from "echarts";
|
|
7
|
+
import { getInstanceByDom, init } from "./echarts.js";
|
|
6
8
|
import { useCallback, useEffect, useRef, useState } from "react";
|
|
7
9
|
import { useChartContextMenu } from "./useChartContextMenu.js";
|
|
8
10
|
import { useChartOptions } from "./useChartOptions.js";
|
|
9
11
|
import { useChartSelection } from "./useChartSelection.js";
|
|
10
|
-
import { buildColumnMap } from "@vuu-ui/vuu-utils";
|
|
11
|
-
import { MeasuredContainer } from "@vuu-ui/vuu-ui-controls";
|
|
12
12
|
import Chart from "./Chart.css";
|
|
13
13
|
const classBase = "vuuChart";
|
|
14
14
|
const Chart_Chart = ({ categoryColumnName, chartSettings = {
|
|
@@ -22,7 +22,7 @@ const Chart_Chart = ({ categoryColumnName, chartSettings = {
|
|
|
22
22
|
}, seriesColumnNames, width, ...htmlAttributes })=>{
|
|
23
23
|
const targetWindow = useWindow();
|
|
24
24
|
useComponentCssInjection({
|
|
25
|
-
testId: "vuu-
|
|
25
|
+
testId: "vuu-chart",
|
|
26
26
|
css: Chart,
|
|
27
27
|
window: targetWindow
|
|
28
28
|
});
|
package/src/ChartSeries.js
CHANGED
|
@@ -15,12 +15,15 @@ class ChartSeries extends EventEmitter {
|
|
|
15
15
|
#columnMap = {};
|
|
16
16
|
#config;
|
|
17
17
|
#dataExclusions;
|
|
18
|
+
#dataSource;
|
|
19
|
+
#subscribed = false;
|
|
18
20
|
#dataWindow = new MovingDataRowWindow(Range(0, 1000));
|
|
19
21
|
#dates = [];
|
|
20
22
|
#palette;
|
|
21
23
|
#series = [];
|
|
22
24
|
#seriesColumns = [];
|
|
23
25
|
handleData = (message)=>{
|
|
26
|
+
if (!this.#subscribed) return;
|
|
24
27
|
if ("subscribed" === message.type) this.#columnMap = buildColumnMap(message.tableSchema.columns);
|
|
25
28
|
else if ("viewport-update" === message.type) {
|
|
26
29
|
if ("size-only" === message.mode) ;
|
|
@@ -43,9 +46,23 @@ class ChartSeries extends EventEmitter {
|
|
|
43
46
|
this.#config = config;
|
|
44
47
|
this.#dataExclusions = dataExclusions;
|
|
45
48
|
this.#palette = palette;
|
|
46
|
-
dataSource
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
this.#dataSource = dataSource;
|
|
50
|
+
this.subscribe();
|
|
51
|
+
}
|
|
52
|
+
subscribe() {
|
|
53
|
+
if (!this.#subscribed) {
|
|
54
|
+
this.#subscribed = true;
|
|
55
|
+
this.#dataSource.subscribe({
|
|
56
|
+
range: Range(0, 1000)
|
|
57
|
+
}, this.handleData);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
detach() {
|
|
61
|
+
this.#subscribed = false;
|
|
62
|
+
}
|
|
63
|
+
dispose() {
|
|
64
|
+
this.#subscribed = false;
|
|
65
|
+
this.#dataSource.unsubscribe();
|
|
49
66
|
}
|
|
50
67
|
get palette() {
|
|
51
68
|
return this.#seriesColumns.map((col)=>this.#colorMap.get(col));
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { EventEmitter, Range, buildColumnMap, metadataKeys } from "@vuu-ui/vuu-utils";
|
|
2
|
+
const { IDX: IDX } = metadataKeys;
|
|
3
|
+
const DEFAULT_MAX_ROWS = 1000;
|
|
4
|
+
class ChartDataStore extends EventEmitter {
|
|
5
|
+
#columnMap;
|
|
6
|
+
#dataSource;
|
|
7
|
+
#denseRows = void 0;
|
|
8
|
+
#maxRows;
|
|
9
|
+
#rows = [];
|
|
10
|
+
#size = 0;
|
|
11
|
+
#subscribed = false;
|
|
12
|
+
#version = 0;
|
|
13
|
+
constructor(dataSource, { maxRows = DEFAULT_MAX_ROWS } = {}){
|
|
14
|
+
super();
|
|
15
|
+
this.#dataSource = dataSource;
|
|
16
|
+
this.#maxRows = maxRows;
|
|
17
|
+
this.#columnMap = buildColumnMap(dataSource.columns);
|
|
18
|
+
}
|
|
19
|
+
get columnMap() {
|
|
20
|
+
return this.#columnMap;
|
|
21
|
+
}
|
|
22
|
+
get dataSource() {
|
|
23
|
+
return this.#dataSource;
|
|
24
|
+
}
|
|
25
|
+
get isSubscribed() {
|
|
26
|
+
return this.#subscribed;
|
|
27
|
+
}
|
|
28
|
+
get rows() {
|
|
29
|
+
if (void 0 === this.#denseRows) {
|
|
30
|
+
const rows = [];
|
|
31
|
+
const count = Math.min(this.#rows.length, this.#size);
|
|
32
|
+
for(let i = 0; i < count; i++){
|
|
33
|
+
const row = this.#rows[i];
|
|
34
|
+
if (void 0 !== row) rows.push(row);
|
|
35
|
+
}
|
|
36
|
+
this.#denseRows = rows;
|
|
37
|
+
}
|
|
38
|
+
return this.#denseRows;
|
|
39
|
+
}
|
|
40
|
+
get size() {
|
|
41
|
+
return this.#size;
|
|
42
|
+
}
|
|
43
|
+
get version() {
|
|
44
|
+
return this.#version;
|
|
45
|
+
}
|
|
46
|
+
getValue(row, columnName) {
|
|
47
|
+
const index = this.#columnMap[columnName];
|
|
48
|
+
return void 0 === index ? void 0 : row[index];
|
|
49
|
+
}
|
|
50
|
+
subscribe() {
|
|
51
|
+
if (!this.#subscribed) {
|
|
52
|
+
this.#subscribed = true;
|
|
53
|
+
this.#dataSource.subscribe({
|
|
54
|
+
range: Range(0, this.#maxRows)
|
|
55
|
+
}, this.handleData);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
dispose() {
|
|
59
|
+
if (this.#subscribed) {
|
|
60
|
+
this.#subscribed = false;
|
|
61
|
+
this.#dataSource.unsubscribe();
|
|
62
|
+
}
|
|
63
|
+
this.removeAllListeners();
|
|
64
|
+
this.#rows = [];
|
|
65
|
+
this.#denseRows = void 0;
|
|
66
|
+
this.#size = 0;
|
|
67
|
+
}
|
|
68
|
+
changed() {
|
|
69
|
+
this.#denseRows = void 0;
|
|
70
|
+
this.#version += 1;
|
|
71
|
+
this.emit("update");
|
|
72
|
+
}
|
|
73
|
+
handleData = (message)=>{
|
|
74
|
+
if (!this.#subscribed) return;
|
|
75
|
+
switch(message.type){
|
|
76
|
+
case "subscribed":
|
|
77
|
+
if (message.columns) this.#columnMap = buildColumnMap(message.columns);
|
|
78
|
+
break;
|
|
79
|
+
case "viewport-update":
|
|
80
|
+
{
|
|
81
|
+
let changed = false;
|
|
82
|
+
if ("number" == typeof message.size && message.size !== this.#size) {
|
|
83
|
+
this.#size = message.size;
|
|
84
|
+
if (this.#rows.length > message.size) this.#rows.length = message.size;
|
|
85
|
+
changed = true;
|
|
86
|
+
}
|
|
87
|
+
if ("size-only" !== message.mode && message.rows) {
|
|
88
|
+
for (const row of message.rows){
|
|
89
|
+
const index = row[IDX];
|
|
90
|
+
if (index >= 0 && index < this.#maxRows) {
|
|
91
|
+
this.#rows[index] = row;
|
|
92
|
+
changed = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (this.#size < this.#rows.length) this.#size = this.#rows.length;
|
|
96
|
+
}
|
|
97
|
+
if (changed) this.changed();
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
case "viewport-clear":
|
|
101
|
+
this.#rows = [];
|
|
102
|
+
this.#size = 0;
|
|
103
|
+
this.changed();
|
|
104
|
+
break;
|
|
105
|
+
default:
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
class ChartDataStores {
|
|
110
|
+
#stores = new Map();
|
|
111
|
+
constructor(dataSources, options, onUpdate){
|
|
112
|
+
for (const dataSource of dataSources)if (!this.#stores.has(dataSource)) {
|
|
113
|
+
const store = new ChartDataStore(dataSource, options);
|
|
114
|
+
store.on("update", onUpdate);
|
|
115
|
+
this.#stores.set(dataSource, store);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
get(dataSource) {
|
|
119
|
+
const store = this.#stores.get(dataSource);
|
|
120
|
+
if (void 0 === store) throw Error("[ChartDataStores] no store for DataSource. All DataSources must be declared to the chart");
|
|
121
|
+
return store;
|
|
122
|
+
}
|
|
123
|
+
subscribe() {
|
|
124
|
+
for (const store of this.#stores.values())store.subscribe();
|
|
125
|
+
}
|
|
126
|
+
dispose() {
|
|
127
|
+
for (const store of this.#stores.values())store.dispose();
|
|
128
|
+
this.#stores.clear();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
export { ChartDataStore, ChartDataStores, DEFAULT_MAX_ROWS };
|