@tsingroc/tsingroc-components 4.8.0 → 5.0.0-alpha.10
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/package.json +28 -27
- package/src/components/Auth.tsx +6 -1
- package/src/components/AutoResizedECharts.tsx +70 -0
- package/src/components/ConnectedECharts.tsx +62 -0
- package/src/components/ECharts.tsx +206 -0
- package/src/components/LeftAlignedECharts.tsx +190 -0
- package/src/components/Sidebar.tsx +2 -7
- package/src/deckgl/TiandituLayer.ts +1 -1
- package/src/index.ts +23 -0
- package/src/types.d.ts +5 -0
- package/src/utils/debug.ts +39 -0
- package/src/utils/math.ts +16 -0
- package/src/utils/mock.ts +69 -0
- package/src/utils/startOfQuarter.ts +6 -0
- package/dist/components/Auth.d.ts +0 -310
- package/dist/components/Auth.js +0 -268
- package/dist/components/Auth.js.map +0 -1
- package/dist/components/Calendar.d.ts +0 -50
- package/dist/components/Calendar.js +0 -91
- package/dist/components/Calendar.js.map +0 -1
- package/dist/components/CircularProgress.d.ts +0 -21
- package/dist/components/CircularProgress.js +0 -15
- package/dist/components/CircularProgress.js.map +0 -1
- package/dist/components/Header.d.ts +0 -67
- package/dist/components/Header.js +0 -45
- package/dist/components/Header.js.map +0 -1
- package/dist/components/ImageBackground.d.ts +0 -32
- package/dist/components/ImageBackground.js +0 -18
- package/dist/components/ImageBackground.js.map +0 -1
- package/dist/components/IndicatorLight.d.ts +0 -44
- package/dist/components/IndicatorLight.js +0 -59
- package/dist/components/IndicatorLight.js.map +0 -1
- package/dist/components/LineChartEditor.d.ts +0 -74
- package/dist/components/LineChartEditor.js +0 -338
- package/dist/components/LineChartEditor.js.map +0 -1
- package/dist/components/LineChartTable.d.ts +0 -38
- package/dist/components/LineChartTable.js +0 -197
- package/dist/components/LineChartTable.js.map +0 -1
- package/dist/components/LinkedECharts.d.ts +0 -17
- package/dist/components/LinkedECharts.js +0 -34
- package/dist/components/LinkedECharts.js.map +0 -1
- package/dist/components/LinkedLineChart.d.ts +0 -45
- package/dist/components/LinkedLineChart.js +0 -71
- package/dist/components/LinkedLineChart.js.map +0 -1
- package/dist/components/QuickDateRangePicker.d.ts +0 -30
- package/dist/components/QuickDateRangePicker.js +0 -36
- package/dist/components/QuickDateRangePicker.js.map +0 -1
- package/dist/components/SegmentedButtons.d.ts +0 -22
- package/dist/components/SegmentedButtons.js +0 -31
- package/dist/components/SegmentedButtons.js.map +0 -1
- package/dist/components/Sidebar.d.ts +0 -79
- package/dist/components/Sidebar.js +0 -130
- package/dist/components/Sidebar.js.map +0 -1
- package/dist/components/StrictECharts.d.ts +0 -47
- package/dist/components/StrictECharts.js +0 -2
- package/dist/components/StrictECharts.js.map +0 -1
- package/dist/components/TsingrocDatePicker.d.ts +0 -38
- package/dist/components/TsingrocDatePicker.js +0 -36
- package/dist/components/TsingrocDatePicker.js.map +0 -1
- package/dist/components/TsingrocTheme.d.ts +0 -14
- package/dist/components/TsingrocTheme.js +0 -32
- package/dist/components/TsingrocTheme.js.map +0 -1
- package/dist/components/UserButton.d.ts +0 -42
- package/dist/components/UserButton.js +0 -77
- package/dist/components/UserButton.js.map +0 -1
- package/dist/components/VerticalColorLegend.d.ts +0 -7
- package/dist/components/VerticalColorLegend.js +0 -38
- package/dist/components/VerticalColorLegend.js.map +0 -1
- package/dist/components/WeatherMap.d.ts +0 -18
- package/dist/components/WeatherMap.js +0 -368
- package/dist/components/WeatherMap.js.map +0 -1
- package/dist/deckgl/TiandituLayer.d.ts +0 -13
- package/dist/deckgl/TiandituLayer.js +0 -40
- package/dist/deckgl/TiandituLayer.js.map +0 -1
- package/dist/deckgl/WeatherData.d.ts +0 -53
- package/dist/deckgl/WeatherData.js +0 -108
- package/dist/deckgl/WeatherData.js.map +0 -1
- package/dist/deckgl/index.d.ts +0 -1
- package/dist/deckgl/index.js +0 -2
- package/dist/deckgl/index.js.map +0 -1
- package/dist/echarts/coordinateSystems/grid.d.ts +0 -43
- package/dist/echarts/coordinateSystems/grid.js +0 -88
- package/dist/echarts/coordinateSystems/grid.js.map +0 -1
- package/dist/echarts/coordinateSystems/index.d.ts +0 -2
- package/dist/echarts/coordinateSystems/index.js +0 -3
- package/dist/echarts/coordinateSystems/index.js.map +0 -1
- package/dist/echarts/coordinateSystems/polar.d.ts +0 -45
- package/dist/echarts/coordinateSystems/polar.js +0 -100
- package/dist/echarts/coordinateSystems/polar.js.map +0 -1
- package/dist/echarts/gl.d.ts +0 -116
- package/dist/echarts/gl.js +0 -42
- package/dist/echarts/gl.js.map +0 -1
- package/dist/echarts/index.d.ts +0 -53
- package/dist/echarts/index.js +0 -62
- package/dist/echarts/index.js.map +0 -1
- package/dist/echarts/legend.d.ts +0 -17
- package/dist/echarts/legend.js +0 -15
- package/dist/echarts/legend.js.map +0 -1
- package/dist/echarts/radar.d.ts +0 -24
- package/dist/echarts/radar.js +0 -22
- package/dist/echarts/radar.js.map +0 -1
- package/dist/echarts/series/barSeries.d.ts +0 -23
- package/dist/echarts/series/barSeries.js +0 -14
- package/dist/echarts/series/barSeries.js.map +0 -1
- package/dist/echarts/series/boxplotSeries.d.ts +0 -21
- package/dist/echarts/series/boxplotSeries.js +0 -36
- package/dist/echarts/series/boxplotSeries.js.map +0 -1
- package/dist/echarts/series/index.d.ts +0 -7
- package/dist/echarts/series/index.js +0 -8
- package/dist/echarts/series/index.js.map +0 -1
- package/dist/echarts/series/intervalSeries.d.ts +0 -32
- package/dist/echarts/series/intervalSeries.js +0 -38
- package/dist/echarts/series/intervalSeries.js.map +0 -1
- package/dist/echarts/series/lineSeries.d.ts +0 -36
- package/dist/echarts/series/lineSeries.js +0 -43
- package/dist/echarts/series/lineSeries.js.map +0 -1
- package/dist/echarts/series/maxBarSeries.d.ts +0 -18
- package/dist/echarts/series/maxBarSeries.js +0 -32
- package/dist/echarts/series/maxBarSeries.js.map +0 -1
- package/dist/echarts/series/pieSeries.d.ts +0 -31
- package/dist/echarts/series/pieSeries.js +0 -39
- package/dist/echarts/series/pieSeries.js.map +0 -1
- package/dist/echarts/series/windLineSeries.d.ts +0 -47
- package/dist/echarts/series/windLineSeries.js +0 -59
- package/dist/echarts/series/windLineSeries.js.map +0 -1
- package/dist/echarts/tooltip.d.ts +0 -13
- package/dist/echarts/tooltip.js +0 -14
- package/dist/echarts/tooltip.js.map +0 -1
- package/dist/index.d.ts +0 -18
- package/dist/index.js +0 -19
- package/dist/index.js.map +0 -1
package/package.json
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tsingroc/tsingroc-components",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.10",
|
|
4
4
|
"author": "",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"description": "",
|
|
7
7
|
"keywords": [],
|
|
8
8
|
"type": "module",
|
|
9
|
-
"main": "
|
|
9
|
+
"main": "src/index.ts",
|
|
10
10
|
"sideEffects": [
|
|
11
11
|
"**/*.css"
|
|
12
12
|
],
|
|
13
13
|
"exports": {
|
|
14
|
-
".": "./
|
|
15
|
-
"./echarts": "./
|
|
16
|
-
"./echarts/gl": "./
|
|
14
|
+
".": "./src/index.ts",
|
|
15
|
+
"./echarts": "./src/echarts/index.ts",
|
|
16
|
+
"./echarts/gl": "./src/echarts/gl.ts",
|
|
17
|
+
"./utils/*": "./src/utils/*.ts"
|
|
17
18
|
},
|
|
18
19
|
"files": [
|
|
19
|
-
"dist",
|
|
20
20
|
"src"
|
|
21
21
|
],
|
|
22
22
|
"scripts": {
|
|
@@ -25,14 +25,14 @@
|
|
|
25
25
|
"lint": "eslint .",
|
|
26
26
|
"format": "prettier . -w && eslint . --fix",
|
|
27
27
|
"build": "tsc -b --force && copyfiles -u 1 src/**/*.d.ts dist",
|
|
28
|
-
"prepublishOnly": "
|
|
28
|
+
"prepublishOnly": "tsc -b --force",
|
|
29
29
|
"docs:build": "rspress build",
|
|
30
30
|
"docs:preview": "rspress preview"
|
|
31
31
|
},
|
|
32
32
|
"dependencies": {
|
|
33
33
|
"casdoor-js-sdk": "^0.16.0",
|
|
34
34
|
"dayjs": "^1.11.18",
|
|
35
|
-
"deck.gl": "^9.
|
|
35
|
+
"deck.gl": "^9.2.2",
|
|
36
36
|
"geotiff": "^2.1.3",
|
|
37
37
|
"jwt-decode": "^4.0.0",
|
|
38
38
|
"react-icons": "^5.5.0",
|
|
@@ -41,40 +41,41 @@
|
|
|
41
41
|
},
|
|
42
42
|
"peerDependencies": {
|
|
43
43
|
"@ant-design/icons": "^6.0.0",
|
|
44
|
-
"antd": "^5.27.
|
|
44
|
+
"antd": "^5.27.5",
|
|
45
45
|
"antd-style": "^3.7.1",
|
|
46
46
|
"echarts": "^5.6.0 || ^6.0.0",
|
|
47
47
|
"echarts-for-react": "^3.0.2",
|
|
48
48
|
"echarts-gl": "^2.0.9",
|
|
49
|
-
"react": "^
|
|
50
|
-
"react-dom": "^
|
|
49
|
+
"react": "^19.2.0",
|
|
50
|
+
"react-dom": "^19.2.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@ant-design/icons": "^6.
|
|
54
|
-
"@
|
|
53
|
+
"@ant-design/icons": "^6.1.0",
|
|
54
|
+
"@ant-design/v5-patch-for-react-19": "^1.0.3",
|
|
55
|
+
"@eslint/js": "^9.38.0",
|
|
55
56
|
"@ianvs/prettier-plugin-sort-imports": "^4.7.0",
|
|
56
|
-
"@rspress/
|
|
57
|
-
"@
|
|
58
|
-
"@types/
|
|
59
|
-
"@types/react
|
|
60
|
-
"
|
|
57
|
+
"@rspress/core": "^2.0.0-beta.34",
|
|
58
|
+
"@rspress/plugin-preview": "^2.0.0-beta.34",
|
|
59
|
+
"@types/node": "^22.18.11",
|
|
60
|
+
"@types/react": "^19.2.2",
|
|
61
|
+
"@types/react-dom": "^19.2.2",
|
|
62
|
+
"antd": "^5.27.5",
|
|
61
63
|
"antd-style": "^3.7.1",
|
|
62
64
|
"copyfiles": "^2.4.1",
|
|
63
65
|
"echarts": "^6.0.0",
|
|
64
66
|
"echarts-for-react": "^3.0.4",
|
|
65
67
|
"echarts-gl": "^2.0.9",
|
|
66
|
-
"eslint": "^9.
|
|
67
|
-
"eslint-plugin-react-hooks": "^
|
|
68
|
-
"globals": "^16.
|
|
68
|
+
"eslint": "^9.38.0",
|
|
69
|
+
"eslint-plugin-react-hooks": "^7.0.0",
|
|
70
|
+
"globals": "^16.4.0",
|
|
69
71
|
"prettier": "^3.6.2",
|
|
70
|
-
"react": "^
|
|
71
|
-
"react-dom": "^
|
|
72
|
+
"react": "^19.2.0",
|
|
73
|
+
"react-dom": "^19.2.0",
|
|
72
74
|
"react-markdown": "^10.1.0",
|
|
73
75
|
"remark-gfm": "^4.0.1",
|
|
74
|
-
"
|
|
75
|
-
"
|
|
76
|
-
"typescript": "^
|
|
77
|
-
"typescript-eslint": "^8.41.0"
|
|
76
|
+
"typedoc": "^0.28.14",
|
|
77
|
+
"typescript": "^5.9.3",
|
|
78
|
+
"typescript-eslint": "^8.46.1"
|
|
78
79
|
},
|
|
79
80
|
"overrides": {
|
|
80
81
|
"echarts-gl": {
|
package/src/components/Auth.tsx
CHANGED
|
@@ -544,7 +544,12 @@ export function AuthCheck(props: AuthCheckProps): ReactNode {
|
|
|
544
544
|
if (effectDone.current) return;
|
|
545
545
|
effectDone.current = true;
|
|
546
546
|
if (!auth.userInfo) {
|
|
547
|
-
auth.validate().catch(
|
|
547
|
+
auth.validate().catch(
|
|
548
|
+
props.onFail ??
|
|
549
|
+
(() => {
|
|
550
|
+
auth.login(location.href);
|
|
551
|
+
}),
|
|
552
|
+
);
|
|
548
553
|
}
|
|
549
554
|
// 这个副作用只在首次渲染时执行
|
|
550
555
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useEffect,
|
|
3
|
+
useImperativeHandle,
|
|
4
|
+
useRef,
|
|
5
|
+
type ComponentType,
|
|
6
|
+
} from "react";
|
|
7
|
+
|
|
8
|
+
import { debugAssert } from "../utils/debug";
|
|
9
|
+
import type { EChartsHOCType, EChartsProps, EChartsRef } from "./ECharts";
|
|
10
|
+
import EChartsInstance from "./ECharts";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 为 ECharts 增加自适应容器尺寸的能力。
|
|
14
|
+
*
|
|
15
|
+
* 该组件通常需要添加类似 `width: 100%; height: 100%;` 的 CSS 属性,或者放在 Flex/Grid
|
|
16
|
+
* 容器中。
|
|
17
|
+
*
|
|
18
|
+
* 为了保证图像能正常缩小,如果使用 Flex,请务必设置 flex 方向上的
|
|
19
|
+
* `min-width`/`min-height`;在 Grid 中则需要确保 ECharts 所在的行/列的宽度设置为
|
|
20
|
+
* `minmax(..., ...fr)` 而不可直接使用 `fr`。不这么做会导致图像只能放大而无法缩小。
|
|
21
|
+
*/
|
|
22
|
+
export const withAutoResize: EChartsHOCType =
|
|
23
|
+
<Props extends EChartsProps>(ECharts: ComponentType<Props>) =>
|
|
24
|
+
(props: Props) => {
|
|
25
|
+
"use memo";
|
|
26
|
+
const { ref: outerRef, ...rest } = props;
|
|
27
|
+
|
|
28
|
+
const ref = useRef<EChartsRef>(null);
|
|
29
|
+
useImperativeHandle(outerRef, () => {
|
|
30
|
+
debugAssert(ref.current, "ref should be connected");
|
|
31
|
+
return ref.current;
|
|
32
|
+
}, []);
|
|
33
|
+
|
|
34
|
+
useEffect(() => {
|
|
35
|
+
let firstFire = true;
|
|
36
|
+
const observer = new ResizeObserver(() => {
|
|
37
|
+
// 首次触发时不调整大小,以免打断动画
|
|
38
|
+
if (firstFire) {
|
|
39
|
+
firstFire = false;
|
|
40
|
+
} else {
|
|
41
|
+
debugAssert(
|
|
42
|
+
ref.current,
|
|
43
|
+
"observer event should only be triggered when ref is connected",
|
|
44
|
+
);
|
|
45
|
+
ref.current.instance?.resize();
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
debugAssert(ref.current, "effect should run after ref is connected");
|
|
49
|
+
observer.observe(ref.current.container);
|
|
50
|
+
return () => observer.disconnect();
|
|
51
|
+
}, []);
|
|
52
|
+
|
|
53
|
+
// @ts-expect-error 严格来说 { ref, ...rest } 不一定就是 Props,
|
|
54
|
+
// 因为 Props 可能是一个类,而 { ref, ...rest } 显然不是这个类的实例。
|
|
55
|
+
// 理想情况下,我们应该规定 Props 必须不能是类。
|
|
56
|
+
// 然而,TypeScript 的类型系统没有办法编码这样的约束。
|
|
57
|
+
// 由于 React 组件的属性对象实际上通常不会是一个类的实例,
|
|
58
|
+
// 否则这个组件会无法使用 JSX 语法实例化,因此这里可以忽略类型错误。
|
|
59
|
+
return <ECharts ref={ref} {...rest} />;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 预置了自适应容器尺寸能力的 ECharts 组件。
|
|
64
|
+
*
|
|
65
|
+
* @see {@linkcode withAutoResize}
|
|
66
|
+
*/
|
|
67
|
+
const AutoResizedECharts = Object.assign(withAutoResize(EChartsInstance), {
|
|
68
|
+
displayName: "AutoResizedECharts",
|
|
69
|
+
});
|
|
70
|
+
export default AutoResizedECharts;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import * as echarts from "echarts/core";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
use,
|
|
5
|
+
useId,
|
|
6
|
+
type ComponentType,
|
|
7
|
+
type ReactNode,
|
|
8
|
+
} from "react";
|
|
9
|
+
|
|
10
|
+
import AutoResizedECharts, { type withAutoResize } from "./AutoResizedECharts"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
11
|
+
import type { EChartsHOCType, EChartsProps } from "./ECharts";
|
|
12
|
+
|
|
13
|
+
const EChartsConnectorContext = createContext<string>("default");
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 为 {@linkcode ConnectedECharts}/{@linkcode withConnector | withConnector(ECharts)}
|
|
17
|
+
* 提供联动上下文,只有同一个上下文内的 ECharts 会联动。
|
|
18
|
+
*/
|
|
19
|
+
export function EChartsConnector({ children }: { children: ReactNode }) {
|
|
20
|
+
const group = useId();
|
|
21
|
+
return (
|
|
22
|
+
<EChartsConnectorContext.Provider value={group}>
|
|
23
|
+
{children}
|
|
24
|
+
</EChartsConnectorContext.Provider>
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* 为 ECharts 增加联动能力。所有添加了该能力的 ECharts 都会相互联动。
|
|
30
|
+
*
|
|
31
|
+
* 可以使用 {@linkcode EChartsConnector} 将同一个页面上的 ECharts
|
|
32
|
+
* 划分为多个组,不同组的 ECharts 不会联动。也可以使用 `group` 属性手动指定联动组。
|
|
33
|
+
*/
|
|
34
|
+
export const withConnector: EChartsHOCType<{ group?: string }> =
|
|
35
|
+
<Props extends EChartsProps>(ECharts: ComponentType<Props>) =>
|
|
36
|
+
(props: Props & { group?: string }) => {
|
|
37
|
+
"use memo";
|
|
38
|
+
const contextGroup = use(EChartsConnectorContext);
|
|
39
|
+
const { onInit, group = contextGroup, ...rest } = props;
|
|
40
|
+
return (
|
|
41
|
+
// @ts-expect-error 见 ./AutoResizedECharts.tsx
|
|
42
|
+
<ECharts
|
|
43
|
+
onInit={(instance) => {
|
|
44
|
+
instance.group = group;
|
|
45
|
+
echarts.connect(group);
|
|
46
|
+
return onInit?.(instance);
|
|
47
|
+
}}
|
|
48
|
+
{...rest}
|
|
49
|
+
/>
|
|
50
|
+
);
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 预置了联动和自适应容器尺寸能力的 ECharts。
|
|
55
|
+
*
|
|
56
|
+
* @see {@linkcode withConnector}
|
|
57
|
+
* @see {@linkcode withAutoResize}
|
|
58
|
+
*/
|
|
59
|
+
const ConnectedECharts = Object.assign(withConnector(AutoResizedECharts), {
|
|
60
|
+
displayName: "ConnectedECharts",
|
|
61
|
+
});
|
|
62
|
+
export default ConnectedECharts;
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
import type { EChartsOption } from "echarts";
|
|
2
|
+
import type {
|
|
3
|
+
EChartsCoreOption,
|
|
4
|
+
EChartsInitOpts,
|
|
5
|
+
ECharts as EChartsInstance,
|
|
6
|
+
SetOptionOpts,
|
|
7
|
+
} from "echarts/core";
|
|
8
|
+
import * as echarts from "echarts/core";
|
|
9
|
+
import {
|
|
10
|
+
useEffect,
|
|
11
|
+
useEffectEvent,
|
|
12
|
+
useImperativeHandle,
|
|
13
|
+
useRef,
|
|
14
|
+
type ComponentType,
|
|
15
|
+
type HTMLAttributes,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
type Ref,
|
|
18
|
+
type RefObject,
|
|
19
|
+
} from "react";
|
|
20
|
+
|
|
21
|
+
import { debugAssert } from "../utils/debug";
|
|
22
|
+
|
|
23
|
+
export interface EChartsProps<Option extends EChartsCoreOption = EChartsOption>
|
|
24
|
+
extends HTMLAttributes<HTMLDivElement> {
|
|
25
|
+
/**
|
|
26
|
+
* 用于命令式操作 ECharts 容器和实例的 ref。
|
|
27
|
+
*
|
|
28
|
+
* 注意 effect 首次执行时 ECharts 实例尚未被创建。如果需要在 ECharts
|
|
29
|
+
* 实例上添加事件监听器或者执行其它的初始化操作,请使用 {@linkcode onInit}。
|
|
30
|
+
*/
|
|
31
|
+
ref?: Ref<EChartsRef>;
|
|
32
|
+
/** 主题。 @default "default" */
|
|
33
|
+
theme?: string | Record<string, any> | null;
|
|
34
|
+
/**
|
|
35
|
+
* 初始化实例时的选项。
|
|
36
|
+
*
|
|
37
|
+
* 修改该属性会导致 ECharts 实例被销毁并重新创建,因此建议不要频繁修改该属性,并且推荐使用
|
|
38
|
+
* React Compiler 或者 useMemo 将该属性记忆化。
|
|
39
|
+
*/
|
|
40
|
+
initOpts?: EChartsInitOpts;
|
|
41
|
+
/** 配置对象。 */
|
|
42
|
+
option?: Option;
|
|
43
|
+
/**
|
|
44
|
+
* 更新 {@linkcode option} 时的选项。其中的 {@linkcode SetOptionOpts.silent | silent}
|
|
45
|
+
* 选项也会在更新 {@linkcode theme} 时使用。
|
|
46
|
+
*
|
|
47
|
+
* 注意该属性变化后只会从下一次 option 更新开始生效。
|
|
48
|
+
*/
|
|
49
|
+
setOptionOpts?: SetOptionOpts;
|
|
50
|
+
/**
|
|
51
|
+
* 初始化实例(`echarts.init()`)后执行的回调。如果返回函数,则会在销毁实例(`instance.dispose()`)之前调用。
|
|
52
|
+
*
|
|
53
|
+
* 推荐在这个回调中为 ECharts 实例添加事件监听器。
|
|
54
|
+
*/
|
|
55
|
+
onInit?: (instance: EChartsInstance) => void | undefined | (() => void);
|
|
56
|
+
/** option 更新(`instance.setOption()`)后执行的回调。注意如果设置了 {@linkcode lazyUpdate},那么这个回调大概率没什么用。 */
|
|
57
|
+
onSetOption?: (instance: EChartsInstance) => void;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface EChartsRef {
|
|
61
|
+
/** 容纳 ECharts 画布的 div 容器。 */
|
|
62
|
+
container: HTMLDivElement;
|
|
63
|
+
/** ECharts 实例。注意该实例在 ref 连接时尚未创建,如果要在实例创建时执行代码请使用 {@linkcode EChartsProps.onInit | onInit} 属性。 */
|
|
64
|
+
instance?: EChartsInstance;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** 用于 HOC 的返回值。 */
|
|
68
|
+
export type EChartsComponentType<Extra> = <
|
|
69
|
+
Option extends EChartsCoreOption = EChartsOption,
|
|
70
|
+
>(
|
|
71
|
+
props: EChartsProps<Option> & Extra,
|
|
72
|
+
) => ReactNode;
|
|
73
|
+
|
|
74
|
+
/** 用于方便定义 HOC。 */
|
|
75
|
+
export interface EChartsHOCType<Extra = {}> {
|
|
76
|
+
<PrevExtra>(
|
|
77
|
+
ECharts: EChartsComponentType<PrevExtra>,
|
|
78
|
+
): EChartsComponentType<PrevExtra & Extra>;
|
|
79
|
+
<Props extends EChartsProps>(
|
|
80
|
+
ECharts: (props: Props) => ReactNode,
|
|
81
|
+
): (props: Props & Extra) => ReactNode;
|
|
82
|
+
<Props extends EChartsProps>(
|
|
83
|
+
ECharts: ComponentType<Props>,
|
|
84
|
+
): ComponentType<Props & Extra>;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* ECharts 的 React 封装,支持 `theme`、`opts` 等初始化参数和 `option` 配置的响应式更新。
|
|
89
|
+
*/
|
|
90
|
+
const ECharts: EChartsComponentType<{}> = <
|
|
91
|
+
Option extends EChartsCoreOption = EChartsOption,
|
|
92
|
+
>(
|
|
93
|
+
props: EChartsProps<Option>,
|
|
94
|
+
) => {
|
|
95
|
+
const {
|
|
96
|
+
ref: outerRef,
|
|
97
|
+
theme,
|
|
98
|
+
initOpts: opts,
|
|
99
|
+
option,
|
|
100
|
+
setOptionOpts,
|
|
101
|
+
onInit,
|
|
102
|
+
onSetOption,
|
|
103
|
+
...rest
|
|
104
|
+
} = props;
|
|
105
|
+
|
|
106
|
+
const ref = useRef<EChartsRef>(null);
|
|
107
|
+
useForwardEChartsRef(outerRef, ref);
|
|
108
|
+
|
|
109
|
+
// theme 变化时,设置 theme
|
|
110
|
+
const setTheme = useEffectEvent(
|
|
111
|
+
(theme: string | Record<string, any> | null | undefined) => {
|
|
112
|
+
debugAssert(ref.current, "effect should run after ref is connected");
|
|
113
|
+
ref.current.instance?.setTheme(theme ?? "default", {
|
|
114
|
+
silent: setOptionOpts?.silent,
|
|
115
|
+
});
|
|
116
|
+
},
|
|
117
|
+
);
|
|
118
|
+
// 没必要判断是否首次运行,因为首次运行时 ref.current?.instance 必定为 undefined
|
|
119
|
+
useEffect(() => setTheme(theme), [theme]);
|
|
120
|
+
|
|
121
|
+
// option 变化时,设置 option
|
|
122
|
+
const setOption = useEffectEvent(
|
|
123
|
+
(option: Option, isInit: boolean = false) => {
|
|
124
|
+
debugAssert(ref.current, "effect should run after ref is connected");
|
|
125
|
+
const { instance } = ref.current;
|
|
126
|
+
if (instance) {
|
|
127
|
+
instance.setOption(option, {
|
|
128
|
+
...setOptionOpts,
|
|
129
|
+
// 在实例创建时,由于我们已经处在一个 animation frame,没必要再等待下一个 animation frame
|
|
130
|
+
lazyUpdate: !isInit && setOptionOpts?.lazyUpdate,
|
|
131
|
+
});
|
|
132
|
+
onSetOption?.(instance);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
);
|
|
136
|
+
// 同理,没必要判断是否首次运行
|
|
137
|
+
useEffect(() => option && setOption(option), [option]);
|
|
138
|
+
|
|
139
|
+
// opts 变化时,重新创建实例
|
|
140
|
+
const onDisposeRef = useRef<() => void>(null);
|
|
141
|
+
const init = useEffectEvent((opts: EChartsInitOpts | undefined) => {
|
|
142
|
+
debugAssert(ref.current, "effect should run after ref is connected");
|
|
143
|
+
const { container, instance: existingInstance } = ref.current;
|
|
144
|
+
// 严格模式下由于 effect 被执行两次,第二次执行时 instance 已经创建,故应该跳过
|
|
145
|
+
if (existingInstance) return;
|
|
146
|
+
const instance = echarts.init(container, theme, opts);
|
|
147
|
+
ref.current = { container, instance };
|
|
148
|
+
onDisposeRef.current = onInit?.(instance) ?? null;
|
|
149
|
+
if (option) {
|
|
150
|
+
setOption(option, true);
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
const dispose = useEffectEvent(() => {
|
|
154
|
+
if (ref.current) {
|
|
155
|
+
const { container, instance } = ref.current;
|
|
156
|
+
if (instance) {
|
|
157
|
+
onDisposeRef.current?.();
|
|
158
|
+
instance.dispose();
|
|
159
|
+
ref.current = { container };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
useEffect(() => {
|
|
164
|
+
// 将实例创建延迟到下一个 animation frame 以确保首次渲染尺寸正确
|
|
165
|
+
const animationFrame = requestAnimationFrame(() => init(opts));
|
|
166
|
+
return () => {
|
|
167
|
+
cancelAnimationFrame(animationFrame);
|
|
168
|
+
dispose();
|
|
169
|
+
};
|
|
170
|
+
}, [opts]);
|
|
171
|
+
|
|
172
|
+
return (
|
|
173
|
+
<div
|
|
174
|
+
ref={(container) => {
|
|
175
|
+
if (container === null) {
|
|
176
|
+
ref.current = null;
|
|
177
|
+
} else {
|
|
178
|
+
ref.current = { container };
|
|
179
|
+
}
|
|
180
|
+
}}
|
|
181
|
+
{...rest}
|
|
182
|
+
/>
|
|
183
|
+
);
|
|
184
|
+
};
|
|
185
|
+
export default ECharts;
|
|
186
|
+
|
|
187
|
+
// React Compiler 无法处理 getter
|
|
188
|
+
function useForwardEChartsRef(
|
|
189
|
+
outerRef: Ref<EChartsRef> | undefined,
|
|
190
|
+
innerRef: RefObject<EChartsRef | null>,
|
|
191
|
+
) {
|
|
192
|
+
useImperativeHandle(outerRef, () => {
|
|
193
|
+
debugAssert(innerRef.current, "ref should be connected");
|
|
194
|
+
return {
|
|
195
|
+
get container() {
|
|
196
|
+
debugAssert(innerRef.current, "ref should be connected");
|
|
197
|
+
return innerRef.current.container;
|
|
198
|
+
},
|
|
199
|
+
get instance() {
|
|
200
|
+
debugAssert(innerRef.current, "ref should be connected");
|
|
201
|
+
return innerRef.current.instance;
|
|
202
|
+
},
|
|
203
|
+
};
|
|
204
|
+
// oxlint-disable-next-line exhaustive-deps
|
|
205
|
+
}, []);
|
|
206
|
+
}
|
|
@@ -0,0 +1,190 @@
|
|
|
1
|
+
import type { ECharts } from "echarts/core";
|
|
2
|
+
import {
|
|
3
|
+
createContext,
|
|
4
|
+
use,
|
|
5
|
+
useEffect,
|
|
6
|
+
useEffectEvent,
|
|
7
|
+
useId,
|
|
8
|
+
useImperativeHandle,
|
|
9
|
+
useRef,
|
|
10
|
+
useState,
|
|
11
|
+
type ComponentType,
|
|
12
|
+
type ReactNode,
|
|
13
|
+
} from "react";
|
|
14
|
+
import type { Element, ElementProps, Group } from "zrender";
|
|
15
|
+
|
|
16
|
+
import { debugAssert } from "../utils/debug";
|
|
17
|
+
import type { withAutoResize } from "./AutoResizedECharts"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
18
|
+
import ConnectedECharts, { type withConnector } from "./ConnectedECharts"; // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
19
|
+
import type { EChartsHOCType, EChartsProps, EChartsRef } from "./ECharts";
|
|
20
|
+
|
|
21
|
+
interface LeftAlignContext {
|
|
22
|
+
maxLabelsWidth: number;
|
|
23
|
+
register: (id: string, size: number) => void;
|
|
24
|
+
unregister: (id: string) => void;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const LeftAlignContext = createContext<LeftAlignContext | null>(null);
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 为 {@linkcode LeftAlignedECharts} 和
|
|
31
|
+
* {@linkcode withLeftAlign | withLeftAlign(ECharts)}
|
|
32
|
+
* 提供对齐上下文。
|
|
33
|
+
*/
|
|
34
|
+
export function EChartsLeftAligner({ children }: { children: ReactNode }) {
|
|
35
|
+
const [labelsWidthMap, setLabelsWidthMap] = useState(
|
|
36
|
+
new Map<string, number>(),
|
|
37
|
+
);
|
|
38
|
+
return (
|
|
39
|
+
<LeftAlignContext.Provider
|
|
40
|
+
value={{
|
|
41
|
+
maxLabelsWidth: Math.max(...labelsWidthMap.values()),
|
|
42
|
+
register: (id, size) =>
|
|
43
|
+
setLabelsWidthMap((map) => new Map(map).set(id, size)),
|
|
44
|
+
unregister: (id) =>
|
|
45
|
+
setLabelsWidthMap(
|
|
46
|
+
(map) => ((map = new Map(map)), map.delete(id), map),
|
|
47
|
+
),
|
|
48
|
+
}}
|
|
49
|
+
>
|
|
50
|
+
{children}
|
|
51
|
+
</LeftAlignContext.Provider>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function findLabelsGroup<T extends ElementProps>(
|
|
56
|
+
element: Element<T>,
|
|
57
|
+
): Group | undefined {
|
|
58
|
+
if (element.isGroup) {
|
|
59
|
+
const group = element as Group;
|
|
60
|
+
if (group.children().every((el) => el.type === "text")) {
|
|
61
|
+
return group;
|
|
62
|
+
} else {
|
|
63
|
+
for (const child of group.children()) {
|
|
64
|
+
const found = findLabelsGroup(child);
|
|
65
|
+
if (found) {
|
|
66
|
+
return found;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getLabelsWidth(instance: ECharts) {
|
|
74
|
+
const yAxis = instance
|
|
75
|
+
.getZr()
|
|
76
|
+
.storage.getRoots()
|
|
77
|
+
.find(
|
|
78
|
+
(it: unknown) =>
|
|
79
|
+
(it as { __ecComponentInfo: { mainType: string } }).__ecComponentInfo
|
|
80
|
+
.mainType === "yAxis",
|
|
81
|
+
);
|
|
82
|
+
if (!yAxis) {
|
|
83
|
+
return undefined;
|
|
84
|
+
}
|
|
85
|
+
const labelsGroup = findLabelsGroup(yAxis as unknown as Element);
|
|
86
|
+
if (!labelsGroup) {
|
|
87
|
+
return undefined;
|
|
88
|
+
}
|
|
89
|
+
// 不知道为什么,直接在 labelsGroup 上使用 getBoundingRect 测量到的宽度有时是错误的
|
|
90
|
+
const childrenWidths = labelsGroup
|
|
91
|
+
.children()
|
|
92
|
+
.map((it) => it.getBoundingRect().width);
|
|
93
|
+
return Math.max(...childrenWidths);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 为 ECharts 添加与其它 ECharts 纵轴左对齐的能力。
|
|
98
|
+
*
|
|
99
|
+
* 使用该组件时必须满足下列限制:
|
|
100
|
+
*
|
|
101
|
+
* - 必须包裹在 {@linkcode EChartsLeftAligner} 内,否则会立即抛出错误。
|
|
102
|
+
* - 图像所在的容器本身必须已经左对齐。
|
|
103
|
+
* - 图像中必须包含且仅包含一个 grid 坐标系。
|
|
104
|
+
* 如果图像不包含 grid,那么该组件不会产生任何效果。
|
|
105
|
+
* 如果图像包含多个 grid,那么该组件只会对齐其中一个 grid,且无法保证是哪一个。
|
|
106
|
+
* - grid 必须有且仅有一个纵轴,并且在左侧,不满足这一条件的影响同上一条。
|
|
107
|
+
* - grid 坐标系必须被设置为 `{ containLabel: true }` 或者
|
|
108
|
+
* `{ outerBoundsMode: "same", outerBoundsContain: "axisLabel" }`。
|
|
109
|
+
* 这也是组件库内的 `echarts.grid()` 的默认设置。
|
|
110
|
+
* 如果你不这么设置,而是使用 `left` 和 `bottom` 来手动对齐轴线,那就没必要使用这个组件,强行使用可能导致布局异常。
|
|
111
|
+
* - grid 坐标系的 `left` 属性会被该组件覆盖,最终效果总是会使得纵轴标签最宽的坐标系的标签紧贴容器左边缘(即 `left === 0`)。
|
|
112
|
+
* - 左对齐时原本的过渡动画会被打断。
|
|
113
|
+
*/
|
|
114
|
+
export const withLeftAlign: EChartsHOCType =
|
|
115
|
+
<Props extends EChartsProps>(ECharts: ComponentType<Props>) =>
|
|
116
|
+
(props: Props) => {
|
|
117
|
+
"use memo";
|
|
118
|
+
const { ref: outerRef, option, onSetOption, ...rest } = props;
|
|
119
|
+
|
|
120
|
+
const ref = useRef<EChartsRef>(null);
|
|
121
|
+
useImperativeHandle(outerRef, () => {
|
|
122
|
+
debugAssert(ref.current, "ref should be connected");
|
|
123
|
+
return ref.current;
|
|
124
|
+
}, []);
|
|
125
|
+
|
|
126
|
+
const context = use(LeftAlignContext);
|
|
127
|
+
if (!context) {
|
|
128
|
+
throw new Error(
|
|
129
|
+
"LeftAlignedECharts or withLeftAlign(ECharts) must be wrapped inside an EChartsLeftAligner!",
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
const { maxLabelsWidth, register, unregister } = context;
|
|
133
|
+
const id = useId();
|
|
134
|
+
const [labelsWidth, setLabelsWidth] = useState<number | undefined>();
|
|
135
|
+
|
|
136
|
+
// 布局变化时,测量并报告最新的 labelsWidth 值
|
|
137
|
+
const reportLayoutChange = useEffectEvent((instance: ECharts) => {
|
|
138
|
+
const labelsWidth = getLabelsWidth(instance);
|
|
139
|
+
if (labelsWidth !== undefined) {
|
|
140
|
+
setLabelsWidth(labelsWidth);
|
|
141
|
+
register(id, labelsWidth);
|
|
142
|
+
}
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
// 卸载时,注销自身
|
|
146
|
+
const dispose = useEffectEvent(() => unregister(id));
|
|
147
|
+
useEffect(() => dispose, []);
|
|
148
|
+
|
|
149
|
+
return (
|
|
150
|
+
// @ts-expect-error 见 ./AutoResizedECharts.tsx
|
|
151
|
+
<ECharts
|
|
152
|
+
ref={ref}
|
|
153
|
+
option={
|
|
154
|
+
option === undefined ||
|
|
155
|
+
option.grid === undefined ||
|
|
156
|
+
labelsWidth === undefined ||
|
|
157
|
+
Number.isNaN(labelsWidth) ||
|
|
158
|
+
labelsWidth >= maxLabelsWidth
|
|
159
|
+
? option
|
|
160
|
+
: {
|
|
161
|
+
...option,
|
|
162
|
+
grid: (Array.isArray(option.grid)
|
|
163
|
+
? option.grid
|
|
164
|
+
: [option.grid]
|
|
165
|
+
).map((it) => ({
|
|
166
|
+
...it,
|
|
167
|
+
left: maxLabelsWidth - labelsWidth,
|
|
168
|
+
})),
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
onSetOption={(instance) => {
|
|
172
|
+
reportLayoutChange(instance);
|
|
173
|
+
return onSetOption?.(instance);
|
|
174
|
+
}}
|
|
175
|
+
{...rest}
|
|
176
|
+
/>
|
|
177
|
+
);
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* 预置了纵轴左对齐、联动和自适应容器尺寸能力的 ECharts 组件。
|
|
182
|
+
*
|
|
183
|
+
* @see {@linkcode withLeftAlign}
|
|
184
|
+
* @see {@linkcode withConnector}
|
|
185
|
+
* @see {@linkcode withAutoResize}
|
|
186
|
+
*/
|
|
187
|
+
const LeftAlignedECharts = Object.assign(withLeftAlign(ConnectedECharts), {
|
|
188
|
+
displayName: "ConnectedECharts",
|
|
189
|
+
});
|
|
190
|
+
export default LeftAlignedECharts;
|
|
@@ -220,7 +220,8 @@ const useStyles = createStyles(
|
|
|
220
220
|
top: 75%;
|
|
221
221
|
right: 0;
|
|
222
222
|
transform: translate(50%, -50%);
|
|
223
|
-
|
|
223
|
+
width: 32px;
|
|
224
|
+
height: 32px;
|
|
224
225
|
font-size: ${token.sizeSM}px;
|
|
225
226
|
|
|
226
227
|
transition: opacity ${token.motionDurationMid};
|
|
@@ -234,12 +235,6 @@ const useStyles = createStyles(
|
|
|
234
235
|
display: flex;
|
|
235
236
|
flex-direction: column;
|
|
236
237
|
}
|
|
237
|
-
|
|
238
|
-
/* 为了提高权重 */
|
|
239
|
-
& .${cx(sidebarBtn)} {
|
|
240
|
-
width: 32px;
|
|
241
|
-
height: 32px;
|
|
242
|
-
}
|
|
243
238
|
& .${cx(sidebarMenu)}.${cx(sidebarMenu)} {
|
|
244
239
|
border-inline-end: none;
|
|
245
240
|
}
|