Graph Example 7 for How to Create Effective Network Data Visualization
One method that seems intuitive for creating links between existing nodes is to allow the user to drag that node onto the node to which you want to make the connection.
This is particularly easy to do when the nodes are circular, since you just have to measure the distance from the center of each node to the center of other nodes. With squares or more irregular shapes, you'll need to settle for less accurate detection or using a bounding-box method.
After making new connections, the modularity is recalculated based on the new links.
The method used here, which creates a background circle that lights up when in the proximity of another node, was first developed by Scott Murray and I for Kindred Britain.
This example relies on the jLouvain library to calculate community structure.
forked from mjamei's block: Networks - Graphs 7
xxxxxxxxxx
<html>
<head>
<title>Edge creation</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v3.min.js" type="text/JavaScript"></script>
<script src="jsLouvain.js" type="text/JavaScript"></script>
</head>
<style>
svg {
height: 800px;
width: 800px;
border: 1px solid gray;
}
</style>
<body>
<div id="viz">
<svg class="main">
</svg>
</div>
</body>
<footer>
<script>
d3.csv("firm.csv",function(error,data) {createNetwork(data)});
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function createNetwork(edgelist) {
var nodeHash = {};
var nodes = [];
var edges = [];
edgelist.forEach(function (edge) {
if (!nodeHash[edge.source]) {
nodeHash[edge.source] = {id: edge.source, label: edge.source};
nodes.push(nodeHash[edge.source]);
}
if (!nodeHash[edge.target]) {
nodeHash[edge.target] = {id: edge.target, label: edge.target};
nodes.push(nodeHash[edge.target]);
}
if (edge.weight >= 1) {
edges.push({id: nodeHash[edge.source].id + "-" + nodeHash[edge.target].id, source: nodeHash[edge.source], target: nodeHash[edge.target], weight: edge.weight});
}
});
createForceNetwork(nodes, edges);
console.log(nodeHash)
}
function modularityCensus(nodes, edges, moduleHash) {
edges.forEach(function (edge) {
if (edge.source.module !== edge.target.module) {
edge.border = true;
}
else {
edge.border = false;
}
});
nodes.forEach(function (node) {
var theseEdges = edges.filter(function(d) {return d.source === node || d.target === node});
var theseSourceModules = theseEdges.map(function (d) {return d.source.module}).filter(onlyUnique);
var theseTargetModules = theseEdges.map(function (d) {return d.target.module}).filter(onlyUnique);
if (theseSourceModules.length > 1 || theseTargetModules.length > 1) {
node.border = true;
}
else {
node.border = false;
}
});
}
function createForceNetwork(nodes, edges) {
//create a network from an edgelist
var colors = d3.scale.ordinal().domain([0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]).range(["#996666", "#66CCCC", "#FFFF99", "#CC9999", "#666633", "#993300", "#999966", "#660000", "#996699", "#cc6633", "#ff9966", "#339999", "#6699cc", "#ffcc66", "#ff6600", "#00ccccc"]);
var node_data = nodes.map(function (d) {return d.id});
var edge_data = edges.map(function (d) {return {source: d.source.id, target: d.target.id, weight: 1}; });
var community = jLouvain().nodes(node_data).edges(edge_data);
var result = community();
nodes.forEach(function (node) {
node.module = result[node.id]
});
modularityCensus(nodes, edges, result);
var force = d3.layout.force().nodes(nodes).links(edges)
.size([800,800])
.charge(-1071)
.chargeDistance(571)
.gravity(0.1)
.on("tick", updateNetwork);
var drag = force.drag()
.on("drag", drag)
.on("dragend", dragend)
.on("dragstart", dragstart);
var edgeEnter = d3.select("svg.main").selectAll("g.edge")
.data(edges, function (d) {return d.id})
.enter()
.append("g")
.attr("class", "edge");
edgeEnter
.append("line")
.style("stroke-width", function (d) {return d.border ? "3px" : "1px"})
.style("stroke", "black")
.style("pointer-events", "none");
var nodeEnter = d3.select("svg.main").selectAll("g.node")
.data(nodes, function (d) {return d.id})
.enter()
.append("g")
.attr("class", "node")
.call(drag);
nodeEnter.append("circle")
.attr("r", 16)
.attr("class", "background")
.style("fill", "lightgreen")
.style("opacity", 0)
.style("pointer-events", "none");
nodeEnter.append("circle")
.attr("r", 12)
.attr("class", "foreground")
.style("fill", function (d) {return colors(d.module)})
.style("stroke", "black")
.style("stroke-width", function (d) {return d.border ? "3px" : "1px"})
nodeEnter.append("text")
.style("text-anchor", "middle")
.attr("y", 3)
.style("stroke-width", "1px")
.style("stroke-opacity", 0.75)
.style("stroke", "white")
.style("font-size", "12px")
.text(function (d) {return d.id})
.style("pointer-events", "none")
nodeEnter.append("text")
.style("text-anchor", "middle")
.attr("y", 3)
.style("font-size", "12px")
.text(function (d) {return d.id})
.style("pointer-events", "none")
force.start();
function dragstart() {
nodes.forEach(function (node) {
node.fixed = true;
})
}
function drag(d) {
var nodeDom = this;
var foundOverlap = false
nodes.forEach(function (otherNode) {
var distance = Math.sqrt(Math.pow(otherNode.x - d.x, 2) + Math.pow(otherNode.y - d.y, 2));
if (otherNode != d && distance < 16) {
foundOverlap = true;
}
})
if (foundOverlap == true) {
d3.select(nodeDom).select("circle.background")
.style("opacity", 0.5)
}
else {
d3.select(nodeDom).select("circle.background")
.style("opacity", 0)
}
}
function dragend(d) {
force.stop();
d3.selectAll("circle.background")
.style("opacity", 0);
nodes.forEach(function (otherNode) {
otherNode.fixed = false;
var distance = Math.sqrt(Math.pow(otherNode.x - d.x, 2) + Math.pow(otherNode.y - d.y, 2));
if (otherNode != d && distance < 16) {
var newEdge = {id: d.id + "-" + otherNode.id, source: d, target: otherNode};
if (edges.map(function (d) {return d.id}).indexOf(newEdge.id) == -1) {
edges.push(newEdge);
}
}
});
force.links(edges);
var edgeEnter = d3.select("svg.main").selectAll("g.edge")
.data(edges, function (d) {return d.id})
.enter()
.insert("g", "g.node")
.attr("class", "edge");
edgeEnter
.append("line")
.style("stroke-width", function (d) {return d.border ? "3px" : "1px"})
.style("stroke", "black")
.style("pointer-events", "none");
var community = jLouvain().nodes(node_data).edges(edge_data);
var result = community();
nodes.forEach(function (node) {
node.module = result[node.id]
});
node_data = nodes.map(function (d) {return d.id});
edge_data = edges.map(function (d) {return {source: d.source.id, target: d.target.id, weight: 1}; });
modularityCensus(nodes, edges, result);
d3.selectAll("circle.foreground")
.style("fill", function (d) {return colors(d.module)})
.style("stroke-width", function (d) {return d.border ? "3px" : "1px"});
force.start();
}
function updateNetwork(e) {
d3.select("svg.main").selectAll("line")
.attr("x1", function (d) {return d.source.x})
.attr("y1", function (d) {return d.source.y})
.attr("x2", function (d) {return d.target.x})
.attr("y2", function (d) {return d.target.y});
d3.select("svg.main").selectAll("g.node")
.attr("transform", function (d) {return "translate(" + d.x + "," + d.y + ")"});
}
}
</script>
</footer>
</html>
Modified http://d3js.org/d3.v3.min.js to a secure url
https://d3js.org/d3.v3.min.js