Skip to content

Instantly share code, notes, and snippets.

@nagarajhubli
Created February 20, 2020 11:39
Show Gist options
  • Save nagarajhubli/d1e7a7df182516243261fcb0b0e896ba to your computer and use it in GitHub Desktop.
Save nagarajhubli/d1e7a7df182516243261fcb0b0e896ba to your computer and use it in GitHub Desktop.
JSON Model for Arrow Directed Graph
license: mit
<!DOCTYPE html>
<html>
<head>
<script src="https://d3js.org/d3.v3.min.js"></script>
<meta charset="utf-8">
<title>JS Bin</title>
<style>
line.node-link, path.node-link {
fill: none;
}
circle.node {
// fill: #336699;
// stroke: black
}
circle.node+text {
text-anchor: middle;
}
text {
font-family: sans-serif;
pointer-events: none;
}
</style>
</head>
<body>
<script type="text/javascript">
var num = 9;
function getRandomInt() {return Math.floor(Math.random() * (num));}
// var nodes = d3.range(num).map(function(d) { return {id: d}; });
// console.log(nodes);
var data = {
nodes: [
{id: 1,name: "TOTAL STORE ALCOHOL", inValue: 28201.70, color: "#ec545a" },
{id: 2,name: "TOTAL STORE BAKERY", inValue: 8510, color: "#ec545a"},
{id: 3,name: "TOTAL STORE BEAUTY CARE", inValue: 22045.60, color: "#ec545a"},
{id: 4,name: "TOTAL STORE DAIRY", inValue: 69357.60, color: "#5ccab9"},
{id: 5,name: "TOTAL STORE DELI", inValue: 33433.60, color: "#ec545a"},
{id: 6,name: "TOTAL STORE FROZEN FOODS", inValue: 66943.40, color: "#ec545a"},
{id: 7,name: "TOTAL STORE MERCHANDISE", inValue: 0, color: "#ec545a"},
{id: 8,name: "All other", inValue: 688693.60, color: "#5ccab9"},
],
links: [
{ source: 1, target: 2, outValue: 110 },
{ source: 1, target: 4, outValue: 992.9 },
{ source: 1, target: 5, outValue: 661 },
{ source: 1, target: 6, outValue: 2269.60 },
{ source: 1, target: 8, outValue: 120.80 },
{ source: 2, target: 4, outValue: 1920.20 },
{ source: 2, target: 5, outValue: 814.70 },
{ source: 2, target: 6, outValue: 1936.60 },
{ source: 2, target: 8, outValue: 2778 },
{ source: 3, target: 1, outValue: 319.40 },
{ source: 3, target: 2, outValue: 71.30 },
{ source: 3, target: 4, outValue: 877.70 },
{ source: 3, target: 5, outValue: 804.70 },
{ source: 3, target: 6, outValue: 1647.10 },
{ source: 3, target: 8, outValue: 1643.50 },
{ source: 4, target: 5, outValue: 162.40 },
{ source: 4, target: 6, outValue: 1749.60 },
{ source: 4, target: 8, outValue: 22.50 },
{ source: 5, target: 6, outValue: 237.8 },
{ source: 7, target: 1, outValue: 2825.20 },
{ source: 7, target: 2, outValue: 1700.60 },
{ source: 7, target: 3, outValue: 2601.90 },
{ source: 7, target: 4, outValue: 7555.20 },
{ source: 7, target: 5, outValue: 3483.60 },
{ source: 7, target: 6, outValue: 10357.40 },
{ source: 7, target: 8, outValue: 36365 },
{ source: 8, target: 5, outValue: 3954.40 },
{ source: 8, target: 6, outValue: 10385.70 },
]
}
var map = {}
data.nodes.forEach(function(d,i){
map[d.id] = i;
})
data.links.forEach(function(d) {
d.source = map[d.source];
d.target = map[d.target];
})
var minNodeValue = d3.min(data.nodes, function(d) {return d.inValue;});
var maxNodeValue = d3.max(data.nodes, function(d) {return d.inValue;});
var nodeScale = d3.scale.linear().domain([minNodeValue, maxNodeValue]).range([4,70]); // change min and max values of nodes
var minLinkValue = d3.min(data.links, function(d) {return d.outValue;});
var maxLinkValue = d3.max(data.links, function(d) {return d.outValue;});
var linkScale = d3.scale.linear().domain([minLinkValue, maxLinkValue]).range([1,12]); // change min and max values of links
var width = 800,
height = 800;
var force = d3.layout.force()
.nodes(data.nodes)
.links(data.links)
.size([width-300, height-300]);
// evenly spaces nodes along arc
var circleCoord = function(node, index, num_nodes){
var circumference = circle.node().getTotalLength();
console.log(circle.node(),circumference);
var pointAtLength = function(l){return circle.node().getPointAtLength(l)};
var sectionLength = (circumference)/num_nodes;
var position = sectionLength*index+sectionLength/2;
return pointAtLength(circumference-position)
}
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", `translate(50, 50)`)
// fades out lines that aren't connected to node d
var is_connected = function(d, opacity) {
lines.transition().style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
markers.transition().style("fill-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
}
var dim = width - 200
var circle = svg.append("path")
.attr("d", "M 40, "+(dim/2+40)+" a "+dim/2+","+dim/2+" 0 1,0 "+dim+",0 a "+dim/2+","+dim/2+" 0 1,0 "+dim*-1+",0")
.attr("id", "")
.style("fill", "none")
// .style('stroke', '#000000')
force.start();
data.nodes.forEach(function(n, i) {
var coord = circleCoord(n, i, data.nodes.length)
n.x = coord.x
n.y = coord.y
});
var markers = svg.append("svg:defs").selectAll("marker")
.data(data.links)
.enter().append("marker")
.attr("id", function(d, i) {return "arrowhead"+i;})
.attr("viewBox", "-5 -5 10 10")
.attr("refX", 0.8 )
.attr("refY", 0)
.attr("markerWidth", 53)
.attr("markerHeight", 21)
.attr("markerUnits","userSpaceOnUse")
.attr("orient", "auto")
.append("svg:path")
.attr("d", "M0,-5L10,0L0,5")
.attr("fill", "#ccc");
function calculateX(tx, ty, sx, sy, radius){
if(tx == sx) return tx; //if the target x == source x, no need to change the target x.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(tx > sx) return tx - xLength * ratio; //if target x > source x return target x - radius
if(tx < sx) return tx + xLength * ratio; //if target x < source x return target x + radius
}
function calculateY(tx, ty, sx, sy, radius){
if(ty == sy) return ty; //if the target y == source y, no need to change the target y.
var xLength = Math.abs(tx - sx); //calculate the difference of x
var yLength = Math.abs(ty - sy); //calculate the difference of y
//calculate the ratio using the trigonometric function
var ratio = radius / Math.sqrt(xLength * xLength + yLength * yLength);
if(ty > sy) return ty - yLength * ratio; //if target y > source y return target x - radius
if(ty < sy) return ty + yLength * ratio; //if target y > source y return target x - radius
}
var lines = svg.selectAll("line.node-link")
.data(data.links).enter().append("line")
.attr("class", "node-link")
.attr("x1", function(d) { console.log(nodesPadding);return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function (d) {
return calculateX(d.target.x, d.target.y, d.source.x, d.source.y, nodeScale(d.target.inValue)+30);
})
.attr("y2", function (d) {
return calculateY(d.target.x, d.target.y, d.source.x, d.source.y, nodeScale(d.target.inValue) + 30);
})
.attr("marker-end", function(d,i) { return "url(#" + 'arrowhead' + i+ ")"; })
.attr("stroke-width", function(d) { return linkScale(d.outValue)})
.attr("stroke", "#ccc")
.attr("fill", "#ccc")
var gnodes = svg.selectAll('g.gnode')
.data(data.nodes).enter().append('g')
.attr("transform", function(d) {
return "translate("+d.x+","+d.y+")"
})
.classed('gnode', true);
var nodesPadding = gnodes.append("circle")
.attr("r", function(d) {return nodeScale(d.inValue) + 10})
.attr("fill", "#ffffff");
var node = gnodes.append("circle")
.attr("r", function(d) {return nodeScale(d.inValue)})
.attr('fill', function(d) {return d.color})
.attr("class", "node")
.attr("stroke", "#ffffff")
.on("mouseenter", function(d) {
is_connected(d, 0.1)
node.transition().duration(100).attr("r", function(d) {return nodeScale(d.inValue)})
d3.select(this).transition().duration(100).attr("r", function(d) {return nodeScale(d.inValue)+5;})
})
.on("mouseleave", function(d) {
is_connected(d, 1)
node.transition().duration(100).attr("r", function(d) {return nodeScale(d.inValue)});
});
var labels = gnodes.append("text")
.attr("dy", function(d) {
console.log(d);
return 4;}
)
.attr('fill', "#000000")
.text(function(d){return d.name})
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment