@publishfx/publish-chart 2.1.43 → 2.1.44

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,8 @@
1
+ **2.1.44** feat(chart): 更新图表组件及发布脚本
2
+ - 2026-07-08T11:17:13+08:00 [feat(chart): 更新图表组件及发布脚本](http://lf.git.oa.mt/publish_platform/web/publish/commit/c3efe54dd3e640b72ea027815efd84b5d817f472)
3
+ - 2026-07-07T14:20:55+08:00 [feat: 组合图副轴间距调整 f-6880297364](http://lf.git.oa.mt/publish_platform/web/publish/commit/b662f2234806c5b6e40c5ae8d2ed4a7cacc03bee)
4
+ - 2026-07-07T12:42:22+08:00 [feat: 辅助线延伸优化 f-6880297364](http://lf.git.oa.mt/publish_platform/web/publish/commit/41290b358961c2a1e7ff100addd11b23dc2d5990)
5
+
1
6
  **2.1.41** feat(chart): 分组对比折线图自定义图例与tooltip交互优化 f-6719092744
2
7
  - 2026-06-16T16:45:08+08:00 [feat(chart): 分组对比折线图自定义图例与tooltip交互优化 f-6719092744](http://lf.git.oa.mt/publish_platform/web/publish/commit/446f1dcea5fa50c1db96a395747bf26a5213a2f5)
3
8
 
@@ -285,6 +285,12 @@ export interface ApplyAuxiliaryLineYOptions {
285
285
  stroke?: string;
286
286
  strokeOpacity?: number;
287
287
  labelMaxLength?: number;
288
+ /**
289
+ * 辅助线向 Y 轴刻度区域左侧延伸的像素距离。
290
+ * G2 lineY 默认只覆盖主绘图区,设置后会把起点往左平移指定像素,让线穿过左侧坐标轴区域。
291
+ * 传 'auto' 时,会根据坐标系左侧偏移(paddingLeft + marginLeft)自动填满 Y 轴刻度区域。
292
+ */
293
+ extendLeft?: number | 'auto';
288
294
  }
289
295
  /** 在 view 上添加多条 lineY 辅助线 */
290
296
  export declare function applyAuxiliaryLineY(view: any, lines: AuxiliaryLineItem[], options?: ApplyAuxiliaryLineYOptions): void;
@@ -146,28 +146,140 @@ function applyLegendColor(view, options = {}) {
146
146
  }
147
147
  function applyAuxiliaryLineY(view, lines, options = {}) {
148
148
  if (!lines?.length) return;
149
- const { labelMaxLength = 5, stroke = 'rgb(234, 54, 106)', maxLeft, maxRight } = options;
150
- lines.forEach((auxLine)=>{
151
- view.lineY().data([
149
+ const { labelMaxLength = 5, stroke = 'rgb(234, 54, 106)', maxLeft, maxRight, extendLeft = 0 } = options;
150
+ const isAutoExtend = 'auto' === extendLeft;
151
+ lines.forEach((auxLine, index)=>{
152
+ const lineMark = view.lineY().data([
152
153
  {
153
154
  y: auxLine.value
154
155
  }
155
156
  ]).encode('y', auxLine.value).style('stroke', stroke).style('strokeOpacity', 1).style('opacity', 1).style('lineWidth', 2).style('shadowColor', 'transparent').animate(false).scale('y', {
156
157
  key: auxLine?.axis === 'right' ? 'line' : 'main'
157
158
  }).axis('x', false).label({
158
- opacity: 1,
159
- dx: -20,
160
- dy: 0,
161
- text: splitTextToMultiline(auxLine.name, labelMaxLength, '<br />'),
162
- position: auxLine?.axis === 'right' ? 'bottom-right' : 'bottom-left',
163
- render: (text)=>{
164
- let top = auxLine?.name?.length > 5 ? -38 : -24;
165
- if ((auxLine?.axis === 'left' || auxLine?.axis === '') && maxLeft && auxLine.value > 0.92 * maxLeft || auxLine?.axis === 'right' && maxRight && auxLine.value > 0.92 * maxRight) top = 0;
166
- top += 'px';
167
- return `<div style="background-color: rgba(255, 0, 102, 0.15); padding: 5px; border-radius: 5px;font-size: 12px;position: relative;left: ${auxLine?.axis === 'right' ? '-75px' : '0px'}; top: ${top} ; display:inline-block;line-height: 14px; color: #FF0066;
168
- font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;">${text}</div>`;
169
- }
159
+ text: ' ',
160
+ fill: 'transparent',
161
+ opacity: 0
170
162
  });
163
+ if (isAutoExtend || 'number' == typeof extendLeft && extendLeft > 0) {
164
+ lineMark.style('strokeOpacity', 0).style('opacity', 0);
165
+ view.shape().data([
166
+ {
167
+ x: `_aux_${index}`,
168
+ y: auxLine.value
169
+ }
170
+ ]).encode('x', 'x').encode('y', 'y').scale('x', {
171
+ type: 'band',
172
+ independent: true
173
+ }).scale('y', {
174
+ key: auxLine?.axis === 'right' ? 'line' : 'main'
175
+ }).axis('x', false).tooltip(false).style('clip', false).style('render', ({ y: py }, context)=>{
176
+ const { document } = context;
177
+ const coordinate = context?.coordinate;
178
+ const coorOptions = coordinate?.getOptions?.() || {};
179
+ const plotWidth = coorOptions.width ?? 0;
180
+ const plotHeight = coorOptions.height ?? 0;
181
+ const transformations = coorOptions.transformations ?? [];
182
+ const isTransposed = transformations.some((t)=>Array.isArray(t) ? t.includes('transpose') : 'transpose' === t);
183
+ const isRightAxis = auxLine?.axis === 'right';
184
+ const domainMax = isRightAxis ? maxRight ?? 1 : maxLeft ?? 1;
185
+ const normalized = domainMax > 0 ? auxLine.value / domainMax : 0;
186
+ let linePos;
187
+ if (isTransposed) {
188
+ const mapped = coordinate?.map?.([
189
+ 0,
190
+ 1 - normalized
191
+ ]);
192
+ linePos = mapped?.[0] ?? py;
193
+ } else linePos = py;
194
+ const g = document.createElement('g', {});
195
+ const axisLeftWidth = (coorOptions.paddingLeft ?? 0) + (coorOptions.marginLeft ?? 0);
196
+ const axisRightWidth = (coorOptions.paddingRight ?? 0) + (coorOptions.marginRight ?? 0);
197
+ const axisTopHeight = (coorOptions.paddingTop ?? 0) + (coorOptions.marginTop ?? 0);
198
+ const leftExtend = isAutoExtend ? axisLeftWidth : extendLeft;
199
+ const rightExtend = isAutoExtend ? axisRightWidth : extendLeft;
200
+ const topExtend = isAutoExtend ? axisTopHeight : extendLeft;
201
+ let pathD;
202
+ pathD = isTransposed ? `M ${linePos} ${-topExtend} L ${linePos} ${plotHeight}` : isRightAxis ? `M 0 ${linePos} L ${plotWidth + rightExtend} ${linePos}` : `M ${-leftExtend} ${linePos} L ${plotWidth} ${linePos}`;
203
+ const path = document.createElement('path', {
204
+ style: {
205
+ d: pathD,
206
+ stroke,
207
+ lineWidth: 2,
208
+ strokeOpacity: 1,
209
+ opacity: 1
210
+ }
211
+ });
212
+ g.appendChild(path);
213
+ const labelText = splitTextToMultiline(auxLine.name, labelMaxLength, '\n');
214
+ const labelLines = labelText.split('\n');
215
+ const lineHeight = 14;
216
+ const fontSize = 12;
217
+ const padding = 5;
218
+ const maxLineWidth = Math.max(...labelLines.map((line)=>{
219
+ let width = 0;
220
+ for (const char of line)width += char.charCodeAt(0) > 127 ? fontSize : 0.6 * fontSize;
221
+ return width;
222
+ }));
223
+ const labelWidth = maxLineWidth + 2 * padding;
224
+ const labelHeight = labelLines.length * lineHeight + 2 * padding;
225
+ const isNearTop = (auxLine?.axis === 'left' || auxLine?.axis === '') && maxLeft && auxLine.value > 0.98 * maxLeft || isRightAxis && maxRight && auxLine.value > 0.98 * maxRight;
226
+ let top = auxLine?.name?.length > 5 ? -38 : -24;
227
+ if (isNearTop) top = 8;
228
+ const isNearRightEdge = isTransposed && maxLeft && auxLine.value > 0.98 * maxLeft;
229
+ let labelX;
230
+ let labelY;
231
+ let textX;
232
+ let textAnchor;
233
+ if (isTransposed) {
234
+ if (isNearRightEdge) {
235
+ labelX = linePos - labelWidth - 8;
236
+ textAnchor = 'start';
237
+ } else {
238
+ labelX = linePos + 8;
239
+ textAnchor = 'start';
240
+ }
241
+ labelY = plotHeight - labelHeight;
242
+ textX = labelX + padding;
243
+ } else if (isRightAxis) {
244
+ const leftShift = 0;
245
+ const rightEdge = plotWidth + rightExtend - leftShift;
246
+ labelY = linePos + top - padding;
247
+ labelX = rightEdge - labelWidth;
248
+ textX = labelX + padding;
249
+ textAnchor = 'start';
250
+ } else {
251
+ labelY = linePos + top - padding;
252
+ labelX = -leftExtend;
253
+ textX = labelX + padding;
254
+ textAnchor = 'start';
255
+ }
256
+ const labelBg = document.createElement('rect', {
257
+ style: {
258
+ x: labelX,
259
+ y: labelY,
260
+ width: labelWidth,
261
+ height: labelHeight,
262
+ fill: 'rgba(255, 0, 102, 0.15)',
263
+ radius: 5
264
+ }
265
+ });
266
+ g.appendChild(labelBg);
267
+ const labelEl = document.createElement('text', {
268
+ style: {
269
+ x: textX,
270
+ y: labelY + padding,
271
+ text: labelText,
272
+ fill: '#FF0066',
273
+ fontSize,
274
+ fontFamily: "'PingFang SC', 'Microsoft YaHei', sans-serif",
275
+ textBaseline: 'top',
276
+ textAnchor
277
+ }
278
+ });
279
+ g.appendChild(labelEl);
280
+ return g;
281
+ });
282
+ }
171
283
  });
172
284
  }
173
285
  function getColorByGroupType(groupType, options) {
@@ -330,15 +442,15 @@ function fillMissingIndicator(baseItems, safeTitle, isCompare, isGroup = false)
330
442
  if (3 !== count) {
331
443
  const color = baseItems.filter((item)=>'transparent' !== item.color).map((item)=>item.color)[0];
332
444
  if (!baseItems.find((item)=>item.indicatorId === indicatorId && item.name === indicatorId)) baseItems.push({
333
- indicatorId: indicatorId,
445
+ indicatorId,
334
446
  name: indicatorId,
335
447
  value: '-',
336
448
  x: safeTitle,
337
- color: color,
449
+ color,
338
450
  dataType: 'manual'
339
451
  });
340
452
  if (!baseItems.find((item)=>item.indicatorId === indicatorId && item.name === indicatorId + '_compare')) baseItems.push({
341
- indicatorId: indicatorId,
453
+ indicatorId,
342
454
  name: indicatorId + '_compare',
343
455
  tipType: 'compareline',
344
456
  value: '-',
@@ -346,7 +458,7 @@ function fillMissingIndicator(baseItems, safeTitle, isCompare, isGroup = false)
346
458
  dataType: 'manual'
347
459
  });
348
460
  if (!baseItems.find((item)=>item.indicatorId === indicatorId && item.name === indicatorId + '_change')) baseItems.push({
349
- indicatorId: indicatorId,
461
+ indicatorId,
350
462
  name: indicatorId + '_change',
351
463
  isChange: true,
352
464
  value: '-',
@@ -134,7 +134,8 @@ function renderG2BarChart(container, options) {
134
134
  });
135
135
  applyAuxiliaryLineY(view, auxiliaryLineData, {
136
136
  labelMaxLength: 5,
137
- maxLeft: maxY
137
+ maxLeft: maxY,
138
+ extendLeft: 'auto'
138
139
  });
139
140
  const nodeResult = applyNodeData(view, nodeData, x, y);
140
141
  if (isDataTag && formatLabel) interval.label({
@@ -2,9 +2,9 @@
2
2
  * G2 柱状 + 折线组合图函数式 API
3
3
  * 复用 G2BarChart / G2LineChart 的模式:React 负责数据与配置,G2 负责纯渲染
4
4
  */
5
- import { Chart } from "@antv/g2";
6
- import type { ChartTimeRange, IndicatorInfo } from "../../../core/ChartTypes";
7
- import { type AuxiliaryLineItem } from "./g2Helpers";
5
+ import { Chart } from '@antv/g2';
6
+ import type { ChartTimeRange, IndicatorInfo } from '../../../core/ChartTypes';
7
+ import { type AuxiliaryLineItem } from './g2Helpers';
8
8
  export interface RenderG2CombineChartOptions {
9
9
  /** 已转换后的数据:约定包含 groupName、groupType、groupValue 等字段 */
10
10
  data: any[];
@@ -18,7 +18,7 @@ function renderG2CombineChart(container, options) {
18
18
  });
19
19
  const minLabelWidth = data.some((d)=>d.groupName.length < 3) ? 30 : 90;
20
20
  const view = getMainView(chart);
21
- view.attr('marginLeft', 0).attr('marginRight', 30).attr('marginTop', 16).attr('marginBottom', 0).animate(false);
21
+ view.attr('marginLeft', 0).attr('marginRight', 0).attr('marginTop', 16).attr('marginBottom', 0).animate(false);
22
22
  view.data(data);
23
23
  const xValueCount = new Set(data.map((d)=>d[x])).size;
24
24
  applyAxisX(view, {
@@ -28,7 +28,7 @@ function renderG2CombineChart(container, options) {
28
28
  containerWidth: container.clientWidth,
29
29
  minLabelWidth
30
30
  });
31
- const barColor = themeColors[0] ?? "#5B8FF9";
31
+ const barColor = themeColors[0] ?? '#5B8FF9';
32
32
  applyHighlightDate(view, x, data, highlightDate, isHighlight);
33
33
  const intervalData = data.filter((item)=>!item.isCombine).map((item)=>({
34
34
  ...item,
@@ -39,7 +39,7 @@ function renderG2CombineChart(container, options) {
39
39
  const auxRightValues = auxiliaryLineData?.length ? auxiliaryLineData.filter((item)=>'right' === item.axis).map((item)=>item.value) : [];
40
40
  const maxAuxRight = auxRightValues.length ? Math.max(...auxRightValues) : 0;
41
41
  const finalIntervalMax = Math.max(maxLeft, maxAuxLeft);
42
- const interval = view.interval().data(intervalData).encode("x", x).encode("y", BAR_Y_FIELD).encode("color", "groupType").animate(false).transform(isGroup ? {
42
+ const interval = view.interval().data(intervalData).encode('x', x).encode('y', BAR_Y_FIELD).encode('color', 'groupType').animate(false).transform(isGroup ? {
43
43
  type: 'stackY',
44
44
  reverse: true
45
45
  } : {
@@ -60,7 +60,7 @@ function renderG2CombineChart(container, options) {
60
60
  fill: (datum)=>{
61
61
  const groupType = String(datum.groupType ?? '');
62
62
  const index = legendItems.findIndex((item)=>item.id === `${groupType}_${datum.isCombine}`);
63
- if (isGroup) return themeColors[index % themeColors.length] ?? themeColors[0] ?? "#5B8FF9";
63
+ if (isGroup) return themeColors[index % themeColors.length] ?? themeColors[0] ?? '#5B8FF9';
64
64
  if (groupType.includes('_compare')) return {
65
65
  image: lines({
66
66
  backgroundColor: '#fff',
@@ -110,7 +110,7 @@ function renderG2CombineChart(container, options) {
110
110
  name: datum['groupType'],
111
111
  indicatorId,
112
112
  channel: Y_AXIS_FIELD,
113
- color: themeColors[index % themeColors.length] ?? themeColors[0] ?? "#5B8FF9",
113
+ color: themeColors[index % themeColors.length] ?? themeColors[0] ?? '#5B8FF9',
114
114
  percent: `${(100 * Number(datum.percent)).toFixed(2)}%`,
115
115
  isCombine: false,
116
116
  x: datum[x]
@@ -156,7 +156,7 @@ function renderG2CombineChart(container, options) {
156
156
  [LINE_Y_FIELD]: item[Y_AXIS_FIELD]
157
157
  }));
158
158
  const finalLineMax = Math.max(maxRight, maxAuxRight);
159
- const line = view.line().data(lineData).encode("x", x).encode("y", LINE_Y_FIELD).encode('color', 'groupType').animate(false).style({
159
+ const line = view.line().data(lineData).encode('x', x).encode('y', LINE_Y_FIELD).encode('color', 'groupType').animate(false).style({
160
160
  lineWidth: 2,
161
161
  stroke: (datum)=>{
162
162
  const currentDatum = datum[0];
@@ -261,7 +261,7 @@ function renderG2CombineChart(container, options) {
261
261
  marker: false,
262
262
  render: (_event, payload)=>{
263
263
  const { title, items } = payload;
264
- let safeTitle = title.replace(/,.*$/, '');
264
+ const safeTitle = title.replace(/,.*$/, '');
265
265
  const baseItems = items.filter((item)=>item.hasOwnProperty('isCombine') && !item.isCombine);
266
266
  const safeItems = fillMissingIndicator(baseItems, safeTitle, isCompare, isGroup);
267
267
  const combineItems = items.filter((item)=>item.hasOwnProperty('isCombine') && item.isCombine);
@@ -302,7 +302,9 @@ function renderG2CombineChart(container, options) {
302
302
  });
303
303
  applyAuxiliaryLineY(view, auxiliaryLineData, {
304
304
  maxLeft,
305
- maxRight
305
+ maxRight,
306
+ extendLeft: 'auto',
307
+ labelMaxLength: 5
306
308
  });
307
309
  const nodeResult = applyNodeData(view, nodeData, x, 'groupValue');
308
310
  if (formatLabel) {
@@ -120,7 +120,8 @@ function renderG2LineChart(container, options) {
120
120
  tipBoxShadow: '0 3px 6px -4px rgba(0, 0, 0, 1)'
121
121
  });
122
122
  applyAuxiliaryLineY(view, auxiliaryLineData, {
123
- maxLeft: maxY
123
+ maxLeft: maxY,
124
+ extendLeft: 'auto'
124
125
  });
125
126
  const nodeResult = applyNodeData(view, nodeData, x, y);
126
127
  if (isDataTag && formatLabel) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@publishfx/publish-chart",
3
- "version": "2.1.43",
3
+ "version": "2.1.44",
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": [