changeset 10:2fbdf2eb27b4

All data is displayed now, still some formatting to do
author Jan Kanis <jan.code@jankanis.nl>
date Fri, 09 May 2014 18:16:48 +0200
parents bbdc8fb0de2b
children 7660519f2dc9
files visualise.html.jinja visualise.py
diffstat 2 files changed, 202 insertions(+), 23 deletions(-) [+]
line wrap: on
line diff
--- a/visualise.html.jinja	Thu May 08 18:59:32 2014 +0200
+++ b/visualise.html.jinja	Fri May 09 18:16:48 2014 +0200
@@ -60,7 +60,7 @@
       .white {
       background-color: white;
       border: 1px solid #cccccc;
-      padding: 1em;
+      padding: 1.5em 2%;
       }
 
       .graphicrow {
@@ -98,7 +98,7 @@
       padding-right: .2em;
       max-width: 50em;
       text-align: left;
-      height: 4em;
+      height: 2.8em;
       overflow-y: hidden;
       }
 
@@ -151,6 +151,12 @@
       background-color: red;
       color: white;
       }
+      .scale .graphicitem:first-child div {
+      margin-left: 0px;
+      }
+      .scale .graphicitem:last-child div {
+      margin-right: 0px;
+      }
 
       a.matchresult {
       display: block;
@@ -164,22 +170,55 @@
       height: 4px;
       }
 
+      div#descriptions {
+      }
+      
       table.descriptiontable {
+      font-size: 85%;
+      border: 1px solid #97b0c8;
+      border-spacing: 0;
+      color: #222222;
+      line-height: 1.3em;
+      background-color: white;
+      }
+      table.descriptiontable col:first-child {
       width: 100%;
       }
