Skip to content

Fix save restore mismatch#1151

Open
100pah wants to merge 9 commits intoreleasefrom
fix-save-restore-mismatch
Open

Fix save restore mismatch#1151
100pah wants to merge 9 commits intoreleasefrom
fix-save-restore-mismatch

Conversation

@100pah
Copy link
Copy Markdown
Contributor

@100pah 100pah commented Mar 12, 2026

Hover Layer

  1. Fix that ctx.save() and ctx.restore() do not properly paired, which can cause canvas is not properly cleared due to incorrect transform. It can be reproduced when both hover layered and zlevel is used. Close [Bug] In Line series chart, the series is not removed when using the legend for filter After applying the datazoom apache/echarts#18688 .
  2. Clarity el.autoBatch and fix that batched rendering may not be executed in some edge cases.

test case: echarts/test/hover-layer.html

Incremental / Progressive Rendering

In practice, previously incremental layer was likely to dirty and discard the rendered content, and restarted all drawing per frame, which probably caused the cumulative draw calls to significantly block the rendering in large data. This commit introduces more precise layer content reuse strategies:

  1. Based on the recorded first element rather than the first displayList index of the last pass (which is likely to be changed by preceding irrelevant elements on underlying layers);
  2. Support multiple runs of consecutive incremental elements to render into one singe layer without wrongly dirty each other - introduce data structure LayerDrawCursor per run of elements to render separately, and change Displayable['incremental'] from boolean to number to designate different runs. The scenarios can be: echarts large bar and large candlestick series exist at the same time.
  3. Previously hover layer was likely to be repeatedly rendered; fix that.

test case: echarts/test/stream-basic5.html echarts/test/stream-basic2.html echarts/test/stream-basic3.html echarts/test/stream-basic4.html echarts/test/stream-basic5.html

