Public
Edited
Jul 5, 2024
Insert cell
Insert cell
d3 = require("d3@7")

Insert cell
Insert cell
// Prepare your data
data = [
{ group: "A", taxa: "Type1", abundance: 30 },
{ group: "A", taxa: "Type2", abundance: 50 },
{ group: "B", taxa: "Type3", abundance: 40 },
{ group: "B", taxa: "Type4", abundance: 60 },
{ group: "C", taxa: "Type1", abundance: 70 },
{ group: "C", taxa: "Type2", abundance: 20 },
{ group: "D", taxa: "Type3", abundance: 10 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type1", abundance: 30 },
{ group: "A", taxa: "Type2", abundance: 50 },
{ group: "B", taxa: "Type3", abundance: 40 },
{ group: "B", taxa: "Type4", abundance: 60 },
{ group: "C", taxa: "Type1", abundance: 70 },
{ group: "C", taxa: "Type2", abundance: 20 },
{ group: "D", taxa: "Type3", abundance: 10 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type1", abundance: 30 },
{ group: "A", taxa: "Type2", abundance: 50 },
{ group: "B", taxa: "Type3", abundance: 40 },
{ group: "B", taxa: "Type4", abundance: 60 },
{ group: "C", taxa: "Type1", abundance: 70 },
{ group: "C", taxa: "Type2", abundance: 20 },
{ group: "D", taxa: "Type3", abundance: 10 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type1", abundance: 30 },
{ group: "A", taxa: "Type2", abundance: 50 },
{ group: "B", taxa: "Type3", abundance: 40 },
{ group: "B", taxa: "Type4", abundance: 60 },
{ group: "C", taxa: "Type1", abundance: 70 },
{ group: "C", taxa: "Type2", abundance: 20 },
{ group: "D", taxa: "Type3", abundance: 10 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type4", abundance: 30 },
{ group: "A", taxa: "Type1", abundance: 50 },
{ group: "B", taxa: "Type2", abundance: 40 },
{ group: "B", taxa: "Type3", abundance: 60 },
{ group: "C", taxa: "Type4", abundance: 70 },
{ group: "C", taxa: "Type1", abundance: 20 },
{ group: "D", taxa: "Type2", abundance: 10 },
{ group: "D", taxa: "Type3", abundance: 80 },
{ group: "A", taxa: "Type3", abundance: 30 },
{ group: "A", taxa: "Type1", abundance: 50 },
{ group: "B", taxa: "Type4", abundance: 40 },
{ group: "B", taxa: "Type2", abundance: 60 },
{ group: "C", taxa: "Type4", abundance: 70 },
{ group: "C", taxa: "Type3", abundance: 20 },
{ group: "D", taxa: "Type2", abundance: 10 },
{ group: "D", taxa: "Type1", abundance: 80 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type1", abundance: 30 },
{ group: "A", taxa: "Type2", abundance: 50 },
{ group: "B", taxa: "Type3", abundance: 40 },
{ group: "B", taxa: "Type4", abundance: 60 },
{ group: "C", taxa: "Type1", abundance: 70 },
{ group: "C", taxa: "Type2", abundance: 20 },
{ group: "D", taxa: "Type3", abundance: 10 },
{ group: "D", taxa: "Type4", abundance: 80 },
{ group: "A", taxa: "Type4", abundance: 30 },
{ group: "A", taxa: "Type1", abundance: 50 },
{ group: "B", taxa: "Type2", abundance: 40 },
{ group: "B", taxa: "Type3", abundance: 60 },
{ group: "C", taxa: "Type4", abundance: 70 },
{ group: "C", taxa: "Type1", abundance: 20 },
{ group: "D", taxa: "Type2", abundance: 10 },
{ group: "D", taxa: "Type3", abundance: 80 },
{ group: "A", taxa: "Type3", abundance: 30 },
{ group: "A", taxa: "Type1", abundance: 50 },
{ group: "B", taxa: "Type4", abundance: 40 },
{ group: "B", taxa: "Type2", abundance: 60 },
{ group: "C", taxa: "Type4", abundance: 70 },
{ group: "C", taxa: "Type3", abundance: 20 },
{ group: "D", taxa: "Type2", abundance: 10 },
{ group: "D", taxa: "Type1", abundance: 80 }
// Add more data points
]
Insert cell
groupData = [
{group: 'A', temperature: 15, time: 120},
{group: 'B', temperature: 20, time: 110},
{group: 'C', temperature: 10, time: 130},
{group: 'D', temperature: 25, time: 140},
]

Insert cell
scales = {
const colorScale = d3
.scaleOrdinal()
.domain(["Type1", "Type2", "Type3", "Type4"])
.range(["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]);

const sizeScale = d3
.scaleSqrt()
.domain([
d3.min(data, (d) => d.abundance),
d3.max(data, (d) => d.abundance)
])
.range([4, 10]);

let groupOrder = groupData.map((d) => d.group);

const groupScale = d3
.scaleOrdinal()
.domain(groupOrder)
.range([150, 450, 750, 1050]);

return { colorScale, sizeScale, groupOrder, groupScale };
}
Insert cell
simulation = d3.forceSimulation()
Insert cell
Insert cell
Insert cell
chart = {
const { colorScale, sizeScale, groupScale } = scales;
const width = 1200;
const height = 600;

const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

const nodes = data.map((d) => ({
...d,
radius: sizeScale(d.abundance),
color: colorScale(d.taxa),
x: groupScale(d.group),
y: height / 2
}));

simulation
.nodes(nodes)
.force("x", d3.forceX((d) => groupScale(d.group)).strength(0.01))
.force("y", d3.forceY(height / 2).strength(0.1))
.force(
"collide",
d3
.forceCollide()
.radius((d) => d.radius + 2)
.iterations(1)
)
.on("tick", ticked);

const node = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", (d) => d.radius)
.attr("fill", (d) => d.color);

function ticked() {
node
.transition()
.duration(100)
.attr("cx", (d) => d.x)
.attr("cy", (d) => d.y);
}

return svg.node();
}
Insert cell
{
let { groupOrder, groupScale } = scales;

switch (orderBy) {
case "Temperature":
groupOrder = groupData
.sort((a, b) => d3.ascending(a.temperature, b.temperature))
.map((d) => d.group);
break;
case "Time":
groupOrder = groupData
.sort((a, b) => d3.ascending(a.time, b.time))
.map((d) => d.group);
break;
default:
groupOrder = groupData
.sort((a, b) => d3.ascending(a.time, b.time))
.map((d) => d.group);
}

groupScale.domain(groupOrder);

if (simulation) {
simulation.force("x", d3.forceX((d) => groupScale(d.group)).strength(0.1));
simulation.alpha(1).restart();
}

return groupScale;
}
Insert cell
Insert cell
Insert cell
Insert cell
scalesSquare = {
const colorScale = d3
.scaleOrdinal()
.domain(["Type1", "Type2", "Type3", "Type4"])
.range(["#1f77b4", "#ff7f0e", "#2ca02c", "#d62728"]);

const sizeScale = d3
.scaleSqrt()
.domain([
d3.min(data, (d) => d.abundance),
d3.max(data, (d) => d.abundance)
])
.range([5, 30]);

let groupOrder = groupData.map((d) => d.group);

const groupScale = d3
.scaleOrdinal()
.domain(groupOrder)
.range([150, 450, 750, 1050]);

return { colorScale, sizeScale, groupScale, groupOrder };
}
Insert cell
groupBounds = {
const width = 260;
const height = 400;
return {
A: { x: 0, y: 0, width, height },
B: { x: 300, y: 0, width, height },
C: { x: 600, y: 0, width, height },
D: { x: 900, y: 0, width, height }
};
}
Insert cell
simulationSquare = d3.forceSimulation()
Insert cell
chartSquare = {
const { colorScale, sizeScale } = scalesSquare;
const width = 1200;
const height = 400;

const svg = d3.create("svg").attr("viewBox", [0, 0, width, height]);

// Draw group rectangles
svg
.selectAll("rect")
.data(Object.values(groupBounds))
.enter()
.append("rect")
.attr("x", (d) => d.x)
.attr("y", (d) => d.y)
.attr("width", (d) => d.width)
.attr("height", (d) => d.height)
.attr("fill", "none")
.attr("stroke", "#ccc");

const nodes = data.map((d) => ({
...d,
radius: sizeScale(d.abundance),
color: colorScale(d.taxa),
x: groupBounds[d.group].x + Math.random() * groupBounds[d.group].width,
y: groupBounds[d.group].y + Math.random() * groupBounds[d.group].height
}));

simulationSquare
.nodes(nodes)
.force(
"x",
d3
.forceX((d) => groupBounds[d.group].x + groupBounds[d.group].width / 2)
.strength(0.01)
)
.force(
"y",
d3
.forceY((d) => groupBounds[d.group].y + groupBounds[d.group].height / 2)
.strength(0.01)
)
.force(
"collide",
d3
.forceCollide()
.radius((d) => d.radius * 1.15)
.iterations(10)
)
.force("bounds", () => {
nodes.forEach((d) => {
const bounds = groupBounds[d.group];
d.x = Math.max(
bounds.x + d.radius,
Math.min(bounds.x + bounds.width - d.radius, d.x)
);
d.y = Math.max(
bounds.y + d.radius,
Math.min(bounds.y + bounds.height - d.radius, d.y)
);
});
})
.on("tick", ticked);

const node = svg
.selectAll("circle")
.data(nodes)
.enter()
.append("circle")
.attr("r", (d) => d.radius)
.attr("fill", (d) => d.color);

function ticked() {
node.attr("cx", (d) => d.x).attr("cy", (d) => d.y);
}

return svg.node();
}
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