Public
Edited
Jul 5, 2023
1 fork
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
redraw()
Insert cell
function redraw() {
const plt1 = Plot.plot({
width: 400,
aspectRatio: 1.0,
grid: true,
x: { nice: true, domain: bounding.range.x },
y: { nice: true, domain: bounding.range.y },
color: { nice: true, legend: true, scheme: "BuRd" },
marks: [
Plot.dot(lfpPoints, {
x: "x",
y: "y",
r: 10,
stroke: "charge",
strokeWidth: 3
}),
Plot.text(lfpPoints, {
x: "x",
y: "y",
text: (d) => (d.charge > 0 ? "+" : "-"),
fontSize: 30
}),

Plot.dot(bounding.surroundings, { x: "x", y: "y", opacity: 0.1 }),

Plot.frame({ opacity: 0.2 })
]
});

const surroundings = updateSurroundings(),
m = d3.max(
surroundings
.map((d) => Math.abs(d.fieldX))
.concat(surroundings.map((d) => Math.abs(d.fieldY)))
),
scale2 = d3.scaleLinear().domain([-m, m]).range([-2, 2]);

const plt2 = Plot.plot({
width: 400,
aspectRatio: 1.0,
grid: true,
x: { nice: true, domain: bounding.range.x },
y: { nice: true, domain: bounding.range.y },
color: { nice: true, legend: true, scheme: "BuRd" },
marks: [
Plot.dot(surroundings, {
x: "x",
y: "y",
r: "fieldStr",
fill: "fieldStrInNorm",
stroke: "white"
}),

Plot.link(surroundings, {
x1: "x",
y1: "y",
x2: (d) => d.x + scale2(d.fieldX),
y2: (d) => d.y + scale2(d.fieldY)
// stroke: "fieldTheta"
}),

Plot.dot(lfpPoints, {
x: "x",
y: "y",
r: 10,
stroke: "gray",
opacity: 0.5
}),
Plot.text(lfpPoints, {
x: "x",
y: "y",
text: (d) => (d.charge > 0 ? "+" : "-"),
fontSize: 30,
opacity: 0.5
}),

Plot.dot(bounding.surroundings, { x: "x", y: "y", opacity: 0.1 }),

Plot.frame({ opacity: 0.2 })
]
});

return htl.html`
<div style="display: flex">
${plt1}
${plt2}
</div>
`;
}
Insert cell
updateSurroundings()
Insert cell
updateSurroundings = (moveLPFPointsFlag = true) => {
if (moveLPFPointsFlag) {
const { positionFlag } = bounding;
lfpPoints.map((pnt) => {
var { x, y, vx, vy } = pnt;
x += vx;
y += vy;
if (positionFlag({ x, y }) !== 0) {
x -= vx;
y -= vy;
Object.assign(pnt, rndSpeed());
}
Object.assign(pnt, { x, y });
});
}

return surroundings.map((dst) => {
const { x, y, nx, ny } = dst,
fields = lfpPoints.map((src) => computeField(dst, src)),
fieldX = d3.sum(fields, (d) => d.x),
fieldY = d3.sum(fields, (d) => d.y),
fieldStr = Math.sqrt(Math.pow(fieldX, 2) + Math.pow(fieldY, 2)),
fieldStrInNorm = fieldX * nx + fieldY * ny;

return Object.assign(
{},
{ fieldX, fieldY, fieldStr, fieldStrInNorm, x, y }
);
});
}
Insert cell
computeField(surroundings[0], lfpPoints[2])
Insert cell
/**
* Compute the field between dst and src point.
* The dst is the location being computed,
* and the src is the source of electricity charge.
*/
computeField = (dst, src) => {
const c = 1e1,
{ charge } = src,
d = distance(dst, src),
str = (charge / Math.pow(d, 2)) * c,
{ x, y } = {
x: (str * (dst.x - src.x)) / d,
y: (str * (dst.y - src.y)) / d
};

return { str, x, y };
}
Insert cell
lfpPoints = {
const n = LFPNumbers,
positives = [],
negatives = [],
rndCharge = d3.randomUniform(0.2, 0.8);

for (let i = 0; i < n; ++i) {
var { x, y } = rndPosition(),
{ vx, vy } = rndSpeed();
positives.push({ x, y, vx, vy, charge: rndCharge() });

var { x, y } = rndPosition(),
{ vx, vy } = rndSpeed();
negatives.push({ x, y, vx, vy, charge: -rndCharge() });
}

const points = positives.concat(negatives);

return points;
}
Insert cell
lfpPoints
Type Table, then Shift-Enter. Ctrl-space for more options.

Insert cell
rndSpeed = () => {
const { rndSpeedX, rndSpeedY } = bounding;
return {
vx: rndSpeedX(),
vy: rndSpeedY()
};
}
Insert cell
rndPosition = () => {
const { rndRadian, rndR } = bounding,
radian = rndRadian(),
r = rndR();

return { x: Math.cos(radian) * r, y: Math.sin(radian) * r };
}
Insert cell
surroundings = bounding.surroundings.map((d, i) => Object.assign({ i }, d))
Insert cell
bounding = {
const innerRadius = 10,
outerRadius = 11,
center = { x: 0, y: 0 },
range = { x: [-12, 12], y: [-12, 12] },
rndRadian = d3.randomUniform(0, 2 * Math.PI),
rndR = d3.randomUniform(innerRadius * 0.2, innerRadius * 0.8),
rndSpeedX = d3.randomNormal(0, 0.1),
rndSpeedY = d3.randomNormal(0, 0.1);

/**
* Compute the flag refers the position of the given point, p
* p = {x, y}
*
* 0 for inside the innerRadius
* 2 for outside the outerRadius
* 1 for others
*/
function positionFlag(p) {
const r = distance(p, center);
if (r < innerRadius) return 0;
if (r > outerRadius) return 2;
return 1;
}

// Surroundings
const surroundings = [],
pos = (radian) => {
return {
x: Math.cos(radian) * outerRadius,
y: Math.sin(radian) * outerRadius
};
},
n = 100;
for (let i = 0; i < n; ++i) {
var { x, y } = pos((i / n) * 2 * Math.PI);
var { x: nx, y: ny } = norm({ x, y });
surroundings.push({ x, y, nx, ny });
}

return {
innerRadius,
outerRadius,
center,
range,
positionFlag,
rndRadian,
rndR,
surroundings,
rndSpeedX,
rndSpeedY
};
}
Insert cell
norm = (r) => {
const d = distance(r, { x: 0, y: 0 });
return { x: r.x / d, y: r.y / d };
}
Insert cell
distance = (a, b) => {
return Math.sqrt(Math.pow(a.x - b.x, 2) + Math.pow(a.y - b.y, 2));
}
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