Public
Edited
Nov 1, 2024
Insert cell
md`# Elijah Meeks - Particle Sankey
Ported from: https://bl.ocks.org/emeeks/e9d64d27f286e61493c9`

Insert cell
{yield html `
<div id = 'container'>
<svg id = "svgCont" width="1000" height="200"></svg>
<canvas id = "canvasCont" width="1000" height="200"></canvas>
</div>
`
drawChart()}
Insert cell
drawChart = function(){
var svg = d3.select("svg")
.attr("width", width)
.attr("height", height);
var sankey = d3.sankey()
.nodeWidth(15)
.nodePadding(10)
.size([width, height]);
var formatNumber = d3.format(",.0f"),
format = function(d) { return formatNumber(d) + " TWh"; }
var path = sankey.link();
var freqCounter = 1;

energy.links.forEach(function(d){
d.o_value = d.value;
d.value = 1;
})
energy.nodes.forEach(function(d,i){
d.color = colors[i%colors.length]
})
sankey
.nodes(energy.nodes)
.links(energy.links)
.layout(32);
var link = svg.append("g").selectAll(".link")
.data(energy.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke-width", function(d){
return Math.max(1, d.dy);
})
.sort(function(a,b) {return b.dy - a.dy});
link.append("title")
.text(function(d) {return d.source.name + " -> " + d.target.name + "\n" + format(d.o_value);});

var node = svg.append("g").selectAll(".node")
.data(energy.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d){ return "translate(" + d.x + ", " + d.y + ")";})
.call(d3.drag()
.on("drag", function (event,d){
d.x = event.x
d.y = event.y
d3.select(this).attr("transform", d => `translate(${d.x},${d.y})`);
sankey.relayout();
link.attr("d",path);}
));
node.append("rect")
.attr("height", function(d) {return d.dy;})
.attr("width", sankey.nodeWidth())
.style("fill", function(d,i) {return d.color})
.style("rx", 5)
.style("stroke", "none");
var linkExtent = d3.extent(energy.links, function(d) {return d.o_value});

var frequencyScale = d3.scaleLinear().domain(linkExtent).range([0.05, 1]);
var particleSize = d3.scaleLinear().domain(linkExtent).range([1,5]);
energy.links.forEach(function(link) {
link.freq = frequencyScale(link.o_value);
link.particleSize = 2;
link.particleColor = d3.scaleLinear().domain([0,1])
.range([link.source.color, link.target.color])
});
var particles = [];
function tick(elapsed, time) {
//Remove complete particles
particles = particles.filter(function(d) {return d.current < d.path.getTotalLength()});
function particleEdgeCanvasPath(elapsed) {
var context = d3.select("canvas").node().getContext("2d")
context.clearRect(0,0,width,height);
context.fillStyle = "gray";
context.lineWidth = "1px";
for (var x in particles) {
//Particle Progress: difference between current time, and time of creation
var currentTime = elapsed - particles[x].time;
//Progress Adjusted for speed?
particles[x].current = currentTime * 0.15 * particles[x].speed
var currentPos = particles[x].path.getPointAtLength(particles[x].current);
context.beginPath();
context.fillStyle = particles[x].link.particleColor(0);
context.arc(currentPos.x, currentPos.y + particles[x].offset, particles[x].link.particleSize, 0, 2*Math.PI);
context.fill();
}
};
//Generate particle data
d3.selectAll("path.link")
.each(function(d) {
for (var x=0; x<2; x++) {
//Y jitter
var offset = (Math.random() - .5) * (d.dy - 4);
//Generate particles based on frequency distribution
if (Math.random() < d.freq) {
var length = this.getTotalLength();
particles.push({link: d, time: elapsed, offset: offset, path: this, length: length, animateTime: length, speed: 0.5 + (Math.random())})
}
}
})
particleEdgeCanvasPath(elapsed)
};
var t = d3.timer(tick, 1000);

}
Insert cell
energy.links
Insert cell
margin = ({top: 1, right: 1, bottom: 6, left: 1})
Insert cell
width = 960 - margin.left - margin.right
Insert cell
height = 500 - margin.top - margin.bottom
Insert cell
colors = [
"#01ffff",
"#f6a7ab",
"#0b2934",
"#fcba74",
"#49d811",
"#ff0000",
"#cea9cf",
"#0137d0",
"#008fe1",
"#330120",
"#00dce6",
"#7e0050",
"#f98b10",
"#9c1bad",
"#ae1daf"
]
Insert cell
energy = ({"nodes":[
{"name":"Outdoor Air"},
{"name":"HVAC Damper"},

],
"links":[
{"source":0,"target":1,"value":100},
]})

Insert cell
html`
<style>

#container {
position: relative;
}

#svgCont {
position: absolute;
}




.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}

.node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}

.link {
fill: none;
stroke: #000;
stroke-opacity: .15;
}

.link:hover {
stroke-opacity: .25;
}

</style>
`
Insert cell
d3 = require("d3@6", "d3-sankey@0.1")
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