@publishfx/publish-chart 2.1.31 → 2.1.33
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 +30 -0
- package/dist/adapters/DataAdapter.js +22 -2
- package/dist/adapters/__tests__/DataAdapter.contract.test.d.ts +1 -0
- package/dist/adapters/__tests__/DataAdapter.contract.test.js +517 -0
- package/dist/components/g2/base/G2CombineChart.js +65 -22
- package/dist/components/g2/base/g2Helpers.js +41 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
**2.1.33** fix(chart): 修复几个问题
|
|
2
|
+
- 2026-03-13T15:39:21+08:00 [fix(chart): 修复几个问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/8007ec69939e98bacdb694b917c73fff6f8254d2)
|
|
3
|
+
- 2026-03-11T16:17:35+08:00 [fix(chart): 离线分析compareTimeValue为空时补充字符。](http://lf.git.oa.mt/publish_platform/web/publish/commit/9f042730b66e8378c90400981cd86376e8b3018d)
|
|
4
|
+
- 2026-03-10T22:15:37+08:00 [fix(chart): 横向展示矩形图时label间距设置为30](http://lf.git.oa.mt/publish_platform/web/publish/commit/67cf0e022b39355d03cba75d3fc69d52eb12282c)
|
|
5
|
+
- 2026-03-10T21:41:26+08:00 [fix(chart): 组合图同一指标tooltip兼容](http://lf.git.oa.mt/publish_platform/web/publish/commit/a3894b7e59c8a33e9f1d0b487e620e085abbc281)
|
|
6
|
+
- 2026-03-10T21:05:02+08:00 [feat(chart): 柱状图空值tooltip展示-](http://lf.git.oa.mt/publish_platform/web/publish/commit/86baec40eb6fdff60a71571ed42adc850dd8f0ed)
|
|
7
|
+
- 2026-03-10T20:47:26+08:00 [feat(chart): NaN数据处理](http://lf.git.oa.mt/publish_platform/web/publish/commit/95c631436b870c525f8b1a97d365a15b255e2e18)
|
|
8
|
+
- 2026-03-10T20:38:08+08:00 [fix(chart): 处理label自适应宽度的临时方案](http://lf.git.oa.mt/publish_platform/web/publish/commit/563fc86358654fa2249a75d8748586029cb4e1f2)
|
|
9
|
+
- 2026-03-10T20:15:42+08:00 [fix(chart): X轴label最小值调整,以适配国家维度](http://lf.git.oa.mt/publish_platform/web/publish/commit/cef0e9687ccf1404a739b78655c6d322006f1373)
|
|
10
|
+
- 2026-03-10T17:50:32+08:00 [feat(chart): 优化辅助线文本展示](http://lf.git.oa.mt/publish_platform/web/publish/commit/9975aca40e6cb006596355c36ee3ad37a7120315)
|
|
11
|
+
- 2026-03-10T17:43:12+08:00 [fix(chart): timeRange判断增加null的判断](http://lf.git.oa.mt/publish_platform/web/publish/commit/8d13a0938a894afc58badf678b8d8052dde00e35)
|
|
12
|
+
- 2026-03-10T17:18:11+08:00 [fix(chart): ts问题修复](http://lf.git.oa.mt/publish_platform/web/publish/commit/38aee4e4f0c23d93778df8fc43ef120ed59dca45)
|
|
13
|
+
- 2026-03-10T17:16:05+08:00 [fix(chart): 节点颜色修复](http://lf.git.oa.mt/publish_platform/web/publish/commit/73bc2aac8443ee2c14a74f8466bb1a30a5068970)
|
|
14
|
+
- 2026-03-10T16:46:01+08:00 [feat(chart): 辅助线文本完全按照设计稿来](http://lf.git.oa.mt/publish_platform/web/publish/commit/92224f3f8d5a9c7f6de83d21b616c3c29859d910)
|
|
15
|
+
- 2026-03-10T15:40:23+08:00 [feat(chart): 图表右边margin预留30防止X轴刻度超出](http://lf.git.oa.mt/publish_platform/web/publish/commit/376e7d802ab76ec77311bc661bddfff605398e74)
|
|
16
|
+
- 2026-03-10T15:38:51+08:00 [fix(chart): 辅助线还是改成非render模式](http://lf.git.oa.mt/publish_platform/web/publish/commit/abdd0300801d70a00559e11f4dcd2ab11da4f617)
|
|
17
|
+
- 2026-03-10T15:26:29+08:00 [feat(chart): 组合图空数据tooltip展示](http://lf.git.oa.mt/publish_platform/web/publish/commit/3e19dfc8d52a3ebd26c99505207f6c03f2f5dac1)
|
|
18
|
+
- 2026-03-10T12:10:18+08:00 [fix(chart): 组合图降序排列时的逻辑校正](http://lf.git.oa.mt/publish_platform/web/publish/commit/b6bbc0cbecd9374a399b79508ae5ae09daaa4998)
|
|
19
|
+
- 2026-03-10T11:05:09+08:00 [fix(chart): 移除多余代码](http://lf.git.oa.mt/publish_platform/web/publish/commit/6b28a33b5c383b71abb689914c3698505f779501)
|
|
20
|
+
- 2026-03-10T11:04:22+08:00 [fix(chart): 折线图移除height对render图表的影响](http://lf.git.oa.mt/publish_platform/web/publish/commit/3d30e722de60ded1cf3e132729b5b861363e7f45)
|
|
21
|
+
- 2026-03-10T11:03:20+08:00 [fix(chart): tooltip使用组件自身的bounding](http://lf.git.oa.mt/publish_platform/web/publish/commit/d85690b015d5510d5fca47c3341ff3cb21503118)
|
|
22
|
+
- 2026-03-09T21:14:35+08:00 [feat(chart): 辅助线样式](http://lf.git.oa.mt/publish_platform/web/publish/commit/c6e5ee27606c832807da80ec785c51d2d49844de)
|
|
23
|
+
- 2026-03-09T20:47:53+08:00 [feat(chart): 组合图空值绘制问题&副轴取第一个指标](http://lf.git.oa.mt/publish_platform/web/publish/commit/3557796be46126c6df957078275fa27cfdc3f13c)
|
|
24
|
+
- 2026-03-09T15:56:21+08:00 [feat(chart): 组合图groupValue为空问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/81ffac637ccb092b0934201ce01c57be638b9eba)
|
|
25
|
+
- 2026-03-07T08:07:27+08:00 [fix(chart): 组合图不带分组项且在可视化看板中时不支持对比时间](http://lf.git.oa.mt/publish_platform/web/publish/commit/d78f7cc84e1166f2bd1aee2cc79d52708f878bf8)
|
|
26
|
+
- 2026-03-07T07:24:45+08:00 [fix(chart): 修复横向柱状图刻度空间不够的问题](http://lf.git.oa.mt/publish_platform/web/publish/commit/b074d0457a16104d3d2f48ff8efb10303f72a3d5)
|
|
27
|
+
- 2026-03-07T06:59:58+08:00 [fix(chart): 移除组合图tooltip的mount配置](http://lf.git.oa.mt/publish_platform/web/publish/commit/68c7154f03802da5965b2c6ebe0e76bb307dfdb2)
|
|
28
|
+
- 2026-03-07T06:59:26+08:00 [fix(chart): 修复辅助线重复渲染bug](http://lf.git.oa.mt/publish_platform/web/publish/commit/ecd2001248da429c7d531f0fbf068ca5dcd1450e)
|
|
29
|
+
- 2026-03-07T06:57:26+08:00 [fix(chart): 柱状图启用文本隐藏](http://lf.git.oa.mt/publish_platform/web/publish/commit/80b7ef9d954d5377ebd03799e684feb7d5d4bf45)
|
|
30
|
+
|
|
1
31
|
**2.1.20** fix(chart): TS错误去除
|
|
2
32
|
- 2026-03-07T04:44:58+08:00 [fix(chart): TS错误去除](http://lf.git.oa.mt/publish_platform/web/publish/commit/d81fbe1bc27f08b5e0c6749ee1334a54a0f5202a)
|
|
3
33
|
- 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)
|
|
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
|
-
|
|
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 @@
|
|
|
1
|
+
export {};
|
|
@@ -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
|
+
});
|
|
@@ -47,7 +47,7 @@ const transformDataToGroupBarLineFormat = (data, x = "groupName", y = "", indica
|
|
|
47
47
|
isCombine: isCombineFlag,
|
|
48
48
|
total: compareValue,
|
|
49
49
|
change: changeValue,
|
|
50
|
-
compareTime: compareTimeValue
|
|
50
|
+
compareTime: compareTimeValue || '对比时间'
|
|
51
51
|
});
|
|
52
52
|
return rows;
|
|
53
53
|
});
|
|
@@ -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
|
-
|
|
133
|
-
|
|
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,7 +164,8 @@ 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;
|
|
@@ -170,24 +173,39 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
|
|
|
170
173
|
if (!isGroupRef.current) return result;
|
|
171
174
|
{
|
|
172
175
|
const groupTypes = Array.from(new Set(result.map((d)=>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
|
+
});
|
|
173
187
|
let groupResult = [];
|
|
174
188
|
groupTypes.forEach((groupType)=>{
|
|
175
|
-
const groupData = leftData.filter((d)=>d.groupType === groupType)
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
189
|
+
const groupData = leftData.filter((d)=>d.groupType === groupType).map((d)=>({
|
|
190
|
+
...d
|
|
191
|
+
}));
|
|
192
|
+
groupData.sort((a, b)=>{
|
|
193
|
+
if (isDescend) {
|
|
194
|
+
const aTotal = groupNameTotalMap[String(a.groupName)] ?? 0;
|
|
195
|
+
const bTotal = groupNameTotalMap[String(b.groupName)] ?? 0;
|
|
196
|
+
if (aTotal !== bTotal) return bTotal - aTotal;
|
|
197
|
+
}
|
|
198
|
+
const aIndex = leftDataOrder.get(String(a.groupName));
|
|
199
|
+
const bIndex = leftDataOrder.get(String(b.groupName));
|
|
200
|
+
if (void 0 === aIndex && void 0 === bIndex) return xKey.indexOf(a.groupName) - xKey.indexOf(b.groupName);
|
|
201
|
+
if (void 0 === aIndex) return 1;
|
|
202
|
+
if (void 0 === bIndex) return -1;
|
|
203
|
+
return aIndex - bIndex;
|
|
185
204
|
});
|
|
186
|
-
if (!isDescend) groupData.sort((a, b)=>xKey.indexOf(a.groupName) - xKey.indexOf(b.groupName));
|
|
187
205
|
groupResult = groupResult.concat(groupData);
|
|
188
206
|
});
|
|
189
207
|
groupResult.forEach((item)=>{
|
|
190
|
-
item.total =
|
|
208
|
+
item.total = groupNameTotalMap[String(item.groupName)] ?? 0;
|
|
191
209
|
});
|
|
192
210
|
return groupResult;
|
|
193
211
|
}
|
|
@@ -196,11 +214,36 @@ const G2CombineChart = ({ height = 300, data, x = "", y = "", z = "", indicatorM
|
|
|
196
214
|
activeIds
|
|
197
215
|
]);
|
|
198
216
|
const filterRightData = useMemo(()=>{
|
|
199
|
-
|
|
200
|
-
|
|
217
|
+
const base = activeIds.length ? rightData.filter((d)=>d.groupType ? activeIds.includes(String(d.groupType.replace("_compare", "") + "_" + d.isCombine)) : true) : [
|
|
218
|
+
...rightData
|
|
219
|
+
];
|
|
220
|
+
const leftOrder = new Map();
|
|
221
|
+
filterLeftData.forEach((item)=>{
|
|
222
|
+
const key = String(item.groupName);
|
|
223
|
+
if (!leftOrder.has(key)) leftOrder.set(key, leftOrder.size);
|
|
224
|
+
});
|
|
225
|
+
const rightOriginOrder = new Map();
|
|
226
|
+
rightData.forEach((item)=>{
|
|
227
|
+
const key = String(item.groupName);
|
|
228
|
+
if (!rightOriginOrder.has(key)) rightOriginOrder.set(key, rightOriginOrder.size);
|
|
229
|
+
});
|
|
230
|
+
base.sort((a, b)=>{
|
|
231
|
+
const aKey = String(a.groupName);
|
|
232
|
+
const bKey = String(b.groupName);
|
|
233
|
+
const aLeftIndex = leftOrder.get(aKey);
|
|
234
|
+
const bLeftIndex = leftOrder.get(bKey);
|
|
235
|
+
if (void 0 !== aLeftIndex && void 0 !== bLeftIndex) return aLeftIndex - bLeftIndex;
|
|
236
|
+
if (void 0 !== aLeftIndex) return -1;
|
|
237
|
+
if (void 0 !== bLeftIndex) return 1;
|
|
238
|
+
const aOrigin = rightOriginOrder.get(aKey) ?? Number.MAX_SAFE_INTEGER;
|
|
239
|
+
const bOrigin = rightOriginOrder.get(bKey) ?? Number.MAX_SAFE_INTEGER;
|
|
240
|
+
return aOrigin - bOrigin;
|
|
241
|
+
});
|
|
242
|
+
return base;
|
|
201
243
|
}, [
|
|
202
244
|
rightData,
|
|
203
|
-
activeIds
|
|
245
|
+
activeIds,
|
|
246
|
+
filterLeftData
|
|
204
247
|
]);
|
|
205
248
|
const maxLeft = useMemo(()=>filterLeftData.reduce((max, item)=>{
|
|
206
249
|
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)=>
|
|
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.
|
|
3
|
+
"version": "2.1.33",
|
|
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": [
|