diff --git a/__tests__/integration/snapshots/api/chart-render-update-attributes/step1.png b/__tests__/integration/snapshots/api/chart-render-update-attributes/step1.png index bd3e23f9b2..b6612fcf14 100644 Binary files a/__tests__/integration/snapshots/api/chart-render-update-attributes/step1.png and b/__tests__/integration/snapshots/api/chart-render-update-attributes/step1.png differ diff --git a/__tests__/plots/api/chart-render-update-attributes.ts b/__tests__/plots/api/chart-render-update-attributes.ts index 2929e85927..31041c7418 100644 --- a/__tests__/plots/api/chart-render-update-attributes.ts +++ b/__tests__/plots/api/chart-render-update-attributes.ts @@ -18,6 +18,8 @@ export function chartRenderUpdateAttributes(context) { canvas, }); + const xy = { x: 'date', y: 'close' }; + const options = { type: 'line', data: { @@ -25,10 +27,7 @@ export function chartRenderUpdateAttributes(context) { value: 'data/aapl.csv', transform: [{ type: 'slice', start: 0, end: 10 }], }, - encode: { - x: 'date', - y: 'close', - }, + encode: { ...xy, color: 'red' }, axis: { x: false }, }; @@ -46,18 +45,16 @@ export function chartRenderUpdateAttributes(context) { if (lineDash) { chart.options({ ...options, - style: { - lineDash: null, - }, + encode: { ...xy, color: 'red' }, + style: { lineDash: null }, }); lineDash = false; chart.render().then(resolve1); } else { chart.options({ ...options, - style: { - lineDash: [5, 4], - }, + encode: { ...xy, color: undefined }, + style: { lineDash: [5, 4] }, }); lineDash = true; chart.render().then(resolve); diff --git a/__tests__/unit/utils/deepAssign.spec.ts b/__tests__/unit/utils/deepAssign.spec.ts new file mode 100644 index 0000000000..5384bba7b3 --- /dev/null +++ b/__tests__/unit/utils/deepAssign.spec.ts @@ -0,0 +1,78 @@ +import { deepAssign } from '../../../src/utils/helper'; + +describe('deepAssign', () => { + it('deepAssign(dist, src) should mutate dist obj.', () => { + const dist = { a: 1 }; + deepAssign(dist, { a: 2 }); + expect(dist.a).toBe(2); + }); + + it('deepAssign(dist, src) should return dist obj.', () => { + const dist = { a: 1 }; + expect(deepAssign(dist, { a: 2 })).toBe(dist); + }); + + it('deepAssign(dist, src) should override primitive key.', () => { + expect( + deepAssign( + { a: 1, b: 'a', c: false, d: 3, e: 5 }, + { a: 2, b: 'b', c: true, d: undefined, e: null }, + ), + ).toEqual({ + a: 2, + b: 'b', + c: true, + d: undefined, + e: null, + }); + }); + + it('deepAssign(dist, src) should override array key.', () => { + expect(deepAssign({ a: '1' }, { a: [1, 2, 3] })).toEqual({ a: [1, 2, 3] }); + }); + + it('deepAssign(dist, src) should override built-in object.', () => { + const a = deepAssign( + { + a: new Map([ + [1, 1], + [2, 2], + ]), + }, + { + a: new Map([ + [1, 'a'], + [2, 'b'], + ]), + }, + ).a as Map; + expect(a.get(1)).toBe('a'); + }); + + it('deepAssign(dist, src) should override non-plain-object dist key.', () => { + expect(deepAssign({ a: 1 }, { a: { b: 2 } })).toEqual({ a: { b: 2 } }); + }); + + it('deepAssign(dist, src) should assign plain object recursively.', () => { + const dist = { a: { b: { c: { d: 5 } } } }; + const src = { a: { b: { c: { d: 6 } } } }; + deepAssign(dist, src); + expect(dist.a.b.c.d).toBe(6); + }); + + it('deepAssign(dist, src) should has default max level.', () => { + const dist = { a: { b: { c: { d: { e: { f: 5 }, g: 5 } } } } }; + const src = { a: { b: { c: { d: { e: { f: 6 }, g: 6 } } } } }; + deepAssign(dist, src); + expect(dist.a.b.c.d.e.f).toBe(5); + expect(dist.a.b.c.d.g).toBe(6); + }); + + it('deepAssign(dist, src, maxLevel, level) should specify maxLevel.', () => { + const dist = { a: { b: { c: 1 }, e: 1 } }; + const src = { a: { b: { c: 2 }, e: 2 } }; + deepAssign(dist, src, 2); + expect(dist.a.b.c).toBe(1); + expect(dist.a.e).toBe(2); + }); +}); diff --git a/src/api/utils.ts b/src/api/utils.ts index 33c0d79f0f..2f16266f47 100644 --- a/src/api/utils.ts +++ b/src/api/utils.ts @@ -1,6 +1,6 @@ -import { deepMix } from '@antv/util'; import { G2ViewTree } from '../runtime'; import { getContainerSize } from '../utils/size'; +import { deepAssign } from '../utils/helper'; import { Node } from './node'; import { mark } from './mark'; import { composition } from './composition'; @@ -139,7 +139,7 @@ function updateNode(node: Node, newOptions: G2ViewTree) { const { type, children, ...value } = newOptions; if (node.type === type || type === undefined) { // Update node. - node.value = deepMix(node.value, value); + deepAssign(node.value, value); } else if (typeof type === 'string') { // Transform node. node.type = type; diff --git a/src/utils/helper.ts b/src/utils/helper.ts index 0dd7deda1f..9d17e6d278 100644 --- a/src/utils/helper.ts +++ b/src/utils/helper.ts @@ -1,5 +1,5 @@ import { DisplayObject } from '@antv/g'; -import { lowerFirst, upperFirst } from '@antv/util'; +import { lowerFirst, upperFirst, isPlainObject } from '@antv/util'; export function identity(x: T): T { return x; @@ -146,3 +146,26 @@ export function isStrictObject(d: any): boolean { export function isUnset(value) { return value === null || value === false; } + +export function deepAssign( + dist: Record, + src: Record, + maxLevel = 5, + level = 0, +): Record { + if (level >= maxLevel) return; + for (const key of Object.keys(src)) { + const value = src[key]; + if (!isPlainObject(value) || !isPlainObject(dist[key])) { + dist[key] = value; + } else { + deepAssign( + dist[key] as Record, + value as Record, + maxLevel, + level + 1, + ); + } + } + return dist; +}