Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit e441972

Browse files
committedDec 30, 2024
feat: add handle of fucntion and series
1 parent c52891a commit e441972

File tree

3 files changed

+240
-17
lines changed

3 files changed

+240
-17
lines changed
 

‎packages/vmind/__tests__/unit/vchartSpec/bar.test.ts

+95
Original file line numberDiff line numberDiff line change
@@ -200,4 +200,99 @@ describe('mergeAppendSpec of barchart', () => {
200200
}
201201
]);
202202
});
203+
204+
it('should parse complicated path when `parentKeyPath` > spec ', () => {
205+
const append = {
206+
leafSpec: {
207+
grid: {
208+
style: {
209+
strokeOpacity: 1
210+
}
211+
}
212+
},
213+
parentKeyPath: 'axes[0].grid.style'
214+
};
215+
216+
const { newSpec } = mergeAppendSpec(merge({}, spec), append);
217+
218+
expect(newSpec.axes).toEqual([
219+
{
220+
grid: {
221+
style: {
222+
strokeOpacity: 1
223+
}
224+
}
225+
}
226+
]);
227+
228+
const { newSpec: newSpec2 } = mergeAppendSpec(merge({}, newSpec), {
229+
aliasKeyPath: 'xAxis',
230+
parentKeyPath: 'axes',
231+
leafSpec: {
232+
axes: [
233+
{
234+
title: {
235+
text: '城市'
236+
}
237+
}
238+
]
239+
}
240+
});
241+
242+
expect(newSpec2.axes).toEqual([
243+
{
244+
grid: {
245+
style: {
246+
strokeOpacity: 1
247+
}
248+
}
249+
},
250+
{
251+
title: {
252+
text: '城市'
253+
},
254+
_alias_name: 'xAxis',
255+
orient: 'bottom'
256+
}
257+
]);
258+
});
259+
260+
it('should handle function', () => {
261+
const append = {
262+
aliasKeyPath: 'xAxis.label.style.fill',
263+
leafSpec: {
264+
bar: {
265+
style: {
266+
fill: "(datum, index) => index === 0 ? 'red' : null"
267+
}
268+
}
269+
},
270+
parentKeyPath: 'bar'
271+
};
272+
273+
const { newSpec } = mergeAppendSpec(merge({}, spec), append);
274+
275+
expect(newSpec.bar.style.fill).toBeDefined();
276+
expect(newSpec.bar.style.fill('test', 0)).toBe('red');
277+
expect(newSpec.bar.style.fill('test', 1)).toBeNull();
278+
});
279+
280+
it('should not not add series when has only one series', () => {
281+
const append = {
282+
leafSpec: {
283+
'series[0].extensionMark[0].style.size': 10
284+
},
285+
parentKeyPath: 'series[0].extensionMark[0].style.size',
286+
aliasKeyPath: 'bar.extensionMark[0].style.size'
287+
};
288+
289+
const { newSpec } = mergeAppendSpec(merge({}, spec), append);
290+
expect(newSpec.extensionMark).toEqual([
291+
{
292+
style: {
293+
size: 10
294+
}
295+
}
296+
]);
297+
});
203298
});

‎packages/vmind/__tests__/unit/vchartSpec/util.test.ts

+70-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,17 @@
1-
import { checkDuplicatedKey } from '../../../src/atom/VChartSpec/utils';
1+
import { checkDuplicatedKey, convertFunctionString } from '../../../src/atom/VChartSpec/utils';
22

