/** * @license * Copyright 2018-2019 Streamlit Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ 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) } } } /** * Returns apache-arrow specific typeId of column. */ private getColumnTypeId(table: Table, columnIndex: number): Type { return table.schema.fields[columnIndex].type.typeId } private nanosToDate(nanos: number): Date { return new Date(nanos / 1e6) } }