@vuu-ui/vuu-chart 4.0.0-alpha.21 → 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.
Files changed (45) hide show
  1. package/README.md +324 -0
  2. package/package.json +12 -15
  3. package/src/Chart.css.js +22 -0
  4. package/src/Chart.js +4 -4
  5. package/src/ChartSeries.js +20 -3
  6. package/src/chart-data/ChartDataStore.js +131 -0
  7. package/src/chart-data/projection-utils.js +119 -0
  8. package/src/charts/CandlestickChart.js +31 -0
  9. package/src/charts/CartesianChart.js +28 -0
  10. package/src/charts/DepthChart.js +28 -0
  11. package/src/charts/HeatmapChart.js +31 -0
  12. package/src/charts/HierarchyChart.js +41 -0
  13. package/src/charts/candlestick-model.js +373 -0
  14. package/src/charts/cartesian-model.js +234 -0
  15. package/src/charts/depth-model.js +159 -0
  16. package/src/charts/heatmap-model.js +243 -0
  17. package/src/charts/hierarchy-model.js +224 -0
  18. package/src/echarts.js +26 -0
  19. package/src/engine/ChartModel.js +2 -0
  20. package/src/engine/VuuChartContainer.js +196 -0
  21. package/src/engine/useStructuralMemo.js +18 -0
  22. package/src/index.js +8 -0
  23. package/src/options/common.js +181 -0
  24. package/src/options/mergeOptions.js +37 -0
  25. package/src/theme/chartTheme.js +74 -0
  26. package/src/useChartOptions.js +25 -2
  27. package/src/vuu-chart-types.js +0 -0
  28. package/types/Chart.d.ts +0 -28
  29. package/types/ChartSeries.d.ts +0 -32
  30. package/types/DataRowMovingWindow.d.ts +0 -23
  31. package/types/chart-types.d.ts +0 -42
  32. package/types/chartTooltip.d.ts +0 -12
  33. package/types/index.d.ts +0 -5
  34. package/types/src/Chart.d.ts +0 -28
  35. package/types/src/ChartSeries.d.ts +0 -32
  36. package/types/src/DataRowMovingWindow.d.ts +0 -23
  37. package/types/src/chart-types.d.ts +0 -42
  38. package/types/src/chartTooltip.d.ts +0 -12
  39. package/types/src/index.d.ts +0 -5
  40. package/types/src/useChartContextMenu.d.ts +0 -16
  41. package/types/src/useChartOptions.d.ts +0 -28
  42. package/types/src/useChartSelection.d.ts +0 -14
  43. package/types/useChartContextMenu.d.ts +0 -16
  44. package/types/useChartOptions.d.ts +0 -28
  45. 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.21",
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.21",
14
- "@vuu-ui/vuu-protocol-types": "4.0.0-alpha.21",
15
- "@vuu-ui/vuu-data-local": "4.0.0-alpha.21"
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
18
  "@salt-ds/styles": "0.4.1",
19
19
  "@salt-ds/window": "0.1.2",
20
- "@vuu-ui/vuu-context-menu": "4.0.0-alpha.21",
21
- "@vuu-ui/vuu-utils": "4.0.0-alpha.21",
22
- "clsx": "^2.0.0"
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.1.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
- "types": "./types/index.d.ts",
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-toast-notification",
25
+ testId: "vuu-chart",
26
26
  css: Chart,
27
27
  window: targetWindow
28
28
  });
@@ -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.subscribe({
47
- range: Range(0, 1000)
48
- }, this.handleData);
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 };