33
describe('checkDuplicatedKey', () => {
44
it('should return null when not match', () => {
55
expect(checkDuplicatedKey('a.b.c', 'd')).toBeNull();
6-
expect(checkDuplicatedKey('a.b.c', 'b')).toBeNull();
7-
8-
expect(checkDuplicatedKey('[0].a', 'a')).toBeNull();
96
});
107

118
it('should return correct result', () => {
9+
expect(checkDuplicatedKey('[0].a', 'a')).toEqual({
10+
remainKeyPath: ''
11+
});
12+
expect(checkDuplicatedKey('a.b.c', 'b')).toEqual({
13+
remainKeyPath: 'c'
14+
});
1215
expect(checkDuplicatedKey('[0].a', '0')).toEqual({
1316
remainKeyPath: 'a'
1417
});
@@ -29,3 +32,66 @@ describe('checkDuplicatedKey', () => {
2932
});
3033
});
3134
});
35+
36+
describe('convertFunctionString', () => {
37+
it('should convert arrow function string to function', () => {
38+
const func1 = convertFunctionString('(datum, index) => index === 0? "red" : null');
39+
expect(func1({}, 0)).toBe('red');
40+
expect(func1({}, 1)).toBeNull();
41+
});
42+
43+
it('should convert function string to function', () => {
44+
const func1 = convertFunctionString('function(datum, index) { return index === 0? "red" : null }');
45+
expect(func1({}, 0)).toBe('red');
46+
expect(func1({}, 1)).toBeNull();
47+
});
48+
49+
it('should not convert normal string', () => {
50+
const str1 = convertFunctionString('a');
51+
expect(str1).toBe('a');
52+
});
53+
54+
it('should not convert normal object', () => {
55+
const obj1 = convertFunctionString({ a: 1 });
56+
expect(obj1).toEqual({ a: 1 });
57+
});
58+
59+
it('should not convert normal array', () => {
60+
const arr1 = convertFunctionString([1, 2, 3]);
61+
expect(arr1).toEqual([1, 2, 3]);
62+
});
63+
64+
it('should not convert normal number', () => {
65+
const num1 = convertFunctionString(1);
66+
expect(num1).toBe(1);
67+
});
68+
69+
it('should not convert normal boolean', () => {
70+
const bool1 = convertFunctionString(true);
71+
expect(bool1).toBe(true);
72+
});
73+
74+
it('should not convert normal null', () => {
75+
const null1 = convertFunctionString(null);
76+
expect(null1).toBeNull();
77+
});
78+
it('should not convert normal undefined', () => {
79+
const undefined1 = convertFunctionString(undefined);
80+
expect(undefined1).toBeUndefined();
81+
});
82+
it('should not convert normal function', () => {
83+
const func1 = convertFunctionString(() => {
84+
return 1;
85+
});
86+
expect(func1).toBeInstanceOf(Function);
87+
});
88+
it('should not convert normal arrow function', () => {
89+
const func1 = convertFunctionString(() => 1);
90+
expect(func1).toBeInstanceOf(Function);
91+
});
92+
it('should not convert normal object with function', () => {
93+
const spec = { a: () => 1 };
94+
const obj1 = convertFunctionString(spec);
95+
expect(obj1.a).toEqual(spec.a);
96+
});
97+
});

‎packages/vmind/src/atom/VChartSpec/utils.ts

+75-13
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { isArray, isNil, isPlainObject, merge } from '@visactor/vutils';
1+
import { isArray, isNil, isObject, isPlainObject, isString, isValid, merge } from '@visactor/vutils';
22
import type { AppendSpecInfo } from '../../types/atom';
33
import { set } from '../../utils/set';
44

@@ -288,13 +288,26 @@ export const checkDuplicatedKey = (parentPath: string, key: string) => {
288288
}
289289
}
290290

