16
14
point => \&draw_point,
17
# Default pad_left is recursive through all parts. We certainly
18
# don't want to do this for all parts in the graph.
21
return 0 unless $self->level == 0;
22
return $self->SUPER::pad_left(@_);
25
# Default pad_left is recursive through all parts. We certainly
26
# don't want to do this for all parts in the graph.
29
return 0 unless $self->level == 0;
30
return $self->SUPER::pad_right(@_);
20
34
shift->option('point_radius') || DEFAULT_POINT_RADIUS;
38
shift->Bio::Graphics::Glyph::generic::pad_top(@_);
43
my $pad = $self->Bio::Graphics::Glyph::generic::pad_bottom(@_);
44
if ($pad < ($self->font('gdTinyFont')->height)/4) {
45
$pad = ($self->font('gdTinyFont')->height)/4; # extra room for the scale
27
57
my ($gd,$dx,$dy) = @_;
28
58
my ($left,$top,$right,$bottom) = $self->calculate_boundaries($dx,$dy);
30
59
my @parts = $self->parts;
31
61
return $self->SUPER::draw(@_) unless @parts > 0;
33
63
my ($min_score,$max_score) = $self->minmax(\@parts);
65
my $side = $self->_determine_side();
35
67
# if a scale is called for, then we adjust the max and min to be even
36
68
# multiples of a power of 10.
37
if ($self->option('scale')) {
38
70
$max_score = max10($max_score);
39
71
$min_score = min10($min_score);
42
my $height = $self->option('height');
74
my $height = $self->height;
43
75
my $scale = $max_score > $min_score ? $height/($max_score-$min_score)
46
my $y = $dy + $self->top + $self->pad_top;
79
my $y = $top + $self->pad_top;
81
# position of "0" on the scale
82
my $y_origin = $min_score <= 0 ? $bottom - (0 - $min_score) * $scale : $bottom;
83
$y_origin = $top if $max_score < 0;
85
my $clip_ok = $self->option('clip');
86
$self->{_clip_ok} = $clip_ok;
87
$self->{_scale} = $scale;
88
$self->{_min_score} = $min_score;
89
$self->{_max_score} = $max_score;
91
$self->{_bottom} = $bottom;
48
93
# now seed all the parts with the information they need to draw their positions
50
my $s = eval {$_->feature->score};
51
96
next unless defined $s;
52
my $position = ($s-$min_score) * $scale;
53
$_->{_y_position} = $bottom - $position;
56
my $type = $self->option('graph_type');
57
$self->_draw_histogram($gd,$x,$y) if $type eq 'histogram';
58
$self->_draw_boxes($gd,$x,$y) if $type eq 'boxes';
59
$self->_draw_line ($gd,$x,$y) if $type eq 'line'
60
or $type eq 'linepoints';
61
$self->_draw_points($gd,$x,$y) if $type eq 'points'
62
or $type eq 'linepoints';
64
$self->_draw_scale($gd,$scale,$min_score,$max_score,$dx,$dy) if $self->option('scale');
97
$_->{_y_position} = $self->score2position($s);
100
my $type = $self->option('graph_type') || $self->option('graphtype') || 'boxes';
101
my $draw_method = $self->lookup_draw_method($type);
102
$self->throw("Invalid graph type '$type'") unless $draw_method;
103
$self->$draw_method($gd,$x,$y,$y_origin);
105
$self->_draw_scale($gd,$scale,$min_score,$max_score,$dx,$dy,$y_origin);
106
$self->draw_label(@_) if $self->option('label');
107
$self->draw_description(@_) if $self->option('description');
110
sub lookup_draw_method {
114
return '_draw_histogram' if $type eq 'histogram';
115
return '_draw_boxes' if $type eq 'boxes';
116
return '_draw_line' if $type eq 'line' or $type eq 'linepoints';
117
return '_draw_points' if $type eq 'points' or $type eq 'linepoints';
122
my $s = $self->option('score');
123
return $s if defined $s;
124
return eval { $self->feature->score };
131
return unless defined $score;
133
if ($self->{_clip_ok} && $score < $self->{_min_score}) {
134
return $self->{_bottom};
137
elsif ($self->{_clip_ok} && $score > $self->{_max_score}) {
138
return $self->{_top};
142
my $position = ($score-$self->{_min_score}) * $self->{_scale};
143
return $self->{_bottom} - $position;
67
147
sub log10 { log(shift)/log(10) }
151
return -min10(-$a) if $a<0;
152
return max10($a*10)/10 if $a < 1;
154
my $l=int(log10($a));
74
157
return $r*$l if int($r) == $r;
75
158
return $l*int(($a+$l)/$l);
163
return -max10(-$a) if $a<0;
164
return min10($a*10)/10 if $a < 1;
80
166
my $l=int(log10($a));
115
201
my ($x3,$y3,$x4,$y4) = $parts[-1]->calculate_boundaries($left,$top);
116
202
$gd->line($x4,$parts[-1]->{_y_position},$x4,$y4,$fgcolor);
118
# from left to right -- don't like this
119
# $gd->line($x1,$y2,$x4,$y4,$fgcolor);
121
204
# That's it. Not too hard.
124
207
sub _draw_boxes {
125
208
my $self = shift;
126
my ($gd,$left,$top) = @_;
128
my @parts = $self->parts;
129
my $fgcolor = $self->fgcolor;
130
my $bgcolor = $self->bgcolor;
131
my $height = $self->height;
209
my ($gd,$left,$top,$y_origin) = @_;
211
my @parts = $self->parts;
212
my $fgcolor = $self->fgcolor;
213
my $bgcolor = $self->bgcolor;
214
my $lw = $self->linewidth;
215
my $negative = $self->color('neg_color') || $bgcolor;
216
my $height = $self->height;
218
my $partcolor = $self->code_option('part_color');
219
my $factory = $self->factory;
133
221
# draw each of the component lines of the histogram surface
134
222
for (my $i = 0; $i < @parts; $i++) {
135
224
my $part = $parts[$i];
136
225
my $next = $parts[$i+1];
137
my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top);
138
$self->filled_box($gd,$x1,$part->{_y_position},$x2,$y2,$bgcolor,$fgcolor);
140
my ($x3,$y3,$x4,$y4) = $next->calculate_boundaries($left,$top);
141
$gd->line($x2,$y2,$x3,$y4,$fgcolor) if $x2 < $x3;
227
my ($color,$negcolor);
229
# special check here for the part_color being defined so as not to introduce lots of
230
# checking overhead when it isn't
232
$color = $factory->translate_color($factory->option($part,'part_color',0,0));
236
$negcolor = $negative;
239
# my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top);
240
my ($x1,$x2) = ($left+$part->{left},$left+$part->{left}+$part->{width}-1);
241
if ($part->{_y_position} < $y_origin) {
242
$self->filled_box($gd,$x1,$part->{_y_position},$x2,$y_origin,$color,$fgcolor,$lw);
244
$self->filled_box($gd,$x1,$y_origin,$x2,$part->{_y_position},$negcolor,$fgcolor,$lw);
178
282
my $bgcolor = $self->bgcolor;
179
283
my $pr = $self->point_radius;
285
my $partcolor = $self->code_option('part_color');
286
my $factory = $self->factory;
181
288
for my $part (@parts) {
182
my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top);
289
my ($x1,$x2) = ($left+$part->{left},$left+$part->{left}+$part->{width}-1);
183
290
my $x = ($x1+$x2)/2;
184
291
my $y = $part->{_y_position};
185
$symbol_ref->($gd,$x,$y,$pr,$bgcolor);
295
$color = $factory->translate_color($factory->option($part,'part_color',0,0));
300
$symbol_ref->($gd,$x,$y,$pr,$color);
307
my $side = $self->option('scale');
308
return if $side eq 'none';
309
$side ||= $self->default_scale();
189
313
sub _draw_scale {
190
314
my $self = shift;
191
my ($gd,$scale,$min,$max,$dx,$dy) = @_;
315
my ($gd,$scale,$min,$max,$dx,$dy,$y_origin) = @_;
192
316
my ($x1,$y1,$x2,$y2) = $self->calculate_boundaries($dx,$dy);
194
my $side = $self->option('scale');
195
return if $side eq 'none';
318
$y2 -= $self->pad_bottom - 1;
320
my $side = $self->_determine_side();
198
322
my $fg = $self->fgcolor;
199
my $half = ($y1+$y2)/2;
200
323
my $font = $self->font('gdTinyFont');
202
325
$gd->line($x1,$y1,$x1,$y2,$fg) if $side eq 'left' || $side eq 'both';
203
326
$gd->line($x2,$y1,$x2,$y2,$fg) if $side eq 'right' || $side eq 'both';
205
for ([$y1,$max],[$half,int(($max-$min)/2+0.5)]) {
328
$gd->line($x1,$y_origin,$x2,$y_origin,$fg);
330
my @points = ([$y1,$max],[($y1+$y2)/2,($min+$max)/2],[$y2,$min]);
331
push @points,[$y_origin,0] if ($min < 0 && $max > 0);
333
my $last_font_pos = -99999999999;
206
336
$gd->line($x1-3,$_->[0],$x1,$_->[0],$fg) if $side eq 'left' || $side eq 'both';
207
337
$gd->line($x2,$_->[0],$x2+3,$_->[0],$fg) if $side eq 'right' || $side eq 'both';
339
my $font_pos = $_->[0]-($font->height/2);
341
next unless $font_pos > $last_font_pos + $font->height; # prevent labels from clashing
208
342
if ($side eq 'left' or $side eq 'both') {
209
# $gd->string(gdTinyFont,
210
# $x1 - gdTinyFont->width * length($_->[1]) - 3,$_->[0]-(gdTinyFont->height/3),
214
$x1 - $font->width * length($_->[1]) - 3,$_->[0]-($font->height/3),
344
$x1 - $font->width * length($_->[1]) - 3,$font_pos,
218
348
if ($side eq 'right' or $side eq 'both') {
219
# $gd->string(gdTinyFont,
220
# $x2 + 4,$_->[0]-(gdTinyFont->height/3),
223
349
$gd->string($font,
224
$x2 + 4,$_->[0]-($font->height/3),
354
$last_font_pos = $font_pos;
420
548
-graph_height Specify height of the graph Same as the
551
-neg_color For boxes only, bgcolor for Same as bgcolor
552
points with negative scores
554
-part_color For boxes & points only, none
555
bgcolor of each part (should
556
be a callback). Supersedes
559
-clip If min_score and/or max_score false
560
are manually specified, then
561
setting this to true will
562
cause values outside the
423
565
Note that when drawing scales on the left or right that the scale is
424
566
actually drawn a few pixels B<outside> the boundaries of the glyph.
425
567
You may wish to add some padding to the image using -pad_left and
426
568
-pad_right when you create the panel.
570
The B<-part_color> option can be used to color each part of the
571
graph. Only the "boxes", "points" and "linepoints" styles are
572
affected by this. Here's a simple example:
574
$panel->add_track->(\@affymetrix_data,
576
-graph_type => 'boxes',
578
my $score = shift->score;
579
return 'red' if $score < 0;
580
return 'lightblue' if $score < 500;
581
return 'blue' if $score >= 500;
587
For those developers wishing to derive new modules based on this
588
glyph, the main method to override is:
592
=item 'method_name' = $glyph-E<gt>lookup_draw_method($type)
594
This method accepts the name of a graph type (such as 'histogram') and
595
returns the name of a method that will be called to draw the contents
596
of the graph, for example '_draw_histogram'. This method will be
597
called with three arguments:
599
$self->$draw_method($gd,$left,$top,$y_origin)
601
where $gd is the GD object, $left and $top are the left and right
602
positions of the whole glyph (which includes the scale and label), and
603
$y_origin is the position of the zero value on the y axis (in
604
pixels). By the time this method is called, the y axis and labels will
605
already have been drawn, and the scale of the drawing (in pixels per
606
unit score) will have been calculated and stored in
607
$self-E<gt>{_scale}. The y position (in pixels) of each point to graph
608
will have been stored into the part, as $part-E<gt>{_y_position}. Hence
609
you could draw a simple scatter plot with this code:
611
sub lookup_draw_method {
614
if ($type eq 'simple_scatterplot') {
615
return 'draw_points';
617
return $self->SUPER::lookup_draw_method($type);
623
my ($gd,$left,$top) = @_;
624
my @parts = $self->parts;
625
my $bgcolor = $self->bgcolor;
627
for my $part (@parts) {
628
my ($x1,$y1,$x2,$y2) = $part->calculate_boundaries($left,$top);
629
my $x = ($x1+$x2)/2; # take center
630
my $y = $part->{_y_position};
631
$gd->setPixel($x,$y,$bgcolor);
634
=item $y_position = $self-E<gt>score2position($score)
636
Translate a score into a y pixel position, obeying clipping rules and
430
643
Please report them.