|
| 1 | +<!DOCTYPE HTML> |
| 2 | +<html lang="en"> |
| 3 | + <head> |
| 4 | + <!--[if gt IE 8]><!--> <html class="no-js"> <!--<![endif]--> |
| 5 | + <meta charset="utf-8"> |
| 6 | + <meta http-equiv="X-UA-Compatible" content="IE=edge"> |
| 7 | + <title>Singularity Hub Comparison Tree</title> |
| 8 | + <meta name="viewport" content="width=device-width, initial-scale=1"> |
| 9 | + <meta name="description" content="singularity container and workflow management" /> |
| 10 | + <meta name="keywords" content="singularity, containers" /> |
| 11 | + <meta name="author" content="vsoch" /> |
| 12 | + |
| 13 | + <link rel="icon" type="image/ico" href="/static/img/favicon.ico"/> |
| 14 | + |
| 15 | + <!-- Facebook and Twitter integration --> |
| 16 | + <meta property="og:title" content="singularity-hub"/> |
| 17 | + <meta property="og:image" content=""/> |
| 18 | + <meta property="og:url" content=""/> |
| 19 | + <meta property="og:site_name" content="Singularity Hub"/> |
| 20 | + <meta property="og:description" content="singularity container and workflow management"/> |
| 21 | + <meta name="twitter:title" content="SingularityApp" /> |
| 22 | + <meta name="twitter:image" content="" /> |
| 23 | + <meta name="twitter:url" content="https://www.twitter.com/SingularityApp" /> |
| 24 | + <meta name="twitter:card" content="" /> |
| 25 | + |
| 26 | + <!-- Google Webfonts --> |
| 27 | + <link href='https://fonts.googleapis.com/css?family=Roboto:400,300,100,500' rel='stylesheet' type='text/css'> |
| 28 | + <link href='https://fonts.googleapis.com/css?family=Roboto+Slab:400,300,100,500' rel='stylesheet' type='text/css'> |
| 29 | + <link href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css" rel="stylesheet"> |
| 30 | + |
| 31 | + |
| 32 | + |
| 33 | + |
| 34 | +<style> |
| 35 | +.btn-sm { |
| 36 | + margin-top:20px !important; |
| 37 | +} |
| 38 | +.container-fluid { |
| 39 | + margin: 5px; |
| 40 | +} |
| 41 | + |
| 42 | +#tree { |
| 43 | + z-index:10; |
| 44 | +} |
| 45 | + |
| 46 | +.node circle { |
| 47 | + fill: none; |
| 48 | + stroke: none; |
| 49 | +} |
| 50 | + |
| 51 | +.node text { |
| 52 | + font: 20px sans-serif; |
| 53 | +} |
| 54 | + |
| 55 | +.link { |
| 56 | + fill: none; |
| 57 | + stroke: #ccc; |
| 58 | + stroke-width: 1.5px; |
| 59 | +} |
| 60 | + |
| 61 | +.node circle { |
| 62 | + fill: #e32929; |
| 63 | +} |
| 64 | + |
| 65 | +.node text { |
| 66 | + font: 10px sans-serif; |
| 67 | +} |
| 68 | + |
| 69 | +.node--internal circle { |
| 70 | + fill: #555; |
| 71 | +} |
| 72 | + |
| 73 | +.node--internal text { |
| 74 | + text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff; |
| 75 | +} |
| 76 | + |
| 77 | +.link { |
| 78 | + fill: none; |
| 79 | + stroke: #555; |
| 80 | + stroke-opacity: 0.4; |
| 81 | + stroke-width: 1.5px; |
| 82 | +} |
| 83 | + |
| 84 | +label { |
| 85 | + display: block; |
| 86 | +} |
| 87 | + |
| 88 | +</style> |
| 89 | + |
| 90 | + |
| 91 | + <!-- Animate.css --> |
| 92 | + <link rel="stylesheet" href="https://singularity-hub.org/static/css/animate.css"> |
| 93 | + <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons"> |
| 94 | + <!-- Theme Style --> |
| 95 | + <link rel="stylesheet" href="https://singularity-hub.org/static/css/style.css"> |
| 96 | + <!-- Modernizr JS --> |
| 97 | + <script src="https://singularity-hub.org/static/js/modernizr-2.6.2.min.js"></script> |
| 98 | + <!-- FOR IE9 below --> |
| 99 | + <!--[if lt IE 9]> |
| 100 | + <script src="/static/js/respond.min.js"></script> |
| 101 | + <![endif]--> |
| 102 | + |
| 103 | + |
| 104 | + </head> |
| 105 | + |
| 106 | + |
| 107 | + <body> |
| 108 | + |
| 109 | + <header> |
| 110 | + |
| 111 | + |
| 112 | + |
| 113 | + <nav class="navbar navbar-default" role="navigation"> |
| 114 | + <a class="navbar-brand" href="/">Singularity</a> |
| 115 | + |
| 116 | + <ul class="nav navbar-nav"> |
| 117 | + <li><a href="https://singularity-hub.org/collections">Containers</a></li> |
| 118 | + <li><a href="https://singularity-hub.org/about">About</a></li> |
| 119 | + <li><a href="https://singularity-hub.org/faq">User Guide</a></li> |
| 120 | + <li class="dropdown"> |
| 121 | + <a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false"> |
| 122 | + Tools |
| 123 | + <span class="caret"></span></a> |
| 124 | + <ul class="dropdown-menu"> |
| 125 | + <li><a href="https://singularity-hub.org/tools/compare">Container Similarity Tree</a></li> |
| 126 | + <li><a href="https://singularity-hub.org/tools/converter/dockerfile">Dockerfile Converter</a></li> |
| 127 | + <!--<li role="separator" class="divider"></li>--> |
| 128 | + </ul> |
| 129 | + </li> |
| 130 | + <li><a href="https://singularity-hub.org/search"><i class="fa fa-search"></i></a></li> |
| 131 | + </ul> |
| 132 | + |
| 133 | + <ul class="nav navbar-nav navbar-right" style="margin-right:20px"> |
| 134 | + |
| 135 | + <li><a href="https://singularity-hub.org/login/">Login</a></li> |
| 136 | + |
| 137 | + <!-- Search Icon --> |
| 138 | + <!--<li><a href=""><i class="fa fa-search"></i></a></li>--> |
| 139 | + </ul> |
| 140 | + |
| 141 | + </nav> |
| 142 | + |
| 143 | + </header> |
| 144 | + |
| 145 | + |
| 146 | + <div class="container-fluid" style="margin:30px"> |
| 147 | + |
| 148 | + |
| 149 | +<!-- Error messages for builds will be added here--> |
| 150 | + |
| 151 | + |
| 152 | +<div class="row"> |
| 153 | + <div class="col-md-12"> |
| 154 | + <h2>Singularity Hub Replication Analysis</h2> |
| 155 | + <br> |
| 156 | + </div> |
| 157 | +</div> |
| 158 | +<div class="row"> |
| 159 | + <div class="col-md-12"> |
| 160 | + <label class="radio-inline"><input type="radio" name="mode" value="cluster" checked> Dendrogram</label> |
| 161 | + <label class="radio-inline"><input type="radio" name="mode" value="tree"> Tree</label> |
| 162 | + </div> |
| 163 | +</div> |
| 164 | +<div class="row"> |
| 165 | + <div class="col-md-9" id="tree"> |
| 166 | + <!-- Visualization tree will be appended here--> |
| 167 | + <svg width="960" height="900"></svg> |
| 168 | + </div> |
| 169 | + <div class="col-md-3" id="files"> |
| 170 | + <h3>Containers <i style="color:#999" id="helpme" class='fa fa-question-circle'></i></h3> |
| 171 | + <div id="container_set"> |
| 172 | + <!-- Containers will be shown here--> |
| 173 | + </div> |
| 174 | + <div id="aboutit" style="display:none"> |
| 175 | + <p>This tree shows similarity of the latest container of each public collection, and unlike the live version in Singularity Hub, this version was generated using content hashes for a more robust metric. The other version (quick, generated on a nightly basis) has a <a href="https://github.com/singularityware/singularity-python/blob/master/singularity/analysis/compare.py#L134" target="_blank">metric</a> that is based on the number of common folder paths between the containers, so containers with similar software will be shown closer together in the tree. We use hierarchical clustering from <a href="https://github.com/singularityware/singularity-python/blob/master/singularity/views/trees.py#L236" target="_blank">scipy</a> to generate clusters, and <a href="https://d3js.org/" target="_blank">d3.js</a> to render the tree.</p> |
| 176 | + </div> |
| 177 | + </div> |
| 178 | +</div> |
| 179 | + |
| 180 | + </div> |
| 181 | + |
| 182 | + |
| 183 | + |
| 184 | + <script src="https://singularity-hub.org/static/js/jquery.min.js"></script> |
| 185 | + <script src="https://singularity-hub.org/static/js/jquery.easing.1.3.js"></script> |
| 186 | + <script src="https://singularity-hub.org/static/js/bootstrap.min.js"></script> |
| 187 | + <script src="https://singularity-hub.org/static/js/jquery.waypoints.min.js"></script> |
| 188 | + <script src="https://singularity-hub.org/static/js/main.js"></script> |
| 189 | + <script src="https://singularity-hub.org/static/js/bootstrap-checkbox-radio.js"></script> |
| 190 | + <script src="https://singularity-hub.org/static/js/bootstrap-notify.js"></script> |
| 191 | + <script src="https://singularity-hub.org/static/js/paper-dashboard.js"></script> |
| 192 | + |
| 193 | +<script src="https://singularity-hub.org/static/js/d3.v4.min.js"></script> |
| 194 | +<script> |
| 195 | + |
| 196 | +String.prototype.hashCode = function() { |
| 197 | + var hash = 0, i, chr, len; |
| 198 | + if (this.length === 0) return hash; |
| 199 | + for (i = 0, len = this.length; i < len; i++) { |
| 200 | + chr = this.charCodeAt(i); |
| 201 | + hash = ((hash << 5) - hash) + chr; |
| 202 | + hash |= 0; // Convert to 32bit integer |
| 203 | + } |
| 204 | + return hash; |
| 205 | +}; |
| 206 | + |
| 207 | +var svg = d3.select("svg"), |
| 208 | + width = +svg.attr("width"), |
| 209 | + height = +svg.attr("height"), |
| 210 | + g = svg.append("g").attr("transform", "translate(40,0)"); |
| 211 | + |
| 212 | +var tree = d3.tree() |
| 213 | + .size([height - 400, width - 160]); |
| 214 | + |
| 215 | +var cluster = d3.cluster() |
| 216 | + .size([height, width - 160]); |
| 217 | + |
| 218 | +var data = {{ graph | safe }} |
| 219 | + |
| 220 | +const root = d3.hierarchy(data); |
| 221 | +tree(root); |
| 222 | +cluster(root); |
| 223 | + |
| 224 | +var link = g.selectAll(".link") |
| 225 | + .data(root.descendants().slice(1)) |
| 226 | + .enter().append("path") |
| 227 | + .attr("class", "link") |
| 228 | + .attr("d", diagonal); |
| 229 | + |
| 230 | +var node = g.selectAll(".node") |
| 231 | + .data(root.descendants()) |
| 232 | + .enter().append("g") |
| 233 | + .attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); }) |
| 234 | + .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }) |
| 235 | + .style('cursor',function(d){ |
| 236 | + if (d.data.name.split('|||').length==1){ |
| 237 | + return 'pointer'; |
| 238 | + } else { |
| 239 | + return 'click'; |
| 240 | + } |
| 241 | + }) |
| 242 | + .on('mouseover',function(d){ |
| 243 | + if (d.data.name!="root") { |
| 244 | + var containers = d.data.name.split("|||") |
| 245 | + $("#" + d.data.name.hashCode()).show(); |
| 246 | + $("#container_set").text(""); |
| 247 | + var list = '<ul>' |
| 248 | + $.each(containers, function( index, container ) { |
| 249 | + list = list + "<li><a href='#'/>" + container +"</a></li>" |
| 250 | + }) |
| 251 | + list = list + "</ul>" |
| 252 | + $("#container_set").append($(list)) |
| 253 | + } |
| 254 | + }) |
| 255 | + .on('mouseout',function(d){ |
| 256 | + $("#" + d.data.name.hashCode()).hide(); |
| 257 | + }); |
| 258 | + |
| 259 | +node.append("circle") |
| 260 | + .attr("r", 5); |
| 261 | + |
| 262 | +node.append("text") |
| 263 | + .attr("dy", 3) |
| 264 | + .attr("id", function(d) { return d.data.name.hashCode() }) |
| 265 | + .attr("x", function(d) { return d.children ? -8 : 8; }) |
| 266 | + .style("text-anchor", function(d) { return d.children ? "end" : "start"; }) |
| 267 | + .style('display','none') |
| 268 | + .style('font-size',16) |
| 269 | + .text(function(d) { return d.data.name.split('|||').length; }); |
| 270 | + |
| 271 | +d3.selectAll("input") |
| 272 | + .on("change", changed); |
| 273 | + |
| 274 | +var timeout = setTimeout(function() { |
| 275 | + d3.select("input[value=\"tree\"]") |
| 276 | + .property("checked", true) |
| 277 | + .dispatch("change"); |
| 278 | +}, 1000); |
| 279 | + |
| 280 | +function changed() { |
| 281 | + timeout = clearTimeout(timeout); |
| 282 | + (this.value === "tree" ? tree : cluster)(root); |
| 283 | + var t = d3.transition().duration(750); |
| 284 | + node.transition(t).attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); |
| 285 | + link.transition(t).attr("d", diagonal); |
| 286 | +} |
| 287 | + |
| 288 | +function diagonal(d) { |
| 289 | + return "M" + d.y + "," + d.x |
| 290 | + + "C" + (d.parent.y + 100) + "," + d.x |
| 291 | + + " " + (d.parent.y + 100) + "," + d.parent.x |
| 292 | + + " " + d.parent.y + "," + d.parent.x; |
| 293 | +} |
| 294 | + |
| 295 | +$(document).ready(function(){ |
| 296 | + $("#helpme").click(function(){ |
| 297 | + $("#aboutit").toggle(); |
| 298 | + }) |
| 299 | + $('.icons').remove() |
| 300 | +}); |
| 301 | +</script> |
| 302 | + |
| 303 | + |
| 304 | +<style> |
| 305 | +.btn:active, .btn:hover { |
| 306 | + border: 1px solid transparent !important; |
| 307 | + border-color: #ccc !important; |
| 308 | + background: #CCC !important; |
| 309 | +} |
| 310 | +#tools { |
| 311 | + min-height:180px; |
| 312 | + margin-top:20px; |
| 313 | + background-color:#F7F7F7; |
| 314 | + padding:20px; |
| 315 | +} |
| 316 | +#containers { |
| 317 | + padding-bottom:20px; |
| 318 | +} |
| 319 | +#tool-toggle { |
| 320 | + padding-left: 10px; |
| 321 | + padding-top: 20px; |
| 322 | + max-height:20px; |
| 323 | +} |
| 324 | +#controls { |
| 325 | + margin-bottom:0px; |
| 326 | + padding-bottom:0px; |
| 327 | +} |
| 328 | +.container-button { |
| 329 | + width:300px; |
| 330 | + padding:10px; |
| 331 | + background:skyblue; |
| 332 | + color:white; |
| 333 | + font-weight:500; |
| 334 | +} |
| 335 | +.container-box { |
| 336 | + float:left; |
| 337 | +} |
| 338 | +#show_container_set { |
| 339 | + position: absolute; |
| 340 | + bottom:0px; |
| 341 | + left:0px; |
| 342 | + background: none !important; |
| 343 | + border:none !important; |
| 344 | + box-shadow:none !important; |
| 345 | +} |
| 346 | + |
| 347 | +#show_container_set:hover, |
| 348 | +#show_container_set:focus, |
| 349 | +#show_container_set:active, |
| 350 | + { |
| 351 | + border:none !important; |
| 352 | + box-shadow:none !important; |
| 353 | +} |
| 354 | +.notransition { |
| 355 | + -webkit-transition: none !important; |
| 356 | + -moz-transition: none !important; |
| 357 | + -o-transition: none !important; |
| 358 | + transition: none !important; |
| 359 | +} |
| 360 | +</style> |
| 361 | + |
| 362 | + |
| 363 | +<script> |
| 364 | +// Enable tooltips |
| 365 | +$('[data-toggle="tooltip"]').tooltip() |
| 366 | + |
| 367 | +</script> |
| 368 | + |
| 369 | + |
| 370 | + </body> |
| 371 | + |
| 372 | +</html> |
0 commit comments