|
|
|
<!DOCTYPE html> |
|
|
|
<html lang="en"> |
|
<head> |
|
<meta charset="utf-8" /> |
|
<title>Sankey Particles</title> |
|
<style> |
|
.flow_node rect { |
|
//cursor: move; |
|
fill-opacity: .9; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.flow_node text { |
|
pointer-events: none; |
|
} |
|
|
|
.flow_link { |
|
fill: none; |
|
//stroke: #000; |
|
stroke: white; |
|
stroke-opacity: .15; |
|
} |
|
|
|
.flow_link:hover { |
|
stroke-opacity: .25; |
|
} |
|
|
|
svg { |
|
position: absolute; |
|
} |
|
|
|
canvas { |
|
position: absolute; |
|
} |
|
body{ |
|
background: black; |
|
} |
|
|
|
</style> |
|
</head> |
|
<body> |
|
<canvas width="1000" height="1000" ></canvas> |
|
<svg width="1000" height="1000" ></svg> |
|
|
|
<!-- Referencing v4 works --> |
|
<script src="https://d3js.org/d3.v5.min.js"></script> |
|
|
|
<script src="https://d3js.org/d3-color.v1.min.js"></script> |
|
<script src="https://d3js.org/d3-interpolate.v1.min.js"></script> |
|
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script> |
|
<script src="https://d3js.org/d3-timer.v1.min.js"></script> |
|
<script src="https://d3js.org/d3-hierarchy.v1.min.js"></script> |
|
|
|
<script src="d3.sankey.js" charset="utf-8" type="text/javascript"></script> |
|
|
|
<script type="text/javascript"> |
|
|
|
var flowData = |
|
{ |
|
"nodes": [ |
|
{ "name": "F5SDPROD", "segment": 0, "active": 1 }, |
|
{ "name": "F5SDPROD - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5SDPROD - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5SDPROD VIP Health", "segment": 0, "health": 93}, |
|
{ "name": "F5WANPROD", "segment": 0, "active": 1 }, |
|
{ "name": "F5WANPROD - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5WANPROD - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5WANPROD VIP Health", "segment": 0, "health": 100}, |
|
{ "name": "F5DRSDPROD", "segment": 0, "active": 1 }, |
|
{ "name": "F5DRSDPROD - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5DRSDPROD - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5DRSDPROD VIP Health", "segment": 0, "health": 96}, |
|
{ "name": "F5DRWANPROD", "segment": 0, "active": 1 }, |
|
{ "name": "F5DRWANPROD - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5DRWANPROD - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5DRWANPROD VIP Health", "segment": 0, "health": 97}, |
|
{ "name": "F5SDTBF", "segment": 0, "active": 1 }, |
|
{ "name": "F5SDTBF - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5SDTBF - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5SDTBF VIP Health", "segment": 0, "health": 92}, |
|
{ "name": "F5WANTBF", "segment": 0, "active": 1 }, |
|
{ "name": "F5WANTBF - HA1", "segment": 0, "active": 1}, |
|
{ "name": "F5WANTBF - HA2", "segment": 0, "active": 1}, |
|
{ "name": "F5WANTBF VIP Health", "segment": 0, "health": 90} |
|
], |
|
"links": [ |
|
{ "source": "F5SDPROD", "target": "F5SDPROD - HA1", "value": 10, "active": 1 }, |
|
{ "source": "F5SDPROD", "target": "F5SDPROD - HA2", "value": 10, "active": 0 }, |
|
{ "source": "F5SDPROD - HA1", "target": "F5SDPROD VIP Health", "value": 10, "active": 1 }, |
|
{ "source": "F5SDPROD - HA2", "target": "F5SDPROD VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5WANPROD", "target": "F5WANPROD - HA1", "value": 10, "active": 1 }, |
|
{ "source": "F5WANPROD", "target": "F5WANPROD - HA2", "value": 10, "active": 0 }, |
|
{ "source": "F5WANPROD - HA1", "target": "F5WANPROD VIP Health", "value": 10, "active": 1 }, |
|
{ "source": "F5WANPROD - HA2", "target": "F5WANPROD VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5DRSDPROD", "target": "F5DRSDPROD - HA1", "value": 10, "active": 1 }, |
|
{ "source": "F5DRSDPROD", "target": "F5DRSDPROD - HA2", "value": 10, "active": 0 }, |
|
{ "source": "F5DRSDPROD - HA1", "target": "F5DRSDPROD VIP Health", "value": 10, "active": 1 }, |
|
{ "source": "F5DRSDPROD - HA2", "target": "F5DRSDPROD VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5DRWANPROD", "target": "F5DRWANPROD - HA1", "value": 10, "active": 1 }, |
|
{ "source": "F5DRWANPROD", "target": "F5DRWANPROD - HA2", "value": 10, "active": 0 }, |
|
{ "source": "F5DRWANPROD - HA1", "target": "F5DRWANPROD VIP Health", "value": 10, "active": 1 }, |
|
{ "source": "F5DRWANPROD - HA2", "target": "F5DRWANPROD VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5SDTBF", "target": "F5SDTBF - HA1", "value": 10, "active": 1 }, |
|
{ "source": "F5SDTBF", "target": "F5SDTBF - HA2", "value": 10, "active": 0 }, |
|
{ "source": "F5SDTBF - HA1", "target": "F5SDTBF VIP Health", "value": 10, "active": 1 }, |
|
{ "source": "F5SDTBF - HA2", "target": "F5SDTBF VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5WANTBF", "target": "F5WANTBF - HA1", "value": 10, "active": 0 }, |
|
{ "source": "F5WANTBF", "target": "F5WANTBF - HA2", "value": 10, "active": 1 }, |
|
{ "source": "F5WANTBF - HA1", "target": "F5WANTBF VIP Health", "value": 10, "active": 0 }, |
|
{ "source": "F5WANTBF - HA2", "target": "F5WANTBF VIP Health", "value": 10, "active": 1 }, |
|
] |
|
} |
|
|
|
|
|
|
|
var margin = {top: 1, right: 250, bottom: 6, left: 1}, |
|
width = 800 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var formatNumber = d3.format(",.0f"), |
|
format = function(d) { return formatNumber(d) + " TWh"; }, |
|
|
|
color = d3.scaleOrdinal(d3.schemeCategory10); |
|
|
|
var svg = d3.select("svg") |
|
.attr("width", width + margin.left + margin.right) |
|
.attr("height", height + margin.top + margin.bottom) |
|
.append("g") |
|
.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); |
|
|
|
var sankey = d3.sankey() |
|
.nodeWidth(15) |
|
.nodePadding(10) |
|
.size([width, height]); |
|
|
|
var path = sankey.link(); |
|
|
|
var freqCounter = 1; |
|
|
|
|
|
// load the data |
|
d3.hierarchy(flowData, function (graph) { |
|
var nodeMap = {}; |
|
graph.nodes.forEach(function (x) { nodeMap[x.name] = x; }); |
|
graph.links = graph.links.map(function (x) { |
|
return { |
|
source: nodeMap[x.source], |
|
target: nodeMap[x.target], |
|
value: x.value, |
|
active: x.active |
|
}; |
|
}); |
|
|
|
sankey |
|
.nodes(graph.nodes) |
|
.links(graph.links) |
|
.layout(32); |
|
|
|
// add in the links |
|
var link = svg.append("g").selectAll(".flow_link") |
|
.data(graph.links) |
|
.enter().append("path") |
|
.attr("class", "flow_link") |
|
|
|
.style("stroke", function (d) { |
|
if (d.active == 0 && d.source.active ==1) { |
|
return "#17a2b8"; |
|
} |
|
}) |
|
|
|
|
|
|
|
|
|
.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; }); |
|
|
|
var node = svg.append("g").selectAll(".flow_node") |
|
.data(graph.nodes) |
|
.enter().append("g") |
|
.attr("class", "flow_node") |
|
.attr("transform", function (d) { return "translate(" + d.x + "," + d.y + ")"; }); |
|
|
|
node.append("rect") |
|
.attr("height", function (d) { return d.dy; }) |
|
.attr("width", sankey.nodeWidth()) |
|
.style("fill", function (d) { |
|
if (d.segment == 0) { |
|
return d.color = color(d.name.replace(/ .*/, "")); |
|
} |
|
if (d.segment == 1) { |
|
return "#8856a7"; |
|
} |
|
}) |
|
.style("stroke", "none") |
|
.append("title") |
|
.text(function (d) { return d.name; }); |
|
|
|
node.append("text") |
|
.attr("x", -6) |
|
.attr("y", function (d) { return d.dy / 2; }) |
|
.attr("dy", ".35em") |
|
.attr("transform", null) |
|
//.text(function (d) { return d.name + " " + d.health + "%"; }) |
|
.text(function (d) { |
|
if (!d.health){ |
|
return d.name |
|
} |
|
else{ |
|
return d.name + " " + d.health + "%"; |
|
} |
|
|
|
}) |
|
.style("fill",function (d) { |
|
if (!d.health){ return "fff"} |
|
else{ |
|
if(d.health < 95){ |
|
return "#dc3545"; |
|
} |
|
if(d.health < 100){ |
|
return "#ffc107"; |
|
} |
|
|
|
return "#28a745"; |
|
} |
|
|
|
}) |
|
|
|
.attr("x", 6 + sankey.nodeWidth()) |
|
.attr("text-anchor", "start") |
|
.style("font-size", "1.0em"); |
|
|
|
|
|
function dragmove(d) { |
|
d3.select(this).attr("transform", "translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")"); |
|
sankey.relayout(); |
|
link.attr("d", path); |
|
} |
|
|
|
var linkExtent = d3.extent(graph.links, function (d) { return 1 }); |
|
var frequencyScale = d3.scaleLinear().domain(linkExtent).range([.01, .01]); |
|
var particleSize = d3.scaleLinear().domain(linkExtent).range([1, 5]); |
|
|
|
|
|
graph.links.forEach(function (link) { |
|
link.freq = frequencyScale(link.o_value); |
|
|
|
if(link.active==0){ |
|
link.particleSize = 0; |
|
} |
|
else{ |
|
link.particleSize = 3; |
|
} |
|
link.particleColor = d3.scaleLinear().domain([0, 10]) |
|
.range(["#f000", link.target.color]); |
|
}) |
|
|
|
var t = d3.timer(tick, 10); |
|
var particles = []; |
|
|
|
function tick(elapsed, time) { |
|
|
|
particles = particles.filter(function (d) { return d.current < d.path.getTotalLength() }); |
|
|
|
d3.selectAll("path.flow_link") |
|
.each( |
|
function (d) { |
|
for (var x = 0; x < 2; x++) { |
|
var offset = (Math.random() - .5) * (d.dy - 4); |
|
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); |
|
} |
|
|
|
function particleEdgeCanvasPath(elapsed) { |
|
var context = d3.select("canvas").node().getContext("2d") |
|
context.clearRect(0, 0, 1000, 1000); |
|
|
|
context.fillStyle = "gray"; |
|
context.lineWidth = "1px"; |
|
for (var x in particles) { |
|
var currentTime = elapsed - particles[x].time; |
|
particles[x].current = currentTime * 0.1 * 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(); |
|
} |
|
} |
|
|
|
}); |
|
|
|
</script> |
|
</body> |
|
</html> |