-
-
Save mayblue9/5b3ac882929adcb9e39379e4429431a0 to your computer and use it in GitHub Desktop.
Taffy Edges
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<head> | |
<style> | |
body { font-family: Hevetica; } | |
.node { | |
stroke: #fff; | |
stroke-width: 1.5px; | |
} | |
.link { | |
stroke: #999; | |
stroke-opacity: 0; | |
} | |
</style> | |
<title>Taffy Edges</title> | |
</head> | |
<body> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script> | |
var width = 960, | |
height = 500; | |
stateChange = true; | |
refreshGraph = 100; | |
nodeSettings = { | |
emperor: {number: 1, color: "red", size: 15}, | |
governor: {number: 0, color: "orange", size: 10}, | |
hierarchical3: {number: 0, color: "yellow", size: 8}, | |
hierarchical4: {number: 0, color: "green", size: 6}, | |
hierarchical5: {number: 0, color: "blue", size: 4}, | |
hierarchical6: {number: 0, color: "darkblue", size: 2}, | |
outsiderA: {number: 0, color: "lightgray", size: 8}, | |
outsiderB: {number: 2, color: "gray", size: 4} | |
} | |
var color = d3.scale.category20(); | |
var force = d3.layout.force() | |
.charge(function(d) {return d.weight * -30}) | |
.linkDistance(40) | |
.linkStrength(function(d) {return d.weight}) | |
.gravity(.05) | |
.size([width, height]); | |
var svg = d3.select("body").append("svg") | |
.attr("width", width) | |
.attr("height", height); | |
taffyEdge = d3.svg.line().x(function(d) {return d.x}).y(function(d) {return d.y}).interpolate("basis"); | |
function edgePoints(sourceNode, targetNode) { | |
var sourceWidth = Math.max(nodeSettings[sourceNode.type].size * 2) / 2 | |
var targetWidth = Math.max(nodeSettings[targetNode.type].size * 2) / 2 | |
var xOffset = sourceNode.x - targetNode.x; | |
var yOffset = sourceNode.y - targetNode.y; | |
if (Math.abs(xOffset) > Math.abs(yOffset)) { | |
sourceWidthX = 0; | |
sourceWidthY = sourceWidth; | |
targetWidthX = 0; | |
targetWidthY = targetWidth; | |
} | |
else { | |
sourceWidthX = sourceWidth; | |
sourceWidthY = 0; | |
targetWidthX = targetWidth; | |
targetWidthY = 0; | |
} | |
// var width = 10; | |
var taffyPoints = [ | |
{x: sourceNode.x, y: sourceNode.y}, | |
{x: sourceNode.x - sourceWidthX, y: sourceNode.y - sourceWidthY}, | |
{x: (sourceNode.x + targetNode.x) / 2, y: (sourceNode.y + targetNode.y) / 2}, | |
{x: targetNode.x - sourceWidthX, y: targetNode.y - sourceWidthY}, | |
{x: targetNode.x, y: targetNode.y}, | |
{x: targetNode.x + targetWidthX, y: targetNode.y + targetWidthY}, | |
{x: (sourceNode.x + targetNode.x) / 2, y: (sourceNode.y + targetNode.y) / 2}, | |
{x: sourceNode.x + targetWidthX, y: sourceNode.y + targetWidthY}, | |
{x: sourceNode.x, y: sourceNode.y} | |
] | |
return taffyPoints; | |
} | |
numCommunities = 6; | |
function maxIndex(x) { | |
var maxValue = d3.max(x); | |
return x.indexOf(maxValue); | |
} | |
var graphVariable; | |
graph = graphConstructor(); | |
graphVariable = graph; | |
graphVariable.nodes.forEach( function (d) { | |
d.nodeStrength = 1; | |
d.communities = []; | |
d.communityBuffer = []; | |
}) | |
testCommunities(); | |
force | |
.nodes(graph.nodes) | |
.links(graph.links) | |
.start(); | |
/* | |
graph.nodes[0].fixed = true; | |
graph.nodes[0].x = 100; | |
graph.nodes[0].px = 100; | |
graph.nodes[0].y = 100; | |
graph.nodes[0].py = 100; | |
graph.nodes[62].fixed = true; | |
graph.nodes[62].x = 860; | |
graph.nodes[62].px = 860; | |
graph.nodes[62].y = 400; | |
graph.nodes[62].py = 400; | |
*/ | |
force | |
.linkStrength(function(d) {return (d.source.nodeStrength == 0 || d.target.nodeStrength == 0) ? 0 : 1}) | |
.charge(function(d) {return d.nodeStrength * -60}) | |
var link = svg.selectAll(".link") | |
.data(graph.links) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("d", function (d) {return taffyEdge(edgePoints(d.source, d.target)) + "Z"}) | |
.style("opacity", 0); | |
var node = svg.selectAll(".node") | |
.data(graph.nodes) | |
.enter().append("g") | |
.attr("class", "node") | |
.on("click", grayOut) | |
.call(force.drag); | |
node.append("circle") | |
.attr("r", 1) | |
.style("fill", "gray") | |
node.append("text") | |
.text(function(d) { return d.label; }) | |
.style("stroke", "none") | |
.style("font-size", "0px") | |
.style("font-weight", 0) | |
.attr("text-anchor", "middle") | |
.style("pointer-events", "none"); | |
d3.selectAll("g.node").select("circle") | |
.each(function(d,i) { | |
d3.select(this) | |
.transition() | |
.delay(2000 + (i * 300)) | |
// .duration(function(d, i) {return i * 100}) | |
.attr("r", Math.max(10, nodeSettings[d.type].size * 2)) | |
.style("fill", nodeSettings[d.type].color) | |
.transition() | |
.duration(1000) | |
.attr("r", nodeSettings[d.type].size) | |
}); | |
d3.selectAll("g.node").select("text") | |
.each(function(d,i) { | |
d3.select(this) | |
.transition() | |
.delay(2000 + (i * 300)) | |
.style("font-size", function (d) {return Math.max(12, nodeSettings[d.type].size * 2) + "px"}) | |
.style("font-weight", 500) | |
.transition() | |
.duration(1000) | |
.style("font-size", function (d) {return nodeSettings[d.type].size + "px"}) | |
.style("font-weight", 150) | |
}) | |
d3.selectAll("path.link") | |
.transition() | |
.delay(function(d,i) {return 2000 + (i * 300)}) | |
.style("opacity", 1) | |
.transition() | |
.duration(1000) | |
.style("fill", function(d) {return d.type == "horizontal" ? "lightblue" : "lightgreen"}) | |
force.on("tick", tick); | |
function tick() { | |
/* | |
if(refreshGraph > 0) { | |
console.log("skipped") | |
refreshGraph--; | |
return; | |
} | |
*/ | |
force.alpha(.05); | |
if (Math.random() < .025) { | |
var randomPerturb = (Math.floor(Math.random() * (graphVariable.nodes.length))); | |
graphVariable.nodes[randomPerturb].nodeStrength = Math.max(0, graphVariable.nodes[randomPerturb].nodeStrength - (Math.random() > .50 ? -1 : 1)) | |
stateChange = true; | |
} | |
if (stateChange == true) { | |
force.stop(); | |
stateChange = false; | |
d3.selectAll("circle.node") | |
// .style("fill", function(d) {return d.nodeStrength == 0 ? "gray" : color(maxIndex(d.communities))}) | |
.style("display", function(d) {return d.nodeStrength == 0 ? "none" : "block"}) | |
.attr("opacity", function(d) {return Math.max(.33, (d.nodeStrength * .1))}) | |
d3.selectAll("path.link") | |
.style("display", function(d) {return (d.source.nodeStrength == 0 || d.target.nodeStrength == 0) ? "none" : "block"}); | |
setTimeout(function() {force.start()}, 100); | |
refreshGraph = 100; | |
} | |
d3.selectAll(".link") | |
.attr("d", function (d) {return taffyEdge(edgePoints(d.source, d.target)) + "Z"}) | |
/* link.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.selectAll("g.node") | |
.attr("transform", function(d) { return "translate("+d.x+","+d.y+")"}) | |
} | |
function grayOut(d,i) { | |
d.fixed = true; | |
testCommunities() | |
// stateChange = true; | |
} | |
function testCommunities() { | |
graphVariable.nodes.forEach( function(node) { | |
node.communities = []; | |
node.communityBuffer = []; | |
for (var community = 0; community < numCommunities; community++) { | |
// Initialize with a small Exponential variate | |
node.communities[community] = 0.01 * -Math.log(Math.random()); | |
node.communityBuffer[community] = 0.0; | |
} | |
}); | |
var communitySums = []; | |
for (var iteration = 0; iteration < 20; iteration++) { | |
for (var community = 0; community < numCommunities; community++) { | |
communitySums[community] = 0.0; | |
} | |
// Estimate community memberships for each edge | |
graphVariable.links.forEach( function(edge) { | |
// if (edge.source.nodeStrength > 0 && edge.target.nodeStrength > 0) { | |
var sourceCommunities = edge.source.communities; | |
var targetCommunities = edge.target.communities; | |
var distribution = []; | |
// Multiply the two community membership vectors | |
for (var community = 0; community < numCommunities; community++) { | |
distribution[community] = sourceCommunities[community] * targetCommunities[community]; | |
} | |
// Normalize and add to the gradient | |
var normalizer = edge.weight / d3.sum(distribution); | |
for (var community = 0; community < numCommunities; community++) { | |
distribution[community] *= normalizer; | |
communitySums[community] += distribution[community]; | |
edge.source.communityBuffer[community] += distribution[community]; | |
edge.target.communityBuffer[community] += distribution[community]; | |
} | |
// } | |
}); | |
// We need to divide each node value by the square root of the community sum. | |
var communityNormalizers = [] | |
for (var community = 0; community < numCommunities; community++) { | |
communityNormalizers[community] = 1.0 / Math.sqrt(communitySums[community]); | |
} | |
// Update parameters and clear the buffer. | |
graphVariable.nodes.forEach( function(node) { | |
for (var community = 0; community < numCommunities; community++) { | |
node.communities[community] = node.communityBuffer[community] * communityNormalizers[community]; | |
node.communityBuffer[community] = 0.0; | |
} | |
}); | |
} | |
d3.selectAll("circle.node") | |
.style("fill", function(d) { return color(maxIndex(d.communities)); }) | |
} | |
function graphConstructor() { | |
var nodes = [], links = []; | |
var verticalEdgeSettings = | |
[ | |
{source: "governor", target: "emperor", number: 6, numbermin: 4}, | |
{source: "hierarchical3", target: "governor", number: 3, numbermin: 2}, | |
{source: "hierarchical4", target: "hierarchical3", number: 3, numbermin: 1}, | |
{source: "hierarchical5", target: "hierarchical4", number: 3, numbermin: 1}, | |
{source: "hierarchical6", target: "hierarchical5", number: 5, numbermin: 0}, | |
{source: "outsiderA", target: "emperor", number: 2, numbermin: 2} | |
] | |
var horizontalEdgeSettings = | |
[ | |
{source: "governor", target: "governor", probability: .01}, | |
{source: "hierarchical3", target: "hierarchical3", probability: .05}, | |
{source: "hierarchical4", target: "hierarchical4", probability: .025}, | |
{source: "hierarchical5", target: "hierarchical5", probability: .01}, | |
{source: "outsiderA", target: "emperor", probability: .1}, | |
{source: "outsiderA", target: "governor", probability: .25}, | |
{source: "outsiderB", target: "governor", probability: .1}, | |
{source: "outsiderB", target: "hierarchical3", probability: .2} | |
] | |
for (nodeClass in nodeSettings) { | |
var x = 1; | |
while (x <= nodeSettings[nodeClass].number) { | |
var topNode = {label: nodeClass + "-" + x, type: nodeClass} | |
nodes.push(topNode); | |
x++; | |
} | |
} | |
var node = 0; | |
while (node < nodes.length && node < 500) { | |
for (verticalEdge in verticalEdgeSettings) { | |
if (nodes[node].type == verticalEdgeSettings[verticalEdge].target) { | |
var x = 1; | |
var x = Math.min(verticalEdgeSettings[verticalEdge].number - verticalEdgeSettings[verticalEdge].numbermin, Math.ceil(verticalEdgeSettings[verticalEdge].number * Math.random())) | |
while (x < verticalEdgeSettings[verticalEdge].number) { | |
var spawnNode = {label: verticalEdgeSettings[verticalEdge].source + "-" + x, type: verticalEdgeSettings[verticalEdge].source} | |
nodes.push(spawnNode); | |
var newLink = {source: nodes[node], target: spawnNode, weight: 2, type: "vertical"}; | |
links.push(newLink); | |
x++; | |
} | |
} | |
} | |
node++; | |
} | |
for (nodex in nodes) { | |
for (nodey in nodes) { | |
for (horizontalEdge in horizontalEdgeSettings) { | |
if (horizontalEdgeSettings[horizontalEdge].source == nodes[nodex].type && horizontalEdgeSettings[horizontalEdge].target == nodes[nodey].type) { | |
var randomChance = Math.random(); | |
if (randomChance < horizontalEdgeSettings[horizontalEdge].probability) { | |
var newLink = {source: nodes[nodex], target: nodes[nodey], weight: 1, type: "horizontal"}; | |
links.push(newLink); | |
} | |
} | |
} | |
} | |
} | |
genNodes = nodes; | |
genEdges = links; | |
var returnObject = {links: genEdges, nodes: genNodes}; | |
return returnObject; | |
} | |
function populateLists() { | |
var textNodes = "<h3>Node List</h3><p>id<br>"; | |
for (x in force.nodes()) { | |
textNodes += force.nodes()[x].label; | |
textNodes += "<br>"; | |
} | |
textNodes += "</p>" | |
textNodes += "<h3>Edge List</h3><p>source,target,type<br>"; | |
for (x in force.links()) { | |
textNodes += force.links()[x].source.label; | |
textNodes += ","; | |
textNodes += force.links()[x].target.label; | |
textNodes += ","; | |
textNodes += force.links()[x].type; | |
textNodes += "<br>"; | |
} | |
textNodes += "</p>" | |
d3.select("#code").html(textNodes); | |
} | |
</script> | |
<p>Settings: <input type="button" value="Edge List" onclick="populateLists()" /></p> | |
<pre id="code"> | |
nodeSettings = { | |
emperor: {number: 1, color: "red", size: 15}, | |
hierarchical2: {number: 0, color: "orange", size: 10}, | |
hierarchical3: {number: 0, color: "yellow", size: 8}, | |
hierarchical4: {number: 0, color: "green", size: 6}, | |
hierarchical5: {number: 0, color: "blue", size: 4}, | |
hierarchical6: {number: 0, color: "darkblue", size: 2}, | |
outsiderA: {number: 0, color: "lightgray", size: 8}, | |
outsiderB: {number: 2, color: "gray", size: 4} | |
} | |
var verticalEdgeSettings = | |
[ | |
{source: "hierarchical2", target: "emperor", number: 6, numbermin: 4}, | |
{source: "hierarchical3", target: "hierarchical2", number: 3, numbermin: 2}, | |
{source: "hierarchical4", target: "hierarchical3", number: 3, numbermin: 1}, | |
{source: "hierarchical5", target: "hierarchical4", number: 3, numbermin: 1}, | |
{source: "hierarchical6", target: "hierarchical5", number: 5, numbermin: 0}, | |
{source: "outsiderA", target: "emperor", number: 2, numbermin: 2} | |
] | |
horizontalEdgeSettings = | |
[ | |
{source: "hierarchical2", target: "hierarchical2", probability: .01}, | |
{source: "hierarchical3", target: "hierarchical3", probability: .05}, | |
{source: "hierarchical4", target: "hierarchical4", probability: .025}, | |
{source: "hierarchical5", target: "hierarchical5", probability: .01}, | |
{source: "outsiderA", target: "hierarchical1", probability: .1}, | |
{source: "outsiderA", target: "hierarchical2", probability: .25}, | |
{source: "outsiderB", target: "hierarchical2", probability: .1}, | |
{source: "outsiderB", target: "hierarchical3", probability: .2} | |
] | |
</pre> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment