Data Model
Data Model
Section titled “Data Model”CellStore
Section titled “CellStore”The CellStore is a sparse storage backed by a Map<string, CellData>. Keys are "row:col" strings (e.g., "5:3"). Only cells that contain data are stored — empty cells return undefined.
// O(1) get and setconst cell = cellStore.get(5, 3); // CellData | undefinedcellStore.set(5, 3, { value: 42 }); // Stores cell at row 5, col 3cellStore.delete(5, 3); // Removes cell datacellStore.has(5, 3); // Check if cell existscellStore.clear(); // Clear all cellsMetadata
Section titled “Metadata”cellStore.setMetadata(5, 3, { status: 'changed' });cellStore.clearMetadata(5, 3);Bulk Operations
Section titled “Bulk Operations”// Load a chunk of data from an arraycellStore.bulkLoadChunk(data, columnKeys, startRow);
// Generate rows with a factory functioncellStore.bulkGenerate(startRow, count, columnKeys, (index) => ({ id: index + 1, name: `Row ${index + 1}`,}));Properties
Section titled “Properties”| Property | Type | Description |
|---|---|---|
size | number | Number of cells stored |
version | number | Mutation counter (increments on every write) |
This sparse representation is efficient for large datasets where most cells may be empty (e.g., 100K rows × 40 columns, but only populated cells use memory).
CellData
Section titled “CellData”Each stored cell is represented by a CellData object:
interface CellData { value: CellValue; // Raw value: string | number | boolean | Date | null displayValue?: string; // Formatted text shown in the cell formula?: string; // Formula string (e.g., "=SUM(A1:A10)") style?: CellStyleRef; // Reference to a shared style in the StylePool type?: CellType; // Overrides column-level type metadata?: CellMetadata; // Status indicators, links, comments}CellValue Types
Section titled “CellValue Types”The value field stores one of:
| Type | Example | Notes |
|---|---|---|
string | "Hello" | Plain text |
number | 42, 3.14 | Numeric values |
boolean | true, false | Rendered as checkbox |
null | null | Empty cell |
The displayValue is the formatted string used for canvas rendering. For example, a number 1234.5 might have a displayValue of "1,234.50" depending on formatting. If displayValue is not set, value is converted to string for rendering.
DataView
Section titled “DataView”The DataView layer provides logical-to-physical row mapping. It sits between the rendering/interaction layer and the CellStore.
Logical index (what the user sees) │ ┌────▼────┐ │ DataView │ ← Remaps indices when sort/filter is active └────┬────┘ │Physical index (actual position in CellStore)When no sort or filter is active, DataView is a passthrough — logical index equals physical index. When sorting or filtering, DataView maintains a mapping array that translates visible row indices to the actual data positions.
// Convert between logical and physicalconst physicalRow = dataView.getPhysicalRow(logicalRow);const logicalRow = dataView.getLogicalRow(physicalRow);
// Get the number of visible rows (may be less than total when filtered)const visibleCount = dataView.visibleRowCount;
// Update total row countdataView.setTotalRowCount(newCount);RowStore and ColStore
Section titled “RowStore and ColStore”Row heights and column widths are stored as Float64Array cumulative position arrays. This enables two key operations:
- O(1) cell rectangle lookup by index — Position of row
niscumulative[n], height iscumulative[n+1] - cumulative[n]. - O(log n) index lookup by pixel coordinate — Binary search on the cumulative array to find which row/column a pixel position falls in.
// Cumulative positions example for 4 rows with height 30px each:// [0, 30, 60, 90, 120]//// Row 2 starts at position 60, ends at 90, height = 30LayoutEngine
Section titled “LayoutEngine”The LayoutEngine combines RowStore and ColStore to compute cell rectangles:
interface CellRect { x: number; // Left edge in pixels y: number; // Top edge in pixels width: number; // Cell width in pixels height: number; // Cell height in pixels}
// O(1) - direct array lookupconst rect = layoutEngine.getCellRect(row, col);
// O(log n) - binary search on cumulative arrayconst { row, col } = layoutEngine.getCellAt(pixelX, pixelY);The getCellAt method is used by EventTranslator for hit-testing — converting mouse click coordinates into cell addresses.
StylePool
Section titled “StylePool”The StylePool deduplicates cell styles. When multiple cells share the same style (font, color, alignment, etc.), they reference the same style object in memory instead of each holding a copy.
// Two cells with identical styles share one objectconst styleId = stylePool.intern({ fontWeight: 'bold', color: '#333' });cellA.style = stylePool.get(styleId);cellB.style = stylePool.get(styleId); // Same reference as cellA.styleThis reduces memory usage significantly in tables where many cells share common formatting.