Merge branch 'master' into 3198-writable-fuse
[arvados.git] / apps / workbench / app / views / collections / graph.html.erb
1 <%#= render :partial => 'nav' %>
2 <table class="table table-bordered">
3   <tbody>
4     <tr>
5       <td class="d3">
6       </td>
7     </tr>
8   </tbody>
9 </table>
10
11 <% content_for :head do %>
12 <%= javascript_include_tag '/d3.v3.min.js' %>
13
14     <style type="text/css">
15
16 path.link {
17   fill: none;
18   stroke: #666;
19   stroke-width: 1.5px;
20 }
21
22 path.link.derived_from {
23   stroke: green;
24   stroke-dasharray: 0,4 1;
25 }
26
27 path.link.can_write {
28   stroke: green;
29 }
30
31 path.link.member_of {
32   stroke: blue;
33   stroke-dasharray: 0,4 1;
34 }
35
36 path.link.created {
37   stroke: red;
38 }
39
40 circle.node {
41   fill: #ccc;
42   stroke: #333;
43   stroke-width: 1.5px;
44 }
45
46 edgetext {
47   font: 12px sans-serif;
48   pointer-events: none;
49     text-align: center;
50 }
51
52 text {
53   font: 12px sans-serif;
54   pointer-events: none;
55 }
56
57 text.shadow {
58   stroke: #fff;
59   stroke-width: 3px;
60   stroke-opacity: .8;
61 }
62
63     </style>
64 <% end %>
65
66 <% content_for :js do %>
67
68 jQuery(function($){
69
70     var links = <%= raw d3ify_links(@links).to_json %>;
71
72     var nodes = {};
73
74     // Compute the distinct nodes from the links.
75     links.forEach(function(link) {
76         link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
77         link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
78     });
79
80     var fill_for = {'ldvyl': 'green',
81                     'j58dm': 'red',
82                     '4zz18': 'blue'};
83     jQuery.each(nodes, function(i, node) {
84         var m = node.name.match(/-([a-z0-9]{5})-/)
85         if (m)
86             node.fill = fill_for[m[1]] || '#ccc';
87         else if (node.name.match(/^[0-9a-f]{32}/))
88             node.fill = fill_for['4zz18'];
89         else
90             node.fill = '#ccc';
91     });
92
93     var w = 960,
94     h = 600;
95
96     var force = d3.layout.force()
97         .nodes(d3.values(nodes))
98         .links(links)
99         .size([w, h])
100         .linkDistance(150)
101         .charge(-300)
102         .on("tick", tick)
103         .start();
104
105     var svg = d3.select("td.d3").append("svg:svg")
106         .attr("width", w)
107         .attr("height", h);
108
109     // Per-type markers, as they don't inherit styles.
110     svg.append("svg:defs").selectAll("marker")
111         .data(["member_of", "owner", "derived_from"])
112         .enter().append("svg:marker")
113         .attr("id", String)
114         .attr("viewBox", "0 -5 10 10")
115         .attr("refX", 15)
116         .attr("refY", -1.5)
117         .attr("markerWidth", 6)
118         .attr("markerHeight", 6)
119         .attr("orient", "auto")
120         .append("svg:path")
121         .attr("d", "M0,-5L10,0L0,5");
122
123     var path = svg.append("svg:g").selectAll("path")
124         .data(force.links())
125         .enter().append("svg:path")
126         .attr("class", function(d) { return "link " + d.type; })
127         .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
128
129     var circle = svg.append("svg:g").selectAll("circle")
130         .data(force.nodes())
131         .enter().append("svg:circle")
132         .attr("r", 6)
133         .style("fill", function(d) { return d.fill; })
134         .call(force.drag);
135
136     var text = svg.append("svg:g").selectAll("g")
137         .data(force.nodes())
138         .enter().append("svg:g");
139
140     // A copy of the text with a thick white stroke for legibility.
141     text.append("svg:text")
142         .attr("x", 8)
143         .attr("y", ".31em")
144         .attr("class", "shadow")
145         .text(function(d) { return d.name.replace(/^([0-9a-z]{5}-){2}/,''); });
146
147     text.append("svg:text")
148         .attr("x", 8)
149         .attr("y", ".31em")
150         .text(function(d) { return d.name.replace(/^([0-9a-z]{5}-){2}/,''); });
151
152     var edgetext = svg.append("svg:g").selectAll("g")
153         .data(force.links())
154         .enter().append("svg:g");
155
156     edgetext
157         .append("svg:text")
158         .attr("x","-5em")
159         .attr("y","-0.2em")
160         .text(function(d) { return d.type; });
161
162     // Use elliptical arc path segments to doubly-encode directionality.
163     function tick() {
164         path.attr("d", function(d) {
165             var dx = d.target.x - d.source.x,
166             dy = d.target.y - d.source.y,
167             // dr = Math.sqrt(dx * dx + dy * dy);
168             dr = 0;
169             return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
170         });
171
172         circle.attr("transform", function(d) {
173             return "translate(" + d.x + "," + d.y + ")";
174         });
175
176         text.attr("transform", function(d) {
177             return "translate(" + d.x + "," + d.y + ")";
178         });
179
180         edgetext.attr("transform", function(d) {
181             return "translate(" +
182                 (d.source.x + d.target.x)/2 + "," +
183                 (d.source.y + d.target.y)/2 +
184                 ")rotate(" +
185                 (Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) * 180 / Math.PI) +
186                 ")";
187         });
188     }
189
190 })(jQuery);
191 <% end %>