Published
Edited
Jan 23, 2021
Importers
12 stars
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
viewof curve_bez = new View([[177,316],[494,361],[346,66],[243,251]])
Insert cell
Insert cell
curve_cheb = bez3_to_cheb(curve_bez)
Insert cell
Insert cell
npts = 0x81 // n = 2^k + 1
Insert cell
Insert cell
darclength = {
const [x, y] = curve_cheb;
const pts = chebpts(npts);
const Dx = diff(x), Dy = diff(y);
return vals2coeffs(pts.map(p => {
const Dxp = chebeval(Dx, p), Dyp = chebeval(Dy, p);
return Math.sqrt(Dxp*Dxp + Dyp*Dyp);
}));
}
Insert cell
Insert cell
arclength = {
const arclength = cumsum(darclength);
arclength.splice(-1); // Keep using power-of-2 degree polynomial.
return arclength;
}
Insert cell
Insert cell
total_arclength = sum(darclength)
Insert cell
Insert cell
inversearclength = function inversearclength (s0) {
const eps = 1e-8;
if (s0 <= 0) return 0;
if (s0 >= total_arclength) return 1;

let bounds = [-1, 1];
let t = 2 * s0 / total_arclength - 1; // For initial guess, assume a straight line.
let s = chebeval(arclength, t) - s0; // distance along curve from target to guess
let ds = chebeval(darclength, t);
bounds[+(s > 0)] = t; // Establish initial interval.
for (let i = 0; i < 20 && Math.abs(s) > eps; i++) {
t = t - s / ds;
if (t <= bounds[0] || t >= bounds[1]) // If out of bounds, bisect.
t = (bounds[0] + bounds[1])*.5;
s = chebeval(arclength, t) - s0;
ds = chebeval(darclength, t);
bounds[+(s > 0)] = t;
}
return (t + 1) * .5; // Use convention of t in [0, 1].
}
Insert cell
Insert cell
inversearclength_coeffs = {
const vals = chebpts(npts).map(
p => inversearclength(.5 * (p + 1) * total_arclength));
return vals2coeffs(vals);
}
Insert cell
Insert cell
Insert cell
Insert cell
bez3_to_cheb = function bez3_to_cheb([b0, b1, b2, b3]) {
const denom = 1/32;
const [b00, b01] = b0, [b10, b11] = b1, [b20, b21] = b2, [b30, b31] = b3;
return [[
( 10*b00 + 6*b10 + 6*b20 + 10*b30) * denom,
(-15*b00 - 3*b10 + 3*b20 + 15*b30) * denom,
( 6*b00 - 6*b10 - 6*b20 + 6*b30) * denom,
( -b00 + 3*b10 - 3*b20 + b30) * denom
], [
( 10*b01 + 6*b11 + 6*b21 + 10*b31) * denom,
(-15*b01 - 3*b11 + 3*b21 + 15*b31) * denom,
( 6*b01 - 6*b11 - 6*b21 + 6*b31) * denom,
( -b01 + 3*b11 - 3*b21 + b31) * denom
]];
}
Insert cell
Insert cell
Insert cell
Insert cell
import {evaluate as chebeval, diff, cumsum, sum, coeffs2vals, vals2coeffs, chebpts} from "@jrus/cheb"
Insert cell
Insert cell
import {math_css, $, $$} with {$css as $css} from "@jrus/misc"
Insert cell
$css = html`${math_css}`
Insert cell
Insert cell
d3 = require("d3@5")
Insert cell
Insert cell
import {View} from "@mbostock/synchronized-views"
Insert cell
Insert cell
Insert cell
draw_bullet = function draw_bullet(color) {
const svg = d3.select(DOM.svg(10, 10));
svg.append("circle")
.attr("fill", color).attr("r", 4)
.attr("cx", 5).attr("cy", 5);
return svg.node();
}
Insert cell
Insert cell
arclengthdata = {
const vals = coeffs2vals(arclength);
return chebpts(arclength.length).map(
(p,i) => [(p+1)*.5, vals[i]]);
}
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
viewof update_points = {
const
t1 = param,
t1_ = t1 * 2 - 1,
t2 = chebeval(inversearclength_coeffs, t1_),
t2_ = t2 * 2 - 1;
viewof arclength_plot_points.value = [
[t1, chebeval(arclength, t1_)],
[t2, chebeval(arclength, t2_)]]
viewof curve_points.value = [
[chebeval(curve_cheb[0], t1_), chebeval(curve_cheb[1], t1_)],
[chebeval(curve_cheb[0], t2_), chebeval(curve_cheb[1], t2_)]];
return {
"Bezier Plot": viewof curve_points.value,
"Arclength Plot": viewof arclength_plot_points.value};
}
Insert cell
clamp = function clamp(x, min, max) {
return (x < min) ? min : (x > max) ? max : x;
}
Insert cell
Insert cell
bounding_box = function bounding_box(cubic_cheb) {
const x = cubic_cheb, Dx = diff(x);
const extrema = [chebeval(x, -1), chebeval(x, 1)];
// Use quadratic formula to find roots of Dx and Dy in [-1, 1].
// Combine with endpoints, then take min/max.
const a = - Dx[1] - Math.sqrt(Dx[1]*Dx[1] - 8*Dx[2]*(Dx[0]-Dx[2]));
if (!isNaN(a)) {
extrema.push(
chebeval(x, clamp(a / (4 * Dx[2]), -1, 1)),
chebeval(x, clamp(2 * (Dx[0]-Dx[2]) / a, -1, 1)));
}
return [Math.min(...extrema), Math.max(...extrema)];
}
Insert cell

One platform to build and deploy the best data apps

Experiment and prototype by building visualizations in live JavaScript notebooks. Collaborate with your team and decide which concepts to build out.
Use Observable Framework to build data apps locally. Use data loaders to build in any language or library, including Python, SQL, and R.
Seamlessly deploy to Observable. Test before you ship, use automatic deploy-on-commit, and ensure your projects are always up-to-date.
Learn more