+      table.descriptiontable tr:hover {
+      background-color: #D5DEE3;
+      }
+      table.descriptiontable th {
+      background-color: #F0F0F0;
+      background: linear-gradient(#FFFFFF, #F0F0F0);
+      border-bottom: 1px solid #D4DFE9;
+      border-right: 1px solid #CFCFCF;
+      border-left: 0px solid black;
+      border-top: 0px solid black;
+      }
       table.descriptiontable td {
       overflow: hidden;
-      white-space: nowrap;
+      text-align: center;
+      padding: .4em .8em;
       }
       table.descriptiontable td div {
       width: 1em;
       overflow: visible;
       white-space: nowrap;
+      text-align: left;
       }
 
     </style>
 
-    
+    <script type="text/javascript">
+      function toggle_visibility(id) {
+	 var e = document.getElementById(id);
+	 if(e.style.display == 'none')
+	    e.style.display = 'block';
+	 else
+	    e.style.display = 'none';
+      }
+    </script>
+
   </head>
 
   <body>
@@ -223,7 +262,7 @@
                     {% for s in queryscale %}
                     <div class=graphicitem
 			 style="width: {{s.width|safe}}%">
-		      <div style="{{'margin-left: 0px' if loop.first else 'margin-right: 0px' if loop.last}}">{{s.label|safe}}</div>
+		      <div>{{s.label|safe}}</div>
 		    </div>
                     {% endfor %}
 		</div>
@@ -250,11 +289,14 @@
         </div>
       </div>
 
+
+      
       <div id=descriptions>
         <h2>Descriptions</h2>
 
         <div class=grey><div class=white>
             <h4 class=blackHeader>Sequences producing significant alignments:</h4>
+
             <table class=descriptiontable>
 	      <col/><col/><col/><col/><col/><col/><col/>
               <tr>
@@ -268,7 +310,11 @@
               </tr>
               {% for hit in hit_info %}
               <tr>
-                <td style="width: 100%"><div>{{hit.description}}</div></td>
+                <td><div><a href="#hit{{hit.link_id}}"
+			    title="{{hit.title}}"
+			    name=description{{hit.link_id}}>
+		      {{hit.title}}
+		</a></div></td>
                 <td>{{hit.maxscore}}</td>
                 <td>{{hit.totalscore}}</td>
                 <td>{{hit.cover}}</td>
@@ -280,7 +326,78 @@
             </table>
             
         </div></div>
+      </div>
 
+
+      
+      <div id=alignments>
+	<h2>Alignments</h2>
+	
+	<div class=grey><div class=white>
+	    {% for hit in hits %}
+	    <div class=alignment id=hit{{hit.Hit_num}}>
+	      
+	      <div class=linkheader>
+		<a href="#description{{hit.Hit_num}}">Descriptions</a>
+	      </div>
+
+	      <div class=title>
+		<p class=hittitle>{{hit|firsttitle}}</p>
+		<p class=titleinfo>
+		  <span class=b>Sequence ID:</span> {{hit|seqid}}
+		  <span class=b>Length:</span> {{hit.Hit_len}}
+		  <span class=b>Number of Matches:</span> {{hit.Hit_hsps.Hsp|length}}
+		</p>
+	      </div>
+
+	      <a class=showmoretitles onclick="toggle_visibility('moretitles{{hit.Hit_num}}'); return false;" href=''>
+		Show {{hit|othertitles|length}} more title(s)
+	      </a>
+
+	      <div class=moretitles id=moretitles{{hit.Hit_num}}>
+		{% for title in hit|othertitles %}
+		<div class=title>
+		  <p class=hittitle>{{title.title}}</p>
+		  <p class=titleinfo>
+		    <span class=b>Sequence ID:</span> {{title.id}}
+		  </p>
+		</div>
+		{% endfor %}
+	      </div>
+
+	      {% for hsp in hit.Hit_hsps.Hsp %}
+	      <div class=hotspots>
+		<p>Range {{hsp.Hsp_num}}: {{hsp['Hsp_hit-from']}} to {{hsp['Hsp_hit-to']}}</p>
+
+		<table class=hotspotstable>
+		  <tr>
+		    <th>Score</th><th>Expect</th><th>Identities</th><th>Gaps</th><th>Strand</th>
+		  </tr>
+		  <tr>
+		    <td>{{hsp['Hsp_bit-score']|fmt('.1f')}} bits({{hsp.Hsp_score}})</td>
+		    <td>{{hsp.Hsp_evalue|fmt('.1f')}}</td>
+		    <td>{{ hsp.Hsp_identity }}/{{ hsp|len }}({{
+		      (hsp.Hsp_identity/hsp|len) |fmt('.0%') }})</td>
+		    <td>{{ hsp.Hsp_gaps }}/{{ hsp|len
+		      }}({{ (hsp.Hsp_gaps / hsp|len) | fmt('.0%') }})</td>
+		    <td>{{ hsp['Hsp_query-frame']|asframe }}/{{ hsp['Hsp_hit-frame']|asframe }}</td>
+
+		  </tr>
+		</table>
+
+		<pre>{{hsp|alignment_pre}}</pre>
+		
+	      </div>
+	      {% endfor %}
+	      
+	      <p>
+	      {{hit.Hit_id}}
+	      
+	      Hit {{hit.Hit_num}}
+	      </p>
+	    </div>
+	    {% endfor %}
+	</div></div>
       </div>
 
     </div>
--- a/visualise.py	Thu May 08 18:59:32 2014 +0200
+++ b/visualise.py	Fri May 09 18:16:48 2014 +0200
@@ -11,6 +11,21 @@
 import jinja2
 
 
+blast = objectify.parse('blast xml example1.xml').getroot()
+loader = jinja2.FileSystemLoader(searchpath='.')
+environment = jinja2.Environment(loader=loader, lstrip_blocks=True, trim_blocks=True, autoescape=True)
+
+def filter(func_or_name):
+    if isinstance(func_or_name, str):
+        def inner(func):
+            environment.filters[func_or_name] = func
+            return func
+        return inner
+    else:
+        environment.filters[func_or_name.__name__] = func_or_name
+        return func_or_name
+
+
 def color_idx(length):
     if length < 40:
         return 0
@@ -22,20 +37,66 @@
         return 3
     return 4
 
-
 colors = ['black', 'blue', 'green', 'magenta', 'red']
 
-blast = objectify.parse('blast xml example1.xml').getroot()
-loader = jinja2.FileSystemLoader(searchpath='.')
-environment = jinja2.Environment(loader=loader, lstrip_blocks=True, trim_blocks=True, autoescape=True)
 environment.filters['color'] = lambda length: match_colors[color_idx(length)]
 
+@filter
+def fmt(val, fmt):
+    return format(float(val), fmt)
+
+@filter
+def firsttitle(hit):
+    return hit.Hit_def.text.split('>')[0]
+
+@filter
+def othertitles(hit):
+    """Split a hit.Hit_def that contains multiple titles up, splitting out the hit ids from the titles."""
+    id_titles = hit.Hit_def.text.split('>')
+
+    titles = []
+    for t in id_titles[1:]:
+        fullid, title = t.split(' ', 1)
+        id = fullid.split('|', 2)[2]
+        titles.append(dict(id = id,
+                           fullid = fullid,
+                           title = title))
+    return titles
+
+@filter
+def hitid(hit):
+    return hit.Hit_id.text.split('|', 2)[1]
+
+@filter
+def seqid(hit):
+    return hit.Hit_id.text.split('|', 2)[2]
+
+@filter
+def alignment_pre(hsp):
+    return (
+        "Query   {:>7s}  {}  {}\n".format(hsp['Hsp_query-from'], hsp.Hsp_qseq, hsp['Hsp_query-to']) +
+        "        {:7s}  {}\n".format('', hsp.Hsp_midline) +
+        "Subject {:>7s}  {}  {}".format(hsp['Hsp_hit-from'], hsp.Hsp_hseq, hsp['Hsp_hit-to']))
+
+@filter('len')
+def hsplen(node):
+    return int(node['Hsp_align-len'])
+
+@filter
+def asframe(frame):
+    if frame == 1:
+        return 'Plus'
+    elif frame == -1:
+        return 'Minus'
+    raise Exception("frame should be either +1 or -1")
+
+
 query_length = int(blast["BlastOutput_query-len"])
 
 hits = blast.BlastOutput_iterations.Iteration.Iteration_hits.Hit
 # sort hits by longest hotspot first
 ordered_hits = sorted(hits,
-                      key=lambda h: max(hsp['Hsp_align-len'] for hsp in h.Hit_hsps.Hsp),
+                      key=lambda h: max(hsplen(hsp) for hsp in h.Hit_hsps.Hsp),
                       reverse=True)
 
 def match_colors():
@@ -48,12 +109,12 @@
     for hit in hits:
         # sort hotspots from short to long, so we can overwrite index colors of
         # short matches with those of long ones.
-        hotspots = sorted(hit.Hit_hsps.Hsp, key=lambda hsp: hsp['Hsp_align-len'])
+        hotspots = sorted(hit.Hit_hsps.Hsp, key=lambda hsp: hsplen(hsp))
         table = bytearray([255]) * query_length
         for hsp in hotspots:
             frm = hsp['Hsp_query-from'] - 1
             to = int(hsp['Hsp_query-to'])
-            table[frm:to] = repeat(color_idx(hsp['Hsp_align-len']), to - frm)
+            table[frm:to] = repeat(color_idx(hsplen(hsp)), to - frm)
 
         matches = []
         last = table[0]
@@ -67,7 +128,7 @@
             count = 1
         matches.append((count * percent_multiplier, colors[last] if last != 255 else 'none'))
 
-        yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=hit.Hit_def)
+        yield dict(colors=matches, link="#hit"+hit.Hit_num.text, defline=firsttitle(hit))
 
 
 def queryscale():
