Skip to content

Instantly share code, notes, and snippets.

@ayala-usma
Last active November 28, 2017 06:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ayala-usma/9788ff0bf450313971e83c99cefaaa1e to your computer and use it in GitHub Desktop.
Save ayala-usma/9788ff0bf450313971e83c99cefaaa1e to your computer and use it in GitHub Desktop.
Red de contratistas
license: mit
border: no
height: 1000

This visualisation uses a D3 force simulation to show the Twitter relationships between the Assembly Members in the Welsh Assembly in terms of the number of times each assembly member has mentioned another assembly member in a tweet

Twitter relationships were mined on 22/03/2017, and are representative of the conversational relationships on that date. Links between AMs represent a conversational relationship: one AM has mentioned the other. Party colour indicates the direction of the mention.

Hover over the nodes to fade out non-connected nodes.

Rather than using intermediate nodes to create curved links (as in Mike Bostock's block), this adds curves by adding a calculated control point for each edge

forked from martinjc's block: D3 force simulation, curved edges and hover interaction (Data: Twitter mentions between members of the Welsh Assembly)

forked from ayala-usma's block: D3 force simulation, curved edges and hover interaction (Human FOXP2 gene interaction network)

forked from ayala-usma's block: FOXP2 protein interaction network: (D3 force simulation, curved edges and hover interaction)

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>FOXP2 Human Interactome</title>
<link rel="stylesheet" href="style.css">
<script src="//d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-scale-chromatic.v1.min.js"></script>
</head>
<body>
<div class="human_network"></div>
<script src="script.js">
</script>
</body>
</html>
//Canvas dimensions and margins
var width = 800,
height = 800;
var margin = {
top: 50,
bottom: 50,
left: 50,
right: 50,
}
//Creating a categorical color scale for the protein type and establishing the category color coding by protein type.
var color = d3.scaleOrdinal(d3.schemeDark2),
proteinType = ["Cell signaling","Immunity", "Metabolism", "Molecular traffic", "Structural", "Transcription factor", "Unknown"];
//Central node in the simulation
var selectedNodes = ["FOXP2"];
//Percentile 98 of interactions to be drawn
var percentile = 0;
// create an svg to draw in
var svg = d3.select(".human_network")
.append("svg")
.attr("width", width)
.attr("height", height)
.append('g')
.attr('transform', 'translate(' + margin.top + ',' + margin.left + ')');
width = width - margin.left - margin.right;
height = height - margin.top - margin.bottom;
var simulation = d3.forceSimulation()
// pull nodes together based on the links between them
.force("link", d3.forceLink().id(function(d) {
return d.name;
})
.strength(0.025))
// push nodes apart to space them out
.force("charge", d3.forceManyBody().strength(-2000))
// add some collision detection so they don't overlap
.force("collide", d3.forceCollide().radius(30))
// and draw them around the centre of the space
.force("center", d3.forceCenter(width / 2, height / 2));
// load the graph
d3.json("human_foxp2.json", function(error, graph) {
// set the nodes
var nodes = graph.nodes;
// links between nodes
var links = graph.edges;
// add the curved links to our graphic
var link = svg.selectAll(".link")
.data(links)
.enter()
.append("path")
.attr("class", "link")
.attr('stroke', function(d){
return "#ddd";
})
.filter(function (d){if (d.weight > percentile){return this}});
// add the nodes to the graphic
var node = svg.selectAll(".node")
.data(nodes)
.enter().append("g")
// a circle to represent the node
node.append("circle")
.attr("class", "node")
.attr("id", function (d){return d.name})
.attr("r", 9)
.attr("fill", function(d) {
return color(proteinType.indexOf(d.type));})
.call(d3.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended))
.on("mouseover", mouseOver(.2))
.on("mouseout", mouseOut);
// hover text for the node
node.append("title")
.text(function(d) {
return "Protein: " + d.name + "\n" + "Molecular function: " + d.type;
});
// add a label to each node
node.append("text")
.attr("dx", 12)
.attr("dy", ".35em")
.attr("font-size", "14px")
.text(function(d) {
return d.name;
})
.style("fill", function(d) {
return color(proteinType.indexOf(d.type));
});
// add the nodes to the simulation and
// tell it what to do on each tick
simulation
.nodes(nodes)
.on("tick", ticked);
// add the links to the simulation
simulation
.force("link")
.links(links);
// on each tick, update node and link positions
function ticked() {
link.attr("d", positionLink);
node.attr("transform", positionNode);
}
// links are drawn as curved paths between nodes,
// through the intermediate nodes
function positionLink(d) {
var offset = 30;
var midpoint_x = (d.source.x + d.target.x) / 2;
var midpoint_y = (d.source.y + d.target.y) / 2;
var dx = (d.target.x - d.source.x);
var dy = (d.target.y - d.source.y);
var normalise = Math.sqrt((dx * dx) + (dy * dy));
var offSetX = midpoint_x + offset*(dy/normalise);
var offSetY = midpoint_y - offset*(dx/normalise);
return "M" + d.source.x + "," + d.source.y +
"S" + offSetX + "," + offSetY +
" " + d.target.x + "," + d.target.y;
}
// move the node based on forces calculations
function positionNode(d) {
// keep the node within the boundaries of the svg
if(selectedNodes.indexOf(d.name) > -1){
d.fx = width / 2;
d.fy = height / 2;
};
if (d.x < 0) {
d.x = 0
};
if (d.y < 0) {
d.y = 0
};
if (d.x > width) {
d.x = width
};
if (d.y > height) {
d.y = height
};
return "translate(" + d.x + "," + d.y + ")";
}
// build a dictionary of nodes that are linked
var linkedByIndex = {};
links.forEach(function(d) {
linkedByIndex[d.source.name + "," + d.target.name] = 1;
});
// check the dictionary to see if nodes are linked
function isConnected(a, b) {
return linkedByIndex[a.name + "," + b.name] || linkedByIndex[b.name + "," + a.name] || a.name === b.name;
}
//Fits a linear width scale for the links on mouseover
var min_width = 0.3,
max_width = 5,
scale_converter = d3.scaleLinear().domain([d3.min(graph.edges, function(d){return d.weight}),d3.max(graph.edges, function(d){return d.weight})]).range([min_width,max_width]);
// fade nodes on hover
function mouseOver(opacity) {
return function(d) {
// check all other nodes to see if they're connected
// to this one. if so, keep the opacity at 1, otherwise
// fade
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
node.style("fill-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
return thisOpacity;
});
// also style link accordingly
link.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
link.style("stroke", function(o){
return o.source === d || o.target === d ? color(proteinType.indexOf(o.source.type)) : "#ddd";
});
link.style("stroke-width", function(o){return scale_converter(o.weight)});
};
}
function mouseOut() {
node.style("stroke-opacity", 1);
node.style("fill-opacity", 1);
link.style("stroke-opacity", 1);
link.style("stroke", "#ddd");
link.style("stroke-width", 1);
}
function dragstarted(d) {
if (!d3.event.active) simulation.alphaTarget(0.5).restart();
d.fx = d.x;
d.fy = d.y;
}
function dragged(d) {
d.fx = d3.event.x;
d.fy = d3.event.y;
}
function dragended(d) {
if (!d3.event.active) simulation.alphaTarget(0.01);
d.fx = null;
d.fy = null;
}
//Insert legend by color coding in the graph
var legendRectSize = 18,
legendSpacing = 4; //Legend square size
var legend = svg.selectAll('.legend')
.data(proteinType)
.enter()
.append('g')
.attr('class', 'legend')
.attr('transform', function(d, i) {
var heightLegend = legendRectSize + legendSpacing;
var offset = 600;
var horz = -2 * legendRectSize;
var vert = (i * heightLegend) + offset;
return 'translate(' + horz + ',' + vert + ')';
});
legend.append('rect')
.attr('width', legendRectSize)
.attr('height', legendRectSize)
.style('fill', function(d,i){return color(i)})
.style('stroke', function(d,i){return color(i)});
legend.append('text')
.attr('x', legendRectSize + legendSpacing)
.attr('y', legendRectSize - legendSpacing)
.attr ("font-size", "14px")
.text(function(d, i) { return d;});
});
text {
font-family: "HelveticaNeue-Light", "Helvetica Neue Light", "Helvetica Neue", Helvetica, Arial, "Lucida Grande", sans-serif;
}
svg {
width: 100%;
}
.link {
fill: none;
stroke: #ddd;
stroke-width: 1px;
}
.node {
stroke: #e8e8e8;
stroke-width: 0px;
}
circle {
cursor: pointer;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment