Published
Edited
May 7, 2021
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
seriesA = (regen,
getRandomWalk(
n,
randWalkStart,
d3.randomNormal(randWalkMean, randWalkStDev)
))
Insert cell
seriesB = (regen,
getRandomWalk(
n,
randWalkStart + 0.1,
d3.randomNormal(randWalkMean, randWalkStDev)
))

Insert cell
xAccessor = d => d[0]
Insert cell
yAccessor = d => d[1]
Insert cell
Insert cell
series = [seriesA, seriesB].map(serie => serie.map(normalize))
Insert cell
productSeries = d3.zip(...series).map(([a, b]) => [a[0], a[1] / b[1]])
Insert cell
partialProduct = i =>
productSeries.slice(0, i + 1).concat(series[0].slice(i + 1))
Insert cell
data = d3.zip(...series).map(([a, b]) => ({
x: a[0],
a: a[1],
b: b[1],
A: [0, y(a[1]) - y(0)],
B: [0, y(b[1]) - y(0)]
}))
Insert cell
normalize = (d, i) => [xAccessor(d, i), yAccessor(d, i)]
Insert cell
Insert cell
xExtent = d3.extent(series.flatMap(s => s.map(d => d[0])))
Insert cell
yExtent = d3.extent([
0,
1,
...series.flatMap(s => s.map(d => d[1])),
...productSeries.map(d => d[1])
])
Insert cell
x = d3.scaleLinear(xExtent, [margin.left, width - margin.right])
Insert cell
y = d3.scaleLinear(yExtent, [height - margin.bottom, margin.top])
Insert cell
margin = ({ top: 10, right: 90, bottom: 20, left: 25 })
Insert cell
height = width / 2
Insert cell
Insert cell
xAxis = g => g.attr("transform", `translate(0, ${y(0)})`).call(d3.axisBottom(x))
Insert cell
yAxis = g =>
g.attr("transform", `translate(${x.range()[0]})`).call(d3.axisLeft(y))
Insert cell
dataLine = d3
.line()
.x((d, i) => x(d[0]))
.y((d, i) => y(d[1]))
Insert cell
line = d3
.line()
.x(d => d[0])
.y(d => d[1])
Insert cell
l = p => path => path.attr("d", line([o, p]))
Insert cell
pt = ([px, py]) => circle => circle.attr("cx", px).attr("cy", py)
Insert cell
o = [0, 0]
Insert cell
unity = [0, y(1) - y(0)]
Insert cell
Insert cell
renderPoint = function(d, i) {
const sel = d3.select(this);

d.l1 = sel
.append("path")
.attr("stroke", girderColor)
.call(l(unity));
d.pt1 = sel
.append("circle")
.attr("fill", girderColor)
.attr("r", ptR)
.call(pt(unity));

d.l1AB = sel
.append("path")
.attr("stroke", girderColor)
.attr("d", line([d.A, d.B]));
d.lAB = sel
.append("path")
.attr("stroke", girderColor)
.call(l(d.A));
d.ptAB = sel
.append("circle")
.attr("fill", girderColor)
.attr("r", ptR)
.call(pt(d.A));

d.lA = sel
.append("path")
.attr("stroke", color(null, 0))
.call(l(d.A));
d.lB = sel
.append("path")
.attr("stroke", color(null, 1))
.call(l(d.B));
d.ptA = sel
.append("circle")
.attr("fill", color(null, 0))
.attr("r", ptR)
.call(pt(d.A));
d.ptB = sel
.append("circle")
.attr("fill", color(null, 1))
.attr("r", ptR)
.call(pt(d.B));
}
Insert cell
color = (d, i) => (i ? "steelblue" : "orange")
Insert cell
girderColor = "#ccc"
Insert cell
duration = speed
Insert cell
delay = speed
Insert cell
Insert cell
// https://stackoverflow.com/a/2259502/120290
rotate = ([px, py], angle, [cx, cy] = [0, 0]) => {
const s = Math.sin(angle);
const c = Math.cos(angle);

// translate point back to origin
px -= cx;
py -= cy;

// rotate point
const xnew = px * c - py * s;
const ynew = px * s + py * c;

// translate point back
px = xnew + cx;
py = ynew + cy;
return [px, py];
}
Insert cell
multiply = ([px, py], coef) => [coef * px, coef * py]
Insert cell
Insert cell
d3 = require("d3")
Insert cell
import { getRandomWalk } from "@tophtucker/scrapbook"
Insert cell
import { slider } from "@jashkenas/inputs"
Insert cell
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more