mirror of
https://github.com/asadbek064/hyparquet.git
synced 2025-12-27 15:46:36 +00:00
Demo table
This commit is contained in:
parent
5fdb71fca3
commit
b01fbe7b75
115
demo.css
115
demo.css
@ -8,11 +8,15 @@ body {
|
||||
display: flex;
|
||||
font-family: sans-serif;
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
nav {
|
||||
width: 320px;
|
||||
border-right: 1px solid #ddd;
|
||||
min-width: 320px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
padding: 10px;
|
||||
width: 320px;
|
||||
}
|
||||
h1 {
|
||||
font-size: 20pt;
|
||||
@ -24,32 +28,110 @@ p {
|
||||
margin: 10px 0;
|
||||
width: 300px;
|
||||
}
|
||||
#welcome {
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 20px;
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
.error {
|
||||
color: #c11;
|
||||
font-family: monospace;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#overlay {
|
||||
align-items: center;
|
||||
font-size: 125%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: rgba(240, 240, 240, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
display: none;
|
||||
padding: 12px;
|
||||
z-index: 40;
|
||||
}
|
||||
|
||||
#dropzone {
|
||||
display: flex;
|
||||
flex: 1;
|
||||
max-width: 100vw;
|
||||
}
|
||||
|
||||
#content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
#welcome {
|
||||
border: 2px dashed #08e;
|
||||
border-radius: 10px;
|
||||
color: #444;
|
||||
flex: 1;
|
||||
margin: 10px;
|
||||
overflow: auto;
|
||||
padding: 10px;
|
||||
align-items: center;
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
font-size: 20px;
|
||||
justify-content: center;
|
||||
}
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
.over {
|
||||
background-color: lightblue;
|
||||
#overlay {
|
||||
font-size: 125%;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
background-color: rgba(240, 240, 240, 0.6);
|
||||
backdrop-filter: blur(4px);
|
||||
display: none;
|
||||
padding: 12px;
|
||||
z-index: 40;
|
||||
}
|
||||
.error {
|
||||
color: #c11;
|
||||
.over #overlay {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* table */
|
||||
table {
|
||||
border-collapse: separate;
|
||||
border-spacing: 0;
|
||||
}
|
||||
table:focus-visible {
|
||||
outline: none;
|
||||
}
|
||||
th {
|
||||
background-color: #f8f8f8;
|
||||
border: 1px solid #ddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
background-color: #eaeaeb;
|
||||
border: none;
|
||||
border-top: 4px solid #706fb1;
|
||||
border-bottom: 2px solid #c9c9c9;
|
||||
box-sizing: content-box;
|
||||
color: #444;
|
||||
position: sticky;
|
||||
top: -1px; /* fix 1px gap above thead */
|
||||
user-select: none;
|
||||
}
|
||||
th, td {
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-right: 1px solid #ddd;
|
||||
height: 32px;
|
||||
max-width: 1000px; /* prevent columns expanding */
|
||||
padding: 4px 12px;
|
||||
text-align: left;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
#layout {
|
||||
margin-top: 20px;
|
||||
word-break: break-all;
|
||||
@ -61,6 +143,9 @@ input[type="file"] {
|
||||
.layout a:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
.layout > strong {
|
||||
font-size: 10pt;
|
||||
}
|
||||
.layout div {
|
||||
background-color: rgba(0, 0, 0, 0.05);
|
||||
border: 1px solid #ccc;
|
||||
@ -120,5 +205,5 @@ nav ul,
|
||||
|
||||
#metadata pre {
|
||||
white-space: pre-wrap;
|
||||
break-word: break-all;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
99
demo.js
99
demo.js
@ -1,18 +1,28 @@
|
||||
import { parquetMetadata, parquetMetadataAsync, parquetRead, toJson } from './src/hyparquet.js'
|
||||
import { parquetMetadata, parquetMetadataAsync, parquetRead, parquetSchema, toJson } from './src/hyparquet.js'
|
||||
|
||||
const dropzone = document.getElementById('dropzone')
|
||||
const fileInput = document.getElementById('file-input')
|
||||
const content = document.getElementById('content')
|
||||
const welcome = document.getElementById('welcome')
|
||||
|
||||
const layout = document.getElementById('layout')
|
||||
const metadataDiv = document.getElementById('metadata')
|
||||
const fileInput = document.getElementById('file-input')
|
||||
|
||||
let enterCount = 0
|
||||
|
||||
dropzone.addEventListener('dragenter', e => {
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
dropzone.classList.add('over')
|
||||
enterCount++
|
||||
})
|
||||
|
||||
dropzone.addEventListener('dragover', e => {
|
||||
e.preventDefault()
|
||||
e.dataTransfer.dropEffect = 'copy'
|
||||
dropzone.classList.add('over')
|
||||
})
|
||||
|
||||
dropzone.addEventListener('dragleave', () => {
|
||||
dropzone.classList.remove('over')
|
||||
enterCount--
|
||||
if (!enterCount) dropzone.classList.remove('over')
|
||||
})
|
||||
|
||||
dropzone.addEventListener('drop', e => {
|
||||
@ -37,18 +47,19 @@ dropzone.addEventListener('drop', e => {
|
||||
})
|
||||
|
||||
async function processUrl(url) {
|
||||
content.innerHTML = ''
|
||||
try {
|
||||
// Check if file is accessible and get its size
|
||||
const head = await fetch(url, { method: 'HEAD' })
|
||||
if (!head.ok) {
|
||||
dropzone.innerHTML = `<strong>${url}</strong>`
|
||||
dropzone.innerHTML += `<div class="error">Error fetching file\n${head.status} ${head.statusText}</div>`
|
||||
content.innerHTML = `<strong>${url}</strong>`
|
||||
content.innerHTML += `<div class="error">Error fetching file\n${head.status} ${head.statusText}</div>`
|
||||
return
|
||||
}
|
||||
const size = head.headers.get('content-length')
|
||||
if (!size) {
|
||||
dropzone.innerHTML = `<strong>${url}</strong>`
|
||||
dropzone.innerHTML += '<div class="error">Error fetching file\nNo content-length header</div>'
|
||||
content.innerHTML = `<strong>${url}</strong>`
|
||||
content.innerHTML += '<div class="error">Error fetching file\nNo content-length header</div>'
|
||||
return
|
||||
}
|
||||
// Construct an AsyncBuffer that fetches file chunks
|
||||
@ -56,6 +67,7 @@ async function processUrl(url) {
|
||||
byteLength: Number(size),
|
||||
slice: async (start, end) => {
|
||||
const rangeEnd = end === undefined ? '' : end - 1
|
||||
console.log(`Fetch ${url} bytes=${start}-${rangeEnd}`)
|
||||
const res = await fetch(url, {
|
||||
headers: { Range: `bytes=${start}-${rangeEnd}` },
|
||||
})
|
||||
@ -63,40 +75,54 @@ async function processUrl(url) {
|
||||
},
|
||||
}
|
||||
const metadata = await parquetMetadataAsync(asyncBuffer)
|
||||
url = `<a href="${url}">${url}</a>`
|
||||
renderSidebar(asyncBuffer, metadata, url)
|
||||
await render(asyncBuffer, metadata, `<a href="${url}">${url}</a>`)
|
||||
} catch (e) {
|
||||
console.error('Error fetching file', e)
|
||||
dropzone.innerHTML = `<strong>${url}</strong>`
|
||||
dropzone.innerHTML += `<div class="error">Error fetching file\n${e}</div>`
|
||||
content.innerHTML = `<strong>${url}</strong>`
|
||||
content.innerHTML += `<div class="error">Error fetching file\n${e}</div>`
|
||||
}
|
||||
}
|
||||
|
||||
function processFile(file) {
|
||||
content.innerHTML = ''
|
||||
const reader = new FileReader()
|
||||
reader.onload = e => {
|
||||
reader.onload = async e => {
|
||||
try {
|
||||
const arrayBuffer = e.target.result
|
||||
const metadata = parquetMetadata(arrayBuffer)
|
||||
renderSidebar(arrayBuffer, metadata, file.name)
|
||||
const startTime = performance.now()
|
||||
// parquetRead({ file: arrayBuffer, onComplete(data) {
|
||||
// const ms = performance.now() - startTime
|
||||
// console.log(`parsed ${file.name} in ${ms.toFixed(0)} ms`)
|
||||
// } }) // TODO
|
||||
await render(arrayBuffer, metadata, file.name)
|
||||
} catch (e) {
|
||||
console.error('Error parsing file', e)
|
||||
dropzone.innerHTML = `<strong>${file.name}</strong>`
|
||||
dropzone.innerHTML += `<div class="error">Error parsing file\n${e}</div>`
|
||||
content.innerHTML = `<strong>${file.name}</strong>`
|
||||
content.innerHTML += `<div class="error">Error parsing file\n${e}</div>`
|
||||
}
|
||||
}
|
||||
reader.onerror = e => {
|
||||
console.error('Error reading file', e)
|
||||
dropzone.innerText = `Error reading file\n${e.target.error}`
|
||||
content.innerHTML = `<strong>${file.name}</strong>`
|
||||
content.innerHTML += `<div class="error">Error reading file\n${e.target.error}</div>`
|
||||
}
|
||||
reader.readAsArrayBuffer(file)
|
||||
}
|
||||
|
||||
async function render(asyncBuffer, metadata, name) {
|
||||
renderSidebar(asyncBuffer, metadata, name)
|
||||
|
||||
const { children } = parquetSchema(metadata)
|
||||
const header = children.map(child => child.element.name)
|
||||
|
||||
const startTime = performance.now()
|
||||
await parquetRead({
|
||||
file: asyncBuffer,
|
||||
rowEnd: 1000,
|
||||
onComplete(data) {
|
||||
const ms = performance.now() - startTime
|
||||
console.log(`parsed ${name} in ${ms.toFixed(0)} ms`)
|
||||
content.appendChild(renderTable(header, data))
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
function renderSidebar(asyncBuffer, metadata, name) {
|
||||
layout.innerHTML = `<strong>${name}</strong>`
|
||||
// render file layout
|
||||
@ -106,7 +132,7 @@ function renderSidebar(asyncBuffer, metadata, name) {
|
||||
metadataDiv.appendChild(fileMetadata(toJson(metadata)))
|
||||
}
|
||||
|
||||
dropzone.addEventListener('click', () => {
|
||||
welcome.addEventListener('click', () => {
|
||||
fileInput.click()
|
||||
})
|
||||
|
||||
@ -175,3 +201,28 @@ function fileMetadata(metadata) {
|
||||
})
|
||||
return div
|
||||
}
|
||||
|
||||
function renderTable(header, data) {
|
||||
const table = document.createElement('table')
|
||||
const thead = document.createElement('thead')
|
||||
const tbody = document.createElement('tbody')
|
||||
const headerRow = document.createElement('tr')
|
||||
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')
|
||||
for (const value of Object.values(row)) {
|
||||
const td = document.createElement('td')
|
||||
td.innerText = value
|
||||
tr.appendChild(td)
|
||||
}
|
||||
tbody.appendChild(tr)
|
||||
}
|
||||
table.appendChild(tbody)
|
||||
return table
|
||||
}
|
||||
|
||||
41
index.html
41
index.html
@ -7,24 +7,31 @@
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Mulish:wght@400;600&display=swap"/>
|
||||
</head>
|
||||
<body>
|
||||
<nav>
|
||||
<h1>hyparquet</h1>
|
||||
<h2>parquet file reader</h2>
|
||||
<p>
|
||||
Online demo of <a href="https://github.com/hyparam/hyparquet">hyparquet</a>: a parser for apache parquet files.
|
||||
</p>
|
||||
<p>
|
||||
Drag and drop a parquet file onto the dropzone to see parquet data.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/hyparam/hyparquet">github</a></li>
|
||||
<li><a href="https://www.npmjs.com/package/hyparquet">npm</a></li>
|
||||
</ul>
|
||||
<div id="layout" class="layout"></div>
|
||||
<div id="metadata" class="layout"></div>
|
||||
</nav>
|
||||
<div id="dropzone">
|
||||
<label id="welcome">Drop .parquet file here</label>
|
||||
<div id="overlay">
|
||||
Drop .parquet file
|
||||
</div>
|
||||
<nav>
|
||||
<h1>hyparquet</h1>
|
||||
<h2>parquet file reader</h2>
|
||||
<p>
|
||||
Online demo of <a href="https://github.com/hyparam/hyparquet">hyparquet</a>: a parser for apache parquet files.
|
||||
</p>
|
||||
<p>
|
||||
Drag and drop a parquet file onto the dropzone to see parquet data.
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="https://github.com/hyparam/hyparquet">github</a></li>
|
||||
<li><a href="https://www.npmjs.com/package/hyparquet">npm</a></li>
|
||||
</ul>
|
||||
<div id="layout" class="layout"></div>
|
||||
<div id="metadata" class="layout"></div>
|
||||
</nav>
|
||||
<div id="content">
|
||||
<div id="welcome">
|
||||
Drop .parquet file here
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<input id="file-input" type="file">
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user