From 6be708dec3b47ec3943ccba5ef4e2b8f75fad615 Mon Sep 17 00:00:00 2001 From: Olivier Lamy Date: Mon, 22 Apr 2013 13:28:38 +0000 Subject: [PATCH] [MRM-1752] Graph visualization for dependencies Submitted by Antoine ROUAZE git-svn-id: https://svn.apache.org/repos/asf/archiva/trunk@1470498 13f79535-47bb-0310-9956-ffa450edef68 --- .../apache/archiva/i18n/default.properties | 1 + .../src/main/webapp/css/archiva.css | 35 +++ .../src/main/webapp/js/archiva/archiva.js | 1 + .../src/main/webapp/js/archiva/main.js | 18 ++ .../src/main/webapp/js/archiva/search.js | 209 +++++++++++++++++- .../webapp/js/templates/archiva/search.html | 8 +- 6 files changed, 269 insertions(+), 3 deletions(-) diff --git a/archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties b/archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties index d47fc23a2..61dd7c539 100644 --- a/archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties +++ b/archiva-modules/archiva-web/archiva-web-common/src/main/resources/org/apache/archiva/i18n/default.properties @@ -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 diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/css/archiva.css b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/css/archiva.css index cf688aa23..e93392117 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/css/archiva.css +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/css/archiva.css @@ -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 diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/archiva.js b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/archiva.js index a37efb5b7..debca7aa0 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/archiva.js +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/archiva.js @@ -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", diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/main.js b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/main.js index e63d0f2d8..ec039f6ea 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/main.js +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/main.js @@ -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; diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/search.js b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/search.js index 3e980792f..f920e2d8f 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/search.js +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/archiva/search.js @@ -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 = "
" + + "

Details

" + + "
  • ArtifactId: " +d.artifact.artifactId + "
  • " + + "
  • GroupId: " +d.artifact.groupId + "
  • " + + "
  • Version: " +d.artifact.version + "
" + + "
"; + $("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 +}); diff --git a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/templates/archiva/search.html b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/templates/archiva/search.html index bb491d614..6193884e9 100644 --- a/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/templates/archiva/search.html +++ b/archiva-modules/archiva-web/archiva-webapp/src/main/webapp/js/templates/archiva/search.html @@ -517,6 +517,10 @@ ${$.i18n.prop('artifact.detail.tab.header.dependency.tree')} +
  • + ${$.i18n.prop('artifact.detail.tab.header.dependency.graph')} +
  • ${$.i18n.prop('artifact.detail.tab.header.file.download')} @@ -693,6 +697,8 @@
    +
    +
    @@ -968,4 +974,4 @@
    
       
     
    -
    \ No newline at end of file
    +
    -- 
    2.39.5