Created
October 15, 2023 22:59
-
-
Save TyOverby/9778ca247d6d0716f7d8bdf2e657c702 to your computer and use it in GitHub Desktop.
sample-viewer
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> | |
<script defer src="./index.js"></script> | |
<style> | |
body{ overflow:clip; } | |
* { | |
padding:0; | |
margin:0; | |
background: #09141f; | |
} | |
svg { | |
max-height: 100vh; | |
filter: saturate(0.7); | |
} | |
</style> | |
</head> | |
<body> | |
</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
function svg_elt(name, attrs) { | |
const e = document.createElementNS('http://www.w3.org/2000/svg', name); | |
for (const attr in attrs) { | |
e.setAttribute(attr, attrs[attr]); | |
} | |
return e; | |
} | |
function next_movement(state, {x, y}) { | |
let min_dist = Math.abs(x); | |
for (const p of state.points) { | |
if (Math.abs(p.y - y) >= state.radius * 2) { | |
continue; | |
} | |
const dx = x - p.x; | |
const dy = y - p.y; | |
const dist = Math.sqrt(dx * dx + dy * dy) - 2 * state.radius; | |
min_dist = Math.min(min_dist, dist); | |
} | |
return min_dist; | |
} | |
function refine(state, {x, y}, dir_sign) { | |
while(true) { | |
let dist = next_movement(state, {x, y}); | |
if (dist <= 0.01) { break; } | |
x = x + dist * dir_sign; | |
} | |
return {x, y}; | |
} | |
function place_point(state, sample, side) { | |
let start_x = state.max_w + state.radius * 2; | |
let dir_sign = side === 'l' ? 1 : -1; | |
start_x = start_x * (-1 * dir_sign); | |
let {x, y} = refine(state, {x:start_x, y:sample}, dir_sign); | |
state.points.push({x, y}); | |
state.max_w = Math.max(state.max_w, Math.abs(x)); | |
state.max_h = Math.max(state.max_h, y + state.radius); | |
state.min_h = Math.min(state.min_h, y - state.radius); | |
} | |
function place_all(samples, radius) { | |
const state = { points: [], radius, max_w:0, max_h:0, min_h:0 }; | |
let side = 'l'; | |
for (const sample of samples) { | |
place_point(state, sample, side); | |
side = side === 'l' ? 'r' : 'l'; | |
} | |
return state; | |
} | |
function rate(state) { | |
let badness = 0.0; | |
for (const {x} of state.points) { | |
badness += x * x; | |
} | |
return badness; | |
} | |
function jsf32(a, b, c, d) { | |
return function() { | |
a |= 0; b |= 0; c |= 0; d |= 0; | |
var t = a - (b << 27 | b >>> 5) | 0; | |
a = b ^ (c << 17 | c >>> 15); | |
b = c + d | 0; | |
c = d + t | 0; | |
d = a + t | 0; | |
return (d >>> 0) / 4294967296; | |
} | |
} | |
function shuffleArray(array, seed) { | |
const rand = jsf32(seed * 2671, seed * 5399, seed * 6947, seed * 7879); | |
for (var i = array.length - 1; i > 0; i--) { | |
var j = Math.floor(rand() * (i + 1)); | |
var temp = array[i]; | |
array[i] = array[j]; | |
array[j] = temp; | |
} | |
} | |
function make_plot(samples, radius) { | |
let state = place_all(samples, radius); | |
let badness = rate(state); | |
for (let i = 0; i < 100; i++) { | |
shuffleArray(samples, i); | |
let proposed = place_all(samples, radius); | |
let proposed_badness = rate(proposed); | |
if (proposed_badness < badness) { | |
state = proposed; | |
badness = proposed_badness; | |
} | |
} | |
return state; | |
} | |
function render_into_group(state, radius, attrs) { | |
const group = svg_elt("g", attrs); | |
for (const { x, y } of state.points) { | |
group.appendChild(svg_elt("circle", {fill:"black", cx:x, cy:state.max_h - y, r:state.radius + 0.5})) | |
} | |
for (const { x, y } of state.points) { | |
const circle = svg_elt("circle", {fill: "rgba(255,255,255,1)", cx:x, cy:state.max_h - y, r:state.radius - 0.5}); | |
const title = svg_elt("title"); | |
title.textContent = `${y}`; | |
circle.appendChild(title); | |
group.appendChild(circle) | |
} | |
return group; | |
} | |
function render(samples, radius) { | |
const state = make_plot(samples, radius); | |
const container = svg_elt("svg"); | |
container.appendChild(render_into_group(state, radius, {})); | |
container.setAttribute( | |
"viewBox", | |
`${-state.max_w - state.radius} ${state.min_h} ${(state.max_w + state.radius) * 2} ${state.max_h + state.radius * 2}`); | |
return container; | |
} | |
function render_many(samples_array, radius) { | |
const states = samples_array.map(samples => make_plot(samples, radius)); | |
const global_max_h = Math.max(... states.map(({max_h}) => max_h)) + radius; | |
const global_min_h = Math.min(... states.map(({min_h}) => min_h)); | |
// const global_max_w = Math.max(... states.map(({max_w}) => max_w)); | |
const container = svg_elt("svg"); | |
document.body.appendChild(container) | |
let green = "#289628"; | |
let orange = "#ffda00"; | |
let red = "#b51212"; | |
container.innerHTML = ` | |
<defs> | |
<linearGradient gradientUnits="userSpaceOnUse" x1="0" x2="0" y1="${global_max_h - 80}" y2="${global_max_h - (80 + (32*2))}" id="green-to-red"> | |
<stop offset="0" style="stop-color:${green}; stop-opacity:1;" /> | |
<stop offset="0.0001" style="stop-color:${orange}; stop-opacity:1;" /> | |
<stop offset="0.5" style="stop-color:${orange}; stop-opacity:1;" /> | |
<stop offset="0.5001" style="stop-color:${red}; stop-opacity:1;" /> | |
<stop offset="1" style="stop-color:${red}; stop-opacity:1;"/> | |
</linearGradient> | |
</defs>` | |
let offset = radius; | |
for (const state of states) { | |
let group, self_offset; | |
if (state.points.length === 0) { | |
group = svg_elt("g"); | |
container.appendChild(group); | |
let widest; | |
{ | |
const text_bg = svg_elt("text", { x: radius, y: global_max_h, "text-anchor":"end", style:"font-weight: bold; dominant-baseline: central; font-family: monospace; stroke:black; stroke-width:2px;", }); | |
text_bg.textContent = "" + global_max_h; | |
group.appendChild(text_bg); | |
widest = group.getBoundingClientRect().width; | |
group.removeChild(text_bg); | |
} | |
for (let i = 32; i < global_max_h - 8; i += 32) { | |
const text_bg = svg_elt("text", { x: widest + radius, y: global_max_h - i, "text-anchor":"end", style:"font-weight: bold; dominant-baseline: central; font-family: monospace; stroke:black; stroke-width:2px;", }); | |
text_bg.textContent = "" + i; | |
group.appendChild(text_bg); | |
const text = svg_elt("text", { x: widest + radius, y: global_max_h - i, "text-anchor":"end", style:"font-weight: bold; dominant-baseline: central; font-family: monospace; fill:white;" }); | |
text.textContent = "" + i; | |
group.appendChild(text); | |
} | |
state.max_w = group.getBoundingClientRect().width + radius * 2; | |
offset += (state.max_w + radius * 2); | |
for (let i = 16; i < global_max_h - 8; i += 32) { | |
group.prepend(svg_elt("line", {stroke:"rgba(255,255,255,0.7)", style:"mix-blend-mode:overlay;", x1:-state.max_w, x2:state.max_w+state.radius, y1:global_max_h - i, y2:global_max_h - i})); | |
} | |
for (let i = 32; i < global_max_h - 8; i += 32) { | |
group.prepend(svg_elt("line", {stroke:"rgba(255,255,255,0.7)", style:"mix-blend-mode:plus-lighter;", x1:widest + radius + 5, x2:state.max_w+state.radius, y1:global_max_h - i, y2:global_max_h - i})); | |
} | |
group.prepend(svg_elt("rect", {fill:"url(#green-to-red)",rx: 10, x:-state.max_w, y:global_min_h, width: state.max_w * 2 + state.radius, height: global_max_h - global_min_h})); | |
} else { | |
state.max_h = global_max_h; | |
const min_point = Math.min(... state.points.map(({x}) => x)) - radius * 2; | |
const max_point = Math.max(... state.points.map(({x}) => x)) + radius * 2; | |
group = render_into_group(state, radius, {transform : `translate(${offset - min_point} 0)`}); | |
for (let i = 32; i < global_max_h - 8; i += 32) { | |
group.prepend(svg_elt("line", {stroke:"rgba(255,255,255,0.7)", /*style:"mix-blend-mode:plus-lighter;",*/ x1:min_point, x2:max_point, y1:global_max_h - i, y2:global_max_h - i})); | |
} | |
for (let i = 16; i < global_max_h - 8; i += 32) { | |
group.prepend(svg_elt("line", {stroke:"rgba(255,255,255,0.7)", style:"mix-blend-mode:overlay;", x1:min_point, x2:max_point, y1:global_max_h - i, y2:global_max_h - i})); | |
} | |
container.appendChild(group); | |
offset += (max_point - min_point) + radius * 2; | |
group.prepend(svg_elt("rect", {fill:"url(#green-to-red)",rx: radius, x:min_point, y:global_min_h, width: max_point - min_point, height: global_max_h - global_min_h})); | |
} | |
} | |
offset += radius * 2; | |
container.setAttribute("viewBox", `${0} ${global_min_h - radius} ${offset} ${global_max_h + radius * 2}`); | |
return container; | |
} | |
const samples = [ [], | |
[ 73, 89, 66, 73, 73, 75, 73, 77, 68, 74, 60, 75, 61, 72, 63, 71, 66, 65, 60, 81, 63, 67, 60, 80, 67, 74, 59, 67, 60, 74 ], | |
[ 573, 606, 572, 589, 599, 590, 600, 619, 585, 602, 596, 597, 586, 610, 577, 583, 581, 585, 573, 610, 569, 570, 562, 592, 599, 587, 576 ], | |
[ 141, 132, 110, 150, 119, 137, 120, 134, 120, 133, 134, 127, 117, 135, 118, 126, 110, 134, 114, 133, 120, 127, 135, 121, 111, 126, 116, 120, 110, 130], | |
[ 315, 305, 274, 277, 282, 326, 319, 333, 314, 332, 318, 278, 309, 308, 327, 277, 310, 313, 304, 335, 303, 312, 296, 314, 322, 292, 296, 320, 321, 324 ] | |
] | |
document.body.appendChild(render_many(samples, 5)) | |
// document.body.appendChild(render_many(samples, 5)) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment