!!!###!!!title=2.6 Progressive Loading of Scene Tree——VisActor/VTable Contributing Documents!!!###!!!!!!###!!!description=---title: 2.6 Progressive Loading of Scene Tree key words: VisActor,VChart,VTable,VStrory,VMind,VGrammar,VRender,Visualization,Chart,Data,Table,Graph,Gis,LLM--- ProgressProxy is a core submodule of the Scenegraph module in VTable, primarily responsible for performance optimization in scenarios with large data volumes. This module ensures smooth rendering and interactive experience under large data volumes by controlling the creation and update process of scene nodes. This article aims to explore the practical characteristics of ProgressProxy by reading the source code. \r !!!###!!!

ProgressProxy is a core submodule of the Scenegraph module in VTable, primarily responsible for performance optimization in scenarios with large data volumes. This module ensures smooth rendering and interactive experience under large data volumes by controlling the creation and update process of scene nodes. This article aims to explore the practical characteristics of ProgressProxy by reading the source code. \r

Progressive Loading Implementation Plan

Control the Number of Rendered Nodes for Progressive Creation

Code location: VTable\packages\vtable\src\scenegraph\group-creater\progress\proxy.ts

We know that Vtable has optimized the first screen loading, and the idea of optimizing the first screen actually has something in common with the optimizations we do in daily development. It basically boils down to two directions: the first is to reduce the number of rendering nodes, and the second is to improve the rendering speed. Next, we will introduce how to limit the number of nodes. \r

In the constructor of SceneProxy, the control logic for the number of nodes created for the first screen, createGroupForFirstScreen(), is mainly reflected in the configuration of rowLimit and colLimit. Here, the maximum number of rows and columns maintained in the scene tree is limited. By limiting the number of nodes maintained simultaneously, memory usage and rendering pressure are reduced, ensuring smooth performance even under large data volumes.

constructor(table: BaseTableAPI) {
  this.table = table;

  if (this.table.isPivotChart()) {
    this.rowLimit = Math.max(100, Math.ceil((table.tableNoFrameHeight * 2) / table.defaultRowHeight));
    this.colLimit = Math.max(100, Math.ceil((table.tableNoFrameWidth * 2) / table.defaultColWidth));
  } else if (this.table.isAutoRowHeight()) {
    this.rowLimit = Math.max(100, Math.ceil((table.tableNoFrameHeight * 2) / table.defaultRowHeight));
  } else if (this.table.widthMode === 'autoWidth') {
    this.colLimit = Math.max(100, Math.ceil((table.tableNoFrameWidth * 2) / table.defaultColWidth));
  } else {
    this.rowLimit = Math.max(200, Math.ceil((table.tableNoFrameHeight * 2) / table.defaultRowHeight));
    this.colLimit = Math.max(100, Math.ceil((table.tableNoFrameWidth * 2) / table.defaultColWidth));
  }
}    

The configured formula is for example: 100, Math.ceil((table.tableNoFrameHeight * 2) / table.defaultRowHeight)

Such a calculation involves dividing the visible area height of the table by the row height, which is the number of rows and columns that need to be displayed in real-time, then multiplying by two and using 100 as a fallback. The purpose of multiplying by two is to set a buffer, loading twice the number of nodes. While minimizing the number of rendered nodes, it also reserves space for scrolling up and down, ensuring that there is enough buffer before and after scrolling to avoid blank spaces due to loading delays.

Specific Implementation Details of Incremental Creation

After configuring rowLimit and colLimit, how is progressive loading implemented?

