Skip to content

Instantly share code, notes, and snippets.

@fzyukio
Created July 28, 2018 09:44
Show Gist options
  • Save fzyukio/c1bcda60e0cf88c07c26cea3c2089d53 to your computer and use it in GitHub Desktop.
Save fzyukio/c1bcda60e0cf88c07c26cea3c2089d53 to your computer and use it in GitHub Desktop.
Force Directed Graph
license: MIT
.d3-tip {
line-height: 1;
font-weight: bold;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: #fff;
border-radius: 2px;
}
/* Creates a small triangle extender for the tooltip */
.d3-tip:after {
box-sizing: border-box;
display: inline;
font-size: 10px;
width: 100%;
line-height: 1;
color: rgba(0, 0, 0, 0.8);
content: "\25BC";
position: absolute;
text-align: center;
}
/* Style northward tooltips differently */
.d3-tip.n:after {
margin: -1px 0 0 0;
top: 100%;
left: 0;
}
<!DOCTYPE>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Force Directed Graph</title>
<!-- JavaScript Libraries //-->
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script>
<link href="d3-tip.css" rel="stylesheet" type="text/css">
<!-- CSS Style //-->
<link href="style.css" rel="stylesheet" type="text/css">
<script type="text/javascript" src="main.js"></script>
</head>
<body>
<div style="margin:16px;" class="forceDirectedChart"></div>
</body>
<script>
var inputData = [];
inputData.push({source:"Positive Cashflow",target:"Wallet",type:"positive_cashflow",value:1000});
inputData.push({source:"Positive Cashflow",target:"Bank",type:"positive_cashflow",value:4000});
inputData.push({source:"Positive Cashflow",target:"Government",type:"positive_cashflow",value:5000});
inputData.push({source:"Positive Cashflow",target:"eCommerce",type:"positive_cashflow",value:500});
inputData.push({source:"Negative Cashflow",target:"Telecom",type:"negative_cashflow",value:500});
inputData.push({source:"Negative Cashflow",target:"Wallet",type:"negative_cashflow",value:1500});
inputData.push({source:"Negative Cashflow",target:"Bank",type:"negative_cashflow",value:5000});
inputData.push({source:"Negative Cashflow",target:"Food",type:"negative_cashflow",value:2500});
var totalMaxValue = 20000;
var defaultColorScheme = ["#E57373","#BA68C8","#7986CB","#A1887F","#90A4AE","#AED581","#9575CD","#FF8A65","#4DB6AC","#FFF176","#64B5F6","#00E676"];
renderForceDirectedGraph (inputData,totalMaxValue,".forceDirectedChart", true, defaultColorScheme, "Person")</script>
</html>
function renderForceDirectedGraph (dataset, totalMaxValue, dom_element_to_append_to,isCurrency, rootNodeName){
var margin = {top: 20, right: 20, bottom: 20, left: 20};
var width = 700 - margin.left - margin.right;
// height = $(window).height() - 120 - margin.top - margin.bottom;
var height = width*3/5;
var links = dataset;
var nodes = {};
// Compute the distinct nodes from the links.
links.forEach(function(link) {
link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
});
Object.keys(nodes).forEach(function(key) {
if(key=="Positive Cashflow"){
nodes["Positive Cashflow"].fixed=true;
nodes["Positive Cashflow"].x=0 + 200;
nodes["Positive Cashflow"].y=height/2;
}
else if(key=="Negative Cashflow"){
nodes["Negative Cashflow"].fixed=true;
nodes["Negative Cashflow"].x=width - 200;
nodes["Negative Cashflow"].y=height/2;
}
else if(key==rootNodeName){
nodes[rootNodeName].fixed=true;
nodes[rootNodeName].x=width/2;
nodes[rootNodeName].y=0 + 50;
}
else if(key=="Others"){
nodes["Others"].fixed=true;
nodes["Others"].x=width*3/4;
nodes["Others"].y=height*2/3;
}
else if(key=="Promotional"){
nodes["Promotional"].fixed=true;
nodes["Promotional"].x=width/2;
nodes["Promotional"].y=height*2/3;
}
else if(key=="Transactional"){
nodes["Transactional"].fixed=true;
nodes["Transactional"].x=width/4;
nodes["Transactional"].y=height*2/3;
}
});
var scaleLength = d3.scale.linear().domain([ 0, totalMaxValue ]).range([ 20, 100 ]);
var scaleThickness = d3.scale.linear().domain([ 0, totalMaxValue ]).range([ 5, 30 ]);
var force = d3.layout.force()
.nodes(d3.values(nodes))
.links(links)
.size([width, height])
.linkDistance(function(d){return scaleLength(d.value)*3;})
.charge(-900)
.on("tick", tick)
.start();
var svg = d3.select(dom_element_to_append_to).append("svg")
.attr("width", width)
.attr("height", height);
var path = svg.append("g").selectAll("path")
.data(force.links())
.enter().append("path")
.attr("stroke-width", function(d) { return scaleThickness(d.value)/2; })
.attr("class", function(d) { return "link " + d.type; })
.attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
var circle = svg.append("g").selectAll("circle")
.data(force.nodes())
.enter().append("circle")
.attr("class", function(d) { return "circle " + d.type; })
.attr("r", function(d) { if(d.name=='Positive Cashflow' || d.name=='Negative Cashflow'){return 14;}else if(d.name==rootNodeName){return 18;}return 10; })
.call(force.drag);
var text = svg.append("g").selectAll("text")
.data(force.nodes())
.enter().append("text")
.attr("x", 8)
.attr("y", ".31em")
.text(function(d) { return d.name; });
// Use elliptical arc path segments to doubly-encode directionality.
function tick() {
path.attr("d", linkArc);
circle.attr("transform", transform);
text.attr("transform", transform);
}
function linkArc(d) {
var dx = d.target.x - d.source.x,
dy = d.target.y - d.source.y,
dr = Math.sqrt(dx * dx + dy * dy);
return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
}
function transform(d) {
return "translate(" + d.x + "," + d.y + ")";
}
var tip_node = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<div><span>Category:</span> <span style='color:white'>" + d.name + "</span></div>";
});
var tip_path = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
var currency = "&#8377;";
if (isCurrency)
return "<div><span>Value:</span> <span style='color:white'>" + currency + "&nbsp;" + d.value + "</span></div>";
return "<div><span>Value:</span> <span style='color:white'>" + d.value + "</span></div>";
});
svg.call(tip_path);
svg.call(tip_node);
d3.selectAll(dom_element_to_append_to+ " circle")
.on('mouseover', tip_node.show).on('mouseout',tip_node.hide);
d3.selectAll(dom_element_to_append_to+ " path")
.on('mouseover', tip_path.show).on('mouseout',tip_path.hide);
}
.forceDirectedChart .link {
fill: none;
stroke: #666;
}
.link.parent_trans {
stroke: #2980b9;
}
.link.parent_promo {
stroke: #27ae60;
}
.link.parent_others {
stroke: #c0392b;
}
.link.transactional {
stroke: #3498db;
opacity:0.75;
}
.link.promotional {
opacity:0.75;
stroke: #2ecc71;
}
.link.others {
opacity:0.75;
stroke: #e74c3c;
}
.link.parent_positive {
stroke: #27ae60;
}
.link.parent_negative {
stroke: #c0392b;
}
.link.positive_cashflow {
stroke: #2ecc71;
opacity:0.75;
}
.link.negative_cashflow {
opacity:0.75;
stroke: #e74c3c;
}
.forceDirectedChart circle {
fill: #bdc3c7;
stroke: #95a5a6;
stroke-width: 1px;
}
.forceDirectedChart text {
font: 18px sans-serif;
pointer-events: none;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment