Built with blockbuilder.org
forked from iravitejakumar's block: JSON Model for Arrow Directed Graph
license: mit |
Built with blockbuilder.org
forked from iravitejakumar's block: JSON Model for Arrow Directed Graph
<!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> |