diff options
-rw-r--r-- | wqflask/wqflask/markdown_routes.py | 55 | ||||
-rw-r--r-- | wqflask/wqflask/static/new/css/markdown.css | 41 | ||||
-rw-r--r-- | wqflask/wqflask/templates/environment.html | 152 |
3 files changed, 221 insertions, 27 deletions
diff --git a/wqflask/wqflask/markdown_routes.py b/wqflask/wqflask/markdown_routes.py index 601845d7..183f4caa 100644 --- a/wqflask/wqflask/markdown_routes.py +++ b/wqflask/wqflask/markdown_routes.py @@ -4,6 +4,10 @@ Render pages from github, or if they are unavailable, look for it else where """ import requests import markdown +import os +import sys + +from bs4 import BeautifulSoup from flask import Blueprint from flask import render_template @@ -16,18 +20,25 @@ policies_blueprint = Blueprint("policies_blueprint", __name__) facilities_blueprint = Blueprint("facilities_blueprint", __name__) -def render_markdown(file_name): +def render_markdown(file_name, is_remote_file=True): """Try to fetch the file name from Github and if that fails, try to look for it inside the file system """ github_url = ("https://raw.githubusercontent.com/" "genenetwork/gn-docs/master/") + if not is_remote_file: + text = "" + with open(file_name, "r", encoding="utf-8") as input_file: + text = input_file.read() + return markdown.markdown(text, + extensions=['tables']) + md_content = requests.get(f"{github_url}{file_name}") + if md_content.status_code == 200: + return markdown.markdown(md_content.content.decode("utf-8"), + extensions=['tables']) - return markdown.markdown(md_content.content.decode("utf-8"), extensions=['tables']) - # TODO: Add fallback on our git server by checking the mirror. - # Content not available return (f"\nContent for {file_name} not available. " "Please check " "(here to see where content exists)" @@ -35,6 +46,14 @@ look for it inside the file system """ "Please reach out to the gn2 team to have a look at this") +def get_file_from_python_search_path(pathname_suffix): + cands = [os.path.join(d, pathname_suffix) for d in sys.path] + try: + return list(filter(os.path.exists, cands))[0] + except IndexError: + return None + + @glossary_blueprint.route('/') def glossary(): return render_template( @@ -51,7 +70,33 @@ def references(): @environments_blueprint.route("/") def environments(): - return render_template("environment.html", rendered_markdown=render_markdown("general/environments/environments.md")), 200 + + 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)), + 200 + ) + # Fallback: Fetch file from server + return (render_template( + "environment.html", + svg_data=None, + rendered_markdown=render_markdown( + "general/environments/environments.md")), + 200) @links_blueprint.route("/") diff --git a/wqflask/wqflask/static/new/css/markdown.css b/wqflask/wqflask/static/new/css/markdown.css index dca3e31d..38d664e2 100644 --- a/wqflask/wqflask/static/new/css/markdown.css +++ b/wqflask/wqflask/static/new/css/markdown.css @@ -57,8 +57,47 @@ 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%; +} + +#markdown td { + padding: 1em; + text-align: left; +} + +#markdown th { + text-align: center; +} + +#markdown table, +#markdown td, +#markdown th { + border: solid 2px black; +} + +#markdown td, +#markdown th { + padding-top: 8px; + padding-bottom: 8px; +} + @media(max-width:650px) { .container { width: 100vw; } -}
\ No newline at end of file +} diff --git a/wqflask/wqflask/templates/environment.html b/wqflask/wqflask/templates/environment.html index 94b31464..5fe01dad 100644 --- a/wqflask/wqflask/templates/environment.html +++ b/wqflask/wqflask/templates/environment.html @@ -8,33 +8,143 @@ {% block content %} - <div class="github-btn-container"> - <div class="github-btn "> - <a href="https://github.com/genenetwork/gn-docs"> - Edit Text - <img src="/static/images/edit.png"> - </a> - </div> -</div> <div id="markdown" class="container"> <div class="cls-table-style">{{ rendered_markdown|safe }} </div> </div> -<style type="text/css"> -table { - width: 100%; -} -table, th, td { - border: 1px solid black; +{% 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; }); -td,th{ - padding-top:8px; - padding-bottom: 8px; - text-align: center; -} + // 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 %} -</style> {% endblock %} |