Skip to content

Commit

Permalink
feat: add handle of fucntion and series
Browse files Browse the repository at this point in the history
  • Loading branch information
xile611 committed Dec 30, 2024
1 parent c52891a commit e441972
Show file tree
Hide file tree
Showing 3 changed files with 240 additions and 17 deletions.
95 changes: 95 additions & 0 deletions packages/vmind/__tests__/unit/vchartSpec/bar.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -200,4 +200,99 @@ describe('mergeAppendSpec of barchart', () => {
}
]);
});

it('should parse complicated path when `parentKeyPath` > spec ', () => {
const append = {
leafSpec: {
grid: {
style: {
strokeOpacity: 1
}
}
},
parentKeyPath: 'axes[0].grid.style'
};

const { newSpec } = mergeAppendSpec(merge({}, spec), append);

expect(newSpec.axes).toEqual([
{
grid: {
style: {
strokeOpacity: 1
}
}
}
]);

const { newSpec: newSpec2 } = mergeAppendSpec(merge({}, newSpec), {
aliasKeyPath: 'xAxis',
parentKeyPath: 'axes',
leafSpec: {
axes: [
{
title: {
text: '城市'
}
}
]
}
});

expect(newSpec2.axes).toEqual([
{
grid: {
style: {
strokeOpacity: 1
}
}
},
{
title: {
text: '城市'
},
_alias_name: 'xAxis',
orient: 'bottom'
}
]);
});

it('should handle function', () => {
const append = {
aliasKeyPath: 'xAxis.label.style.fill',
leafSpec: {
bar: {
style: {
fill: "(datum, index) => index === 0 ? 'red' : null"
}
}
},
parentKeyPath: 'bar'
};

const { newSpec } = mergeAppendSpec(merge({}, spec), append);

expect(newSpec.bar.style.fill).toBeDefined();
expect(newSpec.bar.style.fill('test', 0)).toBe('red');
expect(newSpec.bar.style.fill('test', 1)).toBeNull();
});

it('should not not add series when has only one series', () => {
const append = {
leafSpec: {
'series[0].extensionMark[0].style.size': 10
},
parentKeyPath: 'series[0].extensionMark[0].style.size',
aliasKeyPath: 'bar.extensionMark[0].style.size'
};

const { newSpec } = mergeAppendSpec(merge({}, spec), append);
expect(newSpec.extensionMark).toEqual([
{
style: {
size: 10
}
}
]);
});
});
74 changes: 70 additions & 4 deletions packages/vmind/__tests__/unit/vchartSpec/util.test.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { checkDuplicatedKey } from '../../../src/atom/VChartSpec/utils';
import { checkDuplicatedKey, convertFunctionString } from '../../../src/atom/VChartSpec/utils';

describe('checkDuplicatedKey', () => {
it('should return null when not match', () => {
expect(checkDuplicatedKey('a.b.c', 'd')).toBeNull();
expect(checkDuplicatedKey('a.b.c', 'b')).toBeNull();

expect(checkDuplicatedKey('[0].a', 'a')).toBeNull();
});

it('should return correct result', () => {
expect(checkDuplicatedKey('[0].a', 'a')).toEqual({
remainKeyPath: ''
});
expect(checkDuplicatedKey('a.b.c', 'b')).toEqual({
remainKeyPath: 'c'
});
expect(checkDuplicatedKey('[0].a', '0')).toEqual({
remainKeyPath: 'a'
});
Expand All @@ -29,3 +32,66 @@ describe('checkDuplicatedKey', () => {
});
});
});

describe('convertFunctionString', () => {
it('should convert arrow function string to function', () => {
const func1 = convertFunctionString('(datum, index) => index === 0? "red" : null');
expect(func1({}, 0)).toBe('red');
expect(func1({}, 1)).toBeNull();
});

it('should convert function string to function', () => {
const func1 = convertFunctionString('function(datum, index) { return index === 0? "red" : null }');
expect(func1({}, 0)).toBe('red');
expect(func1({}, 1)).toBeNull();
});

it('should not convert normal string', () => {
const str1 = convertFunctionString('a');
expect(str1).toBe('a');
});

it('should not convert normal object', () => {
const obj1 = convertFunctionString({ a: 1 });
expect(obj1).toEqual({ a: 1 });
});

it('should not convert normal array', () => {
const arr1 = convertFunctionString([1, 2, 3]);
expect(arr1).toEqual([1, 2, 3]);
});

it('should not convert normal number', () => {
const num1 = convertFunctionString(1);
expect(num1).toBe(1);
});

it('should not convert normal boolean', () => {
const bool1 = convertFunctionString(true);
expect(bool1).toBe(true);
});

it('should not convert normal null', () => {
const null1 = convertFunctionString(null);
expect(null1).toBeNull();
});
it('should not convert normal undefined', () => {
const undefined1 = convertFunctionString(undefined);
expect(undefined1).toBeUndefined();
});
it('should not convert normal function', () => {
const func1 = convertFunctionString(() => {
return 1;
});
expect(func1).toBeInstanceOf(Function);
});
it('should not convert normal arrow function', () => {
const func1 = convertFunctionString(() => 1);
expect(func1).toBeInstanceOf(Function);
});
it('should not convert normal object with function', () => {
const spec = { a: () => 1 };
const obj1 = convertFunctionString(spec);
expect(obj1.a).toEqual(spec.a);
});
});
88 changes: 75 additions & 13 deletions packages/vmind/src/atom/VChartSpec/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isArray, isNil, isPlainObject, merge } from '@visactor/vutils';
import { isArray, isNil, isObject, isPlainObject, isString, isValid, merge } from '@visactor/vutils';
import type { AppendSpecInfo } from '../../types/atom';
import { set } from '../../utils/set';

