chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<title>Perspective - Block</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Interactive analytics and data visualization component for large and streaming datasets."
|
||||
/>
|
||||
<link rel="icon" href="https://openjsf.org/favicon.ico" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container">
|
||||
<nav class="navbar">
|
||||
<div class="navbar__inner">
|
||||
<a href="/" class="navbar__logo"><img alt="Perspective" src="/svg/perspective-logo-dark.svg" /></a>
|
||||
<ul class="navbar__links">
|
||||
<li><a href="/guide/">Docs</a></li>
|
||||
<li><a href="/examples.html">Examples</a></li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/perspective-dev/perspective"
|
||||
>GitHub</a
|
||||
>
|
||||
</li>
|
||||
<li id="theme-toggle"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-wrapper">
|
||||
<div class="block-detail" id="block-detail"></div>
|
||||
<br /><br />
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__inner">
|
||||
<img
|
||||
class="footer__logo"
|
||||
id="footer-logo"
|
||||
alt="OpenJS Foundation Logo"
|
||||
src="/img/openjs_foundation-logo-horizontal-white.png"
|
||||
/>
|
||||
<div class="footer__copyright">
|
||||
<br /><br />Copyright © 2017 OpenJS Foundation and
|
||||
Perspective contributors.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script type="module" src="block.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,93 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { initTheme, createThemeToggle } from "./components/theme.js";
|
||||
import Prism from "prismjs";
|
||||
import "prismjs/components/prism-json";
|
||||
import "prismjs/components/prism-markdown";
|
||||
import "prismjs/components/prism-css";
|
||||
|
||||
initTheme();
|
||||
document.getElementById("theme-toggle")!.replaceWith(createThemeToggle());
|
||||
|
||||
const EXT_TO_LANG: Record<string, string> = {
|
||||
js: "javascript",
|
||||
mjs: "javascript",
|
||||
ts: "javascript",
|
||||
html: "markup",
|
||||
css: "css",
|
||||
json: "json",
|
||||
md: "markdown",
|
||||
};
|
||||
|
||||
const container = document.getElementById("block-detail")!;
|
||||
const params = new URL(document.location.href).searchParams;
|
||||
const example = params.get("example");
|
||||
|
||||
if (!example) {
|
||||
container.innerHTML = "<p>No example specified.</p>";
|
||||
} else {
|
||||
document.title = `Perspective - ${example}`;
|
||||
|
||||
const h1 = document.createElement("h1");
|
||||
h1.textContent = example;
|
||||
container.appendChild(h1);
|
||||
|
||||
const iframe = document.createElement("iframe");
|
||||
iframe.width = "960";
|
||||
iframe.height = "640";
|
||||
iframe.src = `/blocks/${example}/index.html`;
|
||||
container.appendChild(iframe);
|
||||
|
||||
const br = document.createElement("br");
|
||||
container.appendChild(br);
|
||||
|
||||
const link = document.createElement("a");
|
||||
link.href = `/blocks/${example}/index.html`;
|
||||
link.className = "block-detail__link";
|
||||
link.textContent = "Open in New Tab";
|
||||
link.target = "_blank";
|
||||
container.appendChild(link);
|
||||
|
||||
const br2 = document.createElement("br");
|
||||
container.appendChild(br2);
|
||||
|
||||
// Fetch manifest and display all source files
|
||||
fetch("/blocks/manifest.json")
|
||||
.then((res) => res.json())
|
||||
.then(async (manifest: Record<string, string[]>) => {
|
||||
const files = manifest[example] || [];
|
||||
for (const filename of files) {
|
||||
const res = await fetch(`/blocks/${example}/${filename}`);
|
||||
if (!res.ok) continue;
|
||||
const contents = await res.text();
|
||||
|
||||
const title = document.createElement("div");
|
||||
title.className = "block-detail__file-title";
|
||||
title.textContent = filename;
|
||||
container.appendChild(title);
|
||||
|
||||
const pre = document.createElement("pre");
|
||||
const code = document.createElement("code");
|
||||
const ext = filename.split(".").pop() || "";
|
||||
const lang = EXT_TO_LANG[ext] || "plain";
|
||||
const grammar = Prism.languages[lang];
|
||||
if (grammar) {
|
||||
code.innerHTML = Prism.highlight(contents, grammar, lang);
|
||||
} else {
|
||||
code.textContent = contents;
|
||||
}
|
||||
pre.appendChild(code);
|
||||
container.appendChild(pre);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,132 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { random_row } from "../data/random.js";
|
||||
import { LAYOUTS } from "../data/layouts.js";
|
||||
import { getPerspectiveTheme } from "./theme.js";
|
||||
|
||||
let TABLE: any;
|
||||
let VIEWER: any;
|
||||
let FREQ = 100;
|
||||
let REALTIME_PAUSED = true;
|
||||
let selectedId = "sparkgrid";
|
||||
|
||||
function update(table: any, viewer: any) {
|
||||
if (!REALTIME_PAUSED && FREQ <= 189.9) {
|
||||
const viewport_height = document.documentElement.clientHeight;
|
||||
if (viewport_height - window.scrollY > 0) {
|
||||
const arr = [];
|
||||
for (let i = 0; i < 10; i++) {
|
||||
arr.push(random_row());
|
||||
}
|
||||
table.update(arr);
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => update(table, viewer), FREQ);
|
||||
}
|
||||
|
||||
function select(viewer: any, id: string, extra: any = {}) {
|
||||
selectedId = id;
|
||||
viewer.restore({ ...LAYOUTS[id], ...extra });
|
||||
}
|
||||
|
||||
async function startStreaming(perspective: any, viewer: any) {
|
||||
const data = [];
|
||||
for (let x = 0; x < 1000; x++) {
|
||||
data.push(random_row());
|
||||
}
|
||||
|
||||
const worker = await perspective.worker();
|
||||
const tbl = worker.table(data, { index: "id" });
|
||||
setTimeout(async () => {
|
||||
const table = await tbl;
|
||||
update(table, viewer);
|
||||
});
|
||||
|
||||
return tbl;
|
||||
}
|
||||
|
||||
export async function initDemo(container: HTMLElement) {
|
||||
const [perspectiveMod] = await Promise.all([
|
||||
import("../data/worker.js"),
|
||||
import("@perspective-dev/viewer"),
|
||||
import("@perspective-dev/viewer-datagrid"),
|
||||
import("@perspective-dev/viewer-charts"),
|
||||
]);
|
||||
|
||||
const wrapper = document.createElement("div");
|
||||
wrapper.className = "demo";
|
||||
|
||||
const viewer = document.createElement("perspective-viewer") as any;
|
||||
viewer.className = "nosuperstore";
|
||||
wrapper.appendChild(viewer);
|
||||
|
||||
const visButtons = document.createElement("div");
|
||||
visButtons.className = "demo__vis-buttons";
|
||||
|
||||
for (const key of Object.keys(LAYOUTS)) {
|
||||
const btn = document.createElement("div");
|
||||
btn.className = "demo__vis-button";
|
||||
if (key === selectedId) {
|
||||
btn.classList.add("demo__vis-button--active");
|
||||
}
|
||||
btn.id = key;
|
||||
btn.textContent = key;
|
||||
btn.addEventListener("mouseover", () => {
|
||||
visButtons
|
||||
.querySelectorAll(".demo__vis-button")
|
||||
.forEach((b) => b.classList.remove("demo__vis-button--active"));
|
||||
btn.classList.add("demo__vis-button--active");
|
||||
select(viewer, key);
|
||||
});
|
||||
visButtons.appendChild(btn);
|
||||
}
|
||||
|
||||
wrapper.appendChild(visButtons);
|
||||
|
||||
const timeControls = document.createElement("div");
|
||||
timeControls.className = "demo__time-controls";
|
||||
|
||||
const freqLabel = document.createElement("span");
|
||||
freqLabel.textContent =
|
||||
FREQ >= 189 ? "paused" : `${((1000 / FREQ) * 10).toFixed(0)} msg/s`;
|
||||
timeControls.appendChild(freqLabel);
|
||||
|
||||
const slider = document.createElement("input");
|
||||
slider.type = "range";
|
||||
slider.className = "demo__freq-slider";
|
||||
slider.setAttribute(
|
||||
"aria-label",
|
||||
"Demo update rate in messages per second",
|
||||
);
|
||||
slider.value = String(Math.round((FREQ - 190) * (5 / -9)));
|
||||
slider.addEventListener("input", () => {
|
||||
FREQ = (-9 / 5) * Number(slider.value) + 190;
|
||||
freqLabel.textContent =
|
||||
FREQ >= 189 ? "paused" : `${((1000 / FREQ) * 10).toFixed(0)} msg/s`;
|
||||
});
|
||||
timeControls.appendChild(slider);
|
||||
|
||||
wrapper.appendChild(timeControls);
|
||||
container.appendChild(wrapper);
|
||||
|
||||
REALTIME_PAUSED = false;
|
||||
|
||||
if (TABLE === undefined) {
|
||||
TABLE = await startStreaming(perspectiveMod, viewer);
|
||||
}
|
||||
|
||||
VIEWER = viewer;
|
||||
VIEWER.load(TABLE);
|
||||
select(viewer, selectedId, { theme: getPerspectiveTheme() });
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import EXAMPLES from "../data/features.js";
|
||||
import { WORKER, SUPERSTORE_TABLE } from "../data/superstore.js";
|
||||
import { getColorMode, getPerspectiveTheme } from "./theme.js";
|
||||
|
||||
function showOverlay(index: number) {
|
||||
const overlay = document.createElement("div");
|
||||
overlay.className = "gallery-overlay";
|
||||
|
||||
const viewer = document.createElement("perspective-viewer") as any;
|
||||
viewer.setAttribute("theme", getPerspectiveTheme());
|
||||
overlay.appendChild(viewer);
|
||||
|
||||
overlay.addEventListener("click", (event) => {
|
||||
if (event.target === overlay) {
|
||||
overlay.remove();
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
SUPERSTORE_TABLE.then((table: any) => {
|
||||
viewer.load(WORKER);
|
||||
viewer.restore({
|
||||
plugin: "Datagrid",
|
||||
table: "superstore",
|
||||
group_by: [],
|
||||
expressions: {},
|
||||
split_by: [],
|
||||
sort: [],
|
||||
aggregates: {},
|
||||
...EXAMPLES[index].config,
|
||||
settings: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
interface MontageMap {
|
||||
tile_width: number;
|
||||
tile_height: number;
|
||||
columns: number;
|
||||
order: number[];
|
||||
}
|
||||
|
||||
export async function initGallery(container: HTMLElement) {
|
||||
const resp = await fetch("/features/montage_map.json");
|
||||
const map: MontageMap = await resp.json();
|
||||
const rows = Math.ceil(map.order.length / map.columns);
|
||||
const isDark = getColorMode() === "dark";
|
||||
const img = document.createElement("img");
|
||||
img.alt = "Perspective feature gallery";
|
||||
img.src = `/features/montage${isDark ? "_dark" : "_light"}.png`;
|
||||
img.addEventListener("click", (event: MouseEvent) => {
|
||||
const col = Math.floor((event.offsetX / img.offsetWidth) * map.columns);
|
||||
const row = Math.floor((event.offsetY / img.offsetHeight) * rows);
|
||||
const tileIndex = row * map.columns + col;
|
||||
const featureIndex = map.order[tileIndex];
|
||||
if (featureIndex === undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
showOverlay(featureIndex);
|
||||
});
|
||||
|
||||
container.appendChild(img);
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import type { HTMLPerspectiveViewerElement } from "@perspective-dev/viewer";
|
||||
|
||||
const SUN_SVG = `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></svg>`;
|
||||
const MOON_SVG = `<svg viewBox="0 0 24 24" width="20" height="20" fill="currentColor"><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></svg>`;
|
||||
|
||||
export function getColorMode(): "dark" | "light" {
|
||||
const stored = localStorage.getItem("perspective-theme");
|
||||
if (stored === "dark" || stored === "light") {
|
||||
return stored;
|
||||
}
|
||||
|
||||
return "dark";
|
||||
}
|
||||
|
||||
export function applyTheme(mode?: "dark" | "light") {
|
||||
const theme = mode ?? getColorMode();
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
localStorage.setItem("perspective-theme", theme);
|
||||
|
||||
const viewer = document.querySelector(
|
||||
".demo perspective-viewer",
|
||||
) as HTMLPerspectiveViewerElement;
|
||||
viewer?.restore?.({ theme: theme === "dark" ? "Pro Dark" : "Pro Light" });
|
||||
|
||||
// Swap navbar logo
|
||||
const navLogo = document.querySelector(
|
||||
".navbar__logo img",
|
||||
) as HTMLImageElement | null;
|
||||
if (navLogo) {
|
||||
navLogo.src =
|
||||
theme === "dark"
|
||||
? "/svg/perspective-logo-dark.svg"
|
||||
: "/svg/perspective-logo-light.svg";
|
||||
}
|
||||
|
||||
// Swap footer logo
|
||||
const footerLogo = document.getElementById("footer-logo") as
|
||||
| HTMLImageElement
|
||||
| undefined;
|
||||
if (footerLogo) {
|
||||
footerLogo.src =
|
||||
theme === "dark"
|
||||
? "/img/openjs_foundation-logo-horizontal-white.png"
|
||||
: "/img/openjs_foundation-logo-horizontal-black.png";
|
||||
}
|
||||
|
||||
// Swap gallery montage
|
||||
const galleryImg = document.querySelector(
|
||||
".gallery img",
|
||||
) as HTMLImageElement | null;
|
||||
if (galleryImg) {
|
||||
galleryImg.src =
|
||||
theme === "dark"
|
||||
? "/features/montage_dark.png"
|
||||
: "/features/montage_light.png";
|
||||
}
|
||||
|
||||
// Update toggle button icon
|
||||
const toggleBtn = document.getElementById("theme-toggle-btn");
|
||||
if (toggleBtn) {
|
||||
toggleBtn.innerHTML = theme === "dark" ? SUN_SVG : MOON_SVG;
|
||||
toggleBtn.setAttribute(
|
||||
"aria-label",
|
||||
theme === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export function initTheme() {
|
||||
applyTheme();
|
||||
}
|
||||
|
||||
export function createThemeToggle(): HTMLElement {
|
||||
const li = document.createElement("li");
|
||||
const btn = document.createElement("button");
|
||||
btn.id = "theme-toggle-btn";
|
||||
btn.className = "theme-toggle";
|
||||
btn.type = "button";
|
||||
const current = getColorMode();
|
||||
btn.innerHTML = current === "dark" ? SUN_SVG : MOON_SVG;
|
||||
btn.setAttribute(
|
||||
"aria-label",
|
||||
current === "dark" ? "Switch to light mode" : "Switch to dark mode",
|
||||
);
|
||||
btn.addEventListener("click", () => {
|
||||
const next = getColorMode() === "dark" ? "light" : "dark";
|
||||
applyTheme(next);
|
||||
});
|
||||
li.appendChild(btn);
|
||||
return li;
|
||||
}
|
||||
|
||||
export function getPerspectiveTheme(): string {
|
||||
return getColorMode() === "dark" ? "Pro Dark" : "Pro Light";
|
||||
}
|
||||
@@ -0,0 +1,653 @@
|
||||
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
* ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
* ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
* ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
* ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
*/
|
||||
|
||||
@import url("https://fonts.googleapis.com/css?display=block&family=Open+Sans:400");
|
||||
|
||||
@import "../../node_modules/@perspective-dev/viewer/dist/css/themes.css";
|
||||
|
||||
/* --- Reset --- */
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
font-family:
|
||||
"Open Sans",
|
||||
system-ui,
|
||||
-apple-system,
|
||||
sans-serif;
|
||||
font-weight: 400;
|
||||
line-height: 1.65;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
hr {
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-top: 1px solid var(--border-color);
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* --- Theme Variables --- */
|
||||
|
||||
:root {
|
||||
--color-primary: #2e8555;
|
||||
--bg-color: #ffffff;
|
||||
--bg-surface: #f2f4f6;
|
||||
--text-color: #1c1e21;
|
||||
--text-muted: #666;
|
||||
--border-color: #dadde1;
|
||||
--navbar-bg: #ffffff;
|
||||
--navbar-height: 60px;
|
||||
--content-width: 590px;
|
||||
--logo-url: url("../../static/svg/perspective-logo-light.svg");
|
||||
color-scheme: light;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
--color-primary: #25c2a0;
|
||||
--bg-color: #1b1b1d;
|
||||
--bg-surface: #242526;
|
||||
--text-color: #e3e3e3;
|
||||
--text-muted: #999;
|
||||
--border-color: #444;
|
||||
--navbar-bg: #242526;
|
||||
--logo-url: url("../../static/svg/perspective-logo-dark.svg");
|
||||
color-scheme: dark;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
/* --- Regular Table Reset --- */
|
||||
|
||||
td,
|
||||
th,
|
||||
table,
|
||||
tbody,
|
||||
table thead {
|
||||
border: 0;
|
||||
font-size: 12px;
|
||||
font-family:
|
||||
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
|
||||
"Liberation Mono", monospace;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
vertical-align: middle;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table tr {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
table tr:nth-child(2n) {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
table th,
|
||||
table td {
|
||||
border: none;
|
||||
padding: 0px 5px;
|
||||
}
|
||||
|
||||
/* --- Navbar --- */
|
||||
|
||||
.navbar {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: var(--navbar-height);
|
||||
background-color: var(--navbar-bg);
|
||||
z-index: 100000;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
box-shadow: none;
|
||||
margin-bottom: -60px;
|
||||
}
|
||||
|
||||
.navbar__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
width: var(--content-width);
|
||||
}
|
||||
|
||||
.navbar__logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 21px;
|
||||
}
|
||||
|
||||
.navbar__logo img {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.navbar__links {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 20px;
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.navbar__links a {
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.navbar__links a:hover {
|
||||
opacity: 1;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
/* --- Theme Toggle --- */
|
||||
|
||||
.theme-toggle {
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
padding: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: var(--text-color);
|
||||
opacity: 0.8;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
/* --- Header shift (homepage only) --- */
|
||||
|
||||
.header-shift .navbar {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
padding-top: 100px;
|
||||
}
|
||||
|
||||
.header-shift .main-wrapper {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.main-wrapper {
|
||||
background-color: var(--navbar-bg);
|
||||
}
|
||||
|
||||
/* --- Hero Banner --- */
|
||||
|
||||
.hero-banner {
|
||||
padding: 4rem 0;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
background-color: var(--navbar-bg);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 996px) {
|
||||
.hero-banner {
|
||||
padding: 2rem;
|
||||
}
|
||||
}
|
||||
|
||||
.hero-banner__buttons {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
/* --- Demo --- */
|
||||
|
||||
.demo {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.demo perspective-viewer {
|
||||
width: 1000px;
|
||||
height: 600px;
|
||||
margin-top: 24px;
|
||||
margin-bottom: 48px;
|
||||
}
|
||||
|
||||
.demo__vis-buttons {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.demo__vis-button {
|
||||
font-size: 12px;
|
||||
border: 1px solid;
|
||||
padding: 8px;
|
||||
border-radius: 3px;
|
||||
margin-right: 2px;
|
||||
opacity: 0.3;
|
||||
cursor: pointer;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.demo__vis-button--active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.demo__time-controls {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 12px;
|
||||
}
|
||||
|
||||
.demo__time-controls > * {
|
||||
margin-right: 15px;
|
||||
}
|
||||
|
||||
.demo__time-controls span {
|
||||
text-align: end;
|
||||
width: 80px;
|
||||
font-family:
|
||||
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
|
||||
"Liberation Mono", monospace;
|
||||
opacity: 0.5;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.demo__freq-slider {
|
||||
-webkit-appearance: none;
|
||||
background: transparent;
|
||||
border: 1px solid;
|
||||
width: 200px;
|
||||
height: 10px;
|
||||
border-radius: 5px;
|
||||
outline: none;
|
||||
opacity: 0.7;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.demo__freq-slider::-webkit-slider-thumb {
|
||||
background: var(--bg-color);
|
||||
-webkit-appearance: none;
|
||||
appearance: none;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
border-radius: 50%;
|
||||
border: 1px solid;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.demo__freq-slider::-moz-range-thumb {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
border-color: #fff;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* --- perspective-viewer shared --- */
|
||||
|
||||
perspective-viewer {
|
||||
text-align: initial;
|
||||
border-radius: 10px;
|
||||
border: 1px solid #bebebe;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
[data-theme="dark"] perspective-viewer {
|
||||
border-color: #666;
|
||||
}
|
||||
|
||||
/* --- Features Section --- */
|
||||
|
||||
.features {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 2rem 0;
|
||||
width: 100%;
|
||||
background-color: var(--bg-surface);
|
||||
}
|
||||
|
||||
.features__container {
|
||||
margin: auto;
|
||||
max-width: var(--content-width);
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.features__container hr {
|
||||
margin: 2rem 0;
|
||||
}
|
||||
|
||||
.feature-item {
|
||||
display: inline-flex;
|
||||
min-height: 150px;
|
||||
}
|
||||
|
||||
.feature-item__icon {
|
||||
min-width: 53px;
|
||||
margin-right: 50px;
|
||||
stroke: #242526;
|
||||
fill: none;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .feature-item__icon {
|
||||
stroke: white;
|
||||
}
|
||||
|
||||
.feature-item__text p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* --- Gallery Section --- */
|
||||
|
||||
.gallery-section {
|
||||
background-color: var(--bg-surface);
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.gallery {
|
||||
max-width: 638px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.gallery img {
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
/* --- Gallery Overlay --- */
|
||||
|
||||
.gallery-overlay {
|
||||
position: fixed;
|
||||
background-color: rgba(0, 0, 0, 0.5);
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding: 80px;
|
||||
z-index: 200000;
|
||||
}
|
||||
|
||||
.gallery-overlay perspective-viewer {
|
||||
max-width: 1200px;
|
||||
max-height: 1000px;
|
||||
flex: 1 1 auto;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* --- Examples Page --- */
|
||||
|
||||
.examples-grid {
|
||||
display: flex;
|
||||
margin: 0 auto;
|
||||
width: 600px;
|
||||
max-width: 90%;
|
||||
}
|
||||
|
||||
.examples-grid table {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.examples-grid td {
|
||||
max-width: 400px;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.examples-grid a {
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
.examples-grid h4 {
|
||||
margin: 0.5em 0;
|
||||
}
|
||||
|
||||
.examples-grid img {
|
||||
width: 100%;
|
||||
border-radius: 10px;
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
/* --- Block Page --- */
|
||||
|
||||
.block-detail {
|
||||
width: 960px;
|
||||
margin: 0 auto;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
flex-direction: column;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.block-detail h1 {
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.block-detail iframe {
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.block-detail__link {
|
||||
align-self: flex-end;
|
||||
margin: 1rem 0;
|
||||
color: var(--color-primary);
|
||||
}
|
||||
|
||||
.block-detail pre {
|
||||
background-color: var(--bg-surface);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
margin: 1rem 0;
|
||||
}
|
||||
|
||||
.block-detail pre code {
|
||||
font-family:
|
||||
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
|
||||
"Liberation Mono", monospace;
|
||||
}
|
||||
|
||||
.block-detail__file-title {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
margin: 1.5rem 0 0 0;
|
||||
padding: 0.5rem 1rem;
|
||||
background-color: var(--bg-surface);
|
||||
border: 1px solid var(--border-color);
|
||||
border-bottom: none;
|
||||
border-radius: 6px 6px 0 0;
|
||||
font-family:
|
||||
"ui-monospace", "SFMono-Regular", "SF Mono", "Menlo", "Consolas",
|
||||
"Liberation Mono", monospace;
|
||||
}
|
||||
|
||||
.block-detail__file-title + pre {
|
||||
margin-top: 0;
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
/* --- Footer --- */
|
||||
|
||||
.footer {
|
||||
background-color: var(--bg-surface);
|
||||
padding: 2rem 0;
|
||||
}
|
||||
|
||||
.footer__inner {
|
||||
max-width: var(--content-width);
|
||||
margin: auto;
|
||||
text-align: center;
|
||||
padding: 0 1rem;
|
||||
}
|
||||
|
||||
.footer__logo {
|
||||
width: 160px;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.footer__copyright {
|
||||
font-size: 11px;
|
||||
color: var(--text-muted);
|
||||
line-height: 1.8;
|
||||
}
|
||||
|
||||
.footer__copyright a {
|
||||
color: var(--text-muted);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* --- Page container --- */
|
||||
|
||||
.page-container {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.page-container > main {
|
||||
flex: 1;
|
||||
margin-top: 100px;
|
||||
}
|
||||
|
||||
.page-container.examples > main {
|
||||
margin-top: 60px;
|
||||
}
|
||||
|
||||
/* --- Prism Syntax Highlighting --- */
|
||||
|
||||
.token.comment,
|
||||
.token.prolog,
|
||||
.token.doctype,
|
||||
.token.cdata {
|
||||
color: #708090;
|
||||
}
|
||||
|
||||
.token.punctuation {
|
||||
color: #999;
|
||||
}
|
||||
|
||||
.token.property,
|
||||
.token.tag,
|
||||
.token.boolean,
|
||||
.token.number,
|
||||
.token.constant,
|
||||
.token.symbol,
|
||||
.token.deleted {
|
||||
color: #905;
|
||||
}
|
||||
|
||||
.token.selector,
|
||||
.token.attr-name,
|
||||
.token.string,
|
||||
.token.char,
|
||||
.token.builtin,
|
||||
.token.inserted {
|
||||
color: #690;
|
||||
}
|
||||
|
||||
.token.operator,
|
||||
.token.entity,
|
||||
.token.url {
|
||||
color: #9a6e3a;
|
||||
}
|
||||
|
||||
.token.atrule,
|
||||
.token.attr-value,
|
||||
.token.keyword {
|
||||
color: #07a;
|
||||
}
|
||||
|
||||
.token.function,
|
||||
.token.class-name {
|
||||
color: #dd4a68;
|
||||
}
|
||||
|
||||
.token.regex,
|
||||
.token.important,
|
||||
.token.variable {
|
||||
color: #e90;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.comment,
|
||||
[data-theme="dark"] .token.prolog,
|
||||
[data-theme="dark"] .token.doctype,
|
||||
[data-theme="dark"] .token.cdata {
|
||||
color: #6272a4;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.punctuation {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.property,
|
||||
[data-theme="dark"] .token.tag,
|
||||
[data-theme="dark"] .token.boolean,
|
||||
[data-theme="dark"] .token.number,
|
||||
[data-theme="dark"] .token.constant,
|
||||
[data-theme="dark"] .token.symbol,
|
||||
[data-theme="dark"] .token.deleted {
|
||||
color: #ff79c6;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.selector,
|
||||
[data-theme="dark"] .token.attr-name,
|
||||
[data-theme="dark"] .token.string,
|
||||
[data-theme="dark"] .token.char,
|
||||
[data-theme="dark"] .token.builtin,
|
||||
[data-theme="dark"] .token.inserted {
|
||||
color: #50fa7b;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.operator,
|
||||
[data-theme="dark"] .token.entity,
|
||||
[data-theme="dark"] .token.url {
|
||||
color: #f8f8f2;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.atrule,
|
||||
[data-theme="dark"] .token.attr-value,
|
||||
[data-theme="dark"] .token.keyword {
|
||||
color: #8be9fd;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.function,
|
||||
[data-theme="dark"] .token.class-name {
|
||||
color: #f1fa8c;
|
||||
}
|
||||
|
||||
[data-theme="dark"] .token.regex,
|
||||
[data-theme="dark"] .token.important,
|
||||
[data-theme="dark"] .token.variable {
|
||||
color: #ffb86c;
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,122 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
export const LAYOUTS: Record<string, any> = {
|
||||
sparkgrid: {
|
||||
plugin: "Datagrid",
|
||||
plugin_config: {
|
||||
columns: {},
|
||||
scroll_lock: true,
|
||||
},
|
||||
columns_config: {
|
||||
"chg (-)": {
|
||||
number_fg_mode: "bar",
|
||||
fg_gradient: 14.34,
|
||||
},
|
||||
chg: {
|
||||
number_bg_mode: "gradient",
|
||||
bg_gradient: 29.17,
|
||||
},
|
||||
"chg (+)": {
|
||||
number_fg_mode: "bar",
|
||||
fg_gradient: 17.4,
|
||||
},
|
||||
},
|
||||
group_rollup_mode: "rollup",
|
||||
settings: true,
|
||||
title: "Market Monitor",
|
||||
group_by: ["name"],
|
||||
split_by: ["client"],
|
||||
columns: ["chg (-)", "chg", "chg (+)"],
|
||||
filter: [],
|
||||
sort: [["chg", "desc"]],
|
||||
expressions: {
|
||||
"chg (+)": 'if("chg">0){"chg"}else{0}',
|
||||
"chg (-)": 'if("chg"<0){"chg"}else{0}',
|
||||
},
|
||||
aggregates: {
|
||||
chg: "avg",
|
||||
"chg (+)": "avg",
|
||||
"chg (-)": "avg",
|
||||
},
|
||||
},
|
||||
datagrid: {
|
||||
plugin: "datagrid",
|
||||
title: "Blotter",
|
||||
columns: ["ask", "bid", "chg"],
|
||||
group_rollup_mode: "rollup",
|
||||
sort: [
|
||||
["name", "desc"],
|
||||
["lastUpdate", "desc"],
|
||||
],
|
||||
aggregates: { name: "last", lastUpdate: "last" },
|
||||
group_by: ["name", "lastUpdate"],
|
||||
split_by: ["client"],
|
||||
plugin_config: {},
|
||||
},
|
||||
"x bar": {
|
||||
title: "Px (Δ)",
|
||||
group_rollup_mode: "flat",
|
||||
columns: ["chg"],
|
||||
plugin: "X Bar",
|
||||
sort: [["chg", "asc"]],
|
||||
group_by: ["name"],
|
||||
split_by: ["client"],
|
||||
},
|
||||
"y line": {
|
||||
title: "Time Series (Px)",
|
||||
group_rollup_mode: "flat",
|
||||
plugin: "Y Line",
|
||||
group_by: ["lastUpdate"],
|
||||
sort: [["lastUpdate", "desc"]],
|
||||
split_by: ["client"],
|
||||
columns: ["bid"],
|
||||
aggregates: { bid: "avg", chg: "avg", name: "last" },
|
||||
},
|
||||
"xy scatter": {
|
||||
title: "Spread Scatter",
|
||||
group_rollup_mode: "flat",
|
||||
plugin: "X/Y Scatter",
|
||||
group_by: ["name"],
|
||||
split_by: [],
|
||||
columns: ["bid", "ask", "chg", "vol", null, "name"],
|
||||
aggregates: { bid: "avg", ask: "avg", vol: "avg", name: "dominant" },
|
||||
sort: [],
|
||||
},
|
||||
treemap: {
|
||||
plugin: "Treemap",
|
||||
group_rollup_mode: "flat",
|
||||
title: "Volume Map",
|
||||
group_by: ["name", "client"],
|
||||
split_by: [],
|
||||
columns: ["vol", "chg"],
|
||||
aggregates: { bid: "sum", chg: "sum", name: "last" },
|
||||
sort: [
|
||||
["name", "desc"],
|
||||
["chg", "desc"],
|
||||
],
|
||||
},
|
||||
heatmap: {
|
||||
group_rollup_mode: "flat",
|
||||
title: "Spread Heatmap",
|
||||
columns: ["name"],
|
||||
plugin: "Heatmap",
|
||||
expressions: {
|
||||
'bucket("bid",2)': 'bucket("bid",2)',
|
||||
'bucket("ask",2)': `bucket("ask",2)`,
|
||||
},
|
||||
group_by: [`bucket("bid",2)`],
|
||||
split_by: [`bucket("ask",2)`],
|
||||
sort: [],
|
||||
aggregates: {},
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,75 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const SECURITIES = [
|
||||
"AAPL.N",
|
||||
"MSFT.N",
|
||||
"AMZN.N",
|
||||
"GOOGL.N",
|
||||
"FB.N",
|
||||
"TSLA.N",
|
||||
"BABA.N",
|
||||
"TSM.N",
|
||||
"V.N",
|
||||
"NVDA.N",
|
||||
"JPM.N",
|
||||
"JNJ.N",
|
||||
"WMT.N",
|
||||
"UNH.N",
|
||||
"MA.N",
|
||||
"BAC.N",
|
||||
"DIS.N",
|
||||
"ASML.N",
|
||||
"ADBE.N",
|
||||
"CMCSA.N",
|
||||
"NKE.N",
|
||||
"XOM.N",
|
||||
"TM.N",
|
||||
"KO.N",
|
||||
"ORCL.N",
|
||||
"NFLX.N",
|
||||
];
|
||||
|
||||
const CLIENTS = [
|
||||
"Homer",
|
||||
"Marge",
|
||||
"Bart",
|
||||
"Lisa",
|
||||
"Maggie",
|
||||
"Barney",
|
||||
"Ned",
|
||||
"Moe",
|
||||
];
|
||||
|
||||
let id = 0;
|
||||
|
||||
function randn_bm(): number {
|
||||
let u = 0,
|
||||
v = 0;
|
||||
while (u === 0) u = Math.random();
|
||||
while (v === 0) v = Math.random();
|
||||
return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v);
|
||||
}
|
||||
|
||||
export function random_row() {
|
||||
id = id % 1000;
|
||||
return {
|
||||
name: SECURITIES[Math.floor(Math.random() * SECURITIES.length)],
|
||||
client: CLIENTS[Math.floor(Math.random() * CLIENTS.length)],
|
||||
lastUpdate: new Date(),
|
||||
chg: randn_bm() * 10,
|
||||
bid: randn_bm() * 5 + 95,
|
||||
ask: randn_bm() * 5 + 105,
|
||||
vol: randn_bm() * 5 + 105,
|
||||
id: id++,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { worker } from "./worker.js";
|
||||
// @ts-ignore
|
||||
import SUPERSTORE_URL from "superstore-arrow/superstore.lz4.arrow";
|
||||
|
||||
export const WORKER = worker();
|
||||
|
||||
export const SUPERSTORE_TABLE = (async function () {
|
||||
const req = await fetch(SUPERSTORE_URL);
|
||||
const arrow = await req.arrayBuffer();
|
||||
return await WORKER.then((w) =>
|
||||
w.table(arrow.slice(), { name: "superstore" }),
|
||||
);
|
||||
})();
|
||||
@@ -0,0 +1,34 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
const WORKER = (async () => {
|
||||
const perspective = await import("@perspective-dev/client");
|
||||
const perspective_viewer = await import("@perspective-dev/viewer");
|
||||
const server_wasm = import(
|
||||
"@perspective-dev/server/dist/wasm/perspective-server.wasm"
|
||||
);
|
||||
|
||||
const client_wasm = import(
|
||||
"@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm"
|
||||
);
|
||||
|
||||
await Promise.all([
|
||||
perspective.init_server(server_wasm.then((x: any) => x.default)),
|
||||
perspective_viewer.init_client(client_wasm.then((x: any) => x.default)),
|
||||
]);
|
||||
|
||||
return await perspective.worker();
|
||||
})();
|
||||
|
||||
export function worker() {
|
||||
return WORKER;
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<title>Perspective - Examples</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Interactive analytics and data visualization component for large and streaming datasets."
|
||||
/>
|
||||
<link rel="icon" href="https://openjsf.org/favicon.ico" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container examples">
|
||||
<nav class="navbar">
|
||||
<div class="navbar__inner">
|
||||
<a href="/" class="navbar__logo"><img alt="Perspective" src="/svg/perspective-logo-dark.svg" /></a>
|
||||
<ul class="navbar__links">
|
||||
<li><a href="/guide/index.html">Docs</a></li>
|
||||
<li><a href="/examples.html">Examples</a></li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/perspective-dev/perspective"
|
||||
>GitHub</a
|
||||
>
|
||||
</li>
|
||||
<li id="theme-toggle"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="main-wrapper">
|
||||
<br />
|
||||
<div class="examples-grid" id="examples-grid"></div>
|
||||
<br /><br />
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__inner">
|
||||
<img
|
||||
class="footer__logo"
|
||||
id="footer-logo"
|
||||
alt="OpenJS Foundation Logo"
|
||||
src="/img/openjs_foundation-logo-horizontal-white.png"
|
||||
/>
|
||||
<div class="footer__copyright">
|
||||
<br /><br />Copyright © 2017 OpenJS Foundation and
|
||||
Perspective contributors.
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script type="module" src="examples.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,75 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { initTheme, createThemeToggle } from "./components/theme.js";
|
||||
|
||||
initTheme();
|
||||
document.getElementById("theme-toggle")!.replaceWith(createThemeToggle());
|
||||
|
||||
const LOCAL_EXAMPLES = [
|
||||
"editable",
|
||||
"file",
|
||||
"duckdb",
|
||||
"fractal",
|
||||
"market",
|
||||
"raycasting",
|
||||
"evictions",
|
||||
"nypd",
|
||||
"streaming",
|
||||
"covid",
|
||||
"webcam",
|
||||
"movies",
|
||||
"superstore",
|
||||
"olympics",
|
||||
"dataset",
|
||||
];
|
||||
|
||||
function partition<T>(input: T[], spacing: number): T[][] {
|
||||
const output: T[][] = [];
|
||||
for (let i = 0; i < input.length; i += spacing) {
|
||||
output.push(input.slice(i, i + spacing));
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
const grid = document.getElementById("examples-grid")!;
|
||||
|
||||
const table = document.createElement("table");
|
||||
const tbody = document.createElement("tbody");
|
||||
|
||||
for (const group of partition(LOCAL_EXAMPLES, 2)) {
|
||||
const tr = document.createElement("tr");
|
||||
for (const name of group) {
|
||||
const td = document.createElement("td");
|
||||
const a = document.createElement("a");
|
||||
a.href = `/block.html?example=${name}`;
|
||||
|
||||
const br = document.createElement("br");
|
||||
a.appendChild(br);
|
||||
|
||||
const h4 = document.createElement("h4");
|
||||
h4.textContent = name;
|
||||
a.appendChild(h4);
|
||||
|
||||
const img = document.createElement("img");
|
||||
img.width = 400;
|
||||
img.src = `/blocks/${name}/preview.png`;
|
||||
a.appendChild(img);
|
||||
|
||||
td.appendChild(a);
|
||||
tr.appendChild(td);
|
||||
}
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
|
||||
table.appendChild(tbody);
|
||||
grid.appendChild(table);
|
||||
@@ -0,0 +1,354 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no"
|
||||
/>
|
||||
<title>Perspective</title>
|
||||
<meta
|
||||
name="description"
|
||||
content="Interactive analytics and data visualization component for large and streaming datasets."
|
||||
/>
|
||||
<link rel="icon" href="https://openjsf.org/favicon.ico" />
|
||||
<link rel="stylesheet" href="style.css" />
|
||||
</head>
|
||||
<body>
|
||||
<div class="page-container header-shift" id="page">
|
||||
<nav class="navbar">
|
||||
<div class="navbar__inner">
|
||||
<a href="/" class="navbar__logo"><img alt="Perspective" src="/svg/perspective-logo-dark.svg"/></a>
|
||||
<ul class="navbar__links">
|
||||
<li><a href="/guide/index.html">Docs</a></li>
|
||||
<li><a href="/examples.html">Examples</a></li>
|
||||
<li>
|
||||
<a
|
||||
href="https://github.com/perspective-dev/perspective"
|
||||
>GitHub</a
|
||||
>
|
||||
</li>
|
||||
<li id="theme-toggle"></li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main>
|
||||
<header class="hero-banner">
|
||||
<div class="hero-banner__buttons" id="demo-container"></div>
|
||||
</header>
|
||||
|
||||
<section class="features">
|
||||
<div class="features__container">
|
||||
<hr />
|
||||
<br /><br /><br />
|
||||
<div>
|
||||
<h2>What is Perspective?</h2>
|
||||
<p>
|
||||
Perspective is an <i>interactive</i> analytics
|
||||
and data visualization component, which is
|
||||
especially well-suited for <i>large</i> and/or
|
||||
<i>streaming</i> datasets. Use it to create
|
||||
user-configurable reports, dashboards, notebooks
|
||||
and applications, then deploy stand-alone in the
|
||||
browser, or in concert with Python and/or
|
||||
<a
|
||||
href="https://jupyterlab.readthedocs.io/en/stable/"
|
||||
>Jupyterlab</a
|
||||
>.
|
||||
</p>
|
||||
<br />
|
||||
<h3>Features</h3>
|
||||
<br />
|
||||
<div class="feature-item">
|
||||
<svg
|
||||
class="feature-item__icon"
|
||||
width="53"
|
||||
height="67"
|
||||
viewBox="0 0 53 67"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M11.6482 1.56063L1.6114 11.2435C1.22069 11.6204 1 12.14 1 12.6829V64C1 65.1046 1.89543 66 3 66H50C51.1046 66 52 65.1046 52 64V50.4471C52 49.6719 51.3716 49.0435 50.5963 49.0435C49.8211 49.0435 49.1927 48.415 49.1927 47.6398V39.1428C49.1927 38.3676 49.8211 37.7391 50.5963 37.7391C51.3716 37.7391 52 37.1107 52 36.3355V3C52 1.89543 51.1046 1 50 1H13.0368C12.5188 1 12.021 1.20098 11.6482 1.56063Z"
|
||||
/>
|
||||
<path
|
||||
d="M29.2391 29H20.5652L16 43.431H24.2174L21.4783 56L37 38.7759H26.0435L29.2391 29Z"
|
||||
/>
|
||||
<path d="M42 8H45V19H42V8Z" />
|
||||
<path d="M36 8H39V19H36V8Z" />
|
||||
<path d="M31 8H34V19H31V8Z" />
|
||||
<path d="M25 8H28V19H25V8Z" />
|
||||
<path d="M19 8H22V19H19V8Z" />
|
||||
<path d="M14 8H17V19H14V8Z" />
|
||||
<path d="M8 14H11V22H8V14Z" />
|
||||
</svg>
|
||||
<div class="feature-item__text">
|
||||
<p>
|
||||
A fast, memory efficient streaming query
|
||||
engine, written in C++ and compiled for
|
||||
both
|
||||
<a href="https://webassembly.org/"
|
||||
>WebAssembly</a
|
||||
>
|
||||
and
|
||||
<a href="https://www.python.org/"
|
||||
>Python</a
|
||||
>, with read/write/streaming for
|
||||
<a href="https://arrow.apache.org/"
|
||||
>Apache Arrow</a
|
||||
>, and a high-performance columnar
|
||||
expression language based on
|
||||
<a
|
||||
href="https://github.com/ArashPartow/exprtk"
|
||||
>ExprTK</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<svg
|
||||
class="feature-item__icon"
|
||||
width="49"
|
||||
height="71"
|
||||
viewBox="0 0 49 71"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d="M1 23.5001L24.3345 6.16554L47.669 23.5001L24.3345 40.8346L1 23.5001Z"
|
||||
/>
|
||||
<path
|
||||
d="M9 41.5L1 47.3345L24.3345 64.669L47.669 47.3345L39.6518 41.3787"
|
||||
/>
|
||||
<path
|
||||
d="M9 29.5001L1 35.3345L24.3345 52.6691L47.669 35.3345L39.6518 29.3788"
|
||||
/>
|
||||
</svg>
|
||||
<div class="feature-item__text">
|
||||
<p>
|
||||
A framework-agnostic User Interface
|
||||
packaged as a
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements"
|
||||
>Custom Element</a
|
||||
>, powered either in-browser via
|
||||
WebAssembly or virtually via WebSocket
|
||||
server (Python/Node).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-item">
|
||||
<svg
|
||||
class="feature-item__icon"
|
||||
width="48"
|
||||
height="59"
|
||||
viewBox="0 0 48 59"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<rect
|
||||
x="2"
|
||||
y="1"
|
||||
width="45"
|
||||
height="57"
|
||||
rx="2"
|
||||
/>
|
||||
<circle
|
||||
cx="10"
|
||||
cy="11"
|
||||
r="2"
|
||||
fill="#666"
|
||||
/>
|
||||
<circle
|
||||
cx="10"
|
||||
cy="30"
|
||||
r="2"
|
||||
fill="#666"
|
||||
/>
|
||||
<circle
|
||||
cx="10"
|
||||
cy="49"
|
||||
r="2"
|
||||
fill="#666"
|
||||
/>
|
||||
<path d="M10 11H0" />
|
||||
<path d="M39 10H17" />
|
||||
<path d="M39 14H17" />
|
||||
<path d="M39 18H17" />
|
||||
<path d="M39 22H17" />
|
||||
<path d="M39 26H17" />
|
||||
<path d="M39 30H17" />
|
||||
<path d="M39 34H17" />
|
||||
<path d="M39 38H17" />
|
||||
<path d="M39 42H17" />
|
||||
<path d="M39 46H17" />
|
||||
<path d="M39 50H17" />
|
||||
<path d="M10 30H0" />
|
||||
<path d="M10 49H0" />
|
||||
</svg>
|
||||
<div class="feature-item__text">
|
||||
<p>
|
||||
A
|
||||
<a href="https://jupyter.org/"
|
||||
>JupyterLab</a
|
||||
>
|
||||
widget and Python client library, for
|
||||
interactive data analysis in a notebook,
|
||||
as well as <i>scalable</i> production
|
||||
<a
|
||||
href="https://github.com/voila-dashboards/voila"
|
||||
>Voila</a
|
||||
>
|
||||
applications.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<br /><br /><br />
|
||||
<div>
|
||||
<h2>JavaScript</h2>
|
||||
<p>
|
||||
Interactive dashboards built on Perspective.js
|
||||
<a
|
||||
href="https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements"
|
||||
>Custom Elements</a
|
||||
>
|
||||
are easy to integrate into any web application
|
||||
framework.
|
||||
</p>
|
||||
<p>
|
||||
Using Perspective's simple query language,
|
||||
elements like
|
||||
<code><perspective-viewer></code> can be
|
||||
<i>symmetrically</i> configured via API or User
|
||||
interaction. Web Applications built with
|
||||
Perspective Custom Elements can be re-hydrated
|
||||
from their serialized state, driven from external
|
||||
Events, or persisted to any store. Workspaces
|
||||
can mix virtual, server-side Python data with
|
||||
in-browser client data seamlessly, and
|
||||
independent data Views can be cross-filtered,
|
||||
duplicated, exported, stacked and saved.
|
||||
</p>
|
||||
<p>
|
||||
To achieve Desktop-like performance in the
|
||||
Browser, Perspective.js relies on
|
||||
<a href="https://webassembly.org/"
|
||||
>WebAssembly</a
|
||||
>
|
||||
for excellent <i>query calculation</i> time, and
|
||||
<a href="https://arrow.apache.org/"
|
||||
>Apache Arrow</a
|
||||
>
|
||||
for its conservative <i>memory footprint</i> and
|
||||
efficient <i>data serialization</i>.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<hr />
|
||||
<br /><br /><br />
|
||||
<div>
|
||||
<h2>Python</h2>
|
||||
<p>
|
||||
<code>perspective-python</code>, built on the
|
||||
same C++ data engine used by the
|
||||
<a
|
||||
href="https://perspective-dev.github.io/docs/md/js.html"
|
||||
>WebAssembly version</a
|
||||
>, implements the Perspective API directly in
|
||||
Python, either as a virtualized server for
|
||||
Production, or as an embedded JupyterLab Widget
|
||||
for Research.
|
||||
</p>
|
||||
<p>
|
||||
For Application Developers, virtualized
|
||||
<code><perspective-viewer></code> will only
|
||||
consume the data necessary to render the current
|
||||
screen, enabling <i>ludicrous size</i> datasets
|
||||
with nearly instant load. Or - efficiently stream
|
||||
the entire dataset to the WebAssembly runtime via
|
||||
Apache Arrow, and give your server a break!
|
||||
</p>
|
||||
<p>
|
||||
For Researchers and Data Scientists,
|
||||
<code>PerspectiveWidget</code> is available as a
|
||||
<a
|
||||
href="https://jupyterlab.readthedocs.io/en/stable/"
|
||||
>Jupyter/JupyterLab</a
|
||||
>
|
||||
widget, allowing interactive
|
||||
<a href="https://pandas.pydata.org/">Pandas</a>
|
||||
and
|
||||
<a href="https://arrow.apache.org/"
|
||||
>Apache Arrow</a
|
||||
>
|
||||
visualization within a notebook.
|
||||
</p>
|
||||
</div>
|
||||
<br />
|
||||
<hr />
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="gallery-section">
|
||||
<div class="gallery" id="gallery"></div>
|
||||
<div style="max-width: 638px; margin: 0 auto">
|
||||
<hr />
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<footer class="footer">
|
||||
<div class="footer__inner">
|
||||
<img
|
||||
class="footer__logo"
|
||||
id="footer-logo"
|
||||
alt="OpenJS Foundation Logo"
|
||||
src="/img/openjs_foundation-logo-horizontal-white.png"
|
||||
/>
|
||||
<div class="footer__copyright">
|
||||
<br /><br />Copyright © 2017 OpenJS Foundation and
|
||||
Perspective contributors.<br /><br />
|
||||
All rights reserved. The OpenJS Foundation has registered
|
||||
trademarks and uses trademarks. For a list of trademarks
|
||||
of the OpenJS Foundation, please see our Trademark Policy
|
||||
and Trademark List. Trademarks and logos not indicated on
|
||||
the list of OpenJS Foundation trademarks are
|
||||
trademarks™ or registered® trademarks of their
|
||||
respective holders. Use of them does not imply any
|
||||
affiliation with or endorsement by them.<br /><br />
|
||||
<a href="https://openjsf.org">The OpenJS Foundation</a>
|
||||
|
|
||||
<a href="https://terms-of-use.openjsf.org"
|
||||
>Terms of Use</a
|
||||
>
|
||||
|
|
||||
<a href="https://privacy-policy.openjsf.org"
|
||||
>Privacy Policy</a
|
||||
>
|
||||
| <a href="https://bylaws.openjsf.org">Bylaws</a> |
|
||||
<a href="https://code-of-conduct.openjsf.org"
|
||||
>Code of Conduct</a
|
||||
>
|
||||
|
|
||||
<a href="https://trademark-policy.openjsf.org"
|
||||
>Trademark Policy</a
|
||||
>
|
||||
|
|
||||
<a href="https://trademark-list.openjsf.org"
|
||||
>Trademark List</a
|
||||
>
|
||||
|
|
||||
<a href="https://www.linuxfoundation.org/cookies"
|
||||
>Cookie Policy</a
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
<script type="module" src="index.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,37 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ Copyright (c) 2017, the Perspective Authors. ┃
|
||||
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
|
||||
// ┃ This file is part of the Perspective library, distributed under the terms ┃
|
||||
// ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃
|
||||
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
|
||||
import { initTheme, createThemeToggle } from "./components/theme.js";
|
||||
import { initDemo } from "./components/demo.js";
|
||||
import { initGallery } from "./components/gallery.js";
|
||||
|
||||
initTheme();
|
||||
document.getElementById("theme-toggle")!.replaceWith(createThemeToggle());
|
||||
|
||||
const page = document.getElementById("page")!;
|
||||
|
||||
function handleScroll() {
|
||||
const scrollTop = document.documentElement.scrollTop;
|
||||
if (scrollTop > 90 && page.classList.contains("header-shift")) {
|
||||
page.classList.remove("header-shift");
|
||||
} else if (scrollTop <= 90 && !page.classList.contains("header-shift")) {
|
||||
page.classList.add("header-shift");
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener("scroll", handleScroll);
|
||||
|
||||
const demoContainer = document.getElementById("demo-container")!;
|
||||
initDemo(demoContainer);
|
||||
|
||||
const gallery = document.getElementById("gallery")!;
|
||||
initGallery(gallery);
|
||||
Reference in New Issue
Block a user