Skip to content

Commit

Permalink
feat(perf): implement cache manager, precompute rows values / indexes
Browse files Browse the repository at this point in the history
  • Loading branch information
ChronicStone committed Apr 25, 2024
1 parent d2541ac commit 57d2961
Show file tree
Hide file tree
Showing 8 changed files with 60 additions and 197 deletions.
181 changes: 0 additions & 181 deletions examples/el.md

This file was deleted.

Binary file modified examples/financial-report.xlsx
Binary file not shown.
Binary file modified examples/kitchen-sink.xlsx
Binary file not shown.
Binary file modified examples/playground.xlsx
Binary file not shown.
File renamed without changes.
17 changes: 9 additions & 8 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* eslint-disable ts/ban-types */
import XLSX, { type WorkSheet, utils } from 'xlsx-js-style'
import type { CellValue, Column, ColumnGroup, ExcelBuildOutput, ExcelBuildParams, ExcelSchema, GenericObject, NestedPaths, Not, SchemaColumnKeys, SheetConfig, SheetParams, SheetTable, SheetTableBuilder, TOutputType, TransformersMap } from './types'
import { applyGroupBorders, buildSheetConfig, computeSheetRange, createCell, getColumnHeaderStyle, getColumnSeparatorIndexes, getPrevRowsHeight, getRowMaxHeight, getRowValue, getSheetChunkMaxHeight, getWorksheetColumnWidths, splitIntoChunks, tableHasSummary } from './utils'
import { CacheManager, applyGroupBorders, buildSheetConfig, computeSheetRange, createCell, getCellValue, getColumnHeaderStyle, getColumnSeparatorIndexes, getPrevRowsHeight, getRowMaxHeight, getSheetChunkMaxHeight, getWorksheetColumnWidths, splitIntoChunks, tableHasSummary } from './utils'

export type * from './types'

