Skip to content

Instantly share code, notes, and snippets.

@wasiqm
Last active July 12, 2018 17:19
Show Gist options
  • Save wasiqm/20ea167b5def5706b6b8acf4c984d572 to your computer and use it in GitHub Desktop.
Save wasiqm/20ea167b5def5706b6b8acf4c984d572 to your computer and use it in GitHub Desktop.
Zoomable directed graph with text transitions
{
"nodes": [
{
"name": "Wasiq",
"label": "Customer A",
"id": 1,
"group": 1,
"country": "CA",
"risk": "low"
},
{
"name": "Goldman Sachs",
"label": "Bank A",
"id": 2,
"group": 2,
"country": "USA",
"risk": "low"
},
{
"label": "Ordering Bank",
"id": 3,
"group": 2,
"country": "UAE",
"risk": "medium"
},
{
"name": "HSBC",
"label": "Acct w/ Bank",
"id": 6,
"group": 2,
"country": "UK",
"risk": "low"
},
{
"name": "Syed",
"label": "Customer B",
"id": 7,
"group": 1,
"country": "CA",
"risk": "low"
}
],
"links": [
{
"source": 1,
"target": 2,
"value": "$100"
},
{
"source": 2,
"target": 3
},
{
"source": 3,
"target": 6
},
{
"source": 6,
"target": 7
}
]
}
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.link {
stroke: #999;
stroke-opacity: 0.55;
stroke-width: 5px;
}
.node {
cursor: pointer;
}
</style>
<body>
<script src="https://d3js.org/d3.v4.min.js" type="text/javascript"></script>
<script src="https://d3js.org/d3-selection-multi.v1.js"></script>
<script>
var width = 960,
height = 600,
r = 50,
active = d3.select(null);
var zoom = d3.zoom()
.scaleExtent([1, 8])
.on("zoom", zoomed);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.on("click", stopped, true);
var margin = 20,
diameter = +svg.attr("width")
var colours = d3.scaleLinear()
.domain([-1, 5])
.range(["hsl(152,80%,80%)", "hsl(228,30%,40%)"])
.interpolate(d3.interpolateHcl);
svg.append('defs').append('marker')
.attrs({'id':'arrowhead',
'viewBox':'-0 -5 10 10',
'refX':34,
'refY':0,
'orient':'auto',
'markerWidth':4,
'markerHeight':4,
'xoverflow':'visible'})
.append('svg:path')
.attr('d', 'M 0,-5 L 10 ,0 L 0,5')
.attr('fill', '#999')
.style('stroke','none');
var g = svg.append("g");
d3.json("data.json", function (error, graph) {
if (error) throw error;
links = graph.links;
nodes = graph.nodes;
g.selectAll(".link")
.data(links)
.enter()
.append("line")
.attrs({
'class': 'link',
'marker-end':'url(#arrowhead)',
'x1': function (d, i) {return ((1 + i) * svg.attr("width") / nodes.length) - 55},
'y1': svg.attr("height") / 2,
'x2': function (d, i) {return ((2 + i) * svg.attr("width") / nodes.length) - 55},
'y2': svg.attr("height") / 2
})
.append("title")
.text(function (d) {return d.type;});
g.selectAll(".edgepath")
.data(links)
.enter()
.append('path')
.attrs({
'class': 'edgepath',
'fill-opacity': 50,
'stroke-opacity': 50,
'id': function (d, i) {return 'edgepath' + i}
})
.style("pointer-events", "none");
g.selectAll(".edgelabel")
.data(links)
.enter()
.append('text')
.style("pointer-events", "none")
.attrs({
'class': 'edgelabel',
'id': function (d, i) {return 'edgelabel' + i},
'font-size': 10,
'font-family': 'arial',
'fill': '#aaa',
'dy': -4
})
.append('textPath')
.attr('xlink:href', function (d, i) {return '#edgepath' + i})
.style("text-anchor", "middle")
.style("pointer-events", "none")
.attr("startOffset", "50%")
.text(function (d) {return d.value});
node = g.selectAll(".node")
.data(nodes)
.enter()
.append("g")
.attr("class", "node")
.attr("transform", function(d, i) {
d.x = ((1 + i) * svg.attr("width") / nodes.length) - 55,
d.y = svg.attr("height") / 2,
d.r = r;
return "translate(" + d.x + "," + d.y + ")";
})
.on("click", clicked);
node.append("circle")
.attr("r", r)
.attr("class", "node")
.style("fill", function(d) { return colours(d.group); })
.style("stroke", function(d) { return d3.rgb(colours(d.group)).darker(); })
node.append("text")
.attrs({
'id' : 'label',
'font-size': 10,
'font-family': 'arial',
'font-weight': 'bold',
'text-anchor': 'middle',
'fill': '#000000',
'dy': '-0.5em'
})
.text(function(d) { return d.label; });
node.append("text")
.attrs({
'id' : 'name',
'font-size': 10,
'font-family': 'arial',
'font-weight': 'bold',
'text-anchor': 'middle',
'fill': '#000000',
'dy': '0.6em'
})
.text(function(d) { return d.name; });
});
function clicked(d) {
if (active.node() === this) return reset();
active.classed("active", false);
active = d3.select(this);
var bounds = [[d.x - r, d.y - r], [d.x + r, d.y + r]],
dx = bounds[1][0] - bounds[0][0],
dy = bounds[1][1] - bounds[0][1],
x = (bounds[0][0] + bounds[1][0]) / 2,
y = (bounds[0][1] + bounds[1][1]) / 2,
scale = Math.max(1, Math.min(8, 0.9 / Math.max(dx / width, dy / height))),
translate = [width / 2 - scale * x, height / 2 - scale * y];
d3.select(active["_groups"][0][0]["children"]["label"])
.transition()
.attr("transform", "translate(0, -19)")
.duration(750)
//.attr("font-size", 8);
d3.select(active["_groups"][0][0]["children"]["name"])
.transition()
.attr("transform", "translate(0, -19)")
.duration(750)
//.attr("font-size", 8);
active.append("text")
.attrs({
'id' : 'country',
'font-size': 8,
'font-family': 'arial',
'font-weight': 'bold',
'text-anchor': 'middle',
'fill': '#4d4e51',
'dy': '1em',
'opacity': 0
})
.text(function(d) { return "country: " + d.country; })
.transition()
.delay(75)
.duration(500)
.attr("opacity", 1);
active.append("text")
.attrs({
'id' : 'risk',
'font-size': 8,
'font-family': 'arial',
'font-weight': 'bold',
'text-anchor': 'middle',
'fill': '#4d4e51',
'dy': '2em',
'opacity': 0
})
.text(function(d) { return "risk: " + d.risk; })
.transition()
.delay(75)
.duration(500)
.attr("opacity", 1);
svg.transition()
.duration(750)
// .style("stroke-width", 1.5 / scale + "px")
.call( zoom.transform, d3.zoomIdentity.translate(translate[0],translate[1]).scale(scale) );
}
function reset() {
active.classed("active", false);
d3.select('#country')
.transition()
.delay(75)
.duration(500)
.attr("opacity", 0)
.remove();
d3.select('#risk')
.transition()
.delay(75)
.duration(500)
.attr("opacity", 0)
.remove();
d3.select(active["_groups"][0][0]["children"]["label"])
//.attr("font-size", 10)
.transition()
.attr("transform", "translate(0, 0)")
.duration(750);
d3.select(active["_groups"][0][0]["children"]["name"])
//.attr("font-size", 10)
.transition()
.attr("transform", "translate(0, 0)")
.duration(750);
active = d3.select(null);
svg.transition()
.duration(750)
// .call( zoom.transform, d3.zoomIdentity.translate(0, 0).scale(1) ); // not in d3 v4
.call( zoom.transform, d3.zoomIdentity ); // updated for d3 v4
}
function zoomed() {
g.style("stroke-width", 1.5 / d3.event.transform.k + "px");
// g.attr("transform", "translate(" + d3.event.translate + ")scale(" + d3.event.scale + ")"); // not in d3 v4
g.attr("transform", d3.event.transform); // updated for d3 v4
}
// If the drag behavior prevents the default click,
// also stop propagation so we don’t click-to-zoom.
function stopped() {
if (d3.event.defaultPrevented) d3.event.stopPropagation();
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment