|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
import { Table, Type } from "apache-arrow" |
|
|
|
type CellType = "blank" | "index" | "columns" | "data" |
|
|
|
export interface ArrowDataframeProto { |
|
data: ArrowTableProto |
|
height: string |
|
width: string |
|
} |
|
|
|
export interface ArrowTableProto { |
|
data: Uint8Array |
|
index: Uint8Array |
|
columns: Uint8Array |
|
styler: Styler |
|
} |
|
|
|
interface Cell { |
|
classNames: string |
|
content: string |
|
id?: string |
|
type: CellType |
|
} |
|
|
|
interface Styler { |
|
caption?: string |
|
displayValuesTable: Table |
|
styles?: string |
|
uuid: string |
|
} |
|
|
|
export class ArrowTable { |
|
private readonly dataTable: Table |
|
private readonly indexTable: Table |
|
private readonly columnsTable: Table |
|
private readonly styler?: Styler |
|
|
|
constructor( |
|
dataBuffer: Uint8Array, |
|
indexBuffer: Uint8Array, |
|
columnsBuffer: Uint8Array, |
|
styler?: any |
|
) { |
|
this.dataTable = Table.from(dataBuffer) |
|
this.indexTable = Table.from(indexBuffer) |
|
this.columnsTable = Table.from(columnsBuffer) |
|
this.styler = styler |
|
? { |
|
caption: styler.get("caption"), |
|
displayValuesTable: Table.from(styler.get("displayValues")), |
|
styles: styler.get("styles"), |
|
uuid: styler.get("uuid"), |
|
} |
|
: undefined |
|
} |
|
|
|
get rows(): number { |
|
return this.indexTable.length + this.columnsTable.numCols |
|
} |
|
|
|
get columns(): number { |
|
return this.indexTable.numCols + this.columnsTable.length |
|
} |
|
|
|
get headerRows(): number { |
|
return this.rows - this.dataRows |
|
} |
|
|
|
get headerColumns(): number { |
|
return this.columns - this.dataColumns |
|
} |
|
|
|
get dataRows(): number { |
|
return this.dataTable.length |
|
} |
|
|
|
get dataColumns(): number { |
|
return this.dataTable.numCols |
|
} |
|
|
|
get uuid(): string | undefined { |
|
return this.styler && this.styler.uuid |
|
} |
|
|
|
get caption(): string | undefined { |
|
return this.styler && this.styler.caption |
|
} |
|
|
|
get styles(): string | undefined { |
|
return this.styler && this.styler.styles |
|
} |
|
|
|
get table(): Table { |
|
return this.dataTable |
|
} |
|
|
|
get index(): Table { |
|
return this.indexTable |
|
} |
|
|
|
get columnTable(): Table { |
|
return this.columnsTable |
|
} |
|
|
|
public getCell = (rowIndex: number, columnIndex: number): Cell => { |
|
const isBlankCell = |
|
rowIndex < this.headerRows && columnIndex < this.headerColumns |
|
const isIndexCell = |
|
rowIndex >= this.headerRows && columnIndex < this.headerColumns |
|
const isColumnsCell = |
|
rowIndex < this.headerRows && columnIndex >= this.headerColumns |
|
|
|
if (isBlankCell) { |
|
const classNames = ["blank"] |
|
if (columnIndex > 0) { |
|
classNames.push("level" + rowIndex) |
|
} |
|
|
|
return { |
|
type: "blank", |
|
classNames: classNames.join(" "), |
|
content: "", |
|
} |
|
} else if (isColumnsCell) { |
|
const dataColumnIndex = columnIndex - this.headerColumns |
|
const classNames = [ |
|
"col_heading", |
|
"level" + rowIndex, |
|
"col" + dataColumnIndex, |
|
] |
|
|
|
return { |
|
type: "columns", |
|
classNames: classNames.join(" "), |
|
content: this.getContent(this.columnsTable, dataColumnIndex, rowIndex), |
|
} |
|
} else if (isIndexCell) { |
|
const dataRowIndex = rowIndex - this.headerRows |
|
const classNames = [ |
|
"row_heading", |
|
"level" + columnIndex, |
|
"row" + dataRowIndex, |
|
] |
|
|
|
return { |
|
type: "index", |
|
id: `T_${this.uuid}level${columnIndex}_row${dataRowIndex}`, |
|
classNames: classNames.join(" "), |
|
content: this.getContent(this.indexTable, dataRowIndex, columnIndex), |
|
} |
|
} else { |
|
const dataRowIndex = rowIndex - this.headerRows |
|
const dataColumnIndex = columnIndex - this.headerColumns |
|
const classNames = [ |
|
"data", |
|
"row" + dataRowIndex, |
|
"col" + dataColumnIndex, |
|
] |
|
const content = this.styler |
|
? this.getContent( |
|
this.styler.displayValuesTable, |
|
dataRowIndex, |
|
dataColumnIndex |
|
) |
|
: this.getContent(this.dataTable, dataRowIndex, dataColumnIndex) |
|
|
|
return { |
|
type: "data", |
|
id: `T_${this.uuid}row${dataRowIndex}_col${dataColumnIndex}`, |
|
classNames: classNames.join(" "), |
|
content, |
|
} |
|
} |
|
} |
|
|
|
public getContent = ( |
|
table: Table, |
|
rowIndex: number, |
|
columnIndex: number |
|
): any => { |
|
const column = table.getColumnAt(columnIndex) |
|
if (column === null) { |
|
return "" |
|
} |
|
|
|
const columnTypeId = this.getColumnTypeId(table, columnIndex) |
|
switch (columnTypeId) { |
|
case Type.Timestamp: { |
|
return this.nanosToDate(column.get(rowIndex)) |
|
} |
|
default: { |
|
return column.get(rowIndex) |
|
} |
|
} |
|
} |
|
|
|
|
|
|
|
|
|
private getColumnTypeId(table: Table, columnIndex: number): Type { |
|
return table.schema.fields[columnIndex].type.typeId |
|
} |
|
|
|
private nanosToDate(nanos: number): Date { |
|
return new Date(nanos / 1e6) |
|
} |
|
} |
|
|