291-
if (parentPath.startsWith(`${key}.`)) {
291+
if (parentPath.startsWith(`${key}`)) {
292+
let remainKeyPath = parentPath.substring(key.length);
293+
294+
if (remainKeyPath[0] === '.') {
295+
remainKeyPath = remainKeyPath.substring(1);
296+
}
297+
292298
return {
293-
remainKeyPath: parentPath.substring(key.length + 1)
299+
remainKeyPath
294300
};
295-
} else if (parentPath.startsWith(`${key}[`)) {
301+
} else if (parentPath.includes(`.${key}`)) {
302+
const str = `.${key}`;
303+
let remainKeyPath = parentPath.substring(parentPath.indexOf(str) + str.length);
304+
305+
if (remainKeyPath[0] === '.') {
306+
remainKeyPath = remainKeyPath.substring(1);
307+
}
308+
296309
return {
297-
remainKeyPath: parentPath.substring(key.length)
310+
remainKeyPath
298311
};
299312
}
300313

@@ -321,35 +334,84 @@ export const reduceDuplicatedPath = (parentPath: string, spec: any): any => {
321334
} else if (isArray(spec) && parentPath) {
322335
const res = /^\[((\d)+)\]/.exec(parentPath);
323336

324-
if (res && +res[1] < spec.length) {
337+
if (res) {
325338
const remainPath = parentPath.substring(res[0].length + 1);
339+
const val = spec[+res[1]] ?? spec[spec.length - 1];
326340

327-
return remainPath ? reduceDuplicatedPath(remainPath, spec[+res[1]]) : spec[+res[1]];
341+
return remainPath ? reduceDuplicatedPath(remainPath, val) : val;
342+
}
343+
}
344+
345+
return spec;
346+
};
347+
348+
/**
349+
* 将大模型返回的spec中的函数字符串转换成函数
350+
* @param spec 转换后的spec
351+
* @returns
352+
*/
353+
export const convertFunctionString = (spec: any): any => {
354+
if (isPlainObject(spec)) {
355+
const newSpec: any = {};
356+
357+
Object.keys(spec).forEach(key => {
358+
newSpec[key] = convertFunctionString((spec as any)[key]);
359+
});
360+
361+
return newSpec;
362+
} else if (isArray(spec)) {
363+
return spec.map(convertFunctionString);
364+
}
365+
366+
if (isString(spec)) {
367+
if (spec.includes('=>') || spec.includes('function')) {
368+
try {
369+
// 将函数自浮窗转换成可执行的函数
370+
return new Function(`return (${spec})`)();
371+
} catch (e) {
372+
return spec;
373+
}
328374
}
329375
}
330376

331377
return spec;
332378
};
333379

334380
export const mergeAppendSpec = (prevSpec: any, appendSpec: AppendSpecInfo) => {
335-
const { leafSpec, parentKeyPath = '', aliasKeyPath = '' } = appendSpec;
381+
const { aliasKeyPath = '' } = appendSpec;
382+
let { parentKeyPath = '', leafSpec } = appendSpec;
336383
let newSpec = merge({}, prevSpec);
337384

338385
if (parentKeyPath) {
339-
const aliasResult = parseRealPath(parentKeyPath, aliasKeyPath, newSpec);
386+
let aliasResult = parseRealPath(parentKeyPath, aliasKeyPath, newSpec);
340387

341388
if (aliasResult.appendSpec && aliasResult.appendPath) {
342-
set(newSpec, aliasResult.appendPath, aliasResult.appendSpec);
389+
if (aliasResult.appendPath.includes('series') && !newSpec.series) {
390+
// 系列比较特殊,默认是打平在第一层的
391+
leafSpec = leafSpec.series
392+
? isArray(leafSpec.series)
393+
? leafSpec.series[0]
394+
: leafSpec.series
395+
: parentKeyPath in leafSpec
396+
? leafSpec[parentKeyPath]
397+
: leafSpec;
398+
parentKeyPath = parentKeyPath.slice(parentKeyPath.indexOf('.') + 1);
399+
aliasResult = { path: parentKeyPath };
400+
} else {
401+
set(newSpec, aliasResult.appendPath, aliasResult.appendSpec);
402+
}
343403
}
344404

345405
const finalParentKeyPath = aliasResult.path ?? parentKeyPath;
346406

347407
set(
348408
newSpec,
349409
finalParentKeyPath,
350-
reduceDuplicatedPath(
351-
finalParentKeyPath,
352-
aliasResult.aliasName ? reduceDuplicatedPath(aliasResult.aliasName, leafSpec) : leafSpec
410+
convertFunctionString(
411+
reduceDuplicatedPath(
412+
finalParentKeyPath,
413+
aliasResult.aliasName ? reduceDuplicatedPath(aliasResult.aliasName, leafSpec) : leafSpec
414+
)
353415
)
354416
);
355417
} else {

0 commit comments

Comments
 (0)
Please sign in to comment.