After reading, it was found that the core logic of progressive rendering lies in setY and setX, which are used to handle scrolling and progressive rendering in the vertical/horizontal directions, respectively. The main function is to dynamically update the rows of the table based on the scroll position, ensuring that the rows in the currently visible area are correctly rendered, while releasing the rows in the invisible area to save memory. Here, taking the vertical direction's SetY as an example:

 async setY(y: number, isEnd = false) {
    const yLimitTop =
      this.table.getRowsHeight(this.bodyTopRow, this.bodyTopRow + (this.rowEnd - this.rowStart + 1)) / 2;
    const yLimitBottom = this.table.getAllRowsHeight() - yLimitTop;

    const screenTop = this.table.getTargetRowAt(y + this.table.scenegraph.colHeaderGroup.attribute.height);
    if (screenTop) {
      this.screenTopRow = screenTop.row;
    }

    if (y < yLimitTop && this.rowStart === this.bodyTopRow) {
      // 执行真实body group坐标修改
      this.updateDeltaY(y);
      this.updateBody(y - this.deltaY);
    } else if (y > yLimitBottom && this.rowEnd === this.bodyBottomRow) {
      // 执行真实body group坐标修改
      this.updateDeltaY(y);
      this.updateBody(y - this.deltaY);
    } else if (
      (!this.table.scenegraph.bodyGroup.firstChild ||
        this.table.scenegraph.bodyGroup.firstChild.type !== 'group' ||
        this.table.scenegraph.bodyGroup.firstChild.childrenCount === 0) &&
      (!this.table.scenegraph.rowHeaderGroup.firstChild ||
        this.table.scenegraph.rowHeaderGroup.firstChild.type !== 'group' ||
        this.table.scenegraph.rowHeaderGroup.firstChild.childrenCount === 0)
    ) {
      this.updateDeltaY(y);
      // 兼容异步加载数据promise的情况 childrenCount=0 如果用户立即调用setScrollTop执行dynamicSetY会出错
      this.updateBody(y - this.deltaY);
    } else {
      // 执行动态更新节点
      this.dynamicSetY(y, screenTop, isEnd);
    }
  }    

The method itself first calculates the top limit yLimitTop and the bottom limit yLimitBottom when scrolling, and maintains the index of the current top line of the screen, which is the starting index for rendering, indicating from which line to start rendering. Combined with the previous context, by obtaining the starting index for rendering and the maximum number of lines restricted for rendering, we can slice out the local data we need to render from the massive rendering data.

After obtaining the starting coordinates for rendering, implement rendering through some update methods in the source code that start with 'update'.

Dynamic Updates

Code location: VTable\packages\vtable\src\scenegraph\group-creater\progress\update-positon

dynamicSetY method will dynamically load or unload rows based on the scroll position, ensuring that the rows in the currently visible area are rendered correctly while releasing the rows in the non-visible area to save memory.

export async function dynamicSetY(y: number, screenTop: RowInfo | null, isEnd: boolean, proxy: SceneProxy) {
  if (!screenTop) {
    return;
  }
  const screenTopRow = screenTop.row;
  const screenTopY = screenTop.top;

  let deltaRow;
  if (isEnd) {
    deltaRow = proxy.bodyBottomRow - proxy.rowEnd;
  } else {
    deltaRow = screenTopRow - proxy.referenceRow;
  }
  move(deltaRow, screenTopRow, screenTopY, y, proxy);
  if (isEnd) {
    const cellGroup = proxy.table.scenegraph.highPerformanceGetCell(proxy.colStart, proxy.rowEnd, true);
    if (cellGroup.role === 'cell') {
      const deltaY =
        cellGroup.attribute.y +
        cellGroup.attribute.height -
        (proxy.table.tableNoFrameHeight - proxy.table.getFrozenRowsHeight() - proxy.table.getBottomFrozenRowsHeight()) -
        y;
      proxy.deltaY = -deltaY;
      proxy.updateBody(y - proxy.deltaY);
    }
  }
  // proxy.table.scenegraph.updateNextFrame();
}    

The core design of the dynamicSetY method lies in achieving efficient and smooth progressive rendering by dynamically adjusting the row rendering range of the table. Its principle is based on real-time calculation of the scroll position and dynamic updating of row offsets, ensuring that the rows in the currently visible area are correctly rendered while releasing the rows in the non-visible area to save memory. The method first obtains the information of the current screen's top row through screenTop and determines whether it is scrolling or has ended based on the isEnd parameter, thereby calculating the row offset deltaRow. Then, it calls the move method to dynamically adjust the row rendering range of the table according to the row offset, ensuring that the table content is synchronized with the scroll position. At the end of scrolling, the method further calculates the vertical offset deltaY and updates the coordinates of the body group to ensure the complete display of the table content. During large data volume scrolling, the ProgressProxy module will dynamically update the scene nodes. During scrolling, in the direction of the scroll, some cells will update their position in the scene tree and update the elements within the cells to complete the scrolling effect.

Summary

We have concluded that the secret to VTable maintaining high performance and a smooth user experience in large data scenarios lies in the ProgressProxy module, which controls the table to render only the data within the visible range. It continuously calculates and dynamically updates interactions and data updates during scrolling, making it one of the core modules for optimizing the performance of the table component.

This document was revised and organized by the following personnel

玄魂