blast_report.tpl.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  1. <?php
  2. /**
  3. * Display the results of a BLAST job execution
  4. */
  5. // Set ourselves up to do link-out if our blast database is configured to do so.
  6. $linkout = FALSE;
  7. if ($blast_job->blastdb->linkout->none === FALSE) {
  8. $linkout_type = $blast_job->blastdb->linkout->type;
  9. $linkout_regex = $blast_job->blastdb->linkout->regex;
  10. // Note that URL prefix is not required if linkout type is 'custom'
  11. if (isset($blast_job->blastdb->linkout->db_id->urlprefix) && !empty($blast_job->blastdb->linkout->db_id->urlprefix)) {
  12. $linkout_urlprefix = $blast_job->blastdb->linkout->db_id->urlprefix;
  13. }
  14. // Check that we can determine the linkout URL.
  15. // (ie: that the function specified to do so, exists).
  16. if (function_exists($blast_job->blastdb->linkout->url_function)) {
  17. $url_function = $blast_job->blastdb->linkout->url_function;
  18. $linkout = TRUE;
  19. }
  20. }
  21. // Handle no hits. This following array will hold the names of all query
  22. // sequences which didn't have any hits.
  23. $query_with_no_hits = array();
  24. // Furthermore, if no query sequences have hits we don't want to bother listing
  25. // them all but just want to give a single, all-include "No Results" message.
  26. $no_hits = TRUE;
  27. ?>
  28. <script type="text/javascript">
  29. // JQuery controlling display of the alignment information (hidden by default)
  30. $(document).ready(function(){
  31. // Hide the alignment rows in the table
  32. // (ie: all rows not labelled with the class "result-summary" which contains the tabular
  33. // summary of the hit)
  34. $("#blast_report tr:not(.result-summary)").hide();
  35. $("#blast_report tr:first-child").show();
  36. // When a results summary row is clicked then show the next row in the table
  37. // which should be corresponding the alignment information
  38. $("#blast_report tr.result-summary").click(function(){
  39. $(this).next("tr").toggle();
  40. $(this).find(".arrow").toggleClass("up");
  41. });
  42. });
  43. </script>
  44. <style>
  45. .no-hits-message {
  46. color: red;
  47. font-style: italic;
  48. }
  49. </style>
  50. <div class="blast-report">
  51. <div class="blast-job-info">
  52. <!-- Provide Information to the user about their blast job -->
  53. <div class="blast-job-info">
  54. <div class="blast-download-info"><strong>Download</strong>:
  55. <a href="<?php print '../../' . $blast_job->files->result->html; ?>">Alignment</a>,
  56. <a href="<?php print '../../' . $blast_job->files->result->tsv; ?>">Tab-Delimited</a>,
  57. <a href="<?php print '../../' . $blast_job->files->result->xml; ?>">XML</a>,
  58. <a href="<?php print '../../' . $blast_job->files->result->gff; ?>">GFF3</a>
  59. </div>
  60. <br />
  61. <div class="blast-query-info"><strong>Query Information</strong>:
  62. <?php print $blast_job->files->query;?></div>
  63. <div class="blast-target-info"><strong>Search Target</strong>:
  64. <?php print $blast_job->blastdb->db_name;?></div>
  65. <div class="blast-date-info"><strong>Submission Date</strong>:
  66. <?php print format_date($blast_job->date_submitted, 'medium');?></div>
  67. <div class="blast-cmd-info"><strong>BLAST Command executed</strong>:
  68. <?php print $blast_job->blast_cmd;?></div>
  69. <br />
  70. <div class="num-results">
  71. <strong>Number of Results</strong>: <?php print $num_results; ?>
  72. </div>
  73. </div>
  74. <?php
  75. if (variable_get('blast_ui_cvitjs_enabled', false)
  76. && isset($blast_job->blastdb->cvitjs_enabled)
  77. && $blast_job->blastdb->cvitjs_enabled == '1') {
  78. $cvitjs_location = variable_get('blast_ui_cvitjs_location', '');
  79. ?>
  80. <!-- CViTjs image of BLAST hits, if enabled -->
  81. <div class="cvitjs">
  82. <div id="title-div"></div>
  83. <div id="cvit-div"></div>
  84. </div>
  85. <?php
  86. drupal_add_js(array(
  87. 'blast_ui'=> array(
  88. 'dataset' => $blast_job->blastdb->db_name)
  89. ),
  90. 'setting'
  91. );
  92. drupal_add_js(array(
  93. 'blast_ui'=> array(
  94. 'gff' => '../../' . $blast_job->files->result->gff)),'setting'
  95. );
  96. $base = drupal_get_path('module','blast_ui')
  97. . DIRECTORY_SEPARATOR . $cvitjs_location
  98. . DIRECTORY_SEPARATOR . 'js'
  99. . DIRECTORY_SEPARATOR . 'lib' . DIRECTORY_SEPARATOR;
  100. drupal_add_css($base.'bootstrap/css/bootstrap.min.css',array('preprocess'=>FALSE));
  101. drupal_add_css($base.'hopscotch/css/hopscotch.min.css',array('preprocess'=>FALSE));
  102. drupal_add_css($base.'../../css/cvit.css',array('preprocess'=> FALSE));
  103. drupal_add_js($base.'require/require.js',array('group'=>'JS_LIBRARY','type'=>'file'));
  104. drupal_add_js($base.'require/blast_ui-config.js',array('group'=>'JS_THEME'));
  105. ?>
  106. <?php
  107. }
  108. ?>
  109. </div>
  110. <br />
  111. <div class="report-table">
  112. <?php
  113. /**
  114. * We are using the drupal table theme functionality to create this listing
  115. * @see theme_table() for additional documentation
  116. */
  117. if ($xml) {
  118. ?>
  119. <p>The following table summarizes the results of your BLAST.
  120. Click on a <em>triangle </em> on the left to see the alignment and a visualization of the hit,
  121. and click the <em>target name </em> to get more information about the target hit.</p>
  122. <?php
  123. // Specify the header of the table
  124. $header = array(
  125. 'arrow-col' => array('data' => '', 'class' => array('arrow-col')),
  126. 'number' => array('data' => '#', 'class' => array('number')),
  127. 'query' => array('data' => 'Query Name (Click for alignment & visualization)', 'class' => array('query')),
  128. 'hit' => array('data' => 'Target Name', 'class' => array('hit')),
  129. 'evalue' => array('data' => 'E-Value', 'class' => array('evalue')),
  130. );
  131. $rows = array();
  132. $count = 0;
  133. // Parse the BLAST XML to generate the rows of the table
  134. // where each hit results in two rows in the table: 1) A summary of the query/hit and
  135. // significance and 2) additional information including the alignment
  136. foreach ($xml->{'BlastOutput_iterations'}->children() as $iteration) {
  137. $children_count = $iteration->{'Iteration_hits'}->children()->count();
  138. // Save some information needed for the hit visualization.
  139. $target_name = '';
  140. $q_name = $xml->{'BlastOutput_query-def'};
  141. $query_size = $xml->{'BlastOutput_query-len'};
  142. $target_size = $iteration->{'Iteration_stat'}->{'Statistics'}->{'Statistics_db-len'};
  143. if ($children_count != 0) {
  144. foreach ($iteration->{'Iteration_hits'}->children() as $hit) {
  145. if (is_object($hit)) {
  146. $count +=1;
  147. $zebra_class = ($count % 2 == 0) ? 'even' : 'odd';
  148. $no_hits = FALSE;
  149. // SUMMARY ROW
  150. // -- Save additional information needed for the summary.
  151. $score = (float) $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_score'};
  152. $evalue = (float) $hit->{'Hit_hsps'}->{'Hsp'}->{'Hsp_evalue'};
  153. $query_name = (string) $iteration->{'Iteration_query-def'};
  154. // If the id is of the form gnl|BL_ORD_ID|### then the parseids flag
  155. // to makeblastdb did a really poor job. In thhis case we want to use
  156. // the def to provide the original FASTA header.
  157. // @todo Deepak changed this to use just the hit_def; inquire as to why.
  158. $hit_name = (preg_match('/BL_ORD_ID/', $hit->{'Hit_id'})) ? $hit->{'Hit_def'} : $hit->{'Hit_id'};
  159. // Used for the hit visualization to ensure the name isn't truncated.
  160. $hit_name_short = (preg_match('/^([^\s]+)/', $hit_name, $matches)) ? $matches[1] : $hit_name;
  161. // Round e-value to two decimal values.
  162. $rounded_evalue = '';
  163. if (strpos($evalue,'e') != false) {
  164. $evalue_split = explode('e', $evalue);
  165. $rounded_evalue = round($evalue_split[0], 2, PHP_ROUND_HALF_EVEN);
  166. $rounded_evalue .= 'e' . $evalue_split[1];
  167. }
  168. else {
  169. $rounded_evalue = $evalue;
  170. }
  171. // State what should be in the summary row for theme_table() later.
  172. $summary_row = array(
  173. 'data' => array(
  174. 'arrow-col' => array('data' => '<div class="arrow"></div>', 'class' => array('arrow-col')),
  175. 'number' => array('data' => $count, 'class' => array('number')),
  176. 'query' => array('data' => $query_name, 'class' => array('query')),
  177. 'hit' => array('data' => $hit_name, 'class' => array('hit')),
  178. 'evalue' => array('data' => $rounded_evalue, 'class' => array('evalue')),
  179. ),
  180. 'class' => array('result-summary')
  181. );
  182. // ALIGNMENT ROW (collapsed by default)
  183. // Process HSPs
  184. $HSPs = array();
  185. // We need to save some additional summary information in order to draw the
  186. // hit visualization. First, initialize some variables...
  187. $track_start = INF;
  188. $track_end = -1;
  189. $hsps_range = '';
  190. $hit_hsps = '';
  191. $hit_hsp_score = '';
  192. $target_size = $hit->{'Hit_len'};
  193. $Hsp_bit_score = '';
  194. // Then for each hit hsp, keep track of the start of first hsp and the end of
  195. // the last hsp. Keep in mind that hsps might not be recorded in order.
  196. foreach ($hit->{'Hit_hsps'}->children() as $hsp_xml) {
  197. $HSPs[] = (array) $hsp_xml;
  198. if ($track_start > $hsp_xml->{'Hsp_hit-from'}) {
  199. $track_start = $hsp_xml->{'Hsp_hit-from'} . "";
  200. }
  201. if ($track_end < $hsp_xml->{'Hsp_hit-to'}) {
  202. $track_end = $hsp_xml->{'Hsp_hit-to'} . "";
  203. }
  204. // The BLAST visualization code requires the hsps to be formatted in a
  205. // very specific manner. Here we build up the strings to be submitted.
  206. // hits=4263001_4262263_1_742;4260037_4259524_895_1411;&scores=722;473;
  207. $hit_hsps .= $hsp_xml->{'Hsp_hit-from'} . '_' .
  208. $hsp_xml->{'Hsp_hit-to'} . '_' .
  209. $hsp_xml->{'Hsp_query-from'} . '_' . $hsp_xml->{'Hsp_query-to'} .
  210. ';';
  211. $Hsp_bit_score .= $hsp_xml->{'Hsp_bit-score'} .';';
  212. }
  213. // Finally record the range.
  214. // @todo figure out why we arbitrarily subtract 50,000 here...
  215. // @more removing the 50,000 and using track start/end appears to cause no change...
  216. $range_start = (int) $track_start;// - 50000;
  217. $range_end = (int) $track_end;// + 50000;
  218. if ($range_start < 1) $range_start = 1;
  219. // Call the function to generate the hit image.
  220. $hit_img = generate_blast_hit_image($target_name, $Hsp_bit_score, $hit_hsps,
  221. $target_size, $query_size, $q_name, $hit_name_short);
  222. // State what should be in the alignment row for theme_table() later.
  223. $alignment_row = array(
  224. 'data' => array(
  225. 'arrow' => array(
  226. 'data' => theme('blast_report_alignment_row', array('HSPs' => $HSPs, 'hit_visualization' => $hit_img)),
  227. 'colspan' => 5,
  228. ),
  229. ),
  230. 'class' => array('alignment-row', $zebra_class),
  231. 'no_striping' => TRUE
  232. );
  233. // LINK-OUTS.
  234. // It was determined above whether link-outs were supported for the
  235. // tripal blast database used as a search target. Thus we only want to
  236. // determine a link-out if it's actually supported... ;-)
  237. if ($linkout) {
  238. // First extract the linkout text using the regex provided through
  239. // the Tripal blast database node.
  240. if (preg_match($linkout_regex, $hit_name, $linkout_match)) {
  241. $hit->{'linkout_id'} = $linkout_match[1];
  242. $hit->{'hit_name'} = $hit_name;
  243. // Allow custom functions to determine the URL to support more complicated
  244. // link-outs rather than just using the tripal database prefix.
  245. $hit_name = call_user_func(
  246. $url_function,
  247. $linkout_urlprefix,
  248. $hit,
  249. array(
  250. 'query_name' => $query_name,
  251. 'score' => $score,
  252. 'e-value' => $evalue,
  253. 'HSPs' => $HSPs,
  254. 'Target' => $blast_job->blastdb->db_name,
  255. )
  256. );
  257. }
  258. // Replace the target name with the link.
  259. $summary_row['data']['hit']['data'] = $hit_name;
  260. }
  261. // ADD TO TABLE ROWS
  262. $rows[] = $summary_row;
  263. $rows[] = $alignment_row;
  264. }//end of if - checks $hit
  265. }//end of foreach - iteration_hits
  266. }//end of if - check for iteration_hits
  267. else {
  268. // Currently where the "no results" is added.
  269. $query_name = $iteration->{'Iteration_query-def'};
  270. $query_with_no_hits[] = $query_name;
  271. }//no results
  272. }//end of foreach - BlastOutput_iterations
  273. if ($no_hits) {
  274. print '<p class="no-hits-message">No results found.</p>';
  275. }
  276. else {
  277. // We want to warn the user if some of their query sequences had no hits.
  278. if (!empty($query_with_no_hits)) {
  279. print '<p class="no-hits-message">Some of your query sequences did not '
  280. . 'match to the database/template. They are: '
  281. . implode(', ', $query_with_no_hits) . '.</p>';
  282. }
  283. // Actually print the table.
  284. if (!empty($rows)) {
  285. print theme('table', array(
  286. 'header' => $header,
  287. 'rows' => $rows,
  288. 'attributes' => array('id' => 'blast_report'),
  289. 'sticky' => FALSE
  290. ));
  291. }
  292. }//handle no hits
  293. }//XML exists
  294. elseif ($too_many_results) {
  295. print '<div class="messages error">Your BLAST resulted in '. number_format(floatval($num_results)) .' results which is too many to reasonably display. We have provided the result files for Download at the top of this page; however, we suggest you re-submit your query using a more stringent e-value (i.e. a smaller number).</div>';
  296. }
  297. else {
  298. drupal_set_title('BLAST: Error Encountered');
  299. print '<div class="messages error">We encountered an error and are unable to load your BLAST results.</div>';
  300. }
  301. ?>
  302. <p><?php print l(
  303. 'Edit this query and re-submit',
  304. $blast_form_url,
  305. array('query' => array('resubmit' => blast_ui_make_secret($job_id))));
  306. ?></p>
  307. </div>
  308. <?php print theme('blast_recent_jobs', array()); ?>