Public
Edited
Jun 26, 2023
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
redrawPlot = (psi) => {
const data = reuleaux.map((d) => Object.assign({}, d));

data.map((d) => Object.assign(d, rotate(d, psi, 0, 0)));

const extentX = d3.extent(data, (d) => d.x),
extentY = d3.extent(data, (d) => d.y),
center = {
x: -extentX[0],
y: -extentY[0]
},
pnts = data.slice(pntIdx, pntIdx + 1);

data.map((d) => {
d.x -= extentX[0];
d.y -= extentY[0];
});

{
const limit = 1000;

trace.centerTrace.push(Object.assign({}, center));
trace.pntTrace.push(Object.assign({}, pnts[0]));

trace.centerTrace[trace.centerTrace.length - 1].distance =
trace.centerTrace.length < 2
? undefined
: distance(
trace.centerTrace[trace.centerTrace.length - 1],
trace.centerTrace[trace.centerTrace.length - 2]
);

trace.pntTrace[trace.pntTrace.length - 1].distance =
trace.pntTrace.length < 2
? undefined
: distance(
trace.pntTrace[trace.pntTrace.length - 1],
trace.pntTrace[trace.pntTrace.length - 2]
);

if (trace.centerTrace.length > limit) trace.centerTrace.shift();
if (trace.pntTrace.length > limit) trace.pntTrace.shift();
}

const scale1 = d3
.scaleLinear()
.domain(d3.extent(trace.pntTrace, (d) => d.distance))
.range([0.1, 1]),
scale2 = d3
.scaleLinear()
.domain(d3.extent(trace.centerTrace, (d) => d.distance))
.range([0.1, 1]);

return Plot.plot({
width: 500,
x: { nice: true, grid: true },
y: {
nice: true,
grid: true,
reverse: true
},
color: { legend: true, scheme: "Tableau10" },
// opacity: { legend: true },
aspectRatio: 1.0,
marks: [
Plot.dot(data, { x: "x", y: "y", fill: "circleIdx", opacity: 0.5, r: 4 }),

Plot.dot(pnts, {
x: "x",
y: "y",
stroke: "circleIdx",
r: 5
}),
Plot.dot(trace.pntTrace, {
x: "x",
y: "y",
fill: "circleIdx",
r: "distance",
opacity: (d) => scale1(d.distance)
}),

Plot.dot(trace.centerTrace, {
x: "x",
y: "y",
fill: "gray",
r: "distance",
opacity: (d) => scale2(d.distance)
}),
Plot.dot([center], { x: "x", y: "y", fill: "red", r: 5 }),

Plot.line([center, pnts[0]], {
x: "x",
y: "y",
stroke: pnts[0].circleIdx
})
]
});
}
Insert cell
trace
Insert cell
drawTrace = (circleIdx = 0) => {
const { pntTrace, centerTrace } = trace,
pntDistance = pntTrace.map((d, i) => {
const dist = i === 0 ? 0 : distance(d, pntTrace[0]);
return { i, dist, type: "distance" };
}),
centerDistance = centerTrace.map((d, i) => {
const dist = i === 0 ? 0 : distance(d, centerTrace[0]);
return { i, dist, type: "distance" };
}),
pntSpeed = pntDistance.map((_, i) => {
return {
i,
speed: i === 0 ? undefined : distance(pntTrace[i], pntTrace[i - 1]),
type: "speed"
};
}),
centerSpeed = centerDistance.map((_, i) => {
return {
i,
speed:
i === 0 ? undefined : distance(centerTrace[i], centerTrace[i - 1]),
type: "speed"
};
});

return [
Plot.plot({
width: 400,
height: 200,
x: { nice: true },
y: { nice: true, domain: [0, 2] },
color: {
legend: true,
// scheme: "Tableau10",
domain: ["pnt", "center"],
range: [colorMapDiscrete[circleIdx], "gray"]
},
grid: true,
marks: [
Plot.line(pntDistance, {
x: "i",
y: "dist",
stroke: (d) => "pnt"
}),
Plot.line(centerDistance, {
x: "i",
y: "dist",
stroke: (d) => "center"
})
]
}),
Plot.plot({
width: 400,
height: 200,
x: { nice: true },
y: { nice: true },
color: {
legend: true,
// scheme: "Tableau10",
domain: ["pnt", "center"],
range: [colorMapDiscrete[circleIdx], "gray"]
},
grid: true,
marks: [
Plot.line(pntSpeed, {
x: "i",
y: "speed",
stroke: (d) => "pnt"
}),
Plot.line(centerSpeed, {
x: "i",
y: "speed",
stroke: (d) => "center"
})
]
})
];
}
Insert cell
Insert cell
colorMapDiscrete = d3.schemeTableau10
Insert cell
canvasParams = {
const width = 400,
height = 300,
scaleX = d3.scaleLinear().domain([0, 1]).range([0, width]).nice(),
scaleY = d3.scaleLinear().domain([0, 1]).range([0, height]).nice(),
scale = d3
.scaleLinear()
.domain([0, 1])
.range([0, Math.min(width, height)])
.nice();

return {
width,
height,
centerX: scaleX(0.5),
centerY: scaleY(0.5),
scaleX,
scaleY,
scale
};
}
Insert cell
reuleaux = {
const data = [];

circles.map(({ points }) => {
points.filter((d) => d.insides === 3).map((d) => data.push(d));
});

return data;
}
Insert cell
circles = {
const s = d3
.scaleLinear()
.domain([0, samples])
.range([0, 2 * Math.PI]);

const circles = [0, 120, 240].map((d, circleIdx) => {
const { x, y } = getXY(initTheta + angle2radius(d)),
points = new Array(samples)
.fill(0)
.map((d, i) =>
Object.assign(
{ insides: 1, circleIdx },
getXY(s(i), centerDistance, x, y)
)
);

return { x, y, circleIdx, points, traceFlag: false };
});

function _count(a, b, c) {
const polygonB = circles[b].points.map((d) => [d.x, d.y]),
polygonC = circles[c].points.map((d) => [d.x, d.y]);

circles[a].points.map((d) => {
if (d3.polygonContains(polygonB, [d.x, d.y])) {
d.insides += 1;
}
if (d3.polygonContains(polygonC, [d.x, d.y])) {
d.insides += 1;
}
});
}

_count(0, 1, 2);
_count(1, 0, 2);
_count(2, 0, 1);

return circles;
}
Insert cell
/**
* Compute the distance between points a and b
**/
distance = (a, b) => {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
Insert cell
/**
* Rotate pnt with psi degrees,
* The xCanBePnt, y is the rotation axis.
**/
rotate = (pnt, psi, xCanBePnt = 0, y) => {
var x0, y0, dx, dy, dx1, dy1, cos, sin;

if (y === undefined) {
(x0 = xCanBePnt.x), (y0 = xCanBePnt.y);
} else {
(x0 = xCanBePnt), (y0 = y);
}

cos = Math.cos(angle2radius(psi));
sin = Math.sin(angle2radius(psi));

dx = pnt.x - x0;
dy = pnt.y - y0;

dx1 = cos * dx + sin * dy;
dy1 = -sin * dx + cos * dy;

return { x: dx1 + x0, y: dy1 + y0 };
}
Insert cell
angle2radius = d3.scaleLinear().domain([0, 180]).range([0, Math.PI])
Insert cell
/**
* Compute x, y coordinates from radius space,
* with offset (dx, dy).
* x + i y = r * exp(-i * theta) + (dx + i dy)
* where i * i = -1
**/
getXY = (theta, r = 1, dx = 0, dy = 0) => {
return { x: r * Math.cos(theta) + dx, y: r * Math.sin(theta) + dy };
}
Insert cell
/**
* Dynamic trace array
*/
trace = {
refreshTrace;
pntIdx;
keepAnimating;
const centerTrace = [],
pntTrace = [];
return { centerTrace, pntTrace };
}
Insert cell
d3 = require("d3")
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