Published
Edited
Oct 14, 2021
29 stars
Insert cell
Insert cell
Insert cell
Insert cell
viewof chooseData = form(html`<form>
<div>
<label>
Choose an example data set:
<div>
<label><input name="dataset" type="radio" value="data1" checked> <i>data1 (complex)</i></label>
<label><input name="dataset" type="radio" value="data2" > <i>data2 (simple)</i></label>
<label><input name="dataset" type="radio" value="data3"> <i>data3 (lots of circular links)</i></label>
<label><input name="dataset" type="radio" value="data4"> <i>data4 (Water cycle)</i></label>
<label><input name="dataset" type="radio" value="data_wapo"> <i>data5 (WAPO)</i></label>
</label>
</div>
</form>`)
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
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
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
drawSankey(graph9b)
Insert cell
Insert cell
Insert cell
drawSankey(graph9c)
Insert cell
Insert cell
graph9d = addCircularPathData(straigtenVirtualNodes(graph9c))
Insert cell
drawSankey(graph9d)
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
drawSankey(graph10a)
Insert cell
Insert cell
graph10b = addCircularPathData(sortTargetLinks(graph10a))
Insert cell
drawSankey(graph10b)
Insert cell
Insert cell
graph10c = addCircularPathData(resolveNodeLinkOverlaps(graph10b))
Insert cell
drawSankey(graph10c)
Insert cell
Insert cell
Insert cell
Insert cell
drawSankey(graph11)
Insert cell
Insert cell
Insert cell
graph12 = addVirtualPathData(graph11)
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
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
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
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
drawSankey = function(graph) {
let svg = d3
.create("svg")
.attr("width", svgWidth + chartPadding + chartPadding)
.attr("height", svgHeight + chartPadding + chartPadding);

let g = svg
.append("g")
.attr("transform", "translate(" + chartPadding + "," + chartPadding + ")");

let linkG = g
.append("g")
.attr("class", "links")
.attr("fill", "none")
.attr("stroke-opacity", 0.2)
.selectAll("path");

let nodeG = g
.append("g")
.attr("class", "nodes")
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.selectAll("g");

var node = nodeG
.data(graph.nodes)
.enter()
.append("g");

node
.append("rect")
.attr("x", function(d) {
return d.x0;
})
.attr("y", function(d) {
return d.y0;
})
.attr("height", function(d) {
return d.y1 - d.y0;
})
.attr("width", function(d) {
return d.x1 - d.x0;
})
.style("fill", function(d) {
return nodeColour(d.name);
})
.style("stroke", "grey")
.style("opacity", 0.5)
.on("mouseover", function(d) {
let thisName = d.name;

node.selectAll("rect").style("opacity", function(d) {
return highlightNodes(d, thisName);
});

svg.selectAll(".sankey-link").style("opacity", function(l) {
return l.source.name == thisName || l.target.name == thisName ? 1 : 0.3;
});

node.selectAll("text").style("opacity", function(d) {
return highlightNodes(d, thisName);
});
})
.on("mouseout", function(d) {
d3.selectAll("rect").style("opacity", 0.5);
d3.selectAll(".sankey-link").style("opacity", 0.7);
d3.selectAll("text").style("opacity", 1);
});

node
.append("text")
.attr("x", function(d) {
return (d.x0 + d.x1) / 2;
})
.attr("y", function(d) {
let y = d.y0 - 8;
//y = y < graph.y0 ? d.y1 + 12 : y;
return y;
})
.attr("dy", "0.35em")
.attr("text-anchor", "middle")
.text(function(d) {
return d.name;
});

node.append("title").text(function(d) {
return d.name + "\n" + d.value;
});

var link = linkG
.data(graph.links)
.enter()
.append("g");

link
.filter(d => d.path)
.append("path")
.attr("class", "sankey-link")
.attr("d", function(link) {
return link.path;
})
.style("stroke-width", function(d) {
return Math.max(1, d.width);
})
.style("mix-blend-mode", "multiply")
.style("opacity", 0.7)
.style("stroke", function(link, i) {
if (link.circular) {
return "red";
} else if (link.type == "virtual") {
return "yellow";
} else if (link.type == "replaced") {
return "blue";
} else {
return nodeColour(link.source.name);
}
//return link.circular ? "red" : "black";
});

link.append("title").text(function(d) {
return d.source.name + " → " + d.target.name + "\n Index: " + d.index;
});

if (drawArrows) {
let totalDashArrayLength = arrowLength + gapLength;

var arrowsG = linkG
.data(graph.links)
.enter()
.append("g")
.attr("class", "g-arrow");

let arrows = arrowsG
.append('path')
.attr('d', arrowPath)
.style('stroke-width', 1)
.style('stroke', arrowColour)
.style('stroke-dasharray', arrowLength + ',' + gapLength);

arrows.each(function(arrow) {
let thisPath = d3.select(this).node();
let parentG = d3.select(this.parentNode);
let pathLength = thisPath.getTotalLength();
let numberOfArrows = Math.ceil(pathLength / totalDashArrayLength);

// remove the last arrow head if it will overlap the target node
if (
(numberOfArrows - 1) * totalDashArrayLength +
(arrowLength + (arrowHeadSize + 1)) >
pathLength
) {
numberOfArrows = numberOfArrows - 1;
}

let arrowHeadData = d3.range(numberOfArrows).map(function(d, i) {
let length = i * totalDashArrayLength + arrowLength;

let point = thisPath.getPointAtLength(length);
let previousPoint = thisPath.getPointAtLength(length - 2);

let rotation = 0;

if (point.y == previousPoint.y) {
rotation = point.x < previousPoint.x ? 180 : 0;
} else if (point.x == previousPoint.x) {
rotation = point.y < previousPoint.y ? -90 : 90;
} else {
let adj = Math.abs(point.x - previousPoint.x);
let opp = Math.abs(point.y - previousPoint.y);
let angle = Math.atan(opp / adj) * (180 / Math.PI);
if (point.x < previousPoint.x) {
angle = angle + (90 - angle) * 2;
}
if (point.y < previousPoint.y) {
rotation = -angle;
} else {
rotation = angle;
}
}

return { x: point.x, y: point.y, rotation: rotation };
});

let arrowHeads = parentG
.selectAll('.arrow-heads')
.data(arrowHeadData)
.enter()
.append('path')
.attr('d', function(d) {
return (
'M' +
d.x +
',' +
(d.y - arrowHeadSize / 2) +
' ' +
'L' +
(d.x + arrowHeadSize) +
',' +
d.y +
' ' +
'L' +
d.x +
',' +
(d.y + arrowHeadSize / 2)
);
})
.attr('class', 'arrow-head')
.attr('transform', function(d) {
return 'rotate(' + d.rotation + ',' + d.x + ',' + d.y + ')';
})
.style('fill', arrowColour);
});
}

function highlightNodes(node, name) {
let opacity = 0.3;

if (node.name == name) {
opacity = 1;
}
node.sourceLinks.forEach(function(link) {
if (link.target.name == name) {
opacity = 1;
}
});
node.targetLinks.forEach(function(link) {
if (link.source.name == name) {
opacity = 1;
}
});

return opacity;
}

return svg.node();
}
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
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
Insert cell
data_wapo = {
const links = await FileAttachment("wapo.csv").csv({ typed: true });
const nodes = Array.from(
new Set(links.flatMap(l => [l.source, l.target])),
name => ({ name, category: name.replace(/ .*/, "") })
);
return { nodes, links, units: "$bn" };
}
Insert cell
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