Concepts

What is Rendering?

Rendering is the data table rendering capability of the APITable core.

Why do we need Rendering?

Instead of using Dom for table rendering, we chose Canvas for rendering, which allows the tables to scroll smoothly even with large data volumes.

In the actual development process, we used the Konva framework, which provides excellent Canvas rendering performance and a rich graphical drawing API, allowing us to quickly implement various data table features.

But don't worry, the actual Konva development process is not much different from the usual React component development area, we provide perfect documentation and sample code, so that developers can easily get started and quickly develop high-quality data tables.

Here is a brief introduction to Canvas and Konva:

Canvas

Canvas is an important feature in HTML5, which can use JavaScript to draw graphics on web pages, with the advantages of high performance, cross-platform, scalability, flexibility, high interactivity, support for animation effects, embeddability, and so on.

Canvas belongs to the underlying drawing function interface of the browser, we can think of it as a canvas to draw whatever we need. In terms of dynamic update and instant rendering, the performance is significantly better than DOM, so Canvas is more suitable for charting and data visualization scenarios.

Konva

Konva is a Canvas-based JavaScript library that provides an easy-to-use way to create complex graphics, animations, and interactive applications. Our main considerations in the selection process were the following:

  • High performance: Konva is based on Canvas and offers several optimizations such as layer caching and event delegation, which makes it excellent at handling large-scale graphics and complex animations.

  • Ease of use: Konva provides a very intuitive API with which we can easily create elements such as shapes, text, lines and images, and easily perform layout and interactivity operations.

  • Extensibility: Konva provides a number of plugins and extension modules, such as React-Konva, making it possible to integrate seamlessly with frameworks such as React.

  • Cross-platform: Konva can render on a variety of desktop and mobile devices without additional plug-ins or software, making it more widely available and accessible.

  • Community and developer activity: Konva is a mature and popular Canvas library with an active developer community and a large collection of documentation and examples.

What are the Components of Rendering?

As an example, the grid view has the following UI components:

  • GridView

    • GridStage

      • GridLayer

        • Layout

        • CellValue

        • FieldHead

        • Stat

        • ... More Components

    • DomGrid

      • EditorContainer

      • Menus

Data structure of Rendering

The data structure of Rendering consists of the data structure of a datasheet and the derived linear data structure.

This subsection belongs to the drawing pre-knowledge, after understanding the data structure below to better understand how to draw.

Rows

Each view holds a separate list of rows, which is stored persistently in the database and can be thought of as a sequential list of raw rows.

VisibleRows

visibleRows is the final ordered list of rows after filtering, sorting, grouping and searching.

LinearRows

linearRows is a linear data structure that adds group headers, blank rows, and added rows to visibleRows. It serves as a guide to how Canvas draws vertically and contains four data structures: blank rows, group headers, record rows, and added rows.

Interface Statement

type ILinearRowBase = {
  depth: number;
  recordId: string;
};

type ILinearRowBlank = ILinearRowBase & {
  type: CellType.Blank;
};

type ILinearRowAdd = ILinearRowBase & {
  type: CellType.Add;
};

type ILinearRowGroupTab = ILinearRowBase & {
  type: CellType.GroupTab;
};

type ILinearRowRecord = ILinearRowBase & {
  type: CellType.Record;
  groupHeadRecordId: string;
  displayIndex: number;
};

type ILinearRow = ILinearRowBlank | ILinearRowAdd | ILinearRowGroupTab | ILinearRowRecord;

Example of data structure

The following is a real linearRows data for reference purposes only:

[
    {
        "type": "Blank",
        "depth": 0,
        "recordId": "recrMKrMY5jDf"
    },
    {
        "type": "GroupTab",
        "depth": 0,
        "recordId": "recrMKrMY5jDf"
    },
    {
        "type": "GroupTab",
        "depth": 1,
        "recordId": "recrMKrMY5jDf"
    },
    {
        "type": "Record",
        "depth": 2,
        "recordId": "recrMKrMY5jDf",
        "displayIndex": 1,
        "groupHeadRecordId": "recrMKrMY5jDf"
    },
    {
        "type": "Record",
        "depth": 2,
        "recordId": "recslJsH5mGO7",
        "displayIndex": 2,
        "groupHeadRecordId": "recrMKrMY5jDf"
    },
    {
        "type": "AddRecord",
        "depth": 2,
        "recordId": "recslJsH5mGO7"
    },
    {
        "type": "Blank",
        "depth": 1,
        "recordId": "recslJsH5mGO7"
    }
]

