@hsu-react/ui 2.4.1 → 2.4.3
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/es/components/Chart/Bar/index.js +9 -2
- package/es/components/Chart/Common/index.js +9 -2
- package/es/components/Chart/Gauge/index.js +9 -2
- package/es/components/Chart/Heatmap/index.js +9 -2
- package/es/components/Chart/Line/index.js +9 -2
- package/es/components/Chart/Pie/Pie3D/index.js +9 -2
- package/es/components/Chart/Pie/index.js +9 -2
- package/es/components/Chart/Polar/index.js +9 -2
- package/es/components/Chart/Radar/index.js +9 -2
- package/es/components/Chart/Sankey/index.js +9 -2
- package/es/components/Chart/Tree/index.js +9 -2
- package/es/components/Chart/_hooks/useContainerReady.d.ts +18 -0
- package/es/components/Chart/_hooks/useContainerReady.js +56 -0
- package/es/layout/NavTabBar/_hooks/useTabPath.js +45 -8
- package/lib/components/Chart/Bar/index.js +9 -2
- package/lib/components/Chart/Common/index.js +9 -2
- package/lib/components/Chart/Gauge/index.js +9 -2
- package/lib/components/Chart/Heatmap/index.js +9 -2
- package/lib/components/Chart/Line/index.js +9 -2
- package/lib/components/Chart/Pie/Pie3D/index.js +9 -2
- package/lib/components/Chart/Pie/index.js +9 -2
- package/lib/components/Chart/Polar/index.js +9 -2
- package/lib/components/Chart/Radar/index.js +9 -2
- package/lib/components/Chart/Sankey/index.js +9 -2
- package/lib/components/Chart/Tree/index.js +9 -2
- package/lib/components/Chart/_hooks/useContainerReady.d.ts +18 -0
- package/lib/components/Chart/_hooks/useContainerReady.js +51 -0
- package/lib/layout/NavTabBar/_hooks/useTabPath.js +42 -7
- package/package.json +1 -1
- package/src/components/Chart/Bar/index.tsx +10 -1
- package/src/components/Chart/Common/index.tsx +12 -2
- package/src/components/Chart/Gauge/index.tsx +12 -2
- package/src/components/Chart/Heatmap/index.tsx +12 -2
- package/src/components/Chart/Line/index.tsx +10 -1
- package/src/components/Chart/Pie/Pie3D/index.tsx +10 -1
- package/src/components/Chart/Pie/index.tsx +10 -1
- package/src/components/Chart/Polar/index.tsx +12 -2
- package/src/components/Chart/Radar/index.tsx +10 -1
- package/src/components/Chart/Sankey/index.tsx +12 -2
- package/src/components/Chart/Tree/index.tsx +12 -2
- package/src/components/Chart/_hooks/useContainerReady.ts +50 -0
- package/src/layout/NavTabBar/_hooks/useTabPath.ts +43 -10
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.default = void 0;
|
|
7
|
+
var _react = require("react");
|
|
8
|
+
/**
|
|
9
|
+
* Whether the referenced element has a non-zero box yet.
|
|
10
|
+
*
|
|
11
|
+
* `echarts.init` on a 0×0 element logs `Can't get DOM width or height` and builds a canvas with no
|
|
12
|
+
* size, recovering only on the next `resize()`. A chart hits that path whenever it mounts before
|
|
13
|
+
* layout has given it space — inside a keep-alive tab that is not the active one, a collapsed
|
|
14
|
+
* panel, a modal that has not opened yet — so consuming apps see the warning for charts that end
|
|
15
|
+
* up rendering perfectly well.
|
|
16
|
+
*
|
|
17
|
+
* Gating `init` on this flag defers it to the first frame the element actually occupies space.
|
|
18
|
+
*
|
|
19
|
+
* It **latches**: once measured, the element going back to 0×0 (a keep-alive tab losing focus)
|
|
20
|
+
* must not read as "not ready" again, or the init effect would tear the chart down and re-create
|
|
21
|
+
* it on every tab switch.
|
|
22
|
+
*/
|
|
23
|
+
const useContainerReady = ref => {
|
|
24
|
+
const [ready, setReady] = (0, _react.useState)(false);
|
|
25
|
+
(0, _react.useEffect)(() => {
|
|
26
|
+
if (ready) return;
|
|
27
|
+
const el = ref.current;
|
|
28
|
+
if (!el) return;
|
|
29
|
+
const measure = () => {
|
|
30
|
+
const {
|
|
31
|
+
width,
|
|
32
|
+
height
|
|
33
|
+
} = el.getBoundingClientRect();
|
|
34
|
+
if (width > 0 && height > 0) {
|
|
35
|
+
setReady(true);
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
return false;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// Already laid out on mount — the common case, and it must not cost an extra frame
|
|
42
|
+
if (measure()) return;
|
|
43
|
+
const observer = new ResizeObserver(() => {
|
|
44
|
+
if (measure()) observer.disconnect();
|
|
45
|
+
});
|
|
46
|
+
observer.observe(el);
|
|
47
|
+
return () => observer.disconnect();
|
|
48
|
+
}, [ready, ref]);
|
|
49
|
+
return ready;
|
|
50
|
+
};
|
|
51
|
+
var _default = exports.default = useContainerReady;
|
|
@@ -16,6 +16,17 @@ const useTabPath = ({
|
|
|
16
16
|
affixRouter
|
|
17
17
|
}) => {
|
|
18
18
|
const location = (0, _reactRouter.useLocation)();
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Keyed on the *contents* of `affixRouter`, not on the array itself.
|
|
22
|
+
*
|
|
23
|
+
* Callers pass an inline literal (`affixRouter={[HOME]}`), and omitting the prop hands the
|
|
24
|
+
* component a fresh `[]` default on every render — either way the array identity changes each
|
|
25
|
+
* time. Depending on it directly re-runs the effect below on every render, and that effect
|
|
26
|
+
* calls setState, so the whole thing becomes a render loop.
|
|
27
|
+
*/
|
|
28
|
+
const affixKey = affixRouter.join("\u0001");
|
|
29
|
+
const affixSet = (0, _react.useMemo)(() => new Set(affixKey ? affixKey.split("\u0001") : []), [affixKey]);
|
|
19
30
|
const [tabKey, setTabKey] = (0, _react.useState)("");
|
|
20
31
|
const [openKeys, setOpenkeys] = (0, _react.useState)([]);
|
|
21
32
|
|
|
@@ -28,7 +39,7 @@ const useTabPath = ({
|
|
|
28
39
|
_checkAffix(item.children);
|
|
29
40
|
} else {
|
|
30
41
|
// Check whether it is an affixed route
|
|
31
|
-
const isAffix =
|
|
42
|
+
const isAffix = affixSet.has(item.key) || item.affix;
|
|
32
43
|
if (isAffix) {
|
|
33
44
|
setOpenkeys(prev => {
|
|
34
45
|
const find = prev.find(i => i.key === item.key);
|
|
@@ -40,7 +51,7 @@ const useTabPath = ({
|
|
|
40
51
|
}
|
|
41
52
|
}
|
|
42
53
|
});
|
|
43
|
-
}, [
|
|
54
|
+
}, [affixSet]);
|
|
44
55
|
const _checkPath = (0, _react.useCallback)((items, parents) => {
|
|
45
56
|
const pathname = decodeURI(location.pathname);
|
|
46
57
|
const search = location.search;
|
|
@@ -58,32 +69,44 @@ const useTabPath = ({
|
|
|
58
69
|
// Handle param routes
|
|
59
70
|
setTabKey(`${pathname}${search}`);
|
|
60
71
|
setOpenkeys(prev => {
|
|
72
|
+
const nextKey = `${pathname}${search}`;
|
|
61
73
|
const find = prev.find(i => i.key.split("?")[0] === pathname);
|
|
62
74
|
if (find) {
|
|
75
|
+
// Already on this key: return `prev` untouched. `map` builds a new array
|
|
76
|
+
// every time, and React treats a new reference as a state change — which is
|
|
77
|
+
// the other half of the render loop above.
|
|
78
|
+
if (find.key === nextKey) {
|
|
79
|
+
return prev;
|
|
80
|
+
}
|
|
63
81
|
return prev.map(i => i.key.split("?")[0] === pathname ? {
|
|
64
82
|
...i,
|
|
65
|
-
key:
|
|
83
|
+
key: nextKey
|
|
66
84
|
} : i);
|
|
67
85
|
}
|
|
68
86
|
return [...prev, {
|
|
69
87
|
...item,
|
|
70
|
-
key:
|
|
88
|
+
key: nextKey
|
|
71
89
|
}];
|
|
72
90
|
});
|
|
73
91
|
} else {
|
|
74
92
|
// Handle plain routes
|
|
75
93
|
setTabKey(`${item.key}${search}`);
|
|
76
94
|
setOpenkeys(prev => {
|
|
95
|
+
const nextKey = `${item.key}${search}`;
|
|
77
96
|
const find = prev.find(i => i.key.split("?")[0] === item.key);
|
|
78
97
|
if (find) {
|
|
98
|
+
// Same as above: an unchanged tab must not produce a new reference
|
|
99
|
+
if (find.key === nextKey) {
|
|
100
|
+
return prev;
|
|
101
|
+
}
|
|
79
102
|
return prev.map(i => i.key.split("?")[0] === item.key ? {
|
|
80
103
|
...i,
|
|
81
|
-
key:
|
|
104
|
+
key: nextKey
|
|
82
105
|
} : i);
|
|
83
106
|
}
|
|
84
107
|
return [...prev, {
|
|
85
108
|
...item,
|
|
86
|
-
key:
|
|
109
|
+
key: nextKey
|
|
87
110
|
}];
|
|
88
111
|
});
|
|
89
112
|
}
|
|
@@ -91,12 +114,24 @@ const useTabPath = ({
|
|
|
91
114
|
}
|
|
92
115
|
});
|
|
93
116
|
}, [location.pathname, location.search]);
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Closing the last tab navigates to `basePath` — but when that tab *was* `basePath`, the
|
|
120
|
+
* location never changes, so path matching would not re-run and the bar would be left empty
|
|
121
|
+
* while its page is still on screen. Going empty has to re-trigger the match on its own.
|
|
122
|
+
*
|
|
123
|
+
* This settles rather than looping: re-matching adds the tab back, which flips `isEmpty` and
|
|
124
|
+
* runs the effect once more, and that pass finds the tab already present and returns the same
|
|
125
|
+
* state object — so React stops there. (Only true because the setters below bail out when
|
|
126
|
+
* nothing changed; without that this would spin.)
|
|
127
|
+
*/
|
|
128
|
+
const isEmpty = openKeys.length === 0;
|
|
94
129
|
(0, _react.useEffect)(() => {
|
|
95
130
|
// Handle affixed routes first to populate openKeys
|
|
96
131
|
_checkAffix(items);
|
|
97
132
|
// Then run path matching
|
|
98
133
|
_checkPath(items);
|
|
99
|
-
}, [_checkAffix, _checkPath, items]);
|
|
134
|
+
}, [_checkAffix, _checkPath, items, isEmpty]);
|
|
100
135
|
return {
|
|
101
136
|
tabKey,
|
|
102
137
|
setTabKey,
|
package/package.json
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
ChartCommonProps,
|
|
@@ -72,6 +73,8 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
72
73
|
} = props;
|
|
73
74
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
74
75
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
76
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
77
|
+
const containerReady = useContainerReady(chartRef);
|
|
75
78
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
76
79
|
const prevXAxisKeyRef = useRef<string | null>(null);
|
|
77
80
|
const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
|
|
@@ -269,12 +272,17 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
269
272
|
|
|
270
273
|
// Callback for handling chart resize
|
|
271
274
|
const handleResize = useCallback(() => {
|
|
275
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
276
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
277
|
+
const el = chartRef.current;
|
|
278
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
279
|
+
|
|
272
280
|
chartInstanceRef.current?.resize();
|
|
273
281
|
}, []);
|
|
274
282
|
|
|
275
283
|
// Initialize the chart
|
|
276
284
|
useEffect(() => {
|
|
277
|
-
if (!chartRef.current) return;
|
|
285
|
+
if (!chartRef.current || !containerReady) return;
|
|
278
286
|
|
|
279
287
|
// Initialize or reuse the existing instance
|
|
280
288
|
let chart = chartInstanceRef.current;
|
|
@@ -469,6 +477,7 @@ const ChartBar: React.FC<ChartBarProps> = (props) => {
|
|
|
469
477
|
legendScrollInterval,
|
|
470
478
|
// 图例是否接管滚轮由它推导,漏了会拿到过期的滚动配置
|
|
471
479
|
normalizedScroll,
|
|
480
|
+
containerReady,
|
|
472
481
|
]);
|
|
473
482
|
|
|
474
483
|
// Handle auto play
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import styles from "../index.module.scss";
|
|
3
4
|
import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
|
|
4
5
|
import * as echarts from "echarts";
|
|
@@ -7,6 +8,8 @@ const Common: React.FC<ChartCommonProps> = (props) => {
|
|
|
7
8
|
const { className, style, onChart, ...coreOption } = props;
|
|
8
9
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
9
10
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
11
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
12
|
+
const containerReady = useContainerReady(chartRef);
|
|
10
13
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
11
14
|
|
|
12
15
|
// Cache the chart configuration with useMemo
|
|
@@ -20,12 +23,17 @@ const Common: React.FC<ChartCommonProps> = (props) => {
|
|
|
20
23
|
|
|
21
24
|
// Callback that handles chart resize
|
|
22
25
|
const handleResize = useCallback(() => {
|
|
26
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
27
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
28
|
+
const el = chartRef.current;
|
|
29
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
30
|
+
|
|
23
31
|
chartInstanceRef.current?.resize();
|
|
24
32
|
}, []);
|
|
25
33
|
|
|
26
34
|
// Initialize the chart
|
|
27
35
|
useEffect(() => {
|
|
28
|
-
if (!chartRef.current) return;
|
|
36
|
+
if (!chartRef.current || !containerReady) return;
|
|
29
37
|
|
|
30
38
|
// Initialize or reuse an existing instance
|
|
31
39
|
let chart = chartInstanceRef.current;
|
|
@@ -53,7 +61,9 @@ const Common: React.FC<ChartCommonProps> = (props) => {
|
|
|
53
61
|
return () => {
|
|
54
62
|
window.removeEventListener("resize", handleResize);
|
|
55
63
|
};
|
|
56
|
-
}, [chartOption, handleResize, onChart
|
|
64
|
+
}, [chartOption, handleResize, onChart,
|
|
65
|
+
containerReady,
|
|
66
|
+
]);
|
|
57
67
|
|
|
58
68
|
// Clean up resources on component unmount
|
|
59
69
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
ChartCommonProps,
|
|
@@ -31,6 +32,8 @@ const Gauge: React.FC<ChartGaugeProps> = (props) => {
|
|
|
31
32
|
} = series as echarts.GaugeSeriesOption;
|
|
32
33
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
33
34
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
35
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
36
|
+
const containerReady = useContainerReady(chartRef);
|
|
34
37
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
35
38
|
|
|
36
39
|
// Cache the chart option with useMemo
|
|
@@ -104,12 +107,17 @@ const Gauge: React.FC<ChartGaugeProps> = (props) => {
|
|
|
104
107
|
|
|
105
108
|
// Callback for handling chart resize
|
|
106
109
|
const handleResize = useCallback(() => {
|
|
110
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
111
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
112
|
+
const el = chartRef.current;
|
|
113
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
114
|
+
|
|
107
115
|
chartInstanceRef.current?.resize();
|
|
108
116
|
}, []);
|
|
109
117
|
|
|
110
118
|
// Initialize the chart
|
|
111
119
|
useEffect(() => {
|
|
112
|
-
if (!chartRef.current) return;
|
|
120
|
+
if (!chartRef.current || !containerReady) return;
|
|
113
121
|
|
|
114
122
|
// Initialize or reuse the existing instance
|
|
115
123
|
let chart = chartInstanceRef.current;
|
|
@@ -134,7 +142,9 @@ const Gauge: React.FC<ChartGaugeProps> = (props) => {
|
|
|
134
142
|
return () => {
|
|
135
143
|
window.removeEventListener("resize", handleResize);
|
|
136
144
|
};
|
|
137
|
-
}, [chartOption, handleResize
|
|
145
|
+
}, [chartOption, handleResize,
|
|
146
|
+
containerReady,
|
|
147
|
+
]);
|
|
138
148
|
|
|
139
149
|
// Clean up resources when the component unmounts
|
|
140
150
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import styles from "../index.module.scss";
|
|
3
4
|
import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
|
|
4
5
|
import * as echarts from "echarts";
|
|
@@ -74,6 +75,8 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
|
|
|
74
75
|
} = props;
|
|
75
76
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
76
77
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
78
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
79
|
+
const containerReady = useContainerReady(chartRef);
|
|
77
80
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
78
81
|
// echarts options are plain JS and cannot read the `--vita-*` variables, so the axis and legend
|
|
79
82
|
// colours have to be resolved against the active appearance here
|
|
@@ -255,12 +258,17 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
|
|
|
255
258
|
|
|
256
259
|
// Callback for handling chart resize
|
|
257
260
|
const handleResize = useCallback(() => {
|
|
261
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
262
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
263
|
+
const el = chartRef.current;
|
|
264
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
265
|
+
|
|
258
266
|
chartInstanceRef.current?.resize();
|
|
259
267
|
}, []);
|
|
260
268
|
|
|
261
269
|
// Initialize the chart
|
|
262
270
|
useEffect(() => {
|
|
263
|
-
if (!chartRef.current) return;
|
|
271
|
+
if (!chartRef.current || !containerReady) return;
|
|
264
272
|
|
|
265
273
|
// Initialize or reuse the existing instance
|
|
266
274
|
let chart = chartInstanceRef.current;
|
|
@@ -296,7 +304,9 @@ const Heatmap: React.FC<ChartHeatmapProps> = (props) => {
|
|
|
296
304
|
chartInstanceRef.current?.off("click", onClick);
|
|
297
305
|
}
|
|
298
306
|
};
|
|
299
|
-
}, [chartOption, handleResize, onChart, onClick
|
|
307
|
+
}, [chartOption, handleResize, onChart, onClick,
|
|
308
|
+
containerReady,
|
|
309
|
+
]);
|
|
300
310
|
|
|
301
311
|
// Clean up resources when the component unmounts
|
|
302
312
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
ChartCommonProps,
|
|
@@ -73,6 +74,8 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
73
74
|
} = props;
|
|
74
75
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
75
76
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
77
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
78
|
+
const containerReady = useContainerReady(chartRef);
|
|
76
79
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
77
80
|
const prevXAxisKeyRef = useRef<string | null>(null);
|
|
78
81
|
const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
|
|
@@ -267,12 +270,17 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
267
270
|
|
|
268
271
|
// Callback for handling chart resize
|
|
269
272
|
const handleResize = useCallback(() => {
|
|
273
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
274
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
275
|
+
const el = chartRef.current;
|
|
276
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
277
|
+
|
|
270
278
|
chartInstanceRef.current?.resize();
|
|
271
279
|
}, []);
|
|
272
280
|
|
|
273
281
|
// Initialize the chart
|
|
274
282
|
useEffect(() => {
|
|
275
|
-
if (!chartRef.current) return;
|
|
283
|
+
if (!chartRef.current || !containerReady) return;
|
|
276
284
|
|
|
277
285
|
// Initialize or reuse the existing instance
|
|
278
286
|
let chart = chartInstanceRef.current;
|
|
@@ -467,6 +475,7 @@ const ChartLine: React.FC<ChartLineProps> = (props) => {
|
|
|
467
475
|
legendScrollInterval,
|
|
468
476
|
// 图例是否接管滚轮由它推导,漏了会拿到过期的滚动配置
|
|
469
477
|
normalizedScroll,
|
|
478
|
+
containerReady,
|
|
470
479
|
]);
|
|
471
480
|
|
|
472
481
|
// Handle auto play
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../../_hooks/useContainerReady";
|
|
2
3
|
import "echarts-gl";
|
|
3
4
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
4
5
|
|
|
@@ -108,6 +109,8 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
108
109
|
|
|
109
110
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
110
111
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
112
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
113
|
+
const containerReady = useContainerReady(chartRef);
|
|
111
114
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
112
115
|
const hoveredIndexRef = useRef<number | "">("");
|
|
113
116
|
const optionRef = useRef<ChartsOption | null>(null);
|
|
@@ -145,6 +148,11 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
145
148
|
]);
|
|
146
149
|
|
|
147
150
|
const handleResize = useCallback(() => {
|
|
151
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
152
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
153
|
+
const el = chartRef.current;
|
|
154
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
155
|
+
|
|
148
156
|
chartInstanceRef.current?.resize();
|
|
149
157
|
}, []);
|
|
150
158
|
|
|
@@ -163,7 +171,7 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
163
171
|
);
|
|
164
172
|
|
|
165
173
|
useEffect(() => {
|
|
166
|
-
if (!chartRef.current) return;
|
|
174
|
+
if (!chartRef.current || !containerReady) return;
|
|
167
175
|
|
|
168
176
|
let chart = chartInstanceRef.current;
|
|
169
177
|
if (!chart) {
|
|
@@ -205,6 +213,7 @@ const ChartPie3D: React.FC<ChartPie3DProps> = (props) => {
|
|
|
205
213
|
handleGlobalOut,
|
|
206
214
|
onChart,
|
|
207
215
|
onClick,
|
|
216
|
+
containerReady,
|
|
208
217
|
]);
|
|
209
218
|
|
|
210
219
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
|
|
3
4
|
import {
|
|
4
5
|
ChartCommonProps,
|
|
@@ -66,6 +67,8 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
66
67
|
} = props;
|
|
67
68
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
68
69
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
70
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
71
|
+
const containerReady = useContainerReady(chartRef);
|
|
69
72
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
70
73
|
const legendScrollRef = useRef<ReturnType<typeof autoScrollLegend> | null>(
|
|
71
74
|
null
|
|
@@ -241,12 +244,17 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
241
244
|
|
|
242
245
|
// Callback for handling chart resize
|
|
243
246
|
const handleResize = useCallback(() => {
|
|
247
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
248
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
249
|
+
const el = chartRef.current;
|
|
250
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
251
|
+
|
|
244
252
|
chartInstanceRef.current?.resize();
|
|
245
253
|
}, []);
|
|
246
254
|
|
|
247
255
|
// Initialize the chart
|
|
248
256
|
useEffect(() => {
|
|
249
|
-
if (!chartRef.current) return;
|
|
257
|
+
if (!chartRef.current || !containerReady) return;
|
|
250
258
|
|
|
251
259
|
// Initialize or reuse the existing instance
|
|
252
260
|
let chart = chartInstanceRef.current;
|
|
@@ -313,6 +321,7 @@ const ChartPie: ChartPieFC = (props) => {
|
|
|
313
321
|
seriesData,
|
|
314
322
|
legendVisibleCount,
|
|
315
323
|
legendScrollInterval,
|
|
324
|
+
containerReady,
|
|
316
325
|
]);
|
|
317
326
|
|
|
318
327
|
// Clean up resources when the component unmounts
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import {
|
|
3
4
|
ChartCommonProps,
|
|
4
5
|
ChartOptionType,
|
|
@@ -36,6 +37,8 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
|
|
|
36
37
|
} = props;
|
|
37
38
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
38
39
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
40
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
41
|
+
const containerReady = useContainerReady(chartRef);
|
|
39
42
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
40
43
|
|
|
41
44
|
// Cache the chart option with useMemo
|
|
@@ -170,12 +173,17 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
|
|
|
170
173
|
|
|
171
174
|
// Callback handling chart resize
|
|
172
175
|
const handleResize = useCallback(() => {
|
|
176
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
177
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
178
|
+
const el = chartRef.current;
|
|
179
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
180
|
+
|
|
173
181
|
chartInstanceRef.current?.resize();
|
|
174
182
|
}, []);
|
|
175
183
|
|
|
176
184
|
// Initialize the chart
|
|
177
185
|
useEffect(() => {
|
|
178
|
-
if (!chartRef.current) return;
|
|
186
|
+
if (!chartRef.current || !containerReady) return;
|
|
179
187
|
|
|
180
188
|
// Initialize a new instance or reuse the existing one
|
|
181
189
|
let chart = chartInstanceRef.current;
|
|
@@ -200,7 +208,9 @@ const Polar: React.FC<ChartPolarProps> = (props) => {
|
|
|
200
208
|
return () => {
|
|
201
209
|
window.removeEventListener("resize", handleResize);
|
|
202
210
|
};
|
|
203
|
-
}, [chartOption, handleResize
|
|
211
|
+
}, [chartOption, handleResize,
|
|
212
|
+
containerReady,
|
|
213
|
+
]);
|
|
204
214
|
|
|
205
215
|
// Clean up resources on unmount
|
|
206
216
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as echarts from "echarts";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
|
|
3
4
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
4
5
|
import styles from "../index.module.scss";
|
|
@@ -55,6 +56,8 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
|
|
|
55
56
|
|
|
56
57
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
57
58
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
59
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
60
|
+
const containerReady = useContainerReady(chartRef);
|
|
58
61
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
59
62
|
const hoveredIndicatorRef = useRef<number>(-1);
|
|
60
63
|
|
|
@@ -188,11 +191,16 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
|
|
|
188
191
|
]);
|
|
189
192
|
|
|
190
193
|
const handleResize = useCallback(() => {
|
|
194
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
195
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
196
|
+
const el = chartRef.current;
|
|
197
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
198
|
+
|
|
191
199
|
chartInstanceRef.current?.resize();
|
|
192
200
|
}, []);
|
|
193
201
|
|
|
194
202
|
useEffect(() => {
|
|
195
|
-
if (!chartRef.current) return;
|
|
203
|
+
if (!chartRef.current || !containerReady) return;
|
|
196
204
|
|
|
197
205
|
let chart = chartInstanceRef.current;
|
|
198
206
|
if (!chart) {
|
|
@@ -343,6 +351,7 @@ const Radar: React.FC<ChartRadarProps> = (props) => {
|
|
|
343
351
|
singleIndicatorHighlight,
|
|
344
352
|
singleIndicatorHoverFill,
|
|
345
353
|
color,
|
|
354
|
+
containerReady,
|
|
346
355
|
]);
|
|
347
356
|
|
|
348
357
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import styles from "../index.module.scss";
|
|
3
4
|
import { ChartCommonProps, ChartOptionType, ChartsOption } from "..";
|
|
4
5
|
import * as echarts from "echarts";
|
|
@@ -16,6 +17,8 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
16
17
|
const { className, style, seriesData, seriesLinks, series, getImage } = props;
|
|
17
18
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
18
19
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
20
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
21
|
+
const containerReady = useContainerReady(chartRef);
|
|
19
22
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
20
23
|
|
|
21
24
|
// Cache the chart option with useMemo
|
|
@@ -100,12 +103,17 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
100
103
|
|
|
101
104
|
// Callback handling chart resize
|
|
102
105
|
const handleResize = useCallback(() => {
|
|
106
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
107
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
108
|
+
const el = chartRef.current;
|
|
109
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
110
|
+
|
|
103
111
|
chartInstanceRef.current?.resize();
|
|
104
112
|
}, []);
|
|
105
113
|
|
|
106
114
|
// Initialize the chart
|
|
107
115
|
useEffect(() => {
|
|
108
|
-
if (!chartRef.current) return;
|
|
116
|
+
if (!chartRef.current || !containerReady) return;
|
|
109
117
|
|
|
110
118
|
// Initialize a new instance or reuse the existing one
|
|
111
119
|
let chart = chartInstanceRef.current;
|
|
@@ -141,7 +149,9 @@ const Sankey: React.FC<ChartSankeyProps> = (props) => {
|
|
|
141
149
|
return () => {
|
|
142
150
|
window.removeEventListener("resize", handleResize);
|
|
143
151
|
};
|
|
144
|
-
}, [chartOption, handleResize, getImage
|
|
152
|
+
}, [chartOption, handleResize, getImage,
|
|
153
|
+
containerReady,
|
|
154
|
+
]);
|
|
145
155
|
|
|
146
156
|
// Clean up resources on unmount
|
|
147
157
|
useEffect(() => {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React, { useCallback, useEffect, useMemo, useRef } from "react";
|
|
2
|
+
import useContainerReady from "../_hooks/useContainerReady";
|
|
2
3
|
import styles from "../index.module.scss";
|
|
3
4
|
import {
|
|
4
5
|
ChartCommonProps,
|
|
@@ -19,6 +20,8 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
19
20
|
const { className, style, seriesData, series, getImage } = props;
|
|
20
21
|
const chartRef = useRef<HTMLDivElement>(null);
|
|
21
22
|
const chartInstanceRef = useRef<echarts.ECharts | null>(null);
|
|
23
|
+
// Defers `echarts.init` until the container has a box — see the hook for why
|
|
24
|
+
const containerReady = useContainerReady(chartRef);
|
|
22
25
|
const resizeObserverRef = useRef<ResizeObserver | null>(null);
|
|
23
26
|
|
|
24
27
|
// Cache the chart option with useMemo
|
|
@@ -40,12 +43,17 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
40
43
|
|
|
41
44
|
// Callback handling chart resize
|
|
42
45
|
const handleResize = useCallback(() => {
|
|
46
|
+
// A keep-alive tab losing focus fires the observer with a 0×0 box; resizing to that throws
|
|
47
|
+
// the laid-out canvas away and it has to be rebuilt when the tab comes back
|
|
48
|
+
const el = chartRef.current;
|
|
49
|
+
if (!el || el.clientWidth === 0 || el.clientHeight === 0) return;
|
|
50
|
+
|
|
43
51
|
chartInstanceRef.current?.resize();
|
|
44
52
|
}, []);
|
|
45
53
|
|
|
46
54
|
// Initialize the chart
|
|
47
55
|
useEffect(() => {
|
|
48
|
-
if (!chartRef.current) return;
|
|
56
|
+
if (!chartRef.current || !containerReady) return;
|
|
49
57
|
|
|
50
58
|
// Initialize a new instance or reuse the existing one
|
|
51
59
|
let chart = chartInstanceRef.current;
|
|
@@ -81,7 +89,9 @@ const Tree: React.FC<ChartTreeProps> = (props) => {
|
|
|
81
89
|
return () => {
|
|
82
90
|
window.removeEventListener("resize", handleResize);
|
|
83
91
|
};
|
|
84
|
-
}, [chartOption, handleResize, getImage
|
|
92
|
+
}, [chartOption, handleResize, getImage,
|
|
93
|
+
containerReady,
|
|
94
|
+
]);
|
|
85
95
|
|
|
86
96
|
// Clean up resources on unmount
|
|
87
97
|
useEffect(() => {
|