HighTable demo

This commit is contained in:
Kenny Daniel 2024-06-05 16:22:47 -07:00
parent c20cc90af9
commit 1de02e9747
No known key found for this signature in database
GPG Key ID: 90AB653A8CAD7E45
8 changed files with 140 additions and 69 deletions

11
demo/bundle.min.js vendored Normal file

File diff suppressed because one or more lines are too long

1
demo/bundle.min.js.map Normal file

File diff suppressed because one or more lines are too long

@ -99,6 +99,24 @@ input[type="file"] {
}
/* table */
.table-container {
display: flex;
flex-direction: column;
min-height: 0;
flex: 1;
position: relative;
}
.table-scroll {
flex: 1;
overflow: auto;
}
.table-scroll > div {
position: relative;
}
.table-scroll .table {
position: absolute;
}
table {
border-collapse: separate;
border-spacing: 0;
@ -132,6 +150,22 @@ th, td {
overflow: hidden;
white-space: pre-wrap;
}
/* column resize */
.table thead span {
position: absolute;
border-right: 1px solid #ddd;
top: 0;
right: 0;
bottom: 0;
width: 8px;
cursor: col-resize;
transition: background-color 0.2s ease;
}
.table thead span:hover {
background-color: #aab;
}
/* row numbers */
td:first-child {
background-color: #eaeaeb;
@ -148,6 +182,30 @@ td:first-child {
width: 32px;
}
/* table corner */
.table-corner {
background-color: #e4e4e6;
border-right: 1px solid #ccc;
position: absolute;
height: 44px;
width: 32px;
top: 0;
left: 0;
z-index: 15;
box-shadow: inset 0 0 4px rgba(0, 0, 0, 0.2);
}
/* mock row numbers */
.mock-row-label {
content: "";
position: absolute;
top: 0;
left: 0;
bottom: 0;
background: #eaeaeb;
z-index: -10;
}
#filename {
font-size: 10pt;
margin-top: 20px;

@ -1,8 +1,11 @@
import HighTable from 'hightable'
import { compressors } from 'hyparquet-compressors'
import React from 'react'
import ReactDOM from 'react-dom'
import {
parquetMetadata, parquetMetadataAsync, parquetRead, parquetSchema, toJson,
} from '../src/hyparquet.js'
import { asyncBufferFromUrl } from '../src/utils.js'
import { compressors } from './hyparquet-compressors.min.js'
import { fileLayout, fileMetadata } from './layout.js'
/**
@ -97,27 +100,32 @@ function processFile(file) {
}
/**
* @param {AsyncBuffer} asyncBuffer
* @param {AsyncBuffer} file
* @param {FileMetaData} metadata
* @param {string} name
*/
async function render(asyncBuffer, metadata, name) {
renderSidebar(asyncBuffer, metadata, name)
function render(file, metadata, name) {
renderSidebar(file, metadata, name)
const { children } = parquetSchema(metadata)
const header = children.map(child => child.element.name)
const startTime = performance.now()
await parquetRead({
compressors,
file: asyncBuffer,
rowEnd: 1000,
onComplete(/** @type {any[][]} */ data) {
const ms = performance.now() - startTime
console.log(`parsed ${name} in ${ms.toFixed(0)} ms`)
content.appendChild(renderTable(header, data))
const dataframe = {
header: children.map(child => child.element.name),
numRows: Number(metadata.num_rows),
/**
* @param {number} rowStart
* @param {number} rowEnd
* @returns {Promise<any[][]>}
*/
rows(rowStart, rowEnd) {
console.log(`reading rows ${rowStart}-${rowEnd}`)
return new Promise((resolve, reject) => {
parquetRead({ file, compressors, rowStart, rowEnd, onComplete: resolve })
.catch(reject)
})
},
})
}
renderTable(dataframe)
}
/**
@ -143,51 +151,12 @@ fileInput?.addEventListener('change', () => {
})
/**
* @param {string[]} header
* @param {any[][] | Record<string, any>[]} data
* @returns {HTMLTableElement}
* @param {import('hightable').DataFrame} data
*/
function renderTable(header, data) {
const table = document.createElement('table')
const thead = document.createElement('thead')
const tbody = document.createElement('tbody')
const headerRow = document.createElement('tr')
headerRow.appendChild(document.createElement('th'))
for (const columnName of header) {
const th = document.createElement('th')
th.innerText = columnName
headerRow.appendChild(th)
}
thead.appendChild(headerRow)
table.appendChild(thead)
for (const row of data) {
const tr = document.createElement('tr')
const rowNumber = document.createElement('td')
rowNumber.innerText = String(tbody.children.length + 1)
tr.appendChild(rowNumber)
for (const value of Object.values(row)) {
const td = document.createElement('td')
td.innerText = stringify(value)
tr.appendChild(td)
}
tbody.appendChild(tr)
}
table.appendChild(tbody)
return table
}
/**
* @param {any} value
* @param {number} depth
* @returns {string}
*/
function stringify(value, depth = 0) {
if (value === null) return depth ? 'null' : ''
if (value === undefined) return depth ? 'undefined' : ''
if (typeof value === 'bigint') return value.toString()
if (typeof value === 'string') return value
if (Array.isArray(value)) return `[${value.map(v => stringify(v, depth + 1)).join(', ')}]`
if (value instanceof Date) return value.toISOString()
if (typeof value === 'object') return `{${Object.entries(value).map(([k, v]) => `${k}: ${stringify(v, depth + 1)}`).join(', ')}}`
return value
function renderTable(data) {
// Load HighTable.tsx and render
const container = document.getElementById('content')
// @ts-expect-error ReactDOM type issue
const root = ReactDOM.createRoot(container)
root.render(React.createElement(HighTable, { data }))
}

@ -2,13 +2,13 @@
<html lang="en">
<head>
<meta charset="UTF-8">
<title>hyparquet parquet file parser</title>
<title>hyparquet parquet file parser demo</title>
<link rel="icon" href="favicon.png" />
<link rel="stylesheet" href="demo/demo.css">
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Mulish:wght@400;600&display=swap"/>
<meta name="description" content="Online demo of hyparquet: a parser for apache parquet files. Drag and drop parquet files to view parquet data.">
<meta name="author" content="Hyperparam">
<meta name="keywords" content="hyparquet, parquet, parquet file, parquet parser, parquet reader, parquet viewer, parquet data, apache parquet">
<meta name="keywords" content="hyparquet, parquet, parquet file, parquet parser, parquet reader, parquet viewer, parquet data, apache parquet, hightable">
<meta name="viewport" content="width=device-width, initial-scale=1" />
</head>
<body>
@ -21,6 +21,7 @@
<h2>parquet file reader</h2>
<p>
Online demo of <a href="https://github.com/hyparam/hyparquet">hyparquet</a>: a parser for apache parquet files.
Uses <a href="https://github.com/hyparam/hightable">hightable</a> for high performance windowed table viewing.
</p>
<p>
Drag and drop a parquet file onto the dropzone to see parquet data.
@ -39,6 +40,6 @@
</div>
<input id="file-input" type="file">
<script type="module" src="demo/demo.js"></script>
<script type="module" src="demo/bundle.min.js"></script>
</body>
</html>

@ -22,18 +22,29 @@
"scripts": {
"coverage": "vitest run --coverage --coverage.include=src",
"demo": "http-server -o",
"demo:build": "rollup -c",
"lint": "eslint .",
"test": "vitest run"
},
"devDependencies": {
"@types/node": "22.4.1",
"@typescript-eslint/eslint-plugin": "8.2.0",
"@rollup/plugin-commonjs": "26.0.1",
"@rollup/plugin-node-resolve": "15.2.3",
"@rollup/plugin-replace": "5.0.7",
"@rollup/plugin-terser": "0.4.4",
"@types/node": "22.5.1",
"@types/react": "18.3.4",
"@types/react-dom": "18.3.0",
"@typescript-eslint/eslint-plugin": "8.3.0",
"@vitest/coverage-v8": "2.0.5",
"eslint": "8.57.0",
"eslint-plugin-import": "2.29.1",
"eslint-plugin-jsdoc": "50.2.2",
"hightable": "0.2.3",
"http-server": "14.1.1",
"hyparquet-compressors": "0.1.4",
"react": "18.3.1",
"react-dom": "18.3.1",
"rollup": "4.21.1",
"typescript": "5.5.4",
"vitest": "2.0.5"
}

22
rollup.config.js Normal file

@ -0,0 +1,22 @@
import commonjs from '@rollup/plugin-commonjs'
import resolve from '@rollup/plugin-node-resolve'
import replace from '@rollup/plugin-replace'
import terser from '@rollup/plugin-terser'
export default {
input: 'demo/demo.js',
output: {
file: 'demo/bundle.min.js',
format: 'umd',
sourcemap: true,
},
plugins: [
commonjs(),
replace({
'process.env.NODE_ENV': JSON.stringify('production'), // or 'development' based on your build environment
preventAssignment: true,
}),
resolve({ browser: true }),
terser(),
],
}

@ -6,9 +6,7 @@
"module": "nodenext",
"noEmit": true,
"resolveJsonModule": true,
"skipLibCheck": false,
"strict": true,
"target": "esnext",
"strict": true
},
"include": ["src", "test", "demo"]
}