Public
Edited
May 5
Insert cell
Insert cell
Insert cell
Insert cell
md`### Create SVG
`
Insert cell
htl.html`<svg width="300px" height="150px">
<rect x="50" y="50" width="50" height="30" fill="blue" transform="translate(200,0)"/>
<rect x="100" y="80" width="40" height="25" fill="red"/>
</svg>`
Insert cell
Insert cell
htl.html`<svg width="300px" height="150px">
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red"/>
</svg>`
Insert cell
htl.html`<svg viewBox="0 0 3 1" width="300px" height="150px">
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red"/>
</svg>`
Insert cell
Insert cell
htl.html`<svg viewBox="0 0 3 1" width="300px" height="150px">
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red"/>
<path d="M${1 + 0.4},0.8 L${0.5 + 2},0.5" stroke="#ccc" stroke-width="0.01" />
</svg>`
Insert cell
htl.html`<svg viewBox="0 0 3 1" width="300px" height="150px">
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red"/>
<path d="M${1 + 0.4},${(0.8 + 0.25 / 2)} C${(1 + 0.5 + 2)/2},${(0.8 + 0.25 / 2)},${(1 + 0.5 + 2)/2},${(0.5 + 0.3 / 2)} ${(0.5 + 2)},${(0.5 + 0.3 / 2)}" stroke="#ccc" stroke-width="0.1" fill="none" />
</svg>`
Insert cell
Insert cell
htl.html`<svg viewBox="0 0 3 1" width="300px" height="150px">
<g transform="translate(0.5,0)">
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red"/>
<path d="M${1 + 0.4},${(0.8 + 0.25 / 2)} C${(1 + 0.5 + 2)/2},${(0.8 + 0.25 / 2)},${(1 + 0.5 + 2)/2},${(0.5 + 0.3 / 2)} ${(0.5 + 2)},${(0.5 + 0.3 / 2)}" stroke="#ccc" stroke-width="0.1" fill="none" />
</g>
</svg>`
Insert cell
Insert cell
Insert cell
diagram = htl.html`<svg viewBox="0 0 3 1" width="300px" height="150px">
${links.map((d) => {
const x0 = 1.4;
const x1 = 2.5;
const y0 = 0.8 + d.value / 2 + d.offset;
const y1 = 0.5 + d.value / 2 + d.offset;
return htl.svg`<path d="M${x0},${y0} C${(x0 + x1)/2},${y0} ${(x0 + x1)/2},${y1} ${x1},${y1}" stroke="#ccc" stroke-width="${d.value}" fill="none" />`;
})}
<rect x="0.5" y="0.5" width="0.5" height="0.3" fill="blue" class="myRect" transform="translate(2,0)"/>
<rect x="1" y="0.8" width="0.4" height="0.25" fill="red" class="myRect" />
</svg>`
Insert cell
Insert cell
style = html`
<style>
.myRect {
stroke: black;
stroke-width: 0.02px;
}
</style>`
Insert cell
Insert cell
Insert cell
svgSel = d3.select(diagram).selectAll("rect").style("fill", "#69b3a2")
Insert cell
svgSel.nodes()
Insert cell
Insert cell
import { Histogram } from "@d3/histogram"
Insert cell
penMass = penData.map((elem) => elem.body_mass_g)
Insert cell
Histogram(penMass)
Insert cell
Insert cell
Histogram(penMass, { width, height: 200, color: "steelblue" })
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
layout = {
// nodeHeight = max(inflow, outflow)
// Calculate inflow and outflow for each node
const inflow = {}, outflow = {};
fruitsFlowData.links.forEach(link => {
outflow[link.source] = (outflow[link.source] || 0) + link.value;
inflow[link.target] = (inflow[link.target] || 0) + link.value;
});

// Assign each node its total flow (max of in/out)
fruitsFlowData.nodes.forEach(node => {
const input = inflow[node.id] || 0;
const output = outflow[node.id] || 0;
node.flow = Math.max(input, output);
});
// X scale: maps levels to horizontal positions
const maxLevel = d3.max(fruitsFlowData.nodes, n => n.level);
const scaleX = d3.scaleLinear()
.domain([0, maxLevel + 1])
.range([0, width - layoutProp.nodeWidth]);
// Y scale: maps flow value to pixel height
const levelFlow = {}, levelNodes = {}; // Total flow per level, Total nodes per level
fruitsFlowData.nodes.forEach(n => {
levelFlow[n.level] = (levelFlow[n.level] || 0) + n.flow;
levelNodes[n.level] = (levelNodes[n.level] || 0) + 1;
});
const maxFlowPerLevel = d3.max(Object.values(levelFlow));
const maxNodesPerLevel = d3.max(Object.values(levelNodes));
const scaleFlow = d3.scaleLinear()
.domain([0, maxFlowPerLevel])
.range([0, layoutProp.height - (maxNodesPerLevel - 1) * layoutProp.nodePadding]); // leave room for padding

// Position nodes within each level
const levels = {};
fruitsFlowData.nodes.forEach(n => {
if (!levels[n.level]) levels[n.level] = [];
levels[n.level].push(n);
});

for (const level in levels) {
let y = 0;
for (const node of levels[level]) {
node.x = scaleX(node.level);
node.y = y;
node.height = scaleFlow(node.flow);
y += node.height + layoutProp.nodePadding;
}
}

// Assign source/target node references and thickness to links
const nodeMap = {};
fruitsFlowData.nodes.forEach(n => nodeMap[n.id] = n);

fruitsFlowData.links.forEach(link => {
link.sourceNode = nodeMap[link.source];
link.targetNode = nodeMap[link.target];
link.thickness = scaleFlow(link.value);
});

// Stack links grouped by fruit for each source and target
const bySource = {}, byTarget = {};
fruitsFlowData.links.forEach(link => {
(bySource[link.source] ||= []).push(link);
(byTarget[link.target] ||= []).push(link);
});

for (const id in bySource) {
let offset = 0;
for (const link of bySource[id].sort((a, b) => d3.ascending(a.fruit, b.fruit))) {
link.y0 = nodeMap[id].y + offset;
offset += link.thickness;
}
}

for (const id in byTarget) {
let offset = 0;
for (const link of byTarget[id].sort((a, b) => d3.ascending(a.fruit, b.fruit))) {
link.y1 = nodeMap[id].y + offset;
offset += link.thickness;
}
}

return { ...fruitsFlowData };
}
Insert cell
Insert cell
sankey = {
const svg = d3.create("svg")
.attr("width", layoutProp.width)
.attr("height", layoutProp.height);

// Draw links
svg.append("g")
.selectAll("path")
.data(layout.links)
.join("path")
.attr("d", d => {
const x0 = d.sourceNode.x + layoutProp.nodeWidth;
const x1 = d.targetNode.x;
const y0 = d.y0 + d.thickness / 2;
const y1 = d.y1 + d.thickness / 2;
return `M${x0},${y0} C${(x0 + x1)/2},${y0} ${(x0 + x1)/2},${y1} ${x1},${y1}`;
})
.attr("fill", "none")
.attr("stroke", d => fruitColors[d.fruit])
.attr("stroke-width", d => d.thickness)
.attr("opacity", 0.5)
.attr("cursor", "pointer")
.on("mouseover", function () {
d3.select(this).attr("opacity", 0.8);
})
.on("mouseout", function () {
d3.select(this).attr("opacity", 0.5);
});

// Draw nodes
svg.append("g")
.selectAll("rect")
.data(layout.nodes)
.join("rect")
.attr("x", d => d.x)
.attr("y", d => d.y)
.attr("width", layoutProp.nodeWidth)
.attr("height", d => d.height)
.attr("fill", d => "#000");

// Draw labels
svg.append("g")
.selectAll("text")
.data(layout.nodes)
.join("text")
.attr("x", d => d.level === 0 ? d.x + 14 : d.x - 6)
.attr("y", d => d.y + d.height / 2)
.attr("text-anchor", d => d.level === 0 ? "start" : "end")
.attr("alignment-baseline", "middle")
.style("font", "12px")
.text(d => d.id);

return svg.node();
}
Insert cell
Insert cell
import { Legend, Swatches } from "@d3/color-legend"
Insert cell
Insert cell
Swatches(
d3.scaleOrdinal(
Object.keys(fruitColors),
Object.values(fruitColors)
)
)
Insert cell
Insert cell
alphabet = [..."ABCDEFGHIJKLMNOPQRSTUVWXYZ"]
Insert cell
Insert cell
chartStatic = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, 30])
.attr("font-family", "sans-serif")
.attr("font-size", 16);

