Merge pull request #5 from solgenomics/topic/fix_il_maps
[cview.git] / lib / CXGN / Cview / Chromosome / Vector.pm
blobf5c7b3e1d34d84abfac717bb38f5e24ba21fdc0b
1 package CXGN::Cview::Chromosome::Vector;
3 =head1 NAME
5 CXGN::Cview::Chromosome::Vector - a class to draw circular vectors, plasmids, or genomes
7 =head1 DESCRIPTION
9 This class inherits from L<CXGN::Cview::Chromosome>, but represents a circular DNA molecule. The only marker type that is supported is L<CXGN::Cview::Marker::VectorFeature>.
11 The height property of the chromosome object is used to set the height in pixels of the circular structure (represents the diameter) whereas the width property represents the thickness of the molecule. The outer edge of the vector image is represented by height/2, and the thickness is going inwards from there.
13 The mapunits2pixels function has been overridden to yield two coordinates, representing the position on the outer edge of the visual representation of the vector.
15 A function, angle(), has been added to calculate the angle for a given map position.
17 The default map units are bp (set in the constructor).
19 =head1 AUTHOR
21 Lukas Mueller <lam87@cornell.edu>
23 =cut
25 use strict;
26 use warnings;
28 use CXGN::Cview::Marker::VectorFeature;
30 use base qw | CXGN::Cview::Chromosome |;
32 our $pi = 3.1415962;
34 =head2 new
36 Usage:
37 Desc:
38 Ret:
39 Args:
40 Side Effects:
41 Example:
43 =cut
45 sub new {
46 my $class = shift;
48 my $self = $class->SUPER::new(@_);
49 $self->set_units("bp");
50 return $self;
55 =head2 mapunits2pixels
57 Usage:
58 Desc:
59 Ret:
60 Args:
61 Side Effects:
62 Example:
64 =cut
66 sub mapunits2pixels {
67 my $self = shift;
68 my $mapunits = shift;
70 my $angle = $self->angle($mapunits);
72 my $x = sin($angle) * $self->get_radius() + $self->get_X();
73 my $y = cos($angle) * $self->get_radius() + $self->get_Y();
75 return ($x, $y);
79 =head2 angle
81 Usage: $v->angle($offset);
82 Desc: returns the angle on the circle for the given
83 offset
84 Ret: an angle in radians
85 Args: the offset in map units
87 =cut
90 sub angle {
91 my $self = shift;
92 my $mapunits = shift;
93 return (2 * $pi - ($mapunits / $self->get_length()) * ( 2 * $pi) - $pi);
97 =head2 render
99 Usage:
100 Desc:
101 Ret:
102 Args:
103 Side Effects:
104 Example:
106 =cut
108 sub render {
109 my $self = shift;
110 my $image = shift;
112 $self->layout();
114 $self->draw_chromosome($image);
115 $self->draw_markers($image);
116 $self->draw_caption($image);
119 =head2 layout
121 Usage: $v->layout()
122 Desc: lays out the vector and labels
123 Ret: nothing
124 Args: none
125 Side Effects: coordinates calculated in layout are
126 used to render the vector with render()
127 Example:
129 =cut
131 sub layout {
132 my $self = shift;
134 my @right_markers = ();
135 my @left_markers = ();
137 # set each marker to the corresponding x height
138 # of its corresponding vector position
140 my $marker_name = 1;
141 my $r = $self->get_radius() * 1.4;
143 foreach my $m ($self->get_markers()) {
145 my ($x, $y) = $m->get_chromosome()->mapunits2pixels($m->get_offset());
146 my $angle = $m->get_chromosome()->angle($m->get_offset());
147 $m->get_label()->set_Y($y);
149 $m->get_label()->set_reference_point($x, $y);
150 $m->set_marker_name($marker_name);
152 # is the marker on the right side of the vector?
154 if ($x >= $self->get_X()) {
155 $m->set_label_side("right");
156 $m->get_label()->set_X( $self->get_X() + $r * sin($angle) );
157 push @right_markers, $m;
159 else {
160 # the label is on the left...
162 $m->get_label()->align_right(); # the line of the label should attach on the right side of the label
163 $m->get_label()->set_X( $self->get_X() + $r * sin($angle));
164 $m->set_label_side("left");
165 push @left_markers, $m;
167 $marker_name++;
170 @left_markers = sort _sort_by_y_coord @left_markers;
171 @right_markers = sort _sort_by_y_coord @right_markers;
173 @{$self->{left_markers}} = @left_markers;
174 @{$self->{right_markers}} = @right_markers;
176 $self->_distribute_labels(@left_markers);
177 $self->_distribute_labels(@right_markers);
182 sub _sort_by_y_coord {
183 my ($a_x, $a_y) = $a->get_chromosome()->mapunits2pixels($a->get_offset());
184 my ($b_x, $b_y) = $b->get_chromosome()->mapunits2pixels($b->get_offset());
185 $a_y <=> $b_y;
189 sub _distribute_labels {
190 my $self = shift;
191 my @m = @_;
193 if (!@m) { return; }
195 # calculate the downwards offsets
197 my $previous_labelpos = 0;
198 my %downwards=();
199 foreach my $m (@m) {
200 #die "marker name = ". $m->get_name() ."\n";
201 #if ($m->is_visible() && $m->is_label_visible()) {
202 my $offset= $m->get_offset();
203 my ($x, $y) = $self->mapunits2pixels($offset);
204 my $angle = $self->angle($offset);
205 my $labelpos = cos($angle) * ($m->get_label_spacer() + $self->get_radius()) + $self->get_Y();
206 my $labelheight = $m -> get_label_height();
207 #print STDERR "label height: $labelheight\n";
209 if (($labelpos-$labelheight)<$previous_labelpos) {
210 $labelpos = $previous_labelpos+$labelheight;
211 if (exists($downwards{$m->get_name()})) { print STDERR "CATASTROPHE: Duplicate marker name ".($m->get_name())."\n"; }
212 $downwards{$m->get_name()} = $labelpos;
214 else {
215 $downwards{$m->get_name()}=$labelpos;
217 $previous_labelpos = $labelpos;
221 # calculate the upwards offsets
223 my %upwards = ();
224 my @reverse_markers = reverse(@m);
225 my $top_angle = $self->angle($reverse_markers[0]->get_offset());
226 my $toplabelpos = 99999; #sin($top_angle) * ($self->get_height()/2 + $reverse_markers[0]->get_label_spacer());
227 foreach my $m (@reverse_markers) {
228 #if ($m->is_visible() && $m->is_label_visible()) {
229 my $offset=$m->get_offset();
230 my ($x, $y) = $self->mapunits2pixels($offset);
231 my $angle = $self->angle($offset);
232 my $labelpos = cos($angle) * ( $m->get_label_spacer() + $self->get_radius()) + $self->get_Y();
233 #print STDERR "VERTICAL OFFSET = ".$self->get_vertical_offset()."\n";
234 my $labelheight= $m->get_label_height();
235 # print STDERR $m->get_name()." offset = $cM ID=".$m->get_id()."\n";
236 if (($labelpos+$labelheight)>$toplabelpos) {
237 $labelpos = $toplabelpos-$labelheight;
238 if (!$m->get_name()) { print STDERR "CATASTROPHE: Didn't get name on marker ".$m->get_id()."\n"; }
239 if (exists($upwards{$m->get_name})) { print STDERR "CATASTHROPHE: duplicate marker name ".$m->get_name()."\n"; }
240 $upwards{$m->get_name()} = $labelpos;
242 else {
243 $upwards{$m->get_name()}=$labelpos;
245 $toplabelpos = $labelpos;
249 # load into marker objects
251 foreach my $m (@m) {
252 my $marker_name = $m -> get_name();
253 # test to prevent warnings...
254 if (! $downwards{$marker_name}) { $downwards{$marker_name}=0; }
255 if (! $upwards{$marker_name}) { $upwards{$marker_name} = 0; }
257 my $pixels = int(($downwards{$marker_name}+$upwards{$marker_name})/2);
259 $m->get_label()->set_Y($pixels);
260 my $r = 200;
262 #print STDERR "Vertical pixels for marker ".$m->get_marker_name()." : $pixels.\n";
270 =head2 draw_chromosome
272 Usage:
273 Desc:
274 Ret:
275 Args:
276 Side Effects:
277 Example:
279 =cut
281 sub draw_chromosome {
282 my $self = shift;
283 my $image = shift;
285 # render vector backbone
287 my $draw_color = $image->colorAllocate(0,0,0);
288 $image->arc($self->get_X(), $self->get_Y(), $self->get_height(), $self->get_height(), 0, 360, $draw_color);
290 $image->arc($self->get_X(), $self->get_Y(), $self->get_height()- 2 * $self->get_width(), $self->get_height()- 2 * $self->get_width(), 0, 360, $draw_color);
292 my $fill_color = $image->colorAllocate($self->get_color());
293 $image->fill($self->get_X()+$self->get_height()/2 - 2, $self->get_Y(), $fill_color);
295 $self->draw_caption($image);
299 sub draw_markers {
300 my $self = shift;
301 my $image = shift;
303 # first render markers with ranges, otherwise this will mess up their highlighting.
304 # then draw the other marker ticks.
305 # then draw the labels.
307 foreach my $m (@{$self->{right_markers}}, @{$self->{left_markers}}) {
308 if ($m->has_range()) { $m->render_region($image); }
310 foreach my $m (@{$self->{right_markers}}, @{$self->{left_markers}}) {
311 $m->get_label()->render($image);
313 foreach my $m (@{$self->{right_markers}}, @{$self->{left_markers}}) {
314 if (!$m->has_range()) { $m->draw_tick($image); }
321 =head2 draw_caption
323 Usage: $v->draw_caption($image)
324 Desc: draws the caption on $image, centered in
325 the vector.
326 Ret:
327 Args:
328 Side Effects:
329 Example:
331 =cut
333 sub draw_caption {
334 my $self = shift;
335 my $image = shift;
337 my $font = GD::Font->Giant;
338 my $color = $image->colorAllocate(0, 0, 0);
340 my $x = $self->get_X() - $font->width() * length($self->get_caption()) / 2;
341 my $y = $self->get_Y() - $font->height();
343 $image->string($font, $x, $y, $self->get_caption(), $color);
345 $font = GD::Font->Small;
347 my $string = "(".$self->get_length()." bp)";
348 $x = $self->get_X() - $font->width() * length($string)/2 ;
349 $y = $self->get_Y() + $font->height()/2;
350 $image->string($font, $x, $y, $string, $color);
354 =head2 get_radius
356 Usage: my $r = $v->get_radius();
357 Desc: returns the radius of the circle deliniating the
358 vector. There is no setter, use set_height() instead,
359 which corresponds to the diameter.
360 Ret:
361 Args:
362 Side Effects:
363 Example:
365 =cut
367 sub get_radius {
368 my $self = shift;
369 return $self->get_height()/2;
373 return 1;