File Coverage

blib/lib/Bio/GMOD/Blast/Graph.pm
Criterion Covered Total %
statement 8 8 100.0
branch n/a
condition n/a
subroutine 4 4 100.0
pod n/a
total 12 12 100.0


line stmt bran cond sub pod time code
1             package Bio::GMOD::Blast::Graph;
2             BEGIN {
3 2     2   570109 $Bio::GMOD::Blast::Graph::AUTHORITY = 'cpan:RBUELS';
4             }
5             BEGIN {
6 2     2   45 $Bio::GMOD::Blast::Graph::VERSION = '0.06';
7             }
8             # ABSTRACT: display a graphical summary of a BLAST report
9              
10              
11 2     2   18 use strict;
  2         4  
  2         178  
12              
13 2     2   11 use base 'Bio::Root::IO';
  2         3  
  2         3314  
14             use Bio::SearchIO;
15             use GD;
16              
17             use File::Spec;
18              
19             use Bio::GMOD::Blast::Graph::IntSpan;
20             use Bio::GMOD::Blast::Graph::MyDebug qw( dmsg dmsgs assert );
21             use Bio::GMOD::Blast::Graph::MyMath qw( round max );
22             use Bio::GMOD::Blast::Graph::MyUtils;
23             use Bio::GMOD::Blast::Graph::HitWrapper;
24             use Bio::GMOD::Blast::Graph::WrapPartitionsFixed;
25             use Bio::GMOD::Blast::Graph::WrapList;
26             use Bio::GMOD::Blast::Graph::MapSpace;
27             use Bio::GMOD::Blast::Graph::MapUtils;
28             use Bio::GMOD::Blast::Graph::MapDefs
29             qw( $imgWidth $imgHeight $fontWidth $fontHeight $imgTopBorder
30             $imgBottomBorder $imgLeftBorder $imgRightBorder $namesHorizBorder
31             $imgHorizBorder $imgVertBorder $arrowHeight $halfArrowHeight
32             $arrowWidth $halfArrowWidth $hspPosInit $hspArrowPad $hspHeight
33             $formFieldWidth $tickHeight $bottomDataOffset $topDataOffset
34             $kNumberOfPartitions $bucketBest $bucketZeroMax $bucketOneMax
35             $bucketTwoMax $bucketThreeMax $bucketFourMax );
36              
37             our $ID = __PACKAGE__;
38              
39             ################################################################
40             sub new {
41             ################################################################
42              
43              
44             ##################################################################
45             my ($class, @args) = @_;
46              
47             my $self = $class->SUPER::new(@args);
48              
49             $self->_init(@args);
50              
51             $self->_parseFile;
52              
53             return $self;
54              
55             }
56              
57             ##################################################################
58             sub showGraph {
59             ##################################################################
60              
61              
62             ##################################################################
63             my ($self) = @_;
64              
65             $self->_createAndShowGraph;
66              
67             }
68              
69             ##################################################################
70             sub getImageFile {
71             ##################################################################
72              
73              
74             ##################################################################
75             my ($self) = @_;
76              
77             return $self->{'_dstDir'}.$self->{'_imgName'};
78              
79             }
80              
81             ##################################################################
82             sub hitNameArrayRef {
83             ##################################################################
84              
85              
86             ##################################################################
87             my ($self) = @_;
88              
89             return $self->{'_hitNameArrayRef'};
90              
91             }
92              
93             ##################################################################
94             sub _init {
95             ##################################################################
96             # This private method checks that all the required arguments
97             # have been provided and stores them within the object, and
98             # initializes variables for optional arguments if they are not
99             # provided.
100              
101             my ($self, @args) = @_;
102              
103             my ($outputFile, $format, $dstDir, $dstURL, $imgName,
104             $showNamesP, $db) =
105             $self->_rearrange([qw(OUTPUTFILE
106             FORMAT
107             DSTDIR
108             DSTURL
109             IMGNAME
110             SHOWNAMESP
111             DB)], @args);
112              
113             if (!$outputFile) {
114              
115             $self->throw("The search output file needs to be passed to '$ID' object.");
116              
117             }
118              
119             if (!$dstDir) {
120              
121             $self->throw("A tmp directory for storing the image file needs to be passed to '$ID' object.");
122              
123             }
124              
125             if (!$dstURL) {
126              
127             $self->throw("The root URL for the image file needs to be passed to '$ID' object.");
128              
129             }
130              
131             $self->{'_outputFile'} = $outputFile;
132              
133             $self->{'_format'} = $format || 'blast';
134              
135             # if ($format !~ /^(blast|fasta|HMMER)/i) {
136              
137             # $self->throw("The format must be [blast|fasta|HMMER]");
138              
139             # }
140              
141             $self->{'_dstDir'} = $dstDir;
142              
143             $self->{'_dstURL'} = $dstURL;
144              
145             $self->{'_imgName'} = $imgName || "$$.50.png";
146              
147             $self->{'_showNamesP'} = $showNamesP;
148              
149             $self->{'_mapName'} = 'imap';
150              
151             $self->{'_formFieldWidth'} = 100;
152              
153             $self->{'_debugCount'} = 0;
154              
155             $self->{'_tickList'} = [];
156              
157             $self->{'_mapUtils'} = new Bio::GMOD::Blast::Graph::MapUtils($showNamesP);
158              
159              
160             }
161              
162             ###################################################################
163             sub _parseFile {
164             ###################################################################
165             # This private method parses the search output file by simply
166             # calling the Bioperl SearchIO module and stores the hits in
167             # the hit list.
168              
169             my ($self) = @_;
170              
171             my $searchio = Bio::SearchIO->new(-file=>$self->{'_outputFile'},
172             -format=>$self->{'_format'});
173              
174             my $wrapList = new Bio::GMOD::Blast::Graph::WrapList();
175              
176             my @hitName;
177              
178             while (my $result = $searchio->next_result) {
179              
180             $self->{'_srcLength'} = $result->query_length;
181              
182             if (!scalar$result->hits()) {
183             $self->_print( "<p>Sorry, no hits found for your query sequence.</p>" );
184             return;
185             }
186              
187             while (my $hit = $result->next_hit()) {
188              
189              
190             push(@hitName, $hit->name);
191              
192             # if ($hit->name =~ /^ORF[NP]:(.+)$/) {
193              
194             # push(@orf, $1);
195              
196             # }
197              
198             my $wrap = new Bio::GMOD::Blast::Graph::HitWrapper( $hit );
199              
200             # dmsg( "adding", $wrap->toString(), $wrap->getPExponent() );
201              
202             $wrapList->addElement( $wrap );
203              
204             }
205              
206             # we want to scale everything to fit in the Mapic.
207             # convert from length to pixels. length * (pixels/length) = pixels.
208             $self->{'_horizRatio'} =
209             $self->{'_mapUtils'}->getQueryWidth()/$self->{'_srcLength'};
210              
211             }
212              
213             $self->{'_hitNameArrayRef'} = \@hitName;
214              
215             $wrapList->sortByPValue();
216              
217             undef $searchio;
218              
219             # remember how many hits we had
220             # so that we can report how many
221             # we don't show.
222             $self->{'_hitCount'} = $wrapList->getCount();
223              
224             $self->{'_parts'} =
225             new Bio::GMOD::Blast::Graph::WrapPartitionsFixed( $wrapList );
226              
227             $self->{'_parts'}->reduce();
228              
229             $self->{'_hitCountBefore'} = $self->{'_hitCount'};
230              
231             $self->{'_hitCountAfter'} =
232             $self->{'_parts'}->getPartitionElementsCountAfter();
233              
234             if( $self->{'_hitCountAfter'} == $self->{'_hitCountBefore'} ) {
235              
236             $self->{'_allShowingP'} = 1;
237              
238             }
239             else {
240              
241             $self->{'_allShowingP'} = 0;
242              
243             }
244              
245             }
246              
247             #######################################################################
248             sub _createAndShowGraph {
249             #######################################################################
250             # This is a wrapper method which simply calls each private method to
251             # do the job.
252              
253             my ($self) = @_;
254              
255             $self->_countHTML($self->{'_hitCountAfter'},
256             $self->{'_hitCountBefore'});
257              
258             $self->_initGD;
259              
260             $self->_writeIMapStart;
261              
262             $self->_drawQuery;
263              
264             $self->_drawGraph;
265              
266             $self->_drawKey;
267              
268             $self->_drawStamp;
269              
270             $self->_writeImage;
271              
272             $self->_writeIMapEnd;
273              
274             }
275              
276             ###################################################################
277             sub _initGD {
278             ###################################################################
279             # This private method initializes the GD object and colors, and
280             # draws a frame around the map.
281              
282             my ($self) = @_;
283              
284             my $annotationWidth =
285             $self->{'_parts'}->getMaxAnnotationWidthForFont($fontWidth);
286              
287             $self->{'_mapUtils'}->putNamesHorizBorder($annotationWidth+10);
288              
289             $self->{'_realWidth'} = $self->{'_mapUtils'}->getImgWidth();
290              
291             $self->{'_realHeight'} = $self->{'_parts'}->getHeight() + $imgVertBorder;
292              
293             my $img = new GD::Image($self->{'_realWidth'},
294             $self->{'_realHeight'});
295              
296             $img->interlaced('true');
297              
298             $self->{'_white'} = $img->colorAllocate( 255, 255, 255 );
299             $self->{'_black'} = $img->colorAllocate( 0, 0, 0 );
300             $self->{'_grayLight'} = $img->colorAllocate( 204, 204, 204 );
301             $self->{'_gray'} = $img->colorAllocate( 153, 153, 153 );
302             $self->{'_grayDark'} = $img->colorAllocate( 102, 102, 102 );
303             $self->{'_debugColor'} = $img->colorAllocate( 0, 204, 0 );
304             $self->{'_bgColor2'} = $self->{'_white'};
305             $self->{'_bgColor3'} = $self->{'_grayLight'};
306              
307             # range colors. things are hard-coded throughout to use these.
308             # brighter blues because they are so dark to begin with.
309              
310             $self->{'_blue'} = $img->colorAllocate( 51, 51, 204 );
311             $self->{'_blueDark'} = $img->colorAllocate( 51, 51, 153 );
312              
313             $self->{'_cyan'} = $img->colorAllocate( 0, 204, 204 );
314             $self->{'_cyanDark'} = $img->colorAllocate( 0, 153, 153 );
315              
316             $self->{'_green'} = $img->colorAllocate( 0, 204, 0 );
317             $self->{'_greenDark'} = $img->colorAllocate( 0, 153, 0 );
318              
319             $self->{'_magenta'} = $img->colorAllocate( 204, 0, 204 );
320             $self->{'_magentaDark'} = $img->colorAllocate( 153, 0, 153 );
321              
322             $self->{'_red'} = $img->colorAllocate( 204, 0, 0 );
323             $self->{'_redDark'} = $img->colorAllocate( 153, 0, 0 );
324              
325             # will have an alternating background
326             # to help hilight hsps in the same hit.
327             $self->{'_curBgColor'} = $self->{'_bgColor2'};
328              
329             # but everything else should have a white background
330             # to distinguish where the hits start & end.
331              
332             # $img->filledRectangle(0, 0,
333             # $self->{'_realWidth'},
334             # $self->{'_imgHeight'},
335             # $self->{'_white'});
336              
337             $img->rectangle(0, 0,
338             $self->{'_realWidth'},
339             $self->{'_realHeight'},
340             $self->{'_blue'});
341              
342              
343             $self->{'_img'} = $img;
344              
345             }
346              
347             #----------------------------------------
348             # spit image out to a file.
349             ######################################################################
350             sub _writeImage {
351             ######################################################################
352             # This private method writes the image into a tmp directory specified by
353             # the client interface.
354              
355             my ($self) = @_;
356              
357             my $img_path = File::Spec->catfile( $self->{'_dstDir'}, $self->{'_imgName'} );
358             open my $img, '>', $img_path
359             or die "$! writing $img_path";
360              
361             if ($self->{'_img'}->can('png')) {
362             print $img $self->{'_img'}->png;
363             }
364             else {
365             print $img $self->{'_img'}->gif;
366             }
367              
368             }
369              
370             #----------------------------------------
371             # draw partitions in order.
372             # must come after drawQuery if
373             # you want the ticks everywhere.
374             #####################################################################
375             sub _drawGraph {
376             #####################################################################
377             # This private method loops through each hit from the list and calls
378             # different private methods to draw the different parts.
379              
380             my ($self) = @_;
381              
382             # dmsg( "drawGraph()..." );
383              
384             my( $hspBefore, $hspAfter, $hspMid );
385             my( $countsRef );
386             my( $countsStr );
387              
388             my( $totalCount, $shownCount );
389              
390             my $hspPos = $hspPosInit;
391              
392             for( my $pdex = 0; $pdex < $kNumberOfPartitions; $pdex++ ) {
393              
394             my $part = $self->{'_parts'}->getPartitionAt( $pdex );
395              
396             #dmsg( "drawGraph(): partition \#$pdex count =", $part->getCount() );
397              
398             # draw the hsps in the hits.
399             # keep track of how much vertical space is used.
400              
401             $hspBefore = $hspPos;
402              
403             my $enum = $part->getEnumerator();
404              
405             my $wrap;
406              
407             while( defined( $wrap = $enum->getNextElement() ) ) {
408              
409             $hspPos = $self->_drawWrap( $wrap, $hspPos );
410              
411             }
412              
413             $hspAfter = $hspPos;
414              
415             # annotate with count of
416             # shown/total per bucket.
417              
418             $countsRef =
419             $self->{'_parts'}->getPartitionElementsCountsRefAt($pdex);
420              
421             #dmsgs( "drawGraph(): partition counts = ", @{$countsRef} );
422              
423             $totalCount = $$countsRef[ 0 ];
424              
425             $shownCount = $$countsRef[ 1 ];
426              
427             if( $totalCount != 0 ) {
428              
429             if( $shownCount == $totalCount ) {
430              
431             $countsStr = 'All';
432             }
433             else {
434              
435             $countsStr = $shownCount . '/' . $totalCount;
436              
437             }
438              
439             if( $self->{'_allShowingP'} == 0 ) {
440              
441             $hspMid = $self->_getHspMid( $hspBefore, $hspAfter );
442              
443             $self->_drawString( $countsStr, GD::gdSmallFont(),
444             $self->{'_realWidth'}-$imgRightBorder+3,
445             $hspMid,
446             $self->_pickColorN($pdex));
447              
448             }
449             }
450             }
451              
452             #dmsg( "...drawGraph()" );
453             }
454              
455             ##############################################################################
456             sub _getHspMid {
457             ##############################################################################
458             # This private method is used to get the position for the given hsp.
459              
460             my($self, $hspBefore, $hspAfter) = @_;
461              
462             return ($hspBefore + ($hspAfter - $hspBefore)/2 - $fontHeight/2 - 2);
463              
464              
465             }
466              
467             #############################################################################
468             sub _drawWrap {
469             #############################################################################
470             # This private method is used to the wrap background.
471              
472             my($self, $wrap, $hspPos ) = @_;
473              
474             my $fwdRef = $wrap->getForwardBucketSet();
475             my $revRef = $wrap->getReverseBucketSet();
476              
477             my $fwdCount = $fwdRef->getCount(); # number of lines.
478             my $revCount = $revRef->getCount();
479              
480             # alternating background color. serious fudge factors
481             # because i'm way too confused by math. so if you change
482             # values in MapDefs this will be all wrong. sorry.
483              
484             my $bgY1 = $hspPos;
485             my $bgY2 = $hspPos + $hspHeight * $wrap->getHSPLineCount() - 1;
486              
487             $self->{'_curBgColor'} =
488             ($self->{'_curBgColor'} == $self->{'_bgColor2'}) ?
489             $self->{'_bgColor3'} : $self->{'_bgColor2'};
490              
491             $self->{'_img'}->filledRectangle($self->{'_mapUtils'}->getNoteLeft(),
492             $bgY1,
493             $self->{'_realWidth'}-$imgRightBorder,
494             $bgY2,
495             $self->{'_curBgColor'});
496              
497             $self->_annotateIMap($wrap,
498             $self->{'_mapUtils'}->getNoteLeft(),
499             $bgY1,
500             $self->{'_realWidth'}-$imgRightBorder,
501             $bgY2 );
502              
503             foreach my $tickX (@{$self->{'_tickList'}}) {
504              
505             $self->{'_img'}->line($self->{'_mapUtils'}->getQueryLeft()+$tickX,
506             $bgY1,
507             $self->{'_mapUtils'}->getQueryLeft()+$tickX,
508             $bgY2,
509             $self->{'_white'});
510              
511             }
512              
513             if($self->{'_showNamesP'}) {
514              
515             $self->{'_img'}->line($self->{'_mapUtils'}->getQueryLeft(),
516             $bgY1,
517             $self->{'_mapUtils'}->getQueryLeft(),
518             $bgY2,
519             $self->{'_white'});
520             }
521              
522             my $colorN = $self->_getColorNFromP($wrap, 0);
523              
524             my $hspBefore = $hspPos;
525              
526             if( $fwdCount > 0 ) {
527              
528             $hspPos = $self->_drawDirection($fwdRef->getBucketList(),
529             $hspPos,
530             'plus',
531             $colorN );
532              
533             }
534              
535             if( $revCount > 0 ) {
536              
537             $hspPos = $self->_drawDirection($revRef->getBucketList(),
538             $hspPos,
539             'minus',
540             $colorN );
541             }
542              
543             my $hspAfter = $hspPos;
544              
545             if( $self->{'_showNamesP'}) {
546              
547             my $mdefs = $self->{'_mapUtils'};
548              
549             my $buf = $mdefs->getNamesHorizBorder();
550             my $note = $wrap->getGraphAnnotation();
551             my ($w, $h) = $mdefs->getStringDimensions($note);
552              
553             # [[ assuming that the border is at least as wide as the string! ]]
554              
555             $buf -= $w;
556             $buf /= 2;
557              
558             my $x = $mdefs->getNoteLeft() + $buf;
559              
560             my $hspMid = $self->_getHspMid($hspBefore, $hspAfter);
561              
562             $self->{'_img'}->string(GD::gdSmallFont(),
563             $x,
564             $hspMid,
565             $note,
566             $self->{'_black'});
567             }
568              
569             return $hspPos;
570              
571             }
572              
573             ##############################################################################
574             sub _drawDirection {
575             ##############################################################################
576             # This private method is used to draw the arrow direction.
577              
578             my($self, $bucketList, $hspPos, $dir, $colorN) = @_;
579              
580             while (my $bucket = $bucketList->my_shift()) {
581              
582             my $regionList = $bucket->getRegions();
583              
584             while(my $region = $regionList->my_shift()) {
585              
586             my $start = round( $region->min() * $self->{'_horizRatio'} );
587             my $end = round( $region->max() * $self->{'_horizRatio'} );
588             my $scaledLength = $end - $start;
589             my $x1 = $self->{'_mapUtils'}->getQueryLeft() + $start;
590             my $y1 = $hspPos + $hspArrowPad;
591             my $x2 = $x1 + $scaledLength;
592             my $y2 = $y1 + $arrowHeight;
593              
594             $self->_drawArrowedOutlinedFromN($x1, $y1, $scaledLength, $dir, $colorN);
595              
596             }
597              
598             $hspPos += $hspHeight;
599              
600             }
601              
602             return $hspPos;
603              
604             }
605              
606             #----------------------------------------
607             # must come before drawGraph if
608             # you want the ticks everywhere.
609             ##############################################################################
610             sub _drawQuery {
611             ##############################################################################
612             # This private method is used to draw the query sequence bar.
613              
614             my($self) = @_;
615              
616              
617             ### try to space the ticks out reasonably.
618              
619             my $rawStep;
620              
621             if( $self->{'_srcLength'} < 100 ) { $rawStep = 10; }
622             elsif( $self->{'_srcLength'} < 500 ) { $rawStep = 50; }
623             elsif( $self->{'_srcLength'} < 1000 ) { $rawStep = 100; }
624             elsif( $self->{'_srcLength'} < 5000 ) { $rawStep = 200; }
625             else { $rawStep = 500; }
626              
627             $self->{'_img'}->line($self->{'_mapUtils'}->getQueryLeft(),
628             $topDataOffset,
629             $self->{'_mapUtils'}->getQueryLeft()+$self->{'_srcLength'}*$self->{'_horizRatio'},
630             $topDataOffset,
631             $self->{'_black'});
632              
633             $self->{'_img'}->string(GD::gdSmallFont(),
634             $self->{'_mapUtils'}->getQueryLeft(),
635             $topDataOffset-15,
636             "Query",
637             $self->{'_black'});
638              
639              
640             for(my $rawX=$rawStep; $rawX < $self->{'_srcLength'}; $rawX+=$rawStep) {
641              
642             my $str = "$rawX";
643              
644             $self->_drawTick( $str, $rawX);
645              
646             }
647              
648             my $pX = $self->{'_mapUtils'}->getQueryLeft();
649             $self->{'_img'}->line($pX,
650             $topDataOffset,
651             $pX,
652             $topDataOffset+2,
653             $self->{'_black'});
654              
655             $pX = $self->{'_mapUtils'}->getQueryLeft()+int($self->{'_srcLength'}*$self->{'_horizRatio'});
656              
657             $self->{'_img'}->line($pX,
658             $topDataOffset,
659             $pX,
660             $topDataOffset+2,
661             $self->{'_black'});
662              
663             }
664              
665             ################################################################################
666             sub _drawTick {
667             ################################################################################
668             # This private method is used to draw the tick marks for the query sequence
669             # bar.
670              
671             my ($self, $str, $rawX) = @_;
672              
673             my $nudgeTextX = round(length($str)*5/2.0);
674              
675             my $pX = int($rawX * $self->{'_horizRatio'});
676              
677             push(@{$self->{'_tickList'}}, $pX);
678              
679             $self->{'_img'}->line($self->{'_mapUtils'}->getQueryLeft()+$pX,
680             $topDataOffset,
681             $self->{'_mapUtils'}->getQueryLeft()+$pX,
682             $topDataOffset-$tickHeight,
683             $self->{'_black'});
684              
685             $self->{'_img'}->string(GD::gdSmallFont(),
686             $self->{'_mapUtils'}->getQueryLeft()+$pX-$nudgeTextX,
687             $topDataOffset-15,
688             $str,
689             $self->{'_black'});
690             }
691              
692             ##################################################################################
693             sub _drawString {
694             ################################################################################
695             # This private method is used to draw the text string.
696              
697             my ($self, $str, $font, $xpos, $ypos, $color) = @_;
698              
699             if( !defined( $color ) ) { $color = $self->{'_black'}; }
700              
701             my $end = length($str) * $fontWidth;
702              
703             $self->{'_img'}->string($font,
704             $xpos,
705             $ypos,
706             $str,
707             $color );
708              
709             return $end;
710              
711             }
712              
713             ###################################################################################
714             sub _annotateIMap {
715             ################################################################################
716             # This private method is used to initialize the mouseover function.
717              
718             my ($self, $wrap, $x1, $y1, $x2, $y2) = @_;
719              
720             my $cx1 = $x1 - $arrowWidth;
721             my $cy1 = $y1;
722             my $cx2 = $x2 + $arrowWidth;
723             my $cy2 = $y2;
724              
725             my $href = $wrap->getName();
726              
727             my $name = $href;
728              
729             $self->_print( "<area shape='rect' coords='$cx1,$cy1,$cx2,$cy2' href=\"#" . $href . "_A\" " );
730              
731             my $scoreDesc = "p=" . $wrap->getP() . " s=" . $wrap->getScore();
732              
733             my $pos = $self->{'_formFieldWidth'} - length($scoreDesc);
734              
735             my $englishDesc = $wrap->getDescription();
736              
737             # The description can contain a *different* name!
738              
739             $name =~ s/([^_]*).*/$1/;
740              
741             if( $englishDesc !~ m/$name/i ) { $englishDesc = "$name|$englishDesc"; }
742              
743             $englishDesc = substr( $englishDesc, 0, $pos );
744              
745             # the description might contain 5' which then
746             # confuses the hell out of javascript, so i
747             # have to escape those.
748             $englishDesc =~ s/\'/\&\#39/g;
749              
750             $self->_print( "ONMOUSEOVER='document.daform.notes.value=\"$scoreDesc $englishDesc\"'>\n" );
751              
752             }
753              
754             ###################################################################################
755             sub _makeColorBarHelper {
756             ###################################################################################
757             # This method is used to initialize some variables for color bar.
758              
759             my ($self, $min, $sep, $max, $colorN) = @_;
760              
761             if(Bio::GMOD::Blast::Graph::ScientificNotation::numberP($min)) {
762              
763             $min = abs( Bio::GMOD::Blast::Graph::ScientificNotation::getExponent( $min ) );
764              
765             }
766              
767             if(Bio::GMOD::Blast::Graph::ScientificNotation::numberP($max)) {
768              
769             $max = abs( Bio::GMOD::Blast::Graph::ScientificNotation::getExponent( $max ) );
770              
771             }
772              
773             return $min.$sep.$max;
774              
775             }
776              
777             ####################################################################################
778             sub _makeColorBar {
779             ####################################################################################
780             # This private method is used to populate the bar parts.
781              
782             my ($self) = @_;
783              
784             # going from worst to best.
785             my @barParts;
786             push( @barParts, $self->_makeColorBarHelper( '', '< ', $bucketThreeMax ), 4 );
787             push( @barParts, $self->_makeColorBarHelper( $bucketThreeMax, '-', $bucketTwoMax ), 3 );
788             push( @barParts, $self->_makeColorBarHelper( $bucketTwoMax, '-', $bucketOneMax ), 2 );
789             push( @barParts, $self->_makeColorBarHelper( $bucketOneMax, '-', $bucketZeroMax ), 1 );
790             push( @barParts, $self->_makeColorBarHelper( $bucketZeroMax, ' <', '' ), 0 );
791              
792             return( @barParts );
793              
794             }
795              
796              
797             ####################################################################################
798             sub _drawKey {
799             ####################################################################################
800             # This private method is used to draw the map keys.
801              
802             my ($self) = @_;
803              
804             # draw the fixed parts, the arrows.
805              
806             my $strOffset = 22;
807              
808             my $ypos = $self->{'_realHeight'} - $imgBottomBorder + $bottomDataOffset;
809              
810             my $xpos = $self->{'_mapUtils'}->getQueryLeft();
811              
812             $strOffset = $self->_drawString("Fwd:",
813             GD::gdMediumBoldFont(),
814             $xpos,
815             $ypos+1,
816             $self->{'_grayDark'});
817              
818             $xpos += $strOffset + 4;
819              
820             $self->_drawArrowedOutlined($xpos,
821             int($ypos+$fontHeight/2),
822             9,
823             'plus',
824             $self->{'_grayDark'},
825             $self->{'_grayDark'});
826              
827              
828             $xpos += 18;
829              
830             $self->_drawString("Rev:",
831             GD::gdMediumBoldFont(),
832             $xpos,
833             $ypos+1,
834             $self->{'_grayDark'});
835              
836             $xpos += $strOffset + 4;
837              
838             $self->_drawArrowedOutlined($xpos,
839             int($ypos+$fontHeight/2),
840             9,
841             'minus',
842             $self->{'_grayDark'},
843             $self->{'_grayDark'});
844              
845              
846             my @barParts = $self->_makeColorBar();
847              
848              
849             my $partPad = 10;
850              
851             my $scoreStr = "Neg P Exponent: ";
852              
853             # figure out box spacing.
854              
855             my $strWidthPart = length($scoreStr) * $fontWidth + $partPad;
856              
857             my $strWidthFull = $strWidthPart || '0';
858              
859             my $strWidthPartMax;
860              
861             for( my $dex = 0; $dex < 5; $dex++ ) {
862              
863             my $str = $barParts[$dex*2];
864              
865             $strWidthPart = length($str) * $fontWidth + $partPad;
866              
867             Bio::GMOD::Blast::Graph::MyUtils::updateBoundRef(\$strWidthPartMax,
868             $strWidthPart,
869             \&Bio::GMOD::Blast::Graph::MyUtils::largerP);
870              
871             $strWidthFull += $strWidthPart;
872              
873             }
874              
875             # center key in image.
876             $xpos = $self->{'_mapUtils'}->getQueryLeft() +
877             int($self->{'_mapUtils'}->getQueryWidth()-$strWidthFull)/2;
878              
879             # nudge it to the left to be optically more balanced.
880             $xpos -= 5;
881              
882             $self->{'_img'}->string(GD::gdMediumBoldFont(),
883             $xpos,
884             $ypos+1,
885             $scoreStr,
886             $self->{'_grayDark'});
887              
888             $xpos += length($scoreStr) * $fontWidth + $partPad;
889              
890             for( my $dex = 0; $dex < 5; $dex++ ) {
891              
892             my $str = $barParts[$dex*2];
893              
894             my $clr = $self->_pickColorN($barParts[$dex*2+1]);
895              
896             $self->{'_img'}->filledRectangle($xpos,
897             $ypos,
898             $xpos+$strWidthPartMax,
899             $ypos+$fontHeight+5,
900             $clr );
901              
902             $strWidthPart = length($str) * $fontWidth;
903              
904             $strOffset = ( $strWidthPartMax - $strWidthPart ) / 2;
905              
906             $self->{'_img'}->string(GD::gdSmallFont(),
907             $xpos+$strOffset,
908             $ypos+1,
909             $str,
910             $self->{'_white'});
911              
912             $xpos += $strWidthPartMax;
913              
914             }
915              
916             }
917              
918             #########################################################################
919             sub _getArrowedLinePoly {
920             #########################################################################
921             # This private method is used to get the coordinates for the arrow
922             # locations.
923              
924             my( $self, $x1, $y1, $scaledLength, $dir) = @_;
925              
926             # fudge-a-licious math to prevent the arrows from exploding if the
927             # hit is smaller than an arrow width (since we normally draw the
928             # arrows inside the bounding box of the hit).
929             if( $scaledLength < ($arrowWidth*2) ) {
930              
931             my $fudge = (($arrowWidth * 2) - $scaledLength) / 2;
932              
933             $x1 -= $fudge;
934              
935             $scaledLength += ($fudge*2);
936              
937             }
938              
939             my $x2 = $x1 + $scaledLength;
940             my $y2 = $y1 + $arrowHeight;
941             my $ymid = $y1 + $halfArrowHeight;
942             my $poly = new GD::Polygon;
943              
944             # drawing them with the arrows inside the bounding box.
945              
946             if( $self->_rightP($dir) ) {
947              
948             # top.
949             $poly->addPt( $x1, $y1 );
950             $poly->addPt( $x2-$arrowWidth, $y1 );
951              
952             # rhs.
953             $poly->addPt( $x2, $ymid );
954             $poly->addPt( $x2-$arrowWidth, $y2 );
955              
956             # bottom.
957             $poly->addPt( $x1, $y2 );
958              
959             # lhs.
960             $poly->addPt( $x1+$arrowWidth, $ymid );
961              
962             }
963             elsif( $self->_leftP($dir) ) {
964              
965             # top.
966             $poly->addPt( $x1+$arrowWidth, $y1 );
967             $poly->addPt( $x2, $y1 );
968              
969             # rhs.
970             $poly->addPt( $x2-$arrowWidth, $ymid );
971             $poly->addPt( $x2, $y2 );
972              
973             # bottom.
974             $poly->addPt( $x1+$arrowWidth, $y2 );
975              
976             # lhs.
977             $poly->addPt( $x1, $ymid );
978              
979             }
980             else {
981              
982             croak( "invalid direction $dir\n" );
983              
984             }
985              
986             return $poly;
987              
988             }
989              
990             #############################################################################
991             sub _drawArrowedOutlinedFromN {
992             #############################################################################
993             # This method is used to draw the arrow outlines
994              
995             my ($self, $x1, $y1, $scaledLength, $dir, $colorN) = @_;
996              
997             my $light = $self->_pickColorN($colorN, 0);
998             my $dark = $self->_pickColorN($colorN, 1);
999              
1000             $self->_drawArrowedOutlined($x1, $y1,
1001             $scaledLength,
1002             $dir, $light, $dark );
1003              
1004             }
1005              
1006             #############################################################################
1007             sub _drawArrowedOutlined {
1008             #############################################################################
1009             # This method is used to draw the arrow outlines
1010              
1011             my ($self, $x1, $y1, $scaledLength, $dir, $light, $dark) = @_;
1012              
1013             my $poly = $self->_getArrowedLinePoly( $x1, $y1, $scaledLength, $dir );
1014              
1015             $self->{'_img'}->filledPolygon( $poly, $light );
1016              
1017             # put an arrow in the middle, to help distinguish direction.
1018             # (try to avoid rounding problems.)
1019             my $xmidLeft = $x1 + int($scaledLength/2) - $halfArrowWidth;
1020             my $xmidRight = $xmidLeft + $arrowWidth;
1021             my $ymid = $y1 + $halfArrowHeight;
1022             my $y2 = $y1 + $arrowHeight;
1023              
1024             # used to use curBgColor but i think all white is more clear.
1025             if($self->_rightP($dir)) {
1026              
1027             $self->{'_img'}->line($xmidLeft, $y1,
1028             $xmidRight, $ymid,
1029             $self->{'_white'});
1030              
1031             $self->{'_img'}->line($xmidRight, $ymid,
1032             $xmidLeft, $y2,
1033             $self->{'_white'});
1034              
1035             }
1036             else {
1037              
1038             $self->{'_img'}->line($xmidRight, $y1,
1039             $xmidLeft, $ymid,
1040             $self->{'_white'});
1041              
1042             $self->{'_img'}->line($xmidLeft, $ymid,
1043             $xmidRight, $y2,
1044             $self->{'_white'});
1045              
1046             }
1047              
1048             # now apply the outline.
1049             $self->{'_img'}->polygon( $poly, $dark );
1050              
1051             }
1052              
1053             #####################################################################
1054             sub _getColorNFromP {
1055             #####################################################################
1056             # This private method is used to initialize the color based on the
1057             # pvalue.
1058              
1059             my ($self, $wrap, $darkP) = @_;
1060              
1061             # [[ this assumes that we have 5 partitions,
1062             # since the number of colors is fixed. ]]
1063              
1064             my $value = $wrap->getP();
1065              
1066             return $self->{'_parts'}->getPartitionIndexFromExtendedValue($value);
1067              
1068             }
1069              
1070             #####################################################################
1071             sub _pickColorN {
1072             #####################################################################
1073             # This private method is used to pick up a right color for the hit.
1074              
1075             my($self, $n, $darkP) = @_;
1076              
1077              
1078             if( !defined($darkP) ) { $darkP = 0; }
1079              
1080             my $color;
1081              
1082             if( $n == 4 ) {
1083              
1084             $color = ( $darkP == 1 ) ? $self->{'_blueDark'} : $self->{'_blue'};
1085              
1086             }
1087             elsif( $n == 3 ) {
1088              
1089             $color = ( $darkP == 1 ) ? $self->{'_cyanDark'} : $self->{'_cyan'};
1090              
1091             }
1092             elsif( $n == 2 ) {
1093              
1094             $color = ( $darkP == 1 ) ? $self->{'_greenDark'} : $self->{'_green'};
1095              
1096             }
1097             elsif( $n == 1 ) {
1098              
1099             $color = ( $darkP == 1 ) ? $self->{'_magentaDark'} : $self->{'_magenta'};
1100              
1101             }
1102             elsif( $n == 0 ) {
1103              
1104             $color = ( $darkP == 1 ) ? $self->{'_redDark'} : $self->{'_red'};
1105              
1106             }
1107             else {
1108              
1109             croak( "_pickColorN(): invalid index $n" );
1110             }
1111              
1112             return $color;
1113              
1114             }
1115              
1116              
1117             ####################################################################################
1118             sub _pickNextDebugColors {
1119             ####################################################################################
1120             # This private method is used to pick up the debug colors.
1121              
1122             my ($self) = @_;
1123              
1124             my $dex = $self->{'_debugCount'};
1125              
1126             my ($color, $bgColor);
1127              
1128             if( $dex == 0 ) {
1129              
1130             $bgColor = $self->{'_blueDark'};
1131             $color = $self->{'_blue'};
1132              
1133             }
1134             elsif( $dex == 1 ) {
1135              
1136             $bgColor = $self->{'_greenDark'};
1137             $color = $self->{'_green'};
1138              
1139             }
1140             elsif( $dex == 2 ) {
1141              
1142             $bgColor = $self->{'_cyanDark'};
1143             $color = $self->{'_cyan'};
1144              
1145             }
1146             elsif( $dex == 3 ) {
1147              
1148             $bgColor = $self->{'_magentaDark'};
1149             $color = $self->{'_magenta'};
1150              
1151             }
1152             elsif( $dex == 4 ) {
1153              
1154             $bgColor = $self->{'_redDark'};
1155             $color = $self->{'_red'};
1156              
1157             }
1158              
1159             $self->{'_debugCount'} = ++$dex % 5;
1160              
1161             return( $color, $bgColor );
1162              
1163             }
1164              
1165             ###################################################################################
1166             sub _drawStamp {
1167             ###################################################################################
1168             # This private method is used to print the date on the map.
1169              
1170             my ($self) = @_;
1171              
1172             my %tomonth = ('0'=>'Jan', '1'=>'Feb', '2'=>'Mar', '3'=>'Apr', '4'=>'May',
1173             '5'=>'Jun', '6'=>'Jul', '7'=>'Aug', '8'=>'Sep', '9'=>'Oct',
1174             '10'=>'Nov', '11'=>'Dec');
1175              
1176             my @date = localtime();
1177             my $year = $date[5] + 1900;
1178             my $month = $tomonth{$date[4]};
1179             my $day = $date[3];
1180              
1181             my $dstr = join( "/", $day, $month, $year );
1182             my $xpos = $self->{'_realWidth'} - (length( $dstr ) * $fontWidth) - $imgRightBorder;
1183             my $ypos = $self->{'_realHeight'} - $imgBottomBorder + $bottomDataOffset;
1184              
1185             $self->_drawString($dstr,
1186             GD::gdSmallFont(),
1187             $xpos,
1188             $ypos,
1189             $self->{'_grayDark'});
1190              
1191             }
1192              
1193             ####################################################################################
1194             sub _countHTML {
1195             ####################################################################################
1196             # This private method is used to print a short message about how many hits displayed
1197             # on the map.
1198              
1199             my ($self, $shown, $max) = @_;
1200              
1201             my $word;
1202              
1203             if( $max > 1 ) { $word = 'hits'; }
1204             else { $word = 'hit'; }
1205              
1206             # print( '<center><h1>Summary of BLAST Results</h1></center>' );
1207              
1208             $self->_print( '<p align=center>' );
1209              
1210             if( $shown < $max ) {
1211              
1212             $self->_print( 'The graph shows the highest hits per range.<br>' );
1213             $self->_print( '<b>Data has been omitted:</b> ' );
1214             $self->_print( "$shown/$max $word displayed." );
1215              
1216             }
1217             else {
1218              
1219             $self->_print( 'All hits shown.' );
1220              
1221             }
1222              
1223             $self->_print( "</p>\n" );
1224              
1225             }
1226              
1227             ##################################################################################
1228             sub _writeIMapStart {
1229             ##################################################################################
1230             # This private method is used to print a start_form tag, a text field for
1231             # displaying the mouseover message, and a start map tag.
1232              
1233             my ($self) = @_;
1234              
1235             $self->_print( '<center><form name="daform">' );
1236              
1237             $self->_print( '<input type="text" id="notes" value="" size="30">' );
1238              
1239             $self->_print( '<MAP NAME="' . $self->{'_mapName'} . '">' );
1240              
1241              
1242             }
1243              
1244             #################################################################################
1245             sub _writeIMapEnd {
1246             #################################################################################
1247             # This private method is used to draw the end map tag and print the map to the
1248             # stdout (browser).
1249              
1250             my ($self) = @_;
1251              
1252             $self->_print( "</MAP>\n" );
1253              
1254             my $img = sprintf '<img src="%s" usemap="#%s">', $self->{_dstURL} . $self->{_imgName}, $self->{_mapName};
1255             $self->_print( $img );
1256              
1257             $self->_print( '</form></center>' );
1258              
1259             }
1260              
1261             #################################################################################
1262             sub _rightP {
1263             #################################################################################
1264             my ($self, $dir) = @_;
1265              
1266             if( $dir =~ m/plus/i ) { # is plus == right?
1267              
1268             return 1;
1269              
1270             }
1271             else {
1272              
1273             return 0;
1274              
1275             }
1276              
1277             }
1278              
1279             #################################################################################
1280             sub _leftP {
1281             #################################################################################
1282             my ($self, $dir) = @_;
1283              
1284             if( $dir =~ m/minus/i ) { # is minus == left?
1285              
1286             return 1;
1287              
1288             }
1289             else {
1290              
1291             return 0;
1292              
1293             }
1294              
1295             }
1296              
1297             #################################################################################
1298             1;
1299             #################################################################################
1300              
1301              
1302              
1303              
1304              
1305              
1306              
1307              
1308             __END__
1309             =pod
1310              
1311             =encoding utf-8
1312              
1313             =head1 NAME
1314              
1315             Bio::GMOD::Blast::Graph - display a graphical summary of a BLAST report
1316              
1317             =head1 DESCRIPTION
1318              
1319             This package provides methods for graphically displaying a BLAST
1320             search report.
1321              
1322             =head1 METHODS
1323              
1324             =head2 new
1325              
1326             This is the constructor. It expects to be passed named arguments for
1327             the search outputfile, the file format (blast or fasta), the image
1328             file path, and image url. It can also accept an optional filehandle
1329             argument, which is the filehandle to which it will print its HTML
1330             output when L</showGraph> is called. By default, prints to STDOUT.
1331              
1332             Usage :
1333              
1334             my $graph = Bio::GMOD::Blast::Graph->new(
1335             -outputfile => $blastOutputFile,
1336             -format => 'blast',
1337             -dstDir => $imageDir,
1338             -dstURL => $imageUrl
1339             -fh => \*STDOUT,
1340             );
1341              
1342             =head2 showGraph
1343              
1344             This method prints the map to stdout (web browser).
1345              
1346             Usage:
1347              
1348             $graph->showGraph;
1349              
1350             =head2 getImageFile
1351              
1352             This method returns the newly created image file name (with full path).
1353              
1354             Usage:
1355              
1356             my $imageFile = $graph->getImageFile;
1357              
1358             =head2 hitNameArrayRef
1359              
1360             This method returns the array ref for the hit names.
1361              
1362             Usage:
1363              
1364             my $hitArrayRef = $graph->hitNameArrayRef;
1365              
1366             foreach my $hitName (@$hitArrayRef) {
1367              
1368             # do something useful here
1369              
1370             }
1371              
1372             =head1 AUTHORS
1373              
1374             =over 4
1375              
1376             =item *
1377              
1378             Shuai Weng <shuai@genome.stanford.edu>
1379              
1380             =item *
1381              
1382             John Slenk <jces@genome.stanford.edu>
1383              
1384             =item *
1385              
1386             Robert Buels <rmb32@cornell.edu>
1387              
1388             =item *
1389              
1390             Jonathan "Duke" Leto <jonathan@leto.net>
1391              
1392             =back
1393              
1394             =head1 COPYRIGHT AND LICENSE
1395              
1396             This software is Copyright (c) 2011 by The Board of Trustees of Leland Stanford Junior University.
1397              
1398             This is free software, licensed under:
1399              
1400             The Artistic License 1.0
1401              
1402             =cut
1403