Current strategy:

  • Two use patterns are covered per incremental layer:

    • [INCREMENTAL_CASE_SINGLE_ELEMENT]
      An single incremental element with a customized buildPath, using Displayable['notClear']
      to retain the rendered content.
    • [INCREMENTAL_CASE_MULTIPLE_ELEMENTS]
      A run of consecutive incremental elements, progressively drawing per frame in _paintList. This
      is not an optimal approach for rendering due to the increasing cost of updating and sorting
      displayList. However, it support varying styles and can balance the cost between rendering and
      hit testing during hover (which may degrade with excessive points in single shape).
  • [LAYER_STACKING]

    • An Layer instance represents a physical layer, typically a HTML Canvas.
    • Each zlevel will be splitted to 2 or 3 physical layers if incremental elements occur,
      designated by zlevel2. A full version can be like this:
      [[ layer_hover zlevel:100000 ]]
      [[ layer_normal_above zlevel:0, zlevel2:2 (normal el after incremental el) ]]
      [[ layer_incremental zlevel:0, zlevel2:1 (incremental el) ]]
      [[ layer_normal_below zlevel:0, zlevel2:0 (normal el before incremental el) ]]
      (But layer_normal_below may be omitted if not needed.)
    • Physical layers (HTML Canvas) should not be created excessively, therefore, within a single
      zlevel, multiple runs of incremental elements share one physical layer (i.e., zlevel2: 1).
    • Theoretically, a physical layer can switch bettween incremental or non-incremental. But currently
      we do not support it.
    • [LIMITED_TO_3_LAYERS_PER_ZLEVEL]
      To avoid excessive HTML Canvas creation, at most 3 layers can be created for a single zlevel.
      If two runs of consecutive incremental elements are separated by some normal elements, those normal
      elements are painted on zlevel: 2, and all incremental elements are painted on zlevel: 1,
      regardless of el.z and el.z2 settings.
      Users can explicitly specify a higher zlevel to allow more incremental layers to be created.
    • NOTE: Elements do not necessarily have different z or z2 - even if all z or z2 are 0, z-order is
      determined by add(el) order.
  • [DISPLAY_LIST_SORTING_AND_LAYERING]
    Currently there are 5 parameters to determine the layer and z-order for each element:
    <zlevel, zlevel2, incremental(LayerDrawCursor), z, z2>
    Only zlevel, z and z2 are user specified.
    The displayList is sorted only by zlevel, z and z2.
    A <zlevel, zlevel2> pair determines a layer.
    A incremental(LayerDrawCursor) acts like a "soft layer", representing a run of consecutive
    incremental elements. Multiple LayerDrawCursors share one layer.
    Users must use different el.incremental (a number) to distinguish different runs of consecutive
    incremental elements. And each el.incremental has its exclusive LayerDrawCursor. Take echarts
    as an example: if there are multiple "series" requiring incremental, e.g., a bar series and a
    candlestick series in a Cartesian, and their zlevel/z/z2 are typicall the same.
    See LAYER_SAMPLE_CASE_3 for more details.

  • Consider sample cases below to check the implementation:

    • [LAYER_SAMPLE_CASE_1]:
      zlevel:5 is explicitly specified by users.
      zlevel:0 is the default.
      [[ layer_hover zlevel:100000 ]]
      [[ layer_normal_above_2 zlevel:5, zlevel2:2 ]]
      [[ layer_incremental_2 zlevel:5, zlevel2:1 ]]
      [[ layer_normal_below_2 zlevel:5, zlevel2:0 ]]
      [[ layer_normal_above_1 zlevel:0, zlevel2:2 ]]
      [[ layer_incremental_1 zlevel:0, zlevel2:1 ]]
      [[ layer_normal_below_1 zlevel:0, zlevel2:0 ]]
    • [LAYER_SAMPLE_CASE_2]:
      No elements are before incremental elements.
      [[ layer_hover zlevel: 100000 ]]
      [[ layer_normal_above zlevel:0, zlevel2:2 ]]
      [[ layer_incremental zlevel:0, zlevel2:1 ]]
    • [LAYER_SAMPLE_CASE_3]:
      Multiple runs of consecutive incremental elements, may (or not) be separated by some normal elements.
      Suppose a sorted displayList is:
      [{a_nor}, {b_inc:7}, {c_inc:7}, {d_nor}, {e_inc:9}, {f_inc:9}, {g_nor}].
      Then both incremental:7 and incremental:9 have new elements added.
      The sorted displayList become:
      [{a_nor}, {b_inc:7}, {c_inc:7}, {m_inc:7}, {d_nor}, {e_inc:9}, {f_inc:9}, {n_inc:9}, {g_nor}].
      The order can not match the original displayList - new elements are inserted in the middle rather
      than at the end. Therefore, multiple layerDrawCursors are introduced to manage the pointers separately,
      enabling them to share one physical layer.
      They are arranged into layers like this:
      [[ layer_hover zlevel:100000 ]]
      [[ layer_normal_above zlevel:0, zlevel2:2 layerDrawCursor:0 {d_nor}, {g_nor} ]]
      [[ layer_incremental zlevel:0, zlevel2:1 layerDrawCursor:9 {e_inc:9}, {f_inc:9} {n_inc:9} ]]
      [[ layerDrawCursor:7 {b_inc:7}, {c_inc:7} {m_inc:7} ]]
      [[ layer_normal_below zlevel:0, zlevel2:0 layerDrawCursor:0 {a_nor} ]]
  • [LAYER_DIRTY_RULES]:
    Only dirty layer will be cleared and repaint later. layer.__dirty is set by:

    • REDRAW_BIT of every element. [LAYER_DIRTY_BY_REDRAW_BIT]
      For normal layers, currently REDRAW_BIT is the only reliable way to make sure repainting, since
      reorder is not checked. So we conservatively always dirty the layers if any REDRAW_BIT occur.
      For incremental layers, we aggressively dirty the layer only if drawn elements have REDRAW_BIT,
      since redorder of incremental elements hardly occurs.
    • Mismatching of layerDrawCursor.first and layerDrawCursor.endIdx.
      [LAYER_CONTENT_RETAINED]:
      This strategy is mainly required by progressive rendering, where typicall new elements are
      appended, and repaint from the start per frame should be prevented. Otherwise, increasing
      draw calls can significantly block rendering. Additionally, If displayList indices of incremental
      elements are changed due to preceding elements of other layers, the drawing should not be restarted.
      Therefore, we record the first element to shift indices for this case.
      [LAYER_FAIL_TO_DIRTY_IF_ONLY_REORDER]:
      This strategy has also been applied to normal layers to prevent them from repainting in progressive
      frames. Upstream applications should remain the order of elements unchanged if no REDRAW_BIT is
      set - no checking for this currently. Otherwise, layers fail to dirty unexpectedly.
      Take echarts as an example, consider common patterns: "remove some elements", "modify z/z2 typically
      via useState", "clear and recreate all elements per user interaction", "reuse elements if possible
      but update attributes and styles per user interaction". Layer dirty can be triggered. If bad cases
      occur, more mechanism can be introduced (e.g., record el ids in layerDrawCursor for checking).
  • PENDING:

    • [PENDING_SEPARATE_DISPLAY_LIST]:
      In INCREMENTAL_CASE_MULTIPLE_ELEMENTS, displayList sorting and _updateAndAddDisplayable will be
      executed per frame and significantly consume time in high element counts (indicatively, 1e6 in
      certain environments). Perhaps displayList can be separated by Layer or by LayerDrawCursor,
      and perform targeted optimization - omitting unnecessary sorting and update().
    • Also sort displayList by el.incremental to automatically ensure consecutive?
      Currently, the contiguity can only be ensured by the order of add() call.

100pah added 9 commits March 12, 2026 20:59
…, which can cause canvas is not properly cleared due to incorrect transform. It can be reproduced when both hover layered and zlevel is used. Close apache/echarts#18688 . (2) Clarity el.autoBatch and fix that batched rendering may not be executed in some edge cases.
… only style modifying, and fix the omission of __hoverStyle usage. Previously the hover layer effect is inconsistent and not preferable - when the original layer is required to change by the subsequent user interactions, the final composited effect will change unexpectedly due to the modification of el props. See test case in `echarts/test/hover-layer.html`.
…r for large data and progressive rendering - prevent unnecessary repeated hoverLayer rendering.
…er was likely to dirty and discard the rendered content, and restarted all drawing per frame, which probably caused the cumulative draw calls to significantly block the rendering in large data. This commit introduces more precise layer content reuse strategies: (1) Based on the recorded first element rather than the first displayList index of the last pass (which is likely to be changed by preceding irrelevant elements on underlying layers); (2) Support multiple runs of consecutive incremental elements to render into one singe layer without wrongly dirty each other - introduce data structure `LayerDrawCursor` per run of elements to render separately, and change `Displayable['incremental']` from boolean to number to designate different runs. The scenarios can be: echarts large bar and large candlestick series exist at the same time. (3) Previously hover layer was likely to be repeatedly rendered; fix that.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant