diff --git a/default.profraw b/default.profraw deleted file mode 100644 index 11a6b7d4ac..0000000000 Binary files a/default.profraw and /dev/null differ diff --git a/examples/blocks/examples.js b/examples/blocks/examples.js index cba1710237..474ace0d90 100644 --- a/examples/blocks/examples.js +++ b/examples/blocks/examples.js @@ -21,6 +21,7 @@ const LOCAL_EXAMPLES = [ "magic", "streaming", "covid", + "webcam", "movies", "superstore", "citibike", diff --git a/examples/blocks/src/raycasting/index.js b/examples/blocks/src/raycasting/index.js index 8702afb1a2..b5039bcdd5 100644 --- a/examples/blocks/src/raycasting/index.js +++ b/examples/blocks/src/raycasting/index.js @@ -98,16 +98,13 @@ for (var j := 1; j <= radialSegments; j += 1) { if (t >= 0) { var t2 := 1 - u - v; var d1[3] := v0 * t2 + v1 * u + v2 * v; - var d2[3] := d1 - camera; - var dist := norm3(d2); + var dist := norm3(d1 - camera); if (dist < depth) { depth := dist; // Lighting - var ww[3] := v0 - v1; - var zz[3] := v2 - v1; var n[3]; - cross_product3(ww, zz, n); + cross_product3(v0 - v1, v2 - v1, n); color := acos(dot_product3(light, n) / (light_norm * norm3(n))) } } diff --git a/examples/blocks/src/webcam/README.md b/examples/blocks/src/webcam/README.md new file mode 100644 index 0000000000..bb9a184579 --- /dev/null +++ b/examples/blocks/src/webcam/README.md @@ -0,0 +1,2 @@ + +A Perspective example which uses your computer's webcam as a data source. \ No newline at end of file diff --git a/examples/blocks/src/webcam/index.css b/examples/blocks/src/webcam/index.css new file mode 100644 index 0000000000..323fa06b08 --- /dev/null +++ b/examples/blocks/src/webcam/index.css @@ -0,0 +1,89 @@ +/* ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ + * ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ + * ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ + * ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ + * ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ + * ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ + * ┃ 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). ┃ + * ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ + */ + +body { + background: #242526; + color: white; + font-family: "Roboto Mono"; + touch-action: none; +} + +* { + box-sizing: border-box; +} + +#app { + display: flex; + flex-direction: column; + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; +} + +#header { + display: flex; + flex-wrap: wrap; + align-items: center; +} + +#header a { + display: inline-flex; +} + +perspective-viewer { + border-top: 1px solid #666; + flex: 1 1 auto; +} + +label { + height: 32px; + font-size: 12px; + padding: 6px 0px; + margin-right: 4px; + margin-left: 14px; + margin-top: 8px; + margin-bottom: 8px; + border: 1px solid transparent; +} + +img { + vertical-align: middle; + margin-left: 14px; +} + +select, button { + font-family: "Roboto Mono"; + font-size: 12px; + appearance: none; + background-color: transparent; + border: 1px solid #666; + border-radius: 2px; + padding: 6px 10px; + color: #f4f5f6; + cursor: pointer; + margin-right: 4px; + margin-left: 4px; + outline: none; + user-select: none; + height: 32px; + margin-top: 8px; + margin-bottom: 8px; +} + +select:hover, button:hover { + color: #242526; + background-color: #f4f5f6; + border-color: #f4f5f6; +} \ No newline at end of file diff --git a/examples/blocks/src/webcam/index.html b/examples/blocks/src/webcam/index.html new file mode 100644 index 0000000000..0a86904b1b --- /dev/null +++ b/examples/blocks/src/webcam/index.html @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + +
+ + +
+ + diff --git a/examples/blocks/src/webcam/layouts.json b/examples/blocks/src/webcam/layouts.json new file mode 100644 index 0000000000..e1228f33ed --- /dev/null +++ b/examples/blocks/src/webcam/layouts.json @@ -0,0 +1,153 @@ +[ + { + "plugin": "Heatmap", + "title": "Heatmap Cam", + "group_by": ["x"], + "split_by": ["y"], + "columns": ["color"], + "expressions": { + "y": "-floor(\"index\" / 80)", + "x": "-\"index\" % 80" + }, + "aggregates": { + "New Column 1": "any" + } + }, + { + "plugin": "Heatmap", + "plugin_config": {}, + "settings": true, + "theme": "Pro Light", + "title": "Downsampled Heatmap Cam", + "group_by": ["x"], + "split_by": ["y"], + "columns": ["color"], + "filter": [], + "sort": [], + "expressions": { + "y": "bucket(-floor(\"index\" / 80), 3)", + "x": "bucket(-\"index\" % 80, 3)" + }, + "aggregates": {} + }, + { + "plugin": "Datagrid", + "plugin_config": { + "columns": { + "color": { + "bg_gradient": 251.04, + "neg_bg_color": "#ffa38f", + "number_bg_mode": "gradient", + "number_fg_mode": "disabled", + "pos_bg_color": "#346ead" + } + }, + "editable": false, + "scroll_lock": false + }, + "title": "Spreadsheet Cam", + "group_by": ["y"], + "split_by": ["x"], + "columns": ["color"], + "filter": [], + "sort": [], + "expressions": { + "New Column 1": "bucket(\"color\", 5)", + "y": "floor(\"index\" / 80)", + "x": "-\"index\" % 80" + }, + "aggregates": {} + }, + { + "plugin": "Y Bar", + "plugin_config": {}, + "title": "Luminosity Histogram", + "group_by": ["bucket(\"color\", 5)"], + "split_by": [], + "columns": ["color"], + "filter": [], + "sort": [], + "expressions": { + "bucket(\"color\", 5)": "bucket(\"color\", 5)", + "y": "-floor(\"index\" / 80)", + "x": "-\"index\" % 80" + }, + "aggregates": {} + }, + { + "plugin": "Datagrid", + "plugin_config": { + "columns": { + "color": { + "bg_gradient": 2463.68, + "neg_bg_color": "#ffa38f", + "number_bg_mode": "gradient", + "number_fg_mode": "disabled", + "pos_bg_color": "#307bb0" + } + }, + "editable": false, + "scroll_lock": false + }, + "title": "Small Spreadsheet Cam", + "group_by": ["bucket(y, 5)"], + "split_by": ["bucket(x, 5)"], + "columns": ["color"], + "filter": [["bucket(x, 5)", "<", 0.0]], + "sort": [], + "expressions": { + "bucket(y, 5)": "bucket(floor(\"index\" / 80), 2)", + "New Column 1": "bucket(\"color\", 5)", + "bucket(x, 5)": "bucket(-\"index\" % 80, 5)" + }, + "aggregates": {} + }, + { + "plugin": "Y Line", + "plugin_config": {}, + "title": "Max Headroom", + "group_by": ["x"], + "split_by": ["y"], + "columns": ["New Column 2"], + "filter": [["x", "<", 0.0]], + "sort": [], + "expressions": { + "x": "-\"index\" % 80", + "y": "floor(\"index\" / 80)", + "New Column 2": "-floor(\"index\" / 80) * 20 - \"color\"" + }, + "aggregates": { "New Column 2": "avg" } + }, + { + "plugin": "X/Y Scatter", + "plugin_config": {}, + "title": "Scatter Cam", + "group_by": ["x", "y"], + "split_by": [], + "columns": ["x", "New Column 2", "color", null, null, null, null], + "filter": [["x", "<", 0.0]], + "sort": [], + "expressions": { + "New Column 2": "-floor(\"index\" / 80) * 50 - \"color\"", + "x": "-\"index\" % 80", + "y": "floor(\"index\" / 80)" + }, + "aggregates": { "x": "avg", "New Column 2": "avg" } + }, + { + "plugin": "Datagrid", + "plugin_config": { + "columns": {}, + "editable": false, + "scroll_lock": false + }, + "title": "Raw Stream", + "group_by": [], + "split_by": [], + "columns": ["index", "color"], + "filter": [], + "sort": [], + "expressions": {}, + "aggregates": {} + } +] diff --git a/examples/blocks/src/webcam/preview.png b/examples/blocks/src/webcam/preview.png new file mode 100644 index 0000000000..d4f9dbb0ce Binary files /dev/null and b/examples/blocks/src/webcam/preview.png differ diff --git a/examples/blocks/src/webcam/thumbnail.png b/examples/blocks/src/webcam/thumbnail.png new file mode 100644 index 0000000000..6c06e1fa0b Binary files /dev/null and b/examples/blocks/src/webcam/thumbnail.png differ diff --git a/examples/blocks/src/webcam/webcam.js b/examples/blocks/src/webcam/webcam.js new file mode 100644 index 0000000000..5c57cee2bd --- /dev/null +++ b/examples/blocks/src/webcam/webcam.js @@ -0,0 +1,83 @@ +// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ +// ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ +// ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ +// ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ +// ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ +// ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ +// ┃ 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 "/node_modules/@finos/perspective/dist/cdn/perspective.js"; + +const canvas = document.getElementById("canvas"); +const context = canvas.getContext("2d", { willReadFrequently: true }); +const video = document.getElementById("video"); +const WIDTH = canvas.width; +const HEIGHT = canvas.height; +const WORKER = perspective.shared_worker(); + +async function poll(table, tdata) { + context.drawImage(video, 0, 0, WIDTH, HEIGHT); + const data = context.getImageData(0, 0, WIDTH, HEIGHT); + for (let i = 0; i < data.data.byteLength / 4; i++) { + const r = data.data[i * 4]; + const g = data.data[i * 4 + 1]; + const b = data.data[i * 4 + 2]; + const color = 255 - (0.21 * r + 0.72 * g + 0.07 * b); + tdata.color[i] = color; + } + + await table.update(tdata); + setTimeout(() => poll(table, tdata), 50); +} + +async function init_tables() { + if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { + const stream = await navigator.mediaDevices.getUserMedia({ + video: true, + }); + + video.srcObject = stream; + video.play(); + } + + const tdata = { index: [], color: [] }; + for (let i = 0; i < WIDTH * HEIGHT; i++) { + tdata.index[i] = i; + tdata.color[i] = 0; + } + + const table = await WORKER.table(tdata, { index: "index" }); + poll(table, tdata); + return table; +} + +async function init_layouts() { + const req = await fetch("layouts.json"); + return await req.json(); +} + +const INIT_TASK = [init_tables(), init_layouts()]; + +window.addEventListener("DOMContentLoaded", async function () { + const [table, layouts] = await Promise.all(INIT_TASK); + const settings = !/(iPad|iPhone|iPod)/g.test(navigator.userAgent); + const select = document.querySelector("select"); + const viewer = document.querySelector("perspective-viewer"); + viewer.load(table); + viewer.restore({ settings, ...layouts[0] }); + for (const layout of layouts) { + const option = document.createElement("option"); + option.value = layout.title; + option.textContent = layout.title; + select.appendChild(option); + } + + select.addEventListener("change", async (event) => { + const layout = layouts.find((x) => x.title === event.target.value); + await viewer.restore(layout); + }); +});