From 81bf2845912c84a28a2025152b09e04b9cceedb5 Mon Sep 17 00:00:00 2001 From: Eugene Date: Mon, 24 Jul 2023 16:35:38 +0300 Subject: [PATCH] Added caching for render sheet viewport for O(n) --- src/components/sheet.ts | 20 ++++++------ src/main.ts | 47 ++++++++++++++++++++++++++--- src/modules/cache.ts | 67 +++++++++++++++++++++++++++++++++++++++++ src/modules/viewport.ts | 34 +++------------------ 4 files changed, 124 insertions(+), 44 deletions(-) create mode 100644 src/modules/cache.ts diff --git a/src/components/sheet.ts b/src/components/sheet.ts index c620505..76e1317 100644 --- a/src/components/sheet.ts +++ b/src/components/sheet.ts @@ -22,7 +22,7 @@ export class Sheet { this.element = canvas const ctx = this.element.getContext('2d') - if(!ctx) throw new Error('Enable hardware acceleration') + if (!ctx) throw new Error('Enable hardware acceleration') this.ctx = ctx } @@ -30,17 +30,17 @@ export class Sheet { getCellByCoords(x: number, y: number): Position { let row = 0; let height = 0 - while(height <= y) { + while (height <= y) { height += this.root.config.rows[row].height - if(height >= y) break; + if (height >= y) break; row++; } let col = 0; let width = 0; - while(width <= x) { + while (width <= x) { width += this.root.config.columns[col].width - if(width >= x) break; + if (width >= x) break; col++; } @@ -48,7 +48,7 @@ export class Sheet { } renderCell(position: Position) { - const {column, row} = position + const { column, row } = position this.root.data[row][column].render(this.root) } @@ -58,11 +58,11 @@ export class Sheet { const lastRowIdx = this.root.viewport.lastRow + 3 const firstColIdx = this.root.viewport.firstCol - for(let row = firstRowIdx; row <= lastRowIdx; row++) { - for(let col = firstColIdx; col <= lastColIdx; col++ ) { - if(!this.root.config.columns[col] || !this.root.config.rows[row]) break; //* Prevent read undefined + for (let row = firstRowIdx; row <= lastRowIdx; row++) { + for (let col = firstColIdx; col <= lastColIdx; col++) { + if (!this.root.config.columns[col] || !this.root.config.rows[row]) break; //* Prevent read undefined - this.renderCell({column: col, row}) + this.renderCell({ column: col, row }) } } diff --git a/src/main.ts b/src/main.ts index 2be42a8..07dec92 100644 --- a/src/main.ts +++ b/src/main.ts @@ -11,6 +11,7 @@ import { Styles } from "./modules/styles"; import { Viewport } from "./modules/viewport"; import './scss/main.scss' import { createSampleConfig, createSampleData } from "./utils/createData"; +import { Cache, CachedColumn, CachedRow } from "./modules/cache"; /* ! Component structure @@ -41,24 +42,26 @@ export class Spreadsheet { public data: Cell[][] public viewport: Viewport public selection: Selection + public cache: Cache constructor(target: string | HTMLElement, props?: SpreadsheetConstructorProperties) { - const config = createSampleConfig(750, 750) + const config = createSampleConfig(10000, 600) if (props?.view) { config.view = props.view } this.config = new Config(config) this.sheet = new Sheet(this) - const data = createSampleData(750, 750) + const data = createSampleData(10000, 600) this.table = new Table(this) this.scroller = new Scroller(this) this.toolbar = new Toolbar(this) this.header = new Header(this) - this.editor = new Editor(this) + this.editor = new Editor(this) + this.cache = this.getInitialCache() this.viewport = new Viewport(this, this.scroller.getViewportBoundlingRect()) this.selection = new Selection() - + this.data = data this.styles = new Styles() @@ -66,6 +69,42 @@ export class Spreadsheet { this.appendTableToTarget(target) } + private getInitialCache(): Cache { + const cachedCols: CachedColumn[] = [] + let currentWidth = 0 + for(let i = 0; i <= this.config.columns.length - 1; i++) { + const col = this.config.columns[i] + currentWidth += col.width + const cacheCol = new CachedColumn({ + xPos: currentWidth, + colIdx: i + }) + cachedCols.push(cacheCol) + } + + const cachedRows: CachedRow[] = [] + let currentHeight = 0 + for(let i = 0; i <= this.config.rows.length - 1; i++) { + const row = this.config.rows[i] + currentHeight += row.height + const cacheRow = new CachedRow({ + yPos: currentHeight, + rowIdx: i + }) + cachedRows.push(cacheRow) + } + + + const cache = new Cache({ + columns: cachedCols, + rows: cachedRows + }) + + console.log("CACHE: ", cache) + console.log("CONFIG: ", this.config) + return cache + } + private buildComponent(): void { const content = document.createElement('div') //* Abstract diff --git a/src/modules/cache.ts b/src/modules/cache.ts new file mode 100644 index 0000000..5de0330 --- /dev/null +++ b/src/modules/cache.ts @@ -0,0 +1,67 @@ +export interface CachedColumnProperties { + xPos: number + colIdx: number +} + +export class CachedColumn { + xPos: number + colIdx: number + + constructor(props: CachedColumnProperties) { + this.xPos = props.xPos + this.colIdx = props.colIdx + } +} + +export interface CachedRowProperties { + yPos: number + rowIdx: number +} + +export class CachedRow { + yPos: number + rowIdx: number + + constructor(props: CachedRowProperties) { + this.yPos = props.yPos + this.rowIdx = props.rowIdx + } +} + +export interface CacheConstructorProps { + columns: CachedColumn[] + rows: CachedRow[] +} + +export class Cache { + public columns: CachedColumn[] + public rows: CachedRow[] + constructor(initial: CacheConstructorProps) { + this.columns = initial.columns + this.rows = initial.rows + } + + public getRowByYCoord(y: number): number { + let rowIdx = 0; + for (let i = 0; i < this.rows.length; i++) { + if (y <= this.rows[i].yPos) { //* Intersection detect + rowIdx = i; + break; + } + } + return rowIdx; + } + + + public getColumnByXCoord(x: number): number { + let colIdx = 0; + for (let i = 0; i < this.columns.length; i++) { + if (x <= this.columns[i].xPos) { //* Intersection detect + colIdx = i; + break; + } + } + return colIdx; + } + +} \ No newline at end of file diff --git a/src/modules/viewport.ts b/src/modules/viewport.ts index 3b2a386..1d331f7 100644 --- a/src/modules/viewport.ts +++ b/src/modules/viewport.ts @@ -55,49 +55,23 @@ export class Viewport { /** Get index of first row in viewport */ private getFirstRow(): number { - let rowIdx = 0 - for (let idx = 0, currHeight = 0; currHeight <= this.top; idx++) { - currHeight += this.root.config.rows[idx].height - rowIdx = idx - } + let rowIdx = this.root.cache.getRowByYCoord(this.top) return rowIdx } private getLastRow(): number { - let rowIdx = this.getFirstRow() - let height = this.top - - while (height <= this.bottom) { - height += this.root.config.rows[rowIdx].height - if (height >= this.bottom) break; - rowIdx++; - } - + let rowIdx = this.root.cache.getRowByYCoord(this.bottom) return rowIdx } private getFirstCol(): number { - let colIdx = 0; - let currWidth = 0 - - while (currWidth <= this.left) { - currWidth += this.root.config.columns[colIdx].width - if (currWidth >= this.left) break; - colIdx += 1 - } + let colIdx = this.root.cache.getColumnByXCoord(this.left) return colIdx } private getLastCol(): number { - let colIdx = this.getFirstCol() - let width = this.left - - while (width <= this.right) { - width += this.root.config.columns[colIdx].width - if (width >= this.right) break; - colIdx++; - } + let colIdx = this.root.cache.getColumnByXCoord(this.right) return colIdx }