-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1595 from finos/mandelbrot
Add ExprTK example fractal
- Loading branch information
Showing
7 changed files
with
314 additions
and
342 deletions.
There are no files selected for viewing
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
license: apache-2.0 | ||
height: 800 |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Demo of [Perspective](https://github.com/finos/perspective). | ||
|
||
A classic [fractal](https://en.wikipedia.org/wiki/Mandelbrot_set) implemented entirely in ExprTK/Perspective. |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,107 @@ | ||
perspective-viewer { | ||
flex: 1; | ||
margin: 24px; | ||
overflow: visible; | ||
--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; | ||
} |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<!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/webfontloader"></script> | ||
<script>WebFont.load({google:{families:["Roboto Mono:200,400","Material Icons","Open Sans:300,400"]}})</script> | ||
<script src="/node_modules/@finos/perspective/dist/umd/perspective.js"></script> | ||
<script src="/node_modules/@finos/perspective-viewer/dist/umd/perspective-viewer.js"></script> | ||
<script src="/node_modules/@finos/perspective-viewer-datagrid/dist/umd/perspective-viewer-datagrid.js"></script> | ||
<script src="/node_modules/@finos/perspective-viewer-d3fc/dist/umd/perspective-viewer-d3fc.js"></script> | ||
<link rel='stylesheet' href="/node_modules/@finos/perspective-viewer/dist/umd/material-dense.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> |
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,158 @@ | ||
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, | ||
row_pivots: [`floor("index" / ${params.height})`], | ||
column_pivots: [`"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")); | ||
}); |
Oops, something went wrong.