this bl.ock is a step closer to using a Voronoi overlay for easier mouseover interactions with nodes. when you mouseover a Voronoi polygon, the polygon will appear, the circle representing the node that it contains will briefly grow.
svg.selectAll("path.voronoi")
.on('mouseover', function(d) {
// highlight the node
d3.selectAll("circle").filter(function (p) {
return p.id === d.data.id
})
.transition()
.duration(1200)
.attr("r", 8)
.transition()
.duration(1200)
.attr("r", 3)
// show the Voronoi polygon
d3.select(this)
.transition()
.delay(1200)
.duration(1500)
.style("fill-opacity", 0.4)
.style("stroke-opacity", 1)
});
readme-vis
is a look at README.md
files from bl.ocks.org that contain links to other bl.ocks
community detection done with the jLouvain library
source data and scripts used to generate the graph data are at this repository
a set of bl.ocks informed this iteration:
MIT License
xxxxxxxxxx
<html>
<head>
<title>Blocks Graph - Readme Mentions</title>
<meta charset="utf-8" />
<script src="https://d3js.org/d3.v3.min.js"></script>
<script src="jsLouvain.js"></script>
<style>
/*body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }*/
/*svg { width: 100%; height: 100%; }*/
/* Make the chart container fill the page using CSS. */
#viz {
position: fixed;
left: 0px;
right: 0px;
top: 0px;
bottom: 0px;
}
</style>
</head>
<body>
<div id="viz"></div>
<script>
// Extract the width and height that was computed by CSS.
var vizDiv = document.getElementById("viz");
var width = vizDiv.clientWidth;
var height = vizDiv.clientHeight;
var minSide = d3.min([width, height]);
var xOffset = (width - minSide) / 2;
var yOffset = (height - minSide) / 2;
// Use the extracted size to set the size of an SVG element.
var svg = d3.select(vizDiv).append("svg")
.attr("width", width)
.attr("height", height)
.classed("main", true);
d3.json("readme-blocks-graph.json", function(error,data) { createNetwork(data) });
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
function createNetwork(graphContainer) {
var nodeHash = {};
var nodes = [];
var edges = [];
var edgelist = graphContainer["graph"]["edges"];
var nodelist = graphContainer["graph"]["nodes"];
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 == 5*/) {
edges.push({id: nodeHash[edge.source].id + "-" + nodeHash[edge.target].id, source: nodeHash[edge.source], target: nodeHash[edge.target], weight: 1/*edge.weight*/});
}
});
// get some attributes from the nodelist (calculated elsewhere)
// and store them in the nodeHash
nodelist.forEach(function (node) {
if(nodeHash[node.id]) {
nodeHash[node.id]["user"] = node["user"];
nodeHash[node.id]["createdAt"] = node["createdAt"];
nodeHash[node.id]["updatedAt"] = node["updatedAt"];
nodeHash[node.id]["description"] = node["description"];
}
})
// take the attributes now in the nodeHash
// and hang them on the nodes (calculated here from the edgelist)
nodes.forEach(function (node) {
if(nodeHash[node.id]) {
node["user"] = nodeHash[node.id]["user"];
node["createdAt"] = nodeHash[node.id]["createdAt"];
node["updatedAt"] = nodeHash[node.id]["updatedAt"];
node["description"] = nodeHash[node.id]["description"];
}
})
console.log("nodes", nodes);
console.log("edges", edges);
createForceNetwork(nodes, edges);
}
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 colors = d3.scale.category20();
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}; });
console.log("node_data for jLouvain", node_data);
console.log("edge_data for jLouvain", edge_data);
var community = jLouvain().nodes(node_data).edges(edge_data);
var result = community();
console.log("jLouvain result", result);
nodes.forEach(function (node) {
node.module = result[node.id]
//console.log("node.module", node.module)
});
modularityCensus(nodes, edges, result);
var force = d3.layout.force().nodes(nodes).links(edges)
.size([minSide, minSide]) // make a square
.charge(-100)
.chargeDistance(100)
.linkStrength(2)
.linkDistance(3)
.gravity(0.07);
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", "1px")
.style("stroke", "gray")
.style("pointer-events", "none");
var nodeEnter = d3.select("svg.main").selectAll("g.node")
.data(nodes, function (d) {return d.id})
.enter();
nodeEnter
.append("a")
.attr("xlink:href", function (d){
return "https://bl.ocks.org/" + d.user + "/" + d.id;
})
//.attr("target", "_blank")
.append("circle")
.attr("r", 3)
.attr("class", "foreground")
.style("fill", function (d) {return colors(d.module)})
.style("stroke-width", function (d) {return d.border ? "3px" : "1px"})
/*
// draw labels over nodes
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", "8px")
.text(function (d) {return d.id})
.style("pointer-events", "none")
nodeEnter.append("text")
.style("text-anchor", "middle")
.attr("y", 3)
.style("font-size", "8px")
.text(function (d) {return d.id})
.style("pointer-events", "none")
*/
force.start();
for(var i = 0; i < 200; i++){
force.tick();
}
// draw the network at each tick
//force.on("tick", updateNetwork);
// after 200 iterations, we say the network
// has stabilized and we stop the
// force layout physics simulation
force.stop();
// now we draw the network
updateNetwork();
function updateNetwork(e) {
createVoronoi();
// draw the links
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})
.attr("transform", "translate(" + xOffset + "," + yOffset +")");
// draw the nodes
d3.select("svg.main").selectAll("circle")
.attr("cx", function (d) {return d.x; })
.attr("cy", function (d) {return d.y; })
.attr("transform", "translate(" + xOffset + "," + yOffset +")");
}
////////////////////////////////////////////////////////////////////
///////////////////////// voronoi overlay //////////////////////////
////////////////////////////////////////////////////////////////////
function createVoronoi() {
var rawPoints = [];
nodes.forEach(function(d) {
rawPoints.push({ x: d.x, y: d.y, id: d.id })
})
var voronoi = d3.geom.voronoi();
voronoi
.x(function(d) { return d.x; })
.y(function(d) { return d.y; })
.clipExtent([[-10 - xOffset, -10 - yOffset], [width+10, height+10]]);
voronoiData = voronoi(rawPoints);
voronoiData = voronoiData.map(function (d,i) {
return {coordinates: d, data: rawPoints[i]}
})
console.log(voronoiData)
svg.selectAll("path.voronoi")
.data(voronoiData)
.enter()
.insert("path")
.attr("class", "voronoi")
//.style("stroke", "gold")
//.style("stroke-width", "1px")
//.style("fill", "yellow")
//.style("fill-opacity", 0.3)
.style("fill-opacity", 0)
.style("fill", "silver")
.style("stroke-opacity", 0)
.style("stroke", "white")
.style("stroke-width", "1px")
.attr("d", function(d) { return "M" + d.coordinates.join("L") + "Z"; })
.attr("transform", "translate(" + xOffset + "," + yOffset +")")
.on('mouseover', function(d) {
console.log("d", d);
// highlight the node
d3.selectAll("circle").filter(function (p) {
return p.id === d.data.id
})
.transition()
.duration(1200)
.attr("r", 8)
.transition()
.duration(1200)
.attr("r", 3)
console.log("this", d3.select(this));
// show the Voronoi polygon
d3.select(this)
.transition()
.delay(1200)
.duration(1500)
.style("fill-opacity", 0.4)
.style("stroke-opacity", 1)
//.transition()
//.duration(1750)
//.style("fill-opacity", 0)
});
}
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////
}
</script>
</body>
</html>
https://d3js.org/d3.v3.min.js