@publishfx/publish-chart 2.0.1 → 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 +3 -0
- package/dist/components/g2/base/G2GaugeChart.d.ts +0 -2
- package/dist/components/g2/base/G2GaugeChart.js +98 -134
- package/dist/components/g2/base/gaugePropsValidator.d.ts +42 -0
- package/dist/components/g2/base/gaugePropsValidator.js +139 -0
- 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,6 @@
|
|
|
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
|
+
|
|
1
4
|
**2.0.1** feat(chart): 使用svg重写gauge图表组件
|
|
2
5
|
- 2026-02-04T11:18:21+08:00 [feat(chart): 使用svg重写gauge图表组件](http://lf.git.oa.mt/publish_platform/web/publish/commit/47a981e483d2a4df79d473e1053d9f9ea927ee10)
|
|
3
6
|
- 2026-02-03T19:25:53+08:00 [feat(chart): 修改ts错误](http://lf.git.oa.mt/publish_platform/web/publish/commit/40e4e8f44c7ff02b079974eca52ece225180329c)
|
|
@@ -7,12 +7,10 @@ export interface G2GaugeChartProps {
|
|
|
7
7
|
value: number;
|
|
8
8
|
min?: number;
|
|
9
9
|
max: number;
|
|
10
|
-
target?: number;
|
|
11
10
|
thresholds?: number[] | Array<{
|
|
12
11
|
value: number;
|
|
13
12
|
color: string;
|
|
14
13
|
}>;
|
|
15
|
-
ticks?: number[];
|
|
16
14
|
height?: number;
|
|
17
15
|
showPercent?: boolean;
|
|
18
16
|
formatter?: (value: number, percent: number) => string;
|
|
@@ -1,69 +1,22 @@
|
|
|
1
1
|
import { jsx } from "react/jsx-runtime";
|
|
2
2
|
import { useEffect, useMemo, useRef } from "react";
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
"#FF9F4C",
|
|
6
|
-
"#56c2f3"
|
|
7
|
-
], tooltip })=>{
|
|
3
|
+
import { safeFormat, safeTickFormat, validateAndNormalizeProps } from "./gaugePropsValidator.js";
|
|
4
|
+
const G2GaugeChart = (props)=>{
|
|
8
5
|
const containerRef = useRef(null);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Math.round(min + step),
|
|
22
|
-
Math.round(min + 2 * step),
|
|
23
|
-
Math.round(min + 3 * step),
|
|
24
|
-
max
|
|
25
|
-
];
|
|
26
|
-
}, [
|
|
27
|
-
min,
|
|
28
|
-
max,
|
|
29
|
-
target,
|
|
30
|
-
ticks
|
|
31
|
-
]);
|
|
32
|
-
const thresholdValues = useMemo(()=>{
|
|
33
|
-
let baseThresholds = [];
|
|
34
|
-
if (thresholds) {
|
|
35
|
-
if (Array.isArray(thresholds) && thresholds.length > 0 && "number" == typeof thresholds[0]) baseThresholds = [
|
|
36
|
-
...thresholds
|
|
37
|
-
];
|
|
38
|
-
else if (Array.isArray(thresholds) && thresholds.length > 0 && "object" == typeof thresholds[0]) baseThresholds = thresholds.map((t)=>t.value);
|
|
39
|
-
} else if (target) baseThresholds = [
|
|
40
|
-
Math.round(0.6 * target),
|
|
41
|
-
Math.round(0.9 * target),
|
|
42
|
-
target
|
|
43
|
-
];
|
|
44
|
-
else {
|
|
45
|
-
const step = (max - min) / 3;
|
|
46
|
-
baseThresholds = [
|
|
47
|
-
Math.round(min + step),
|
|
48
|
-
Math.round(min + 2 * step),
|
|
49
|
-
max
|
|
50
|
-
];
|
|
51
|
-
}
|
|
52
|
-
if (baseThresholds.length > 0 && baseThresholds[baseThresholds.length - 1] !== max) baseThresholds.push(max);
|
|
53
|
-
return baseThresholds;
|
|
54
|
-
}, [
|
|
55
|
-
min,
|
|
56
|
-
max,
|
|
57
|
-
target,
|
|
58
|
-
thresholds
|
|
59
|
-
]);
|
|
60
|
-
const thresholdColorArray = useMemo(()=>{
|
|
61
|
-
if (thresholds && Array.isArray(thresholds) && thresholds.length > 0 && "object" == typeof thresholds[0]) return thresholds.map((t)=>t.color);
|
|
62
|
-
return thresholdColors;
|
|
63
|
-
}, [
|
|
64
|
-
thresholds,
|
|
65
|
-
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
|
|
66
18
|
]);
|
|
19
|
+
const { value, min, max, thresholds: thresholdValues, colors: thresholdColorArray, height, showPercent, formatter, tickFormatter, pointerColor, tooltip } = normalizedProps;
|
|
67
20
|
useEffect(()=>{
|
|
68
21
|
if (!containerRef.current) return;
|
|
69
22
|
const startAngle = -1.2 * Math.PI;
|
|
@@ -83,41 +36,47 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
83
36
|
const innerRadius = 0.85 * radius;
|
|
84
37
|
const outerRadius = 0.95 * radius;
|
|
85
38
|
const totalAngle = endAngle - startAngle;
|
|
86
|
-
const
|
|
39
|
+
const topPadding = 40;
|
|
40
|
+
const bottomPadding = -30;
|
|
41
|
+
const leftPadding = 0;
|
|
42
|
+
const rightPadding = 0;
|
|
87
43
|
let svgOverlay = containerRef.current.querySelector('svg.gauge-arc-overlay');
|
|
88
44
|
if (svgOverlay) {
|
|
89
45
|
svgOverlay.innerHTML = '';
|
|
90
|
-
svgOverlay.setAttribute('width', String(width
|
|
91
|
-
svgOverlay.setAttribute('height', String(height
|
|
92
|
-
svgOverlay.setAttribute('viewBox',
|
|
93
|
-
svgOverlay.style.top =
|
|
94
|
-
svgOverlay.style.left =
|
|
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';
|
|
95
51
|
} else {
|
|
96
52
|
svgOverlay = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
97
53
|
svgOverlay.setAttribute('class', 'gauge-arc-overlay');
|
|
98
|
-
svgOverlay.setAttribute('width', String(width
|
|
99
|
-
svgOverlay.setAttribute('height', String(height
|
|
100
|
-
svgOverlay.setAttribute('viewBox',
|
|
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';
|
|
101
60
|
svgOverlay.style.pointerEvents = 'none';
|
|
102
61
|
svgOverlay.style.overflow = 'visible';
|
|
103
62
|
containerRef.current.appendChild(svgOverlay);
|
|
104
63
|
}
|
|
105
64
|
for(let i = 0; i < thresholdValues.length; i++){
|
|
106
|
-
const start = 0 === i ? min : thresholdValues[i - 1];
|
|
107
|
-
const end = thresholdValues[i];
|
|
108
|
-
const color =
|
|
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';
|
|
109
68
|
const startAngleValue = startAngle + (start - min) / (max - min) * totalAngle;
|
|
110
69
|
const endAngleValue = startAngle + (end - min) / (max - min) * totalAngle;
|
|
111
70
|
const largeArcFlag = Math.abs(endAngleValue - startAngleValue) > Math.PI ? 1 : 0;
|
|
112
71
|
const sweepFlag = 1;
|
|
113
|
-
const startX = centerX +
|
|
114
|
-
const startY = centerY +
|
|
115
|
-
const endX = centerX +
|
|
116
|
-
const endY = centerY +
|
|
117
|
-
const innerStartX = centerX +
|
|
118
|
-
const innerStartY = centerY +
|
|
119
|
-
const innerEndX = centerX +
|
|
120
|
-
const innerEndY = centerY +
|
|
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);
|
|
121
80
|
const pathString = [
|
|
122
81
|
`M ${startX} ${startY}`,
|
|
123
82
|
`A ${outerRadius} ${outerRadius} 0 ${largeArcFlag} ${sweepFlag} ${endX} ${endY}`,
|
|
@@ -135,12 +94,12 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
135
94
|
const tickLength = 0.05 * radius;
|
|
136
95
|
const textOffset = 0.15 * radius;
|
|
137
96
|
const startAngleValue = startAngle;
|
|
138
|
-
const startX = centerX +
|
|
139
|
-
const startY = centerY +
|
|
140
|
-
const startTickEndX = centerX +
|
|
141
|
-
const startTickEndY = centerY +
|
|
142
|
-
const startTextX = centerX +
|
|
143
|
-
const startTextY = centerY +
|
|
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);
|
|
144
103
|
const startTickLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
145
104
|
startTickLine.setAttribute('x1', String(startX));
|
|
146
105
|
startTickLine.setAttribute('y1', String(startY));
|
|
@@ -156,9 +115,9 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
156
115
|
startText.setAttribute('font-size', '14');
|
|
157
116
|
startText.setAttribute('text-anchor', 'middle');
|
|
158
117
|
startText.setAttribute('dominant-baseline', 'middle');
|
|
159
|
-
startText.textContent = tickFormatter
|
|
118
|
+
startText.textContent = safeTickFormat(tickFormatter, min, String(min));
|
|
160
119
|
svgOverlay.appendChild(startText);
|
|
161
|
-
const thresholdTextOffset = 0.
|
|
120
|
+
const thresholdTextOffset = 0.05 * radius;
|
|
162
121
|
const minThresholdDistance = (max - min) * 0.1;
|
|
163
122
|
const textOffsets = [];
|
|
164
123
|
for(let i = 0; i < thresholdValues.length - 1; i++){
|
|
@@ -183,27 +142,27 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
183
142
|
const thresholdValue = thresholdValues[i];
|
|
184
143
|
const currentTextOffset = textOffsets[i];
|
|
185
144
|
const thresholdAngle = startAngle + (thresholdValue - min) / (max - min) * totalAngle;
|
|
186
|
-
const thresholdTextX = centerX +
|
|
187
|
-
const thresholdTextY = centerY +
|
|
145
|
+
const thresholdTextX = centerX + (outerRadius + currentTextOffset) * Math.cos(thresholdAngle);
|
|
146
|
+
const thresholdTextY = centerY + (outerRadius + currentTextOffset) * Math.sin(thresholdAngle);
|
|
188
147
|
const rotationAngle = (180 * thresholdAngle / Math.PI + 90) % 360;
|
|
189
148
|
const thresholdText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
190
149
|
thresholdText.setAttribute('x', String(thresholdTextX));
|
|
191
150
|
thresholdText.setAttribute('y', String(thresholdTextY));
|
|
192
151
|
thresholdText.setAttribute('fill', '#1d2129');
|
|
193
|
-
thresholdText.setAttribute('font-size', '
|
|
152
|
+
thresholdText.setAttribute('font-size', '12');
|
|
194
153
|
thresholdText.setAttribute('text-anchor', 'middle');
|
|
195
154
|
thresholdText.setAttribute('dominant-baseline', 'middle');
|
|
196
155
|
thresholdText.setAttribute('transform', `rotate(${rotationAngle} ${thresholdTextX} ${thresholdTextY})`);
|
|
197
|
-
thresholdText.textContent = tickFormatter
|
|
156
|
+
thresholdText.textContent = safeTickFormat(tickFormatter, thresholdValue, String(thresholdValue));
|
|
198
157
|
svgOverlay.appendChild(thresholdText);
|
|
199
158
|
}
|
|
200
159
|
const endAngleValue = endAngle;
|
|
201
|
-
const endX = centerX +
|
|
202
|
-
const endY = centerY +
|
|
203
|
-
const endTickEndX = centerX +
|
|
204
|
-
const endTickEndY = centerY +
|
|
205
|
-
const endTextX = centerX +
|
|
206
|
-
const endTextY = centerY +
|
|
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);
|
|
207
166
|
const endTickLine = document.createElementNS('http://www.w3.org/2000/svg', 'line');
|
|
208
167
|
endTickLine.setAttribute('x1', String(endX));
|
|
209
168
|
endTickLine.setAttribute('y1', String(endY));
|
|
@@ -219,19 +178,19 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
219
178
|
endText.setAttribute('font-size', '14');
|
|
220
179
|
endText.setAttribute('text-anchor', 'middle');
|
|
221
180
|
endText.setAttribute('dominant-baseline', 'middle');
|
|
222
|
-
endText.textContent = tickFormatter
|
|
181
|
+
endText.textContent = safeTickFormat(tickFormatter, max, String(max));
|
|
223
182
|
svgOverlay.appendChild(endText);
|
|
224
183
|
const pointerValue = Math.min(Math.max(value, min), max);
|
|
225
184
|
const pointerAngle = startAngle + (pointerValue - min) / (max - min) * totalAngle;
|
|
226
185
|
const pointerStartCircleRadius = 4;
|
|
227
186
|
const pointerLength = 0.6 * outerRadius;
|
|
228
|
-
const pointerStartX = centerX +
|
|
229
|
-
const pointerStartY = centerY +
|
|
230
|
-
const pointerEndX = centerX +
|
|
231
|
-
const pointerEndY = centerY +
|
|
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);
|
|
232
191
|
const pointerStartCircle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
|
|
233
|
-
pointerStartCircle.setAttribute('cx', String(centerX
|
|
234
|
-
pointerStartCircle.setAttribute('cy', String(centerY
|
|
192
|
+
pointerStartCircle.setAttribute('cx', String(centerX));
|
|
193
|
+
pointerStartCircle.setAttribute('cy', String(centerY));
|
|
235
194
|
pointerStartCircle.setAttribute('r', String(pointerStartCircleRadius));
|
|
236
195
|
pointerStartCircle.setAttribute('fill', '#fff');
|
|
237
196
|
pointerStartCircle.setAttribute('stroke', pointerColor);
|
|
@@ -246,17 +205,22 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
246
205
|
pointerLine.setAttribute('stroke-width', '3');
|
|
247
206
|
pointerLine.setAttribute('stroke-linecap', 'round');
|
|
248
207
|
svgOverlay.appendChild(pointerLine);
|
|
249
|
-
const
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
const
|
|
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);
|
|
255
219
|
const valueText = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
|
256
220
|
valueText.setAttribute('x', String(textCenterX));
|
|
257
221
|
valueText.setAttribute('y', String(valueTextY));
|
|
258
222
|
valueText.setAttribute('fill', colors[0] || '#9596d9');
|
|
259
|
-
valueText.setAttribute('font-size',
|
|
223
|
+
valueText.setAttribute('font-size', String(valueFontSize));
|
|
260
224
|
valueText.setAttribute('font-weight', 'bold');
|
|
261
225
|
valueText.setAttribute('text-anchor', 'middle');
|
|
262
226
|
valueText.setAttribute('dominant-baseline', 'middle');
|
|
@@ -267,10 +231,10 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
267
231
|
percentText.setAttribute('x', String(textCenterX));
|
|
268
232
|
percentText.setAttribute('y', String(percentTextY));
|
|
269
233
|
percentText.setAttribute('fill', '#8c8c8c');
|
|
270
|
-
percentText.setAttribute('font-size',
|
|
234
|
+
percentText.setAttribute('font-size', String(percentFontSize));
|
|
271
235
|
percentText.setAttribute('text-anchor', 'middle');
|
|
272
236
|
percentText.setAttribute('dominant-baseline', 'middle');
|
|
273
|
-
percentText.textContent = `占比: ${
|
|
237
|
+
percentText.textContent = `占比: ${percentDisplay}%`;
|
|
274
238
|
svgOverlay.appendChild(percentText);
|
|
275
239
|
}
|
|
276
240
|
if (tooltip) {
|
|
@@ -291,11 +255,21 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
291
255
|
tooltipElement.style.whiteSpace = 'nowrap';
|
|
292
256
|
containerRef.current.appendChild(tooltipElement);
|
|
293
257
|
}
|
|
294
|
-
const tooltipLabel = tooltip.label
|
|
295
|
-
|
|
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();
|
|
268
|
+
}
|
|
269
|
+
else tooltipValue = value.toLocaleString();
|
|
296
270
|
tooltipElement.innerHTML = `
|
|
297
|
-
|
|
298
|
-
|
|
271
|
+
${tooltipLabel ? `<div style="color: #666; margin-bottom: 4px;">${tooltipLabel}</div>` : ''}
|
|
272
|
+
${tooltipValue ? `<div style="color: #1d2129; font-weight: 500;">${tooltipValue}</div>` : ''}
|
|
299
273
|
`;
|
|
300
274
|
const handleMouseMove = (e)=>{
|
|
301
275
|
if (!tooltipElement || !containerRef.current) return;
|
|
@@ -304,7 +278,6 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
304
278
|
const mouseY = e.clientY;
|
|
305
279
|
if (mouseX >= svgOverlayRect.left && mouseX <= svgOverlayRect.right && mouseY >= svgOverlayRect.top && mouseY <= svgOverlayRect.bottom) {
|
|
306
280
|
const containerRect = containerRef.current.getBoundingClientRect();
|
|
307
|
-
console.log('containerRect', containerRect, mouseX, mouseY);
|
|
308
281
|
const relativeX = mouseX - containerRect.left;
|
|
309
282
|
const relativeY = mouseY - containerRect.top;
|
|
310
283
|
const offsetX = 10;
|
|
@@ -343,24 +316,15 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
|
|
|
343
316
|
}
|
|
344
317
|
};
|
|
345
318
|
}, [
|
|
346
|
-
|
|
347
|
-
min,
|
|
348
|
-
max,
|
|
349
|
-
thresholdValues,
|
|
350
|
-
thresholdColorArray,
|
|
351
|
-
scaleTicks,
|
|
352
|
-
height,
|
|
353
|
-
formatter,
|
|
354
|
-
tickFormatter,
|
|
355
|
-
showPercent,
|
|
356
|
-
pointerColor,
|
|
357
|
-
tooltip
|
|
319
|
+
normalizedProps
|
|
358
320
|
]);
|
|
359
321
|
return /*#__PURE__*/ jsx("div", {
|
|
360
322
|
ref: containerRef,
|
|
361
323
|
style: {
|
|
362
324
|
width: "100%",
|
|
363
|
-
height: `${height}px
|
|
325
|
+
height: `${height}px`,
|
|
326
|
+
position: 'relative',
|
|
327
|
+
overflow: 'visible'
|
|
364
328
|
}
|
|
365
329
|
});
|
|
366
330
|
};
|
|
@@ -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/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 };
|