File Coverage

blib/lib/Chromosome/Map.pm
Criterion Covered Total %
statement 4 6 66.6
branch n/a
condition n/a
subroutine 2 2 100.0
pod n/a
total 6 8 75.0


line stmt bran cond sub pod time code
1             package Chromosome::Map;
2              
3 2     2   297204 use strict;
  2         7  
  2         78  
4 2     2   4218 use GD;
  0            
  0            
5              
6             use Chromosome::Map::Track;
7              
8             use constant FONT_MEDIUM => 'gdMediumBoldFont';
9             use constant FONT_SMALL => 'gdSmallFont';
10             use constant FONT_TINY => 'gdTinyFont';
11              
12             use constant PAD_TOP => 20;
13             use constant PAD_BOTTOM => 40;
14             use constant PAD_LEFT => 20;
15             use constant PAD_RIGHT => 20;
16              
17             use constant CHR_WIDTH => 6;
18             use constant CHR_PAD_LEFT => 40;
19             use constant CHR_PAD_TOP => 30;
20             use constant PAD_SCALE => 20; # pixel interval for chromosome scale display
21              
22             our $VERSION = '0.01';
23              
24             #-------------------------------------------------------------------------------
25             # public methods
26             #-------------------------------------------------------------------------------
27              
28             sub new {
29             my $class = shift;
30             $class = ref($class) || $class;
31            
32             my %Options = @_;
33             my $self = {};
34             $self->{_name} = $Options{-name};
35             $self->{_size} = $Options{-length};
36             $self->{_start} = $Options{-start} || 0;
37             $self->{_units} = $Options{-units};
38             $self->{_height} = $Options{-height};
39             $self->{_width} = 0; # AUTO
40             # define padding, could override
41             $self->{_pad_left} = $Options{-pad_left} || PAD_LEFT;
42             $self->{_pad_right} = $Options{-pad_right} || PAD_RIGHT;
43             $self->{_pad_top} = $Options{-pad_top} || PAD_TOP;
44             $self->{_pad_bottom} = $Options{-pad_bottom} || PAD_BOTTOM;
45             $self->{_tracks} = {};
46             $self->{_ident_track} = 0;
47             bless $self,$class;
48             return $self;
49             }
50              
51             sub get_map_name {
52             my $self = shift;
53             return $self->{_name};
54             }
55              
56             sub get_map_start {
57             my ($self) = @_;
58             return $self->{_start};
59             }
60              
61             sub get_map_size {
62             my ($self) = @_;
63             return $self->{_size};
64             }
65              
66             sub get_map_units {
67             my ($self) = @_;
68             return $self->{_units};
69             }
70              
71             sub get_nb_tracks {
72             my ($self) = @_;
73             return keys ( %{$self->{_tracks}});
74             }
75              
76             sub get_list_track {
77             my ($self) = @_;
78             my %List = %{$self->{_tracks}};
79             return %List;
80             }
81              
82             sub add_track {
83             # the indice number is automatically increment
84             # the first track added will have an indice number of 1
85             # in case of adding FEATURE track, the indice number will be set to 0
86             # as this track must be the first closest to chr
87             # NOTE: only ONE feature track can be added to a map
88             my ($self,$track) = @_;
89             my $indice = $self->_inc_track_ident;
90            
91             # set indice value to 0 if track type = feature
92             # DO NOT add another feature track if exists
93             if ($track->get_track_type eq 'feature') {
94             $indice = 0;
95             return 0 if ($self->{_tracks}->{$indice});
96             }
97             $self->{_tracks}->{$indice} = $track;
98             $track->{_size} = $self->get_map_size;
99             $track->{_start} = $self->get_map_start;
100             return 1;
101             }
102              
103             sub png {
104             my $gd = shift->_gd;
105             $gd->png;
106             }
107              
108             sub svg {
109             my $gd = shift->_gd;
110             $gd->svg;
111             }
112              
113              
114             #-------------------------------------------------------------------------------
115             # private methods
116             #-------------------------------------------------------------------------------
117              
118             sub _get_track_ident {
119             my $self = shift;
120             return $self->{_ident_track};
121             }
122              
123             sub _inc_track_ident {
124             my $self = shift;
125             my $ident = $self->_get_track_ident;
126             $ident++;
127             $self->{_ident_track} = $ident;
128             return $ident++;
129             }
130              
131             sub _scale {
132             # give the value of chromosomal unit for 1 px
133             my $self = shift;
134             $self->{_scale} = $self->_get_chr_length / ($self->get_map_size - $self->get_map_start);
135             }
136              
137             sub _get_chr_length {
138             # return the pixel value available for chr drawing
139             my $self = shift;
140             my $chr_length = $self->_height - (PAD_TOP + PAD_BOTTOM + CHR_PAD_TOP);
141             return $chr_length;
142             }
143              
144             sub _locate_element {
145             # return the y-axis pixel value for one element
146             # i.e element at 24.9 cM will have an y value = 340 px
147             my ($self,$value) = @_;
148             my $y = $self->_get_y_top + ( ($value - $self->get_map_start) * $self->_scale);
149             return $y;
150             }
151              
152             sub _get_map_width {
153             # use each x_bound track value to calculate final image width
154             my $self = shift;
155             my %Track = $self->get_list_track;
156             my $img_width;
157             foreach my $num (sort {$a <=> $b} keys %Track) {
158             my $track = $Track{$num};
159             $img_width += $track->_get_xbound;
160             }
161             return $img_width;
162             }
163              
164             sub _is_extra_bottom_padding {
165             # search the maximum value of extra_bottom_padding
166             my $self = shift;
167             my %Track = $self->get_list_track;
168             my $extra_padding = 0;
169             foreach my $num (sort {$a <=> $b} keys %Track) {
170             my $track = $Track{$num};
171             $extra_padding = $track->_get_extra_bottom_padding if ($track->_get_extra_bottom_padding > $extra_padding);
172             }
173             return $extra_padding;
174             }
175              
176             sub _height {
177             my $self = shift;
178             return $self->{_height};
179             }
180              
181             sub _pad_left {
182             my $self = shift;
183             return $self->{_pad_left};
184             }
185              
186             sub _pad_right {
187             my $self = shift;
188             return $self->{_pad_right};
189             }
190              
191             sub _pad_top {
192             my $self = shift;
193             return $self->{_pad_top};
194             }
195              
196             sub _pad_bottom {
197             my $self = shift;
198             return $self->{_pad_bottom};
199             }
200              
201             sub _get_y_top {
202             my $self = shift;
203             return ($self->_pad_top + CHR_PAD_TOP);
204             }
205              
206             sub _get_y_bottom {
207             my $self = shift;
208             return ($self->_height - $self->_pad_bottom);
209             }
210              
211             sub _get_screen_size_ratio {
212             # return a "printable" size ratio
213             my $self = shift;
214             my $size = $self->get_map_size;
215             my $screen_size = 1;
216            
217             $screen_size = 1000 if ($size > 10_000);
218             $screen_size = 1_000_000 if ($size > 10_000_000);
219             return $screen_size;
220             }
221              
222             sub _gd {
223             use constant TRACK_PAD => 3;
224             use constant CHR_WIDTH_TRACK => 70;
225            
226             my $self = shift;
227              
228             # Define the choosen font size using constant name
229             my $font_size_medium = FONT_MEDIUM;
230             my $font_size_small = FONT_SMALL;
231             my $font_size_tiny = FONT_TINY;
232             my $font_medium = GD->$font_size_medium;
233             my $font_small = GD->$font_size_small;
234             my $font_tiny = GD->$font_size_tiny;
235              
236             # first render tracks
237             my %Track_list = $self->get_list_track;
238             foreach my $num (sort {$a <=> $b} keys %Track_list) {
239             my $track = $Track_list{$num};
240             $track->_render_track($self);
241             }
242              
243             # defined Y0 and Ymax (map limits)
244             my $y_chr_top = $self->_get_y_top;
245             my $y_chr_bottom = $self->_get_y_bottom;
246             my $x_chr = $self->_pad_left + CHR_PAD_LEFT;
247            
248             # create final image
249             my $width = CHR_WIDTH_TRACK + $self->_get_map_width + $self->_pad_left;
250             my $height = $self->_height + $self->_is_extra_bottom_padding;
251             my $img = GD::Image->new($width,$height);
252              
253             # adding each IMG track to final IMG
254             my $x_track = $x_chr + CHR_WIDTH + 1;
255             foreach my $num (sort {$a <=> $b} keys %Track_list) {
256             my $track = $Track_list{$num};
257             my $gd = $track->_get_gd;
258             my $x_bound = $track->_get_xbound;
259             $x_track += TRACK_PAD if ($track->get_track_type ne 'feature');
260             $img->copy ($gd, $x_track, 0, 0, 0, $x_bound, $height);
261             $x_track += $x_bound;
262             }
263            
264             # get images scale (unit per pixel)
265             my $scale = $self->_scale;
266            
267             # colors definition
268             my $black = $img->colorAllocate(0,0,0);
269             my $white = $img->colorAllocate(255,255,255);
270            
271             $img->fill(0,0,$white);
272            
273             # write map name and draw CHR rectangle
274             my $screen_name = $self->get_map_name." (".$self->get_map_units.")";
275             $img->string($font_medium, $self->_pad_top, $self->_pad_left,$screen_name, $black);
276             $img->filledRectangle($x_chr, $y_chr_top, $x_chr+CHR_WIDTH, $y_chr_bottom, $black);
277            
278             # draw chromosomal scale
279             $self->_draw_grid($img,$x_chr);
280            
281             return $self->{_gd} = $img;
282             }
283              
284              
285             sub _ticks {
286             # calculate major and minor ticks, given a start position
287             # modified from Bio::Graphics::Panel module
288             # the MIN_GD_VALUE is used when the map scale is too big
289             use constant MIN_WIDTH => 40;
290             use constant MIN_GD_VALUE => 50;
291            
292             my $self = shift;
293            
294             my $length = $self->get_map_size;
295             my $min_width = MIN_WIDTH;
296            
297             # figure out tick mark scale
298             # we want no more than 1 major tick mark every 40 pixels
299             # and enough room for the labels
300             my $scale = $self->_scale;
301            
302             $min_width = MIN_GD_VALUE if ($scale > $min_width);
303              
304             my $interval = 1;
305              
306             while (1) {
307             my $pixels = $interval * $scale;
308             last if $pixels >= $min_width;
309             $interval *= 10;
310             }
311             return ($interval,$interval/10);
312             }
313              
314             sub _draw_grid {
315             # draw a grid scale
316             # modified from Bio::Graphics::Panel module
317             use constant GRID_WIDTH => 3;
318            
319             my ($self,$gd,$x_chr) = @_;
320            
321             my $black = $gd->colorAllocate(0,0,0);
322              
323             my @positions;
324             my ($major,$minor) = $self->_ticks;
325              
326             my $first_tick = $minor * int($self->get_map_start/$minor);
327              
328             for (my $i = $first_tick; $i <= $self->get_map_size; $i += $minor) {
329             push @positions,$i;
330             }
331            
332             my $size_ratio = $self->_get_screen_size_ratio;
333             my $x_scale = $self->_pad_left;
334             for my $tick (@positions) {
335             my $y = $self->_locate_element($tick);
336             my $offscale_major = 0;
337             if ($tick % $major == 0) {
338             $offscale_major = GRID_WIDTH;
339             my $screen_value_grid = sprintf ("%.1f",($tick / $size_ratio));
340             $gd->string(gdTinyFont,$x_scale,$y-5,$screen_value_grid,$black);
341             }
342             $gd->line($x_chr - GRID_WIDTH - $offscale_major, $y, $x_chr, $y, $black);
343             }
344             }
345              
346             1;
347              
348             __END__