@publishfx/publish-chart 2.1.32 → 2.1.34

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,36 @@
1
+ **2.1.34** fix(chart): 修复组合图有缺失项时导致的排序不符合预期的问题
2
+ - 2026-03-13T18:27:29+08:00 [fix(chart): 修复组合图有缺失项时导致的排序不符合预期的问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/22494114c4fe99d7a63b434a6b48f1d4ad0fd58e)
3
+
4
+ **2.1.33** fix(chart): 修复几个问题
5
+ - 2026-03-13T15:39:21+08:00 [fix(chart): 修复几个问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/8007ec69939e98bacdb694b917c73fff6f8254d2)
6
+ - 2026-03-11T16:17:35+08:00 [fix(chart): 离线分析compareTimeValue为空时补充字符。](http://lf.git.oa.mt/publish_platform/web/publish/commit/9f042730b66e8378c90400981cd86376e8b3018d)
7
+ - 2026-03-10T22:15:37+08:00 [fix(chart): 横向展示矩形图时label间距设置为30](http://lf.git.oa.mt/publish_platform/web/publish/commit/67cf0e022b39355d03cba75d3fc69d52eb12282c)
8
+ - 2026-03-10T21:41:26+08:00 [fix(chart): 组合图同一指标tooltip兼容](http://lf.git.oa.mt/publish_platform/web/publish/commit/a3894b7e59c8a33e9f1d0b487e620e085abbc281)
9
+ - 2026-03-10T21:05:02+08:00 [feat(chart): 柱状图空值tooltip展示-](http://lf.git.oa.mt/publish_platform/web/publish/commit/86baec40eb6fdff60a71571ed42adc850dd8f0ed)
10
+ - 2026-03-10T20:47:26+08:00 [feat(chart): NaN数据处理](http://lf.git.oa.mt/publish_platform/web/publish/commit/95c631436b870c525f8b1a97d365a15b255e2e18)
11
+ - 2026-03-10T20:38:08+08:00 [fix(chart): 处理label自适应宽度的临时方案](http://lf.git.oa.mt/publish_platform/web/publish/commit/563fc86358654fa2249a75d8748586029cb4e1f2)
12
+ - 2026-03-10T20:15:42+08:00 [fix(chart): X轴label最小值调整,以适配国家维度](http://lf.git.oa.mt/publish_platform/web/publish/commit/cef0e9687ccf1404a739b78655c6d322006f1373)
13
+ - 2026-03-10T17:50:32+08:00 [feat(chart): 优化辅助线文本展示](http://lf.git.oa.mt/publish_platform/web/publish/commit/9975aca40e6cb006596355c36ee3ad37a7120315)
14
+ - 2026-03-10T17:43:12+08:00 [fix(chart): timeRange判断增加null的判断](http://lf.git.oa.mt/publish_platform/web/publish/commit/8d13a0938a894afc58badf678b8d8052dde00e35)
15
+ - 2026-03-10T17:18:11+08:00 [fix(chart): ts问题修复](http://lf.git.oa.mt/publish_platform/web/publish/commit/38aee4e4f0c23d93778df8fc43ef120ed59dca45)
16
+ - 2026-03-10T17:16:05+08:00 [fix(chart): 节点颜色修复](http://lf.git.oa.mt/publish_platform/web/publish/commit/73bc2aac8443ee2c14a74f8466bb1a30a5068970)
17
+ - 2026-03-10T16:46:01+08:00 [feat(chart): 辅助线文本完全按照设计稿来](http://lf.git.oa.mt/publish_platform/web/publish/commit/92224f3f8d5a9c7f6de83d21b616c3c29859d910)
18
+ - 2026-03-10T15:40:23+08:00 [feat(chart): 图表右边margin预留30防止X轴刻度超出](http://lf.git.oa.mt/publish_platform/web/publish/commit/376e7d802ab76ec77311bc661bddfff605398e74)
19
+ - 2026-03-10T15:38:51+08:00 [fix(chart): 辅助线还是改成非render模式](http://lf.git.oa.mt/publish_platform/web/publish/commit/abdd0300801d70a00559e11f4dcd2ab11da4f617)
20
+ - 2026-03-10T15:26:29+08:00 [feat(chart): 组合图空数据tooltip展示](http://lf.git.oa.mt/publish_platform/web/publish/commit/3e19dfc8d52a3ebd26c99505207f6c03f2f5dac1)
21
+ - 2026-03-10T12:10:18+08:00 [fix(chart): 组合图降序排列时的逻辑校正](http://lf.git.oa.mt/publish_platform/web/publish/commit/b6bbc0cbecd9374a399b79508ae5ae09daaa4998)
22
+ - 2026-03-10T11:05:09+08:00 [fix(chart): 移除多余代码](http://lf.git.oa.mt/publish_platform/web/publish/commit/6b28a33b5c383b71abb689914c3698505f779501)
23
+ - 2026-03-10T11:04:22+08:00 [fix(chart): 折线图移除height对render图表的影响](http://lf.git.oa.mt/publish_platform/web/publish/commit/3d30e722de60ded1cf3e132729b5b861363e7f45)
24
+ - 2026-03-10T11:03:20+08:00 [fix(chart): tooltip使用组件自身的bounding](http://lf.git.oa.mt/publish_platform/web/publish/commit/d85690b015d5510d5fca47c3341ff3cb21503118)
25
+ - 2026-03-09T21:14:35+08:00 [feat(chart): 辅助线样式](http://lf.git.oa.mt/publish_platform/web/publish/commit/c6e5ee27606c832807da80ec785c51d2d49844de)
26
+ - 2026-03-09T20:47:53+08:00 [feat(chart): 组合图空值绘制问题&副轴取第一个指标](http://lf.git.oa.mt/publish_platform/web/publish/commit/3557796be46126c6df957078275fa27cfdc3f13c)
27
+ - 2026-03-09T15:56:21+08:00 [feat(chart): 组合图groupValue为空问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/81ffac637ccb092b0934201ce01c57be638b9eba)
28
+ - 2026-03-07T08:07:27+08:00 [fix(chart): 组合图不带分组项且在可视化看板中时不支持对比时间](http://lf.git.oa.mt/publish_platform/web/publish/commit/d78f7cc84e1166f2bd1aee2cc79d52708f878bf8)
29
+ - 2026-03-07T07:24:45+08:00 [fix(chart): 修复横向柱状图刻度空间不够的问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/b074d0457a16104d3d2f48ff8efb10303f72a3d5)
30
+ - 2026-03-07T06:59:58+08:00 [fix(chart): 移除组合图tooltip的mount配置](http://lf.git.oa.mt/publish_platform/web/publish/commit/68c7154f03802da5965b2c6ebe0e76bb307dfdb2)
31
+ - 2026-03-07T06:59:26+08:00 [fix(chart): 修复辅助线重复渲染bug](http://lf.git.oa.mt/publish_platform/web/publish/commit/ecd2001248da429c7d531f0fbf068ca5dcd1450e)
32
+ - 2026-03-07T06:57:26+08:00 [fix(chart): 柱状图启用文本隐藏](http://lf.git.oa.mt/publish_platform/web/publish/commit/80b7ef9d954d5377ebd03799e684feb7d5d4bf45)
33
+
1
34
  **2.1.20** fix(chart): TS错误去除
2
35
  - 2026-03-07T04:44:58+08:00 [fix(chart): TS错误去除](http://lf.git.oa.mt/publish_platform/web/publish/commit/d81fbe1bc27f08b5e0c6749ee1334a54a0f5202a)
3
36
  - 2026-03-07T04:43:55+08:00 [fix(chart): 堆积图height模式修改](http://lf.git.oa.mt/publish_platform/web/publish/commit/f46992872461144f696f5478c2c70d83bd162fb0)
@@ -156,10 +156,30 @@ class DataAdapter {
156
156
  return row;
157
157
  }
158
158
  });
159
- if (config.isDescend) tdv.transform({
159
+ if (config.isDescend) if (config.isGroup) {
160
+ const xKey = config.x;
161
+ const yKey = config.y;
162
+ const groupTotals = tdv.rows.reduce((acc, row)=>{
163
+ const key = String(row[xKey]);
164
+ const value = Number(row[yKey]);
165
+ const safeValue = Number.isFinite(value) ? value : 0;
166
+ acc[key] = (acc[key] ?? 0) + safeValue;
167
+ return acc;
168
+ }, {});
169
+ const groupOrder = Array.from(new Set(tdv.rows.map((row)=>String(row[xKey])))).sort((a, b)=>(groupTotals[b] ?? 0) - (groupTotals[a] ?? 0));
170
+ tdv.rows.sort((a, b)=>{
171
+ const aIndex = groupOrder.indexOf(String(a[xKey]));
172
+ const bIndex = groupOrder.indexOf(String(b[xKey]));
173
+ return aIndex - bIndex;
174
+ });
175
+ } else tdv.transform({
160
176
  type: 'sort',
161
177
  callback (a, b) {
162
- return b.total - a.total;
178
+ const aValue = Number(a[config.y]);
179
+ const bValue = Number(b[config.y]);
180
+ const safeA = Number.isFinite(aValue) ? aValue : -1 / 0;
181
+ const safeB = Number.isFinite(bValue) ? bValue : -1 / 0;
182
+ return safeB - safeA;
163
183
  }
164
184
  });
165
185
  const xKey = config.x ?? 'x';
@@ -0,0 +1,517 @@
1
+ import { DataAdapter } from "../DataAdapter.js";
2
+ jest.mock('@antv/data-set/lib', ()=>{
3
+ class MockView {
4
+ rows = [];
5
+ source(data) {
6
+ this.rows = (data || []).map((item)=>({
7
+ ...item
8
+ }));
9
+ return this;
10
+ }
11
+ transform(config) {
12
+ if (!config || !config.type) return this;
13
+ if ('map' === config.type) {
14
+ this.rows = this.rows.map((row)=>{
15
+ const cloned = {
16
+ ...row
17
+ };
18
+ const next = config.callback ? config.callback(cloned) : cloned;
19
+ return next ?? cloned;
20
+ });
21
+ return this;
22
+ }
23
+ if ('filter' === config.type) {
24
+ this.rows = this.rows.filter((row)=>config.callback ? config.callback(row) : true);
25
+ return this;
26
+ }
27
+ if ('sort' === config.type) {
28
+ this.rows = [
29
+ ...this.rows
30
+ ].sort(config.callback);
31
+ return this;
32
+ }
33
+ if ('aggregate' === config.type) {
34
+ const groupBy = config.groupBy || [];
35
+ const fields = config.fields || [];
36
+ const as = config.as || [];
37
+ const targetField = fields[0];
38
+ const outputField = as[0] || 'value';
39
+ const grouped = new Map();
40
+ this.rows.forEach((row)=>{
41
+ const key = groupBy.map((k)=>String(row[k])).join('||');
42
+ const prev = grouped.get(key);
43
+ const value = Number(row[targetField]) || 0;
44
+ if (prev) {
45
+ prev[outputField] = (Number(prev[outputField]) || 0) + value;
46
+ grouped.set(key, prev);
47
+ } else {
48
+ const base = {};
49
+ groupBy.forEach((k)=>{
50
+ base[k] = row[k];
51
+ });
52
+ base[outputField] = value;
53
+ grouped.set(key, base);
54
+ }
55
+ });
56
+ this.rows = Array.from(grouped.values());
57
+ }
58
+ return this;
59
+ }
60
+ }
61
+ class MockDataSet {
62
+ createView() {
63
+ return new MockView();
64
+ }
65
+ }
66
+ return {
67
+ __esModule: true,
68
+ default: MockDataSet
69
+ };
70
+ });
71
+ const pickFields = (rows, fields)=>rows.map((row)=>{
72
+ const next = {};
73
+ fields.forEach((field)=>{
74
+ next[field] = row[field];
75
+ });
76
+ return next;
77
+ });
78
+ const expectRequiredFields = (rows, fields)=>{
79
+ rows.forEach((row)=>{
80
+ fields.forEach((field)=>{
81
+ expect(Object.prototype.hasOwnProperty.call(row, field)).toBe(true);
82
+ });
83
+ });
84
+ };
85
+ describe('DataAdapter contract', ()=>{
86
+ describe('bar 数据契约', ()=>{
87
+ it('应稳定处理空值并保持排序与对比期拼接规则', ()=>{
88
+ const input = [
89
+ {
90
+ groupName: 'A',
91
+ groupType: 'revenue',
92
+ groupValue: '10'
93
+ },
94
+ {
95
+ groupName: 'B',
96
+ groupType: 'revenue',
97
+ groupValue: '-'
98
+ },
99
+ {
100
+ groupName: 'C',
101
+ groupType: 'revenue',
102
+ groupValue: '3'
103
+ },
104
+ {
105
+ groupName: 'A',
106
+ groupType: 'revenue_compare',
107
+ groupValue: '8'
108
+ },
109
+ {
110
+ groupName: 'B',
111
+ groupType: 'revenue_compare',
112
+ groupValue: '2'
113
+ }
114
+ ];
115
+ const rows = DataAdapter.transform(input, 'bar', {
116
+ type: 'bar',
117
+ x: 'groupName',
118
+ y: 'groupValue',
119
+ isDescend: true,
120
+ isHorizontal: false
121
+ });
122
+ expectRequiredFields(rows, [
123
+ 'groupName',
124
+ 'groupType',
125
+ 'groupValue'
126
+ ]);
127
+ const projection = pickFields(rows, [
128
+ 'groupName',
129
+ 'groupType',
130
+ 'groupValue'
131
+ ]);
132
+ expect(projection).toEqual([
133
+ {
134
+ groupName: 'A',
135
+ groupType: 'revenue',
136
+ groupValue: 10
137
+ },
138
+ {
139
+ groupName: 'C',
140
+ groupType: 'revenue',
141
+ groupValue: 3
142
+ },
143
+ {
144
+ groupName: 'B',
145
+ groupType: 'revenue',
146
+ groupValue: null
147
+ },
148
+ {
149
+ groupName: 'A',
150
+ groupType: 'revenue_compare',
151
+ groupValue: 8
152
+ },
153
+ {
154
+ groupName: 'B',
155
+ groupType: 'revenue_compare',
156
+ groupValue: 2
157
+ }
158
+ ]);
159
+ });
160
+ });
161
+ describe('line 数据契约', ()=>{
162
+ it('应按折线规则归一化数值(无效值转为 -)', ()=>{
163
+ const input = [
164
+ {
165
+ groupName: 'A',
166
+ groupType: 'ctr',
167
+ groupValue: ''
168
+ },
169
+ {
170
+ groupName: 'B',
171
+ groupType: 'ctr',
172
+ groupValue: '-'
173
+ },
174
+ {
175
+ groupName: 'C',
176
+ groupType: 'ctr',
177
+ groupValue: null
178
+ },
179
+ {
180
+ groupName: 'D',
181
+ groupType: 'ctr',
182
+ groupValue: void 0
183
+ },
184
+ {
185
+ groupName: 'E',
186
+ groupType: 'ctr',
187
+ groupValue: 'NaN'
188
+ },
189
+ {
190
+ groupName: 'F',
191
+ groupType: 'ctr',
192
+ groupValue: '5.5'
193
+ }
194
+ ];
195
+ const rows = DataAdapter.transform(input, 'line', {
196
+ type: 'line',
197
+ x: 'groupName',
198
+ y: 'groupValue'
199
+ });
200
+ expectRequiredFields(rows, [
201
+ 'groupName',
202
+ 'groupType',
203
+ 'groupValue'
204
+ ]);
205
+ expect(pickFields(rows, [
206
+ 'groupName',
207
+ 'groupValue'
208
+ ])).toEqual([
209
+ {
210
+ groupName: 'A',
211
+ groupValue: '-'
212
+ },
213
+ {
214
+ groupName: 'B',
215
+ groupValue: '-'
216
+ },
217
+ {
218
+ groupName: 'C',
219
+ groupValue: '-'
220
+ },
221
+ {
222
+ groupName: 'D',
223
+ groupValue: '-'
224
+ },
225
+ {
226
+ groupName: 'E',
227
+ groupValue: '-'
228
+ },
229
+ {
230
+ groupName: 'F',
231
+ groupValue: 5.5
232
+ }
233
+ ]);
234
+ });
235
+ });
236
+ describe('barLine 数据契约', ()=>{
237
+ it('应稳定返回统一结构与字段(transformedData/leftData/rightData/xKey)', ()=>{
238
+ const input = [
239
+ {
240
+ groupName: '2026-01-01',
241
+ groupType: 'cost',
242
+ groupValue: '10',
243
+ total: 10,
244
+ isCombine: false
245
+ },
246
+ {
247
+ groupName: '2026-01-02',
248
+ groupType: 'cost',
249
+ groupValue: '',
250
+ total: 0,
251
+ isCombine: false
252
+ },
253
+ {
254
+ groupName: '2026-01-01',
255
+ groupType: 'roi',
256
+ groupValue: '0.5',
257
+ total: 0.5,
258
+ isCombine: true
259
+ }
260
+ ];
261
+ const result = DataAdapter.transform(input, 'barLine', {
262
+ type: 'barLine',
263
+ x: 'groupName',
264
+ y: 'groupValue',
265
+ isDescend: false
266
+ });
267
+ expect(Object.keys(result).sort()).toEqual([
268
+ 'leftData',
269
+ 'rightData',
270
+ 'transformedData',
271
+ 'xKey'
272
+ ]);
273
+ expect(Array.isArray(result.transformedData)).toBe(true);
274
+ expect(Array.isArray(result.leftData)).toBe(true);
275
+ expect(Array.isArray(result.rightData)).toBe(true);
276
+ expect(Array.isArray(result.xKey)).toBe(true);
277
+ expectRequiredFields(result.transformedData, [
278
+ 'groupName',
279
+ 'groupType',
280
+ 'groupValue',
281
+ 'isCombine'
282
+ ]);
283
+ expectRequiredFields(result.leftData, [
284
+ 'groupName',
285
+ 'groupType',
286
+ 'groupValue',
287
+ 'isCombine'
288
+ ]);
289
+ expectRequiredFields(result.rightData, [
290
+ 'groupName',
291
+ 'groupType',
292
+ 'groupValue',
293
+ 'isCombine'
294
+ ]);
295
+ expect(result.leftData.every((row)=>false === row.isCombine)).toBe(true);
296
+ expect(result.rightData.every((row)=>true === row.isCombine)).toBe(true);
297
+ expect(result.xKey).toEqual([
298
+ '2026-01-01',
299
+ '2026-01-02'
300
+ ]);
301
+ expect(pickFields(result.leftData, [
302
+ 'groupName',
303
+ 'groupValue'
304
+ ])).toEqual([
305
+ {
306
+ groupName: '2026-01-01',
307
+ groupValue: 10
308
+ },
309
+ {
310
+ groupName: '2026-01-02',
311
+ groupValue: null
312
+ }
313
+ ]);
314
+ });
315
+ it('isDescend=true 时右轴数据排序应跟随左轴分类顺序,缺失分类置后', ()=>{
316
+ const input = [
317
+ {
318
+ groupName: 'A',
319
+ groupType: 'cost',
320
+ groupValue: '10',
321
+ total: 10,
322
+ isCombine: false
323
+ },
324
+ {
325
+ groupName: 'B',
326
+ groupType: 'cost',
327
+ groupValue: '20',
328
+ total: 20,
329
+ isCombine: false
330
+ },
331
+ {
332
+ groupName: 'A',
333
+ groupType: 'roi',
334
+ groupValue: '0.4',
335
+ total: 0.4,
336
+ isCombine: true
337
+ },
338
+ {
339
+ groupName: 'B',
340
+ groupType: 'roi',
341
+ groupValue: '0.8',
342
+ total: 0.8,
343
+ isCombine: true
344
+ },
345
+ {
346
+ groupName: 'C',
347
+ groupType: 'roi',
348
+ groupValue: '0.2',
349
+ total: 0.2,
350
+ isCombine: true
351
+ }
352
+ ];
353
+ const result = DataAdapter.transform(input, 'barLine', {
354
+ type: 'barLine',
355
+ x: 'groupName',
356
+ y: 'groupValue',
357
+ isDescend: true
358
+ });
359
+ expect(result.leftData.map((row)=>row.groupName)).toEqual([
360
+ 'B',
361
+ 'A'
362
+ ]);
363
+ expect(result.rightData.map((row)=>row.groupName)).toEqual([
364
+ 'B',
365
+ 'A',
366
+ 'C'
367
+ ]);
368
+ });
369
+ it('isGroup=true 且 isDescend=true 时应按分类汇总值降序排序', ()=>{
370
+ const input = [
371
+ {
372
+ groupName: 'A',
373
+ groupType: 'cost',
374
+ groupValue: '9',
375
+ isCombine: false
376
+ },
377
+ {
378
+ groupName: 'A',
379
+ groupType: 'cost2',
380
+ groupValue: '9',
381
+ isCombine: false
382
+ },
383
+ {
384
+ groupName: 'B',
385
+ groupType: 'cost',
386
+ groupValue: '10',
387
+ isCombine: false
388
+ },
389
+ {
390
+ groupName: 'B',
391
+ groupType: 'cost2',
392
+ groupValue: '1',
393
+ isCombine: false
394
+ },
395
+ {
396
+ groupName: 'A',
397
+ groupType: 'roi',
398
+ groupValue: '0.4',
399
+ isCombine: true
400
+ },
401
+ {
402
+ groupName: 'B',
403
+ groupType: 'roi',
404
+ groupValue: '0.8',
405
+ isCombine: true
406
+ },
407
+ {
408
+ groupName: 'C',
409
+ groupType: 'roi',
410
+ groupValue: '0.2',
411
+ isCombine: true
412
+ }
413
+ ];
414
+ const result = DataAdapter.transform(input, 'barLine', {
415
+ type: 'barLine',
416
+ x: 'groupName',
417
+ y: 'groupValue',
418
+ isDescend: true,
419
+ isGroup: true
420
+ });
421
+ expect(result.leftData.map((row)=>row.groupName)).toEqual([
422
+ 'A',
423
+ 'A',
424
+ 'B',
425
+ 'B'
426
+ ]);
427
+ expect(result.rightData.map((row)=>row.groupName)).toEqual([
428
+ 'A',
429
+ 'B',
430
+ 'C'
431
+ ]);
432
+ });
433
+ });
434
+ describe('groupCompare 数据契约', ()=>{
435
+ it('当前实现应保持透传(用于兼容历史逻辑)', ()=>{
436
+ const input = [
437
+ {
438
+ groupName: 'A',
439
+ groupValue: 1,
440
+ groupType: 'x'
441
+ },
442
+ {
443
+ groupName: 'B',
444
+ groupValue: 2,
445
+ groupType: 'x'
446
+ }
447
+ ];
448
+ const rows = DataAdapter.transform(input, 'groupCompare', {
449
+ type: 'groupCompare'
450
+ });
451
+ expect(rows).toEqual(input);
452
+ });
453
+ });
454
+ describe('迁移字段级 diff 基线', ()=>{
455
+ it('barLine 关键字段投影应保持稳定(迁移对比基线)', ()=>{
456
+ const input = [
457
+ {
458
+ groupName: '2026-01-01',
459
+ groupType: 'revenue',
460
+ groupValue: '100',
461
+ total: 100,
462
+ isCombine: false,
463
+ compareTime: '2025-01-01~2025-01-31',
464
+ change: 0.1,
465
+ percent: 1
466
+ },
467
+ {
468
+ groupName: '2026-01-01',
469
+ groupType: 'roi',
470
+ groupValue: '1.2',
471
+ total: 1.2,
472
+ isCombine: true,
473
+ compareTime: '2025-01-01~2025-01-31',
474
+ change: -0.2,
475
+ percent: 1
476
+ }
477
+ ];
478
+ const result = DataAdapter.transform(input, 'barLine', {
479
+ type: 'barLine',
480
+ x: 'groupName',
481
+ y: 'groupValue'
482
+ });
483
+ const projected = pickFields(result.transformedData, [
484
+ 'groupName',
485
+ 'groupType',
486
+ 'groupValue',
487
+ 'isCombine',
488
+ 'total',
489
+ 'compareTime',
490
+ 'change',
491
+ 'percent'
492
+ ]);
493
+ expect(projected).toEqual([
494
+ {
495
+ groupName: '2026-01-01',
496
+ groupType: 'revenue',
497
+ groupValue: 100,
498
+ isCombine: false,
499
+ total: 100,
500
+ compareTime: '2025-01-01~2025-01-31',
501
+ change: 0.1,
502
+ percent: 1
503
+ },
504
+ {
505
+ groupName: '2026-01-01',
506
+ groupType: 'roi',
507
+ groupValue: 1.2,
508
+ isCombine: true,
509
+ total: 1.2,
510
+ compareTime: '2025-01-01~2025-01-31',
511
+ change: -0.2,
512
+ percent: 1
513
+ }
514
+ ]);
515
+ });
516
+ });
517
+ });
@@ -78,6 +78,8 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
78
78
  const [xyList, setXYList] = useState([]);
79
79
  const [activeIds, setActiveIds] = useState([]);
80
80
  const [legendItems, setLegendItems] = useState([]);
81
+ const lastLegendDataRef = useRef(null);
82
+ const hasLegendInitializedRef = useRef(false);
81
83
  const { transformedData, leftData, rightData, xKey } = useMemo(()=>{
82
84
  if (!legend) {
83
85
  let result = transformDataToGroupBarLineFormat(data, x, y, indicators);
@@ -128,11 +130,9 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
128
130
  safeZ
129
131
  ]);
130
132
  useEffect(()=>{
131
- if (!transformedData.length) {
132
- setLegendItems([]);
133
- setActiveIds([]);
134
- return;
135
- }
133
+ if (!transformedData.length) return;
134
+ const isDataChanged = lastLegendDataRef.current !== data;
135
+ if (hasLegendInitializedRef.current && !isDataChanged) return;
136
136
  const groupTypes = Array.from(new Set(transformedData.map((d)=>d.groupType.replace("_compare", "") + "_" + d.isCombine).filter((v)=>null != v)));
137
137
  const sortedGroupTypes = sortByIndicators(groupTypes, indicators);
138
138
  const lineGroupTypeArr = sortedGroupTypes.filter((v)=>"true" === v.split("_")[1]);
@@ -151,6 +151,8 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
151
151
  };
152
152
  });
153
153
  setLegendItems(items);
154
+ lastLegendDataRef.current = data;
155
+ hasLegendInitializedRef.current = true;
154
156
  const newIds = items.map((i)=>i.id);
155
157
  setActiveIds((prev)=>{
156
158
  if (!prev.length) return newIds;
@@ -162,32 +164,63 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
162
164
  transformedData,
163
165
  safeIndicatorMap,
164
166
  themeColors,
165
- indicators
167
+ indicators,
168
+ data
166
169
  ]);
167
170
  const filterLeftData = useMemo(()=>{
168
171
  if (!activeIds.length) return leftData;
169
172
  const result = leftData.filter((d)=>d.groupType ? activeIds.includes(String(d.groupType.replace("_compare", "") + "_" + d.isCombine)) : true);
170
173
  if (!isGroupRef.current) return result;
171
174
  {
172
- const groupTypes = Array.from(new Set(result.map((d)=>d.groupType)));
175
+ const groupTypes = Array.from(new Set(result.map((d)=>String(d.groupType))));
176
+ const groupNameTotalMap = result.reduce((acc, item)=>{
177
+ const groupName = String(item.groupName);
178
+ const value = "" === item.groupValue || "-" === item.groupValue ? 0 : Number(item.groupValue) || 0;
179
+ acc[groupName] = (acc[groupName] ?? 0) + value;
180
+ return acc;
181
+ }, {});
182
+ const leftDataOrder = new Map();
183
+ leftData.forEach((item)=>{
184
+ const key = String(item.groupName);
185
+ if (!leftDataOrder.has(key)) leftDataOrder.set(key, leftDataOrder.size);
186
+ });
187
+ const orderedGroupNames = Array.from(new Set(result.map((item)=>String(item.groupName)))).sort((a, b)=>{
188
+ if (isDescend) {
189
+ const aTotal = groupNameTotalMap[a] ?? 0;
190
+ const bTotal = groupNameTotalMap[b] ?? 0;
191
+ if (aTotal !== bTotal) return bTotal - aTotal;
192
+ }
193
+ const aIndex = leftDataOrder.get(a);
194
+ const bIndex = leftDataOrder.get(b);
195
+ if (void 0 === aIndex && void 0 === bIndex) return xKey.indexOf(a) - xKey.indexOf(b);
196
+ if (void 0 === aIndex) return 1;
197
+ if (void 0 === bIndex) return -1;
198
+ return aIndex - bIndex;
199
+ });
173
200
  let groupResult = [];
174
201
  groupTypes.forEach((groupType)=>{
175
- const groupData = leftData.filter((d)=>d.groupType === groupType);
176
- xKey.forEach((key)=>{
177
- if (!result.filter((d)=>d.groupType === groupType).some((d)=>d.groupName === key)) groupData.push({
178
- groupName: key,
202
+ const groupDataMap = new Map();
203
+ leftData.filter((d)=>d.groupType === groupType).forEach((d)=>{
204
+ groupDataMap.set(String(d.groupName), {
205
+ ...d
206
+ });
207
+ });
208
+ const groupData = orderedGroupNames.map((groupName)=>{
209
+ const existed = groupDataMap.get(groupName);
210
+ if (existed) return existed;
211
+ return {
212
+ groupName,
179
213
  isCombine: false,
180
- groupType: groupType,
214
+ groupType,
181
215
  groupValue: null,
182
216
  total: 0,
183
217
  percent: ""
184
- });
218
+ };
185
219
  });
186
- if (!isDescend) groupData.sort((a, b)=>xKey.indexOf(a.groupName) - xKey.indexOf(b.groupName));
187
220
  groupResult = groupResult.concat(groupData);
188
221
  });
189
222
  groupResult.forEach((item)=>{
190
- item.total = result.filter((d)=>d.groupName === item.groupName).reduce((acc, d)=>acc + ("" === d.groupValue || "-" === d.groupValue ? 0 : d.groupValue), 0);
223
+ item.total = groupNameTotalMap[String(item.groupName)] ?? 0;
191
224
  });
192
225
  return groupResult;
193
226
  }
@@ -196,11 +229,36 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
196
229
  activeIds
197
230
  ]);
198
231
  const filterRightData = useMemo(()=>{
199
- if (!activeIds.length) return rightData;
200
- return rightData.filter((d)=>d.groupType ? activeIds.includes(String(d.groupType.replace("_compare", "") + "_" + d.isCombine)) : true);
232
+ const base = activeIds.length ? rightData.filter((d)=>d.groupType ? activeIds.includes(String(d.groupType.replace("_compare", "") + "_" + d.isCombine)) : true) : [
233
+ ...rightData
234
+ ];
235
+ const leftOrder = new Map();
236
+ filterLeftData.forEach((item)=>{
237
+ const key = String(item.groupName);
238
+ if (!leftOrder.has(key)) leftOrder.set(key, leftOrder.size);
239
+ });
240
+ const rightOriginOrder = new Map();
241
+ rightData.forEach((item)=>{
242
+ const key = String(item.groupName);
243
+ if (!rightOriginOrder.has(key)) rightOriginOrder.set(key, rightOriginOrder.size);
244
+ });
245
+ base.sort((a, b)=>{
246
+ const aKey = String(a.groupName);
247
+ const bKey = String(b.groupName);
248
+ const aLeftIndex = leftOrder.get(aKey);
249
+ const bLeftIndex = leftOrder.get(bKey);
250
+ if (void 0 !== aLeftIndex && void 0 !== bLeftIndex) return aLeftIndex - bLeftIndex;
251
+ if (void 0 !== aLeftIndex) return -1;
252
+ if (void 0 !== bLeftIndex) return 1;
253
+ const aOrigin = rightOriginOrder.get(aKey) ?? Number.MAX_SAFE_INTEGER;
254
+ const bOrigin = rightOriginOrder.get(bKey) ?? Number.MAX_SAFE_INTEGER;
255
+ return aOrigin - bOrigin;
256
+ });
257
+ return base;
201
258
  }, [
202
259
  rightData,
203
- activeIds
260
+ activeIds,
261
+ filterLeftData
204
262
  ]);
205
263
  const maxLeft = useMemo(()=>filterLeftData.reduce((max, item)=>{
206
264
  const key = isGroupRef.current ? "total" : safeY;
@@ -260,7 +260,9 @@ function applyNodeData(view, nodeData, x, y) {
260
260
  function adjustDomainMax(domainMin, domainMax) {
261
261
  return domainMax === domainMin ? domainMin > 0 ? 1.1 * domainMax : domainMax + 1000 : domainMax;
262
262
  }
263
- const sortByIndicators = (arr, indicators)=>arr.sort((a, b)=>{
263
+ const sortByIndicators = (arr, indicators)=>{
264
+ const hasDuplicateIndicators = new Set(indicators).size !== indicators.length;
265
+ if (!hasDuplicateIndicators) return arr.sort((a, b)=>{
264
266
  const baseA = a.split('_')[0];
265
267
  const baseB = b.split('_')[0];
266
268
  const indexA = indicators.indexOf(baseA);
@@ -268,6 +270,44 @@ const sortByIndicators = (arr, indicators)=>arr.sort((a, b)=>{
268
270
  if (-1 !== indexA && -1 !== indexB) return indexA - indexB;
269
271
  return 0;
270
272
  });
273
+ const indicatorPositions = new Map();
274
+ indicators.forEach((indicatorId, index)=>{
275
+ const key = String(indicatorId);
276
+ if (!indicatorPositions.has(key)) indicatorPositions.set(key, []);
277
+ indicatorPositions.get(key).push(index);
278
+ });
279
+ const usedCount = new Map();
280
+ const getTypeRank = (value)=>{
281
+ if (value.endsWith('_false')) return 0;
282
+ if (value.endsWith('_true')) return 1;
283
+ return 2;
284
+ };
285
+ const decorated = arr.map((value, originalIndex)=>{
286
+ const base = value.split('_')[0];
287
+ const positions = indicatorPositions.get(base) ?? [];
288
+ const used = usedCount.get(base) ?? 0;
289
+ usedCount.set(base, used + 1);
290
+ const isIndicatorBase = positions.length > 0;
291
+ return {
292
+ value,
293
+ base,
294
+ originalIndex,
295
+ typeRank: getTypeRank(value),
296
+ isIndicatorBase,
297
+ orderIndex: used < positions.length ? positions[used] : Number.MAX_SAFE_INTEGER
298
+ };
299
+ });
300
+ decorated.sort((a, b)=>{
301
+ if (a.typeRank !== b.typeRank) return a.typeRank - b.typeRank;
302
+ if (0 === a.typeRank) {
303
+ if (!a.isIndicatorBase || !b.isIndicatorBase) return a.originalIndex - b.originalIndex;
304
+ }
305
+ if (a.orderIndex !== b.orderIndex) return a.orderIndex - b.orderIndex;
306
+ return a.originalIndex - b.originalIndex;
307
+ });
308
+ arr.splice(0, arr.length, ...decorated.map((item)=>item.value));
309
+ return arr;
310
+ };
271
311
  function intervalSort(a, b) {
272
312
  const itemRank = (item)=>{
273
313
  if (item.isChange) return 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@publishfx/publish-chart",
3
- "version": "2.1.32",
3
+ "version": "2.1.34",
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": [