@@ -88,22 +149,23 @@
 
         cover = [False] * query_length
         for hsp in hsps:
-            cover[hsp['Hsp_query-from']-1 : int(hsp['Hsp_query-to'])] = repeat(True, int(hsp['Hsp_align-len']))
+            cover[hsp['Hsp_query-from']-1 : int(hsp['Hsp_query-to'])] = repeat(True, hsplen(hsp))
         cover_count = cover.count(True)
         
         def hsp_val(path):
             return (hsp[path] for hsp in hsps)
         
-        yield dict(description = hit.Hit_def,
-                   maxscore = max(hsp_val('Hsp_bit-score')),
-                   totalscore = sum(hsp_val('Hsp_bit-score')),
+        yield dict(title = firsttitle(hit),
+                   link_id = hit.Hit_num,
+                   maxscore = "{:.1f}".format(float(max(hsp_val('Hsp_bit-score')))),
+                   totalscore = "{:.1f}".format(float(sum(hsp_val('Hsp_bit-score')))),
                    cover = "{:.0%}".format(cover_count / query_length),
-                   e_value = min(hsp_val('Hsp_evalue')),
+                   e_value = "{:.4g}".format(float(min(hsp_val('Hsp_evalue')))),
                    # FIXME: is this the correct formula vv?
-                   ident = "{:.0%}".format(min(hsp.Hsp_identity / hsp['Hsp_align-len'] for hsp in hsps)),
+                   ident = "{:.0%}".format(float(min(hsp.Hsp_identity / hsplen(hsp) for hsp in hsps))),
                    accession = hit.Hit_accession)
-                   
-        
+
+
 def main():
     template = environment.get_template('visualise.html.jinja')
 
@@ -119,7 +181,7 @@
 
     sys.stdout.write(template.render(blast=blast,
                                      length=query_length,
-                                     #hits=blast.BlastOutput_iterations.Iteration.Iteration_hits.Hit,
+                                     hits=blast.BlastOutput_iterations.Iteration.Iteration_hits.Hit,
                                      colors=colors,
                                      match_colors=match_colors(),
                                      queryscale=queryscale(),