Skip to content

Instantly share code, notes, and snippets.

@TyOverby
Created October 15, 2023 22:59
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 TyOverby/9778ca247d6d0716f7d8bdf2e657c702 to your computer and use it in GitHub Desktop.
Save TyOverby/9778ca247d6d0716f7d8bdf2e657c702 to your computer and use it in GitHub Desktop.
sample-viewer
<!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>
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