diff --git a/planet/controllers/expression_network.py b/planet/controllers/expression_network.py index 9ce26f9..0c7c7aa 100644 --- a/planet/controllers/expression_network.py +++ b/planet/controllers/expression_network.py @@ -1,39 +1,51 @@ -from flask import Blueprint, redirect, url_for, render_template, Response -from sqlalchemy.sql import or_, and_ +from flask import Blueprint, url_for, render_template, flash, redirect +from sqlalchemy.sql import and_ from planet.models.expression_networks import ExpressionNetworkMethod, ExpressionNetwork import json + expression_network = Blueprint('expression_network', __name__) + @expression_network.route('/') def expression_network_overview(): + """ + Overview of all networks in the current database with basic information + """ networks = ExpressionNetworkMethod.query.all() return render_template("expression_network.html", networks=networks) -def __process_link(link): - output = {} - if link["gene_id"] is not None: - output = {"data": {"id": link["probe_name"], - "name": link["probe_name"], - "gene_link": url_for('sequence.sequence_view', sequence_id=link["gene_id"]), - "gene_name": link["gene_name"], - "node_type": "linked"}} - else: - output = {"data": {"id": link["probe_name"], - "name": link["probe_name"], - "gene_link": url_for('sequence.sequence_view', sequence_id=""), - "gene_name": link["gene_name"], - "node_type": "linked"}} +@expression_network.route('/view/') +@expression_network.route('/view//') +def expression_network_view(node_id, depth=0): + """ + Page that displays the network graph for a specific network's probe, the depth indicates how many steps away from + the query gene the network is retrieved. For performance reasons depths > 2 are not allowed + + :param node_id: id of the network's probe (the query) to visualize + :param depth: How many steps to include, 0 only the query and the direct neighborhood, 1 a step further, ... + """ + if depth > 2: + flash("Depth cannot be larger than 2. Showing the network with depth 2", "warning") + return redirect(url_for('expression_network.expression_network_view', node_id=node_id, depth=2)) + + node = ExpressionNetwork.query.get(node_id) + return render_template("expression_graph.html", node=node, depth=depth) - return output @expression_network.route('/json/') @expression_network.route('/json//') def expression_network_json(node_id, depth=0): + """ + Generates JSON output compatible with cytoscape.js (see planet/static/planet_graph.js for details how to render) + + :param node_id: id of the network's probe (the query) to visualize + :param depth: How many steps to include, 0 only the query and the direct neighborhood, 1 a step further, ... + """ node = ExpressionNetwork.query.get(node_id) links = json.loads(node.network) @@ -44,7 +56,8 @@ def expression_network_json(node_id, depth=0): "name": node.probe, "gene_link": url_for('sequence.sequence_view', sequence_id=node.gene_id), "gene_name": node.gene.name, - "node_type": "query"}}] + "node_type": "query", + "color": "#888"}}] edges = [] # lists necessary for doing deeper searches @@ -53,9 +66,14 @@ def expression_network_json(node_id, depth=0): existing_nodes = [node.probe] # add direct neighbors of the gene of interest + for link in links: nodes.append(__process_link(link)) - edges.append({"data": {"source": node.probe, "target": link["probe_name"], "depth": 0}}) + edges.append({"data": {"source": node.probe, + "target": link["probe_name"], + "depth": 0, + "link_score": link["link_score"], + "color": "#CCC"}}) additional_nodes.append(link["probe_name"]) existing_edges.append([node.probe, link["probe_name"]]) existing_edges.append([link["probe_name"], node.probe]) @@ -78,7 +96,11 @@ def expression_network_json(node_id, depth=0): next_nodes.append(link["probe_name"]) if [new_node.probe, link["probe_name"]] not in existing_edges: - edges.append({"data": {"source": new_node.probe, "target": link["probe_name"], "depth": i}}) + edges.append({"data": {"source": new_node.probe, + "target": link["probe_name"], + "depth": i, + "link_score": link["link_score"], + "color": "#CCC"}}) existing_edges.append([new_node.probe, link["probe_name"]]) existing_edges.append([link["probe_name"], new_node.probe]) @@ -92,15 +114,38 @@ def expression_network_json(node_id, depth=0): for link in new_links: if link["probe_name"] in existing_nodes: if [new_node.probe, link["probe_name"]] not in existing_edges: - edges.append({"data": {"source": new_node.probe, "target": link["probe_name"], "depth": depth+1}}) + edges.append({"data": {"source": new_node.probe, + "target": link["probe_name"], + "depth": depth+1, + "link_score": link["link_score"], + "color": "#CCC"}}) existing_edges.append([new_node.probe, link["probe_name"]]) existing_edges.append([link["probe_name"], new_node.probe]) return json.dumps({"nodes": nodes, "edges": edges}) -@expression_network.route('/view/') -@expression_network.route('/view//') -def expression_network_view(node_id, depth=0): - node = ExpressionNetwork.query.get(node_id) - return render_template("expression_graph.html", node=node, depth=depth) +def __process_link(linked_probe): + """ + Internal function that processes a linked probe (from the ExpressionNetwork.network field) to a data entry + compatible with cytoscape.js + + :param linked_probe: hash with information from ExpressionNetwork.network field + :return: a hash formatted for use as a node with cytoscape.js + """ + if linked_probe["gene_id"] is not None: + return {"data": {"id": linked_probe["probe_name"], + "name": linked_probe["probe_name"], + "gene_link": url_for('sequence.sequence_view', sequence_id=linked_probe["gene_id"]), + "gene_name": linked_probe["gene_name"], + "node_type": "linked", + "color": "#888"}} + else: + return {"data": {"id": linked_probe["probe_name"], + "name": linked_probe["probe_name"], + "gene_link": url_for('sequence.sequence_view', sequence_id=""), + "gene_name": linked_probe["gene_name"], + "node_type": "linked", + "color": "#888"}} + + diff --git a/planet/static/js/planet_graph.js b/planet/static/js/planet_graph.js index 02e28dc..c6438bc 100644 --- a/planet/static/js/planet_graph.js +++ b/planet/static/js/planet_graph.js @@ -8,7 +8,8 @@ $('#cy').cytoscape({ .css({ 'content': 'data(gene_name)', 'text-valign': 'center', - 'color': 'white', + 'color': '#FFF', + 'background-color': 'data(color)', 'text-outline-width': 1, 'text-outline-color': '#888' }) @@ -22,6 +23,8 @@ $('#cy').cytoscape({ .css({ 'curve-style': 'haystack', 'opacity': 0.75, + 'width': 'mapData(depth, 4, 0, 1, 5)', + 'line-color': 'data(color)' }) .selector(':selected') .css({ @@ -38,16 +41,15 @@ $('#cy').cytoscape({ padding: 20 }, - // on graph initial layout done (could be async depending on layout...) + ready: function(){ window.cy = this; - - // giddy up... cy.nodes().forEach(function(n){ var g = n.data('gene_link'); + // code to add tooltips to the selected node n.qtip({ content: [ { diff --git a/utils/__init__.py b/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/utils/color.py b/utils/color.py new file mode 100644 index 0000000..e45300e --- /dev/null +++ b/utils/color.py @@ -0,0 +1,11 @@ +""" +A set of functions designed to work with colors for websites +""" +import hashlib + + +def string_to_hex_color(input_string): + hashed_string = hashlib.sha1(str(input_string).encode('utf-8')).hexdigest() + color = "#" + hashed_string[0:3].upper() + + return color