]> source.dussan.org Git - archiva.git/commitdiff
[MRM-1752] Graph visualization for dependencies
authorOlivier Lamy <olamy@apache.org>
Mon, 22 Apr 2013 13:28:38 +0000 (13:28 +0000)
committerOlivier Lamy <olamy@apache.org>
Mon, 22 Apr 2013 13:28:38 +0000 (13:28 +0000)
Submitted by Antoine ROUAZE

git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1470498 13f79535-47bb-0310-9956-ffa450edef68

archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/css/archiva.css
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/archiva.js
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/main.js
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/search.js
archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/templates/archiva/search.html

index d47fc23a2f51924797427024e119d3d381b8e857..61dd7c539b8f9fef9f0f6e944313aefab0460546 100644 (file)
@@ -371,6 +371,7 @@ artifact.detail.tab.header.metadatas=Metadata
 artifact.detail.tab.header.mailing.list=Mailing Lists
 artifact.detail.tab.header.info=Info
 artifact.detail.tab.header.dependency.tree=Dependency Tree
+artifact.detail.tab.header.dependency.graph=Dependency Graph
 artifact.detail.tab.header.used.by=Used By
 artifact.detail.tab.header.file.content=Artifacts Content
 artifact.detail.tab.header.file.download=Artifacts
index cf688aa23bd9348ebd47643c07468aea5d39156a..e9339211779bce9634ac2a1065cef475bd9ee495 100644 (file)
@@ -187,4 +187,39 @@ ol.linenums {
     color: #333333;
     font-family: "Helvetica Neue",Helvetica,Arial,sans-serif;
     font-size: 14px;
+}
+
+path.link {
+    fill: none;
+    stroke: #666;
+    stroke-width: 1.5px;
+}
+
+marker#licensing {
+    fill: green;
+}
+
+path.link.licensing {
+    stroke: green;
+}
+
+path.link.resolved {
+    stroke-dasharray: 0,2 1;
+}
+
+circle {
+    fill: #ccc;
+    stroke: #333;
+    stroke-width: 1.5px;
+}
+
+text {
+    font: 10px sans-serif;
+    pointer-events: none;
+}
+
+text.shadow {
+    stroke: #fff;
+    stroke-width: 3px;
+    stroke-opacity: .8;
 }
