@publishfx/publish-chart 2.1.5 → 2.1.7

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,16 @@
1
+ **2.1.7** fix(chart): 修复折线图有空数据的异常场景
2
+ - 2026-03-03T17:50:03+08:00 [fix(chart): 修复折线图有空数据的异常场景](http://lf.git.oa.mt/publish_platform/web/publish/commit/3465ede843d4fb156932871cdf042315dbb17096)
3
+ - 2026-03-03T17:38:27+08:00 [fix(chart): 组合图柱数据缺失问题优化](http://lf.git.oa.mt/publish_platform/web/publish/commit/fa55e35a7569b9e7000d2c4dc5d09eee3b60bfff)
4
+ - 2026-03-03T17:35:16+08:00 [fix: tserror](http://lf.git.oa.mt/publish_platform/web/publish/commit/995efdbe45391ecdb06ef6838c65d9bc87c33efa)
5
+ - 2026-03-03T17:28:25+08:00 [feat: 去掉element:click](http://lf.git.oa.mt/publish_platform/web/publish/commit/5bb84001d505392306f4dfd8300e7d21f47fe95b)
6
+ - 2026-03-03T17:26:42+08:00 [fix: 折线图点击无法下钻](http://lf.git.oa.mt/publish_platform/web/publish/commit/5395a60b96d9db398d5a4c5c8610481cbba8362a)
7
+ - 2026-03-03T16:36:31+08:00 [fix: label缩略时要设置minLength](http://lf.git.oa.mt/publish_platform/web/publish/commit/b11ff5ff52376442f3a9b057b942f0d47c4f9ee5)
8
+ - 2026-03-03T14:19:39+08:00 [feat(chart): 组合图排序代码整理](http://lf.git.oa.mt/publish_platform/web/publish/commit/baab4ed6bbd0a3bac46fccb1d3875af241368770)
9
+
10
+ **2.1.6** fix(chart): 解决ts错误
11
+ - 2026-03-03T06:38:38+08:00 [fix(chart): 解决ts错误](http://lf.git.oa.mt/publish_platform/web/publish/commit/a1a61c8cbfede6e0993a1101dd4f375ad3383302)
12
+ - 2026-03-03T06:30:33+08:00 [fix(chart): 修复所有遗留问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/73456fbb6fe853fc9069e2c6712851375dd372da)
13
+
1
14
  **2.1.5** fix(chart): 修正ts错误
2
15
  - 2026-03-03T04:41:18+08:00 [fix(chart): 修正ts错误](http://lf.git.oa.mt/publish_platform/web/publish/commit/ed6bc3403d6c06d8c0cce6f0193a447d74b68a3a)
3
16
  - 2026-03-03T04:38:12+08:00 [fix(chart): 组合图代码整理](http://lf.git.oa.mt/publish_platform/web/publish/commit/6a94afb106447e54143f4a29e7d8fa24c718b321)
@@ -106,7 +106,7 @@ class DataAdapter {
106
106
  if (config.y) dv.transform({
107
107
  type: 'map',
108
108
  callback: (row)=>{
109
- row[config.y] = '' !== row[config.y] ? +row[config.y] : '-';
109
+ row[config.y] = '' !== row[config.y] && '-' !== row[config.y] && null !== row[config.y] ? +row[config.y] : '-';
110
110
  return row;
111
111
  }
112
112
  });
@@ -158,10 +158,15 @@ class DataAdapter {
158
158
  });
159
159
  const xKey = config.x ?? 'x';
160
160
  const sortData = tdv.rows.map((item)=>item[xKey]);
161
- if (config.isDescend) combinedv.rows.sort((a, b)=>sortData.indexOf(a[xKey]) - sortData.indexOf(b[xKey]));
162
- else combinedv.rows.sort((a, b)=>sortData.indexOf(a[xKey]) - sortData.indexOf(b[xKey]));
161
+ combinedv.rows.sort((a, b)=>{
162
+ const aIndex = sortData.indexOf(a[xKey]);
163
+ const bIndex = sortData.indexOf(b[xKey]);
164
+ if (-1 === aIndex) return 1;
165
+ if (-1 === bIndex) return -1;
166
+ return aIndex - bIndex;
167
+ });
163
168
  }
164
- console.log('renderG2CombineChart transformedData:leftData:', tdv.rows, 'rightData:', combinedv.rows);
169
+ console.log('leftData:', tdv.rows, 'rightData:', combinedv.rows);
165
170
  return {
166
171
  transformedData: dv.rows,
167
172
  leftData: tdv.rows,
@@ -31,6 +31,7 @@ const G2LineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorMap,
31
31
  const [legendItems, setLegendItems] = useState([]);
32
32
  const containerRef = useRef(null);
33
33
  const chartRef = useRef(null);
34
+ const currentTooltipTitleRef = useRef(null);
34
35
  const [viewWidth, setViewWidth] = useState(0);
35
36
  const [viewOffset, setViewOffset] = useState(0);
36
37
  const transformedData = useMemo(()=>{
@@ -72,6 +73,7 @@ const G2LineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorMap,
72
73
  safeY,
73
74
  auxiliaryLineData
74
75
  ]);
76
+ console.log('minY, maxY:', minY, maxY);
75
77
  const formatAxis = useCallback((val)=>getAxisFormat(val, safeIndicatorMap, safeLegend), [
76
78
  safeIndicatorMap,
77
79
  safeLegend
@@ -143,6 +145,7 @@ const G2LineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorMap,
143
145
  } : void 0,
144
146
  height: height - canvasMarginBottom,
145
147
  tooltipRender: (title, items)=>{
148
+ currentTooltipTitleRef.current = title;
146
149
  const container = tooltipContainerRef.current;
147
150
  if (!container) return null;
148
151
  let safeItems = items.map((i)=>({
@@ -174,15 +177,18 @@ const G2LineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorMap,
174
177
  }
175
178
  });
176
179
  chartRef.current = chart;
177
- if (onChartClick) chart.on("element:click", (e)=>{
178
- const datum = e.data?.data;
179
- if (datum) onChartClick({
180
- title: datum[x]
181
- });
180
+ if (onChartClick) chart.on("plot:click", (_e)=>{
181
+ const tooltipTitle = currentTooltipTitleRef.current;
182
+ if (null != tooltipTitle) {
183
+ console.log('使用 tooltip title 触发下钻:', tooltipTitle);
184
+ onChartClick({
185
+ title: tooltipTitle
186
+ });
187
+ }
182
188
  });
183
189
  return ()=>{
184
190
  if (chartRef.current) {
185
- chartRef.current.off("element:click");
191
+ chartRef.current.off("plot:click");
186
192
  chartRef.current.off("element:mouseenter");
187
193
  chartRef.current.off("element:mouseleave");
188
194
  chartRef.current.destroy();
@@ -217,14 +217,17 @@ export declare function applyScaleYLinear(view: any, options?: ScaleYLinearOptio
217
217
  export interface ApplyAxisXOptions {
218
218
  title?: boolean;
219
219
  isHorizontal?: boolean;
220
- labelAutoHide?: 'greedy' | 'equidistant' | boolean;
221
- /** 为 true 时刻度过多会自动旋转为垂直;为 false 时保持水平,依赖 labelAutoHide 避免重叠。默认 false */
222
- labelAutoRotate?: boolean;
223
220
  grid?: boolean;
224
221
  gridStroke?: string;
225
222
  gridLineWidth?: number;
226
223
  gridLineDash?: number[];
227
224
  gridFilter?: (val: any) => boolean;
225
+ /** X 轴数据点数量,用于计算 label/tick 采样间隔 */
226
+ dataCount?: number;
227
+ /** 图表容器宽度(px),用于计算可容纳的最大标签数 */
228
+ containerWidth?: number;
229
+ /** 单个标签最大显示字符数,超出截断。默认 6 */
230
+ maxLabelChars?: number;
228
231
  }
229
232
  /** 配置 X 轴,挂载在 mark 上(如 line / interval) */
230
233
  export declare function applyAxisX(mark: any, options?: ApplyAxisXOptions): void;
@@ -37,10 +37,30 @@ function applyScaleYLinear(view, options = {}) {
37
37
  }
38
38
  });
39
39
  }
40
+ function calcAxisInterval(dataCount, containerWidth, maxLabelChars, fontSize) {
41
+ if (dataCount <= 1) return 1;
42
+ const charWidth = 0.55 * fontSize;
43
+ const ellipsisWidth = Math.min(maxLabelChars * charWidth, 90);
44
+ const slotWidth = ellipsisWidth + 8;
45
+ const plotWidth = 0.85 * containerWidth;
46
+ const maxLabels = Math.max(Math.floor(plotWidth / slotWidth), 2);
47
+ return Math.max(Math.ceil(dataCount / maxLabels), 1);
48
+ }
49
+ function createAxisFilter(type, interval, _dataCount) {
50
+ return (_datum, index)=>{
51
+ if (0 === index) return true;
52
+ return 'tick' === type ? index % interval === 0 : true;
53
+ };
54
+ }
40
55
  function applyAxisX(mark, options = {}) {
41
- const { title = false, grid = true, gridStroke = '#333', gridLineWidth = 50, gridLineDash = [
56
+ const { title = false, isHorizontal = false, grid = true, gridStroke = '#333', gridLineWidth = 50, gridLineDash = [
42
57
  1000
43
- ], gridFilter } = options;
58
+ ], gridFilter, dataCount = 0, containerWidth = 0, maxLabelChars = 30 } = options;
59
+ const fontSize = 12;
60
+ const needSampling = dataCount > 0 && containerWidth > 0;
61
+ const interval = needSampling ? calcAxisInterval(dataCount, containerWidth, maxLabelChars, fontSize) : 1;
62
+ const axisFilter = needSampling && interval > 1 ? (type)=>createAxisFilter(type, interval, dataCount) : null;
63
+ console.log('axisFilter:', interval);
44
64
  mark.axis('x', {
45
65
  title,
46
66
  grid,
@@ -51,17 +71,19 @@ function applyAxisX(mark, options = {}) {
51
71
  gridFilter
52
72
  },
53
73
  labelAutoRotate: false,
54
- labelFontSize: 12,
74
+ labelFontSize: fontSize,
55
75
  labelFontColor: '#000',
76
+ size: isHorizontal ? Math.floor(containerWidth / 5) : 30,
56
77
  labelAutoEllipsis: {
57
78
  type: 'ellipsis',
58
- maxLength: 50
79
+ minLength: 80,
80
+ maxLength: isHorizontal ? Math.floor(containerWidth / 5) : Math.min(maxLabelChars * fontSize * 0.55, 90)
59
81
  },
60
82
  labelAlign: 'horizontal',
61
- labelAutoHide: {
62
- type: 'hide',
63
- keepHeader: true,
64
- keepTail: true
83
+ labelDx: (_datum, _index, _data)=>0,
84
+ ...axisFilter && {
85
+ tickFilter: axisFilter('tick'),
86
+ labelFilter: axisFilter('label')
65
87
  }
66
88
  });
67
89
  }
@@ -20,7 +20,7 @@ function renderG2BarChart(container, options) {
20
20
  });
21
21
  console.log('isLegend:', legend, isLegend);
22
22
  const view = getMainView(chart);
23
- view.attr('marginLeft', 0).attr('marginBottom', isLegend ? 0 : 16);
23
+ view.attr('marginLeft', 0).attr('marginRight', 0).attr('marginBottom', isLegend ? 0 : 16);
24
24
  if (isHorizontal) view.coordinate({
25
25
  transform: [
26
26
  {
@@ -71,14 +71,27 @@ function renderG2BarChart(container, options) {
71
71
  },
72
72
  cursor: isClickable ? 'pointer' : 'default'
73
73
  });
74
+ const xValueCount = new Set(data.map((d)=>d[x])).size;
74
75
  applyAxisX(view, {
75
76
  grid: false,
76
- isHorizontal
77
+ isHorizontal,
78
+ dataCount: xValueCount,
79
+ containerWidth: container.clientWidth
77
80
  });
78
81
  applyAxisY(view, {
79
82
  labelFormatter: formatAxis
80
83
  });
81
84
  view.legend(false);
85
+ view.interaction('poptip', {
86
+ offsetY: -20,
87
+ offsetX: 0,
88
+ tipBackgroundColor: '#fcfcfc',
89
+ tipColor: '#333',
90
+ tipBorderRadius: '6px',
91
+ tipPadding: '10px 12px',
92
+ tipFontSize: '12px',
93
+ tipBoxShadow: '0 3px 6px -4px rgba(0, 0, 0, 1)'
94
+ });
82
95
  interval.tooltip({
83
96
  items: [
84
97
  (datum)=>({
@@ -105,11 +118,18 @@ function renderG2BarChart(container, options) {
105
118
  }) : null
106
119
  ].filter(Boolean)
107
120
  });
121
+ const tooltipBounding = {
122
+ x: 0,
123
+ y: 0,
124
+ width: container.clientWidth || container.offsetWidth,
125
+ height: height
126
+ };
108
127
  if (tooltipRender) view.interaction('tooltip', {
109
128
  shared: true,
110
129
  crosshairsY: true,
111
130
  wait: 100,
112
131
  marker: false,
132
+ bounding: tooltipBounding,
113
133
  render: (_event, payload)=>{
114
134
  const { title, items } = payload;
115
135
  const lastIsChange = items.filter((item)=>item.isChange);
@@ -121,7 +141,9 @@ function renderG2BarChart(container, options) {
121
141
  return tooltipRender(title, safeItems ?? []) ?? null;
122
142
  }
123
143
  });
124
- else view.interaction('tooltip', true);
144
+ else view.interaction('tooltip', {
145
+ bounding: tooltipBounding
146
+ });
125
147
  applyAuxiliaryLineY(view, auxiliaryLineData);
126
148
  const nodeList = [];
127
149
  console.log('nodeList data:', data);
@@ -53,7 +53,10 @@ export interface RenderG2CombineChartOptions {
53
53
  value: any;
54
54
  isChange?: boolean;
55
55
  compareTime?: string;
56
- }>) => HTMLElement | null;
56
+ }>, position?: {
57
+ left: number;
58
+ top: number;
59
+ }) => HTMLElement | null;
57
60
  /** 辅助线 */
58
61
  auxiliaryLineData?: AuxiliaryLineItem[];
59
62
  /** 将计算出的节点信息传递给外层(用于渲染 NodePopover 等) */
@@ -20,9 +20,12 @@ function renderG2CombineChart(container, options) {
20
20
  view.attr('insetLeft', 0).attr('insetRight', 0).attr('marginLeft', 0).attr('marginRight', 0).attr('marginBottom', 0);
21
21
  console.log('renderG2CombineChart data:', data, 'maxY:', maxY, x, y, 'indicators:', indicators);
22
22
  view.data(data);
23
+ const xValueCount = new Set(data.map((d)=>d[x])).size;
23
24
  applyAxisX(view, {
24
25
  title: false,
25
- grid: false
26
+ grid: false,
27
+ dataCount: xValueCount,
28
+ containerWidth: container.clientWidth
26
29
  });
27
30
  const barColor = themeColors[0] ?? "#5B8FF9";
28
31
  console.log('tooltip: themeColors:', themeColors[0], barColor, isGroup);
@@ -183,6 +186,7 @@ function renderG2CombineChart(container, options) {
183
186
  ];
184
187
  const groupType = String(datum?.groupType ?? '');
185
188
  const index = groupTypeArr.findIndex((item)=>item === groupType);
189
+ console.log('line: groupTypeArr:', groupTypeArr, groupType, index, lineColors, getIndicatorCompareName(_indicatorMap, datum['groupType']), datum['groupType']);
186
190
  return {
187
191
  value: datum[LINE_Y_FIELD],
188
192
  name: getIndicatorCompareName(_indicatorMap, datum['groupType']),
@@ -275,7 +279,7 @@ function renderG2CombineChart(container, options) {
275
279
  shared: true,
276
280
  crosshairsY: true,
277
281
  marker: false,
278
- render: (_event, payload)=>{
282
+ render: (event, payload)=>{
279
283
  const { title, items } = payload;
280
284
  const baseItems = items.filter((item)=>!item.isCombine);
281
285
  const combineItems = items.filter((item)=>item.isCombine);
@@ -296,13 +300,66 @@ function renderG2CombineChart(container, options) {
296
300
  };
297
301
  return typeRank(a) - typeRank(b);
298
302
  });
299
- return tooltipRender(title, [
303
+ const content = tooltipRender(title, [
300
304
  ...baseItems,
301
305
  ...sortedCombineItems
302
306
  ]) ?? null;
307
+ if (content) {
308
+ const domEvent = event?.event ?? event;
309
+ const clientX = domEvent?.clientX ?? domEvent?.x ?? 0;
310
+ const clientY = domEvent?.clientY ?? domEvent?.y ?? 0;
311
+ const rect = container.getBoundingClientRect();
312
+ const offsetX = 12;
313
+ const offsetY = 12;
314
+ const applyClamp = ()=>{
315
+ let el = content;
316
+ while(el && el.parentElement){
317
+ const p = el.parentElement;
318
+ if (p.classList?.contains('g2-tooltip')) {
319
+ el = p;
320
+ break;
321
+ }
322
+ el = p;
323
+ }
324
+ const wrapper = el && el.classList?.contains('g2-tooltip') ? el : null;
325
+ if (!wrapper) return;
326
+ const wRect = wrapper.getBoundingClientRect();
327
+ const tooltipW = wRect.width || 260;
328
+ const tooltipH = wRect.height || 300;
329
+ const minLeft = rect.left;
330
+ const maxLeft = rect.right - tooltipW;
331
+ const minTop = rect.top;
332
+ const maxTop = rect.bottom - tooltipH;
333
+ const desiredLeft = clientX + offsetX;
334
+ const desiredTop = clientY + offsetY;
335
+ const left = Math.min(Math.max(desiredLeft, minLeft), Math.max(maxLeft, minLeft));
336
+ const top = Math.min(Math.max(desiredTop, minTop), Math.max(maxTop, minTop));
337
+ wrapper.style.position = 'fixed';
338
+ wrapper.style.left = `${left}px`;
339
+ wrapper.style.top = `${top}px`;
340
+ wrapper.style.transform = 'translate(0px, 0px)';
341
+ wrapper.style.pointerEvents = 'none';
342
+ wrapper.style.zIndex = '9999';
343
+ };
344
+ try {
345
+ applyClamp();
346
+ requestAnimationFrame(applyClamp);
347
+ } catch {}
348
+ }
349
+ return content;
303
350
  }
304
351
  });
305
352
  view.legend(false);
353
+ view.interaction('poptip', {
354
+ offsetY: -20,
355
+ offsetX: 0,
356
+ tipBackgroundColor: '#fcfcfc',
357
+ tipColor: '#333',
358
+ tipBorderRadius: '6px',
359
+ tipPadding: '10px 12px',
360
+ tipFontSize: '12px',
361
+ tipBoxShadow: '0 3px 6px -4px rgba(0, 0, 0, 1)'
362
+ });
306
363
  applyAuxiliaryLineY(view, auxiliaryLineData);
307
364
  const nodeList = [];
308
365
  console.log('nodeList data:', data);
@@ -56,9 +56,12 @@ function renderG2GroupBarChart(container, options) {
56
56
  },
57
57
  stroke: 'transparent'
58
58
  });
59
+ const xValueCount = new Set(data.map((d)=>d[x])).size;
59
60
  applyAxisX(view, {
60
61
  title: false,
61
- grid: false
62
+ grid: false,
63
+ dataCount: xValueCount,
64
+ containerWidth: container.clientWidth
62
65
  });
63
66
  applyAxisY(view, {
64
67
  title: false,
@@ -70,6 +73,7 @@ function renderG2GroupBarChart(container, options) {
70
73
  labelFormatter: formatAxis
71
74
  });
72
75
  view.legend(false);
76
+ view.interaction('poptip', true);
73
77
  interval.tooltip({
74
78
  items: [
75
79
  (datum)=>{
@@ -86,20 +90,38 @@ function renderG2GroupBarChart(container, options) {
86
90
  }
87
91
  ].filter(Boolean)
88
92
  });
89
- if (tooltipRender) interval.interaction('tooltip', {
90
- shared: true,
91
- render: (_event, payload)=>{
92
- const { title, items } = payload;
93
- const lastIsChange = items.filter((item)=>item.isChange);
94
- let safeItems = items;
95
- if (lastIsChange.length > 0) safeItems = [
96
- ...items.filter((item)=>!item.isChange),
97
- lastIsChange[lastIsChange.length - 1]
98
- ];
99
- return tooltipRender(title, safeItems ?? []) ?? null;
100
- }
101
- });
102
- else view.interaction('tooltip', true);
93
+ if (tooltipRender) {
94
+ const tooltipBounding = {
95
+ x: 0,
96
+ y: 0,
97
+ width: container.clientWidth || container.offsetWidth,
98
+ height: height
99
+ };
100
+ interval.interaction('tooltip', {
101
+ shared: true,
102
+ bounding: tooltipBounding,
103
+ render: (_event, payload)=>{
104
+ const { title, items } = payload;
105
+ const lastIsChange = items.filter((item)=>item.isChange);
106
+ let safeItems = items;
107
+ if (lastIsChange.length > 0) safeItems = [
108
+ ...items.filter((item)=>!item.isChange),
109
+ lastIsChange[lastIsChange.length - 1]
110
+ ];
111
+ return tooltipRender(title, safeItems ?? []) ?? null;
112
+ }
113
+ });
114
+ } else {
115
+ const tooltipBounding = {
116
+ x: 0,
117
+ y: 0,
118
+ width: container.clientWidth || container.offsetWidth,
119
+ height: height
120
+ };
121
+ view.interaction('tooltip', {
122
+ bounding: tooltipBounding
123
+ });
124
+ }
103
125
  applyAuxiliaryLineY(view, auxiliaryLineData);
104
126
  const nodeList = [];
105
127
  console.log('nodeList data:', data);
@@ -43,8 +43,11 @@ function renderG2LineChart(container, options) {
43
43
  lineWidth: 2,
44
44
  stroke: (datum)=>getColorByGroupType(datum[0]?.groupType ?? '', colorOpts)
45
45
  });
46
+ const xValueCount = new Set(data.map((d)=>d[x])).size;
46
47
  applyAxisX(view, {
47
- grid: false
48
+ grid: false,
49
+ dataCount: xValueCount,
50
+ containerWidth: container.clientWidth
48
51
  });
49
52
  applyAxisY(view, {
50
53
  labelFormatter: formatAxis
@@ -81,9 +84,16 @@ function renderG2LineChart(container, options) {
81
84
  })
82
85
  ]
83
86
  });
87
+ const tooltipBounding = {
88
+ x: 0,
89
+ y: 0,
90
+ width: container.clientWidth || container.offsetWidth,
91
+ height: height
92
+ };
84
93
  if (tooltipRender) view.interaction('tooltip', {
85
94
  shared: true,
86
95
  marker: false,
96
+ bounding: tooltipBounding,
87
97
  render: (_event, payload)=>{
88
98
  const { title, items } = payload;
89
99
  console.log('tooltipRender:', title, items, indicators);
@@ -112,6 +122,16 @@ function renderG2LineChart(container, options) {
112
122
  }
113
123
  });
114
124
  view.legend(false);
125
+ view.interaction('poptip', {
126
+ offsetY: -20,
127
+ offsetX: 0,
128
+ tipBackgroundColor: '#fcfcfc',
129
+ tipColor: '#333',
130
+ tipBorderRadius: '6px',
131
+ tipPadding: '10px 12px',
132
+ tipFontSize: '12px',
133
+ tipBoxShadow: '0 3px 6px -4px rgba(0, 0, 0, 1)'
134
+ });
115
135
  applyAuxiliaryLineY(view, auxiliaryLineData);
116
136
  const nodeList = [];
117
137
  console.log('nodeList data:', data);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@publishfx/publish-chart",
3
- "version": "2.1.5",
3
+ "version": "2.1.7",
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": [