@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.
- package/README.md +324 -0
- package/package.json +12 -15
- 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
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import { Aggregator, filterRows, getColumnValue, getNumber } from "../chart-data/projection-utils.js";
|
|
2
|
+
import { GROUP_KEY_SEPARATOR } from "../engine/ChartModel.js";
|
|
3
|
+
import { legendOption, textStyle, tooltipOption } from "../options/common.js";
|
|
4
|
+
const defaultFormatValue = (value)=>Math.abs(value) >= 1000 ? value.toLocaleString(void 0, {
|
|
5
|
+
maximumFractionDigits: 0
|
|
6
|
+
}) : value.toLocaleString(void 0, {
|
|
7
|
+
maximumFractionDigits: 2
|
|
8
|
+
});
|
|
9
|
+
const buildHierarchy = (rows, getGroupValue, getValue, depth, aggregation)=>{
|
|
10
|
+
const root = new Map();
|
|
11
|
+
for (const row of rows){
|
|
12
|
+
const value = getValue(row);
|
|
13
|
+
let level = root;
|
|
14
|
+
let key = "";
|
|
15
|
+
for(let d = 0; d < depth; d++){
|
|
16
|
+
const name = String(getGroupValue(row, d) ?? "");
|
|
17
|
+
key = 0 === d ? name : `${key}${GROUP_KEY_SEPARATOR}${name}`;
|
|
18
|
+
let group = level.get(name);
|
|
19
|
+
if (void 0 === group) {
|
|
20
|
+
group = {
|
|
21
|
+
name,
|
|
22
|
+
key,
|
|
23
|
+
aggregator: new Aggregator(aggregation),
|
|
24
|
+
rows: [],
|
|
25
|
+
children: d < depth - 1 ? new Map() : void 0
|
|
26
|
+
};
|
|
27
|
+
level.set(name, group);
|
|
28
|
+
}
|
|
29
|
+
group.aggregator.add(value);
|
|
30
|
+
group.rows.push(row);
|
|
31
|
+
if (group.children) level = group.children;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return Array.from(root.values());
|
|
35
|
+
};
|
|
36
|
+
const createHierarchyModel = ({ aggregation = "sum", colorMode = "palette", dataSource, donut = false, formatValue = defaultFormatValue, groupByColumnNames, legend = "none", showTooltip = true, type, valueColumnName, where })=>{
|
|
37
|
+
const depth = "pie" === type ? 1 : Math.max(1, groupByColumnNames.length);
|
|
38
|
+
const seriesId = type;
|
|
39
|
+
const tooltipFormatter = (params)=>{
|
|
40
|
+
const node = params.data;
|
|
41
|
+
if (!node) return params.name;
|
|
42
|
+
const path = node.vuuGroupKey.split(GROUP_KEY_SEPARATOR).join(" / ");
|
|
43
|
+
const percent = void 0 !== params.percent ? ` (${params.percent.toFixed(1)}%)` : "";
|
|
44
|
+
return `${path}<br/>${valueColumnName}: <b>${formatValue(node.signedValue)}</b>${percent}`;
|
|
45
|
+
};
|
|
46
|
+
const buildBaseOption = (theme)=>{
|
|
47
|
+
const labelStyle = {
|
|
48
|
+
color: theme.foregroundColor,
|
|
49
|
+
fontFamily: theme.fontFamily
|
|
50
|
+
};
|
|
51
|
+
const seriesBase = {
|
|
52
|
+
id: seriesId,
|
|
53
|
+
name: valueColumnName,
|
|
54
|
+
type,
|
|
55
|
+
data: []
|
|
56
|
+
};
|
|
57
|
+
let series;
|
|
58
|
+
series = "treemap" === type ? {
|
|
59
|
+
...seriesBase,
|
|
60
|
+
roam: false,
|
|
61
|
+
nodeClick: "zoomToNode",
|
|
62
|
+
top: 8,
|
|
63
|
+
left: 0,
|
|
64
|
+
right: 0,
|
|
65
|
+
bottom: 28,
|
|
66
|
+
visibleMin: 100,
|
|
67
|
+
breadcrumb: {
|
|
68
|
+
show: depth > 1,
|
|
69
|
+
height: 20,
|
|
70
|
+
bottom: 4,
|
|
71
|
+
itemStyle: {
|
|
72
|
+
color: theme.tooltipBackgroundColor,
|
|
73
|
+
borderColor: theme.axisLineColor,
|
|
74
|
+
textStyle: {
|
|
75
|
+
color: theme.foregroundColor
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
label: {
|
|
80
|
+
show: true,
|
|
81
|
+
...labelStyle,
|
|
82
|
+
color: "#fff",
|
|
83
|
+
formatter: (p)=>p.data ? `${p.name}\n${formatValue(p.data.signedValue)}` : p.name
|
|
84
|
+
},
|
|
85
|
+
upperLabel: {
|
|
86
|
+
show: depth > 1,
|
|
87
|
+
height: 18,
|
|
88
|
+
color: "#fff"
|
|
89
|
+
},
|
|
90
|
+
itemStyle: {
|
|
91
|
+
borderColor: theme.backgroundColor,
|
|
92
|
+
gapWidth: 1
|
|
93
|
+
},
|
|
94
|
+
levels: [
|
|
95
|
+
{
|
|
96
|
+
itemStyle: {
|
|
97
|
+
borderColor: theme.backgroundColor,
|
|
98
|
+
borderWidth: 0,
|
|
99
|
+
gapWidth: 2
|
|
100
|
+
},
|
|
101
|
+
upperLabel: {
|
|
102
|
+
show: false
|
|
103
|
+
}
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
itemStyle: {
|
|
107
|
+
borderColor: theme.splitLineColor,
|
|
108
|
+
borderWidth: 2,
|
|
109
|
+
gapWidth: 1
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
]
|
|
113
|
+
} : "sunburst" === type ? {
|
|
114
|
+
...seriesBase,
|
|
115
|
+
radius: [
|
|
116
|
+
"12%",
|
|
117
|
+
"95%"
|
|
118
|
+
],
|
|
119
|
+
sort: "desc",
|
|
120
|
+
nodeClick: "rootToNode",
|
|
121
|
+
label: {
|
|
122
|
+
...labelStyle,
|
|
123
|
+
rotate: "radial",
|
|
124
|
+
minAngle: 8,
|
|
125
|
+
color: "#fff"
|
|
126
|
+
},
|
|
127
|
+
itemStyle: {
|
|
128
|
+
borderColor: theme.backgroundColor,
|
|
129
|
+
borderWidth: 1
|
|
130
|
+
}
|
|
131
|
+
} : {
|
|
132
|
+
...seriesBase,
|
|
133
|
+
radius: donut ? [
|
|
134
|
+
"45%",
|
|
135
|
+
"75%"
|
|
136
|
+
] : "75%",
|
|
137
|
+
center: [
|
|
138
|
+
"50%",
|
|
139
|
+
"52%"
|
|
140
|
+
],
|
|
141
|
+
label: {
|
|
142
|
+
...labelStyle,
|
|
143
|
+
formatter: "{b}: {d}%"
|
|
144
|
+
},
|
|
145
|
+
labelLine: {
|
|
146
|
+
lineStyle: {
|
|
147
|
+
color: theme.axisLineColor
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
itemStyle: {
|
|
151
|
+
borderColor: theme.backgroundColor,
|
|
152
|
+
borderWidth: 1
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
return {
|
|
156
|
+
animation: false,
|
|
157
|
+
backgroundColor: theme.backgroundColor,
|
|
158
|
+
color: theme.palette,
|
|
159
|
+
textStyle: textStyle(theme),
|
|
160
|
+
legend: legendOption(theme, "pie" === type ? legend : "none"),
|
|
161
|
+
tooltip: {
|
|
162
|
+
...tooltipOption(theme, {
|
|
163
|
+
show: showTooltip,
|
|
164
|
+
trigger: "item"
|
|
165
|
+
}),
|
|
166
|
+
formatter: tooltipFormatter
|
|
167
|
+
},
|
|
168
|
+
series: [
|
|
169
|
+
series
|
|
170
|
+
]
|
|
171
|
+
};
|
|
172
|
+
};
|
|
173
|
+
const buildDataOption = (data, theme)=>{
|
|
174
|
+
const { columnMap, rows: allRows } = data.get(dataSource);
|
|
175
|
+
const rows = filterRows(allRows, columnMap, where);
|
|
176
|
+
const groups = buildHierarchy(rows, (row, d)=>getColumnValue(row, columnMap, groupByColumnNames[d]), (row)=>getNumber(row, columnMap, valueColumnName), depth, aggregation);
|
|
177
|
+
const groupRows = new Map();
|
|
178
|
+
const toNode = (group)=>{
|
|
179
|
+
groupRows.set(group.key, group.rows);
|
|
180
|
+
const signedValue = group.aggregator.value ?? 0;
|
|
181
|
+
const node = {
|
|
182
|
+
name: group.name,
|
|
183
|
+
value: "sign" === colorMode ? Math.abs(signedValue) : signedValue,
|
|
184
|
+
signedValue,
|
|
185
|
+
vuuGroupKey: group.key
|
|
186
|
+
};
|
|
187
|
+
if ("sign" === colorMode) node.itemStyle = {
|
|
188
|
+
color: signedValue < 0 ? theme.downColor : theme.upColor
|
|
189
|
+
};
|
|
190
|
+
if (group.children) {
|
|
191
|
+
node.children = Array.from(group.children.values()).map(toNode);
|
|
192
|
+
node.value = node.children.reduce((sum, child)=>sum + child.value, 0);
|
|
193
|
+
}
|
|
194
|
+
return node;
|
|
195
|
+
};
|
|
196
|
+
const nodes = groups.map(toNode).sort((n1, n2)=>Math.abs(n2.value) - Math.abs(n1.value));
|
|
197
|
+
const rowLookup = {
|
|
198
|
+
[seriesId]: {
|
|
199
|
+
dataSource,
|
|
200
|
+
rows: [],
|
|
201
|
+
groups: groupRows
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
option: {
|
|
206
|
+
series: [
|
|
207
|
+
{
|
|
208
|
+
id: seriesId,
|
|
209
|
+
data: nodes
|
|
210
|
+
}
|
|
211
|
+
]
|
|
212
|
+
},
|
|
213
|
+
rowLookup
|
|
214
|
+
};
|
|
215
|
+
};
|
|
216
|
+
return {
|
|
217
|
+
buildBaseOption,
|
|
218
|
+
buildDataOption,
|
|
219
|
+
dataSources: [
|
|
220
|
+
dataSource
|
|
221
|
+
]
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
export { buildHierarchy, createHierarchyModel };
|
package/src/echarts.js
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { BarChart, CandlestickChart, HeatmapChart, LineChart, PieChart, ScatterChart, SunburstChart, TreemapChart } from "echarts/charts";
|
|
2
|
+
import { AxisPointerComponent, DataZoomInsideComponent, DataZoomSliderComponent, GridComponent, LegendComponent, MarkLineComponent, TitleComponent, TooltipComponent, VisualMapContinuousComponent } from "echarts/components";
|
|
3
|
+
import { getInstanceByDom, init, use } from "echarts/core";
|
|
4
|
+
import { CanvasRenderer, SVGRenderer } from "echarts/renderers";
|
|
5
|
+
use([
|
|
6
|
+
BarChart,
|
|
7
|
+
CandlestickChart,
|
|
8
|
+
HeatmapChart,
|
|
9
|
+
LineChart,
|
|
10
|
+
PieChart,
|
|
11
|
+
ScatterChart,
|
|
12
|
+
SunburstChart,
|
|
13
|
+
TreemapChart,
|
|
14
|
+
AxisPointerComponent,
|
|
15
|
+
DataZoomInsideComponent,
|
|
16
|
+
DataZoomSliderComponent,
|
|
17
|
+
GridComponent,
|
|
18
|
+
LegendComponent,
|
|
19
|
+
MarkLineComponent,
|
|
20
|
+
TitleComponent,
|
|
21
|
+
TooltipComponent,
|
|
22
|
+
VisualMapContinuousComponent,
|
|
23
|
+
CanvasRenderer,
|
|
24
|
+
SVGRenderer
|
|
25
|
+
]);
|
|
26
|
+
export { getInstanceByDom, init };
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useComponentCssInjection } from "@salt-ds/styles";
|
|
3
|
+
import { useWindow } from "@salt-ds/window";
|
|
4
|
+
import { MeasuredContainer } from "@vuu-ui/vuu-ui-controls";
|
|
5
|
+
import { ThemeContext } from "@vuu-ui/vuu-utils";
|
|
6
|
+
import clsx from "clsx";
|
|
7
|
+
import { useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
8
|
+
import { ChartDataStores } from "../chart-data/ChartDataStore.js";
|
|
9
|
+
import { init } from "../echarts.js";
|
|
10
|
+
import { mergeEChartsOptions } from "../options/mergeOptions.js";
|
|
11
|
+
import { resolveChartTheme } from "../theme/chartTheme.js";
|
|
12
|
+
import { GROUP_KEY_SEPARATOR } from "./ChartModel.js";
|
|
13
|
+
import { useStructuralMemo } from "./useStructuralMemo.js";
|
|
14
|
+
import Chart from "../Chart.css";
|
|
15
|
+
const classBase = "vuuChart";
|
|
16
|
+
const applyOverride = (option, override)=>"function" == typeof override ? override(option) : mergeEChartsOptions(option, override);
|
|
17
|
+
const VuuChartContainer = ({ chartType, className, echartsOptions, height, legend: _legend, maxRows, model, onChartReady, onItemClick, onItemContextMenu, renderer = "canvas", resizeStrategy, showTooltip: _showTooltip, style = {
|
|
18
|
+
width: "100%",
|
|
19
|
+
height: "100%"
|
|
20
|
+
}, theme: themeOverrides, width, ...htmlAttributes })=>{
|
|
21
|
+
const targetWindow = useWindow();
|
|
22
|
+
useComponentCssInjection({
|
|
23
|
+
testId: "vuu-chart",
|
|
24
|
+
css: Chart,
|
|
25
|
+
window: targetWindow
|
|
26
|
+
});
|
|
27
|
+
const { themeMode } = useContext(ThemeContext);
|
|
28
|
+
const [chartElement, setChartElement] = useState(null);
|
|
29
|
+
const [chart, setChart] = useState(void 0);
|
|
30
|
+
const overrides = useStructuralMemo(themeOverrides);
|
|
31
|
+
const userOptions = useStructuralMemo(echartsOptions);
|
|
32
|
+
const dataSources = useStructuralMemo(model.dataSources);
|
|
33
|
+
const theme = useMemo(()=>resolveChartTheme(chartElement, overrides), [
|
|
34
|
+
chartElement,
|
|
35
|
+
overrides,
|
|
36
|
+
themeMode
|
|
37
|
+
]);
|
|
38
|
+
const modelRef = useRef(model);
|
|
39
|
+
modelRef.current = model;
|
|
40
|
+
const themeRef = useRef(theme);
|
|
41
|
+
themeRef.current = theme;
|
|
42
|
+
const storesRef = useRef(void 0);
|
|
43
|
+
const rowLookupRef = useRef({});
|
|
44
|
+
const frameRef = useRef(void 0);
|
|
45
|
+
const handlersRef = useRef({
|
|
46
|
+
onItemClick,
|
|
47
|
+
onItemContextMenu
|
|
48
|
+
});
|
|
49
|
+
handlersRef.current = {
|
|
50
|
+
onItemClick,
|
|
51
|
+
onItemContextMenu
|
|
52
|
+
};
|
|
53
|
+
const onChartReadyRef = useRef(onChartReady);
|
|
54
|
+
onChartReadyRef.current = onChartReady;
|
|
55
|
+
useLayoutEffect(()=>{
|
|
56
|
+
if (!chartElement) return;
|
|
57
|
+
const echart = init(chartElement, null, {
|
|
58
|
+
renderer,
|
|
59
|
+
useCoarsePointer: true
|
|
60
|
+
});
|
|
61
|
+
const toItemEvent = (params)=>{
|
|
62
|
+
const seriesId = params.seriesId ?? "";
|
|
63
|
+
const lookup = rowLookupRef.current[seriesId];
|
|
64
|
+
if (lookup) {
|
|
65
|
+
const store = storesRef.current?.get(lookup.dataSource);
|
|
66
|
+
const groupKey = params.data?.vuuGroupKey;
|
|
67
|
+
const groupRows = void 0 !== groupKey ? lookup.groups?.get(groupKey) : void 0;
|
|
68
|
+
return {
|
|
69
|
+
columnMap: store?.columnMap ?? {},
|
|
70
|
+
dataIndex: params.dataIndex,
|
|
71
|
+
dataSource: lookup.dataSource,
|
|
72
|
+
groupPath: groupKey?.split(GROUP_KEY_SEPARATOR),
|
|
73
|
+
nativeEvent: params.event?.event,
|
|
74
|
+
row: groupRows ? groupRows[0] : lookup.rows[params.dataIndex],
|
|
75
|
+
rows: groupRows,
|
|
76
|
+
seriesId,
|
|
77
|
+
seriesName: params.seriesName ?? "",
|
|
78
|
+
value: params.value
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
echart.on("click", (params)=>{
|
|
83
|
+
const { onItemClick } = handlersRef.current;
|
|
84
|
+
const evt = onItemClick && toItemEvent(params);
|
|
85
|
+
if (evt) onItemClick?.(evt);
|
|
86
|
+
});
|
|
87
|
+
echart.on("contextmenu", (params)=>{
|
|
88
|
+
const { onItemContextMenu } = handlersRef.current;
|
|
89
|
+
const evt = onItemContextMenu && toItemEvent(params);
|
|
90
|
+
if (evt) {
|
|
91
|
+
params.event?.stop();
|
|
92
|
+
onItemContextMenu?.(evt);
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
setChart(echart);
|
|
96
|
+
onChartReadyRef.current?.(echart);
|
|
97
|
+
return ()=>{
|
|
98
|
+
setChart(void 0);
|
|
99
|
+
echart.dispose();
|
|
100
|
+
};
|
|
101
|
+
}, [
|
|
102
|
+
chartElement,
|
|
103
|
+
renderer
|
|
104
|
+
]);
|
|
105
|
+
const buildFullOption = useCallback(()=>{
|
|
106
|
+
const stores = storesRef.current;
|
|
107
|
+
if (chart && !chart.isDisposed() && stores) {
|
|
108
|
+
const { buildBaseOption, buildDataOption } = modelRef.current;
|
|
109
|
+
const theme = themeRef.current;
|
|
110
|
+
const { option: dataOption, rowLookup = {} } = buildDataOption(stores, theme);
|
|
111
|
+
const option = applyOverride(mergeEChartsOptions(buildBaseOption(theme), dataOption), userOptions);
|
|
112
|
+
rowLookupRef.current = rowLookup;
|
|
113
|
+
chart.setOption(option, {
|
|
114
|
+
notMerge: true
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}, [
|
|
118
|
+
chart,
|
|
119
|
+
userOptions
|
|
120
|
+
]);
|
|
121
|
+
const applyDataUpdate = useCallback(()=>{
|
|
122
|
+
frameRef.current = void 0;
|
|
123
|
+
const stores = storesRef.current;
|
|
124
|
+
if (chart && !chart.isDisposed() && stores) {
|
|
125
|
+
const { option, rowLookup = {} } = modelRef.current.buildDataOption(stores, themeRef.current);
|
|
126
|
+
rowLookupRef.current = rowLookup;
|
|
127
|
+
chart.setOption(option, {
|
|
128
|
+
lazyUpdate: false,
|
|
129
|
+
silent: true
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}, [
|
|
133
|
+
chart
|
|
134
|
+
]);
|
|
135
|
+
const scheduleDataUpdate = useCallback(()=>{
|
|
136
|
+
if (void 0 === frameRef.current) frameRef.current = requestAnimationFrame(applyDataUpdate);
|
|
137
|
+
}, [
|
|
138
|
+
applyDataUpdate
|
|
139
|
+
]);
|
|
140
|
+
useEffect(()=>{
|
|
141
|
+
if (!chart) return;
|
|
142
|
+
const stores = new ChartDataStores(dataSources, {
|
|
143
|
+
maxRows
|
|
144
|
+
}, scheduleDataUpdate);
|
|
145
|
+
storesRef.current = stores;
|
|
146
|
+
stores.subscribe();
|
|
147
|
+
return ()=>{
|
|
148
|
+
if (void 0 !== frameRef.current) {
|
|
149
|
+
cancelAnimationFrame(frameRef.current);
|
|
150
|
+
frameRef.current = void 0;
|
|
151
|
+
}
|
|
152
|
+
stores.dispose();
|
|
153
|
+
storesRef.current = void 0;
|
|
154
|
+
};
|
|
155
|
+
}, [
|
|
156
|
+
chart,
|
|
157
|
+
dataSources,
|
|
158
|
+
maxRows,
|
|
159
|
+
scheduleDataUpdate
|
|
160
|
+
]);
|
|
161
|
+
useEffect(()=>{
|
|
162
|
+
buildFullOption();
|
|
163
|
+
}, [
|
|
164
|
+
buildFullOption,
|
|
165
|
+
dataSources,
|
|
166
|
+
maxRows,
|
|
167
|
+
model,
|
|
168
|
+
theme
|
|
169
|
+
]);
|
|
170
|
+
const onResize = useCallback((size)=>{
|
|
171
|
+
chart?.resize({
|
|
172
|
+
height: size.height,
|
|
173
|
+
width: size.width
|
|
174
|
+
});
|
|
175
|
+
}, [
|
|
176
|
+
chart
|
|
177
|
+
]);
|
|
178
|
+
return /*#__PURE__*/ jsx(MeasuredContainer, {
|
|
179
|
+
...htmlAttributes,
|
|
180
|
+
className: clsx(classBase, `${classBase}-${chartType}`, className),
|
|
181
|
+
height: height,
|
|
182
|
+
onResize: onResize,
|
|
183
|
+
resizeStrategy: resizeStrategy,
|
|
184
|
+
style: style,
|
|
185
|
+
width: width,
|
|
186
|
+
children: /*#__PURE__*/ jsx("div", {
|
|
187
|
+
className: `${classBase}-canvas`,
|
|
188
|
+
ref: setChartElement,
|
|
189
|
+
style: {
|
|
190
|
+
height: "100%",
|
|
191
|
+
width: "100%"
|
|
192
|
+
}
|
|
193
|
+
})
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
export { VuuChartContainer };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { useRef } from "react";
|
|
2
|
+
const isPlainObject = (value)=>null !== value && "object" == typeof value && (Object.getPrototypeOf(value) === Object.prototype || null === Object.getPrototypeOf(value));
|
|
3
|
+
const isStructurallyEqual = (a, b)=>{
|
|
4
|
+
if (a === b) return true;
|
|
5
|
+
if (Array.isArray(a) && Array.isArray(b)) return a.length === b.length && a.every((v, i)=>isStructurallyEqual(v, b[i]));
|
|
6
|
+
if (isPlainObject(a) && isPlainObject(b)) {
|
|
7
|
+
const keysA = Object.keys(a);
|
|
8
|
+
const keysB = Object.keys(b);
|
|
9
|
+
return keysA.length === keysB.length && keysA.every((key)=>isStructurallyEqual(a[key], b[key]));
|
|
10
|
+
}
|
|
11
|
+
return false;
|
|
12
|
+
};
|
|
13
|
+
const useStructuralMemo = (value)=>{
|
|
14
|
+
const ref = useRef(value);
|
|
15
|
+
if (!isStructurallyEqual(ref.current, value)) ref.current = value;
|
|
16
|
+
return ref.current;
|
|
17
|
+
};
|
|
18
|
+
export { isStructurallyEqual, useStructuralMemo };
|
package/src/index.js
CHANGED
|
@@ -1,2 +1,10 @@
|
|
|
1
1
|
export { Chart } from "./Chart.js";
|
|
2
2
|
export { ChartSeries } from "./ChartSeries.js";
|
|
3
|
+
export { CandlestickChart } from "./charts/CandlestickChart.js";
|
|
4
|
+
export { CartesianChart } from "./charts/CartesianChart.js";
|
|
5
|
+
export { DepthChart } from "./charts/DepthChart.js";
|
|
6
|
+
export { HeatmapChart } from "./charts/HeatmapChart.js";
|
|
7
|
+
export { HierarchyChart, PieChart, SunburstChart, TreemapChart } from "./charts/HierarchyChart.js";
|
|
8
|
+
export { ChartDataStore } from "./chart-data/ChartDataStore.js";
|
|
9
|
+
export { mergeEChartsOptions } from "./options/mergeOptions.js";
|
|
10
|
+
export { defaultChartTheme, resolveChartTheme } from "./theme/chartTheme.js";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { resolveColor } from "../theme/chartTheme.js";
|
|
2
|
+
import { computeMarkLineValue } from "../chart-data/projection-utils.js";
|
|
3
|
+
const pad = (n, width = 2)=>String(n).padStart(width, "0");
|
|
4
|
+
const formatTimeLabel = (ts)=>{
|
|
5
|
+
if (!Number.isFinite(ts)) return "";
|
|
6
|
+
const d = new Date(ts);
|
|
7
|
+
const h = d.getHours();
|
|
8
|
+
const m = d.getMinutes();
|
|
9
|
+
const s = d.getSeconds();
|
|
10
|
+
if (0 === h && 0 === m && 0 === s) return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}`;
|
|
11
|
+
return 0 === s ? `${pad(h)}:${pad(m)}` : `${pad(h)}:${pad(m)}:${pad(s)}`;
|
|
12
|
+
};
|
|
13
|
+
const formatTimestamp = (ts)=>{
|
|
14
|
+
if (!Number.isFinite(ts)) return "";
|
|
15
|
+
const d = new Date(ts);
|
|
16
|
+
return `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${pad(d.getHours())}:${pad(d.getMinutes())}:${pad(d.getSeconds())}`;
|
|
17
|
+
};
|
|
18
|
+
const textStyle = (theme)=>({
|
|
19
|
+
color: theme.foregroundColor,
|
|
20
|
+
fontFamily: theme.fontFamily
|
|
21
|
+
});
|
|
22
|
+
const GRID_OUTER_BOUNDS = {
|
|
23
|
+
left: 4,
|
|
24
|
+
right: 12,
|
|
25
|
+
top: 0,
|
|
26
|
+
bottom: 0
|
|
27
|
+
};
|
|
28
|
+
const axisStyle = (theme, showSplitLine = true)=>({
|
|
29
|
+
axisLine: {
|
|
30
|
+
lineStyle: {
|
|
31
|
+
color: theme.axisLineColor
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
axisTick: {
|
|
35
|
+
lineStyle: {
|
|
36
|
+
color: theme.axisLineColor
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
axisLabel: {
|
|
40
|
+
color: theme.secondaryForegroundColor,
|
|
41
|
+
fontFamily: theme.fontFamily,
|
|
42
|
+
hideOverlap: true
|
|
43
|
+
},
|
|
44
|
+
nameTextStyle: {
|
|
45
|
+
color: theme.secondaryForegroundColor,
|
|
46
|
+
fontFamily: theme.fontFamily
|
|
47
|
+
},
|
|
48
|
+
splitLine: {
|
|
49
|
+
show: showSplitLine,
|
|
50
|
+
lineStyle: {
|
|
51
|
+
color: theme.splitLineColor
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
const axisPointerLabel = (theme)=>({
|
|
56
|
+
backgroundColor: theme.crosshairColor,
|
|
57
|
+
color: theme.tooltipBackgroundColor,
|
|
58
|
+
fontFamily: theme.fontFamily
|
|
59
|
+
});
|
|
60
|
+
const tooltipOption = (theme, { crosshair = true, show = true, trigger = "axis" } = {})=>({
|
|
61
|
+
show,
|
|
62
|
+
trigger,
|
|
63
|
+
confine: true,
|
|
64
|
+
className: "vuuChartTooltip",
|
|
65
|
+
backgroundColor: theme.tooltipBackgroundColor,
|
|
66
|
+
borderColor: theme.tooltipBorderColor,
|
|
67
|
+
textStyle: {
|
|
68
|
+
...textStyle(theme),
|
|
69
|
+
fontSize: 12
|
|
70
|
+
},
|
|
71
|
+
axisPointer: "axis" === trigger ? {
|
|
72
|
+
type: crosshair ? "cross" : "line",
|
|
73
|
+
lineStyle: {
|
|
74
|
+
color: theme.crosshairColor,
|
|
75
|
+
type: "dashed"
|
|
76
|
+
},
|
|
77
|
+
crossStyle: {
|
|
78
|
+
color: theme.crosshairColor
|
|
79
|
+
},
|
|
80
|
+
label: axisPointerLabel(theme)
|
|
81
|
+
} : void 0
|
|
82
|
+
});
|
|
83
|
+
const legendOption = (theme, position)=>"none" === position ? {
|
|
84
|
+
show: false
|
|
85
|
+
} : {
|
|
86
|
+
show: true,
|
|
87
|
+
type: "scroll",
|
|
88
|
+
[position]: 0,
|
|
89
|
+
textStyle: {
|
|
90
|
+
color: theme.secondaryForegroundColor,
|
|
91
|
+
fontFamily: theme.fontFamily
|
|
92
|
+
},
|
|
93
|
+
pageTextStyle: {
|
|
94
|
+
color: theme.secondaryForegroundColor
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
const hasSlider = (dataZoom)=>"slider" === dataZoom || "both" === dataZoom;
|
|
98
|
+
const dataZoomOption = (dataZoom, theme, axisIndex = [
|
|
99
|
+
0
|
|
100
|
+
], orientation = "x")=>{
|
|
101
|
+
if (!dataZoom) return [];
|
|
102
|
+
const axisKey = "x" === orientation ? "xAxisIndex" : "yAxisIndex";
|
|
103
|
+
const zoom = [];
|
|
104
|
+
if (true === dataZoom || "inside" === dataZoom || "both" === dataZoom) zoom.push({
|
|
105
|
+
id: "zoom-inside",
|
|
106
|
+
type: "inside",
|
|
107
|
+
[axisKey]: axisIndex,
|
|
108
|
+
filterMode: "filter"
|
|
109
|
+
});
|
|
110
|
+
if (hasSlider(dataZoom)) zoom.push({
|
|
111
|
+
id: "zoom-slider",
|
|
112
|
+
type: "slider",
|
|
113
|
+
[axisKey]: axisIndex,
|
|
114
|
+
height: 20,
|
|
115
|
+
bottom: 4,
|
|
116
|
+
borderColor: theme.axisLineColor,
|
|
117
|
+
backgroundColor: "transparent",
|
|
118
|
+
fillerColor: "rgba(127,127,127,0.15)",
|
|
119
|
+
dataBackground: {
|
|
120
|
+
lineStyle: {
|
|
121
|
+
color: theme.axisLineColor
|
|
122
|
+
},
|
|
123
|
+
areaStyle: {
|
|
124
|
+
color: theme.splitLineColor
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
handleStyle: {
|
|
128
|
+
color: theme.tooltipBackgroundColor,
|
|
129
|
+
borderColor: theme.axisLineColor
|
|
130
|
+
},
|
|
131
|
+
moveHandleStyle: {
|
|
132
|
+
color: theme.axisLineColor
|
|
133
|
+
},
|
|
134
|
+
textStyle: {
|
|
135
|
+
color: theme.secondaryForegroundColor
|
|
136
|
+
},
|
|
137
|
+
filterMode: "filter"
|
|
138
|
+
});
|
|
139
|
+
return zoom;
|
|
140
|
+
};
|
|
141
|
+
const formatMarkLineValue = (value)=>Math.abs(value) >= 1000 ? value.toFixed(0) : Number(value.toPrecision(6)).toString();
|
|
142
|
+
const markLineOption = (defs, theme, values, { axis = "yAxis", defaultColor, weights } = {})=>{
|
|
143
|
+
if (!defs || 0 === defs.length) return;
|
|
144
|
+
const data = [];
|
|
145
|
+
for (const def of defs){
|
|
146
|
+
const value = computeMarkLineValue(def, values, weights);
|
|
147
|
+
if (null !== value) {
|
|
148
|
+
const color = resolveColor(def.color, theme) ?? defaultColor ?? theme.crosshairColor;
|
|
149
|
+
data.push({
|
|
150
|
+
name: def.label ?? "",
|
|
151
|
+
[axis]: value,
|
|
152
|
+
lineStyle: {
|
|
153
|
+
color,
|
|
154
|
+
type: def.lineType ?? "dashed",
|
|
155
|
+
width: 1
|
|
156
|
+
},
|
|
157
|
+
label: {
|
|
158
|
+
color: theme.tooltipBackgroundColor,
|
|
159
|
+
backgroundColor: color,
|
|
160
|
+
padding: [
|
|
161
|
+
2,
|
|
162
|
+
4
|
|
163
|
+
],
|
|
164
|
+
borderRadius: 2,
|
|
165
|
+
position: def.labelPosition ?? "insideEndTop",
|
|
166
|
+
formatter: def.label ? `${def.label} ${formatMarkLineValue(value)}` : formatMarkLineValue(value)
|
|
167
|
+
}
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
silent: true,
|
|
173
|
+
symbol: [
|
|
174
|
+
"none",
|
|
175
|
+
"none"
|
|
176
|
+
],
|
|
177
|
+
animation: false,
|
|
178
|
+
data
|
|
179
|
+
};
|
|
180
|
+
};
|
|
181
|
+
export { GRID_OUTER_BOUNDS, axisPointerLabel, axisStyle, dataZoomOption, formatTimeLabel, formatTimestamp, hasSlider, legendOption, markLineOption, textStyle, tooltipOption };
|