1 | function d3_graph() { |
---|
2 | |
---|
3 | // Some reference links: |
---|
4 | // How to get link ids instead of index |
---|
5 | // http://stackoverflow.com/questions/23986466/d3-force-layout-linking-nodes-by-name-instead-of-index |
---|
6 | // embedding web2py in d3 |
---|
7 | // http://stackoverflow.com/questions/34326343/embedding-d3-js-graph-in-a-web2py-bootstrap-page |
---|
8 | |
---|
9 | // nodes and links are defined in appadmin.html <script> |
---|
10 | |
---|
11 | |
---|
12 | var edges = []; |
---|
13 | |
---|
14 | links.forEach(function(e) { |
---|
15 | var sourceNode = nodes.filter(function(n) { |
---|
16 | return n.name === e.source; |
---|
17 | })[0], |
---|
18 | targetNode = nodes.filter(function(n) { |
---|
19 | return n.name === e.target; |
---|
20 | })[0]; |
---|
21 | |
---|
22 | edges.push({ |
---|
23 | source: sourceNode, |
---|
24 | target: targetNode, |
---|
25 | value: 1}); |
---|
26 | |
---|
27 | }); |
---|
28 | |
---|
29 | edges.forEach(function(e) { |
---|
30 | |
---|
31 | if (!e.source["linkcount"]) e.source["linkcount"] = 0; |
---|
32 | if (!e.target["linkcount"]) e.target["linkcount"] = 0; |
---|
33 | |
---|
34 | e.source["linkcount"]++; |
---|
35 | e.target["linkcount"]++; |
---|
36 | }); |
---|
37 | |
---|
38 | //var width = 960, height = 600; |
---|
39 | var height = window.innerHeight|| docEl.clientHeight|| bodyEl.clientHeight; |
---|
40 | var width = window.innerWidth || docEl.clientWidth || bodyEl.clientWidth; |
---|
41 | var svg = d3.select("#vis").append("svg") |
---|
42 | .attr("width", width) |
---|
43 | .attr("height", height); |
---|
44 | |
---|
45 | // updated for d3 v4. |
---|
46 | var simulation = d3.forceSimulation() |
---|
47 | .force("link", d3.forceLink().id(function(d) { return d.id; })) |
---|
48 | .force("charge", d3.forceManyBody().strength(strength)) |
---|
49 | .force("center", d3.forceCenter(width / 2, height / 2)) |
---|
50 | .force("collision", d3.forceCollide(35)); |
---|
51 | |
---|
52 | // Node charge strength. Repel strength greater for less links. |
---|
53 | //function strength(d) { return -50/d["linkcount"] ; } |
---|
54 | function strength(d) { return -25 ; } |
---|
55 | |
---|
56 | // Link distance. Distance increases with number of links at source and target |
---|
57 | function distance(d) { return (60 + (d.source["linkcount"] * d.target["linkcount"])) ; } |
---|
58 | |
---|
59 | // Link strength. Strength is less for highly connected nodes (move towards target dist) |
---|
60 | function strengthl(d) { return 5/(d.source["linkcount"] + d.target["linkcount"]) ; } |
---|
61 | |
---|
62 | simulation |
---|
63 | .nodes(nodes) |
---|
64 | .on("tick", tick); |
---|
65 | |
---|
66 | simulation.force("link") |
---|
67 | .links(edges) |
---|
68 | .distance(distance) |
---|
69 | .strength(strengthl); |
---|
70 | |
---|
71 | // build the arrow. |
---|
72 | svg.append("svg:defs").selectAll("marker") |
---|
73 | .data(["end"]) // Different link/path types can be defined here |
---|
74 | .enter().append("svg:marker") // This section adds in the arrows |
---|
75 | .attr("id", String) |
---|
76 | .attr("viewBox", "0 -5 10 10") |
---|
77 | .attr("refX", 25) // Moves the arrow head out, allow for radius |
---|
78 | .attr("refY", 0) // -1.5 |
---|
79 | .attr("markerWidth", 6) |
---|
80 | .attr("markerHeight", 6) |
---|
81 | .attr("orient", "auto") |
---|
82 | .append("svg:path") |
---|
83 | .attr("d", "M0,-5L10,0L0,5"); |
---|
84 | |
---|
85 | var link = svg.selectAll('.link') |
---|
86 | .data(edges) |
---|
87 | .enter().append('line') |
---|
88 | .attr("class", "link") |
---|
89 | .attr("marker-end", "url(#end)"); |
---|
90 | |
---|
91 | var node = svg.selectAll(".node") |
---|
92 | .data(nodes) |
---|
93 | .enter().append("g") |
---|
94 | .attr("class", function(d) { return "node " + d.type;}) |
---|
95 | .attr('transform', function(d) { |
---|
96 | return "translate(" + d.x + "," + d.y + ")"}) |
---|
97 | .classed("auth", function(d) { return (d.name.startsWith("auth") ? true : false);}); |
---|
98 | |
---|
99 | node.call(d3.drag() |
---|
100 | .on("start", dragstarted) |
---|
101 | .on("drag", dragged) |
---|
102 | .on("end", dragended)); |
---|
103 | |
---|
104 | // add the nodes |
---|
105 | node.append('circle') |
---|
106 | .attr('r', 16) |
---|
107 | ; |
---|
108 | |
---|
109 | // add text |
---|
110 | node.append("text") |
---|
111 | .attr("x", 12) |
---|
112 | .attr("dy", "-1.1em") |
---|
113 | .text(function(d) {return d.name;}); |
---|
114 | |
---|
115 | node.on("mouseover", function(d) { |
---|
116 | |
---|
117 | var g = d3.select(this); // the node (table) |
---|
118 | |
---|
119 | // tooltip |
---|
120 | |
---|
121 | var fields = d.fields; |
---|
122 | var fieldformat = "<TABLE>"; |
---|
123 | fields.forEach(function(d) { |
---|
124 | fieldformat += "<TR><TD><B>"+ d.name+"</B></TD><TD>"+ d.type+"</TD><TD>"+ d.disp+"</TD></TR>"; |
---|
125 | }); |
---|
126 | fieldformat += "</TABLE>"; |
---|
127 | var tiplength = d.fields.length; |
---|
128 | |
---|
129 | // Define 'div' for tooltips |
---|
130 | var div = d3.select("body").append("div") // declare the tooltip div |
---|
131 | .attr("class", "tooltip") // apply the 'tooltip' class |
---|
132 | .style("opacity", 0) |
---|
133 | .html('<h5>' + d.name + '</h5>' + fieldformat) |
---|
134 | .style("left", 20 + (d3.event.pageX) + "px")// or just (d.x + 50 + "px") |
---|
135 | .style("top", tooltop(tiplength))// or ... |
---|
136 | .transition() |
---|
137 | .duration(800) |
---|
138 | .style("opacity", 0.9); |
---|
139 | }); |
---|
140 | |
---|
141 | function tooltop(tiplength) { |
---|
142 | //aim to ensure tooltip is fully visible whenver possible |
---|
143 | return (Math.max(d3.event.pageY - 20 - (tiplength * 14),0)) + "px" |
---|
144 | } |
---|
145 | |
---|
146 | node.on("mouseout", function(d) { |
---|
147 | d3.select("body").select('div.tooltip').remove(); |
---|
148 | }); |
---|
149 | |
---|
150 | // instead of waiting for force to end with : force.on('end', function() |
---|
151 | // use .on("tick", instead. Here is the tick function |
---|
152 | function tick() { |
---|
153 | node.attr('transform', function(d) { |
---|
154 | d.x = Math.max(30, Math.min(width - 16, d.x)); |
---|
155 | d.y = Math.max(30, Math.min(height - 16, d.y)); |
---|
156 | return "translate(" + d.x + "," + d.y + ")"; }); |
---|
157 | |
---|
158 | link.attr('x1', function(d) {return d.source.x;}) |
---|
159 | .attr('y1', function(d) {return d.source.y;}) |
---|
160 | .attr('x2', function(d) {return d.target.x;}) |
---|
161 | .attr('y2', function(d) {return d.target.y;}); |
---|
162 | }; |
---|
163 | |
---|
164 | function dragstarted(d) { |
---|
165 | if (!d3.event.active) simulation.alphaTarget(0.3).restart(); |
---|
166 | d.fx = d.x; |
---|
167 | d.fy = d.y; |
---|
168 | }; |
---|
169 | |
---|
170 | function dragged(d) { |
---|
171 | d.fx = d3.event.x; |
---|
172 | d.fy = d3.event.y; |
---|
173 | }; |
---|
174 | |
---|
175 | function dragended(d) { |
---|
176 | if (!d3.event.active) simulation.alphaTarget(0); |
---|
177 | d.fx = null; |
---|
178 | d.fy = null; |
---|
179 | }; |
---|
180 | |
---|
181 | }; |
---|