svg
.selectAll("text")
.data(alphabet)
.join("text")
.attr("x", (d, i) => i * 17)
.attr("y", 17)
.text((d) => d);

return svg.node();
}
Insert cell
Insert cell
htl.html`<svg viewBox="0 0 ${width} 33" font-family="sans-serif" font-size="16">
${alphabet.map(
(d, i) => htl.svg`<text x="${i * 17}" y="17" dy="0.35em">${d}</text>`
)}
</svg>`
Insert cell
Insert cell
Insert cell
Insert cell
randomLetters = {
while (true) {
yield d3
.shuffle(alphabet.slice())
.slice(Math.floor(Math.random() * 10) + 5)
.sort(d3.acending);
await Promises.delay(3000);
}
}
Insert cell
chartDynamic = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 16);

let text = svg.selectAll("text");

return Object.assign(svg.node(), {
update(letters) {
text = text
.data(letters)
.join("text")
.attr("x", (d, i) => i * 17)
.attr("y", 17)
.attr("dy", "0.35em")
.text((d) => d);
}
});
}
Insert cell
chartDynamic.update(randomLetters)
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
chartDynamicKey = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 16);

let text = svg.selectAll("text");

return Object.assign(svg.node(), {
update(letters) {
text = text
.data(letters, (d) => d)
.join(
(enter) =>
enter
.append("text")
.attr("y", 17)
.attr("dy", "0.35em")
.attr("fill", "green")
.text((d) => d),
(update) => update.attr("fill", "gray"),
(exit) => exit.remove()
)
.attr("x", (d, i) => i * 17);
}
});
}
Insert cell
chartDynamicKey.update(randomLetters)
Insert cell
Insert cell
chartDynamicAnimation = {
const svg = d3
.create("svg")
.attr("viewBox", [0, 0, width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 16);

let text = svg.selectAll("text");

return Object.assign(svg.node(), {
update(letters) {
const t = svg.transition().duration(750);
text = text
.data(letters, (d) => d)
.join(
(enter) =>
enter
.append("text")
.attr("y", -7) // start position of new element
.attr("x", (d, i) => i * 17)
.attr("dy", "0.35em")
.attr("fill", "green")
.text((d) => d),
(update) => update.attr("fill", "black"),
(exit) =>
exit
.attr("fill", "red")
.call((text) => text.transition(t).attr("y", 41).remove())
)
.call((text) =>
text
.transition(t)
.attr("y", 17) // end position of each enter and update element
.attr("x", (d, i) => i * 17)
);
}
});
}
Insert cell
chartDynamicAnimation.update(randomLetters)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
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