Public
Edited
Mar 7
2 forks
6 stars
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
beeswarm = (data, {circles, cy, showLines}) => {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
svg.append("g")
.call(xAxis);
svg.append("g")
.selectAll("circle")
.data(circles)
.join("circle")
.attr("cx", d => d.x)
.attr("cy", cy)
.attr("r", 3)
.append("title")
.text(d => d.data.name);
if (showLines){
svg.append("g")
.attr("stroke", "red")
.attr("stroke-width", 1.5)
.selectAll("line")
.data(circles)
.join("line")
.attr("x1", d => d.x)
.attr("x2", d => d.x0)
.attr("y1", cy)
.attr("y2", cy);
}

return svg.node();
}
Insert cell
circles = selected.fn(data, {radius: selected.radius, x: d => x(d.value)})
Insert cell
selected = options[type];
Insert cell
options = ({
"Dodge": {
fn: dodge,
radius: radius * 2 + padding,
cy: d => height - margin.bottom - radius - padding - d.y
},
"Force": {
fn: force,
radius: radius + padding / 2,
cy: d => height / 1.15 - margin.bottom - radius - padding - d.y
},
"Mirrored dodge": {
fn: dodgeMirrored,
radius: radius * 2 + padding,
cy: d => height / 1.25 - margin.bottom - radius - padding - d.y
}
})
Insert cell
function force(data, {radius = 1, x = d => d, ticks = 200} = {}) {
const circles = data.map((d, i, data) => ({x0: x(d, i, data), data: d}));
const simulation = d3.forceSimulation(circles)
.force("x", d3.forceX(d => d.x0))
.force("y", d3.forceY(0))
.force("collide", d3.forceCollide(radius));
for (let i = 0; i < ticks; i++) simulation.tick();
return circles;
}
Insert cell
function dodgeMirrored (data, {radius = 1, x = d => d} = {}) {
const radius2 = radius ** 2;
const circles = data.map((d, i, data) => ({x0: x(d, i, data), x: x(d, i, data), data: d})).sort((a, b) => a.x - b.x);
const epsilon = 1e-4;
let head = null, tail = null;

// Returns true if circle ⟨x,y⟩ intersects with any circle in the queue.
function intersects(x, y) {
let a = head;
while (a) {
if (radius2 - epsilon > (a.x - x) ** 2 + (a.y - y) ** 2) {
return true;
}
a = a.next;
}
return false;
}

// Place each circle sequentially.
for (const b of circles) {

// Remove circles from the queue that can’t intersect the new circle b.
while (head && head.x < b.x - radius2) head = head.next;

// Choose the minimum non-intersecting tangent.
if (intersects(b.x, b.y = 0)) {
let a = head;
b.y = Infinity;
do {
let y1 = a.y + Math.sqrt(radius2 - (a.x - b.x) ** 2);
let y2 = a.y - Math.sqrt(radius2 - (a.x - b.x) ** 2);
if (Math.abs(y1) < Math.abs(b.y) && !intersects(b.x, y1)) b.y = y1;
if (Math.abs(y2) < Math.abs(b.y) && !intersects(b.x, y2)) b.y = y2;
a = a.next;
} while (a);
}

// Add b to the queue.
b.next = null;
if (head === null) head = tail = b;
else tail = tail.next = b;
}

return circles;
}
Insert cell
function dodge(data, {radius = 1, x = d => d} = {}) {
const radius2 = radius ** 2;
const circles = data.map((d, i, data) => ({x0: x(d, i, data), x: +x(d, i, data), data: d})).sort((a, b) => a.x - b.x);
const epsilon = 1e-3;
let head = null, tail = null;

// Returns true if circle ⟨x,y⟩ intersects with any circle in the queue.
function intersects(x, y) {
let a = head;
while (a) {
if (radius2 - epsilon > (a.x - x) ** 2 + (a.y - y) ** 2) {
return true;
}
a = a.next;
}
return false;
}

// Place each circle sequentially.
for (const b of circles) {

// Remove circles from the queue that can’t intersect the new circle b.
while (head && head.x < b.x - radius2) head = head.next;

// Choose the minimum non-intersecting tangent.
if (intersects(b.x, b.y = 0)) {
let a = head;
b.y = Infinity;
do {
let y = a.y + Math.sqrt(radius2 - (a.x - b.x) ** 2);
if (y < b.y && !intersects(b.x, y)) b.y = y;
a = a.next;
} while (a);
}

// Add b to the queue.
b.next = null;
if (head === null) head = tail = b;
else tail = tail.next = b;
}

return circles;
}
Insert cell
data = (await FileAttachment("cars-2.csv").csv({typed: true})).map(({Name: name, Weight_in_lbs: value}) => ({name, value: +value}))
Insert cell
x = d3.scaleLinear()
.domain(d3.extent(data, d => d.value))
.range([margin.left, width - margin.right])
Insert cell
xAxis = g => g
.attr("transform", `translate(0,${height - margin.bottom})`)
.call(d3.axisBottom(x).tickSizeOuter(0))
Insert cell
height = 240
Insert cell
radius = 3
Insert cell
padding = 1.5
Insert cell
margin = ({top: 20, right: 20, bottom: 30, left: 20})
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