Expand Down Expand Up @@ -288,13 +288,26 @@ export const checkDuplicatedKey = (parentPath: string, key: string) => {
}
}

if (parentPath.startsWith(`${key}.`)) {
if (parentPath.startsWith(`${key}`)) {
let remainKeyPath = parentPath.substring(key.length);

if (remainKeyPath[0] === '.') {
remainKeyPath = remainKeyPath.substring(1);
}

return {
remainKeyPath: parentPath.substring(key.length + 1)
remainKeyPath
};
} else if (parentPath.startsWith(`${key}[`)) {
} else if (parentPath.includes(`.${key}`)) {
const str = `.${key}`;
let remainKeyPath = parentPath.substring(parentPath.indexOf(str) + str.length);

if (remainKeyPath[0] === '.') {
remainKeyPath = remainKeyPath.substring(1);
}

return {
remainKeyPath: parentPath.substring(key.length)
remainKeyPath
};
}

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

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

return remainPath ? reduceDuplicatedPath(remainPath, spec[+res[1]]) : spec[+res[1]];
return remainPath ? reduceDuplicatedPath(remainPath, val) : val;
}
}

return spec;
};

/**
* 将大模型返回的spec中的函数字符串转换成函数
* @param spec 转换后的spec
* @returns
*/
export const convertFunctionString = (spec: any): any => {
if (isPlainObject(spec)) {
const newSpec: any = {};

Object.keys(spec).forEach(key => {
newSpec[key] = convertFunctionString((spec as any)[key]);
});

return newSpec;
} else if (isArray(spec)) {
return spec.map(convertFunctionString);
}

if (isString(spec)) {
if (spec.includes('=>') || spec.includes('function')) {
try {
// 将函数自浮窗转换成可执行的函数
return new Function(`return (${spec})`)();
} catch (e) {
return spec;
}
}
}

return spec;
};

export const mergeAppendSpec = (prevSpec: any, appendSpec: AppendSpecInfo) => {
const { leafSpec, parentKeyPath = '', aliasKeyPath = '' } = appendSpec;
const { aliasKeyPath = '' } = appendSpec;
let { parentKeyPath = '', leafSpec } = appendSpec;
let newSpec = merge({}, prevSpec);

if (parentKeyPath) {
const aliasResult = parseRealPath(parentKeyPath, aliasKeyPath, newSpec);
let aliasResult = parseRealPath(parentKeyPath, aliasKeyPath, newSpec);

if (aliasResult.appendSpec && aliasResult.appendPath) {
set(newSpec, aliasResult.appendPath, aliasResult.appendSpec);
if (aliasResult.appendPath.includes('series') && !newSpec.series) {
// 系列比较特殊,默认是打平在第一层的
leafSpec = leafSpec.series
? isArray(leafSpec.series)
? leafSpec.series[0]
: leafSpec.series
: parentKeyPath in leafSpec
? leafSpec[parentKeyPath]
: leafSpec;
parentKeyPath = parentKeyPath.slice(parentKeyPath.indexOf('.') + 1);
aliasResult = { path: parentKeyPath };
} else {
set(newSpec, aliasResult.appendPath, aliasResult.appendSpec);
}
}

const finalParentKeyPath = aliasResult.path ?? parentKeyPath;

set(
newSpec,
finalParentKeyPath,
reduceDuplicatedPath(
finalParentKeyPath,
aliasResult.aliasName ? reduceDuplicatedPath(aliasResult.aliasName, leafSpec) : leafSpec
convertFunctionString(
reduceDuplicatedPath(
finalParentKeyPath,
aliasResult.aliasName ? reduceDuplicatedPath(aliasResult.aliasName, leafSpec) : leafSpec
)
)
);
} else {
Expand Down

0 comments on commit e441972

Please sign in to comment.