@publishfx/publish-chart 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ **2.0.3** feat(chart): G2GaugeChart设置value文本的颜色和阈值保持一致
2
+ - 2026-02-05T15:06:47+08:00 [feat(chart): G2GaugeChart设置value文本的颜色和阈值保持一致](http://lf.git.oa.mt/publish_platform/web/publish/commit/6b80990920484818132164048278789e6f6aa740)
3
+
4
+ **2.0.2** feat(chart): 优化G2GaugeChart组件,仅基于数据驱动UI,不涉及业务逻辑
5
+ - 2026-02-05T14:07:11+08:00 [feat(chart): 优化G2GaugeChart组件,仅基于数据驱动UI,不涉及业务逻辑](http://lf.git.oa.mt/publish_platform/web/publish/commit/ad3728038c692c40a03dcb6a38f7b77b0952cccc)
6
+
1
7
  **2.0.1** feat(chart): 使用svg重写gauge图表组件
2
8
  - 2026-02-04T11:18:21+08:00 [feat(chart): 使用svg重写gauge图表组件](http://lf.git.oa.mt/publish_platform/web/publish/commit/47a981e483d2a4df79d473e1053d9f9ea927ee10)
3
9
  - 2026-02-03T19:25:53+08:00 [feat(chart): 修改ts错误](http://lf.git.oa.mt/publish_platform/web/publish/commit/40e4e8f44c7ff02b079974eca52ece225180329c)
package/README.md CHANGED
@@ -1,13 +1,13 @@
1
- # @moonton/publish-chart
1
+ # @publishfx/publish-chart
2
2
 
3
3
  基于新架构设计的 React 图表组件库,支持离线分析模块使用的所有图表类型。
4
4
 
5
5
  ## 安装
6
6
 
7
7
  ```bash
8
- npm install @moonton/publish-chart
8
+ npm install @publishfx/publish-chart
9
9
  # 或
10
- pnpm add @moonton/publish-chart
10
+ pnpm add @publishfx/publish-chart
11
11
  ```
12
12
 
13
13
  ## 特性
@@ -30,7 +30,7 @@ import {
30
30
  defaultChartConfig,
31
31
  createFormatterService,
32
32
  createDataTransformService
33
- } from '@moonton/publish-chart';
33
+ } from '@publishfx/publish-chart';
34
34
 
35
35
  const chartServices = {
36
36
  formatter: createFormatterService(),
@@ -53,7 +53,7 @@ const chartServices = {
53
53
  如果你只需要自定义业务组件,其他服务使用库的默认配置,可以这样:
54
54
 
55
55
  ```tsx
56
- import { ChartProvider } from '@moonton/publish-chart';
56
+ import { ChartProvider } from '@publishfx/publish-chart';
57
57
 
58
58
  // 只定义 businessComponents,其他使用默认值
59
59
  const chartServices = {
@@ -73,7 +73,7 @@ const chartServices = {
73
73
  ### 2. 使用图表组件
74
74
 
75
75
  ```tsx
76
- import { BarChart, LineChart, BarLineChart } from '@moonton/publish-chart';
76
+ import { BarChart, LineChart, BarLineChart } from '@publishfx/publish-chart';
77
77
 
78
78
  <BarChart
79
79
  data={data}
@@ -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
- const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height = 300, showPercent = true, formatter, tickFormatter, pointerColor = "#c5c5c5", thresholdColors = [
4
- "#9596d9",
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 scaleTicks = useMemo(()=>{
10
- if (ticks && ticks.length > 0) return ticks;
11
- if (target) return [
12
- min,
13
- Math.round(0.5 * target),
14
- target,
15
- Math.round(0.9 * max),
16
- max
17
- ];
18
- const step = (max - min) / 4;
19
- return [
20
- min,
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 padding = 50;
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 + 2 * padding));
91
- svgOverlay.setAttribute('height', String(height + 2 * padding));
92
- svgOverlay.setAttribute('viewBox', `0 0 ${width + 2 * padding} ${height + 2 * padding}`);
93
- svgOverlay.style.top = `-${padding}px`;
94
- svgOverlay.style.left = `-${padding}px`;
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 + 2 * padding));
99
- svgOverlay.setAttribute('height', String(height + 2 * padding));
100
- svgOverlay.setAttribute('viewBox', `0 0 ${width + 2 * padding} ${height + 2 * padding}`);
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 = colors[i] || '#ccc';
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 + padding + outerRadius * Math.cos(startAngleValue);
114
- const startY = centerY + padding + outerRadius * Math.sin(startAngleValue);
115
- const endX = centerX + padding + outerRadius * Math.cos(endAngleValue);
116
- const endY = centerY + padding + outerRadius * Math.sin(endAngleValue);
117
- const innerStartX = centerX + padding + innerRadius * Math.cos(startAngleValue);
118
- const innerStartY = centerY + padding + innerRadius * Math.sin(startAngleValue);
119
- const innerEndX = centerX + padding + innerRadius * Math.cos(endAngleValue);
120
- const innerEndY = centerY + padding + innerRadius * Math.sin(endAngleValue);
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 + padding + innerRadius * Math.cos(startAngleValue);
139
- const startY = centerY + padding + innerRadius * Math.sin(startAngleValue);
140
- const startTickEndX = centerX + padding + (outerRadius + tickLength) * Math.cos(startAngleValue);
141
- const startTickEndY = centerY + padding + (outerRadius + tickLength) * Math.sin(startAngleValue);
142
- const startTextX = centerX + padding + (outerRadius + textOffset) * Math.cos(startAngleValue);
143
- const startTextY = centerY + padding + (outerRadius + textOffset) * Math.sin(startAngleValue);
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 ? tickFormatter(min) : String(min);
118
+ startText.textContent = safeTickFormat(tickFormatter, min, String(min));
160
119
  svgOverlay.appendChild(startText);
161
- const thresholdTextOffset = 0.12 * radius;
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 + padding + (outerRadius + currentTextOffset) * Math.cos(thresholdAngle);
187
- const thresholdTextY = centerY + padding + (outerRadius + currentTextOffset) * Math.sin(thresholdAngle);
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', '14');
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 ? tickFormatter(thresholdValue) : String(thresholdValue);
156
+ thresholdText.textContent = safeTickFormat(tickFormatter, thresholdValue, String(thresholdValue));
198
157
  svgOverlay.appendChild(thresholdText);
199
158
  }
200
159
  const endAngleValue = endAngle;
201
- const endX = centerX + padding + innerRadius * Math.cos(endAngleValue);
202
- const endY = centerY + padding + innerRadius * Math.sin(endAngleValue);
203
- const endTickEndX = centerX + padding + (outerRadius + tickLength) * Math.cos(endAngleValue);
204
- const endTickEndY = centerY + padding + (outerRadius + tickLength) * Math.sin(endAngleValue);
205
- const endTextX = centerX + padding + (outerRadius + textOffset) * Math.cos(endAngleValue);
206
- const endTextY = centerY + padding + (outerRadius + textOffset) * Math.sin(endAngleValue);
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 ? tickFormatter(max) : String(max);
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 + padding + pointerStartCircleRadius * Math.cos(pointerAngle);
229
- const pointerStartY = centerY + padding + pointerStartCircleRadius * Math.sin(pointerAngle);
230
- const pointerEndX = centerX + padding + (pointerStartCircleRadius + pointerLength) * Math.cos(pointerAngle);
231
- const pointerEndY = centerY + padding + (pointerStartCircleRadius + pointerLength) * Math.sin(pointerAngle);
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 + padding));
234
- pointerStartCircle.setAttribute('cy', String(centerY + padding));
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,24 @@ 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 displayValue = formatter ? formatter(value, value / max * 100) : tickFormatter ? tickFormatter(value) : String(value);
250
- const percent = (value / max * 100).toFixed(2);
251
- const textCenterX = centerX + padding;
252
- const textCenterY = centerY + padding + 0.7 * radius;
253
- const valueTextY = textCenterY - 10;
254
- const percentTextY = textCenterY + 10;
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
- valueText.setAttribute('fill', colors[0] || '#9596d9');
259
- valueText.setAttribute('font-size', '18');
222
+ const thresholdIndex = thresholdValues.findIndex((threshold)=>value <= threshold);
223
+ const thresholdColor = thresholdColorArray[thresholdIndex] || '#9596d9';
224
+ valueText.setAttribute('fill', thresholdColor);
225
+ valueText.setAttribute('font-size', String(valueFontSize));
260
226
  valueText.setAttribute('font-weight', 'bold');
261
227
  valueText.setAttribute('text-anchor', 'middle');
262
228
  valueText.setAttribute('dominant-baseline', 'middle');
@@ -267,10 +233,10 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
267
233
  percentText.setAttribute('x', String(textCenterX));
268
234
  percentText.setAttribute('y', String(percentTextY));
269
235
  percentText.setAttribute('fill', '#8c8c8c');
270
- percentText.setAttribute('font-size', '14');
236
+ percentText.setAttribute('font-size', String(percentFontSize));
271
237
  percentText.setAttribute('text-anchor', 'middle');
272
238
  percentText.setAttribute('dominant-baseline', 'middle');
273
- percentText.textContent = `占比: ${percent}%`;
239
+ percentText.textContent = `占比: ${percentDisplay}%`;
274
240
  svgOverlay.appendChild(percentText);
275
241
  }
276
242
  if (tooltip) {
@@ -291,11 +257,21 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
291
257
  tooltipElement.style.whiteSpace = 'nowrap';
292
258
  containerRef.current.appendChild(tooltipElement);
293
259
  }
294
- const tooltipLabel = tooltip.label || '值';
295
- const tooltipValue = tooltip.formatter ? tooltip.formatter(value) : value.toLocaleString();
260
+ const tooltipLabel = tooltip.label;
261
+ let tooltipValue;
262
+ if (tooltip.formatter) try {
263
+ tooltipValue = tooltip.formatter(value);
264
+ } catch (error) {
265
+ if ("development" === process.env.NODE_ENV) console.warn("[G2GaugeChart] tooltip.formatter 函数执行出错,使用降级值", {
266
+ error,
267
+ value
268
+ });
269
+ tooltipValue = value.toLocaleString();
270
+ }
271
+ else tooltipValue = value.toLocaleString();
296
272
  tooltipElement.innerHTML = `
297
- <div style="color: #666; margin-bottom: 4px;">${tooltipLabel}</div>
298
- <div style="color: #1d2129; font-weight: 500;">${tooltipValue}</div>
273
+ ${tooltipLabel ? `<div style="color: #666; margin-bottom: 4px;">${tooltipLabel}</div>` : ''}
274
+ ${tooltipValue ? `<div style="color: #1d2129; font-weight: 500;">${tooltipValue}</div>` : ''}
299
275
  `;
300
276
  const handleMouseMove = (e)=>{
301
277
  if (!tooltipElement || !containerRef.current) return;
@@ -304,7 +280,6 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
304
280
  const mouseY = e.clientY;
305
281
  if (mouseX >= svgOverlayRect.left && mouseX <= svgOverlayRect.right && mouseY >= svgOverlayRect.top && mouseY <= svgOverlayRect.bottom) {
306
282
  const containerRect = containerRef.current.getBoundingClientRect();
307
- console.log('containerRect', containerRect, mouseX, mouseY);
308
283
  const relativeX = mouseX - containerRect.left;
309
284
  const relativeY = mouseY - containerRect.top;
310
285
  const offsetX = 10;
@@ -343,24 +318,15 @@ const G2GaugeChart = ({ value, min = 0, max, target, thresholds, ticks, height =
343
318
  }
344
319
  };
345
320
  }, [
346
- value,
347
- min,
348
- max,
349
- thresholdValues,
350
- thresholdColorArray,
351
- scaleTicks,
352
- height,
353
- formatter,
354
- tickFormatter,
355
- showPercent,
356
- pointerColor,
357
- tooltip
321
+ normalizedProps
358
322
  ]);
359
323
  return /*#__PURE__*/ jsx("div", {
360
324
  ref: containerRef,
361
325
  style: {
362
326
  width: "100%",
363
- height: `${height}px`
327
+ height: `${height}px`,
328
+ position: 'relative',
329
+ overflow: 'visible'
364
330
  }
365
331
  });
366
332
  };
@@ -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.1",
3
+ "version": "2.0.3",
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 };