Public
Edited
Apr 20, 2023
Insert cell
Insert cell
chart = {
const svg = d3.create("svg")
.attr("viewBox", "-" + adj + " -"+ adj + " " + (width + adj*2) + " " + (height + adj*2))
.style("background", "#ffffff");

const {nodes, links} = sankey({
nodes: graph.nodes.map(d => Object.assign({}, d)),
links: graph.links.map(d => Object.assign({}, d))
});
var total_men = 0;
var total_women = 0;
nodes.forEach(function(node,i){
if(node["targetLinks"]){
if(node.name == "active"){
total_men = node.value;
}
if(node.name == "Female "){
total_women = node.value;
}
}
});
//adding nodes
svg.append("g")
.selectAll("rect")
.data(nodes)
.join("rect")
.attr("id", d => d.name)
.attr("x", d => d.x0)
.attr("y", d => d.y0)
.attr("fill", color2)
.attr("height", d => d.y1 - d.y0)
.attr("width", d => "30px")//d.x1 - d.x0)
.on("mouseover", rectMouseOver)
.on("mouseout", rectMouseOut)
.append("title")
.text(d => `${d.name}\n${d.value.toLocaleString()}`);
var x_positions = [];
svg.selectAll("rect").each(function(d,i){
var pos = d3.select(this).attr("x");
if(pos == 0){
pos = 10;
} else{
pos = pos - 1;
}
if(!x_positions.includes(pos)){
x_positions.push(pos);
}
});
var x_objects = [];
var i;
for(i=0; i < keys.length; i++){
x_objects.push({columnName: keys[i], xpos: x_positions[i]});
}
svg.append("g")
.style("font", "25px sans-serif")
.selectAll("text")
.data(x_objects)
.join("text")
.attr("fill","#000000")
.attr("id", "columns")
.attr("font-weight", "bold")
.attr("x", d => d.xpos - 30)
.attr("y", -20)
.text(function(d){return d.columnName});
const path = svg.append("g")
.attr("fill", "none")
.selectAll("g")
.data(links)
.join("path")
.attr("d", d3.sankeyLinkHorizontal())
.attr("stroke", color1)
.attr("stroke-width", d => d.width)
.style("mix-blend-mode", "multiply")
.on("mouseover", d => {
path.attr("stroke", l => {
if(d.names.length==2 && _.isEqual(l.names[0],d.names[0])){
return color(d.names[0]);
}
if(_.isEqual(l.names,d.names.slice(0,l.names.length))){
return color(d.names[0]);
}
return color1;
});
})
.on("mouseout", d => {
path.attr("stroke",color1);
});
path.append("title")
.text(d => `${d.names.join(" → ")}\n${d.value.toLocaleString()}`);
//adding title labels
const label = svg.append("g")
.style("font", "19px sans-serif")
.selectAll("text")
.data(nodes)
.join("text")
.attr("x", d => d.x0 < width / 2 ? d.x1 + 20 : d.x0 - 10)
.attr("y", d => (d.y1 + d.y0) / 2)
.attr("dy", "0.35em")
.attr("font-weight", "bold")
.attr("text-anchor", d => d.x0 < width / 2 ? "start" : "end")
.text(d => d.name)
.attr("fill","#000000")
.append("tspan")
.attr("id", d=>d.name)
.attr("class",d=>d.index)
.attr("fill-opacity", 0.7)
.text(d => ` ${d.value.toLocaleString()}`);
function rectMouseOver(selected){
var sourceNodes = selected["targetLinks"];
var targetNodes = selected["sourceLinks"];
var sourceNodeInd = [];
sourceNodes.forEach(function(node,index){
sourceNodeInd.push({id: node["source"]["index"], value: node.value});
});
targetNodes.forEach(function(node,index){
sourceNodeInd.push({id: node["target"]["index"], value: node.value});
});
var result = [];
sourceNodeInd.reduce(function(res, value) {
if (!res[value.id]) {
res[value.id] = { id: value.id, value: 0 };
result.push(res[value.id]);
}
res[value.id].value += value.value;
return res;
}, {});
var ids = [];
result.forEach(function(d,ind){
ids[ind] = d.id;
});
ids.push(selected.index);
var textArr = [];
for(var i = 0; i < result.length; i++){
textArr[result[i].id] = result[i].value;
}
for(var key in textArr){
svg.select("tspan[class=\'" + key + "\']").text(` ${textArr[key]}`);
}
svg.selectAll("text")
.filter(function(d){
if(!ids.includes(d.index) &&
!d.columnName &&
d.name != "Male " &&
d.name != "Female "){
return true;
}else{
return false;
}
}).style("opacity" , .3);
svg.selectAll("rect")
.filter(d=> !ids.includes(d.index))
.style("opacity", .3);
svg.select("tspan[id='Male ']")
.attr("fill-opacity",1)
.attr("font-weight","bold")
.attr("font-size", "19px")
.attr("fill","#000000")
.text(function(d){
if(selected.name == "Male " || selected.name == "Female "){
return ` ${d3.format(".0%")(total_men / (total_men + total_women)).toLocaleString()}`
}
return ` ${d3.format(".0%")(selected.men_total / selected.value).toLocaleString()}`
});
svg.select("tspan[id='Female ']")
.attr("fill-opacity",1)
.attr("font-weight","bold")
.attr("font-size", "19px")
.attr("fill","#000000")
.text(function(d){
if(selected.name == "Female " || selected.name == "Male "){
return ` ${d3.format(".0%")(total_women / (total_men + total_women)).toLocaleString()}`
}
return ` ${d3.format(".0%")(selected.women_total / selected.value).toLocaleString()}`
});
path.filter(d=>d.target.index !== selected.index)
.filter(d=>d.source.index !== selected.index)
.style("opacity",.1);
}
function rectMouseOut(selected){
svg.selectAll("tspan")
.attr("fill-opacity", 1)
.attr("font-size", "19px")
.text(d => ` ${d.value.toLocaleString()}`);
svg.selectAll("path").style("opacity", 1);
svg.selectAll("rect").style("opacity", 1);
svg.selectAll("text").style("opacity", 1);
}
return svg.node();
}
Insert cell
adj = 50
Insert cell
height = 720 - adj*2
Insert cell
color2 = "#CC0000"
Insert cell
color1 = "#ccc"
Insert cell
sankey = d3.sankey()
.nodeSort(null)//null and undefined are two options here
.linkSort(null)//null and undefined are two options here
.nodeWidth(30)
.nodePadding(25)//space between two nodes
.extent([[0, 5], [width, height - 5]])
Insert cell
graph = {
let index = -1;
const nodes = [];
const nodeByKey = new Map;
const indexByKey = new Map;
const links = [];

for (const k of keys) {
for (const d of data) {
const key = JSON.stringify([k, d[k]]);
if (nodeByKey.has(key)) continue;
const node = {name: d[k]};
nodes.push(node);
nodeByKey.set(key, node);
indexByKey.set(key, ++index);
}
}

for (let i = 1; i < keys.length; ++i) {
const a = keys[i - 1];
const b = keys[i];
const prefix = keys.slice(0, i + 1);
const linkByKey = new Map;
for (const d of data) {
const names = prefix.map(k => d[k]);
const key = JSON.stringify(names);
const value = d.COUNT || 1;
let link = linkByKey.get(key);
if (link) { link.value += value; continue; }
link = {
source: indexByKey.get(JSON.stringify([a, d[a]])),
target: indexByKey.get(JSON.stringify([b, d[b]])),
names,
value
};
links.push(link);
linkByKey.set(key, link);
}
}

return {nodes, links};
}
Insert cell
color = d3.scaleOrdinal(["active", "lost"], ["lightslategray","lightslategray"]).unknown("lightslategray")
Insert cell
Type JavaScript, then Shift-Enter. Ctrl-space for more options. Arrow ↑/↓ to switch modes.

Insert cell
keys = data.columns.slice(1, 5)
Insert cell
data = d3.csvParse(await FileAttachment("sankey@5.csv").text(), d3.autoType)
Insert cell
d3 = require("d3@5", "d3-sankey@0.12")

Insert cell
_ = require('lodash');
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