@publishfx/publish-chart 2.0.0 → 2.0.2
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/CHANGELOG.md +10 -0
- package/dist/components/g2/base/G2GaugeChart.d.ts +6 -18
- package/dist/components/g2/base/G2GaugeChart.js +309 -114
- package/dist/components/g2/base/G2LineChart.d.ts +8 -0
- package/dist/components/g2/base/G2LineChart.js +151 -0
- package/dist/components/g2/base/gaugePropsValidator.d.ts +42 -0
- package/dist/components/g2/base/gaugePropsValidator.js +139 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -1
- package/dist/utils/chartHelpers.d.ts +1 -0
- package/dist/utils/chartHelpers.js +6 -1
- package/package.json +1 -1
- package/dist/components/g2/base/G2GaugeChart.example.d.ts +0 -10
- package/dist/components/g2/base/G2GaugeChart.example.js +0 -88
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
**2.0.2** feat(chart): 优化G2GaugeChart组件,仅基于数据驱动UI,不涉及业务逻辑
|
|
2
|
+
- 2026-02-05T14:07:11+08:00 [feat(chart): 优化G2GaugeChart组件,仅基于数据驱动UI,不涉及业务逻辑](http://lf.git.oa.mt/publish_platform/web/publish/commit/ad3728038c692c40a03dcb6a38f7b77b0952cccc)
|
|
3
|
+
|
|
4
|
+
**2.0.1** feat(chart): 使用svg重写gauge图表组件
|
|
5
|
+
- 2026-02-04T11:18:21+08:00 [feat(chart): 使用svg重写gauge图表组件](http://lf.git.oa.mt/publish_platform/web/publish/commit/47a981e483d2a4df79d473e1053d9f9ea927ee10)
|
|
6
|
+
- 2026-02-03T19:25:53+08:00 [feat(chart): 修改ts错误](http://lf.git.oa.mt/publish_platform/web/publish/commit/40e4e8f44c7ff02b079974eca52ece225180329c)
|
|
7
|
+
- 2026-02-03T19:13:19+08:00 [feat: g2LineChart](http://lf.git.oa.mt/publish_platform/web/publish/commit/86e769b63b237c178a82de0c8dcf3c8d9ffdbab6)
|
|
8
|
+
- 2026-02-02T14:37:21+08:00 [feat(chart): 修改ts错误问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/023b12647850619e340164e02a1f86e884eccf35)
|
|
9
|
+
- 2026-02-02T11:12:14+08:00 [feat(chart): 新增G2图表组件](http://lf.git.oa.mt/publish_platform/web/publish/commit/cfec9e945ceb9d8c0645dcea3d9adfe43c8d0bdc)
|
|
10
|
+
|
|
1
11
|
**2.0.0** feat(chart): 修改ts错误问题
|
|
2
12
|
- 2026-02-02T14:37:21+08:00 [feat(chart): 修改ts错误问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/023b12647850619e340164e02a1f86e884eccf35)
|
|
3
13
|
- 2026-02-02T11:12:14+08:00 [feat(chart): 新增G2图表组件](http://lf.git.oa.mt/publish_platform/web/publish/commit/cfec9e945ceb9d8c0645dcea3d9adfe43c8d0bdc)
|
|
@@ -1,38 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* G2@5 仪表盘(Gauge)图表组件
|
|
3
|
-
*
|
|
3
|
+
* 使用SVG直接绘制,不依赖G2的canvas渲染
|
|
4
4
|
*/
|
|
5
|
-
import React from
|
|
5
|
+
import React from "react";
|
|
6
6
|
export interface G2GaugeChartProps {
|
|
7
|
-
/** 当前值 */
|
|
8
7
|
value: number;
|
|
9
|
-
/** 最小值 */
|
|
10
8
|
min?: number;
|
|
11
|
-
/** 最大值 */
|
|
12
9
|
max: number;
|
|
13
|
-
/** 目标值(用于设置阈值区间和刻度) */
|
|
14
|
-
target?: number;
|
|
15
|
-
/** 自定义阈值区间配置(优先级高于 target) */
|
|
16
10
|
thresholds?: number[] | Array<{
|
|
17
|
-
/** 阈值点 */
|
|
18
11
|
value: number;
|
|
19
|
-
/** 区间颜色 */
|
|
20
12
|
color: string;
|
|
21
13
|
}>;
|
|
22
|
-
/** 自定义刻度点(优先级高于 target) */
|
|
23
|
-
ticks?: number[];
|
|
24
|
-
/** 图表高度 */
|
|
25
14
|
height?: number;
|
|
26
|
-
/** 是否显示百分比 */
|
|
27
15
|
showPercent?: boolean;
|
|
28
|
-
/** 自定义格式化函数 */
|
|
29
16
|
formatter?: (value: number, percent: number) => string;
|
|
30
|
-
/** 刻度标签格式化函数(用于格式化刻度上的数值,如 100000 -> "100.00k") */
|
|
31
17
|
tickFormatter?: (value: number) => string;
|
|
32
|
-
/** 指针颜色 */
|
|
33
18
|
pointerColor?: string;
|
|
34
|
-
/** 阈值颜色数组 */
|
|
35
19
|
thresholdColors?: string[];
|
|
20
|
+
tooltip?: {
|
|
21
|
+
label?: string;
|
|
22
|
+
formatter?: (value: number) => string;
|
|
23
|
+
};
|
|
36
24
|
}
|
|
37
25
|
declare const G2GaugeChart: React.FC<G2GaugeChartProps>;
|
|
38
26
|
export default G2GaugeChart;
|
|
@@ -1,135 +1,330 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
-
import {
|
|
4
|
-
const G2GaugeChart = ({
|
|
5
|
-
'#F4664A',
|
|
6
|
-
'#FAAD14',
|
|
7
|
-
'#52C41A'
|
|
8
|
-
] })=>{
|
|
3
|
+
import { safeFormat, safeTickFormat, validateAndNormalizeProps } from "./gaugePropsValidator.js";
|
|
4
|
+
const G2GaugeChart = (props)=>{
|
|
9
5
|
const containerRef = useRef(null);
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
min,
|
|
23
|
-
Math.round(min + step),
|
|
24
|
-
Math.round(min + 2 * step),
|
|
25
|
-
Math.round(min + 3 * step),
|
|
26
|
-
max
|
|
27
|
-
];
|
|
28
|
-
}, [
|
|
29
|
-
min,
|
|
30
|
-
max,
|
|
31
|
-
target,
|
|
32
|
-
ticks
|
|
33
|
-
]);
|
|
34
|
-
const thresholdValues = useMemo(()=>{
|
|
35
|
-
if (thresholds) {
|
|
36
|
-
if (Array.isArray(thresholds) && thresholds.length > 0 && 'number' == typeof thresholds[0]) return thresholds;
|
|
37
|
-
if (Array.isArray(thresholds) && thresholds.length > 0 && 'object' == typeof thresholds[0]) return thresholds.map((t)=>t.value);
|
|
38
|
-
}
|
|
39
|
-
if (target) return [
|
|
40
|
-
Math.round(0.6 * target),
|
|
41
|
-
Math.round(0.9 * target),
|
|
42
|
-
target
|
|
43
|
-
];
|
|
44
|
-
const step = (max - min) / 3;
|
|
45
|
-
return [
|
|
46
|
-
Math.round(min + step),
|
|
47
|
-
Math.round(min + 2 * step),
|
|
48
|
-
max
|
|
49
|
-
];
|
|
50
|
-
}, [
|
|
51
|
-
min,
|
|
52
|
-
max,
|
|
53
|
-
target,
|
|
54
|
-
thresholds
|
|
55
|
-
]);
|
|
56
|
-
const thresholdColorArray = useMemo(()=>{
|
|
57
|
-
if (thresholds && Array.isArray(thresholds) && thresholds.length > 0 && 'object' == typeof thresholds[0]) return thresholds.map((t)=>t.color);
|
|
58
|
-
return thresholdColors;
|
|
59
|
-
}, [
|
|
60
|
-
thresholds,
|
|
61
|
-
thresholdColors
|
|
6
|
+
const normalizedProps = useMemo(()=>validateAndNormalizeProps(props), [
|
|
7
|
+
props.value,
|
|
8
|
+
props.min,
|
|
9
|
+
props.max,
|
|
10
|
+
props.thresholds,
|
|
11
|
+
props.thresholdColors,
|
|
12
|
+
props.height,
|
|
13
|
+
props.showPercent,
|
|
14
|
+
props.formatter,
|
|
15
|
+
props.tickFormatter,
|
|
16
|
+
props.pointerColor,
|
|
17
|
+
props.tooltip
|
|
62
18
|
]);
|
|
19
|
+
const { value, min, max, thresholds: thresholdValues, colors: thresholdColorArray, height, showPercent, formatter, tickFormatter, pointerColor, tooltip } = normalizedProps;
|
|
63
20
|
useEffect(()=>{
|
|
64
21
|
if (!containerRef.current) return;
|
|
65
|
-
|
|
66
|
-
const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
22
|
+
const startAngle = -1.2 * Math.PI;
|
|
23
|
+
const endAngle = 0.2 * Math.PI;
|
|
24
|
+
const colors = [
|
|
25
|
+
...thresholdColorArray
|
|
26
|
+
];
|
|
27
|
+
while(colors.length < thresholdValues.length)colors.push(colors[colors.length - 1] || "#ccc");
|
|
28
|
+
requestAnimationFrame(()=>{
|
|
29
|
+
if (!containerRef.current) return;
|
|
30
|
+
const rect = containerRef.current.getBoundingClientRect();
|
|
31
|
+
const width = rect.width || containerRef.current.offsetWidth;
|
|
32
|
+
const height = rect.height || containerRef.current.offsetHeight;
|
|
33
|
+
const centerX = width / 2;
|
|
34
|
+
const centerY = height / 2;
|
|
35
|
+
const radius = Math.min(width, height) / 2;
|
|
36
|
+
const innerRadius = 0.85 * radius;
|
|
37
|
+
const outerRadius = 0.95 * radius;
|
|
38
|
+
const totalAngle = endAngle - startAngle;
|
|
39
|
+
const topPadding = 40;
|
|
40
|
+
const bottomPadding = -30;
|
|
41
|
+
const leftPadding = 0;
|
|
42
|
+
const rightPadding = 0;
|
|
43
|
+
let svgOverlay = containerRef.current.querySelector('svg.gauge-arc-overlay');
|
|
44
|
+
if (svgOverlay) {
|
|
45
|
+
svgOverlay.innerHTML = '';
|
|
46
|
+
svgOverlay.setAttribute('width', String(width));
|
|
47
|
+
svgOverlay.setAttribute('height', String(height));
|
|
48
|
+
svgOverlay.setAttribute('viewBox', `-${leftPadding} -${topPadding} ${width + leftPadding + rightPadding} ${height + topPadding + bottomPadding}`);
|
|
49
|
+
svgOverlay.style.top = '0';
|
|
50
|
+
svgOverlay.style.left = '0';
|
|
51
|
+
} else {
|
|
52
|
+
svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
53
|
+
svgOverlay.setAttribute('class', 'gauge-arc-overlay');
|
|
54
|
+
svgOverlay.setAttribute('width', String(width));
|
|
55
|
+
svgOverlay.setAttribute('height', String(height));
|
|
56
|
+
svgOverlay.setAttribute('viewBox', `-${leftPadding} -${topPadding} ${width + leftPadding + rightPadding} ${height + topPadding + bottomPadding}`);
|
|
57
|
+
svgOverlay.style.position = 'absolute';
|
|
58
|
+
svgOverlay.style.top = '0';
|
|
59
|
+
svgOverlay.style.left = '0';
|
|
60
|
+
svgOverlay.style.pointerEvents = 'none';
|
|
61
|
+
svgOverlay.style.overflow = 'visible';
|
|
62
|
+
containerRef.current.appendChild(svgOverlay);
|
|
63
|
+
}
|
|
64
|
+
for(let i = 0; i < thresholdValues.length; i++){
|
|
65
|
+
const start = 0 === i ? min : thresholdValues[i - 1] < min ? min : thresholdValues[i - 1];
|
|
66
|
+
const end = thresholdValues[i] < min ? min : thresholdValues[i];
|
|
67
|
+
const color = thresholdColorArray[i] || '#ccc';
|
|
68
|
+
const startAngleValue = startAngle + (start - min) / (max - min) * totalAngle;
|
|
69
|
+
const endAngleValue = startAngle + (end - min) / (max - min) * totalAngle;
|
|
70
|
+
const largeArcFlag = Math.abs(endAngleValue - startAngleValue) > Math.PI ? 1 : 0;
|
|
71
|
+
const sweepFlag = 1;
|
|
72
|
+
const startX = centerX + outerRadius * Math.cos(startAngleValue);
|
|
73
|
+
const startY = centerY + outerRadius * Math.sin(startAngleValue);
|
|
74
|
+
const endX = centerX + outerRadius * Math.cos(endAngleValue);
|
|
75
|
+
const endY = centerY + outerRadius * Math.sin(endAngleValue);
|
|
76
|
+
const innerStartX = centerX + innerRadius * Math.cos(startAngleValue);
|
|
77
|
+
const innerStartY = centerY + innerRadius * Math.sin(startAngleValue);
|
|
78
|
+
const innerEndX = centerX + innerRadius * Math.cos(endAngleValue);
|
|
79
|
+
const innerEndY = centerY + innerRadius * Math.sin(endAngleValue);
|
|
80
|
+
const pathString = [
|
|
81
|
+
`M ${startX} ${startY}`,
|
|
82
|
+
`A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}`,
|
|
83
|
+
`L ${innerEndX} ${innerEndY}`,
|
|
84
|
+
`A ${innerRadius} ${innerRadius} 0 ${largeArcFlag} ${1 - sweepFlag} ${innerStartX} ${innerStartY}`,
|
|
85
|
+
'Z'
|
|
86
|
+
].join(' ');
|
|
87
|
+
const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path');
|
|
88
|
+
pathElement.setAttribute('d', pathString);
|
|
89
|
+
pathElement.setAttribute('fill', color);
|
|
90
|
+
pathElement.setAttribute('stroke', color);
|
|
91
|
+
pathElement.setAttribute('stroke-width', '0');
|
|
92
|
+
svgOverlay.appendChild(pathElement);
|
|
93
|
+
}
|
|
94
|
+
const tickLength = 0.05 * radius;
|
|
95
|
+
const textOffset = 0.15 * radius;
|
|
96
|
+
const startAngleValue = startAngle;
|
|
97
|
+
const startX = centerX + innerRadius * Math.cos(startAngleValue);
|
|
98
|
+
const startY = centerY + innerRadius * Math.sin(startAngleValue);
|
|
99
|
+
const startTickEndX = centerX + (outerRadius + tickLength) * Math.cos(startAngleValue);
|
|
100
|
+
const startTickEndY = centerY + (outerRadius + tickLength) * Math.sin(startAngleValue);
|
|
101
|
+
const startTextX = centerX + (outerRadius + textOffset) * Math.cos(startAngleValue);
|
|
102
|
+
const startTextY = centerY + (outerRadius + textOffset) * Math.sin(startAngleValue);
|
|
103
|
+
const startTickLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
104
|
+
startTickLine.setAttribute('x1', String(startX));
|
|
105
|
+
startTickLine.setAttribute('y1', String(startY));
|
|
106
|
+
startTickLine.setAttribute('x2', String(startTickEndX));
|
|
107
|
+
startTickLine.setAttribute('y2', String(startTickEndY));
|
|
108
|
+
startTickLine.setAttribute('stroke', '#cccccc');
|
|
109
|
+
startTickLine.setAttribute('stroke-width', '2');
|
|
110
|
+
svgOverlay.appendChild(startTickLine);
|
|
111
|
+
const startText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
112
|
+
startText.setAttribute('x', String(startTextX));
|
|
113
|
+
startText.setAttribute('y', String(startTextY));
|
|
114
|
+
startText.setAttribute('fill', '#1d2129');
|
|
115
|
+
startText.setAttribute('font-size', '14');
|
|
116
|
+
startText.setAttribute('text-anchor', 'middle');
|
|
117
|
+
startText.setAttribute('dominant-baseline', 'middle');
|
|
118
|
+
startText.textContent = safeTickFormat(tickFormatter, min, String(min));
|
|
119
|
+
svgOverlay.appendChild(startText);
|
|
120
|
+
const thresholdTextOffset = 0.05 * radius;
|
|
121
|
+
const minThresholdDistance = (max - min) * 0.1;
|
|
122
|
+
const textOffsets = [];
|
|
123
|
+
for(let i = 0; i < thresholdValues.length - 1; i++){
|
|
124
|
+
let currentTextOffset = thresholdTextOffset;
|
|
125
|
+
if (i > 0) {
|
|
126
|
+
const prevThresholdValue = thresholdValues[i - 1];
|
|
127
|
+
const thresholdValue = thresholdValues[i];
|
|
128
|
+
const thresholdDistance = Math.abs(thresholdValue - prevThresholdValue);
|
|
129
|
+
if (thresholdDistance < minThresholdDistance) {
|
|
130
|
+
const prevThresholdAngle = startAngle + (prevThresholdValue - min) / (max - min) * totalAngle;
|
|
131
|
+
const thresholdAngle = startAngle + (thresholdValue - min) / (max - min) * totalAngle;
|
|
132
|
+
const angleDiff = Math.abs(thresholdAngle - prevThresholdAngle);
|
|
133
|
+
if (angleDiff < Math.PI / 6) {
|
|
134
|
+
const prevOffset = textOffsets[i - 1] || thresholdTextOffset;
|
|
135
|
+
currentTextOffset = Math.max(prevOffset + 0.08 * radius, thresholdTextOffset + 0.08 * radius);
|
|
136
|
+
} else currentTextOffset = thresholdTextOffset + 0.04 * radius;
|
|
137
|
+
}
|
|
79
138
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
139
|
+
textOffsets.push(currentTextOffset);
|
|
140
|
+
}
|
|
141
|
+
for(let i = 0; i < thresholdValues.length - 1; i++){
|
|
142
|
+
const thresholdValue = thresholdValues[i];
|
|
143
|
+
const currentTextOffset = textOffsets[i];
|
|
144
|
+
const thresholdAngle = startAngle + (thresholdValue - min) / (max - min) * totalAngle;
|
|
145
|
+
const thresholdTextX = centerX + (outerRadius + currentTextOffset) * Math.cos(thresholdAngle);
|
|
146
|
+
const thresholdTextY = centerY + (outerRadius + currentTextOffset) * Math.sin(thresholdAngle);
|
|
147
|
+
const rotationAngle = (180 * thresholdAngle / Math.PI + 90) % 360;
|
|
148
|
+
const thresholdText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
149
|
+
thresholdText.setAttribute('x', String(thresholdTextX));
|
|
150
|
+
thresholdText.setAttribute('y', String(thresholdTextY));
|
|
151
|
+
thresholdText.setAttribute('fill', '#1d2129');
|
|
152
|
+
thresholdText.setAttribute('font-size', '12');
|
|
153
|
+
thresholdText.setAttribute('text-anchor', 'middle');
|
|
154
|
+
thresholdText.setAttribute('dominant-baseline', 'middle');
|
|
155
|
+
thresholdText.setAttribute('transform', `rotate(${rotationAngle} ${thresholdTextX} ${thresholdTextY})`);
|
|
156
|
+
thresholdText.textContent = safeTickFormat(tickFormatter, thresholdValue, String(thresholdValue));
|
|
157
|
+
svgOverlay.appendChild(thresholdText);
|
|
158
|
+
}
|
|
159
|
+
const endAngleValue = endAngle;
|
|
160
|
+
const endX = centerX + innerRadius * Math.cos(endAngleValue);
|
|
161
|
+
const endY = centerY + innerRadius * Math.sin(endAngleValue);
|
|
162
|
+
const endTickEndX = centerX + (outerRadius + tickLength) * Math.cos(endAngleValue);
|
|
163
|
+
const endTickEndY = centerY + (outerRadius + tickLength) * Math.sin(endAngleValue);
|
|
164
|
+
const endTextX = centerX + (outerRadius + textOffset) * Math.cos(endAngleValue);
|
|
165
|
+
const endTextY = centerY + (outerRadius + textOffset) * Math.sin(endAngleValue);
|
|
166
|
+
const endTickLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
167
|
+
endTickLine.setAttribute('x1', String(endX));
|
|
168
|
+
endTickLine.setAttribute('y1', String(endY));
|
|
169
|
+
endTickLine.setAttribute('x2', String(endTickEndX));
|
|
170
|
+
endTickLine.setAttribute('y2', String(endTickEndY));
|
|
171
|
+
endTickLine.setAttribute('stroke', '#cccccc');
|
|
172
|
+
endTickLine.setAttribute('stroke-width', '2');
|
|
173
|
+
svgOverlay.appendChild(endTickLine);
|
|
174
|
+
const endText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
175
|
+
endText.setAttribute('x', String(endTextX));
|
|
176
|
+
endText.setAttribute('y', String(endTextY));
|
|
177
|
+
endText.setAttribute('fill', '#1d2129');
|
|
178
|
+
endText.setAttribute('font-size', '14');
|
|
179
|
+
endText.setAttribute('text-anchor', 'middle');
|
|
180
|
+
endText.setAttribute('dominant-baseline', 'middle');
|
|
181
|
+
endText.textContent = safeTickFormat(tickFormatter, max, String(max));
|
|
182
|
+
svgOverlay.appendChild(endText);
|
|
183
|
+
const pointerValue = Math.min(Math.max(value, min), max);
|
|
184
|
+
const pointerAngle = startAngle + (pointerValue - min) / (max - min) * totalAngle;
|
|
185
|
+
const pointerStartCircleRadius = 4;
|
|
186
|
+
const pointerLength = 0.6 * outerRadius;
|
|
187
|
+
const pointerStartX = centerX + pointerStartCircleRadius * Math.cos(pointerAngle);
|
|
188
|
+
const pointerStartY = centerY + pointerStartCircleRadius * Math.sin(pointerAngle);
|
|
189
|
+
const pointerEndX = centerX + (pointerStartCircleRadius + pointerLength) * Math.cos(pointerAngle);
|
|
190
|
+
const pointerEndY = centerY + (pointerStartCircleRadius + pointerLength) * Math.sin(pointerAngle);
|
|
191
|
+
const pointerStartCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
192
|
+
pointerStartCircle.setAttribute('cx', String(centerX));
|
|
193
|
+
pointerStartCircle.setAttribute('cy', String(centerY));
|
|
194
|
+
pointerStartCircle.setAttribute('r', String(pointerStartCircleRadius));
|
|
195
|
+
pointerStartCircle.setAttribute('fill', '#fff');
|
|
196
|
+
pointerStartCircle.setAttribute('stroke', pointerColor);
|
|
197
|
+
pointerStartCircle.setAttribute('stroke-width', '1.5');
|
|
198
|
+
svgOverlay.appendChild(pointerStartCircle);
|
|
199
|
+
const pointerLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
200
|
+
pointerLine.setAttribute('x1', String(pointerStartX));
|
|
201
|
+
pointerLine.setAttribute('y1', String(pointerStartY));
|
|
202
|
+
pointerLine.setAttribute('x2', String(pointerEndX));
|
|
203
|
+
pointerLine.setAttribute('y2', String(pointerEndY));
|
|
204
|
+
pointerLine.setAttribute('stroke', pointerColor);
|
|
205
|
+
pointerLine.setAttribute('stroke-width', '3');
|
|
206
|
+
pointerLine.setAttribute('stroke-linecap', 'round');
|
|
207
|
+
svgOverlay.appendChild(pointerLine);
|
|
208
|
+
const percent = value / max * 100;
|
|
209
|
+
const fallbackValue = safeTickFormat(tickFormatter, value, String(value));
|
|
210
|
+
const displayValue = safeFormat(formatter, value, percent, fallbackValue);
|
|
211
|
+
const percentDisplay = percent.toFixed(2);
|
|
212
|
+
const textCenterX = centerX;
|
|
213
|
+
const textCenterY = centerY + 0.3 * radius;
|
|
214
|
+
const textSpacing = 0.18 * radius;
|
|
215
|
+
const valueTextY = textCenterY + +textSpacing;
|
|
216
|
+
const percentTextY = textCenterY + 2 * textSpacing;
|
|
217
|
+
const valueFontSize = Math.max(14, 0.12 * radius);
|
|
218
|
+
const percentFontSize = Math.max(12, 0.09 * radius);
|
|
219
|
+
const valueText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
220
|
+
valueText.setAttribute('x', String(textCenterX));
|
|
221
|
+
valueText.setAttribute('y', String(valueTextY));
|
|
222
|
+
valueText.setAttribute('fill', colors[0] || '#9596d9');
|
|
223
|
+
valueText.setAttribute('font-size', String(valueFontSize));
|
|
224
|
+
valueText.setAttribute('font-weight', 'bold');
|
|
225
|
+
valueText.setAttribute('text-anchor', 'middle');
|
|
226
|
+
valueText.setAttribute('dominant-baseline', 'middle');
|
|
227
|
+
valueText.textContent = displayValue;
|
|
228
|
+
svgOverlay.appendChild(valueText);
|
|
229
|
+
if (showPercent) {
|
|
230
|
+
const percentText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
231
|
+
percentText.setAttribute('x', String(textCenterX));
|
|
232
|
+
percentText.setAttribute('y', String(percentTextY));
|
|
233
|
+
percentText.setAttribute('fill', '#8c8c8c');
|
|
234
|
+
percentText.setAttribute('font-size', String(percentFontSize));
|
|
235
|
+
percentText.setAttribute('text-anchor', 'middle');
|
|
236
|
+
percentText.setAttribute('dominant-baseline', 'middle');
|
|
237
|
+
percentText.textContent = `占比: ${percentDisplay}%`;
|
|
238
|
+
svgOverlay.appendChild(percentText);
|
|
239
|
+
}
|
|
240
|
+
if (tooltip) {
|
|
241
|
+
let tooltipElement = containerRef.current.querySelector('.gauge-tooltip');
|
|
242
|
+
if (!tooltipElement) {
|
|
243
|
+
tooltipElement = document.createElement('div');
|
|
244
|
+
tooltipElement.className = 'gauge-tooltip';
|
|
245
|
+
tooltipElement.style.position = 'absolute';
|
|
246
|
+
tooltipElement.style.pointerEvents = 'none';
|
|
247
|
+
tooltipElement.style.display = 'none';
|
|
248
|
+
tooltipElement.style.backgroundColor = '#fff';
|
|
249
|
+
tooltipElement.style.borderRadius = '4px';
|
|
250
|
+
tooltipElement.style.padding = '8px 12px';
|
|
251
|
+
tooltipElement.style.boxShadow = '0 2px 8px rgba(0, 0, 0, 0.15)';
|
|
252
|
+
tooltipElement.style.fontSize = '12px';
|
|
253
|
+
tooltipElement.style.lineHeight = '1.5';
|
|
254
|
+
tooltipElement.style.zIndex = '1000';
|
|
255
|
+
tooltipElement.style.whiteSpace = 'nowrap';
|
|
256
|
+
containerRef.current.appendChild(tooltipElement);
|
|
96
257
|
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
258
|
+
const tooltipLabel = tooltip.label;
|
|
259
|
+
let tooltipValue;
|
|
260
|
+
if (tooltip.formatter) try {
|
|
261
|
+
tooltipValue = tooltip.formatter(value);
|
|
262
|
+
} catch (error) {
|
|
263
|
+
if ("development" === process.env.NODE_ENV) console.warn("[G2GaugeChart] tooltip.formatter 函数执行出错,使用降级值", {
|
|
264
|
+
error,
|
|
265
|
+
value
|
|
266
|
+
});
|
|
267
|
+
tooltipValue = value.toLocaleString();
|
|
103
268
|
}
|
|
104
|
-
|
|
105
|
-
|
|
269
|
+
else tooltipValue = value.toLocaleString();
|
|
270
|
+
tooltipElement.innerHTML = `
|
|
271
|
+
${tooltipLabel ? `<div style="color: #666; margin-bottom: 4px;">${tooltipLabel}</div>` : ''}
|
|
272
|
+
${tooltipValue ? `<div style="color: #1d2129; font-weight: 500;">${tooltipValue}</div>` : ''}
|
|
273
|
+
`;
|
|
274
|
+
const handleMouseMove = (e)=>{
|
|
275
|
+
if (!tooltipElement || !containerRef.current) return;
|
|
276
|
+
const svgOverlayRect = svgOverlay.getBoundingClientRect();
|
|
277
|
+
const mouseX = e.clientX;
|
|
278
|
+
const mouseY = e.clientY;
|
|
279
|
+
if (mouseX >= svgOverlayRect.left && mouseX <= svgOverlayRect.right && mouseY >= svgOverlayRect.top && mouseY <= svgOverlayRect.bottom) {
|
|
280
|
+
const containerRect = containerRef.current.getBoundingClientRect();
|
|
281
|
+
const relativeX = mouseX - containerRect.left;
|
|
282
|
+
const relativeY = mouseY - containerRect.top;
|
|
283
|
+
const offsetX = 10;
|
|
284
|
+
const offsetY = -10;
|
|
285
|
+
tooltipElement.style.display = 'block';
|
|
286
|
+
tooltipElement.style.left = `${relativeX + offsetX}px`;
|
|
287
|
+
tooltipElement.style.top = `${relativeY + offsetY}px`;
|
|
288
|
+
tooltipElement.style.transform = 'translateY(-100%)';
|
|
289
|
+
} else tooltipElement.style.display = 'none';
|
|
290
|
+
};
|
|
291
|
+
const handleMouseLeave = ()=>{
|
|
292
|
+
if (tooltipElement) tooltipElement.style.display = 'none';
|
|
293
|
+
};
|
|
294
|
+
containerRef.current.addEventListener('mousemove', handleMouseMove);
|
|
295
|
+
containerRef.current.addEventListener('mouseleave', handleMouseLeave);
|
|
296
|
+
const cleanup = ()=>{
|
|
297
|
+
if (containerRef.current) {
|
|
298
|
+
containerRef.current.removeEventListener('mousemove', handleMouseMove);
|
|
299
|
+
containerRef.current.removeEventListener('mouseleave', handleMouseLeave);
|
|
300
|
+
}
|
|
301
|
+
};
|
|
302
|
+
containerRef.current._tooltipCleanup = cleanup;
|
|
303
|
+
}
|
|
106
304
|
});
|
|
107
|
-
chart.render();
|
|
108
|
-
chartRef.current = chart;
|
|
109
305
|
return ()=>{
|
|
110
|
-
if (
|
|
111
|
-
|
|
112
|
-
|
|
306
|
+
if (containerRef.current) {
|
|
307
|
+
const container = containerRef.current;
|
|
308
|
+
if (container._tooltipCleanup) {
|
|
309
|
+
container._tooltipCleanup();
|
|
310
|
+
delete container._tooltipCleanup;
|
|
311
|
+
}
|
|
312
|
+
const svgOverlay = containerRef.current.querySelector('svg.gauge-arc-overlay');
|
|
313
|
+
if (svgOverlay) svgOverlay.remove();
|
|
314
|
+
const tooltipElement = containerRef.current.querySelector('.gauge-tooltip');
|
|
315
|
+
if (tooltipElement) tooltipElement.remove();
|
|
113
316
|
}
|
|
114
317
|
};
|
|
115
318
|
}, [
|
|
116
|
-
|
|
117
|
-
min,
|
|
118
|
-
max,
|
|
119
|
-
target,
|
|
120
|
-
thresholdValues,
|
|
121
|
-
thresholdColorArray,
|
|
122
|
-
scaleTicks,
|
|
123
|
-
height,
|
|
124
|
-
showPercent,
|
|
125
|
-
formatter,
|
|
126
|
-
tickFormatter
|
|
319
|
+
normalizedProps
|
|
127
320
|
]);
|
|
128
321
|
return /*#__PURE__*/ jsx("div", {
|
|
129
322
|
ref: containerRef,
|
|
130
323
|
style: {
|
|
131
|
-
width:
|
|
132
|
-
height: `${height}px
|
|
324
|
+
width: "100%",
|
|
325
|
+
height: `${height}px`,
|
|
326
|
+
position: 'relative',
|
|
327
|
+
overflow: 'visible'
|
|
133
328
|
}
|
|
134
329
|
});
|
|
135
330
|
};
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { jsx } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
+
import { Chart } from "@antv/g2";
|
|
4
|
+
import { useChartContext } from "../../../core/ChartContext.js";
|
|
5
|
+
import { getAxisFormat, splitTextToMultiline } from "../../../utils/chartHelpers.js";
|
|
6
|
+
import { DataAdapter } from "../../../adapters/DataAdapter.js";
|
|
7
|
+
const G2LineChart = ({ height = 300, data, x = '', y = '', indicatorMap, onChartClick, legend = '', config, nodeSetting = {
|
|
8
|
+
showType: 0,
|
|
9
|
+
type: []
|
|
10
|
+
}, auxiliaryLineData })=>{
|
|
11
|
+
const { formatter, dataTransform, config: contextConfig } = useChartContext();
|
|
12
|
+
const safeIndicatorMap = indicatorMap || contextConfig.indicatorMap || {};
|
|
13
|
+
const safeLegend = legend || y;
|
|
14
|
+
const chartConfig = config || {};
|
|
15
|
+
const { isDataTag = false } = chartConfig;
|
|
16
|
+
const transformedData = useMemo(()=>{
|
|
17
|
+
const result = DataAdapter.transform(data, 'line', {
|
|
18
|
+
type: 'line',
|
|
19
|
+
x,
|
|
20
|
+
y
|
|
21
|
+
});
|
|
22
|
+
return result.map((item)=>dataTransform.processNodeInfo(item, contextConfig.nodeMap));
|
|
23
|
+
}, [
|
|
24
|
+
data,
|
|
25
|
+
x,
|
|
26
|
+
y,
|
|
27
|
+
dataTransform,
|
|
28
|
+
contextConfig.nodeMap
|
|
29
|
+
]);
|
|
30
|
+
const maxY = useMemo(()=>{
|
|
31
|
+
const values = transformedData.filter((item)=>'-' !== item[y] && void 0 !== item[y] && null !== item[y]).map((item)=>Number(item[y]));
|
|
32
|
+
const maxData = Math.max(...values.length > 0 ? values : [
|
|
33
|
+
0
|
|
34
|
+
]);
|
|
35
|
+
const maxAuxiliary = auxiliaryLineData?.length ? Math.max(...auxiliaryLineData.map((item)=>item.value)) : 0;
|
|
36
|
+
return Math.max(maxData, maxAuxiliary);
|
|
37
|
+
}, [
|
|
38
|
+
transformedData,
|
|
39
|
+
y,
|
|
40
|
+
auxiliaryLineData
|
|
41
|
+
]);
|
|
42
|
+
const formatAxis = (val)=>getAxisFormat(val, safeIndicatorMap, safeLegend);
|
|
43
|
+
const containerRef = useRef(null);
|
|
44
|
+
const chartRef = useRef(null);
|
|
45
|
+
useEffect(()=>{
|
|
46
|
+
if (!containerRef.current || !transformedData.length) return;
|
|
47
|
+
if (chartRef.current) chartRef.current.destroy();
|
|
48
|
+
const chart = new Chart({
|
|
49
|
+
container: containerRef.current,
|
|
50
|
+
height,
|
|
51
|
+
autoFit: true
|
|
52
|
+
});
|
|
53
|
+
chart.data(transformedData);
|
|
54
|
+
const line = chart.line().encode('x', x).encode('y', y).encode('shape', 'smooth').style({
|
|
55
|
+
lineWidth: 2
|
|
56
|
+
});
|
|
57
|
+
chart.axis('y', {
|
|
58
|
+
title: false,
|
|
59
|
+
grid: true,
|
|
60
|
+
gridStroke: '#e6e6e6',
|
|
61
|
+
gridLineWidth: 2,
|
|
62
|
+
gridStrokeOpacity: 0.7,
|
|
63
|
+
gridLineDash: [
|
|
64
|
+
0,
|
|
65
|
+
0
|
|
66
|
+
],
|
|
67
|
+
labelFormatter: (val)=>formatAxis(val)
|
|
68
|
+
});
|
|
69
|
+
chart.axis('x', {
|
|
70
|
+
title: false,
|
|
71
|
+
labelAutoHide: 'greedy'
|
|
72
|
+
});
|
|
73
|
+
chart.scale({
|
|
74
|
+
y: {
|
|
75
|
+
type: 'linear',
|
|
76
|
+
domainMax: maxY,
|
|
77
|
+
nice: true
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
line.interaction('tooltip', {
|
|
81
|
+
render: (_event, { title, items })=>`<div>
|
|
82
|
+
<div>${title}</div>
|
|
83
|
+
${items?.map(({ color, name, value })=>`
|
|
84
|
+
<div style="display:flex;justify-content:space-between;line-height:12px;margin-top:5px;">
|
|
85
|
+
<div>
|
|
86
|
+
<span
|
|
87
|
+
style="display:inline-block;width:8px;height:8px;margin-right:5px;background-color:${color};border-radius:50%;"
|
|
88
|
+
></span>
|
|
89
|
+
${safeIndicatorMap[name]?.indicatorName || ''}:
|
|
90
|
+
</div>
|
|
91
|
+
<div style="padding-left:10px;font-weight:bold;">
|
|
92
|
+
${formatter.formatIndicator(value, safeIndicatorMap[name])}
|
|
93
|
+
</div>
|
|
94
|
+
</div>
|
|
95
|
+
`).join('')}
|
|
96
|
+
${auxiliaryLineData?.map(({ name, value })=>`<div
|
|
97
|
+
style="display:flex;justify-content:space-between;line-height:12px;margin-top:10px;margin-left:16px;"
|
|
98
|
+
>
|
|
99
|
+
<div style="margin-right:8px">${name}:</div>
|
|
100
|
+
<div>${formatter.formatIndicator(value, safeIndicatorMap[safeLegend])}</div>
|
|
101
|
+
</div>`).join('')}
|
|
102
|
+
</div>`
|
|
103
|
+
});
|
|
104
|
+
if (auxiliaryLineData && auxiliaryLineData.length > 0) auxiliaryLineData.forEach((auxLine)=>{
|
|
105
|
+
chart.lineY().encode('y', auxLine.value).style('stroke', '#F4664A').style('strokeOpacity', 1).label({
|
|
106
|
+
dy: -3,
|
|
107
|
+
text: splitTextToMultiline(auxLine.name),
|
|
108
|
+
position: 'bottom-left',
|
|
109
|
+
style: {
|
|
110
|
+
fill: '#F4664A'
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
if (isDataTag) line.label({
|
|
115
|
+
text: (d)=>formatter.formatIndicator(d[y], safeIndicatorMap[y])
|
|
116
|
+
});
|
|
117
|
+
chart.render();
|
|
118
|
+
chartRef.current = chart;
|
|
119
|
+
return ()=>{
|
|
120
|
+
if (chartRef.current) {
|
|
121
|
+
chartRef.current.off('element:click');
|
|
122
|
+
chartRef.current.off('element:mouseenter');
|
|
123
|
+
chartRef.current.off('element:mouseleave');
|
|
124
|
+
chartRef.current.destroy();
|
|
125
|
+
chartRef.current = null;
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}, [
|
|
129
|
+
transformedData,
|
|
130
|
+
height,
|
|
131
|
+
x,
|
|
132
|
+
y,
|
|
133
|
+
safeLegend,
|
|
134
|
+
isDataTag,
|
|
135
|
+
maxY,
|
|
136
|
+
safeIndicatorMap,
|
|
137
|
+
formatter,
|
|
138
|
+
onChartClick,
|
|
139
|
+
nodeSetting.showType,
|
|
140
|
+
formatAxis
|
|
141
|
+
]);
|
|
142
|
+
return /*#__PURE__*/ jsx("div", {
|
|
143
|
+
ref: containerRef,
|
|
144
|
+
style: {
|
|
145
|
+
width: '100%',
|
|
146
|
+
height: `${height}px`
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
const base_G2LineChart = G2LineChart;
|
|
151
|
+
export { base_G2LineChart as default };
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* G2GaugeChart Props 验证和规范化工具
|
|
3
|
+
* 用于优雅处理调用方传入的异常参数
|
|
4
|
+
*/
|
|
5
|
+
import { G2GaugeChartProps } from "./G2GaugeChart";
|
|
6
|
+
export interface NormalizedGaugeProps {
|
|
7
|
+
value: number;
|
|
8
|
+
min: number;
|
|
9
|
+
max: number;
|
|
10
|
+
thresholds: number[];
|
|
11
|
+
colors: string[];
|
|
12
|
+
height: number;
|
|
13
|
+
showPercent: boolean;
|
|
14
|
+
formatter?: (value: number, percent: number) => string;
|
|
15
|
+
tickFormatter?: (value: number) => string;
|
|
16
|
+
pointerColor: string;
|
|
17
|
+
tooltip?: G2GaugeChartProps["tooltip"];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* 验证和规范化 G2GaugeChart 的 props
|
|
21
|
+
*
|
|
22
|
+
* 处理以下异常情况:
|
|
23
|
+
* 1. min >= max:自动调整或使用默认值
|
|
24
|
+
* 2. value 超出范围:限制在 [min, max] 内
|
|
25
|
+
* 3. thresholds 不合法:自动生成或过滤无效值
|
|
26
|
+
* 4. colors 不合法:使用默认颜色
|
|
27
|
+
* 5. height 异常:使用默认值
|
|
28
|
+
* 6. formatter 函数异常:使用降级方案
|
|
29
|
+
*
|
|
30
|
+
* @param props 原始 props
|
|
31
|
+
* @returns 规范化后的 props
|
|
32
|
+
*/
|
|
33
|
+
export declare function validateAndNormalizeProps(props: G2GaugeChartProps): NormalizedGaugeProps;
|
|
34
|
+
/**
|
|
35
|
+
* 安全调用 formatter 函数
|
|
36
|
+
* 如果 formatter 抛出错误,返回降级值
|
|
37
|
+
*/
|
|
38
|
+
export declare function safeFormat(formatter: ((value: number, percent: number) => string) | undefined, value: number, percent: number, fallback: string): string;
|
|
39
|
+
/**
|
|
40
|
+
* 安全调用 tickFormatter 函数
|
|
41
|
+
*/
|
|
42
|
+
export declare function safeTickFormat(tickFormatter: ((value: number) => string) | undefined, value: number, fallback: string): string;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
function isValidColor(color) {
|
|
2
|
+
if (!color || "string" != typeof color) return false;
|
|
3
|
+
if (/^#([A-Fa-f0-9]{6}|[A-Fa-f0-9]{3})$/.test(color)) return true;
|
|
4
|
+
if (/^rgba?\(/.test(color)) return true;
|
|
5
|
+
const s = new Option().style;
|
|
6
|
+
s.color = color;
|
|
7
|
+
return "" !== s.color;
|
|
8
|
+
}
|
|
9
|
+
function validateAndNormalizeProps(props) {
|
|
10
|
+
const isDev = "development" === process.env.NODE_ENV;
|
|
11
|
+
let { min = 0, max } = props;
|
|
12
|
+
if ("number" != typeof max || !isFinite(max)) {
|
|
13
|
+
if (isDev) console.warn("[G2GaugeChart] max 必须是有效的数字,使用默认值 100", {
|
|
14
|
+
max
|
|
15
|
+
});
|
|
16
|
+
max = 100;
|
|
17
|
+
}
|
|
18
|
+
if ("number" != typeof min || !isFinite(min)) {
|
|
19
|
+
if (isDev) console.warn("[G2GaugeChart] min 必须是有效的数字,使用默认值 0", {
|
|
20
|
+
min
|
|
21
|
+
});
|
|
22
|
+
min = 0;
|
|
23
|
+
}
|
|
24
|
+
if (min >= max) {
|
|
25
|
+
if (isDev) console.warn(`[G2GaugeChart] min (${min}) 必须小于 max (${max}),自动调整为 max - 1`, {
|
|
26
|
+
min,
|
|
27
|
+
max
|
|
28
|
+
});
|
|
29
|
+
min = max - 1;
|
|
30
|
+
if (min < 0) {
|
|
31
|
+
min = 0;
|
|
32
|
+
max = 100;
|
|
33
|
+
if (isDev) console.warn("[G2GaugeChart] min 和 max 都无效,使用默认值 min=0, max=100");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
let { value } = props;
|
|
37
|
+
if ("number" != typeof value || !isFinite(value)) {
|
|
38
|
+
if (isDev) console.warn(`[G2GaugeChart] value 必须是有效的数字,使用默认值 (min + max) / 2`, {
|
|
39
|
+
value
|
|
40
|
+
});
|
|
41
|
+
value = (min + max) / 2;
|
|
42
|
+
}
|
|
43
|
+
const originalValue = value;
|
|
44
|
+
value = Math.min(Math.max(value, min), max);
|
|
45
|
+
if (originalValue !== value && isDev) console.warn(`[G2GaugeChart] value (${originalValue}) 超出范围 [${min}, ${max}],已限制为 ${value}`, {
|
|
46
|
+
originalValue,
|
|
47
|
+
min,
|
|
48
|
+
max,
|
|
49
|
+
clampedValue: value
|
|
50
|
+
});
|
|
51
|
+
let thresholds = [];
|
|
52
|
+
const defaultColors = [
|
|
53
|
+
'#9596d9',
|
|
54
|
+
'#FF9F4C',
|
|
55
|
+
'#56c2f3'
|
|
56
|
+
];
|
|
57
|
+
if (props.thresholds) {
|
|
58
|
+
if (Array.isArray(props.thresholds) && props.thresholds.length > 0) {
|
|
59
|
+
if ("number" == typeof props.thresholds[0]) thresholds = [
|
|
60
|
+
...props.thresholds
|
|
61
|
+
];
|
|
62
|
+
else if ("object" == typeof props.thresholds[0]) thresholds = props.thresholds.map((t)=>t.value);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
thresholds = thresholds.filter((t)=>"number" == typeof t && isFinite(t)).filter((t)=>t > min && t < max).sort((a, b)=>a - b);
|
|
66
|
+
thresholds = Array.from(new Set(thresholds));
|
|
67
|
+
if (thresholds[thresholds.length - 1] !== max) thresholds.push(max);
|
|
68
|
+
let colors = [];
|
|
69
|
+
if (props.thresholds && Array.isArray(props.thresholds) && props.thresholds.length > 0 && "object" == typeof props.thresholds[0]) colors = props.thresholds.map((t)=>t.color).filter((c)=>!!c && isValidColor(c));
|
|
70
|
+
if (0 === colors.length && props.thresholdColors) colors = props.thresholdColors.filter((c)=>isValidColor(c));
|
|
71
|
+
if (0 === colors.length) {
|
|
72
|
+
colors = [
|
|
73
|
+
...defaultColors
|
|
74
|
+
];
|
|
75
|
+
if (isDev) console.warn("[G2GaugeChart] 未提供有效的颜色,使用默认颜色", {
|
|
76
|
+
colors
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
while(colors.length < thresholds.length){
|
|
80
|
+
const lastColor = colors[colors.length - 1];
|
|
81
|
+
colors.push(lastColor || defaultColors[0]);
|
|
82
|
+
}
|
|
83
|
+
let height = props.height ?? 300;
|
|
84
|
+
if ("number" != typeof height || !isFinite(height) || height <= 0) {
|
|
85
|
+
if (isDev) console.warn(`[G2GaugeChart] height 必须是大于 0 的数字,使用默认值 300`, {
|
|
86
|
+
height: props.height
|
|
87
|
+
});
|
|
88
|
+
height = 300;
|
|
89
|
+
}
|
|
90
|
+
height = Math.min(Math.max(height, 100), 2000);
|
|
91
|
+
let pointerColor = props.pointerColor ?? "#c5c5c5";
|
|
92
|
+
if (!isValidColor(pointerColor)) {
|
|
93
|
+
if (isDev) console.warn(`[G2GaugeChart] pointerColor 无效,使用默认值 #c5c5c5`, {
|
|
94
|
+
pointerColor: props.pointerColor
|
|
95
|
+
});
|
|
96
|
+
pointerColor = "#c5c5c5";
|
|
97
|
+
}
|
|
98
|
+
const formatter = props.formatter;
|
|
99
|
+
const tickFormatter = props.tickFormatter;
|
|
100
|
+
return {
|
|
101
|
+
value,
|
|
102
|
+
min,
|
|
103
|
+
max,
|
|
104
|
+
thresholds,
|
|
105
|
+
colors,
|
|
106
|
+
height,
|
|
107
|
+
showPercent: props.showPercent ?? true,
|
|
108
|
+
formatter,
|
|
109
|
+
tickFormatter,
|
|
110
|
+
pointerColor,
|
|
111
|
+
tooltip: props.tooltip
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
function safeFormat(formatter, value, percent, fallback) {
|
|
115
|
+
if (!formatter) return fallback;
|
|
116
|
+
try {
|
|
117
|
+
return formatter(value, percent);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if ("development" === process.env.NODE_ENV) console.warn("[G2GaugeChart] formatter 函数执行出错,使用降级值", {
|
|
120
|
+
error,
|
|
121
|
+
value,
|
|
122
|
+
percent
|
|
123
|
+
});
|
|
124
|
+
return fallback;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
function safeTickFormat(tickFormatter, value, fallback) {
|
|
128
|
+
if (!tickFormatter) return fallback;
|
|
129
|
+
try {
|
|
130
|
+
return tickFormatter(value);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
if ("development" === process.env.NODE_ENV) console.warn("[G2GaugeChart] tickFormatter 函数执行出错,使用降级值", {
|
|
133
|
+
error,
|
|
134
|
+
value
|
|
135
|
+
});
|
|
136
|
+
return fallback;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
export { safeFormat, safeTickFormat, validateAndNormalizeProps };
|
package/dist/index.d.ts
CHANGED
|
@@ -21,6 +21,7 @@ export { default as GroupLineCompare } from './components/composite/GroupLineCom
|
|
|
21
21
|
export { default as MultipleLine } from './components/composite/MultipleLine';
|
|
22
22
|
export { default as MultipleCompareLine } from './components/composite/MultipleCompareLine';
|
|
23
23
|
export { default as G2BarChart } from './components/g2/base/G2BarChart';
|
|
24
|
+
export { default as G2LineChart } from './components/g2/base/G2LineChart';
|
|
24
25
|
export { default as G2GaugeChart } from './components/g2/base/G2GaugeChart';
|
|
25
26
|
export type { G2GaugeChartProps } from './components/g2/base/G2GaugeChart';
|
|
26
27
|
export { createLazyComponent, createLazyComponents } from './utils/lazyHelpers';
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import GroupLineCompare from "./components/composite/GroupLineCompare.js";
|
|
|
15
15
|
import MultipleLine from "./components/composite/MultipleLine.js";
|
|
16
16
|
import MultipleCompareLine from "./components/composite/MultipleCompareLine.js";
|
|
17
17
|
import G2BarChart from "./components/g2/base/G2BarChart.js";
|
|
18
|
+
import G2LineChart from "./components/g2/base/G2LineChart.js";
|
|
18
19
|
import G2GaugeChart from "./components/g2/base/G2GaugeChart.js";
|
|
19
20
|
import { createLazyComponent, createLazyComponents } from "./utils/lazyHelpers.js";
|
|
20
21
|
import { TypeAdapter } from "./adapters/TypeAdapter.js";
|
|
@@ -25,4 +26,4 @@ import { BigNumberUtil, formatBigNumber, formatIndicatorV2, formatNumberV2, form
|
|
|
25
26
|
import { calTextWidth, calculateBarWidth, createStripePatternCanvas, getAxisFormat, getAxisHorPaddingByText, markerStyle, truncateText } from "./utils/chartHelpers.js";
|
|
26
27
|
import { getIntervalRatio, groupArray, transformIndicatorList } from "./utils/dataTransform.js";
|
|
27
28
|
import { getIndicator, getIndicatorCompareName, getIndicatorName } from "./utils/indicatorHelpers.js";
|
|
28
|
-
export { AuxiliaryLine, BarChart, BarLineAdapter, BarLineChart, BarLineCompareWeekend, BigNumberUtil, ChartProvider, DataAdapter, DefaultDataTransformService, DefaultFormatterService, G2BarChart, G2GaugeChart, GroupBar, GroupBarLine, GroupCompare, GroupLine, GroupLineCompare, LineChart, MultipleCompareLine, MultipleLine, TypeAdapter, XAxisBackground, calTextWidth, calculateBarWidth, createDataTransformService, createFormatterService, createLazyComponent, createLazyComponents, createStripePatternCanvas, defaultChartConfig, formatBigNumber, formatIndicatorV2, formatNumberV2, formatPercentage, getAxisFormat, getAxisFormatFromContext, getAxisHorPaddingByText, getIndicator, getIndicatorCompareName, getIndicatorName, getIntervalRatio, getThemeColors, groupArray, markerStyle, mergeChartConfig, transformIndicatorList, truncateText, useChartContext };
|
|
29
|
+
export { AuxiliaryLine, BarChart, BarLineAdapter, BarLineChart, BarLineCompareWeekend, BigNumberUtil, ChartProvider, DataAdapter, DefaultDataTransformService, DefaultFormatterService, G2BarChart, G2GaugeChart, G2LineChart, GroupBar, GroupBarLine, GroupCompare, GroupLine, GroupLineCompare, LineChart, MultipleCompareLine, MultipleLine, TypeAdapter, XAxisBackground, calTextWidth, calculateBarWidth, createDataTransformService, createFormatterService, createLazyComponent, createLazyComponents, createStripePatternCanvas, defaultChartConfig, formatBigNumber, formatIndicatorV2, formatNumberV2, formatPercentage, getAxisFormat, getAxisFormatFromContext, getAxisHorPaddingByText, getIndicator, getIndicatorCompareName, getIndicatorName, getIntervalRatio, getThemeColors, groupArray, markerStyle, mergeChartConfig, transformIndicatorList, truncateText, useChartContext };
|
|
@@ -50,3 +50,4 @@ export declare const markerStyle: (baseColor: string, isCompare: boolean) => {
|
|
|
50
50
|
*/
|
|
51
51
|
export declare const createStripePatternCanvas: (baseColor: string, spaceUnit?: number) => HTMLCanvasElement;
|
|
52
52
|
export declare const getChartKey: (config: any) => string;
|
|
53
|
+
export declare const splitTextToMultiline: (text: string, maxLength?: number) => string;
|
|
@@ -109,4 +109,9 @@ const createStripePatternCanvas = (baseColor, spaceUnit = 24)=>{
|
|
|
109
109
|
return patternCanvas;
|
|
110
110
|
};
|
|
111
111
|
const getChartKey = (config)=>JSON.stringify(config);
|
|
112
|
-
|
|
112
|
+
const splitTextToMultiline = (text, maxLength = 5)=>{
|
|
113
|
+
let result = '';
|
|
114
|
+
for(let i = 0; i < text.length; i += maxLength)result += text.substring(i, i + maxLength) + '\n';
|
|
115
|
+
return result.trim();
|
|
116
|
+
};
|
|
117
|
+
export { calTextWidth, calculateBarWidth, createStripePatternCanvas, getAxisFormat, getAxisHorPaddingByText, getChartKey, markerStyle, splitTextToMultiline, truncateText };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@publishfx/publish-chart",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.2",
|
|
4
4
|
"description": "A React chart component library for the Publish platform, including BarChart, LineChart, BarLineChart and other visualization components",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* G2 Gauge 图表使用示例
|
|
3
|
-
* 展示如何自定义 scale 和阈值区间
|
|
4
|
-
*/
|
|
5
|
-
import React from 'react';
|
|
6
|
-
export declare const Example1_TargetBased: React.FC;
|
|
7
|
-
export declare const Example2_CustomThresholds: React.FC;
|
|
8
|
-
export declare const Example3_CustomThresholdsWithColors: React.FC;
|
|
9
|
-
export declare const Example4_TargetWithCustomScale: React.FC;
|
|
10
|
-
export declare const Example5_Simple: React.FC;
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
import { jsx } from "react/jsx-runtime";
|
|
2
|
-
import "react";
|
|
3
|
-
import G2GaugeChart from "./G2GaugeChart.js";
|
|
4
|
-
const Example1_TargetBased = ()=>/*#__PURE__*/ jsx(G2GaugeChart, {
|
|
5
|
-
value: 147530,
|
|
6
|
-
min: 0,
|
|
7
|
-
max: 200000,
|
|
8
|
-
target: 160000,
|
|
9
|
-
showPercent: true
|
|
10
|
-
});
|
|
11
|
-
const Example2_CustomThresholds = ()=>/*#__PURE__*/ jsx(G2GaugeChart, {
|
|
12
|
-
value: 147530,
|
|
13
|
-
min: 0,
|
|
14
|
-
max: 200000,
|
|
15
|
-
thresholds: [
|
|
16
|
-
60000,
|
|
17
|
-
120000,
|
|
18
|
-
180000
|
|
19
|
-
],
|
|
20
|
-
thresholdColors: [
|
|
21
|
-
'#FF4D4F',
|
|
22
|
-
'#FFA940',
|
|
23
|
-
'#FFC53D',
|
|
24
|
-
'#52C41A'
|
|
25
|
-
],
|
|
26
|
-
ticks: [
|
|
27
|
-
0,
|
|
28
|
-
50000,
|
|
29
|
-
100000,
|
|
30
|
-
150000,
|
|
31
|
-
200000
|
|
32
|
-
],
|
|
33
|
-
showPercent: true
|
|
34
|
-
});
|
|
35
|
-
const Example3_CustomThresholdsWithColors = ()=>/*#__PURE__*/ jsx(G2GaugeChart, {
|
|
36
|
-
value: 147530,
|
|
37
|
-
min: 0,
|
|
38
|
-
max: 200000,
|
|
39
|
-
thresholds: [
|
|
40
|
-
{
|
|
41
|
-
value: 50000,
|
|
42
|
-
color: '#FF4D4F'
|
|
43
|
-
},
|
|
44
|
-
{
|
|
45
|
-
value: 100000,
|
|
46
|
-
color: '#FFA940'
|
|
47
|
-
},
|
|
48
|
-
{
|
|
49
|
-
value: 150000,
|
|
50
|
-
color: '#FFC53D'
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
value: 200000,
|
|
54
|
-
color: '#52C41A'
|
|
55
|
-
}
|
|
56
|
-
],
|
|
57
|
-
ticks: [
|
|
58
|
-
0,
|
|
59
|
-
50000,
|
|
60
|
-
100000,
|
|
61
|
-
150000,
|
|
62
|
-
200000
|
|
63
|
-
],
|
|
64
|
-
showPercent: true
|
|
65
|
-
});
|
|
66
|
-
const Example4_TargetWithCustomScale = ()=>{
|
|
67
|
-
const max = 200000;
|
|
68
|
-
const target = 160000;
|
|
69
|
-
return /*#__PURE__*/ jsx(G2GaugeChart, {
|
|
70
|
-
value: 147530,
|
|
71
|
-
min: 0,
|
|
72
|
-
max: max,
|
|
73
|
-
target: target,
|
|
74
|
-
thresholdColors: [
|
|
75
|
-
'#FF4D4F',
|
|
76
|
-
'#FFA940',
|
|
77
|
-
'#FFC53D',
|
|
78
|
-
'#52C41A'
|
|
79
|
-
],
|
|
80
|
-
formatter: (value, percent)=>`${value}\n完成度: ${percent.toFixed(1)}%`
|
|
81
|
-
});
|
|
82
|
-
};
|
|
83
|
-
const Example5_Simple = ()=>/*#__PURE__*/ jsx(G2GaugeChart, {
|
|
84
|
-
value: 147530,
|
|
85
|
-
max: 200000,
|
|
86
|
-
showPercent: true
|
|
87
|
-
});
|
|
88
|
-
export { Example1_TargetBased, Example2_CustomThresholds, Example3_CustomThresholdsWithColors, Example4_TargetWithCustomScale, Example5_Simple };
|