rename explorer -> workbench
[arvados.git] / apps / workbench / app / views / collections / graph.html.erb
diff --git a/apps/workbench/app/views/collections/graph.html.erb b/apps/workbench/app/views/collections/graph.html.erb
new file mode 100644 (file)
index 0000000..c6a2565
--- /dev/null
@@ -0,0 +1,191 @@
+<%= render :partial => 'nav' %>
+<table class="table table-bordered">
+  <tbody>
+    <tr>
+      <td class="d3">
+      </td>
+    </tr>
+  </tbody>
+</table>
+
+<% content_for :head do %>
+<%= javascript_include_tag '/d3.v3.min.js' %>
+
+    <style type="text/css">
+
+path.link {
+  fill: none;
+  stroke: #666;
+  stroke-width: 1.5px;
+}
+
+path.link.derived_from {
+  stroke: green;
+  stroke-dasharray: 0,4 1;
+}
+
+path.link.can_write {
+  stroke: green;
+}
+
+path.link.member_of {
+  stroke: blue;
+  stroke-dasharray: 0,4 1;
+}
+
+path.link.created {
+  stroke: red;
+}
+
+circle.node {
+  fill: #ccc;
+  stroke: #333;
+  stroke-width: 1.5px;
+}
+
+edgetext {
+  font: 12px sans-serif;
+  pointer-events: none;
+    text-align: center;
+}
+
+text {
+  font: 12px sans-serif;
+  pointer-events: none;
+}
+
+text.shadow {
+  stroke: #fff;
+  stroke-width: 3px;
+  stroke-opacity: .8;
+}
+
+    </style>
+<% end %>
+
+<% content_for :js do %>
+
+jQuery(function($){
+
+    var links = <%= raw d3ify_links(@links).to_json %>;
+
+    var nodes = {};
+
+    // Compute the distinct nodes from the links.
+    links.forEach(function(link) {
+       link.source = nodes[link.source] || (nodes[link.source] = {name: link.source});
+       link.target = nodes[link.target] || (nodes[link.target] = {name: link.target});
+    });
+
+    var fill_for = {'ldvyl': 'green',
+                   'j58dm': 'red',
+                   '4zz18': 'blue'};
+    jQuery.each(nodes, function(i, node) {
+       var m = node.name.match(/-([a-z0-9]{5})-/)
+       if (m)
+           node.fill = fill_for[m[1]] || '#ccc';
+       else if (node.name.match(/^[0-9a-f]{32}/))
+           node.fill = fill_for['4zz18'];
+       else
+           node.fill = '#ccc';
+    });
+
+    var w = 960,
+    h = 600;
+
+    var force = d3.layout.force()
+       .nodes(d3.values(nodes))
+       .links(links)
+       .size([w, h])
+       .linkDistance(150)
+       .charge(-300)
+       .on("tick", tick)
+       .start();
+
+    var svg = d3.select("td.d3").append("svg:svg")
+       .attr("width", w)
+       .attr("height", h);
+
+    // Per-type markers, as they don't inherit styles.
+    svg.append("svg:defs").selectAll("marker")
+       .data(["member_of", "owner", "derived_from"])
+       .enter().append("svg:marker")
+       .attr("id", String)
+       .attr("viewBox", "0 -5 10 10")
+       .attr("refX", 15)
+       .attr("refY", -1.5)
+       .attr("markerWidth", 6)
+       .attr("markerHeight", 6)
+       .attr("orient", "auto")
+       .append("svg:path")
+       .attr("d", "M0,-5L10,0L0,5");
+
+    var path = svg.append("svg:g").selectAll("path")
+       .data(force.links())
+       .enter().append("svg:path")
+       .attr("class", function(d) { return "link " + d.type; })
+       .attr("marker-end", function(d) { return "url(#" + d.type + ")"; });
+
+    var circle = svg.append("svg:g").selectAll("circle")
+       .data(force.nodes())
+       .enter().append("svg:circle")
+       .attr("r", 6)
+       .style("fill", function(d) { return d.fill; })
+       .call(force.drag);
+
+    var text = svg.append("svg:g").selectAll("g")
+       .data(force.nodes())
+       .enter().append("svg:g");
+
+    // A copy of the text with a thick white stroke for legibility.
+    text.append("svg:text")
+       .attr("x", 8)
+       .attr("y", ".31em")
+       .attr("class", "shadow")
+       .text(function(d) { return d.name.replace(/^([0-9a-z]{5}-){2}/,''); });
+
+    text.append("svg:text")
+       .attr("x", 8)
+       .attr("y", ".31em")
+       .text(function(d) { return d.name.replace(/^([0-9a-z]{5}-){2}/,''); });
+
+    var edgetext = svg.append("svg:g").selectAll("g")
+       .data(force.links())
+       .enter().append("svg:g");
+
+    edgetext
+       .append("svg:text")
+       .attr("x","-5em")
+       .attr("y","-0.2em")
+       .text(function(d) { return d.type; });
+
+    // Use elliptical arc path segments to doubly-encode directionality.
+    function tick() {
+       path.attr("d", function(d) {
+           var dx = d.target.x - d.source.x,
+            dy = d.target.y - d.source.y,
+            // dr = Math.sqrt(dx * dx + dy * dy);
+            dr = 0;
+           return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
+       });
+
+       circle.attr("transform", function(d) {
+           return "translate(" + d.x + "," + d.y + ")";
+       });
+
+       text.attr("transform", function(d) {
+           return "translate(" + d.x + "," + d.y + ")";
+       });
+
+       edgetext.attr("transform", function(d) {
+           return "translate(" +
+               (d.source.x + d.target.x)/2 + "," +
+               (d.source.y + d.target.y)/2 +
+               ")rotate(" +
+               (Math.atan2(d.target.y - d.source.y, d.target.x - d.source.x) * 180 / Math.PI) +
+               ")";
+       });
+    }
+
+})(jQuery);
+<% end %>