Expand Down Expand Up @@ -162,14 +162,15 @@ export class ExcelBuilder<UsedSheetKeys extends string = never> {
titleRowIndexes.push(ROW_OFFSET)

tables.forEach((tableConfig, tableIndex) => {
const tableCache = new CacheManager(tableConfig, tableConfig.content)
if (tableIndex > 0) {
const prevTable = tables[tableIndex - 1]
COL_OFFSET += prevTable.columns.length + TABLE_CELL_OFFSET
}

const tableContentExtraRows = tableConfig.columns.reduce((acc, col) => {
const tableContentExtraRows = tableConfig.columns.reduce((acc, col, columnIndex) => {
return Math.max(acc, tableConfig.content.reduce((acc, row, rowIndex) => {
const values = getRowValue({ row, rowIndex, value: col.value })
const values = tableCache.getCellValue({ columnIndex, rowIndex })
return values.length - 1 + acc
}, 0))
}, 0)
Expand Down Expand Up @@ -204,9 +205,9 @@ export class ExcelBuilder<UsedSheetKeys extends string = never> {
})

tableConfig.content.forEach((row, rowIndex) => {
const maxRowHeight = getRowMaxHeight({ tableConfig, rowIndex })
const prevRowHeight = getPrevRowsHeight({ tableConfig, rowIndex })
const values = getRowValue({ row, rowIndex, value: column.value })
const maxRowHeight = tableCache.getRowMaxHeight(rowIndex)
const prevRowHeight = tableCache.getPrevRowsHeight(rowIndex)
const values = tableCache.getCellValue({ columnIndex: colIndex, rowIndex })

values.forEach((value, valueIndex) => {
const cellRef = utils.encode_cell({
Expand Down Expand Up @@ -279,9 +280,9 @@ export class ExcelBuilder<UsedSheetKeys extends string = never> {

if (tableContentExtraRows > 0) {
tableConfig.content.forEach((row, rowIndex) => {
const prevRowHeight = getPrevRowsHeight({ tableConfig, rowIndex })
const prevRowHeight = tableCache.getPrevRowsHeight(rowIndex)
const rowStart = prevRowHeight + 1 + ROW_OFFSET + (rowHasTitle ? 1 : 0)
const currentRowHeight = getRowMaxHeight({ tableConfig, rowIndex })
const currentRowHeight = tableCache.getRowMaxHeight(rowIndex)
const start = utils.encode_cell({ c: COL_OFFSET, r: rowStart })
const end = utils.encode_cell({ c: COL_OFFSET + tableConfig.columns.length - 1, r: rowStart + (currentRowHeight - 1) })
applyGroupBorders(worksheet, { start, end })
Expand Down
41 changes: 40 additions & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -304,7 +304,7 @@ export function getRowMaxHeight(params: {
}, 1)
}

export function getRowValue(params: {
export function getCellValue(params: {
row: GenericObject
rowIndex: number
value: ReturnType<typeof buildSheetConfig>[number]['tables'][number]['columns'][number]['value']
Expand Down Expand Up @@ -357,3 +357,42 @@ export function createCell(params: {

}
}

// A TABLE CACHE MANAGER, THAT ACCEPTS table: ReturnType<typeof buildSheetConfig>[number]['tables'][number] and rows: GenericObject[] and returns a cache object

export class CacheManager {
private table: ReturnType<typeof buildSheetConfig>[number]['tables'][number]
private rows: GenericObject[]
private rowMaxHeight: Map<number, number> = new Map()
private prevRowsHeight: Map<number, number> = new Map()
private cellValue: Map<string, BaseCellValue[]> = new Map()

constructor(table: ReturnType<typeof buildSheetConfig>[number]['tables'][number], rows: GenericObject[]) {
this.table = table
this.rows = rows

rows.forEach((row, rowIndex) => {
const rowHeight = getRowMaxHeight({ tableConfig: this.table, rowIndex })
const _prevRowsHeight = (this.getPrevRowsHeight(rowIndex - 1) ?? 0) + (this.getRowMaxHeight(rowIndex - 1) ?? 0)
this.rowMaxHeight.set(rowIndex, rowHeight)
this.prevRowsHeight.set(rowIndex, _prevRowsHeight)

table.columns.forEach((column, columnIndex) => {
const cellValue = getCellValue({ row, rowIndex, value: column.value })
this.cellValue.set(`${columnIndex}:${rowIndex}`, cellValue)
})
})
}

getPrevRowsHeight(rowIndex: number) {
return this.prevRowsHeight.get(rowIndex) ?? 0
}

getRowMaxHeight(rowIndex: number) {
return this.rowMaxHeight.get(rowIndex) ?? 0
}

getCellValue({ columnIndex, rowIndex }: { columnIndex: number, rowIndex: number }) {
return this.cellValue.get(`${columnIndex}:${rowIndex}`) ?? []
}
}
18 changes: 11 additions & 7 deletions test/play.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,29 @@ import { ExcelBuilder, ExcelSchemaBuilder } from '../src'

describe('should generate the play excel file', () => {
it('exported', () => {
interface Organization { id: string, name: string }
interface User { id: string, name: string, organizations: Organization[] }
interface User { id: string, name: string }

// Group definition within the schema
const schema = ExcelSchemaBuilder.create<User>()
.column('id', { key: 'id' })
.column('name', { key: 'name' })

.build()

const users: User[] = [{ id: '1', name: 'John', organizations: [{ id: '1', name: 'Org 1' }] }, { id: '2', name: 'Jane', organizations: [{ id: '1', name: 'Org 1' }, { id: '2', name: 'Org 2' }] }, { id: '3', name: 'Bob', organizations: [{ id: '1', name: 'Org 1' }, { id: '2', name: 'Org 2' }, { id: '3', name: 'Org 3' }] }]
console.info('Schema built')
console.time('Generate data')
const users: User[] = Array.from({ length: 400000 }, (_, i) => ({
id: i.toString(),
name: 'John',

}))
console.timeEnd('Generate data')

console.time('build')
const file = ExcelBuilder.create()
.sheet('Sheet1', { tablesPerRow: 2 })
.addTable({ data: users, schema, title: 'Table 1' })
.addTable({ data: users, schema, title: 'Table 2' })
.addTable({ data: users, schema, title: 'Table 3' })
.addTable({ data: users, schema, title: 'Table 4' })
.build({ output: 'buffer' })
console.timeEnd('build')

fs.writeFileSync('./examples/playground.xlsx', file)
})
Expand Down

0 comments on commit 57d2961

Please sign in to comment.