D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
mayblue9
Full window
Github gist
Taffy Edges
<!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="https://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>
Modified
http://d3js.org/d3.v3.min.js
to a secure url
https://d3js.org/d3.v3.min.js