D3
OG
Old school D3 from simpler times
All examples
By author
By category
About
allenbh
Full window
Github gist
Overlaid force directed graphs. Inspired by Bit Coins!
<body> <script src="https://d3js.org/d3.v3.min.js"></script> <script> /* Inspired by Bit Coins! Spending Bit Coin from multiple accounts in the same transaction can be used to group accounts by owner. Then, it may be easier to observe broader behaviors of account owners, not just their accounts. I this demo, a random network of nodes is generated with randomly weighted directed edges between. These nodes represent accounts and transactions. Nodes are grouped together randomly, as if they had participated in spending in the same transaction. The node graph is drawn over the group graph, with grouped nodes floating over their group. Groups and nodes can be dragged. */ var coingraph = coingraph || {}; coingraph.Graph = function () { this._nodes = {}; this._groups = {}; }; coingraph.Graph.prototype.get_node = function (id) { var node = this._nodes[id]; if (node === undefined) { node = new coingraph.Node({id:id, graph:this}); this._nodes[id] = node; } return node; } coingraph.Graph.prototype.get_nodes = function () { return this._nodes; } coingraph.Graph.prototype.merge_groups = function (groups) { for (var i in groups) { var group = this._get_group(i); for (var j in groups) { this._set_group(j, group); } return group; } } coingraph.Graph.prototype.get_group = function (id) { var group = this._get_group(id); this._set_group(id, group); return group; } coingraph.Graph.prototype.get_groups = function () { var groups = {}; for (var i in this._nodes) { var node = this._nodes[i]; var group = this.get_group(node); groups[group.id] = group; } return groups; } coingraph.Graph.prototype._get_group = function (id) { var group = this._groups[id]; while (group !== undefined && group.id !== id) { id = group.id; group = this._groups[id]; } if (group === undefined) { group = new coingraph.Group({id:id, graph:this}); } return group; } coingraph.Graph.prototype._set_group = function (id, as_group) { var group = this._groups[id]; while (group !== undefined && group !== as_group) { as_group.annex_group(group); this._groups[id] = as_group; id = group.id; var group = this._groups[id]; } this._groups[id] = as_group; } coingraph.Node = function(props) { if (props.id === undefined) { throw "undefined id"; } if (props.graph === undefined) { throw "undefined graph"; } this.id = props.id; this.graph = props.graph; this._balance = 0; this._edges = {}; this.get_group().add_node(this); } coingraph.Node.prototype.toString = function() { return this.id; } coingraph.Node.prototype.get_group = function() { return this.graph.get_group(this.id); } coingraph.Node.prototype.get_balance = function() { return this._balance; } coingraph.Node.prototype.add_balance = function(dif) { this._balance += dif; this.get_group().add_balance(dif); } coingraph.Node.prototype.set_balance = function(bal) { this.add_balance(bal - this._balance); } coingraph.Node.prototype.get_edges = function() { return this._edges; } coingraph.Node.prototype.add_edges = function(edges) { for (var i in edges) { var node = this.graph.get_node(i); if (node === this) { continue; } var weight = edges[i]; if (this._edges.hasOwnProperty(i)) { this._edges[i] += weight; } else { this._edges[i] = weight; } } this.get_group().add_edges(edges); } coingraph.Group = function(props) { if (props.id === undefined) { throw "undefined id"; } if (props.graph === undefined) { throw "undefined graph"; } this.id = props.id; this.graph = props.graph; this._balance = 0; this._nodes = {}; this._edges = {}; } coingraph.Group.prototype.get_balance = function(dif) { return this._balance; } coingraph.Group.prototype.add_balance = function(dif) { this._balance += dif; } coingraph.Group.prototype.get_nodes = function() { return this._nodes; } coingraph.Group.prototype.add_node = function(node) { if (!this._nodes.hasOwnProperty(node.id)) { this._nodes[node.id] = node; this.add_balance(node.get_balance()); this.add_edges(node.get_edges()); } } coingraph.Group.prototype.add_nodes = function(nodes) { for (var i in nodes) { this.add_node(nodes[i]); } } coingraph.Group.prototype.get_edges = function(node) { var edges = {}; for (var i in this._edges) { var group = this.graph.get_group(i); if (group === this) { continue; } var weight = this._edges[i]; if (edges.hasOwnProperty(group.id)) { edges[group.id] += weight; } else { edges[group.id] = weight; } } this._edges = edges; return edges; } coingraph.Group.prototype.add_edges = function(edges) { for (var i in edges) { var weight = edges[i]; if (this._edges.hasOwnProperty(i)) { this._edges[i] += weight; } else { this._edges[i] = weight; } } } coingraph.Group.prototype.annex_group = function(group) { this.add_nodes(group._nodes); group.balance = 0; group._nodes = {}; group._edges = {}; } // ------------------------ var graph = new coingraph.Graph(); var count = 15; for(var i = 0; i < count; i++) { var node = graph.get_node(i); var bal = -50*Math.log(Math.random()); node.set_balance(100+bal); var cnt = -1.1*Math.log(Math.random()); for(var j = 0; j <= cnt; j++) { var k = Math.floor(Math.random()*count); var wt = -bal*Math.log(Math.random())/10.0; var edge = {}; edge[k] = 1.1+wt; node.add_edges(edge); } var cnt = -0.8*Math.log(Math.random()); for(var j = 1; j <= cnt; j++) { var k = Math.floor(Math.random()*count); var edge = {}; edge[i] = i; edge[k] = k; graph.merge_groups(edge); } } console.log(graph); var groups = graph.get_groups(); var groups_edges = []; for (var i in groups) { var source = groups[i]; var edges = source.get_edges(); for (var j in edges) { var target = groups[j]; groups_edges.push({ source:source, target:target, weight:edges[j] }); } } groups = d3.values(groups); var nodes = graph.get_nodes(); var nodes_edges = []; for (var i in nodes) { var source = nodes[i]; var edges = source.get_edges(); for (var j in edges) { var target = nodes[j]; nodes_edges.push({ source:source, target:target, weight:edges[j] }); } } nodes = d3.values(nodes); console.log(groups); console.log(groups_edges); console.log(nodes); console.log(nodes_edges); function charge(d) { return -5*(1+d.get_balance()); } function radius(d) { return d.get_balance()/200.0+"em"; } function arc(d) { var sx = d.source.x; var sy = d.source.y; var tx = d.target.x; var ty = d.target.y; var dx = tx - sx; var dy = ty - sy; var r = Math.sqrt(dx * dx + dy * dy) / 8.0; var t = Math.atan2(dy, dx) + Math.PI / 2.0; var mx = (sx + tx) / 2.0 + r * Math.cos(t); var mx1 = mx - dx/4; var mx2 = mx + dx/4; var my = (sy + ty) / 2.0 + r * Math.sin(t); var my1 = my - dy/4; var my2 = my + dy/4; return "M" + sx + "," + sy + "C" + sx + "," + sy + " " + mx1 + "," + my1 + " " + mx + "," + my + "C" + mx2 + "," + my2 + " " + tx + "," + ty + " " + tx + "," + ty; } function get_id(d) { return d.id; } var w = 960, h = 500; var group_force = d3.layout.force() .nodes(groups) .links(groups_edges) .linkStrength(0.02) .linkDistance(1) .size([w, h]) .charge(charge) .on("tick", group_tick) .start(); var node_force = d3.layout.force() .nodes(nodes) .links(nodes_edges) .linkStrength(0.02) .linkDistance(1) .size([w, h]) .charge(charge) .on("tick", node_tick) .start(); var svg = d3.select("body") .append("svg:svg") .attr("width", w) .attr("height", h); var defs = svg.append("svg:defs"); defs.append("svg:marker") .attr("id", "groups_arrow") .attr("orient", "auto") .attr("markerWidth", 4) .attr("markerHeight", 4) .attr("refX", 5) .attr("refY", 0) .attr("viewBox", "0, -5, 10, 10") .append("svg:path") .style("fill", "dimgray") .style("stroke", "none") .attr("d", "M0,-5L10,0L0,5"); defs.append("svg:marker") .attr("id", "nodes_arrow") .attr("orient", "auto") .attr("markerWidth", 4) .attr("markerHeight", 4) .attr("refX", 5) .attr("refY", 0) .attr("viewBox", "0, -5, 10, 10") .append("svg:path") .style("fill", "lightblue") .style("stroke", "none") .attr("d", "M0,-5L10,0L0,5"); var group_path = svg.append("svg:g") .selectAll("path") .data(groups_edges) .enter() .append("svg:path") .style("fill", "none") .style("stroke", "dimgray") .style("stroke-width", function(d) { return Math.log(8*d.weight) + "pt"; }) .attr("marker-mid", "url(#groups_arrow)"); var group_circle = svg.append("svg:g") .selectAll("circle") .data(groups) .enter() .append("svg:circle") .attr("r", radius) .style("fill", "dimgray") .style("stroke", "none") .call(group_force.drag); var node_path = svg.append("svg:g") .selectAll("line") .data(nodes_edges) .enter() .append("svg:path") .style("fill", "none") .style("stroke", "lightblue") .style("stroke-width", function(d) { return Math.log(2*d.weight) + "pt"; }) .attr("marker-mid", "url(#nodes_arrow)"); var node_circle = svg.append("svg:g") .selectAll("circle") .data(nodes) .enter() .append("svg:circle") .attr("r", radius) .style("fill", "lightblue") .text(get_id) .call(node_force.drag); function group_tick(ev) { node_force.alpha(ev.alpha); group_path.attr("d", arc); group_circle.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); } function node_tick(ev) { var nodes = node_force.nodes(); var a = ev.alpha; var b = 1.0 - a; for (var i in nodes) { var node = nodes[i]; var group = node.get_group(); node.x = group.x * a + node.x * b; node.y = group.y * a + node.y * b; } node_path.attr("d", arc); node_circle.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; }); } </script>
Modified
http://d3js.org/d3.v3.min.js
to a secure url
https://d3js.org/d3.v3.min.js