@trackunit/react-chart-components 1.3.86 → 1.3.88
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/index.cjs.js +380 -96
- package/index.esm.js +361 -97
- package/package.json +5 -4
- package/src/Chart/Chart.d.ts +16 -20
- package/src/{PieChart/PieChart.d.ts → DonutChart/DonutChart.d.ts} +23 -5
- package/src/EChart/EChart.d.ts +43 -0
- package/src/LegendItem/LegendItem.d.ts +5 -2
- package/src/index.d.ts +4 -3
- package/src/utils/useChartColor.d.ts +11 -0
- package/src/utils/useLimitDataSet.d.ts +17 -0
- package/src/Chart/index.d.ts +0 -1
- package/src/LegendItem/index.d.ts +0 -1
- package/src/PieChart/index.d.ts +0 -1
package/index.esm.js
CHANGED
|
@@ -1,61 +1,170 @@
|
|
|
1
1
|
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
2
|
+
import { Spinner, Text } from '@trackunit/react-components';
|
|
2
3
|
import { cvaMerge } from '@trackunit/css-class-variance-utilities';
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
5
|
-
import { useRef, useEffect } from 'react';
|
|
4
|
+
import { objectKeys } from '@trackunit/shared-utils';
|
|
5
|
+
import * as echarts from 'echarts';
|
|
6
|
+
import { useRef, useCallback, useEffect, useMemo, useState } from 'react';
|
|
7
|
+
import { DEFAULT_CHART_COLORS, CHART_STATUS_COLORS, DEFAULT_CHART_OTHER } from '@trackunit/ui-design-tokens';
|
|
6
8
|
|
|
9
|
+
function isECElementEvent(value) {
|
|
10
|
+
return (typeof value === "object" &&
|
|
11
|
+
value !== null &&
|
|
12
|
+
"type" in value &&
|
|
13
|
+
"componentType" in value &&
|
|
14
|
+
"componentSubType" in value &&
|
|
15
|
+
"componentIndex" in value &&
|
|
16
|
+
"seriesIndex" in value);
|
|
17
|
+
}
|
|
7
18
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* Handles events and click functionality.
|
|
11
|
-
*
|
|
12
|
-
* For more information see the [eCharts documentation](https://github.com/hustcc/echarts-for-react).
|
|
13
|
-
*
|
|
14
|
-
* @param {ChartProps} props - The props for the Chart component
|
|
15
|
-
* @returns {ReactElement} Chart component
|
|
19
|
+
* A React wrapper for ECharts that handles initialization, events, and cleanup.
|
|
16
20
|
*/
|
|
17
|
-
const
|
|
18
|
-
const
|
|
21
|
+
const EChart = ({ option, style, className, onChartReady, onClick, onEvents, notMerge = false, renderer = "canvas", dataTestId, }) => {
|
|
22
|
+
const containerRef = useRef(null);
|
|
23
|
+
const internalChartRef = useRef(undefined);
|
|
24
|
+
const isInitialRender = useRef(true);
|
|
25
|
+
const setupEventHandlers = useCallback((chart) => {
|
|
26
|
+
// Handle click events
|
|
27
|
+
if (onClick) {
|
|
28
|
+
chart.on("click", function (event) {
|
|
29
|
+
if (isECElementEvent(event)) {
|
|
30
|
+
onClick(event);
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
const zr = chart.getZr();
|
|
34
|
+
zr.setCursorStyle("pointer");
|
|
35
|
+
}
|
|
36
|
+
// Handle other events
|
|
37
|
+
if (onEvents) {
|
|
38
|
+
Object.entries(onEvents).forEach(([eventName, handler]) => {
|
|
39
|
+
if (eventName !== "click" || !onClick) {
|
|
40
|
+
// Don't double-register click if onClick is provided
|
|
41
|
+
chart.on(eventName, function (event) {
|
|
42
|
+
if (isECElementEvent(event)) {
|
|
43
|
+
handler(event);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [onClick, onEvents]);
|
|
50
|
+
const initChart = useCallback(() => {
|
|
51
|
+
const container = containerRef.current;
|
|
52
|
+
if (!container) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// Dispose existing instance if any
|
|
56
|
+
if (internalChartRef.current) {
|
|
57
|
+
internalChartRef.current.dispose();
|
|
58
|
+
internalChartRef.current = undefined;
|
|
59
|
+
}
|
|
60
|
+
// Create new chart instance
|
|
61
|
+
const newChart = echarts.init(container, undefined, {
|
|
62
|
+
renderer,
|
|
63
|
+
width: "auto",
|
|
64
|
+
height: "auto",
|
|
65
|
+
});
|
|
66
|
+
// Store reference and notify ready
|
|
67
|
+
internalChartRef.current = newChart;
|
|
68
|
+
if (onChartReady) {
|
|
69
|
+
onChartReady(newChart);
|
|
70
|
+
}
|
|
71
|
+
}, [renderer, onChartReady]);
|
|
72
|
+
// Initialize chart and handle cleanup
|
|
19
73
|
useEffect(() => {
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
74
|
+
initChart();
|
|
75
|
+
isInitialRender.current = true;
|
|
76
|
+
const resizeObserver = new ResizeObserver(() => {
|
|
77
|
+
const chart = internalChartRef.current;
|
|
78
|
+
if (!chart) {
|
|
79
|
+
return;
|
|
24
80
|
}
|
|
25
|
-
|
|
26
|
-
|
|
81
|
+
if (!chart.isDisposed()) {
|
|
82
|
+
chart.resize();
|
|
27
83
|
}
|
|
84
|
+
});
|
|
85
|
+
const container = containerRef.current;
|
|
86
|
+
if (container) {
|
|
87
|
+
resizeObserver.observe(container);
|
|
88
|
+
}
|
|
89
|
+
return () => {
|
|
90
|
+
resizeObserver.disconnect();
|
|
91
|
+
const chart = internalChartRef.current;
|
|
92
|
+
if (!chart) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
if (!chart.isDisposed()) {
|
|
96
|
+
const zr = chart.getZr();
|
|
97
|
+
zr.setCursorStyle("default");
|
|
98
|
+
chart.dispose();
|
|
99
|
+
internalChartRef.current = undefined;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}, [initChart]);
|
|
103
|
+
// Setup event handlers
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
const chart = internalChartRef.current;
|
|
106
|
+
if (chart && !chart.isDisposed()) {
|
|
107
|
+
// Setup handlers
|
|
108
|
+
setupEventHandlers(chart);
|
|
28
109
|
}
|
|
29
110
|
return () => {
|
|
30
|
-
if (
|
|
31
|
-
|
|
111
|
+
if (chart && !chart.isDisposed()) {
|
|
112
|
+
// Clean up all event handlers
|
|
113
|
+
chart.off("click");
|
|
114
|
+
if (onEvents) {
|
|
115
|
+
objectKeys(onEvents).forEach(eventName => {
|
|
116
|
+
chart.off(eventName);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
32
119
|
}
|
|
33
120
|
};
|
|
34
|
-
}, [
|
|
35
|
-
|
|
121
|
+
}, [onEvents, setupEventHandlers]);
|
|
122
|
+
// Handle options updates
|
|
123
|
+
useEffect(() => {
|
|
124
|
+
const chart = internalChartRef.current;
|
|
125
|
+
if (!chart) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!chart.isDisposed()) {
|
|
129
|
+
// Force notMerge on initial render, respect prop afterwards
|
|
130
|
+
chart.setOption(option, isInitialRender.current || notMerge);
|
|
131
|
+
isInitialRender.current = false;
|
|
132
|
+
}
|
|
133
|
+
}, [option, notMerge]);
|
|
134
|
+
return jsx("div", { className: cvaEChart({ className }), "data-testid": dataTestId, ref: containerRef, style: style });
|
|
135
|
+
};
|
|
136
|
+
const cvaEChart = cvaMerge(["w-full", "h-full"]);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The Chart component is a wrapper component for ECharts.
|
|
140
|
+
* Handles events and click functionality.
|
|
141
|
+
*/
|
|
142
|
+
const Chart = ({ options, unsafeOptions, showLoading, className, style, onChartReady, onClick, onEvents, merge = true, loadingOption, timezoneLabel, renderer = "canvas", dataTestId, }) => {
|
|
143
|
+
const containerStyle = {
|
|
144
|
+
height: style?.height ?? "300px",
|
|
145
|
+
width: style?.width ?? "100%",
|
|
146
|
+
...style,
|
|
147
|
+
};
|
|
148
|
+
return (jsx("div", { className: "grid h-full w-full grid-cols-1 grid-rows-1 items-center gap-1", "data-testid": dataTestId, style: containerStyle, children: showLoading ? (jsxs("div", { className: "grid place-items-center overflow-y-auto", "data-testid": `${dataTestId}-loading`, children: [jsx(Spinner, { centering: "centered" }), loadingOption?.text ? (jsx(Text, { align: "center", subtle: true, children: loadingOption.text })) : null] })) : (jsxs(Fragment, { children: [jsx(EChart, { className: className, dataTestId: `${dataTestId}-chart`, notMerge: !merge, onChartReady: onChartReady, onClick: onClick, onEvents: onEvents, option: options ?? unsafeOptions, renderer: renderer, style: style }), timezoneLabel] })) }));
|
|
36
149
|
};
|
|
37
|
-
const cvaChartContainer = cvaMerge(["grid", "gap-1", "grid-rows-1", "grid-cols-1", "h-full", "w-full", "items-center"]);
|
|
38
|
-
const cvaChartSpinnerContainer = cvaMerge(["grid", "place-items-center", "overflow-y-auto"]);
|
|
39
150
|
|
|
40
151
|
const cvaLegendItem = cvaMerge([
|
|
41
|
-
"
|
|
152
|
+
"flex",
|
|
42
153
|
"items-center",
|
|
43
154
|
"transition-all",
|
|
44
155
|
"ease-in-out",
|
|
45
156
|
"duration-200",
|
|
46
|
-
"text-
|
|
47
|
-
"hover:text-
|
|
48
|
-
"text-sm",
|
|
49
|
-
"gap-2",
|
|
157
|
+
"text-secondary-600",
|
|
158
|
+
"hover:text-secondary-900",
|
|
50
159
|
], {
|
|
51
160
|
variants: {
|
|
52
161
|
selected: {
|
|
53
162
|
false: "",
|
|
54
|
-
true: "font-
|
|
163
|
+
true: ["font-semibold", "text-secondary-900"],
|
|
55
164
|
},
|
|
56
165
|
disabled: {
|
|
57
166
|
false: "",
|
|
58
|
-
true: "text-
|
|
167
|
+
true: ["text-secondary-400", "hover:text-secondary-400"],
|
|
59
168
|
},
|
|
60
169
|
isClickable: {
|
|
61
170
|
false: "cursor-default",
|
|
@@ -68,11 +177,11 @@ const cvaLegendItem = cvaMerge([
|
|
|
68
177
|
isClickable: false,
|
|
69
178
|
},
|
|
70
179
|
});
|
|
71
|
-
const cvaLegendItemIndicator = cvaMerge(["w-3", "h-3", "rounded-[50%]"], {
|
|
180
|
+
const cvaLegendItemIndicator = cvaMerge(["w-3", "mr-1", "h-3", "rounded-[50%]", "flex-none"], {
|
|
72
181
|
variants: {
|
|
73
182
|
selected: {
|
|
74
183
|
false: "",
|
|
75
|
-
true: "
|
|
184
|
+
true: "",
|
|
76
185
|
},
|
|
77
186
|
},
|
|
78
187
|
defaultVariants: {
|
|
@@ -86,77 +195,232 @@ const cvaLegendItemIndicator = cvaMerge(["w-3", "h-3", "rounded-[50%]"], {
|
|
|
86
195
|
* @param {LegendItem} props - The props for the LegendItem component
|
|
87
196
|
* @returns {ReactElement} LegendItem component
|
|
88
197
|
*/
|
|
89
|
-
const LegendItem = ({ className, label, disabled, selected, onClick, color, dataTestId }) => {
|
|
198
|
+
const LegendItem = ({ className, count, label, disabled, selected, onClick, color, dataTestId, onMouseEnter, onMouseLeave, }) => {
|
|
90
199
|
const handleOnClick = onClick && !disabled ? () => onClick() : undefined;
|
|
91
|
-
return (jsxs("div", { className: cvaLegendItem({ disabled, selected, isClickable: Boolean(onClick), className }), "data-testid": dataTestId, onClick: handleOnClick, children: [jsx("div", { className: cvaLegendItemIndicator({ selected }), "data-testid": dataTestId ? `${dataTestId}-indicator` : null, style: { backgroundColor: color } }),
|
|
200
|
+
return (jsxs("div", { className: cvaLegendItem({ disabled, selected, isClickable: !disabled && Boolean(onClick), className }), "data-testid": dataTestId, onClick: handleOnClick, onMouseEnter: onMouseEnter, onMouseLeave: onMouseLeave, children: [jsx("div", { className: cvaLegendItemIndicator({ selected }), "data-testid": dataTestId ? `${dataTestId}-indicator` : null, style: { backgroundColor: color } }), jsxs("p", { className: "truncate text-xs", children: [label, "\u00A0"] }), jsxs("p", { className: "text-xs", children: ["(", count || 0, ")"] })] }));
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Re-ordered chart colors to ensure that adjacent colors are visually different.
|
|
205
|
+
*
|
|
206
|
+
* @returns {string[]} The reordered chart colors.
|
|
207
|
+
*/
|
|
208
|
+
const useChartColor = () => {
|
|
209
|
+
const chartColor = useCallback((index) => {
|
|
210
|
+
return DEFAULT_CHART_COLORS[index % DEFAULT_CHART_COLORS.length] ?? "#000000";
|
|
211
|
+
}, []);
|
|
212
|
+
const chartColorArray = useCallback((total) => {
|
|
213
|
+
return Array.from({ length: total }, (_, index) => chartColor(index));
|
|
214
|
+
}, [chartColor]);
|
|
215
|
+
const chartStatusColor = useCallback((status) => {
|
|
216
|
+
return CHART_STATUS_COLORS[status];
|
|
217
|
+
}, []);
|
|
218
|
+
return {
|
|
219
|
+
chartColor,
|
|
220
|
+
chartColorArray,
|
|
221
|
+
chartStatusColor,
|
|
222
|
+
};
|
|
223
|
+
};
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Limits the data set to the given limit.
|
|
227
|
+
* If the data set is larger than the limit, the data set is limited to the limit and the rest of the data is added to the "Others" group.
|
|
228
|
+
*
|
|
229
|
+
* @param data - The data set to limit.
|
|
230
|
+
* @param limit - The limit to apply to the data set.
|
|
231
|
+
* @returns {object} The limited data set with and without the "others" category.
|
|
232
|
+
*/
|
|
233
|
+
const useLimitDataSet = (data, limit) => {
|
|
234
|
+
const limitedData = useMemo(() => {
|
|
235
|
+
const sortedData = data.sort((a, b) => (b.count ?? 0) - (a.count ?? 0));
|
|
236
|
+
if (sortedData.length > limit) {
|
|
237
|
+
const result = sortedData.slice(0, limit);
|
|
238
|
+
result.push({
|
|
239
|
+
id: "defaultOther",
|
|
240
|
+
name: "Others",
|
|
241
|
+
selected: false,
|
|
242
|
+
count: sortedData.slice(limit).reduce((acc, curr) => acc + (curr.count ?? 0), 0),
|
|
243
|
+
color: DEFAULT_CHART_OTHER,
|
|
244
|
+
original: { defaultOther: true },
|
|
245
|
+
});
|
|
246
|
+
return result;
|
|
247
|
+
}
|
|
248
|
+
return sortedData;
|
|
249
|
+
}, [data, limit]);
|
|
250
|
+
const limitedDataWithoutOthers = useMemo(() => limitedData.filter(item => !item.original?.defaultOther), [limitedData]);
|
|
251
|
+
return { limitedData, limitedDataWithoutOthers };
|
|
92
252
|
};
|
|
93
253
|
|
|
94
254
|
/**
|
|
95
|
-
* Create a
|
|
255
|
+
* Create a DonutChart with legends based on our current Chart component
|
|
96
256
|
*
|
|
97
|
-
* @param {
|
|
257
|
+
* @param {DonutChartProps} props - The props for the Chart component
|
|
98
258
|
* @returns {ReactElement} Chart component
|
|
99
259
|
*/
|
|
100
|
-
const
|
|
101
|
-
const
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
260
|
+
const DonutChart = ({ data, size = "full", loading, onClick, className, dataTestId, legendPosition = "Right", maxDataPoints = 6, showOthers = true, }) => {
|
|
261
|
+
const containerRef = useRef(null);
|
|
262
|
+
const chartRef = useRef(null);
|
|
263
|
+
const totalCount = useMemo(() => data?.map(item => item.count ?? 0).reduce((a, b) => a + b, 0) ?? 0, [data]);
|
|
264
|
+
const [hoveringItem, setHoveringItem] = useState(null);
|
|
265
|
+
const currentCount = useMemo(() => hoveringItem?.count ?? totalCount, [hoveringItem, totalCount]);
|
|
266
|
+
const { limitedData, limitedDataWithoutOthers } = useLimitDataSet(data ?? [], maxDataPoints);
|
|
267
|
+
const { chartColor } = useChartColor();
|
|
268
|
+
const handleChartReady = useCallback((chart) => {
|
|
269
|
+
chartRef.current = chart;
|
|
270
|
+
}, []);
|
|
271
|
+
const handleChartClick = useCallback((evt) => {
|
|
272
|
+
if (onClick && data) {
|
|
273
|
+
const clickedEntry = data.find(x => {
|
|
274
|
+
if (!evt.data || typeof evt.data !== "object" || !("id" in evt.data)) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
return x.id === evt.data.id;
|
|
278
|
+
});
|
|
279
|
+
if (clickedEntry) {
|
|
280
|
+
onClick(clickedEntry);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
}, [onClick, data]);
|
|
284
|
+
const handleChartEvents = useMemo(() => ({
|
|
285
|
+
mouseover: (evt) => {
|
|
286
|
+
const hoveredItem = data?.[evt.dataIndex];
|
|
287
|
+
if (hoveredItem) {
|
|
288
|
+
setHoveringItem(hoveredItem);
|
|
289
|
+
}
|
|
290
|
+
},
|
|
291
|
+
mouseout: () => {
|
|
292
|
+
setHoveringItem(null);
|
|
293
|
+
},
|
|
294
|
+
}), [data, setHoveringItem]);
|
|
295
|
+
const handleLegendMouseEnter = useCallback((item) => {
|
|
296
|
+
setHoveringItem(item);
|
|
297
|
+
const chart = chartRef.current;
|
|
298
|
+
if (!chart) {
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
// Find the index in the current chart data
|
|
302
|
+
const index = (showOthers ? limitedData : limitedDataWithoutOthers).findIndex(d => d.id === item.id);
|
|
303
|
+
if (index !== -1) {
|
|
304
|
+
chart.dispatchAction({
|
|
305
|
+
type: "highlight",
|
|
306
|
+
seriesIndex: 0,
|
|
307
|
+
dataIndex: index,
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
}, [limitedData, limitedDataWithoutOthers, showOthers]);
|
|
311
|
+
const handleLegendMouseLeave = useCallback(() => {
|
|
312
|
+
setHoveringItem(null);
|
|
313
|
+
const chart = chartRef.current;
|
|
314
|
+
if (!chart) {
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
chart.dispatchAction({
|
|
318
|
+
type: "downplay",
|
|
319
|
+
seriesIndex: 0,
|
|
320
|
+
});
|
|
321
|
+
}, []);
|
|
322
|
+
const chartOptions = useMemo(() => {
|
|
323
|
+
return {
|
|
324
|
+
legend: {
|
|
325
|
+
show: false,
|
|
326
|
+
},
|
|
327
|
+
tooltip: {
|
|
328
|
+
show: false,
|
|
329
|
+
},
|
|
330
|
+
series: [
|
|
331
|
+
{
|
|
332
|
+
type: "pie",
|
|
333
|
+
radius: ["50%", "75%"],
|
|
334
|
+
center: ["50%", "50%"],
|
|
335
|
+
minAngle: 5,
|
|
336
|
+
selectedOffset: 8,
|
|
337
|
+
itemStyle: {
|
|
338
|
+
borderColor: "#ffffff",
|
|
339
|
+
borderWidth: 2,
|
|
340
|
+
},
|
|
341
|
+
avoidLabelOverlap: false,
|
|
342
|
+
label: {
|
|
343
|
+
show: true,
|
|
344
|
+
position: "center",
|
|
345
|
+
fontSize: size === "full" ? 18 : 12,
|
|
346
|
+
fontWeight: "bold",
|
|
347
|
+
formatter: currentCount.toString(),
|
|
348
|
+
},
|
|
349
|
+
emphasis: {
|
|
350
|
+
label: {
|
|
351
|
+
show: true,
|
|
352
|
+
formatter: "{c}",
|
|
114
353
|
},
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
show: !hovering,
|
|
129
|
-
position: "center",
|
|
130
|
-
fontSize: size === "full" ? 18 : 12,
|
|
131
|
-
fontWeight: "bold",
|
|
132
|
-
formatter: totalCount.toString(),
|
|
133
|
-
},
|
|
134
|
-
emphasis: {
|
|
135
|
-
label: {
|
|
136
|
-
show: hovering,
|
|
137
|
-
formatter: "{c}",
|
|
138
|
-
},
|
|
139
|
-
},
|
|
140
|
-
labelLine: {
|
|
141
|
-
show: false,
|
|
142
|
-
},
|
|
143
|
-
data: data?.map(item => ({
|
|
144
|
-
id: item.id,
|
|
145
|
-
name: item.name,
|
|
146
|
-
value: item.count ?? 0,
|
|
147
|
-
itemStyle: { color: item.color },
|
|
148
|
-
})),
|
|
354
|
+
},
|
|
355
|
+
labelLine: {
|
|
356
|
+
show: false,
|
|
357
|
+
},
|
|
358
|
+
data: (showOthers ? limitedData : limitedDataWithoutOthers)
|
|
359
|
+
.map((item, index) => {
|
|
360
|
+
const itemColor = item.color ?? chartColor(index);
|
|
361
|
+
return {
|
|
362
|
+
id: item.id,
|
|
363
|
+
name: item.name,
|
|
364
|
+
value: item.count ?? 0,
|
|
365
|
+
itemStyle: {
|
|
366
|
+
color: itemColor,
|
|
149
367
|
},
|
|
150
|
-
|
|
151
|
-
|
|
368
|
+
emphasis: {
|
|
369
|
+
disabled: false,
|
|
370
|
+
scale: true,
|
|
371
|
+
scaleSize: 5,
|
|
372
|
+
},
|
|
373
|
+
selected: hoveringItem?.id === item.id,
|
|
374
|
+
};
|
|
375
|
+
})
|
|
376
|
+
.filter(item => item.value),
|
|
377
|
+
},
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
}, [size, currentCount, showOthers, limitedData, limitedDataWithoutOthers, chartColor, hoveringItem?.id]);
|
|
381
|
+
return (jsxs("div", { className: cvaChartRoot({ className, legendPosition }), "data-testid": dataTestId, ref: containerRef, children: [jsx("div", { className: cvaChartContainer({ legendPosition }), children: jsx(Chart, { className: cvaChart({ legendPosition, size }), dataTestId: "pie-chart", onChartReady: handleChartReady, onClick: handleChartClick, onEvents: handleChartEvents, options: chartOptions, showLoading: loading, style: { width: "100%", height: "100%" } }) }), size === "full" && (jsx("div", { className: cvaLegend({ legendPosition }), "data-testid": "legend", children: limitedData.map((item, index) => (jsx(LegendItem, { className: "p-1.5 py-0.5", color: item.color ?? chartColor(index), count: item.count, dataTestId: `legend-${item.id}`, disabled: item.count === 0, label: item.name, onClick: onClick ? () => onClick(item) : undefined, onMouseEnter: () => handleLegendMouseEnter(item), onMouseLeave: handleLegendMouseLeave, selected: item.selected }, item.id))) }))] }));
|
|
152
382
|
};
|
|
153
|
-
const cvaChartRoot = cvaMerge([
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
383
|
+
const cvaChartRoot = cvaMerge(["flex", "w-full", "h-full", "gap-4"], {
|
|
384
|
+
variants: {
|
|
385
|
+
legendPosition: {
|
|
386
|
+
Right: ["items-center", "justify-items-stretch", "flex-row"],
|
|
387
|
+
Bottom: ["items-center", "flex-col"],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
});
|
|
391
|
+
const cvaChartContainer = cvaMerge(["flex-1", "h-full"], {
|
|
392
|
+
variants: {
|
|
393
|
+
legendPosition: {
|
|
394
|
+
Right: ["justify-end"],
|
|
395
|
+
Bottom: ["flex", "items-center", "w-full"],
|
|
396
|
+
},
|
|
397
|
+
},
|
|
398
|
+
});
|
|
399
|
+
const cvaChart = cvaMerge(["flex-0", "max-w-[200px]", "max-h-[200px]", "place-self-center"], {
|
|
400
|
+
variants: {
|
|
401
|
+
legendPosition: {
|
|
402
|
+
Right: [],
|
|
403
|
+
Bottom: ["max-h-[230px]"],
|
|
404
|
+
},
|
|
405
|
+
size: {
|
|
406
|
+
full: ["min-h-[140px]"],
|
|
407
|
+
compact: ["w-[60px]", "h-[60px]"],
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
defaultVariants: {
|
|
411
|
+
size: "full",
|
|
412
|
+
},
|
|
413
|
+
});
|
|
414
|
+
const cvaLegend = cvaMerge(["flex", "overflow-auto"], {
|
|
415
|
+
variants: {
|
|
416
|
+
legendPosition: {
|
|
417
|
+
Right: ["justify-start", "flex-col", "flex-1"],
|
|
418
|
+
Bottom: ["flex-wrap", "justify-end", "flex-row"],
|
|
419
|
+
},
|
|
420
|
+
},
|
|
421
|
+
defaultVariants: {
|
|
422
|
+
legendPosition: "Right",
|
|
423
|
+
},
|
|
424
|
+
});
|
|
161
425
|
|
|
162
|
-
export { Chart, LegendItem,
|
|
426
|
+
export { Chart, DonutChart, LegendItem, useChartColor };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@trackunit/react-chart-components",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.88",
|
|
4
4
|
"repository": "https://github.com/Trackunit/manager",
|
|
5
5
|
"license": "SEE LICENSE IN LICENSE.txt",
|
|
6
6
|
"engines": {
|
|
@@ -8,10 +8,11 @@
|
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
10
|
"echarts": "5.6.0",
|
|
11
|
-
"echarts-for-react": "^3.0.2",
|
|
12
11
|
"react": "19.0.0",
|
|
13
|
-
"@trackunit/
|
|
14
|
-
"@trackunit/
|
|
12
|
+
"@trackunit/ui-design-tokens": "1.3.73",
|
|
13
|
+
"@trackunit/shared-utils": "1.5.73",
|
|
14
|
+
"@trackunit/css-class-variance-utilities": "1.3.73",
|
|
15
|
+
"@trackunit/react-components": "1.4.88"
|
|
15
16
|
},
|
|
16
17
|
"module": "./index.esm.js",
|
|
17
18
|
"main": "./index.cjs.js",
|
package/src/Chart/Chart.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { CommonProps } from "@trackunit/react-components";
|
|
2
|
-
import { EChartsOption } from "echarts";
|
|
2
|
+
import type { ECElementEvent, ECharts, EChartsOption } from "echarts";
|
|
3
3
|
import { CSSProperties, ReactElement, ReactNode } from "react";
|
|
4
|
+
import { type EventName } from "../EChart/EChart";
|
|
4
5
|
interface UnsafeOptions {
|
|
5
6
|
[key: string]: any;
|
|
6
7
|
}
|
|
@@ -12,7 +13,7 @@ type UnsafeOptionsCondition = {
|
|
|
12
13
|
/**
|
|
13
14
|
* Option values for EChart. Not typed.
|
|
14
15
|
*
|
|
15
|
-
* @deprecated Use options instead.
|
|
16
|
+
* @deprecated Use options instead. Will be removed in next major version.
|
|
16
17
|
*/
|
|
17
18
|
unsafeOptions: UnsafeOptions;
|
|
18
19
|
};
|
|
@@ -24,38 +25,37 @@ type SafeOptionsCondition = {
|
|
|
24
25
|
/**
|
|
25
26
|
* Option values for EChart. Not typed.
|
|
26
27
|
*
|
|
27
|
-
* @deprecated Use options instead.
|
|
28
|
+
* @deprecated Use options instead. Will be removed in next major version.
|
|
28
29
|
*/
|
|
29
30
|
unsafeOptions?: never;
|
|
30
31
|
};
|
|
31
32
|
type ConditionalChartOptions = UnsafeOptionsCondition | SafeOptionsCondition;
|
|
32
|
-
/**
|
|
33
|
-
* https://echarts.apache.org/en/api.html#events
|
|
34
|
-
*/
|
|
35
|
-
type EventName = "datazoom" | "brush" | "brushselected" | "globalout" | "mouseover" | "mousedown" | "mouseup" | "mouseOut" | "click" | "dblclick" | "contextmenu";
|
|
36
33
|
export type ChartProps = CommonProps & ConditionalChartOptions & {
|
|
37
34
|
/**
|
|
38
|
-
* Whether or not to
|
|
35
|
+
* Whether or not to show the preloader.
|
|
39
36
|
*/
|
|
40
37
|
showLoading?: boolean;
|
|
41
38
|
/**
|
|
42
|
-
* onClick event.
|
|
39
|
+
* onClick event handler.
|
|
43
40
|
*/
|
|
44
|
-
onClick?: (event:
|
|
41
|
+
onClick?: (event: ECElementEvent) => void;
|
|
45
42
|
/**
|
|
46
43
|
* CSS styles for the chart.
|
|
47
44
|
*/
|
|
48
45
|
style?: CSSProperties;
|
|
49
46
|
/**
|
|
50
|
-
*
|
|
47
|
+
* Called when the chart instance is ready.
|
|
51
48
|
*/
|
|
52
|
-
onChartReady?: (chart:
|
|
49
|
+
onChartReady?: (chart: ECharts) => void;
|
|
53
50
|
/**
|
|
54
51
|
* Event handlers for the chart.
|
|
55
52
|
*/
|
|
56
|
-
onEvents?: Partial<Record<EventName, (event:
|
|
53
|
+
onEvents?: Partial<Record<EventName, (event: ECElementEvent) => void>>;
|
|
57
54
|
/**
|
|
58
55
|
* Whether to merge with previous option.
|
|
56
|
+
*
|
|
57
|
+
* @deprecated Use notMerge in EChart directly. Will be removed in next major version.
|
|
58
|
+
* @default true
|
|
59
59
|
*/
|
|
60
60
|
merge?: boolean;
|
|
61
61
|
/**
|
|
@@ -69,7 +69,9 @@ export type ChartProps = CommonProps & ConditionalChartOptions & {
|
|
|
69
69
|
*/
|
|
70
70
|
timezoneLabel?: ReactNode;
|
|
71
71
|
/**
|
|
72
|
-
* Type of
|
|
72
|
+
* Type of renderer used. Either "svg" or "canvas".
|
|
73
|
+
*
|
|
74
|
+
* @default "canvas"
|
|
73
75
|
*/
|
|
74
76
|
renderer?: "svg" | "canvas";
|
|
75
77
|
/**
|
|
@@ -79,13 +81,7 @@ export type ChartProps = CommonProps & ConditionalChartOptions & {
|
|
|
79
81
|
};
|
|
80
82
|
/**
|
|
81
83
|
* The Chart component is a wrapper component for ECharts.
|
|
82
|
-
*
|
|
83
84
|
* Handles events and click functionality.
|
|
84
|
-
*
|
|
85
|
-
* For more information see the [eCharts documentation](https://github.com/hustcc/echarts-for-react).
|
|
86
|
-
*
|
|
87
|
-
* @param {ChartProps} props - The props for the Chart component
|
|
88
|
-
* @returns {ReactElement} Chart component
|
|
89
85
|
*/
|
|
90
86
|
export declare const Chart: ({ options, unsafeOptions, showLoading, className, style, onChartReady, onClick, onEvents, merge, loadingOption, timezoneLabel, renderer, dataTestId, }: ChartProps) => ReactElement;
|
|
91
87
|
export {};
|