diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts index 1568078267..215087b72b 100644 --- a/src/chart/custom/CustomView.ts +++ b/src/chart/custom/CustomView.ts @@ -99,6 +99,7 @@ import { applyKeyframeAnimation, stopPreviousKeyframeAnimationAndRestore } from '../../animation/customGraphicKeyframeAnimation'; +import { SeriesModel } from '../../echarts.all'; const EMPHASIS = 'emphasis' as const; const NORMAL = 'normal' as const; @@ -1264,16 +1265,21 @@ function retrieveStyleOptionOnState( // Usage: -// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that -// the existing children will not be removed, and enables the feature that -// update some of the props of some of the children simply by construct +// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates +// that the existing children will not be removed, and enables the feature +// that update some of the props of some of the children simply by construct // the returned children of `renderItem` like: // `var children = group.children = []; children[3] = {opacity: 0.5};` // (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children // by child.name. But that might be lower performance. // (3) If `elOption.$mergeChildren` is `false`, the existing children will be // replaced totally. -// (4) If `!elOption.children`, following the "merge" principle, nothing will happen. +// (4) If `!elOption.children`, following the "merge" principle, nothing will +// happen. +// (5) If `elOption.$mergeChildren` is not `false` neither `'byName'` and the +// `el` is a group, and if any of the new child is null, it means to remove +// the element at the same index, if exists. On the other hand, if the new +// child is and empty object `{}`, it means to keep the element not changed. // // For implementation simpleness, do not provide a direct way to remove sinlge // child (otherwise the total indicies of the children array have to be modified). @@ -1316,24 +1322,52 @@ function mergeChildren( // might be better performance. let index = 0; for (; index < newLen; index++) { - newChildren[index] && doCreateOrUpdateEl( - api, - el.childAt(index), - dataIndex, - newChildren[index] as CustomElementOption, - seriesModel, - el - ); + const newChild = newChildren[index]; + const oldChild = el.childAt(index); + if (newChild) { + if (newChild.ignore == null) { + // The old child is set to be ignored if null (see comments + // below). So we need to set ignore to be false back. + newChild.ignore = false; + } + doCreateOrUpdateEl( + api, + oldChild, + dataIndex, + newChild as CustomElementOption, + seriesModel, + el + ); + } + else { + // If the new element option is null, it means to remove the old + // element. But we cannot really remove the element from the group + // directly, because the element order may not be stable when this + // element is added back. So we set the element to be ignored. + oldChild.ignore = true; + } } for (let i = el.childCount() - 1; i >= index; i--) { - // Do not support leave elements that are not mentioned in the latest - // `renderItem` return. Otherwise users may not have a clear and simple - // concept that how to control all of the elements. const child = el.childAt(i); - child && applyLeaveTransition(child, customInnerStore(el).option, seriesModel); + removeChildFromGroup(el, child, seriesModel); } } +function removeChildFromGroup( + group: graphicUtil.Group, + child: Element, + seriesModel: SeriesModel +) { + // Do not support leave elements that are not mentioned in the latest + // `renderItem` return. Otherwise users may not have a clear and simple + // concept that how to control all of the elements. + child && applyLeaveTransition( + child, + customInnerStore(group).option, + seriesModel + ); +} + type DiffGroupContext = { api: ExtensionAPI; oldChildren: Element[]; diff --git a/test/custom-update.html b/test/custom-update.html new file mode 100644 index 0000000000..2015c4bbb6 --- /dev/null +++ b/test/custom-update.html @@ -0,0 +1,229 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+ + + + + + + + + + + diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 5de0031e9b..e4c4963128 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -63,6 +63,7 @@ "custom-large": 1, "custom-shape-morphing": 1, "custom-text-content": 6, + "custom-update": 4, "dataSelect": 7, "dataset-case": 6, "dataZoom-action": 4, diff --git a/test/runTest/actions/custom-update.json b/test/runTest/actions/custom-update.json new file mode 100644 index 0000000000..dbacd6ff19 --- /dev/null +++ b/test/runTest/actions/custom-update.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":678,"x":793,"y":218},{"type":"mousemove","time":878,"x":436,"y":281},{"type":"mousemove","time":1079,"x":285,"y":329},{"type":"mousemove","time":1285,"x":283,"y":329},{"type":"mousemove","time":1362,"x":282,"y":329},{"type":"mousemove","time":1562,"x":239,"y":316},{"type":"mousemove","time":1765,"x":176,"y":325},{"type":"mousemove","time":1979,"x":158,"y":325},{"type":"mousemove","time":2180,"x":72,"y":178},{"type":"mousemove","time":2386,"x":43,"y":114},{"type":"mousemove","time":2580,"x":43,"y":114},{"type":"mousedown","time":2591,"x":43,"y":114},{"type":"mouseup","time":2723,"x":43,"y":114},{"time":2724,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":4303,"x":43,"y":114},{"type":"mouseup","time":4435,"x":43,"y":114},{"time":4436,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":6044,"x":43,"y":114},{"type":"mouseup","time":6177,"x":43,"y":114},{"time":6178,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1658736074386},{"name":"Action 2","ops":[{"type":"mousemove","time":477,"x":484,"y":279},{"type":"mousemove","time":680,"x":226,"y":164},{"type":"mousemove","time":893,"x":119,"y":145},{"type":"mousemove","time":1094,"x":59,"y":132},{"type":"mousemove","time":1301,"x":51,"y":129},{"type":"mousedown","time":1423,"x":51,"y":129},{"type":"mouseup","time":1547,"x":51,"y":129},{"time":1548,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":2550,"x":51,"y":129},{"type":"mouseup","time":2684,"x":51,"y":129},{"time":2685,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":3923,"x":51,"y":129},{"type":"mouseup","time":4056,"x":51,"y":129},{"time":4057,"delay":400,"type":"screenshot-auto"}],"scrollY":530,"scrollX":0,"timestamp":1658736087421},{"name":"Action 3","ops":[{"type":"mousemove","time":399,"x":153,"y":147},{"type":"mousemove","time":600,"x":77,"y":88},{"type":"mousemove","time":805,"x":48,"y":69},{"type":"mousedown","time":931,"x":45,"y":66},{"type":"mousemove","time":1021,"x":45,"y":66},{"type":"mouseup","time":1062,"x":45,"y":66},{"time":1063,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":2000,"x":45,"y":66},{"type":"mouseup","time":2122,"x":45,"y":66},{"time":2123,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2934,"x":45,"y":66},{"type":"mousedown","time":2988,"x":45,"y":67},{"type":"mousemove","time":3136,"x":45,"y":67},{"type":"mouseup","time":3146,"x":45,"y":67},{"time":3147,"delay":400,"type":"screenshot-auto"}],"scrollY":1128,"scrollX":0,"timestamp":1658736095915},{"name":"Action 4","ops":[{"type":"mousemove","time":166,"x":266,"y":215},{"type":"mousemove","time":370,"x":79,"y":198},{"type":"mousemove","time":582,"x":55,"y":185},{"type":"mousedown","time":751,"x":52,"y":184},{"type":"mousemove","time":786,"x":52,"y":184},{"type":"mouseup","time":851,"x":52,"y":184},{"time":852,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":1708,"x":52,"y":184},{"type":"mousemove","time":1715,"x":52,"y":184},{"type":"mouseup","time":1854,"x":52,"y":184},{"time":1855,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2698,"x":52,"y":184},{"type":"mousedown","time":2732,"x":52,"y":185},{"type":"mouseup","time":2874,"x":52,"y":185},{"time":2875,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2903,"x":52,"y":185}],"scrollY":1548,"scrollX":0,"timestamp":1658736103250}] \ No newline at end of file