Skip to content

Instantly share code, notes, and snippets.

@texodus
Last active March 14, 2022 22:54
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save texodus/5485f6b630b08d38218822e507f09f21 to your computer and use it in GitHub Desktop.
Save texodus/5485f6b630b08d38218822e507f09f21 to your computer and use it in GitHub Desktop.
Perspective / Fractal
license: apache-2.0
height: 800
perspective-viewer {
flex: 1;
margin: 24px;
overflow: visible;
}
perspective-viewer[theme="Material Light"],
perspective-viewer[theme="Material Dark"] {
--d3fc-positive--gradient: linear-gradient(
#94d0ff,
#8795e8,
#966bff,
#ad8cff,
#c774e8,
#c774a9,
#ff6ad5,
#ff6a8b,
#ff8b8b,
#ffa58b,
#ffde8b,
#cdde8b,
#8bde8b,
#20de8b
);
}
#app {
display: flex;
flex-direction: column;
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #f2f4f6;
}
#controls {
display: flex;
margin: 24px 24px 0px 40px;
}
.range {
position: relative;
display: inline-flex;
flex-direction: column;
margin-right: 24px;
}
span,
input,
button {
font-family: "Open Sans";
font-size: 12px;
background: none;
margin: 0px;
border-color: #ccc;
color: #666;
padding: 6px 12px 6px 0px;
}
input {
height: 14px;
border-width: 0px;
border-style: solid;
border-bottom-width: 1px;
color: inherit;
outline: none;
}
input[type="range"] {
margin-top: 2px;
}
input[type="number"] {
font-family: "Roboto Mono";
}
input:focus {
border-color: #1a7da1;
}
input::placeholder {
color: #ccc;
}
button {
border: 1px solid #ccc;
text-transform: uppercase;
text-align: center;
text-decoration: none;
display: inline-block;
padding-left: 12px;
height: 28px;
outline: none;
}
button:hover {
cursor: pointer;
}
#run {
justify-self: center;
margin-right: 24px;
height: 83px;
width: 80px;
}
#run:disabled {
opacity: 0.2;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,minimum-scale=1,user-scalable=no" />
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-datagrid@latest"></script>
<script src="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer-d3fc@latest"></script>
<link rel="stylesheet" crossorigin="anonymous" href="https://cdn.jsdelivr.net/npm/@finos/perspective-viewer/dist/css/themes.css" />
<link rel='stylesheet' href="index.css">
</head>
<body>
<div id="app">
<div id="controls">
<button id="run" disabled>Run</button>
<div class="range">
<span>Size</span>
<input id="width" min="25" max="700" type="number" placeholder="Width" value="200"></input>
<input id="height" min="25" max="500" type="number" placeholder="Height" value="200"></input>
</div>
<div class="range">
<span id="xrange">X [-0.4 , -0.3]</span>
<input id="xmin" min="-2" max="1.0" step="0.1" value="-0.4" type="range"></input>
<input id="xmax" min="-2" max="1.0" step="0.1" value="-0.3" type="range"></input>
</div>
<div class="range">
<span id="yrange">Y [-0.7 , -0.6]</span>
<input id="ymin" min="-1" max="1.0" step="0.1" value="-0.7" type="range"></input>
<input id="ymax" min="-1" max="1.0" step="0.1" value="-0.6" type="range"></input>
</div>
<div class="range">
<span>Iterations</span>
<input id="iterations" min="1" max="1000" type="number" placeholder="Iterations" value="100"></input>
</div>
</div>
<perspective-viewer id="viewer"></perspective-viewer>
</div>
<script src="index.js"></script>
</body>
</html>
function generate_mandelbrot(params) {
return `
// color
var height := ${params.height};
var width := ${params.width};
var xmin := ${params.xmin};
var xmax := ${params.xmax};
var ymin := ${params.ymin};
var ymax := ${params.ymax};
var iterations := ${params.iterations};
var x := floor("index" / height);
var y := "index" % height;
var c := iterations;
var cx := xmin + ((xmax - xmin) * x) / (width - 1);
var cy := ymin + ((ymax - ymin) * y) / (height - 1);
var vx := 0;
var vy := 0;
var vxx := 0;
var vyy := 0;
var vxy := 0;
for (var ii := 0; ii < iterations; ii += 1) {
if (vxx + vyy <= float(4)) {
vxy := vx * vy;
vxx := vx * vx;
vyy := vy * vy;
vx := vxx - vyy + cx;
vy := vxy + vxy + cy;
c -= 1;
}
};
c`;
}
function generate_layout(params) {
return {
plugin: "Heatmap",
settings: true,
group_by: [`floor("index" / ${params.height})`],
split_by: [`"index" % ${params.height}`],
columns: ["color"],
expressions: [
generate_mandelbrot(params).trim(),
`floor("index" / ${params.height})`,
`"index" % ${params.height}`,
],
};
}
async function generate_data(table) {
const run = document.getElementById("run");
let json = new Array(width * height);
for (let x = 0; x < width; ++x) {
for (let y = 0; y < height; ++y) {
const index = x * height + y;
json[index] = {
index,
};
}
}
await table.replace(json);
run.innerHTML = `Run`;
}
// GUI
function get_gui_params() {
return [
"xmin",
"xmax",
"ymin",
"ymax",
"width",
"height",
"iterations",
].reduce((acc, x) => {
acc[x] = window[x].valueAsNumber;
return acc;
}, {});
}
function make_range(x, y, range, name) {
const title = () =>
name +
" [" +
x.valueAsNumber.toFixed(1) +
", " +
y.valueAsNumber.toFixed(1) +
"]";
x.addEventListener("input", () => {
window.run.disabled = false;
x.value = Math.min(x.valueAsNumber, y.valueAsNumber - 0.1);
range.innerHTML = title();
});
y.addEventListener("input", () => {
window.run.disabled = false;
y.value = Math.max(x.valueAsNumber + 0.1, y.valueAsNumber);
range.innerHTML = title();
});
}
const make_run_click_callback = (worker, state) => async () => {
if (window.run.innerHTML.trim() !== "Run") {
window.run.innerHTML = "Run";
return;
}
window.run.disabled = true;
if (!state.table) {
state.table = await worker.table({
index: "integer",
});
window.viewer.load(Promise.resolve(state.table));
}
const run = document.getElementById("run");
const params = get_gui_params();
const new_size = params.width * params.height;
if (!state.size || state.size !== new_size) {
let json = {index: new Array(new_size)};
for (let x = 0; x < new_size; ++x) {
json.index[x] = x;
}
state.table.replace(json);
}
state.size = new_size;
run.innerHTML = `Run`;
window.viewer.restore(generate_layout(params));
};
function set_runnable() {
window.run.disabled = false;
}
window.addEventListener("DOMContentLoaded", async function () {
const heatmap_plugin = await window.viewer.getPlugin("Heatmap");
heatmap_plugin.max_cells = 100000;
make_range(xmin, xmax, xrange, "X");
make_range(ymin, ymax, yrange, "Y");
window.width.addEventListener("input", set_runnable);
window.height.addEventListener("input", set_runnable);
window.iterations.addEventListener("input", set_runnable);
run.addEventListener(
"click",
make_run_click_callback(window.perspective.worker(), {})
);
run.dispatchEvent(new Event("click"));
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment