Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 31 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<a href="https://perspective-dev.github.io">
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/perspective-dev/perspective/raw/master/docs/static/svg/perspective-logo-dark.svg?raw=true">
<img width="260" src="https://github.com/perspective-dev/perspective/raw/master/docs/static/svg/perspective-logo-light.svg?raw=true" />
<img width="260" alt="Perspective" src="https://github.com/perspective-dev/perspective/raw/master/docs/static/svg/perspective-logo-light.svg?raw=true" />
</picture>
</a>
<br/><br/>
Expand All @@ -13,18 +13,25 @@
[![PyPI](https://img.shields.io/pypi/v/perspective-python.svg?style=for-the-badge)](https://pypi.python.org/pypi/perspective-python)
[![crates.io](https://img.shields.io/crates/v/perspective?style=for-the-badge)](https://crates.io/crates/perspective)

Perspective is an interactive analytics and data visualization component for
large, real-time and streaming datasets. Build user-configurable reports,
dashboards, notebooks and applications, backed by a high-performance streaming
query engine that runs in-browser via WebAssembly or server-side in Python,
Node.js and Rust — or delegates to a database you already have.
<!-- site:begin -->

Perspective is an open-source data grid, pivot table and charting component for
large, real-time and streaming datasets. Build user-configurable reports,
dashboards, notebooks and embedded analytics applications, backed by a
high-performance streaming query engine that runs in-browser via WebAssembly or
server-side in Python, Node.js and Rust — or delegates to a database you
already have.

<!-- site:end -->

<br/>
<a href="https://perspective-dev.github.io"><picture>
<source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/collage.png" />
<img src="https://perspective-dev.github.io/projects/light/collage.png" />
<img alt="A collage of Perspective data grids, pivot tables, charts and maps" src="https://perspective-dev.github.io/projects/light/collage.png" />
</picture></a>

<!-- site:begin -->

## Features

- A data-reactive UI packaged as a
Expand All @@ -35,7 +42,8 @@ Node.js and Rust — or delegates to a database you already have.

- A fast, memory-efficient streaming query engine written in C++ and compiled
for [WebAssembly](https://webassembly.org/) (including a 64-bit `memory64`
build for in-browser datasets larger than 4GB),
build for in-browser datasets larger than 4GB, and support for paging
columns to disk via OPFS in the browser or memory-mapped files natively),
[Python](https://www.python.org/) and [Rust](https://www.rust-lang.org/).
Tables update incrementally and views tick in real time, with reactive
joins across tables, a columnar expression language based on
Expand Down Expand Up @@ -90,25 +98,25 @@ Node.js and Rust — or delegates to a database you already have.
<td>Superstore</td><td>Workspace</td><td>Webcam</td>
</tr>
<tr>
<td><a href="https://perspective-dev.github.io/?project=superstore"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/superstore.png" /><img src="https://perspective-dev.github.io/projects/light/superstore.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=superstore-overview"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/superstore-overview.png" /><img src="https://perspective-dev.github.io/projects/light/superstore-overview.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=webcam-video-wall"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/webcam-video-wall.png" /><img src="https://perspective-dev.github.io/projects/light/webcam-video-wall.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=superstore"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/superstore.png" /><img alt="Perspective example: superstore" src="https://perspective-dev.github.io/projects/light/superstore.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=superstore-overview"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/superstore-overview.png" /><img alt="Perspective example: superstore overview" src="https://perspective-dev.github.io/projects/light/superstore-overview.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=webcam-video-wall"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/webcam-video-wall.png" /><img alt="Perspective example: webcam video wall" src="https://perspective-dev.github.io/projects/light/webcam-video-wall.png" /></picture></a></td>
</tr>
<tr>
<td>Raycasting</td><td>Market</td><td>NYPD</td>
</tr>
<tr>
<td><a href="https://perspective-dev.github.io/?project=raycasting"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/raycasting.png" /><img src="https://perspective-dev.github.io/projects/light/raycasting.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=market-trading-desk"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/market-trading-desk.png" /><img src="https://perspective-dev.github.io/projects/light/market-trading-desk.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=nypd-4"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/nypd-4.png" /><img src="https://perspective-dev.github.io/projects/light/nypd-4.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=raycasting"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/raycasting.png" /><img alt="Perspective example: raycasting" src="https://perspective-dev.github.io/projects/light/raycasting.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=market-trading-desk"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/market-trading-desk.png" /><img alt="Perspective example: market trading desk" src="https://perspective-dev.github.io/projects/light/market-trading-desk.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=nypd-4"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/nypd-4.png" /><img alt="Perspective example: nypd 4" src="https://perspective-dev.github.io/projects/light/nypd-4.png" /></picture></a></td>
</tr>
<tr>
<td>Movies</td><td>Evictions</td><td>Fractal</td>
</tr>
<tr>
<td><a href="https://perspective-dev.github.io/?project=movies"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/movies.png" /><img src="https://perspective-dev.github.io/projects/light/movies.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=evictions-2"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/evictions-2.png" /><img src="https://perspective-dev.github.io/projects/light/evictions-2.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=fractal"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/fractal.png" /><img src="https://perspective-dev.github.io/projects/light/fractal.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=movies"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/movies.png" /><img alt="Perspective example: movies" src="https://perspective-dev.github.io/projects/light/movies.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=evictions-2"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/evictions-2.png" /><img alt="Perspective example: evictions 2" src="https://perspective-dev.github.io/projects/light/evictions-2.png" /></picture></a></td>
<td><a href="https://perspective-dev.github.io/?project=fractal"><picture><source media="(prefers-color-scheme: dark)" srcset="https://perspective-dev.github.io/projects/dark/fractal.png" /><img alt="Perspective example: fractal" src="https://perspective-dev.github.io/projects/light/fractal.png" /></picture></a></td>
</tr>
</tbody>
</table>
Expand All @@ -122,18 +130,18 @@ Node.js and Rust — or delegates to a database you already have.
<td><a href="https://github.com/sc1f"><code>@sc1f</code></a></td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=v5Y5ftlGNhU"><img width="240" src="https://img.youtube.com/vi/v5Y5ftlGNhU/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=lDpIu4dnp78"><img width="240" src="https://img.youtube.com/vi/lDpIu4dnp78/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=IO-HJsGdleE"><img width="240" src="https://img.youtube.com/vi/IO-HJsGdleE/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=v5Y5ftlGNhU"><img width="240" alt="Perspective conference talk" src="https://img.youtube.com/vi/v5Y5ftlGNhU/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=lDpIu4dnp78"><img width="240" alt="Perspective conference talk" src="https://img.youtube.com/vi/lDpIu4dnp78/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=IO-HJsGdleE"><img width="240" alt="Perspective conference talk" src="https://img.youtube.com/vi/IO-HJsGdleE/0.jpg" /></a></td>
</tr>
<tr>
<td><a href="https://github.com/texodus"><code>@texodus</code></a></td>
<td><a href="https://github.com/texodus"><code>@texodus</code></a></td>
<td></td>
</tr>
<tr>
<td><a href="https://www.youtube.com/watch?v=no0qChjvdgQ"><img width="240" src="https://img.youtube.com/vi/no0qChjvdgQ/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=0ut-ynvBpGI"><img width="240" src="https://img.youtube.com/vi/0ut-ynvBpGI/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=no0qChjvdgQ"><img width="240" alt="Perspective conference talk" src="https://img.youtube.com/vi/no0qChjvdgQ/0.jpg" /></a></td>
<td><a href="https://www.youtube.com/watch?v=0ut-ynvBpGI"><img width="240" alt="Perspective conference talk" src="https://img.youtube.com/vi/0ut-ynvBpGI/0.jpg" /></a></td>
<td></td>
</tr>
</tbody></table><br/><br/>
Expand All @@ -144,7 +152,7 @@ Node.js and Rust — or delegates to a database you already have.
<br/>
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/openjs-foundation/artwork/raw/master/openjs_foundation/openjs_foundation-logo-horizontal-white.svg?raw=true">
<img width="200" src="https://github.com/openjs-foundation/artwork/raw/master/openjs_foundation/openjs_foundation-logo-horizontal-black.svg?raw=true">
<img width="200" alt="OpenJS Foundation" src="https://github.com/openjs-foundation/artwork/raw/master/openjs_foundation/openjs_foundation-logo-horizontal-black.svg?raw=true">
</picture>
<br/>
<br/>
Expand Down
25 changes: 20 additions & 5 deletions docs/build.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,12 @@ import * as path from "node:path";
import { createRequire } from "module";
import { bundleAsync as bundleCssAsync, composeVisitors } from "lightningcss";
import { fileURLToPath } from "node:url";
import {
postprocessGuide,
writeCrawlFiles,
writeLlms,
writePages,
} from "./build.seo.mjs";

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const DIST = path.join(__dirname, "dist");
Expand All @@ -29,6 +35,8 @@ const RELOAD_PORT = Number(

const HTML_PAGES = ["index.html"];

const PAGE_URLS = { pages: [], guide: [] };

function copyRecursive(src, dest) {
if (!fs.existsSync(src)) return;
const stat = fs.statSync(src);
Expand Down Expand Up @@ -188,19 +196,21 @@ async function buildCss() {
fs.writeFileSync(path.join(DIST, "style.css"), code);
}

function copyHtml() {
async function copyHtml() {
for (const html of HTML_PAGES) {
const source = fs.readFileSync(
path.join(__dirname, "src", html),
"utf8",
);

const output = WATCH
const template = WATCH
? source.replace("</body>", `${RELOAD_SNIPPET} </body>`)
: source;

fs.writeFileSync(path.join(DIST, html), output);
PAGE_URLS.pages = await writePages(template, DIST);
}

writeCrawlFiles(DIST, [...PAGE_URLS.pages, ...PAGE_URLS.guide]);
}

function copyStatic() {
Expand All @@ -216,6 +226,10 @@ function copyStatic() {
} else {
console.warn("Missing superstore-arrow; Superstore Projects will 404.");
}

PAGE_URLS.guide = postprocessGuide(DIST);
writeLlms(DIST);
writeCrawlFiles(DIST, [...PAGE_URLS.pages, ...PAGE_URLS.guide]);
}

function copyDocsBundle() {
Expand Down Expand Up @@ -289,6 +303,7 @@ function esbuildOptions() {
splitting: true,
format: "esm",
outdir: DIST,
publicPath: "/",
minify: !WATCH,
sourcemap: true,
target: ["es2022"],
Expand All @@ -307,7 +322,7 @@ async function build() {
fs.mkdirSync(DIST, { recursive: true });
await buildCss();
await esbuild.build(esbuildOptions());
copyHtml();
await copyHtml();
copyStatic();
copyDocsBundle();
console.log("Build complete: dist/");
Expand Down Expand Up @@ -346,7 +361,7 @@ async function watch() {
console.error(` ✗ css failed:\n${e.message ?? e}`);
}

copyHtml();
await copyHtml();
copyStatic();
copyDocsBundle();

Expand Down
42 changes: 35 additions & 7 deletions docs/build.projects.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ const EVICTIONS_URL =

const MOVIES_URL = "https://vega.github.io/editor/data/movies.json";

const RELEASE_ASSETS =
"https://github.com/perspective-dev/perspective/releases/latest/download";

const BENCHMARKS = ["benchmark-js.arrow", "benchmark-python.arrow"];

const MOVIES_SCHEMA = {
Title: "string",
"US Gross": "float",
Expand Down Expand Up @@ -140,6 +145,17 @@ async function buildMoviesArrow(out) {
fs.writeFileSync(out, arrow);
}

function buildReleaseAsset(name) {
return async (out) => {
const response = await fetch(`${RELEASE_ASSETS}/${name}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status} ${response.statusText}`);
}

fs.writeFileSync(out, new Uint8Array(await response.arrayBuffer()));
};
}

async function buildNypdArrow(out) {
const response = await fetch(NYPD_URL);
if (!response.ok) {
Expand All @@ -164,20 +180,29 @@ async function buildOlympicsArrow(out) {
}
}

async function prepareDataset(name, build) {
async function prepareDataset(name, build, { refresh = false } = {}) {
fs.mkdirSync(DATA, { recursive: true });
const cached = path.join(DATA, name);
if (!fs.existsSync(cached)) {
const stale = refresh && fs.existsSync(cached);
if (refresh || !fs.existsSync(cached)) {
const fresh = `${cached}.tmp`;
try {
await build(cached);
await build(fresh);
fs.renameSync(fresh, cached);
console.log(`Wrote ${name}`);
} catch (e) {
fs.rmSync(cached, { force: true });
fs.rmSync(fresh, { force: true });
if (!stale) {
console.warn(
` ✗ ${name}: ${e.message ?? e} — its Projects will 404.`,
);

return;
}

console.warn(
` ✗ ${name}: ${e.message ?? e} — its Projects will 404.`,
` ✗ ${name}: ${e.message ?? e} — keeping the cached copy.`,
);

return;
}
}

Expand Down Expand Up @@ -207,6 +232,9 @@ async function run() {
await prepareDataset("nypdccrb.arrow", buildNypdArrow);
await prepareDataset("evictions.arrow", buildEvictionsArrow);
await prepareDataset("movies.arrow", buildMoviesArrow);
for (const name of BENCHMARKS) {
await prepareDataset(name, buildReleaseAsset(name), { refresh: true });
}

const server = new perspective.WebSocketServer({
port: PORT,
Expand Down
Loading
Loading