3 CXGN::Transcript::DrawContigAlign - draws alignment graphs of unigenes.
7 This should be instantiated (without arguments), have alignment data fed to it, and instructed where to put the image and imagemap.
9 $imageProgam = CXGN::Transcript::DrawContigAlign->new();
10 $imageProgram->addAlignment('SGN-E1199194', 'SGN-U508096', '+', 0, 477, 0, 0, 1);
11 $imageProgram->addAlignment('SGN-E1189844', 'SGN-U508096', '+', 75, 760, 30, 0, 0);
13 $imageProgram->writeImageToFile('Image.png', 'Mapfile', 'http://some.domain.name/some/url/thingie/', 'Image name');
17 DrawContigAlign receives information about sequences stored in a unigene and produces a graph of the aligned sequences.
21 Rafael Lizarralde <xarbiogeek@gmail.com> (July 2009)
23 Paraphrased from a C script (draw_contigalign.c) of unknown origin.
25 =head1 MEMBER FUNCTIONS
27 This class implements the following functions:
29 =head2 constructor C<new>
35 $imageProgram = CXGN::Transcript::DrawContigAlign->new();
39 a CXGN::Transcript::DrawContigAlign object
49 package CXGN
::Transcript
::DrawContigAlign
;
52 use MooseX
::Method
::Signatures
;
55 use CXGN
::Transcript
::DrawContigAlign
::Pane
;
56 use CXGN
::Transcript
::DrawContigAlign
::ContigAlign
;
57 use CXGN
::Transcript
::DrawContigAlign
::DepthData
;
59 use constant Pane
=> 'CXGN::Transcript::DrawContigAlign::Pane';
60 use constant ContigAlign
=> 'CXGN::Transcript::DrawContigAlign::ContigAlign';
61 use constant DepthData
=> 'CXGN::Transcript::DrawContigAlign::DepthData';
63 #The list of sequences to be aligned
64 has contigAligns
=> (is
=> 'rw', isa
=> 'ArrayRef', default => sub { my @array = (); return \
@array; });
66 =head2 mutator C<addAlignment>
72 $imageProgram->addAlignment('SGN-E1189844', 'SGN-U508096', '+', 75, 760, 30, 0, 0);
76 adds alignment data to the list of data that will be incorporated in the graph
88 the source ID tag of the segment in a Unigene
92 the sequence ID tag of the Unigene
96 an identifier determining whether it is complementary or not (if not, it should be '+')
100 the starting base pair of the segment
104 the ending base pair of the segment
108 the number of base pairs that have been trimmed (do not match) at the start
112 the number of base pairs that have been trimmed (do not match) at the end
114 =item Bool $highlight
116 whether or not the segment should be highlighted
124 #Adds a sequence to the list of contiguous alignments
125 method addAlignment
(Str
$sourceID!, Str
$sequenceID!, Str
$strand!,
126 Int
$startLoc!, Int
$endLoc!, Int
$startTrim!, Int
$endTrim!, Bool
$highlight?
) {
127 my @contigs = @
{ $self->contigAligns };
128 my $contig = ContigAlign
->new($sourceID, $sequenceID, $strand,
129 $startLoc, $endLoc, $startTrim, $endTrim, $highlight);
130 push @contigs, $contig;
131 $self->contigAligns( [ @contigs ] );
134 =head2 accessor C<writeImageToFile>
140 $imageProgram->writeImageToFile('Image.png', 'Mapfile', 'http://some.domain.name/some/url/thingie/', 'Image name');
144 produces an image file and map file with the alignment information
154 =item Str $imageFilename
156 the path of the image file
158 =item Str $mapFilename
160 the path of the map file
162 =item Str $linkBasename
164 the url stub for the EST pages
168 the name for the image
177 my ($HISTOGRAM, $ALIGN, $INFO, $LEGEND) = (0..3);
178 my ($BORDERWIDTH, $PAD, $HISTOGRAMWIDTH) = (5, 10, 350);
180 my ($BLACK, $BLUE, $RED, $YELLOW, $LIGHTGRAY, $DARKGRAY, $FRONTSHADE, $BACKSHADE);
182 #Creates the image based on the currently-possessed data and stores it in an image and map file
183 method writeImageToFile
(Str
$imageFilename!, Str
$mapFilename!, Str
$linkBasename!, Str
$imageName) {
184 $self->contigAligns( [ sort { $a->compare($b) } @
{ $self->contigAligns } ] );
185 $imageName = '(Unspecified)' if(!defined($imageName));
187 my @depths = @
{ $self->computeDepths() };
188 my $yMax = $self->computeYMax(\
@depths);
189 my $yScale = $self->computeYScale($yMax);
190 my $xMax = $self->computeXMax(\
@depths);
191 my $xScale = $self->computeXScale($xMax);
193 my $font = &gdSmallFont
;
194 my $textHeight = $font->height;
195 #makes the font height odd so the center is an integer, to match up with lines
196 $textHeight++ if($textHeight%2 == 0);
198 my @panes = $self->computePanes($font, $textHeight, $yMax);
200 #space needed for the histogram, plus labels (3), plus each segment, plus the border, plus padding for each pane
201 my $imageHeight = $panes[$ALIGN]->south + $PAD + $BORDERWIDTH;
202 #the rightmost edge of a right pane, plus the padding and the border
203 my $imageWidth = $panes[$INFO]->east + $PAD + $BORDERWIDTH;
204 #the width and height, and a true value to use a 24-bit color scheme (RGB 0-255)
205 my $image = GD
::Image
->new($imageWidth + 1,$imageHeight + 1);
207 $self->setColors($image);
208 $self->drawBorder($image, $imageWidth, $imageHeight);
209 $self->drawGrayBars($image, \
@panes, $textHeight);
210 $self->drawHighlight($image, \
@panes, $textHeight);
211 $self->drawHistogram(\
@depths, $image, $panes[$HISTOGRAM], $font, $textHeight, $xMax, $xScale, $yMax, $yScale);
212 $self->drawAlignments($image, $panes[$ALIGN], $font, $textHeight, $xMax, $xScale);
213 $self->drawInfo($image, $panes[$INFO], $font, $textHeight, $mapFilename, $imageName, $linkBasename);
214 $self->drawLegend($image, $panes[$LEGEND], $font, $imageName, $depths[$#depths]->position);
217 open my $IMAGEFILE, ">" , $imageFilename or die "Could not create image file: $!";
218 print $IMAGEFILE $image->png;
222 #Calculates the total depth in each region of the sequence
223 method computeDepths
{
224 my @contigAligns = @
{ $self->contigAligns };
227 #adds all the positions using a hash to eliminate duplicates
228 for my $est (@contigAligns) {
229 $depthHash{$est->start} = CXGN
::Transcript
::DrawContigAlign
::DepthData
->new($est->start);
230 $depthHash{$est->end } = CXGN
::Transcript
::DrawContigAlign
::DepthData
->new($est->end );
232 my @depths = sort { $a->compare($b) } values %depthHash;
234 #increments the depths of each position for each sequence that covers it
235 for my $est (@contigAligns) {
236 for my $depth (@depths) {
237 $depth->increment if($depth->position >= $est->start and $depth->position < $est->end);
244 #This probably needs to be rewritten to be more intelligent--60 is way bigger than most unigenes
245 method computeYMax
(ArrayRef
$depthList!) {
246 my @depths = @
{ $depthList };
249 for my $depth (@depths) { $maxDepth = $depth->depth if($depth->depth > $maxDepth); }
251 return 60 if($maxDepth < 60);
252 return $self->roundTo($maxDepth, 30);
255 #This probably needs to be rewritten to be more intelligent--60 is way bigger than most unigenes
256 method computeYScale
(Int
$maxDepth!) {
257 return (($maxDepth <= 60) ?
15 : 20);
260 #Calculates the total length of the aligned sequence, rounded up to the next 100
261 method computeXMax
(ArrayRef
$depthList!) {
262 my @depths = @
{ $depthList };
263 my $xMax = $depths[$#depths]->position;
264 return $self->roundTo($xMax, 100);
267 #Calculates the distance between tics on the x-axis
268 method computeXScale
(Int
$xMax!) {
269 return $xMax / $XTICS;
272 #Computes the dimensions of the panes, which are arranged like this:
273 # --------------------------------
275 # | Histogram | Legend |
277 # |--------------------------------|
279 # | Sequence | Sequence |
280 # | Alignment | Info |
282 # --------------------------------
283 method computePanes
(Ref
$font!, Int
$textHeight!, Int
$histogramHeight!) {
284 my $sequences = @
{ $self->contigAligns };
286 #the 4*width accounts for the depth tic labels
287 my $histogram = Pane
->new($BORDERWIDTH+$PAD,
288 $BORDERWIDTH+(2 * $PAD)+$histogramHeight+(3 * $textHeight),
289 $BORDERWIDTH+$PAD, $BORDERWIDTH+$PAD+$HISTOGRAMWIDTH+(4 * $font->width)+$textHeight);
291 my $alignment = Pane
->new($histogram->south + $PAD,
292 $histogram->south + $PAD + ($sequences * $textHeight),
293 $histogram->west, $histogram->east);
295 my $info = Pane
->new($alignment->north, $alignment->south,
296 $alignment->east, $alignment->east + (48 * $font->width));
298 my $legend = Pane
->new($histogram->north, $histogram->south,
299 $info->west, $info->east);
301 return ($histogram, $alignment, $info, $legend);
305 #Initializes the colors that are used in the graph
306 method setColors
(Ref
$image!) {
307 #sets the background to white
309 $image->colorAllocate($bg, $bg, $bg);
311 $BLACK = $image->colorResolve(0, 0, 0);
312 $BLUE = $image->colorResolve(0, 0, 255);
313 $RED = $image->colorResolve(255, 0, 0);
314 $YELLOW = $image->colorResolve(255, 255, 100);
315 $LIGHTGRAY = $image->colorResolve(200, 200, 200);
316 $FRONTSHADE = $image->colorResolve($bg - 50, $bg - 50, $bg - 50);
317 $BACKSHADE = $image->colorResolve($bg - 100, $bg - 100, $bg - 100);
320 #Draws the bevel border
321 method drawBorder
(Ref
$image!, Int
$width!, Int
$height!) {
322 for my $i (0..($BORDERWIDTH-1)) {
323 #draws a lighter border on the north and west sides
324 $image->line($i, $i, $width - ($i + 1), $i, $FRONTSHADE);
325 $image->line($i, $i, $i, $height - ($i + 1), $FRONTSHADE);
327 #draws a darker border on the south and east sides
328 $image->line($i, $height - $i, $width - $i, $height - $i, $BACKSHADE);
329 $image->line($width - $i, $i, $width - $i, $height - $i, $BACKSHADE);
333 #Draws the gray bars in the alignment pane as a visual aid
334 method drawGrayBars
(Ref
$image!, ArrayRef
$paneList!, Int
$textHeight!) {
335 my @panes = @
{ $paneList };
336 my $sequences = @
{ $self->contigAligns };
339 for(my $i = 0; $i < $sequences; $i += 6) {
340 my $width = ( ($i < $sequences - 3) ?
3 : ($sequences - $i) );
341 $image->filledRectangle($panes[$ALIGN]->west, $panes[$ALIGN]->north + ($i * $textHeight),
342 $panes[$INFO]->east, $panes[$ALIGN]->north + (($i + $width) * $textHeight),
349 method drawHighlight
(Ref
$image!, ArrayRef
$paneList!, Int
$textHeight!) {
350 my @panes = @
{ $paneList };
351 my @contigAligns = @
{ $self->contigAligns };
353 for my $i (0..(@contigAligns - 1)) {
354 if($contigAligns[$i]->highlight) {
355 $image->filledRectangle($panes[$ALIGN]->west, $panes[$ALIGN]->north + ($i * $textHeight),
356 $panes[$INFO]->east, $panes[$ALIGN]->north + (($i+1) * $textHeight), $YELLOW);
361 #Draws everything in the histogram pane of the image
362 method drawHistogram
(ArrayRef
$depthList!, Ref
$image!, Object
$pane!, Ref
$font!,
363 Int
$textHeight!, Int
$xMax!, Int
$xScale!, Int
$yMax!, Int
$yScale!) {
364 my @depths = @
{ $depthList };
365 #defines the place where the histogram is drawn, instead of the axis labels and such
366 my $grid = Pane
->new($pane->north + (3 * $textHeight), $pane->south - $PAD,
367 $self->round($pane->west + ($font->height * 1.5) + ($font->width * 4)), $pane->east - $PAD);
369 $self->prepareGraph($image, $font, $grid, $xMax, $xScale, $yMax, $yScale);
370 my $unitsPerPixel = $xMax/($grid->east - $grid->west);
372 my $darkGray = $image->colorResolve(128, 128, 128);
373 for my $i (0..(@depths - 2)) {
374 my $left = $grid->west + $self->round($depths[$i]->position/$unitsPerPixel);
375 $left++ if $left == $grid->west;
376 my $right = $grid->west + $self->round($depths[$i+1]->position/$unitsPerPixel);
377 my $top = $grid->north + 1;
378 my $bottom = $grid->north + $depths[$i]->depth + 1;
379 $image->filledRectangle($left, $top, $right, $bottom, $darkGray);
383 #Prepares the grid and axes for the histogram
384 method prepareGraph
(Ref
$image!, Ref
$font!, Object
$pane!,
385 Int
$xMax!, Int
$xScale!, Int
$yMax!, Int
$yScale!) {
388 #draws the surrounding rectangle
389 $image->rectangle($pane->west, $pane->north, $pane->east, $pane->south, $BLACK);
391 #draws the gridlines and tic marks
392 my $unitsPerPixel = $xMax / ($pane->east - $pane->west);
393 for my $i (0..$XTICS) {
394 #draws vertical gridlines
395 $x = $pane->west + $self->round(($i * $xScale)/$unitsPerPixel);
396 if($i > 0 and $i < $XTICS) {
397 for($y = $pane->north; $y < $pane->south; $y += 3) {
398 $image->setPixel($x, $y, $BLACK);
402 #draws horizontal tic marks
403 $image->line($x, $pane->north, $x, $pane->north - 3, $BLACK);
404 $image->line($x, $pane->south, $x, $pane->south + 1, $BLACK);
406 #draws the corresponding x-axis label
407 $label = $i * $xScale;
408 $x -= (length($label) * $font->width) / 2;
409 $y = $pane->north - 4 - $font->height;
410 $image->string($font, $x, $y, $label, $BLACK);
413 #draws the x-axis description
414 $label = 'position (bp)';
415 $x = $pane->west + ($pane->east - $pane->west)/2 - (length($label) * $font->width)/2;
416 $y = $pane->north - 5 - (2 * $font->height);
417 $image->string($font, $x, $y, $label, $BLACK);
419 for(my $i = 0; $i <= $yMax; $i += $yScale) {
420 #draws horizontal gridlines
421 $y = $i + $pane->north;
422 if($i > 0 and $i < $yMax) {
423 for($x = $pane->west; $x < $pane->east; $x += 3) {
424 $image->setPixel($x, $y, $BLACK);
428 #draws vertical tic marks
429 $image->line($pane->west, $y, $pane->west - 3, $y, $BLACK);
430 $image->line($pane->east, $y, $pane->east + 3, $y, $BLACK);
432 #draws the corresponding y-axis label
434 $x = $pane->west - (length($label) * $font->width) - 4;
435 $y -= $font->height / 2;
436 $image->string($font, $x, $y, $label, $BLACK);
439 #draws the y-axis description
441 $x = $pane->west - (4 * $font->width) - $font->height - 1;
442 $y -= $font->height / 2;
443 $image->stringUp($font, $x, $y, $label, $BLACK);
446 #Draws the alignment of the sequences
447 method drawAlignments
(Ref
$image!, Object
$pane!, Ref
$font!,
448 Int
$textHeight!, Int
$xMax!, Int
$xScale!) {
449 my @contigAligns = @
{ $self->contigAligns };
451 #computes the left and right bounds of the histogram above
452 my $west = $pane->west + (1.5 * $font->height) + (4 * $font->width);
453 my $east = $pane->east - $PAD;
454 my $unitsPerPixel = $xMax / ($east - $west);
457 #draws the dotted lines corresponding to histogram depth changes
458 for my $i (0..$XTICS) {
459 $x = $west + $self->round(($i * $xScale) / $unitsPerPixel);
460 for($y = $pane->north; $y < $pane->south; $y += 3) {
461 $image->setPixel($x, $y, $BLACK);
465 #draws the lines for each sequence
466 for my $i (0..(@contigAligns - 1)) {
467 $y = $pane->north + $self->round($textHeight * ($i + .5));
469 my $contigAlign = $contigAligns[$i];
471 #the beginning and end of the matching sequence, excluding trim
472 my $startCover = $west + $self->round($contigAlign->start / $unitsPerPixel);
473 my $endCover = $west + $self->round($contigAlign->end / $unitsPerPixel);
476 #draws a black line if it's the same strand, or a blue one if it's the complementary strand
477 $image->line($startCover, $y, $endCover, $y, (($contigAlign->strand eq '+') ?
$BLACK : $BLUE));
479 #draws a leading red segment if the sequence was trimmed
480 if($contigAlign->startTrim > 0) {
481 if($contigAlign->startLoc < 0) {#This should never happen
482 $self->drawDottedLine($image, $west - 18, $y, $RED);
483 my $label = $contigAlign->startLoc . "bp";
484 $x = $west - ((length($label) + 1) * &gdTinyFont
->width);
485 $image->string(&gdTinyFont
, $x, $y - &gdTinyFont
->height, $label, $RED);
487 } else {#This should always happen
488 $start = $west + $self->round($contigAlign->startLoc / $unitsPerPixel);
490 $image->line($start, $y, $startCover - 1, $y, $RED);
493 if($contigAlign->endTrim > 0) {
494 if($contigAlign->endLoc > $xMax) {#This should never happen
495 $self->drawDottedLine($image, $east, $y, $RED);
496 my $label = ($contigAlign->endLoc - $xMax) . "bp";
497 $x = $east + &gdTinyFont
->width;
498 $image->string(&gdTinyFont
, $x, $y - &gdTinyFont
->height, $label, $RED);
500 } else {#This should always happen
501 $end = $west + $self->round($contigAlign->endLoc / $unitsPerPixel);
503 $image->line($endCover + 1, $y, $end, $y, $RED);
508 #This should never be called
509 method drawDottedLine
(Ref
$image!, Int
$x!, Int
$y!, Int
$color!) {
510 for(my $i = 0; $i < 18; $i += 6) {
511 $image->line($x + $i, $y, $x + $i + 2, $y, $color);
515 #Draws the info for each of the aligned sequences
516 method drawInfo
(Ref
$image!, Object
$pane!, Ref
$font!,
517 Int
$textHeight!, Str
$mapFilename!, Str
$imageName!, Str
$linkBasename!) {
518 my @contigAligns = @
{ $self->contigAligns };
520 my $west = $pane->west + (2 * $font->width);
522 open my $MAPFILE, ">" , $mapFilename or die "$! writing map file $mapFilename";
524 print $MAPFILE "<map name=\"contigmap_$imageName\">";
526 #draws the info for each strand in the alignment
527 for my $i (0..(@contigAligns - 1)) {
528 my $y = $pane->north + ($i * $textHeight);
529 my $contigAlign = $contigAligns[$i];
531 my $total = $contigAlign->endLoc - $contigAlign->startLoc;
532 my $used = $total - ($contigAlign->startTrim + $contigAlign->endTrim);
534 #sets the source ID to a 30-character string
535 my $sourceID = $contigAlign->sourceID;
536 $sourceID = substr($sourceID, 0, 26) . '... ' if(length($sourceID) > 29);
537 $sourceID = sprintf("%-30.30s", $sourceID);
539 #round down to avoid displaying 100% where there it's not a complete match
540 my $percent = $self->round((($used * 100) / $total ) - .5);
542 #set the base pair count to a 6-digit string (to even out 3-digit and 4-digit numbers)
543 my $bpCount = sprintf('%-6.6s', $total . 'bp');
545 my $label = "$sourceID $bpCount ($percent%)";
547 $image->string($font, $west, $y, $label, (($contigAlign->strand eq '+') ?
$BLACK : $BLUE));
549 #die $label . "\n" . $contigAlign->sequenceID . "\n" . $linkBasename;
551 printf $MAPFILE "<area coords=\"%d,%d,%d,%d\" href=\"%s%s\">\n",
552 ($west, $y, $west + (length($label) * $font->width), $y + $font->height,
553 $linkBasename, $contigAlign->sequenceID);
556 print $MAPFILE "</map>\n";
560 #Draws the legend for the graph
561 method drawLegend
(Ref
$image!, Object
$pane!,
562 Ref
$font!, Str
$imageName!, Int
$length!) {
563 my $north = $pane->north + $PAD;
564 my $west = $pane->west + (5 * $PAD);
565 my $line = $font->height;
568 $label = "Alignment Image: $imageName";
569 $image->string($font, $west, $north, $label, $BLACK);
571 my @labels = ("Alignment Image: $imageName", "Given Strand",
572 "Reverse Complement Strand", "Trimmed (non-matching) Sequence");
573 my @colors = ($BLACK, $BLACK, $BLUE, $RED);
575 for my $i (0..($#labels)) {
576 $image->string($font, $west + 20, $north + (($i + 1.5) * $line), $labels[$i], $colors[$i]);
577 $image->line($west, $north + (($i + 2) * $line), $west + 15, $north + (($i + 2) * $line), $colors[$i]);
580 $label = "Highlighted Strand";
581 $image->filledRectangle($west, $north + ((@labels + 1.5) * $line),
582 $pane->east, $north + ((@labels + 2.5) * $line),
584 $image->string($font, $west + 20, $north + ((@labels + 1.5) * $line), $label, $BLACK);
586 $label = "Total Length: $length base pairs";
587 $image->string($font, $west + 20, $north + (7 * $line), $label, $BLACK);
590 #returns the number rounded to the nearest integer
591 method round
(Num
$num!) {
593 $num =~ s/(\d*)\.\d*/$1/;
597 #returns the number rounded up to the next multiple of block
598 method roundTo
(Int
$num!, Int
$block!) { return $num + $block - ($num % $block); }
601 __PACKAGE__
->meta->make_immutable; # be #
602 return 1; # dragons #
606 CXGN::Transcript::Unigene calls this module.
608 This has been written with Moose, a postmodern object-orientation system for Perl, and uses the Moose extension MooseX::Method::Signatures.