Published
Edited
Apr 25, 2022
2 forks
Insert cell
Insert cell
Insert cell
data = FileAttachment("lgbtq1@3.json").json()
Insert cell
Insert cell
Insert cell
d3WithSankey = require("d3@6", "d3-sankey@0.12")
Insert cell
d3WithSankey.sankey()
Insert cell
Insert cell
margin = ({top: 40, bottom: 40, left: 30, right: 30});
Insert cell
height = 600 - margin.top - margin.bottom;
Insert cell
width = 1200 - margin.left - margin.right;
Insert cell
duration = 100
Insert cell
arrow = "\u2192"
Insert cell
Insert cell
mySankey = d3WithSankey.sankey()
.size([width, height])
.nodeId(d => d.name)
.nodePadding(10)
.nodeWidth(20)
.extent([[0, 5], [width, height-5]])
Insert cell
Insert cell
color = (value) => {
return d3WithSankey.interpolateRainbow(value);
}
Insert cell
graph = {
const graph = mySankey({...data});
graph.nodes.forEach((node) => {
node.color = color(node.index / graph.nodes.length);
});

graph.links.forEach((link) => {
// link.gradient = color(link.index / graph.links.length + 0.2);
// link.gradient = DOM.uid("gradient");
link.path = DOM.uid("path");
});
return graph;
}
Insert cell
Insert cell
container = {
// Create a container to hold elements.
const svg = d3.select(DOM.svg(width, height));

const defs = svg.append("defs");

// Add definitions for all of the linear gradients.
// const gradients = defs.selectAll("linearGradient")
// .data(graph.links)
// .join("linearGradient")
// .attr("id", d => d.gradient.id)
// gradients.append("stop").attr("offset", 0.0).attr("stop-color", d => d.source.color);
// gradients.append("stop").attr("offset", 1.0).attr("stop-color", d => d.target.color);
// A view to hold the drawings.
const view = svg.append("g")
.classed("view", true)
.attr("transform", `translate(${margin}, ${margin})`);
// Define the nodes using rectangles (rect)
const nodes = view.selectAll("rect.node")
.data(graph.nodes)
.join("rect")
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("width", d => d.x1 - d.x0) // Defining the width by the difference
.attr("height", d => Math.max(1, d.y1 - d.y0)) // Defining the height by the difference
.attr("fill", d => d.color)
.attr("opacity", 1);
// Add titles for node hover effects.
nodes.append("title").text(d => `${d.name}\n${(d.value)}`);
// Add text labels.
view.selectAll("text.node")
.data(graph.nodes)
.join("text")
.attr("x", d => d.x1)
.attr("dx", 6)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("fill", "black")
.attr("text-anchor", "start")
.attr("font-size", 10)
.attr("font-family", "Helvetica, sans-serif")
.text(d => d.name)
.filter(d => d.x1 > width / 2)
.attr("x", d => d.x0)
.attr("dx", -6)
.attr("text-anchor", "end");
// Define the gray links.
const links = view.selectAll("path.link")
.data(graph.links)
.join("path")
.attr("d", d3WithSankey.sankeyLinkHorizontal())
.attr("stroke", "black")
.attr("stroke-opacity", 0.1)
.attr("stroke-width", d => Math.max(1, d.width))
.attr("fill", "none");
// Add hover effect on the links.
links.append("title").text(d => `${d.source.name} ${arrow} ${d.target.name}\n${(d.value)}`);

// Reference for the following code: https://observablehq.com/@jarrettmeyer/sankey-with-animated-gradients

// Define the default dash behavior for colored gradients.
function setDash(link) {
let el = view.select(`#${link.path.id}`);
let length = el.node().getTotalLength();
el.attr("stroke-dasharray", `${length} ${length}`)
.attr("stroke-dashoffset", length);
}

// Define the gray links
const grayLinks = view.selectAll("path.gray-link")
.data(graph.links)
.join("path")
.classed("gray-link", true)
.attr("id", d => d.path.id)
.attr("d", d3WithSankey.sankeyLinkHorizontal())
.attr("stroke", "black")
.attr("stroke-opacity", 0)
.attr("stroke-width", d => Math.max(1, d.width))
.attr("fill", "none")
.each(setDash);

//Link animation activated when mouse hovers on nodes
//Using nested function to continue animation beyond the selected node
function branchAnimate(evt, node) {
if (node === undefined) node = evt;
let links = view.selectAll("path.gray-link")
.filter((link) => {
return node.sourceLinks.indexOf(link) !== -1;
});
let nextNodes = [];
links.each((link) => {
nextNodes.push(link.target);
});
links.attr("stroke-opacity", 0.3)
.transition()
.duration(duration)
.ease(d3.easeLinear)
.attr("stroke-dashoffset", 0)
.on("end", () => {
nextNodes.forEach((node) => {
branchAnimate(node);
});
});
}
function branchClear() {
grayLinks.transition();
grayLinks.attr("stroke-opactiy", 0)
.each(setDash);
}

nodes.on("mouseover", branchAnimate)
.on("mouseout", branchClear);

return svg
}
Insert cell
svg = container.append("g")
.style("background", "#fff")
.style("width", "100%")
.style("height", "auto")
Insert cell
container.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