|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<title>Traceability</title> |
|
<link href="sankey_style.css" rel="stylesheet"> |
|
<style media="screen" type="text/css"> |
|
|
|
|
|
.sankey-graph .node rect { |
|
shape-rendering: crispEdges; |
|
pointer-events:all; |
|
fill: none; |
|
stroke: rgb(160, 160,160); |
|
stroke-width: 2px; |
|
} |
|
|
|
.sankey-graph .node text { |
|
pointer-events: none; |
|
text-shadow: 0 1px 0 #fff; |
|
} |
|
|
|
.sankey-graph .bar rect { |
|
stroke: none; |
|
} |
|
|
|
.sankey-graph .link { |
|
fill: none; |
|
stroke: #000; |
|
} |
|
|
|
|
|
|
|
|
|
</style> |
|
<body> |
|
|
|
<header> |
|
</header> |
|
|
|
|
|
<p id="chart" class="sankey-graph"> |
|
|
|
<script src="https://d3js.org/d3.v5.min.js"></script> |
|
|
|
<script src="https://unpkg.com/d3-sankey@0"></script> |
|
<script> |
|
|
|
console.clear() |
|
|
|
var margin = { top: 1, right: 0, bottom: 6, left: 0 } |
|
|
|
var width = 1000 |
|
// also change CSS file to change Width |
|
|
|
var height = 1030 - margin.top - margin.bottom |
|
|
|
var node_Width = 200 |
|
node_Stroke = 2 |
|
|
|
var svg = d3 |
|
.select('#chart') |
|
.append('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(node_Width) |
|
.nodePadding(43) |
|
.size([width, height]) |
|
.nodeId(function (d) { |
|
return d.name |
|
}) |
|
.nodeAlign(d3.sankeyJustify) |
|
|
|
d3.json('fish.json').then(function (data) { |
|
// console.log(data) |
|
graph = {} |
|
graph.nodes = [] |
|
graph.links = [] |
|
|
|
data.forEach(function (d) { |
|
graph.links.push({ source: d.source, target: d.target, value: d.value }) |
|
graph.nodes.push({ name: d.source }) |
|
graph.nodes.push({ name: d.target }) |
|
}) |
|
|
|
graph.nodes = d3 |
|
.nest() |
|
.key(function (d) { |
|
return d.name |
|
}) |
|
.entries(graph.nodes) |
|
|
|
graph.nodes.forEach(function (d, i) { |
|
graph.nodes[i] = { name: d.key } |
|
}) |
|
|
|
var drag = d3.drag().on('drag', dragmove) |
|
|
|
function dragmove (d) { |
|
|
|
d3.select(this).attr( |
|
'transform', |
|
'translate(' + |
|
d.x0 + |
|
',' + |
|
(d.y0 = Math.max(0, Math.min(height - d.height, d3.event.y))) + |
|
')' |
|
) |
|
|
|
d.y = d.y0 |
|
sankey.update(graph) |
|
link.attr('d', d3.sankeyLinkHorizontal()) |
|
} |
|
|
|
sankey(graph) |
|
|
|
var link = svg |
|
.append('g') |
|
.selectAll('.link') |
|
.data(graph.links) |
|
.enter() |
|
.append('path') |
|
.attr('class', 'link') |
|
.attr('d', d3.sankeyLinkHorizontal()) |
|
.style('opacity', 0.2) |
|
.style('stroke-width', function (d) { |
|
// d.strokeWidth = d.y1 - d.y0 |
|
return d.width |
|
}) |
|
.sort(function (a, b) { |
|
return b.dy - a.dy |
|
}) |
|
|
|
var node = svg |
|
.append('g') |
|
.selectAll('.node') |
|
.data(graph.nodes) |
|
.enter() |
|
.append('g') |
|
.attr('class', 'node') |
|
.attr('transform', function (d) { |
|
d.x = d.x0 |
|
d.y = d.y0 |
|
return 'translate(' + d.x0 + ',' + d.y0 + ')' |
|
}) |
|
.call(drag) |
|
.on('click', handleNodeClick) |
|
|
|
node |
|
.append('rect') |
|
.attr('height', function (d) { |
|
d.height = d.y1 - d.y0 |
|
return d.height |
|
}) |
|
.attr('width', sankey.nodeWidth()) |
|
.style('fill', 'white') |
|
|
|
node |
|
.append('text') |
|
.attr('y', 11) |
|
.text(function (d) { |
|
return d.name |
|
}) |
|
|
|
node.append("svg:image") |
|
.attr("x", 0) |
|
.attr("y", 0) |
|
.attr("height", 49) |
|
.attr("xlink:href", function(d){ |
|
let suffix = "https://gist.githubusercontent.com/tomshanley/60c473ad73a250d5ed467bec3073698b/raw/4003d8d581423abf4b27fd2ae8425e6ee2a23e44/" |
|
let filename = suffix + d.name + ".png" |
|
return filename |
|
}) |
|
|
|
|
|
// Recursive highlighting of nodes |
|
|
|
function handleNodeClick (d) { |
|
if (d3.event.defaultPrevented) return |
|
|
|
// Reset colour of nodes to black |
|
d3.selectAll('rect').style('fill', 'white') |
|
|
|
d3.selectAll('.link') |
|
.style('stroke', 'black') |
|
.style('opacity', 0.2) |
|
|
|
// Highlight clicked node |
|
d3.selectAll('rect').style('fill', function (d2, i) { |
|
return d.name == d2.name ? 'LightCoral' : 'white' |
|
}) |
|
|
|
iterateLinkedLinksRight(d) // Recurse source direction |
|
iterateLinkedLinksLeft(d) // Recurse target direction |
|
} |
|
|
|
function iterateLinkedLinksRight (pStartNode) { |
|
// Select links that have a given source name |
|
d3.selectAll('path.link') |
|
.filter(function (pLinkedLink, i) { |
|
return pLinkedLink.source.name == pStartNode.name |
|
}) |
|
.style('stroke', 'LightCoral') |
|
.style('opacity', 0.6) |
|
.each(iterateLinkedNodesRight) |
|
} |
|
|
|
function iterateLinkedNodesRight (pStartLink) { |
|
// Select nodes that have a given source name |
|
d3.selectAll('rect') |
|
.filter(function (pLinkedNode, i) { |
|
return pLinkedNode.name == pStartLink.target.name |
|
}) |
|
.style('fill', 'LightCoral') |
|
.each(iterateLinkedLinksRight) |
|
} |
|
|
|
function iterateLinkedLinksLeft (pStartNode) { |
|
// Select links that have a given source name |
|
d3.selectAll('path.link') |
|
.filter(function (pLinkedLink, i) { |
|
return pLinkedLink.target.name == pStartNode.name |
|
}) |
|
.style('stroke', 'LightCoral') |
|
.style('opacity', 0.6) |
|
.each(iterateLinkedNodesLeft) |
|
} |
|
|
|
function iterateLinkedNodesLeft (pStartLink) { |
|
// Select nodes that have a given source name |
|
d3.selectAll('rect') |
|
.filter(function (pLinkedNode, i) { |
|
return pLinkedNode.name == pStartLink.source.name |
|
}) |
|
.style('fill', 'LightCoral') |
|
.each(iterateLinkedLinksLeft) |
|
} |
|
}) |
|
|
|
|
|
</script> |