UI Structure Analogy

Columns

The columns are much simpler than rows, and there is no filtering, sorting, grouping, etc. in the column dimension, only filtering based on the hidden property in each column.

Rendering's drawing process

Based on the above datasheet data structure, we can plot rows and columns.

Explanation of terms

containerWidth

The width of the viewable area

containerHeight

The height of the viewable area

scrollTop

The vertical distance of the scrolling table

scrollLeft

The horizontal scroll distance of the table

rowHeight

Row height, including group header rows, blank rows, record rows and added rows, different types of rows have different heights

columnWidth

The width of the column, if not set by the user, the default width is used

rowStartIndex

The index of the first row in the visible area of the table, this variable will change frequently when scrolling vertically

rowStopIndex

The index of the last row in the visible area of the table, this variable changes frequently when scrolling vertically

columnStartIndex

The index of the first column in the visible area of the table, which changes frequently when scrolling horizontally

columnStopIndex

The index of the last column in the visible area of the table, which changes frequently when scrolling horizontally

Overview of the process

Taking vertical scrolling as an example, the process is as follows:


The specific process is as follows:

  1. Since the Canvas does not have scroll events, set the DOM scroll bar to trigger the scroll event instead, and get the totalHeight and scrollTop information from it.

  2. Initialize the Coordinate coordinate class (see below) based on the initial information for subsequent coordinate calculation

  3. Calculate the rowStartIndex from Coordinate based on scrollTop

  4. Calculate rowStopIndex from Coordinate based on scrollTop, rowStartIndex and containerWidth

  5. Iterate through the index values between rowStartIndex and rowStopIndex to get the rows to be drawn vertically

  6. According to the coordinate information provided by Coordinate, and GridLayout and CellHelper rendering classes to draw the table lines and cell content

  7. After drawing all of them, you get a "frame", and repeat the above steps to get a visual dynamic scrolling

Coordinate

Responsible for the calculation of the base coordinate system.

Explanation of terms

rowMetaDataMap

A collection of row coordinates

columnMetaDataMap

A collection of column coordinates

lastRowIndex

The index of the last row in the row coordinate collection

Specific implementation

Taking vertical scrolling as an example, the process is as follows:

  1. When traversing the data, rowMetaDataMap collects the row information and changes the lastRowIndex

  2. If the target rowIndex is smaller than lastRowIndex, it will be obtained directly from rowMetaDataMap

  3. If the target rowIndex is larger than lastRowIndex, the iteration starts from lastRowIndex and loop step 1

type IndicesMap = Record<number, number>;

type CellMetaData = {
  offset: number;
  size: number;
};

type CellMetaDataMap = Record<number, CellMetaData>;

interface ICoordinate {
  columnWidth: number;
  rowHeight: number;
  columnCount: number;
  rowCount: number;
  containerWidth: number;
  containerHeight: number;
  rowIndicesMap: IndicesMap;
  columnIndicesMap: IndicesMap;
  lastRowIndex?: number;
  lastColumnIndex?: number;
  rowMetaDataMap?: CellMetaDataMap;
  columnMetaDataMap?: CellMetaDataMap;
}

/**
 * Used to construct raster coordinate system, assist in Grid and Gantt view for plotting
 */
class Coordinate<ICoordinate> {}

CellHelper

Responsible for drawing the cell content.

Specific implementation

interface IRenderProps {
  x: number;
  y: number;
  columnWidth: number;
  rowHeight: number;
  recordId: string;
  field: IField;
  cellValue: ICellValue;
  isActive?: boolean;
  editable?: boolean;
  rowHeightLevel: RowHeightLevel;
  ...
}

class CellHelper extends KonvaDrawer {
    public renderCellValue(renderProps: IRenderProps, ctx?: CanvasRenderingContext2D) {
      switch (fieldType) {
        // ...
        case FieldType.Text:
          return this.renderCellText(renderProps, ctx);
      }
    }
    private renderCellText(renderProps: IRenderProps, ctx?: CanvasRenderingContext2D) {...}
    // ...
}

// Wrapping of Canvas basic drawing methods
class KonvaDrawer {
  public line(props: ILineProps) {...}
  public text(props: ITextProps) {...}
  public image(props: IImageProps) {...}
  // ...
}