Published unlisted
Edited
Oct 6, 2020
Insert cell
Insert cell
Insert cell
Insert cell
Added in fork
style = html`<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Source+Serif+Pro:ital,wght@0,400;0,600;0,700;1,400;1,600;1,700&display=swap">
<base target="_top">
<style>/*Copyright 2020 Observable, Inc.*/:root{--syntax_normal:#1b1e23;--syntax_comment:#828282;--syntax_diff:#24292e;--syntax_diff_bg:#fff;--syntax_number:#20a5ba;--syntax_keyword:#c30771;--syntax_atom:#10a778;--syntax_string:#008ec4;--syntax_error:#ffbedc;--syntax_unknown_variable:#838383;--syntax_known_variable:#005f87;--syntax_matchbracket:#20bbfc;--syntax_key:#6636b4;--selection:#d7d4f0;--hr:rgba(0,0,0,0.05);--mono_fonts:14px/1.5 Menlo,Consolas,monospace;--sans-serif:-apple-system,BlinkMacSystemFont,"avenir next",avenir,helvetica,"helvetica neue",ubuntu,roboto,noto,"segoe ui",arial,sans-serif}body{margin:0 14px;font-family:"Source Serif Pro",Iowan Old Style,Apple Garamond,Palatino Linotype,Times New Roman,"Droid Serif",Times,serif,Apple Color Emoji,Segoe UI Emoji,Segoe UI Symbol;font-size:17px;line-height:1.5;-webkit-text-size-adjust:100%;-webkit-font-smoothing:antialiased;color:#1b1e23}body.fullscreen{margin:0}h1,h2,h3,h4,h5,h6{color:#333;font-weight:700;line-height:1.15;margin-top:0;margin-bottom:.25em}h2~p,h3~p,h4~p{margin-top:0}a[href]{text-decoration:none}a[href]:hover{text-decoration:underline}h1 code,h2 code,h3 code,h4 code,h5 code,h6 code{font-size:90%}code,pre,tt{font:var(--mono_fonts)}img{max-width:calc(100vw - 28px)}.katex-display,figure,h1,h2,h3,h4,h5,h6,p,table{max-width:640px}blockquote,ol,ul{max-width:600px}blockquote{margin:1em 1.5em}ol,ul{padding-left:28px}hr{height:1px;margin:1em 0;padding:1em 0;border:none;background:no-repeat 50%/100% 1px linear-gradient(90deg,var(--hr),var(--hr))}pre{padding:2px 0}.observablehq--md-pre{overflow-x:auto}.observablehq>link:only-child,.observablehq>style:only-child{display:block;visibility:hidden;padding:6px 0;white-space:nowrap;font:var(--mono_fonts);color:var(--syntax_keyword)}.observablehq>link:only-child:before{content:"<link>";visibility:visible;text-decoration:none;pointer-events:none}.observablehq>style:only-child:before{content:"<style>";visibility:visible}input:not([type]),input[type=email],input[type=number],input[type=password],input[type=range],input[type=search],input[type=tel],input[type=text],input[type=url]{width:240px}canvas,input{vertical-align:middle}table{width:100%;border-collapse:collapse;font-family:var(--sans-serif);font-size:14px}th{text-align:left}tr:not(:last-child){border-bottom:1px solid #eee}thead tr{border-bottom:1px solid #ccc}figure{margin:1em 0}figure img{max-width:100%}figcaption{font:small var(--sans-serif);color:var(--syntax_unknown_variable)}.observablehq--collapsed,.observablehq--expanded,.observablehq--function,.observablehq--gray,.observablehq--import,.observablehq--string:after,.observablehq--string:before{color:var(--syntax_normal)}.observablehq--collapsed,.observablehq--expanded.observablehq--inspect a{cursor:pointer}.observablehq--caret{margin-right:4px;vertical-align:baseline}.observablehq--field{text-indent:-1em;margin-left:1em}.hljs-comment,.observablehq--empty,.observablehq--prototype-key{color:var(--syntax_comment)}.hljs-built_in{color:var(--syntax_known_variable)}.observablehq--unknown{color:var(--syntax_unknown_variable)}.hljs-doctag,.hljs-keyword,.hljs-name,.hljs-section,.hljs-selector-class,.hljs-selector-id,.hljs-selector-tag,.hljs-strong,.hljs-tag,.hljs-type{color:var(--syntax_keyword)}.observablehq--blue,.observablehq--keyword,a[href]{color:#3182bd}.hljs-deletion,.hljs-variable,.observablehq--forbidden,.observablehq--pink{color:#e377c2}.observablehq--orange{color:#e6550d}.hljs-literal,.observablehq--boolean,.observablehq--null,.observablehq--undefined{color:var(--syntax_atom)}.hljs-bullet,.hljs-link,.hljs-number,.hljs-regexp,.observablehq--bigint,.observablehq--date,.observablehq--green,.observablehq--number,.observablehq--regexp,.observablehq--symbol{color:var(--syntax_number)}.observablehq--index,.observablehq--key{color:var(--syntax_key)}.observablehq--empty{font-style:oblique}.hljs-addition,.hljs-meta,.hljs-string,.hljs-symbol,.hljs-template-tag,.hljs-template-variable,.observablehq--purple,.observablehq--string{color:var(--syntax_string)}.observablehq--error,.observablehq--red{color:#e7040f}.observablehq{position:relative;margin:17px 0;min-height:33px}.observablehq:before{content:"";position:absolute;left:-14px;height:100%;width:4px;transition:background-color .25s linear}.observablehq--changed:before,.observablehq--running:before{background-color:#a9b0bc;transition:none}.observablehq--error:before{background-color:#e7040f}.observablehq--inspect{font:var(--mono_fonts);overflow-x:auto;display:block;padding:6px 0;white-space:pre}.observablehq--inspect.observablehq--import{white-space:normal}.observablehq--inspect::-webkit-scrollbar{display:none}.observablehq--error .observablehq--inspect{word-break:break-all;white-space:pre-wrap}.observablehq--string-expand{margin-left:6px;padding:2px 6px;border-radius:2px;font-size:80%;background:#eee;color:var(--syntax_normal);cursor:pointer;vertical-align:middle;position:sticky;right:0}.observablehq--string-expand:active,.observablehq--string-expand:hover{background:#ddd}</style>
`
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
scpie_eval = function (scpie_diskmap_data) {
const
{atan2, cos, exp, log, sin} = Math,
rotate1 = (array) => [...array.slice(1), array[0]], // returns [A[1], A[2], ... A[n-1], A[0]]
even = (array) => array.filter((x, i) => !(i % 2)),
odd = (array) => array.filter((x, i) => i % 2),
evenodd = (array) => [even(array), odd(array)],
displacement = (array0, array1) => array0.map((x, i) => array1[i] - x),

data = scpie_diskmap_data,
center = data.center,
n = data.vertices.length >> 1,

[nodes0, nodes1] = evenodd(data.nodes),
[values0, values1] = evenodd(data.values),
[weights0, weights1] = evenodd(data.weights),

[vertices0x, vertices0y] = evenodd(data.vertices),
vertices1x = rotate1(vertices0x),
vertices1y = rotate1(vertices0y),
sidesx = displacement(vertices0x, vertices1x),
sidesy = displacement(vertices0y, vertices1y),

[prevertices0x, prevertices0y] = evenodd(data.prevertices),
prevertices1x = rotate1(prevertices0x),
prevertices1y = rotate1(prevertices0y),
[piewidthsx, piewidthsy] = evenodd(data.piewidths),
exponents0 = data.exponents,
exponents1 = rotate1(exponents0);
const scpie_eval = function scpie_eval (x, y) {
if (!isFinite(x + y)) return NaN;
// figure out which sector the point is in
let k;
for (k = 0;; k++) {
if (k === n) return center.slice();
const z_left_z0 = (prevertices0x[k] * y - prevertices0y[k] * x) >= 0;
const z_right_z1 = (prevertices1x[k] * y - prevertices1y[k] * x) < 0;
if (z_left_z0 & z_right_z1) break;
// special case when the sector between k & k+1 is wider than half the disk:
if (piewidthsx[k] <= 0) { if (z_left_z0 | z_right_z1) break; }
}
const
d0x = x - prevertices0x[k],
d0y = y - prevertices0y[k],
d1x = x - prevertices1x[k],
d1y = y - prevertices1y[k],
hx = piewidthsx[k],
hy = piewidthsy[k];
// half-sector closest to prevertex k
if ((d0x*d0x + d0y*d0y) <= (d1x*d1x + d1y*d1y)) {
const // q = denominator of moebius map
qx = d0x - (hx * d1x + hy * d1y),
qy = d0y - (hx * d1y - hy * d1x);
x = (d0x * qx + d0y * qy) * (1 / (qx * qx + qy * qy));
y = (d0y * qx - d0x * qy) * (1 / (qx * qx + qy * qy));

const // complex power
r = exp(0.5 * log(x*x + y*y) * exponents0[k]),
a = atan2(y, x) * exponents0[k];
x = r * cos(a);
y = r * sin(a);
// rational approximation
let out = reval(nodes0[k], values0[k], weights0[k], x, y);
x = out[0]; y = out[1];
// transform to final position; reuse out array
out[0] = vertices0x[k] + (sidesx[k] * x - sidesy[k] * y);
out[1] = vertices0y[k] + (sidesy[k] * x + sidesx[k] * y);
return out;
}
// half-sector closest to prevertex k+1
else {
const // q = denominator of moebius map
qx = d1x - (hx * d0x - hy * d0y),
qy = d1y - (hx * d0y + hy * d0x);
x = (d1x * qx + d1y * qy) * (1 / (qx * qx + qy * qy));
y = (d1y * qx - d1x * qy) * (1 / (qx * qx + qy * qy));

const // complex power
r = exp(0.5 * log(x*x + y*y) * exponents1[k]),
a = atan2(y, x) * exponents1[k];
x = r * cos(a);
y = r * sin(a);
// rational approximation
let out = reval(nodes1[k], values1[k], weights1[k], x, y);
x = out[0]; y = out[1];
// transform to final position; reuse out array
out[0] = vertices1x[k] - (sidesx[k] * x - sidesy[k] * y);
out[1] = vertices1y[k] - (sidesy[k] * x + sidesx[k] * y);
return out;
}
}
Object.assign(scpie_eval, scpie_diskmap_data);
return scpie_eval;
}
Insert cell
reval = function reval(nodes, values, weights, x, y) {
const n = nodes.length;

// running total for numerator & denominator
let px = 0, py = 0, qx = 0, qy = 0;

for (let j = 0; j < n; j += 2) {
// q += w_j / (z - z_j)
// p += f_j * w_j / (z - z_j)
const
xj = x - nodes[j], yj = nodes[j+1] - y,
wxj = weights[j], wyj = weights[j+1],
fxj = values[j], fyj = values[j+1],
dj = 1 / (xj*xj + yj*yj),
qxj = dj * (wxj*xj - wyj*yj),
qyj = dj * (wxj*yj + wyj*xj);
px += fxj * qxj - fyj * qyj;
py += fxj * qyj + fyj * qxj;
qx += qxj, qy += qyj;
}

let
d = 1 / (qx * qx + qy * qy),
fx = (px * qx + py * qy) * d,
fy = (py * qx - px * qy) * d;

// Edge case where x + iy is one of the nodes; directly use value
if (fx + fy !== fx + fy) { // true if x or y is NaN
for (let j = 0; j < n; j += 2) {
const
xj = x - nodes[j],
yj = nodes[j+1] - y;
if ((xj === 0) & (yj === 0)) {
fx = values[j]; fy = values[j+1];
break;
}
}
}

return [fx, fy];
};

Insert cell
Insert cell
diskpts = {
const p1 = 0.7548776662466927; // inverse of plastic number
const p2 = 0.5698402909980532;
return (n) => {
let squarepts = [...Array(2*n).keys()].map(i => [
2 * ((p1 * i) % 1) - 1,
2 * ((p2 * i) % 1) - 1]);
return squarepts.slice(1)
.filter(([x,y]) => x*x + y*y <= 1)
.slice(0, n);
}
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
// bisect chroma until it is in gamut
JCh_in_gamut = (jch) => {
const [J, C, h] = jch;
let cm = C / 2;
return xyz_to_srgb(JzCzhz_inverse(jch).map(X => .5*X))
// TODO: implement this
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Added in fork
done = (scmap_grid_plot,
scmap_angles_plot,
scpie_steps_and_slices_plot,
moebius_plot,
contours_plot,
boundary_points_plot,
LNT_lecture,
lshape_plot,
lshape_convergence_plot)
Insert cell