A chromosome class for scaffold-based chromosomes.
[cview.git] / lib / CXGN / Cview / Chromosome.pm
blobb589d58e5846085c3be6b679b8270b5f7c7d3d82
3 =head1 NAME
5 CXGN::Cview::Chromosome - a class for drawing chromosomes.
7 =head1 DESCRIPTION
9 Inherits from L<CXGN::Cview::ImageI>. The chromosome can be defined by adding markers as CXGN::Cview::Marker objects, using the add_marker() function. An object defining a ruler (CXGN::Cview::Ruler) can be obtained by calling the get_ruler() function. The length of the chromosome is either defined implicitly by the marker with the highest cM position, or explicitly using the set_length() function. The placement of the chromosome is defined by CXGN::Cview::ImageI defined functions such as set_horizontal_offset(), which defines the mid-line of the chromosome, and set_vertical_offset(), which defines the top edge.
11 Chromosomes can be rendered either in full or in section. The sections can start at cM positions other than 0 and are rendered slightly differently. Use set_section() to define the section.
13 The render() function will render the chromosome on the GD::Image supplied. It calls the subroutines to draw the chromosomes, lays out the marker labels and then calls the render function on each marker object.
15 Certain aspects of the appearance of the chromosome can be changed. For example, the color of the chromosome can be changed using set_color(). Labels can be rendered on the left or on the right of the chromosome, using set_labels_right() or set_labels_left().
17 Markers with the highest LOD score or rendered last, such that they will appear on the chromosome at the expense of lower scoring markers.
19 Chromosome areas can be rendered clickable using the function rasterize().
21 =head1 SEE ALSO
23 See also the documentation in L<CXGN::Cview> and L<CXGN::Cview::ImageI>.
25 =head1 AUTHOR(S)
27 Lukas Mueller (lam87@cornell.edu)
29 =head1 FUNCTIONS
31 CXGN::Cview::Chromosome has the following methods:
33 =cut
35 use strict;
36 use CXGN::Cview::ImageObject;
37 use CXGN::Cview::Marker;
38 use CXGN::Cview::Marker::SequencedBAC;
39 use CXGN::Cview::Ruler;
40 use CXGN::Cview::Ruler::PachyteneRuler;
41 use CXGN::Cview::Chromosome::BarGraph;
44 package CXGN::Cview::Chromosome;
46 use base qw(CXGN::Cview::ImageObject);
48 use GD;
50 =head2 function new()
52 my $c = CXGN::Cview::Chromosome -> new( chr number, height in pixels, horizontal offset, vertical offset, [start cM], [end cM])
54 Creates a new chromosome object. The horizontal offset denotes the offset of the chromosome mid-line. The vertical offset defines the upper end of the chromosome. Note that some renditions of the chromosome will add round edges on the top and on the bottom, so that the rounded top position will be smaller than the vertical offset.
56 Optionally, start_cM and end_cM can be supplied, which will set the start and end in cM for a chromosome section. The chromosome will be rendered as a section, i.e, the ends will be flat instead of rounded.
58 =cut
60 sub new {
61 my $class = shift;
63 my $chr_nr = shift;
64 my $height = shift;
65 my $x = shift;
66 my $y = shift;
67 my $start = shift;
68 my $end = shift;
70 my $self = $class -> SUPER::new($x, $y, $height);
72 $self -> set_start_cM($start); # if a chromosome section, where the section starts, in cM
73 $self -> set_end_cM($end); # if a chromosome section, where the section ends, in cM
75 $self -> {markers} = ();
77 $self->set_labels_right();
78 $self -> set_width(20); # default width
79 $self -> {curved_height} = $self->get_width();
81 $self->{chr_nr}=$chr_nr;
82 $self->set_name($chr_nr);
84 $self -> {font} = GD::Font->Small();
85 # set default colors
86 $self -> set_horizontal_offset($x);
87 $self -> set_vertical_offset($y);
88 $self -> set_color(200, 150, 150);
89 $self -> set_hilite_color(255, 255, 0);
90 $self -> set_outline_color(0,0,0);
91 $self -> set_hide_marker_offset();
92 $self -> set_ruler( CXGN::Cview::Ruler->new($self->get_horizontal_offset(), $self->get_vertical_offset(), $self->get_height(), 0, $self->get_length()) );
93 # don't do this - will cause deep recursion... $self -> set_bargraph( CXGN::Cview::Chromosome::BarGraph->new($self->get_horizontal_offset(), $self->get_vertical_offset(), $self->get_height(), 0, $self->get_length()));
94 $self -> hide_bargraph(); # by default, do not show the bar graph.
95 return $self;
98 =head2 function set_height()
100 $chr->set_height($height)
102 Sets the height of the chromosome in pixels. Recalculates all the scaling information.
103 Implemented in superclass.
105 =cut
108 =head2 function get_height()
110 $height = $chr ->get_height()
112 Gets the height of the chromosome in pixels. Implemented in the superclass.
114 =cut
117 =head2 function set_length()
119 sets the length in map units [cM].
121 This can also be automatically determined if not set manually, to the offset of the marker with the highest map unit value.
123 =cut
126 sub set_length {
127 my $self=shift;
128 $self->{chromosome_length_cM}=shift;
131 =head2 function get_length()
133 gets the length of the chromosome in map units.
135 =cut
137 sub get_length {
138 my $self=shift;
140 return $self->{chromosome_length_cM};
144 =head2 function get_units()
146 Synopsis:
147 Arguments:
148 Returns:
149 Side effects:
150 Description:
152 =cut
154 sub get_units {
155 my $self=shift;
156 return $self->get_ruler()->get_units();
159 =head2 function set_units()
161 Synopsis:
162 Arguments:
163 Returns:
164 Side effects:
165 Description:
167 =cut
169 sub set_units {
170 my $self=shift;
171 my $units = shift;
172 $self->get_ruler()->set_units($units);
175 =head2 function get_ruler()
177 Synopsis:
178 Arguments:
179 Returns:
180 Side effects:
181 Description:
183 =cut
185 sub get_ruler {
186 my $self=shift;
187 return $self->{ruler};
190 =head2 function set_ruler()
192 Synopsis:
193 Arguments:
194 Returns:
195 Side effects:
196 Description:
198 =cut
200 sub set_ruler {
201 my $self=shift;
202 $self->{ruler}=shift;
205 =head2 accessors set_bargraph(), get_bargraph()
207 Property: an associated bar graph with this chromosome
208 The bargraph is a CXGN::Cview::Chromosome::BarGraph
209 object. The default is no bar graph, ie, an empty
210 bar graph is initialized in the constructor.
212 Side Effects:
213 Description:
215 =cut
217 sub get_bargraph {
218 my $self=shift;
219 return $self->{bargraph};
222 sub set_bargraph {
223 my $self=shift;
224 $self->{bargraph}=shift;
228 =head2 functions show_bargraph(), hide_bargraph()
230 Synopsis:
231 Arguments:
232 Returns:
233 Side effects:
234 Description:
236 =cut
238 sub show_bargraph {
239 my $self = shift;
240 $self->{show_bargraph}=1;
243 sub hide_bargraph {
244 my $self = shift;
245 $self->{show_bargraph}=0;
248 =head2 function set_section()
250 $chr->set_section($start_in_map_units, $end_in_map_units);
252 Defines the chromosome as a section. The section starts at the start coordinate in map units, and ends at the end coordinate in map units. Chromosomes that are sections are rendered differently than full chromosomes. The section will be rendered so that it fills the entire height of the chromosome as defined with new or set_height, and the top edge will be drawn at the horizontal and vertical offset defined in the new call or with set_horizonatal_offset and set_vertical_offset.
254 =cut
258 sub set_section {
259 my $self = shift;
260 $self -> set_start_cM(shift);
261 $self -> set_end_cM(shift);
262 $self -> {is_section} =1;
265 =head2 function get_section()
267 $flag = $chr->is_section()
269 Returns true if the chromosome $chr is a section.
271 =cut
273 sub is_section {
274 my $self = shift;
275 return $self -> {is_section};
278 =head2 function set_hilite()
280 $chr->set_hilite(start_coord, end_coord)
282 Highlights the region of the chromosome between start_coord and end_coord with the hilite_color (which can be set with set_hilite_color, see below).
284 =cut
286 sub set_hilite {
287 my $self = shift;
288 $self->{hilite_start}=shift;
289 $self->{hilite_end}=shift;
292 =head2 function set_hilite_color()
294 $chr->set_hilite($red_channel, $green_channel, $blue_channel)
296 Sets the hilite color for chromosome highlighting. Three values between 0 and 255 are required for defining red, green and blue channels. The default color is yellow (255, 255,0)
298 =cut
300 sub set_hilite_color {
301 my $self = shift;
302 $self->{hilite_color}[0]=shift;
303 $self->{hilite_color}[1]=shift;
304 $self->{hilite_color}[2]=shift;
307 sub get_hilite_color {
308 my $self = shift;
309 return @{$self->{hilite_color}};
314 =head2 function set_color()
316 Sets the chromosome outline color. Three values between 0 and 255 are required for defining red, green and blue channels. The default color is 0,0,0, which is black. This function is defined in the parent class.
318 =cut
320 sub set_outline_color {
321 my $self = shift;
322 $self->{outline_color}[0]=shift;
323 $self->{outline_color}[1]=shift;
324 $self->{outline_color}[2]=shift;
328 =head2 function set_caption()
330 Sets the caption of the chromosome. The caption will be drawn centered on the top of the chromosome. Usually, the chromosome number should be displayed.
332 =cut
335 sub set_caption {
336 my $self = shift;
337 $self->{caption}=shift;
340 sub get_caption {
341 my $self = shift;
342 return $self->{caption};
345 =head2 function set_labels_left()
347 Causes the labels to be displayed on the left side of the chromosome.
349 =cut
351 sub set_labels_left {
352 my $self = shift;
353 $self->{label_side} = "left";
356 =head2 function set_labels_right()
358 Causes the labels to be displayed on the right side of the chromosome.
360 =cut
362 sub set_labels_right {
363 my $self = shift;
364 $self->{label_side} = "right";
367 =head2 function set_labels_none()
369 Causes the labels not to be displayed for the whole chromosome.
371 =cut
373 sub set_labels_none {
374 my $self = shift;
375 $self -> {label_side} = "";
378 =head2 function get_label_side()
380 Synopsis:
381 Arguments:
382 Returns:
383 Side effects:
384 Description:
386 =cut
388 sub get_label_side {
389 my $self = shift;
390 return $self->{label_side};
394 =head2 function set_display_marker_offset()
396 Causes the marker offsets to be displayed on the opposite side of the labels.
398 =cut
400 sub set_display_marker_offset {
402 # is set, the marker offset in cM will be displayed on the opposite side of the label
404 my $self = shift;
405 $self -> {display_marker_offset} = 1;
408 sub set_hide_marker_offset {
409 my $self = shift;
410 $self -> {display_marker_offset} = 0;
413 =head2 function render()
415 $chr-> render($image);
417 This function is called to render the chromosome and recursively calls all the rendering functions of the objects it contains. The image parameter is an GD image object. Usually, you should not have to call this function, but the MapImage object calls this function for you if you render a map.
419 =cut
421 sub render {
422 my $self = shift;
423 my $image = shift;
425 $self -> layout();
426 $self -> draw_chromosome($image);
427 $self -> render_markers($image);
430 =head2 function render_markers()
432 Synopsis:
433 Arguments:
434 Returns:
435 Side effects:
436 Description: This function renders the markers of the chromosome object.
437 The important thing is to render the markers such that the highest
438 load score marker is rendererd last.
440 =cut
442 sub render_markers {
443 my $self = shift;
444 my $image = shift;
446 my @markers = $self->get_markers();
448 foreach my $lod (-1,0,1,2,3) {
450 foreach my $m (@markers) {
452 if (!$m->isa("CXGN::Cview::Marker::SequencedBAC")) {
453 if ($m->get_confidence()==$lod) {
454 $m -> render($image);
456 else {
463 # render highlighted markers again to prevent clobbering
464 foreach my $m (@markers) {
465 if ($m->is_visible() && $m->get_hilited()) {
466 $m->render($image);
471 foreach my $m (@markers) {
472 if ($m->isa("CXGN::Cview::Marker::SequencedBAC")) {
473 $m->get_label()->set_text_color(200,200,80);
474 $m->get_label()->set_line_color(200,200,80);
475 $m->set_color(255,255,0);
476 $m->render($image);
483 # =head2 function render_labels
485 # Synopsis:
486 # Arguments:
487 # Returns:
488 # Side effects:
489 # Description:
491 # =cut
493 # sub render_labels {
494 # my $self = shift;
495 # my $image = shift;
496 # foreach my $m ($self->get_markers()) {
497 # if ($m->is_visible() && $m->get_label()->get_hilited()) {
498 # $m->get_label()->render($image);
505 sub get_enclosing_rect {
506 my $self = shift;
507 return (int($self->get_horizontal_offset()-$self->get_width()/2), $self->get_vertical_offset(), $self->get_horizontal_offset()+int($self->get_width()/2), $self->get_vertical_offset()+$self->{height});
510 =head2 function rasterize()
512 Causes the chromosome to be rasterized in the MapImage tags. A scaffold of rectangular areas is overlaid on the chromosome that can be used to link to something to this part of the chromosome. The link is supplied through set_rasterize_link. The parameter gives the size of the raster in cM. With the parameter set to zero, no rastering occurs.
514 =cut
516 sub rasterize {
517 # this function sets the rasterize variable, get_image_map then rasterizes the chromosome if necessary.
518 my $self = shift;
519 if (@_) {
520 $self->{rasterize}=shift;
522 return $self->{rasterize};
525 sub set_rasterize_link {
526 my $self = shift;
527 $self->{rasterize_link} = shift;
530 # the rasterize link has to be of the format http://url.domain.etc/etc/program?parameter=values&parameter=values&cM=
531 # rasterizatio will add the number of cM where clicked at the end of the link.
532 sub get_rasterize_link {
533 my $self = shift;
534 if (!exists($self->{rasterize_link}) || !defined($self->{rasterize_link})) {
535 $self->{rasterize_link}="";
537 return $self->{rasterize_link};
540 =head2 function get_image_map()
542 Gets the image map for the chromosome and all contained objects within chromosome. This is normally called by the MapImage object.
544 =cut
546 sub get_image_map {
547 my $self = shift;
548 my $coords = join ",", ($self -> get_enclosing_rect());
549 my $string = "";;
551 if ($self->get_url()) { $string = "<area shape=\"rect\" coords=\"".$coords."\" href=\"".$self->get_url()."\" alt=\"\" />";}
552 foreach my $m (($self->get_markers())) {
553 if ($m->is_visible()) {
554 $string .= $m -> get_image_map();
557 if ($self->rasterize() && $self->get_length()>0) {
558 my $raster = $self->get_length()/20;#$self->{rasterize}; # cM
559 my $steps = ($self->get_length()/$raster);
561 my $halfwidth = ($self->get_width()/2);
562 my $x = $self->get_horizontal_offset();
563 my $y = $self->get_vertical_offset();
564 my $box_pixel_height = $self->get_height()/$steps;
566 for (my $i=0; $i<=($steps+1); $i++) {
567 my $cM = $i*$raster;
568 my $pixels = $self->mapunits2pixels($cM)+$y;
570 $string .="<area shape=\"rect\" coords=\"".int($x-$halfwidth).",".int($pixels).",".int($x+$halfwidth).",".int($pixels+$box_pixel_height)."\" href=\"".$self->get_rasterize_link().$cM."\" alt=\"\" />\n";
573 return $string;
577 sub _sort_by_position {
578 return ($a->get_offset() <=> $b->get_offset);
581 =head2 function sort_markers()
583 Synopsis: $c->sort_markers()
584 Arguments: none
585 Returns: nothing
586 Side effects: sorts the markers according to their position
587 in the internal datastructure. This is required
588 for proper rendering of the marker labels; if
589 the markers are added in sequence, calling this
590 is superfluous.
591 Description:
593 =cut
597 sub sort_markers {
598 my $self=shift;
599 my @sorted_markers = sort _sort_by_position ($self->get_markers());
600 $self->set_markers(@sorted_markers);
603 sub _calculate_scaling_factor {
604 my $self = shift;
606 $self -> _calculate_chromosome_length();
607 if ($self->get_length()==0) { return 0; }
609 $self->{scaling_factor}=($self->get_height()/$self->get_length());
611 #print STDERR "calculating scaling factor. height in pixels: $self->{height} chromosome_length=$self->{chromosome_length_cM} scaling factor: $self->{scaling_factor}\n";
613 return $self->{scaling_factor};
616 sub get_scaling_factor {
617 my $self = shift;
618 if (!exists($self->{scaling_factor})) {
619 $self->{scaling_factor}=0;
620 warn "[CXGN::Cview::Chromosome] WARNING! Scaling factor is 0.\n";
622 return $self->{scaling_factor};
625 =head2 function add_marker()
627 $chr->add_marker($m);
629 Adds the marker object $m to the chromosome.
631 =cut
633 sub add_marker {
634 my $self = shift;
635 my $m = shift;
636 push @{$self->{markers}}, $m;
639 =head2 function get_markers()
641 my @m = $chr -> get_markers();
643 Gets all the markers in the chromosome as an array.
645 =cut
647 sub get_markers {
648 my $self = shift;
649 if (!defined($self->{markers})) { return (); }
650 return @{$self->{markers}};
653 sub set_markers {
654 my $self =shift;
655 @{$self->{markers}} = @_;
658 =head2 accessors set_start_cM(), get_start_cM()
660 Property:
661 Setter Args:
662 Getter Args:
663 Getter Ret:
664 Side Effects:
665 Description:
667 =cut
669 sub get_start_cM {
670 my $self=shift;
671 if (!exists($self->{start_cM}) || !defined($self->{start_cM})) { $self->{start_cM}=0; }
672 return $self->{start_cM};
675 sub set_start_cM {
676 my $self=shift;
677 $self->{start_cM}=shift;
680 =head2 accessors set_end_cM(), get_end_cM()
682 Property:
683 Setter Args:
684 Getter Args:
685 Getter Ret:
686 Side Effects:
687 Description:
689 =cut
691 sub get_end_cM {
692 my $self=shift;
693 if (!exists($self->{end_cM})) { $self->{end_cM}=0; }
694 return $self->{end_cM};
697 sub set_end_cM {
698 my $self=shift;
699 $self->{end_cM}=shift;
704 sub get_markers_in_interval {
705 my $self = shift;
706 my $start = shift; # start in cM
707 my $end = shift; # end position in cM
708 if (!$start && !$end) { $start = 0; $end = $self -> get_length()+1 ; }
709 my @markers;
710 foreach my $m (@{$self->{markers}}) {
711 if ($m -> get_offset() >= $start && $m->get_offset() <= $end) {
712 push @markers, $m;
715 return @markers; # returns all the markers in between start and end
718 sub get_frame_markers {
719 my $self = shift;
720 my @framemarkers = ();
721 foreach my $m (@{$self->{markers}}) {
722 if ($m->is_frame_marker()) {
723 push @framemarkers, $m;
726 return @framemarkers;
729 sub get_markers_ref {
730 my $self = shift;
731 return \@{$self->{markers}};
734 sub layout {
735 my $self = shift;
736 $self->_calculate_chromosome_length();
737 $self->_calculate_scaling_factor();
738 $self -> distribute_labels();
739 $self -> distribute_label_stacking();
743 sub _calculate_chromosome_length {
744 my $self = shift;
745 my $length = 0;
747 # get chromosome length in cM.
749 # if it is a section, we return the length of the section, as defined by start_cM and end_cM
751 if ($self->is_section()) {
752 if ($self->get_end_cM()>$self->{chromosome_length_cM}) {
753 $self->get_end_cM($self->{chromosome_length_cM});
755 $self->{chromosome_length_cM}=$self->get_end_cM()-$self->get_start_cM();
756 return $self->{chromosome_length_cM};
759 # it may be that chromosome_length has been manually set with set_length.
761 if ($self->get_length()) {
762 return $self->get_length();
765 # otherwise, we get the marker with the highest cM position.
766 # the length may have been set already by set_length...
767 else {
768 foreach my $m (@{$self->{markers}}) {
769 my $offset = $m -> get_offset();
770 if ($offset > $length) { $length = $offset; }
772 $self->{chromosome_length_cM} = $length;
774 return $self->{chromosome_length_cM};
777 # otherwise return some default length
779 return 10;
783 sub get_chromosome_length {
784 # same as get_length. Deprecated.
785 my $self = shift;
786 return $self->{chromosome_length_cM};
789 =head2 function mapunits2pixels()
791 my $pixels = $chr->mapunits2pixels($cM_pos);
793 Gets the number of pixels the cM value corresponds to. Note that you have to add the vertical chromosome offset (get_vertical_offset) to this number the get the actual image coordinates.
795 =cut
798 sub mapunits2pixels {
799 my $self = shift;
800 my $cM = shift;
802 if (! $cM) { $cM=0; }
803 my $pixels = ($cM - $self->get_start_cM()) * $self->get_scaling_factor();
804 #print STDERR "Scaling factor: $self->{scaling_factor} cM: $cM = $pixels pixels\n";
805 return $pixels;
808 =head2 function get_pixels_cM()
810 my $cM = $chr->get_pixels_cM($pixels);
812 Gets the number of cM that the number of $pixels correspond to. Note that you have to substract the vertical chromosome offset (get_vertical_offset) from the pixels this number the get the correct number of cM.
814 =cut
816 sub get_pixels_cM {
817 my $self = shift;
818 my $pixels = shift;
819 my $cM = ($pixels / $self->{scaling_factor}) + $self->get_start_cM();
824 sub is_visible {
825 my $self = shift;
826 my $cM = shift;
828 if ($self->is_section()) {
829 if ($cM >= $self->get_start_cM() && $cM <= $self ->get_end_cM()) {
830 return 1;
832 else {return 0; }
834 return 1;
838 =head2 function distribute_labels()
840 Synopsis: $c->distribute_labels()
841 Arguments: none
842 Returns: distributes the labels nicely along the chromosome
843 Side effects: changes the positions of the labels
844 Description: first it calculates label positions by setting the first label
845 to be equal to the top of the chromosome, then adjusts
846 all following label positions not to overlap with the
847 previous label, pushing them down if necessary.
848 Second, it calculates the label positions by setting the last
849 label to be equal to the end of the chromosome, and iterates
850 through all labels not to overlap with the previous label, if
851 necessary, pushing it up. To calculate the final label
852 position, it takes the average of the downwards and the
853 upwards iterations through the labels. Note that the labels need
854 to be ordered for this to work.
855 this function was renamed slightly (_ omitted) and refactored
856 such that left labels and right labels are distributed
857 separately.
859 =cut
861 sub distribute_labels {
862 my $self = shift;
863 $self->_distribute_labels("left");
864 $self->_distribute_labels("right");
869 sub _distribute_labels {
870 my $self = shift;
871 my $side = shift;
873 my @m = $self->get_markers();
874 my $lastlabelpos = 0;
876 # calculate the downwards offsets
878 my %downwards=();
880 if (!@m) { return; }
882 foreach my $m (@m) {
883 #if ($m->get_label_side() !~ /right|left/i) { die "Label side is not set correctly for marker ".$m->get_name()."\n"; }
884 if ($m->is_visible() && $m->is_label_visible() && ($m->get_label_side() eq "$side")) { # || !$m->get_label_side())) {
885 my $cM= $m->get_offset();
886 my $labelpos = $self->mapunits2pixels($cM)+$self->get_vertical_offset();
887 my $labelheight = $m -> get_label_height();
888 #print STDERR "label height: $labelheight\n";
890 if (($labelpos-$labelheight)<$lastlabelpos) {
891 $labelpos = $lastlabelpos+$labelheight;
892 if (exists($downwards{$m->get_name()})) { warn "CATASTROPHE: Duplicate marker name ".($m->get_name())."\n"; }
893 $downwards{$m->get_name()} = $labelpos;
895 else {
896 $downwards{$m->get_name()}=$labelpos;
898 $lastlabelpos = $labelpos;
902 # calculate the upwards offsets
904 my %upwards = ();
905 my $toplabelpos = $self->get_vertical_offset()+$self->get_height()+12+$m[-1]->get_label_height();
906 foreach my $m (reverse(@m)) {
907 if($m->is_visible() && $m->is_label_visible() && $m->get_label_side() eq "$side") {
908 my $cM=$m->get_offset();
909 my $labelpos = $self->mapunits2pixels($cM)+$self->get_vertical_offset();
910 #print STDERR "VERTICAL OFFSET = ".$self->get_vertical_offset()."\n";
911 my $labelheight= $m->get_label_height();
912 # print STDERR $m->get_name()." offset = $cM ID=".$m->get_id()."\n";
913 if (($labelpos+$labelheight)>$toplabelpos) {
914 $labelpos = $toplabelpos-$labelheight;
915 if (!$m->get_name()) { warn "CATASTROPHE: Didn't get name on marker ".$m->get_id()."\n"; }
916 if (exists($upwards{$m->get_name})) { warn "CATASTHROPHE: duplicate marker name ".$m->get_name()."\n"; }
917 $upwards{$m->get_name()} = $labelpos;
919 else {
920 $upwards{$m->get_name()}=$labelpos;
922 $toplabelpos = $labelpos;
926 # load into marker objects
928 foreach my $m (@m) {
929 if ($m->get_label_side() eq "$side") {
930 my $marker_name = $m -> get_name();
931 # test to prevent warnings...
932 if (! $downwards{$marker_name}) { $downwards{$marker_name}=0; }
933 if (! $upwards{$marker_name}) { $upwards{$marker_name} = 0; }
935 my $pixels = int(($downwards{$marker_name}+$upwards{$marker_name})/2);
936 #print STDERR "Vertical pixels for marker ".$m->get_marker_name()." : $pixels.\n";
937 $m->get_label()->set_vertical_offset($pixels);
942 =head2 function distribute_label_stacking()
944 Synopsis:
945 Arguments: none
946 Returns: nothing
947 Side effects: distributes the labels of the markers of type
948 RangeMarker such that they do not collide
949 vertically, and does this for each side
950 independently.
951 Description:
953 =cut
955 sub distribute_label_stacking {
956 my $self = shift;
957 $self->_distribute_label_stacking("left");
958 $self->_distribute_label_stacking("right");
961 sub _distribute_label_stacking {
962 my $self = shift;
963 my $side = shift;
964 my @tracks = ();
965 my $maximum_stacking_level = 0;
966 my $skipped = 0;
967 my $TRACK_LIMIT = 20000; # exit the loop if we exceed this number of tracks.
969 # the @tracks array keeps track of which positions are already occupied by giving the
970 # lower boundary to which the track has been filled.
971 # it is important that the markers are sorted by position in some way
972 # for this to look good (currently, for physical map, BACs are sorted by status, association_type
973 # and offset, which forces sequenced BACs to be rendered next to the chromosome
974 # and other BACs further away). All other maps should just be sorted by position.
976 foreach my $m ($self->get_markers()) {
977 if ($m->is_visible() && $m->get_label_side() eq $side) {
979 # define the position of the marker
980 my $upper_edge = $m->get_offset()-$m->get_north_range(); # the position of the top of the marker
981 if ($upper_edge < 0 || !defined($upper_edge)) { $upper_edge = 0; }
982 my $lower_edge = $m->get_offset()+$m->get_south_range(); # the position of the bottom of the marker
983 if ($lower_edge < 0 || !defined($lower_edge)) { $lower_edge = 0; }
986 my $current_track = 1;
989 while (defined($tracks[$current_track]) && ($tracks[$current_track]>($upper_edge - $m->get_label()->get_vertical_stacking_spacing()))) {
991 if (!exists($tracks[$current_track]) || !defined($tracks[$current_track])) { $tracks[$current_track]=0; }
994 # under certain conditions an infinite loop may be created. To prevent it, we set
995 # an arbitrary limit on the number of tracks supported.
996 if ($current_track>$TRACK_LIMIT) {
997 $skipped++;
998 # if ($m->get_marker_name() =~ /Lpen/) {
999 # print STDERR "Skipping pennellii bac ".($m->get_marker_name())." at position ".($m->get_offset())."\n";
1001 last();
1003 if ($current_track > $maximum_stacking_level) {
1004 $maximum_stacking_level = $current_track;
1006 $current_track++;
1009 # print STDERR "TRACK $current_track has lower bound $tracks[$current_track]. \n";
1010 $tracks[$current_track] = $lower_edge;
1012 $m->get_label()->set_stacking_level($current_track);
1014 #print STDERR "TRACKS: ".($m->get_marker_name())." [$current_track] = $tracks[$current_track], $upper_edge\n";
1018 # here we adjust the label_spacer property of the label to be higher than the highest track.
1019 # seems like a good idea but it may produce weird effects on some maps. Maybe this should
1020 # be factored out into another subroutine so that it could be called only if desired.
1022 foreach my $m ($self->get_markers()) {
1023 if ( ($m->get_label_side() eq "$side") && ($m->get_label()->get_stacking_height()>0)) {
1024 my $label_spacer = $m->get_label()->get_label_spacer();
1025 my $dynamic_spacer = ($maximum_stacking_level+1) * $m->get_label()->get_stacking_height()+10;
1026 if ($dynamic_spacer > $label_spacer ) {
1027 $m->get_label()->set_label_spacer( $dynamic_spacer );
1031 if ($skipped) {
1032 warn "Skipped $skipped because of space constraints (max $TRACK_LIMIT tracks).\n";
1036 =head2 function draw_chromosome()
1038 $chr->draw_chromosome($image, $type);
1040 Draws the chromosome on $image. Image is a GD image. The default chromosome rendering is as a 'sausage' type chromosome. A line model is available by supplying the type parameter "line". This is usually called by the MapImage object.
1042 =cut
1044 sub draw_chromosome {
1045 my $self = shift;
1046 my $image = shift;
1048 # draw chromosome outline
1050 if (! $self->{style}) { $self->{style}=""; }
1051 if ($self->{style} eq "line") {
1052 $self->draw_chromosome_line($image);
1054 elsif ($self->{style} eq "sausage") {
1055 $self->draw_chromosome_sausage($image);
1057 else { $self->draw_chromosome_sausage($image); }
1060 sub draw_chromosome_line {
1061 my $self=shift;
1062 my $image=shift;
1064 my $color = $image -> colorResolve($self->{color}[0], $self->{color}[1], $self->{color}[2]);
1065 #print STDERR "$self->{x}, $self->get_vertical_offset(), $self->{x}, $self->get_vertical_offset()+$self->{height}, $color\n";
1066 $image -> line($self->get_horizontal_offset(), $self->get_vertical_offset(), $self->get_horizontal_offset(), $self->get_vertical_offset()+$self->{height}, $color);
1069 sub draw_chromosome_sausage {
1070 my $self = shift;
1071 my $image = shift;
1073 # allocate colors
1075 my $outline_color = $image -> colorResolve($self->{outline_color}[0], $self->{outline_color}[1], $self->{outline_color}[2]);
1076 my $hilite_color = $image -> colorResolve($self->{hilite_color}[0], $self->{hilite_color}[1], $self->{hilite_color}[2]);
1077 my $color = $image -> colorResolve($self->{color}[0], $self->{color}[1], $self->{color}[2]);
1079 my $halfwidth = $self ->{width}/2;
1081 $image -> line($self->get_horizontal_offset() - $halfwidth, $self->get_vertical_offset() , $self->get_horizontal_offset()-$halfwidth, $self->get_vertical_offset()+$self->{height}, $outline_color);
1082 $image -> line($self->get_horizontal_offset() + $halfwidth, $self->get_vertical_offset() , $self->get_horizontal_offset()+$halfwidth, $self->get_vertical_offset()+$self->{height}, $outline_color);
1083 if ($self->is_section()) {
1084 my $text_color = $image -> colorResolve(50, 50, 50);
1085 $image -> line ($self->get_horizontal_offset()-$halfwidth, $self->get_vertical_offset(), $self->get_horizontal_offset()+$halfwidth, $self->get_vertical_offset(), $outline_color);
1086 $image -> line ($self->get_horizontal_offset()-$halfwidth, $self->get_vertical_offset()+$self->{height}, $self->get_horizontal_offset()+$halfwidth, $self->get_vertical_offset()+$self->{height}, $outline_color);
1087 $image -> fill($self->get_horizontal_offset(), $self->get_vertical_offset()+1, $color);
1088 my $top_label = int($self->get_start_cM()).$self->get_units();
1089 my $bottom_label = int($self ->{end_cM}).$self->get_units();
1090 $image -> string($self->{font}, $self->get_horizontal_offset()-$self->{font}->width()* length($top_label)/2, $self->get_vertical_offset()-$self->{font}->height()-3, $top_label, $text_color);
1091 $image -> string($self->{font}, $self->get_horizontal_offset() - $self->{font}->width() * length($bottom_label)/2, $self->get_vertical_offset()+$self->get_height()+3, $bottom_label,$text_color);
1093 else {
1094 $image -> setAntiAliased($outline_color);
1095 $image -> arc ($self->get_horizontal_offset(), $self->get_vertical_offset(), $self->{width}, $self->{curved_height}, 180, 0, GD::gdAntiAliased);
1096 $image -> arc ($self->get_horizontal_offset(), $self->get_vertical_offset()+$self->{height}, $self->{width}, $self->{curved_height}, 0, 180, GD::gdAntiAliased);
1097 $image -> fill ($self->get_horizontal_offset(), $self->get_vertical_offset(), $color);
1102 if ($self->{hilite_start} || $self->{hilite_end}) {
1104 # if we are dealing with a section, don't hilite more than the section...
1106 if ($self->is_section()) {
1107 if ($self->{hilite_start} < $self->get_start_cM()) {
1108 $self->{hilite_start} = $self->get_start_cM();
1110 if ($self->{hilite_end} > $self->get_end_cM()) {
1111 $self->{hilite_end} = $self->get_end_cM;
1115 my $start = $self->get_vertical_offset()+$self->mapunits2pixels($self->{hilite_start});
1116 my $end = $self->get_vertical_offset()+$self->mapunits2pixels($self->{hilite_end});
1117 $image -> rectangle($self->get_horizontal_offset()-$halfwidth,
1118 $start,
1119 $self->get_horizontal_offset()+$halfwidth,
1120 $end,
1121 $outline_color);
1122 $image -> fill ($self->get_horizontal_offset(), $start+1, $hilite_color);
1126 $self->draw_caption($image);
1129 sub draw_caption {
1130 my $self = shift;
1131 my $image = shift;
1133 my $outline_color = $image -> colorResolve(
1134 $self->{outline_color}[0],
1135 $self->{outline_color}[1],
1136 $self->{outline_color}[2]
1138 my $bigfont = GD::Font->Large();
1139 $image -> string(
1140 $bigfont,
1141 $self->get_horizontal_offset()- $bigfont->width() * length($self->get_caption())/2,
1142 $self->get_vertical_offset()-$bigfont->height()-$self->{curved_height}/2,
1143 $self->get_caption(), $outline_color );