chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,38 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ 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 esbuild = require("esbuild");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
|
||||
async function build() {
|
||||
await esbuild.build({
|
||||
entryPoints: ["client/index.js"],
|
||||
outdir: "dist",
|
||||
format: "esm",
|
||||
bundle: true,
|
||||
target: "es2022",
|
||||
loader: {
|
||||
".ttf": "file",
|
||||
".arrow": "file",
|
||||
".wasm": "file",
|
||||
},
|
||||
assetNames: "[name]",
|
||||
});
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(__dirname, "dist/index.html"),
|
||||
fs.readFileSync(path.join(__dirname, "client/index.html")).toString(),
|
||||
);
|
||||
}
|
||||
|
||||
build();
|
||||
@@ -0,0 +1,21 @@
|
||||
/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
* ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
* ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
* ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
* ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
* ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
* ┃ 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). ┃
|
||||
* ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
|
||||
*/
|
||||
|
||||
|
||||
perspective-viewer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
|
||||
<title>Perspective `esbuild` Example</title>
|
||||
|
||||
<script type="module" src="index.js"></script>
|
||||
<link rel="stylesheet" href="index.css" />
|
||||
</head>
|
||||
<body></body>
|
||||
</html>
|
||||
@@ -0,0 +1,50 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ 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 perspective from "@perspective-dev/client";
|
||||
|
||||
import perspective_viewer from "@perspective-dev/viewer";
|
||||
import "@perspective-dev/viewer-datagrid";
|
||||
import "@perspective-dev/viewer-charts";
|
||||
|
||||
import "@perspective-dev/viewer/dist/css/pro-dark.css";
|
||||
|
||||
import "./index.css";
|
||||
|
||||
import SERVER_WASM from "@perspective-dev/server/dist/wasm/perspective-server.wasm";
|
||||
import CLIENT_WASM from "@perspective-dev/viewer/dist/wasm/perspective-viewer.wasm";
|
||||
|
||||
await Promise.all([
|
||||
perspective.init_server(fetch(SERVER_WASM)),
|
||||
perspective_viewer.init_client(fetch(CLIENT_WASM)),
|
||||
]);
|
||||
|
||||
const viewer = document.createElement("perspective-viewer");
|
||||
document.body.append(viewer);
|
||||
|
||||
// Create two perspective interfaces, one remotely via WebSocket,
|
||||
// and one local via WebWorker.
|
||||
const url = `${window.location.origin.replace("http", "ws")}/subscribe`;
|
||||
const websocket = await perspective.websocket(url);
|
||||
|
||||
// Open a `Table` that is hosted on the server. All instructions
|
||||
// will be proxied to the server `Table` - no calculations are
|
||||
// done on the client.
|
||||
const remote_table = await websocket.open_table("securities");
|
||||
|
||||
// Create a `table` from this, owned by the local WebWorker.
|
||||
// Data is transferred from `view` to the local WebWorker, both
|
||||
// the current state and all future updates, as Arrows.
|
||||
// const local_table = await worker.table(view, { limit: 10000 });
|
||||
|
||||
// Load this in the `<perspective-viewer>`.
|
||||
viewer.load(remote_table);
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "esbuild-remote",
|
||||
"private": true,
|
||||
"version": "4.5.2",
|
||||
"description": "An example of 2 Perspectives, one client and one server, streaming via Apache Arrow.",
|
||||
"scripts": {
|
||||
"start": "node build.js && node server/index.mjs"
|
||||
},
|
||||
"keywords": [],
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@perspective-dev/client": "workspace:",
|
||||
"@perspective-dev/viewer": "workspace:",
|
||||
"@perspective-dev/viewer-charts": "workspace:",
|
||||
"@perspective-dev/viewer-datagrid": "workspace:",
|
||||
"ws": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"esbuild": "catalog:"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ 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 { make_session } from "@perspective-dev/client";
|
||||
import * as securities from "./securities.mjs";
|
||||
import * as path from "node:path";
|
||||
import { promises as fs } from "node:fs";
|
||||
import http from "node:http";
|
||||
import { WebSocketServer as HttpWebSocketServer } from "ws";
|
||||
|
||||
// Don't need this table since it won't be read from node itself, just need
|
||||
// to create it so the WebSocket clients can find it.
|
||||
const _TABLE = await securities.getTable();
|
||||
|
||||
const CONTENT_TYPES = {
|
||||
".js": "text/javascript",
|
||||
".mjs": "text/javascript",
|
||||
".css": "text/css; charset=utf-8",
|
||||
".json": "application/json",
|
||||
".arrow": "arraybuffer",
|
||||
".feather": "arraybuffer",
|
||||
".wasm": "application/wasm",
|
||||
};
|
||||
|
||||
// node buffer -> JS buffer
|
||||
function buffer_to_arraybuffer(buffer) {
|
||||
return new Int8Array(
|
||||
buffer.buffer.slice(
|
||||
buffer.byteOffset,
|
||||
buffer.byteOffset + buffer.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const app = new HttpWebSocketServer({
|
||||
noServer: true,
|
||||
perMessageDeflate: true,
|
||||
});
|
||||
|
||||
app.on("connection", async (ws) => {
|
||||
console.log("Connecting websocket ...");
|
||||
const session = await make_session(async (proto) => {
|
||||
ws.send(buffer_to_arraybuffer(proto));
|
||||
});
|
||||
|
||||
ws.on("message", (proto) => {
|
||||
session.handle_request(buffer_to_arraybuffer(proto));
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
session.close();
|
||||
});
|
||||
});
|
||||
|
||||
const web_server = http.createServer(
|
||||
async function static_files(request, response) {
|
||||
let url =
|
||||
request.url
|
||||
?.split(/[\?\#]/)[0]
|
||||
.replace(/@[\^~]?\d+.[\d\*]*.[\d\*]*/, "") || "/";
|
||||
|
||||
if (url === "/") {
|
||||
url = "/index.html";
|
||||
}
|
||||
|
||||
const extname = path.extname(url);
|
||||
const contentType = CONTENT_TYPES[extname] || "text/html";
|
||||
try {
|
||||
const content = await fs.readFile(`dist/${url}`);
|
||||
if (typeof content !== "undefined") {
|
||||
response.writeHead(200, { "Content-Type": contentType });
|
||||
response.end(content, "utf-8");
|
||||
console.error(`200 ${url}`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.error(`404 ${url}`);
|
||||
response.writeHead(404);
|
||||
response.end("", "utf-8");
|
||||
} catch (error) {
|
||||
console.error(`500 ${url} ${error}`);
|
||||
response.writeHead(500);
|
||||
response.end("", "utf-8");
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
web_server.on("upgrade", (request, socket, head) => {
|
||||
console.log("200 Websocket upgrade");
|
||||
app.handleUpgrade(request, socket, head, (sock) =>
|
||||
app.emit("connection", sock, request),
|
||||
);
|
||||
});
|
||||
|
||||
web_server.listen(8081, () => {
|
||||
console.log(`Listening on ${web_server.address().port}`);
|
||||
});
|
||||
@@ -0,0 +1,153 @@
|
||||
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
|
||||
// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃
|
||||
// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃
|
||||
// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃
|
||||
// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃
|
||||
// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
|
||||
// ┃ 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 perspective from "@perspective-dev/client";
|
||||
|
||||
// Cache updates for faster update rates (but less data diversity)>
|
||||
const CACHE_INPUT = false;
|
||||
|
||||
// If cached, how many updates to cache?
|
||||
const CACHE_ENTRIES = 200;
|
||||
|
||||
// How many rows per update?
|
||||
const UPDATE_SIZE = 50;
|
||||
|
||||
// Update every N milliseconds
|
||||
const TICK_RATE = 20;
|
||||
|
||||
// Size limit of the server-side table
|
||||
const TABLE_SIZE = 10000;
|
||||
|
||||
const SECURITIES = [
|
||||
"AAPL.N",
|
||||
"AMZN.N",
|
||||
"QQQ.N",
|
||||
"NVDA.N",
|
||||
"TSLA.N",
|
||||
"FB.N",
|
||||
"MSFT.N",
|
||||
"TLT.N",
|
||||
"XIV.N",
|
||||
"YY.N",
|
||||
"CSCO.N",
|
||||
"GOOGL.N",
|
||||
"PCLN.N",
|
||||
];
|
||||
const CLIENTS = [
|
||||
"Homer",
|
||||
"Marge",
|
||||
"Bart",
|
||||
"Lisa",
|
||||
"Maggie",
|
||||
"Moe",
|
||||
"Lenny",
|
||||
"Carl",
|
||||
"Krusty",
|
||||
];
|
||||
|
||||
const __CACHE__ = [];
|
||||
|
||||
// perspective.initialize_profile_thread();
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Slow mode (new rows generated on the fly)
|
||||
*/
|
||||
|
||||
function choose(choices) {
|
||||
return choices[Math.floor(Math.random() * choices.length)];
|
||||
}
|
||||
|
||||
function newRows(total_rows) {
|
||||
const rows = [];
|
||||
for (let x = 0; x < total_rows; x++) {
|
||||
rows.push({
|
||||
name: choose(SECURITIES),
|
||||
client: choose(CLIENTS),
|
||||
lastUpdate: new Date(),
|
||||
chg: Math.random() * 20 - 10,
|
||||
bid: Math.random() * 10 + 90,
|
||||
ask: Math.random() * 10 + 100,
|
||||
vol: Math.random() * 10 + 100,
|
||||
});
|
||||
}
|
||||
return rows;
|
||||
}
|
||||
|
||||
async function init_dynamic({ table_size, update_size, tick_rate }) {
|
||||
// Create a `table`.
|
||||
const table = await perspective.table(newRows(table_size), {
|
||||
limit: table_size,
|
||||
name: "securities",
|
||||
});
|
||||
|
||||
// The `table` needs to be registered to a name with the Perspective
|
||||
// `WebSocketServer` in order for the client to get a proxy handle to it.
|
||||
|
||||
// Loop and update the `table` oocasionally.
|
||||
(function postRow() {
|
||||
table.update(newRows(update_size));
|
||||
setTimeout(postRow, tick_rate);
|
||||
})();
|
||||
return table;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
*
|
||||
* Fast mode (rows pre-generated, cached as Arrows)
|
||||
*/
|
||||
|
||||
async function newArrow(total_rows) {
|
||||
const table = await perspective.table(newRows(total_rows));
|
||||
const vw = await table.view();
|
||||
const arrow = await vw.to_arrow();
|
||||
vw.delete();
|
||||
table.delete();
|
||||
return arrow;
|
||||
}
|
||||
|
||||
async function populate_cache(cache_entries) {
|
||||
for (let x = 0; x < cache_entries; x++) {
|
||||
let arrow = await newArrow(100);
|
||||
__CACHE__[x] = arrow;
|
||||
}
|
||||
}
|
||||
|
||||
async function init_cached({ table_size, tick_rate, cache_entries }) {
|
||||
await populate_cache(cache_entries);
|
||||
const table = await perspective.table(newRows(table_size), {
|
||||
limit: table_size,
|
||||
name: "securities",
|
||||
});
|
||||
(function postRow() {
|
||||
const entry = __CACHE__[Math.floor(Math.random() * __CACHE__.length)];
|
||||
table.update(entry);
|
||||
setTimeout(postRow, tick_rate);
|
||||
})();
|
||||
return table;
|
||||
}
|
||||
|
||||
export const getTable = (
|
||||
config = {
|
||||
cached: CACHE_INPUT,
|
||||
tick_rate: TICK_RATE,
|
||||
update_size: UPDATE_SIZE,
|
||||
table_size: TABLE_SIZE,
|
||||
cache_entries: CACHE_ENTRIES,
|
||||
},
|
||||
) => {
|
||||
if (config.cached) {
|
||||
return init_cached(config);
|
||||
} else {
|
||||
return init_dynamic(config);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user