Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(custom): fix elements may not be removed after updates #17333 #17349

Merged
merged 8 commits into from
Jul 29, 2022

Conversation

Ovilia
Copy link
Contributor

@Ovilia Ovilia commented Jul 11, 2022

Brief Information

This pull request is in the type of:

  • bug fixing
  • new feature
  • others

What does this PR do?

When custom series is updated, if the renderItem returns a group that contains null for an element that was previously in the group, it will not be removed, causing some of the elements leave behind by mistake.

Fixed issues

#17333

Details

Before: What was the problem?

There is an extra element after calling setOption for the second time.

After: How does it behave after the fixing?

No extra element after calling setOption for the second time.

Document Info

One of the following should be checked.

Misc

ZRender Changes

  • This PR depends on ZRender changes (ecomfe/zrender#xxx).

Related test cases or examples to use the new APIs

  • added a test case custom-update.html
  • tested and passed the visual tests of current cases with "custom" in their names

Others

Merging options

  • Please squash the commits into a single one when merging.

Other information

@echarts-bot
Copy link

echarts-bot bot commented Jul 11, 2022

Thanks for your contribution!
The community will review it ASAP. In the meanwhile, please checkout the coding standard and Wiki about How to make a pull request.

The pull request is marked to be PR: author is committer because you are a committer of this project.

@Ovilia Ovilia linked an issue Jul 11, 2022 that may be closed by this pull request
@Ovilia Ovilia added this to the 5.4 milestone Jul 11, 2022
@pissang
Copy link
Contributor

pissang commented Jul 11, 2022

I'm not pretty sure but I think it's by design at original. Like the logic of option merging, giving null means not updating this element in the default merge mode. If we wan't to remove it, we need to change the $mergeChildren to false, which will loose the transition. Perhaps @100pah can give more explanation on this.

But from my side. I think the change in this PR will bring much more intuitionistic code. So I will give +1 on this change(and a bit more explanation on the doc will be better). The only change I need to request is that we still need to applyLeaveTransition when removing the child instead of using remove directly

src/chart/custom/CustomView.ts Outdated Show resolved Hide resolved
src/chart/custom/CustomView.ts Outdated Show resolved Hide resolved
@Ovilia
Copy link
Contributor Author

Ovilia commented Jul 12, 2022

@pissang @100pah Thanks for the review. I updated the comment and add a test case where a child of new group returned from the custom series is {}, which should preserve the old element. I will also update the document about this.

);
}
else {
removeChildFromGroup(el, oldChild, seriesModel);
Copy link
Member

@100pah 100pah Jul 12, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If "leave transition animation" is disabled, it will still call parent.remove(el) directly, which uses splice internally and brings a weird behavior:

existing children: [elA, elB, elC]
new children: [elM, null, elN]
result: [merge(elA, elM), elC, elN]

I've changed my opinion that

Basically I agree this change, which might make it more intuitive

When setOption, the default strategy is "merge", which is the same as the other series like line, scatter, ...
The merge strategy in custom series contains:

  • dataItems are merged by index
  • group children are merge by index

In series link line, scatter, graphics are the same between different data items, and it makes "merge" strategy make sense in most cases. But is custom series, graphics may not be the same between different data items, which probably make the "merge" not reasonable.

And consider the case #17349 , with the default "merge" strategy, we can get the transition animation:

aaaaaaaa

But the animation seams not make sense: the data item "a" and the data item "b" has no relationship at all, they are in different month and different date. The animation happens only because they happen to use the same graphic.

This change brings a breaking change but only change the null case in group children but not considered the case like "merge graphic unexpectedly of different data item", "remain properties unexpectedly when merge graphic". I think it is not good enough.

Could we provide a option on custom series like

series: {
     type: 'custom',
     merge: false,
}

When merge: false, there is no merge either between different data items (see https://github.com/apache/echarts/blob/5.3.3/src/chart/custom/CustomView.ts#L222)
or in group children.

And it may fix #17349 thoroughly ?

@Ovilia @pissang what's your opinion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this mainly depends on how we understand merging. I don't think it's intuitive to use the old element in a group when provided null in new setOption. This is just like when providing series data like [10, 20, null, 40] in a new setOption should not take the null data to use the old value in the previous data.

Copy link
Member

@100pah 100pah Jul 13, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think at least this behavior is not good (by the current modification): 🤔

old children: [elA, elB, elC] (length: 3)
new children: [elM, null, elN] (length: 3)

  • if transition animation enabled:
    • result: [merge(elA, elM), merge(elC, elN)] (length: 2, which is different from neither the old children nor the input new children)
  • if transition animation disabled:
    • result: [merge(elA, elM), elC, elN] (length: 3, but the index of elC is changed from 2 to 1 )

Both of them seems not expectable and intuitive for users.
(Do I misunderstand with the modified code?)

Copy link
Contributor Author

@Ovilia Ovilia Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are quite right! I didn't think of the situation. I fixed this and also update the test cases to include with/without animation.

Copy link
Member

@100pah 100pah Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After adding oldRemovedCount the effect is better but still has some issues:

Consider the case below:

  • step 1. setOption with new children: [elA, elB, elC] (length: 3)
  • step 2. setOption with new children: [elA, null, elC] (length: 3) (means hide elB)
  • step 3. setOption with new children: [elA, elB, elC] (length: 3) (means show elB again)

But with current modified code, the result will be:

  • After step 1. children will be [elA, elB, elC]
  • After step 2. children will be [elA, elC]
  • After step 3. children will be [elA, merge(elC, elB), elC]

which is unexpected.

Consider leave animation

If the setOption is triggered by user's repeat click, the interval between two clicks is not predicable, may be longer or shorter than the leave animation duration. In this case, if the indices of some children is different before and after leave animation, the merge result will be not predicable.


Basically, if we use null/undefined to delete a child, I think this principle should be applied:

The index of any other element should not be changed after this deletion

That is, is we delete a child, that place should became "no element"(probably not supported in current zrender) or "an placeholder element that can not displayed"

);
}
else {
removeChildFromGroup(el, oldChild, seriesModel);
Copy link
Member

@100pah 100pah Jul 14, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After adding oldRemovedCount the effect is better but still has some issues:

Consider the case below:

  • step 1. setOption with new children: [elA, elB, elC] (length: 3)
  • step 2. setOption with new children: [elA, null, elC] (length: 3) (means hide elB)
  • step 3. setOption with new children: [elA, elB, elC] (length: 3) (means show elB again)

But with current modified code, the result will be:

  • After step 1. children will be [elA, elB, elC]
  • After step 2. children will be [elA, elC]
  • After step 3. children will be [elA, merge(elC, elB), elC]

which is unexpected.

Consider leave animation

If the setOption is triggered by user's repeat click, the interval between two clicks is not predicable, may be longer or shorter than the leave animation duration. In this case, if the indices of some children is different before and after leave animation, the merge result will be not predicable.


Basically, if we use null/undefined to delete a child, I think this principle should be applied:

The index of any other element should not be changed after this deletion

That is, is we delete a child, that place should became "no element"(probably not supported in current zrender) or "an placeholder element that can not displayed"

const newChild = newChildren[index];
const oldChild = el.childAt(index);
if (newChild) {
if (newChild.ignore == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be:

if (newChild.ignore) {
    newChild.ignore = false;
}

or

newChild.ignore = null; // or false

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newChild is the user-provided option. So if newChild.ignore is true, it means the user want to ignore the element so I don't think we should set newChild.ignore = false in this situation.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it wrong

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if users set a child as null / undefined at the first time ? (rather than the second time)

const newChild = newChildren[index];
const oldChild = el.childAt(index);
if (newChild) {
if (newChild.ignore == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I got it wrong

@Ovilia Ovilia merged commit c584377 into master Jul 29, 2022
@echarts-bot
Copy link

echarts-bot bot commented Jul 29, 2022

Congratulations! Your PR has been merged. Thanks for your contribution! 👍

@Ovilia Ovilia deleted the fix-17333 branch July 29, 2022 03:40
@@ -99,6 +99,7 @@ import {
applyKeyframeAnimation,
stopPreviousKeyframeAnimationAndRestore
} from '../../animation/customGraphicKeyframeAnimation';
import { SeriesModel } from '../../echarts.all';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The import source is incorrect.

Ovilia added a commit to apache/echarts-doc that referenced this pull request Aug 1, 2022
doc: update instruction for custom group being null apache/echarts#17349
@Ovilia Ovilia modified the milestones: 5.4, 5.4.0 Sep 1, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[Bug] Custom series graphic elements rendering bug after updating
4 participants