Last active
July 22, 2019 19:11
-
-
Save texodus/1ce655d6bc0cc0d9db852d562af3e487 to your computer and use it in GitHub Desktop.
Perspective Mandelbrot Visualizer
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
perspective-viewer { | |
flex: 1; | |
margin: 24px; | |
overflow: visible; | |
--plugin--box-shadow: 0px 4px 3px rgba(0, 0, 0, 0.1); | |
--plugin--border: 1px solid #ddd; | |
--hypergrid-row-hover--background: rgba(0, 0, 0, 0.05); | |
--hypergrid-cell-hover--background: rgba(0, 0, 0, 0.1); | |
--notional--hypergrid-positive--background: #55a755; | |
--notional--hypergrid-positive--color: #ffffff; | |
--notional--hypergrid-negative--background: #c25a5a; | |
--notional--hypergrid-negative--color: #ffffff; | |
--d3fc-gradient-positive: linear-gradient( | |
#94D0FF, | |
#8795E8, | |
#966bff, | |
#AD8CFF, | |
#C774E8, | |
#c774a9, | |
#FF6AD5, | |
#ff6a8b, | |
#ff8b8b, | |
#ffa58b, | |
#ffde8b, | |
#cdde8b, | |
#8bde8b, | |
#20de8b | |
); | |
} | |
perspective-viewer.macsoft { | |
--d3fc-gradient-positive: linear-gradient( | |
#1b4247, | |
#09979b, | |
#75d8d5, | |
#ffc0cb, | |
#fe7f9d, | |
#65323e | |
); | |
} | |
#app { | |
display: flex; | |
flex-direction: column; | |
position: absolute; | |
top: 0; | |
left: 0; | |
right: 0; | |
bottom: 0; | |
background-color: #eee | |
} | |
#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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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://unpkg.com/@finos/perspective-viewer"></script> | |
<script src="https://unpkg.com/@finos/perspective-viewer-hypergrid"></script> | |
<script src="https://unpkg.com/@finos/perspective-viewer-d3fc"></script> | |
<script src="https://unpkg.com/@finos/perspective"></script> | |
<link rel='stylesheet' href="https://unpkg.com/@finos/perspective-viewer/dist/umd/material.css" is="custom-style"> | |
<link rel='stylesheet' href="index.css" is="custom-style"> | |
</head> | |
<body> | |
<div id="app"> | |
<div id="controls"> | |
<button id="run">Run</button> | |
<div class="range"> | |
<span>Size</span> | |
<input id="width" min="25" max="300" type="number" placeholder="Width" value="100"></input> | |
<input id="height" min="25" max="300" type="number" placeholder="Height" value="50"></input> | |
</div> | |
<div class="range"> | |
<span id="xrange">X [-0.4 , -0.1]</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.1" 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="60"></input> | |
<input id="batch" min="1" max="1000" type="number" placeholder="Batch Size" value="1"></input> | |
</div> | |
<div class="range"> | |
<span>Theme</span> | |
<button id="theme">Macsoft</button> | |
</div> | |
</div> | |
<perspective-viewer | |
id="viewer" | |
class="macsoft" | |
plugin="d3_heatmap" | |
row-pivots='["x"]' | |
column-pivots='["y"]' | |
columns='["c"]'> | |
</perspective-viewer> | |
</div> | |
<script src="index.js"></script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Mandelbrot calculation | |
function set_defaults(iterations, args = {}) { | |
args.vx = args.vx || 0.0; | |
args.vy = args.vy || 0.0; | |
args.vxx = args.vxx || 0; | |
args.vyy = args.vyy || 0; | |
args.vxy = args.vxy || 0; | |
args.c = args.c || iterations; | |
} | |
function iter_args(cx, cy, iterations, args = {}) { | |
if (args.vxx + args.vyy <= 4) { | |
args.vxy = args.vx * args.vy; | |
args.vxx = args.vx * args.vx; | |
args.vyy = args.vy * args.vy; | |
args.vx = args.vxx - args.vyy + cx; | |
args.vy = args.vxy + args.vxy + cy; | |
args.c--; | |
} | |
return args; | |
} | |
function mandel_step(json, ix, iy, xmin, xmax, ymin, ymax, width, height, iterations) { | |
const index = ix * height + iy; | |
const args = json[index]; | |
const x = xmin + ((xmax - xmin) * ix) / (width - 1); | |
const y = ymin + ((ymax - ymin) * iy) / (height - 1); | |
set_defaults(iterations, args); | |
const new_args = iter_args(x, y, iterations, args); | |
new_args.x = ix; | |
new_args.y = iy; | |
json[index] = new_args; | |
} | |
function *mandel_iter(width, height, batch) { | |
for (let bi = 0; bi < batch; ++bi) { | |
for (let ix = 0; ix < width; ++ix) { | |
for (let iy = 0; iy < height; ++iy) { | |
yield [ix, iy]; | |
} | |
} | |
} | |
} | |
async function mandelbrot(table, view, xmin, xmax, ymin, ymax, width, height, iterations, batch) { | |
const run = document.getElementById("run"); | |
let json = []; | |
try { | |
for (let ii = 0; ii < iterations / batch; ++ii) { | |
for (const [ix, iy] of mandel_iter(width, height, batch)) { | |
mandel_step(json, ix, iy, xmin, xmax, ymin, ymax, width, height, iterations); | |
} | |
table.replace(json); | |
json = await view.to_json(); | |
run.innerHTML = `Stop (${ii * batch}/${iterations})`; | |
} | |
} finally { | |
run.innerHTML = `Run`; | |
} | |
view.delete(); | |
} | |
// GUI | |
const SCHEMA = { | |
x: "integer", | |
y: "integer", | |
vx: "float", | |
vy: "float", | |
vxx: "float", | |
vyy: "float", | |
vxy: "float", | |
c: "float" | |
}; | |
function make_range(x, y, range, name) { | |
const title = () => | |
name + " [" + | |
x.valueAsNumber.toFixed(1) + ", " + | |
y.valueAsNumber.toFixed(1) + "]"; | |
x.addEventListener("input", () => { | |
x.value = Math.min(x.valueAsNumber, y.valueAsNumber - 0.1); | |
range.innerHTML = title(); | |
}); | |
y.addEventListener("input", () => { | |
y.value = Math.max(x.valueAsNumber + 0.1, y.valueAsNumber); | |
range.innerHTML = title(); | |
}); | |
} | |
const make_theme_click_callback = (theme, viewer) => () => { | |
if (theme.innerHTML === "Vaporwave") { | |
theme.innerHTML = "Macsoft"; | |
viewer.classList.add("macsoft"); | |
} else { | |
theme.innerHTML = "Vaporwave"; | |
viewer.classList.remove("macsoft"); | |
} | |
} | |
const make_run_click_callback = (worker, state) => async () => { | |
if (window.run.innerHTML.trim() !== "Run") { | |
state.view.delete(); | |
window.run.innerHTML = "Run"; | |
return; | |
} | |
window.run.innerHTML = `Stop (0/${window.iterations.valueAsNumber})`; | |
const old_table = state.table; | |
if (state.view) { | |
state.view.delete(); | |
} | |
state.table = worker.table(SCHEMA); | |
state.view = state.table.view(); | |
mandelbrot(state.table, state.view, ...[ | |
"xmin", | |
"xmax", | |
"ymin", | |
"ymax", | |
"width", | |
"height", | |
"iterations", | |
"batch" | |
].map(x => window[x].valueAsNumber)); | |
window.viewer.load(state.table); | |
if (old_table) { | |
old_table.delete(); | |
} | |
} | |
// Main | |
window.addEventListener("WebComponentsReady", async function() { | |
make_range(xmin, xmax, xrange, "X"); | |
make_range(ymin, ymax, yrange, "Y"); | |
theme.addEventListener("click", make_theme_click_callback(theme, viewer)); | |
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