\ No newline at end of file
index a37efb5b7e3ba9e09da6a121c679804a1981044f..debca7aa077907be14ebc0d2bd10e67bde95e41a 100644 (file)
@@ -82,6 +82,7 @@ $.ajax({
               "sammy": "sammy.0.7.4",
               "select2": "select2.min-3.2",
               "jqueryFileTree": "jqueryFileTree-1.0.1",
+              "d3": "d3.min.3.1.5",
               "redback": "redback/redback",
               "redback.roles": "redback/roles",
               "redback.user": "redback/user",
index e63d0f2d88a63ed9387af3c1d4da33ff3e090420..ec039f6eaee87171700b55204f09daf4b6814c95 100644 (file)
@@ -550,6 +550,24 @@ function(jquery,ui,sammy,tmpl,i18n,jqueryCookie,bootstrap,archivaSearch,jqueryVa
           checkArtifactDetailContent(groupId,artifactId,version,repositoryId,"artifact-details-dependency-tree-content-a");
         });
 
+        this.get('#artifact-dependency-graph/:groupId/:artifactId/:version', function(context) {
+
+          var repositoryId = this.params.repositoryId;
+          var groupId= this.params.groupId;
+          var artifactId= this.params.artifactId;
+          var version= this.params.version;
+          checkArtifactDetailContent(groupId,artifactId,version,repositoryId,"artifact-details-dependency-graph-content-a");
+        });
+
+        this.get('#artifact-dependency-graph~:repository/::groupId/:artifactId/:version', function(context) {
+
+          var repositoryId = this.params.repositoryId;
+          var groupId= this.params.groupId;
+          var artifactId= this.params.artifactId;
+          var version= this.params.version;
+          checkArtifactDetailContent(groupId,artifactId,version,repositoryId,"artifact-details-dependency-graph-content-a");
+        })
+
         this.get('#artifact-mailing-list/:groupId/:artifactId/:version',function(context){
 
           var repositoryId = this.params.repositoryId;
index 3e980792f7020235eec635e2799ffeb491ea5be3..f920e2d8f51b9ec93d7a4bf1b72edb684a3e610a 100644 (file)
@@ -16,7 +16,7 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-define("archiva.search",["jquery","i18n","jquery.tmpl","select2","knockout","knockout.simpleGrid","jqueryFileTree","prettify"]
+define("archiva.search",["jquery","i18n","jquery.tmpl","select2","knockout","knockout.simpleGrid","jqueryFileTree","prettify", "d3"]
 , function(jquery,i18n,jqueryTmpl,select2,ko,koSimpleGrid) {
 
   //-----------------------------------------
@@ -466,6 +466,17 @@ define("archiva.search",["jquery","i18n","jquery.tmpl","select2","knockout","kno
                   return;
                 }
 
+                if ($(e.target).attr("data-target")=="#artifact-details-dependency-graph-content") {
+                  var location ="#artifact-dependency-graph";
+                  if (self.repositoryId) {
+                    location+="~"+self.repositoryId;
+                  }
+                  location+="/"+self.groupId+"/"+self.artifactId+"/"+self.version;
+                  displayGraph(treeDependencyUrl);
+                  window.sammyArchivaApplication.setLocation(location);
+                  return;
+                }
+
                 if ($(e.target).attr("data-target")=="#artifact-details-used-by-content") {
                   var location ="#artifact-used-by";
                   if (self.repositoryId){
@@ -530,7 +541,201 @@ define("archiva.search",["jquery","i18n","jquery.tmpl","select2","knockout","kno
       });
     }
 
+    displayGraphData=function(data) {
+
+      var w = 960,
+          h = 500,
+          r =6,
+          node,
+          link,
+          root;
+
+      var onmousedown = false;
+
+      var svg = d3.select("#artifact-details-dependency-graph-content")
+          .append("svg:svg")
+            .attr("width", w)
+            .attr("height", h)
+            .attr("pointer-events", "all")
+          .append('svg:g')
+            .call(d3.behavior.zoom().on("zoom", redraw))
+          .append('svg:g');
+
+      svg.append('svg:rect')
+          .attr('width', w)
+          .attr('height', h)
+          .attr('fill', 'rgba(255, 255, 255, 0)')
+
+      var force = d3.layout.force()
+          .on("tick", tick)
+          .linkDistance(100)
+          .charge(-300)
+          .size([w, h]);
+
+      var nodes = flatten(data[0]);
+      var links = d3.layout.tree().links(nodes);
+
+      force.nodes(nodes)
+          .links(links)
+          .start();
+
+      svg.append("svg:defs").selectAll("marker")
+          .data(["suit"])
+          .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 suit";
+            })
+          .attr("marker-end", function (d) {
+              return "url(#suit)";
+            });
+
+      var circle = svg.append("svg:g").selectAll("circle")
+          .data(force.nodes())
+          .enter().append("svg:circle")
+          .attr("r", r)
+          .on("click", click)
+          .on("mouseenter",onmouseover)
+          .on("mouseleave", function (d, i) {
+            setTimeout(function() {
+              $("#plot_overlay").remove();
+            }, 201)})
+          .call(force.drag);
+
+      force.drag()
+          .on("dragstart", function() {
+              $("#plot_overlay").remove();
+              onmousedown = true;
+            })
+          .on("dragend", function() {
+            $("#plot_overlay").remove();
+            onmousedown = false;
+          });
 
+      var text = svg.append("svg:g")
+          .selectAll("g")
+          .data(force.nodes())
+          .enter().append("svg:g");
+
+      text.append("svg:text")
+          .attr("x", 8)
+          .attr("y", ".31em")
+          .attr("class", "shadow")
+          .text(function (d) {
+              return d.artifact.artifactId;
+            });
+
+      text.append("svg:text")
+          .attr("x", 8)
+          .attr("y", ".31em")
+          .text(function (d) {
+              return d.artifact.artifactId;
+            });
+
+      function tick() {
+        path.attr("d", function (d) {
+          var dx = d.target.x - d.source.x,
+              dy = d.target.y - d.source.y,
+              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 + ")";
+        });
+      }
+
+      function redraw() {
+        trans=d3.event.translate;
+        scale=d3.event.scale;
+        svg.attr("transform",
+                 "translate(" + trans + ")"
+                     + " scale(" + scale + ")");
+      }
+
+      function click(d) {
+        var location ="#artifact";
+        var selectedRepo=getSelectedBrowsingRepository();
+        if (selectedRepo){
+          location+="~"+selectedRepo;
+        }
+        location+="/"+d.artifact.groupId+"/"+d.artifact.artifactId+"/"+d.artifact.version;
+
+        window.sammyArchivaApplication.setLocation(location);
+      }
+
+      function onmouseover(d) {
+        if (!onmousedown) {
+          var x;
+          var y;
+          if (d3.event.pageX != undefined && d3.event.pageY != undefined) {
+            x = d3.event.pageX;
+            y = d3.event.pageY;
+          } else {
+            x = d3.event.clientX + document.body.scrollLeft +
+                document.documentElement.scrollLeft;
+            y = d3.event.clientY + document.body.scrollTop +
+                document.documentElement.scrollTop;
+          }
+          x += r;
+          y += r;
+          setTimeout(function() {
+            var bubble_code = "<div id='plot_overlay' class='popover fade in left' style='position:absolute; top:"
+                + y + "px; left:" + x + "px; z-index: 1; display: block;'>" +
+                "<h3 class='popover-title'>Details</h3>" +
+                "<div class='popover-content'><ul><li>ArtifactId: " +d.artifact.artifactId + "</li>"
+                + "<li>GroupId: " +d.artifact.groupId + "</li>"
+                + "<li>Version: " +d.artifact.version + "</li></ul></div>" +
+                "</div>";
+            $("body").append(bubble_code);
+          }, 200);
+        }
+      }
+
+      function flatten(root) {
+        var nodes = [], i = 0;
+
+        function recurse(node) {
+          if (node.childs) {
+            node.childs.forEach(recurse);
+            node.children = node.childs;
+          }
+          if (!node.id) node.id = ++i;
+
+          nodes.push(node);
+        }
+
+        recurse(root);
+        return nodes;
+      }
+    }
+
+    displayGraph=function(treeDependencyUrl) {
+
+      $.ajax(treeDependencyUrl, {
+        type: "GET",
+        dataType: 'json',
+        success: function(data) {
+          displayGraphData(data);
+        }
+      });
+    }
 
     displayGroup=function(groupId){
       var selectedRepo=getSelectedBrowsingRepository();
@@ -2101,4 +2306,4 @@ define("archiva.search",["jquery","i18n","jquery.tmpl","select2","knockout","kno
 
 
 
-});
\ No newline at end of file
+});
index bb491d614bdb747ba117fb50a96684d937145286..6193884e978802fd07991357b35916f3f30f9951 100644 (file)
     <a data-toggle="tab" id="artifact-details-dependency-tree-content-a"
        data-target="#artifact-details-dependency-tree-content" href="#artifact-details-dependency-tree-content">${$.i18n.prop('artifact.detail.tab.header.dependency.tree')}</a>
   </li>
+  <li>
+    <a data-toggle="tab" id="artifact-details-dependency-graph-content-a"
+       data-target="#artifact-details-dependency-graph-content" href="#artifact-details-dependency-graph-content">${$.i18n.prop('artifact.detail.tab.header.dependency.graph')}</a>
+  </li>
   <li>
     <a data-toggle="tab" id="artifact-details-download-content-a" data-target="#artifact-details-download-content"
        href="#artifact-details-download-content">${$.i18n.prop('artifact.detail.tab.header.file.download')}</a>
 
 <div id="artifact-details-dependency-tree-content" class="tab-pane"></div>
 
+<div id="artifact-details-dependency-graph-content" class="tab-pane"></div>
+
 <div id="artifact-details-files-content" class="tab-pane"></div>
 
 <div id="artifact-details-download-content" class="tab-pane"></div>
     <pre class="prettyprint linenums" id="artifact-content-text"></pre>
   </div>
 
-</script>
\ No newline at end of file
+</script>