aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBonfaceKilz2020-12-03 23:59:14 +0300
committerBonfaceKilz2020-12-04 00:29:42 +0300
commit64bfeadac33e6d22297714544cd96ef16677fe16 (patch)
tree54fa5791dc43fc86aca16da7225e161fd8aa060b
parent66fb2fa6bf83b8d6e6d1cb7e159ea806bca4aebb (diff)
downloadgenenetwork2-64bfeadac33e6d22297714544cd96ef16677fe16.tar.gz
Display d3js chord dependency diagram of gn2 dependenices
* wqflask/wqflask/markdown_routes.py: Add new bs4 import. (references): Filter out javascript from the guix-generated d3js html file and pass it to the jinja template. * wqflask/wqflask/static/new/css/markdown.css: New styles for the graph content. * wqflask/wqflask/templates/environment.html: New graph content.
-rw-r--r--wqflask/wqflask/markdown_routes.py14
-rw-r--r--wqflask/wqflask/static/new/css/markdown.css14
-rw-r--r--wqflask/wqflask/templates/environment.html134
3 files changed, 162 insertions, 0 deletions
diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py
index 59a465e7..183f4caa 100644
--- a/wqflask/wqflask/markdown_routes.py
+++ b/wqflask/wqflask/markdown_routes.py
@@ -7,6 +7,8 @@ import markdown
import os
import sys
+from bs4 import BeautifulSoup
+
from flask import Blueprint
from flask import render_template
@@ -68,10 +70,21 @@ def references():
@environments_blueprint.route("/")
def environments():
+
md_file = get_file_from_python_search_path("wqflask/DEPENDENCIES.md")
+ svg_file = get_file_from_python_search_path(
+ "wqflask/dependency-graph.html")
+ svg_data = None
+ if svg_file:
+ with open(svg_file, 'r') as f:
+ svg_data = "".join(
+ BeautifulSoup(f.read(),
+ 'lxml').body.script.contents)
+
if md_file is not None:
return (
render_template("environment.html",
+ svg_data=svg_data,
rendered_markdown=render_markdown(
md_file,
is_remote_file=False)),
@@ -80,6 +93,7 @@ def environments():
# Fallback: Fetch file from server
return (render_template(
"environment.html",
+ svg_data=None,
rendered_markdown=render_markdown(
"general/environments/environments.md")),
200)
diff --git a/wqflask/wqflask/static/new/css/markdown.css b/wqflask/wqflask/static/new/css/markdown.css
index 0c0309fb..38d664e2 100644
--- a/wqflask/wqflask/static/new/css/markdown.css
+++ b/wqflask/wqflask/static/new/css/markdown.css
@@ -57,6 +57,20 @@
word-spacing: 0.2em;
}
+.graph-legend h1 {
+ text-align: center;
+}
+
+.graph-legend,
+#guix-graph {
+ width: 90%;
+ margin: 10px auto;
+}
+
+#guix-graph {
+ border: solid 2px black;
+}
+
#markdown table {
width: 100%;
}
diff --git a/wqflask/wqflask/templates/environment.html b/wqflask/wqflask/templates/environment.html
index cd30e768..5fe01dad 100644
--- a/wqflask/wqflask/templates/environment.html
+++ b/wqflask/wqflask/templates/environment.html
@@ -13,4 +13,138 @@
<div class="cls-table-style">{{ rendered_markdown|safe }} </div>
</div>
+{% if svg_data %}
+
+<div class="graph-legend">
+ <h1>Chord dependency Graph of Genenetwork2</h1>
+ Graph generated from <a href="http://git.genenetwork.org/guix-bioinformatics/guix-bioinformatics/src/branch/master/gn/packages/genenetwork.scm">genenetwork.scm</a>. You can zoom in and out within the bounding box.
+</div>
+
+<div id="guix-graph"></div>
+{% endif %}
+
+{% endblock %}
+
+{% block js %}
+
+{% if svg_data %}
+<script language="javascript" type="text/javascript" src="{{ url_for('js', filename='d3js/d3.min.js') }}"></script>
+<script type="text/javascript">
+ {{ svg_data|safe }}
+ // based on http://bl.ocks.org/mbostock/1046712 under GPLv3
+ // Adapted from: https://elephly.net/graph.html
+ var outerRadius = (nodeArray.length * 10) / 2,
+ innerRadius = outerRadius - 100,
+ width = outerRadius * 2,
+ height = outerRadius * 2,
+ colors = d3.scale.category20c(),
+ matrix = [];
+
+ function neighborsOf (node) {
+ return links.filter(function (e) {
+ return e.source === node;
+ }).map(function (e) {
+ return e.target;
+ });
+ }
+
+ function zoomed () {
+ zoomer.attr("transform",
+ "translate(" + d3.event.translate + ")" +
+ "scale(" + d3.event.scale + ")");
+ }
+
+ function fade (opacity, root) {
+ return function (g, i) {
+ root.selectAll("g path.chord")
+ .filter(function (d) {
+ return d.source.index != i && d.target.index != i;
+ })
+ .transition()
+ .style("opacity", opacity);
+ };
+ }
+
+ // Now that we have all nodes in an object we can replace each reference
+ // with the actual node object.
+ links.forEach(function (link) {
+ link.target = nodes[link.target];
+ link.source = nodes[link.source];
+ });
+
+ // Construct a square matrix for package dependencies
+ nodeArray.forEach(function (d, index, arr) {
+ var source = index,
+ row = matrix[source];
+ if (!row) {
+ row = matrix[source] = [];
+ for (var i = -1; ++i < arr.length;) row[i] = 0;
+ }
+ neighborsOf(d).forEach(function (d) { row[d.index]++; });
+ });
+
+ // chord layout
+ var chord = d3.layout.chord()
+ .padding(0.01)
+ .sortSubgroups(d3.descending)
+ .sortChords(d3.descending)
+ .matrix(matrix);
+
+ var arc = d3.svg.arc()
+ .innerRadius(innerRadius)
+ .outerRadius(innerRadius + 20);
+
+ var zoom = d3.behavior.zoom()
+ .scaleExtent([0.1, 10])
+ .on("zoom", zoomed);
+
+ var svg = d3.select("#guix-graph").append("svg")
+ .attr("width", "100%")
+ .attr("height", "100%")
+ .attr('viewBox','0 0 '+Math.min(width,height)+' '+Math.min(width,height))
+ .attr('preserveAspectRatio','xMinYMin')
+ .call(zoom);
+
+ var zoomer = svg.append("g");
+
+ var container = zoomer.append("g")
+ .attr("transform", "translate(" + outerRadius + "," + outerRadius + ")");
+
+ // Group for arcs and labels
+ var g = container.selectAll(".group")
+ .data(chord.groups)
+ .enter().append("g")
+ .attr("class", "group")
+ .on("mouseout", fade(1, container))
+ .on("mouseover", fade(0.1, container));
+
+ // Draw one segment per package
+ g.append("path")
+ .style("fill", function (d) { return colors(d.index); })
+ .style("stroke", function (d) { return colors(d.index); })
+ .attr("d", arc);
+
+ // Add circular labels
+ g.append("text")
+ .each(function (d) { d.angle = (d.startAngle + d.endAngle) / 2; })
+ .attr("dy", ".35em")
+ .attr("transform", function (d) {
+ return "rotate(" + (d.angle * 180 / Math.PI - 90) + ")"
+ + "translate(" + (innerRadius + 26) + ")"
+ + (d.angle > Math.PI ? "rotate(180)" : "");
+ })
+ .style("text-anchor", function (d) { return d.angle > Math.PI ? "end" : null; })
+ .text(function (d) { return nodeArray[d.index].label; });
+
+ // Draw chords from source to target; color by source.
+ container.selectAll(".chord")
+ .data(chord.chords)
+ .enter().append("path")
+ .attr("class", "chord")
+ .style("stroke", function (d) { return d3.rgb(colors(d.source.index)).darker(); })
+ .style("fill", function (d) { return colors(d.source.index); })
+ .attr("d", d3.svg.chord().radius(innerRadius));
+</script>
+{% endif %}
+
{% endblock %}