blast_report.tpl.php 15 KB

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