line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package MassSpec::ViewSpectrum;
|
2
|
|
|
|
|
|
|
|
3
|
1
|
|
|
1
|
|
23598
|
use strict;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
39
|
|
4
|
1
|
|
|
1
|
|
4
|
use warnings;
|
|
1
|
|
|
|
|
2
|
|
|
1
|
|
|
|
|
28
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
1
|
|
5
|
use Carp;
|
|
1
|
|
|
|
|
6
|
|
|
1
|
|
|
|
|
92
|
|
7
|
1
|
|
|
1
|
|
478
|
use GD;
|
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
8
|
|
|
|
|
|
|
use GD::Graph::lines;
|
9
|
|
|
|
|
|
|
use GD::Graph::lines;
|
10
|
|
|
|
|
|
|
use GD::Graph::colour qw(:lists :colours);
|
11
|
|
|
|
|
|
|
|
12
|
|
|
|
|
|
|
our @ISA = qw(GD::Graph::Error);
|
13
|
|
|
|
|
|
|
|
14
|
|
|
|
|
|
|
our $VERSION = '0.08';
|
15
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
|
17
|
|
|
|
|
|
|
# Preloaded methods go here.
|
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
# Pairs of pattern and their associated annotations category used in
|
20
|
|
|
|
|
|
|
# conjunction with the colormap. This is an array rather than a hash
|
21
|
|
|
|
|
|
|
# because we want to consider the patterns in a prescribed order.
|
22
|
|
|
|
|
|
|
my @defaultannotationsmatching = (
|
23
|
|
|
|
|
|
|
'^y$|^y[ -]| y$| y[ -]', 'y',
|
24
|
|
|
|
|
|
|
'^b$|^b[ -]| b$| b[ -]', 'b',
|
25
|
|
|
|
|
|
|
'[iI]nternal', 'internal',
|
26
|
|
|
|
|
|
|
'^$', 'none'
|
27
|
|
|
|
|
|
|
);
|
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
|
30
|
|
|
|
|
|
|
my %defaultcolormap = (
|
31
|
|
|
|
|
|
|
'y' => 'red',
|
32
|
|
|
|
|
|
|
'b' => 'blue',
|
33
|
|
|
|
|
|
|
'internal' => 'green',
|
34
|
|
|
|
|
|
|
'other' => 'dbrown',
|
35
|
|
|
|
|
|
|
'none' => 'black'
|
36
|
|
|
|
|
|
|
);
|
37
|
|
|
|
|
|
|
|
38
|
|
|
|
|
|
|
my %fontmap = (
|
39
|
|
|
|
|
|
|
'tiny' => gdTinyFont,
|
40
|
|
|
|
|
|
|
'small' => gdSmallFont,
|
41
|
|
|
|
|
|
|
'medium' => gdMediumBoldFont,
|
42
|
|
|
|
|
|
|
'large' => gdLargeFont,
|
43
|
|
|
|
|
|
|
);
|
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
my %Defaults = (
|
46
|
|
|
|
|
|
|
width => 500,
|
47
|
|
|
|
|
|
|
height => 500,
|
48
|
|
|
|
|
|
|
title => '',
|
49
|
|
|
|
|
|
|
linewidth => 2,
|
50
|
|
|
|
|
|
|
extranegativeheight => 0.1,
|
51
|
|
|
|
|
|
|
ylabeldelta => 4, # offset of annotations on y axis measured in pixels
|
52
|
|
|
|
|
|
|
xticknumber => 5, # of tick marks on X axis
|
53
|
|
|
|
|
|
|
xlabeldelta => 6, # pixels for offset of annotations
|
54
|
|
|
|
|
|
|
yaxismultiplier => 2.0, # a ratio, used to permit vertical room for peak annotations
|
55
|
|
|
|
|
|
|
outputformat => 'png',
|
56
|
|
|
|
|
|
|
peakfontsize => 'medium',
|
57
|
|
|
|
|
|
|
x_label => 'm/z',
|
58
|
|
|
|
|
|
|
y_label => 'Relative Intensity',
|
59
|
|
|
|
|
|
|
annotationsmatching => \@defaultannotationsmatching,
|
60
|
|
|
|
|
|
|
colormap => \%defaultcolormap,
|
61
|
|
|
|
|
|
|
);
|
62
|
|
|
|
|
|
|
|
63
|
|
|
|
|
|
|
sub new (\@\@;\@\%\%) # (masses, intensities, [optional] annotations, annotations_matching, colormap)
|
64
|
|
|
|
|
|
|
{
|
65
|
|
|
|
|
|
|
my $type = shift;
|
66
|
|
|
|
|
|
|
my $self = {};
|
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
bless $self,$type;
|
69
|
|
|
|
|
|
|
|
70
|
|
|
|
|
|
|
my($massesRef, $intensitiesRef, $annotationsRef, $annotations_matchingRef, $colormapRef) = @_;
|
71
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# Initialise all relevant parameters to defaults
|
73
|
|
|
|
|
|
|
$self->initialise() or return;
|
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
$self->{masses} = $massesRef;
|
76
|
|
|
|
|
|
|
$self->{intensities} = $intensitiesRef;
|
77
|
|
|
|
|
|
|
$self->{annotations} = $annotationsRef;
|
78
|
|
|
|
|
|
|
$self->{annotationsmatching} = $annotations_matchingRef if $annotations_matchingRef;
|
79
|
|
|
|
|
|
|
$self->{colormap} = $colormapRef if $colormapRef;
|
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
return $self;
|
82
|
|
|
|
|
|
|
}
|
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
sub initialise
|
85
|
|
|
|
|
|
|
{
|
86
|
|
|
|
|
|
|
my $self = shift;
|
87
|
|
|
|
|
|
|
|
88
|
|
|
|
|
|
|
foreach (keys %Defaults)
|
89
|
|
|
|
|
|
|
{
|
90
|
|
|
|
|
|
|
$self->set($_, $Defaults{$_});
|
91
|
|
|
|
|
|
|
}
|
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
return $self;
|
94
|
|
|
|
|
|
|
}
|
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
sub set {
|
98
|
|
|
|
|
|
|
my ($self, $key, $value) = @_;
|
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
$self->{$key} = $value;
|
101
|
|
|
|
|
|
|
}
|
102
|
|
|
|
|
|
|
|
103
|
|
|
|
|
|
|
sub plot
|
104
|
|
|
|
|
|
|
{
|
105
|
|
|
|
|
|
|
my $self = shift;
|
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
my $minmass;
|
108
|
|
|
|
|
|
|
my $maxmass;
|
109
|
|
|
|
|
|
|
my $maxintensity;
|
110
|
|
|
|
|
|
|
my $minintensity;
|
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
my @data_for_graph;
|
113
|
|
|
|
|
|
|
|
114
|
|
|
|
|
|
|
my $i;
|
115
|
|
|
|
|
|
|
my $j = 0;
|
116
|
|
|
|
|
|
|
#
|
117
|
|
|
|
|
|
|
# We are playing a trick by intentionally alternating the real y data values
|
118
|
|
|
|
|
|
|
# with undefined values (and GD::Graph's skip_undef option), so that GD::Graph
|
119
|
|
|
|
|
|
|
# doesn't try to plot our actual data ... we need to perform this plotting ourselves.
|
120
|
|
|
|
|
|
|
#
|
121
|
|
|
|
|
|
|
for ($i = 0; $i <= $#{$self->{masses}}; $i++) {
|
122
|
|
|
|
|
|
|
my $mass = $self->{masses}[$i];
|
123
|
|
|
|
|
|
|
my $intensity = $self->{intensities}[$i];
|
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
if ($i > 0) {
|
126
|
|
|
|
|
|
|
$data_for_graph[0][$j] = $mass;
|
127
|
|
|
|
|
|
|
$data_for_graph[1][$j] = undef;
|
128
|
|
|
|
|
|
|
$j++;
|
129
|
|
|
|
|
|
|
}
|
130
|
|
|
|
|
|
|
$data_for_graph[0][$j] = $mass;
|
131
|
|
|
|
|
|
|
$data_for_graph[1][$j] = $intensity;
|
132
|
|
|
|
|
|
|
$minmass = $mass unless defined $minmass and $minmass < $mass;
|
133
|
|
|
|
|
|
|
$maxmass = $mass unless defined $maxmass and $maxmass > $mass;
|
134
|
|
|
|
|
|
|
$minintensity = $intensity unless defined $minintensity and $minintensity < $intensity;
|
135
|
|
|
|
|
|
|
$maxintensity = $intensity unless defined $maxintensity and $maxintensity > $intensity;
|
136
|
|
|
|
|
|
|
$j++;
|
137
|
|
|
|
|
|
|
}
|
138
|
|
|
|
|
|
|
|
139
|
|
|
|
|
|
|
#
|
140
|
|
|
|
|
|
|
# adjust the min and max masses slightly, since otherwise
|
141
|
|
|
|
|
|
|
# the min and max mass peaks will be obscured by the y axis and the graph's
|
142
|
|
|
|
|
|
|
# right boundary
|
143
|
|
|
|
|
|
|
#
|
144
|
|
|
|
|
|
|
# we also force the ticks multiples of 5
|
145
|
|
|
|
|
|
|
#
|
146
|
|
|
|
|
|
|
my $massdiff = $maxmass - $minmass;
|
147
|
|
|
|
|
|
|
my $tickconstraint = 5.0 * $self->{xticknumber};
|
148
|
|
|
|
|
|
|
$minmass = int(($minmass - 0.04 * $massdiff)/$tickconstraint) * $tickconstraint;
|
149
|
|
|
|
|
|
|
$maxmass = int(($maxmass + 0.04 * $massdiff)/$tickconstraint + 0.5) * $tickconstraint;
|
150
|
|
|
|
|
|
|
$minmass = 0 if $minmass < 0;
|
151
|
|
|
|
|
|
|
|
152
|
|
|
|
|
|
|
# note that we permit negative intensities; this permits some
|
153
|
|
|
|
|
|
|
# interesting visualization capabilities
|
154
|
|
|
|
|
|
|
#
|
155
|
|
|
|
|
|
|
# extra vertical space is required to make the labels fit
|
156
|
|
|
|
|
|
|
$minintensity = 0 if $minintensity > 0;
|
157
|
|
|
|
|
|
|
$minintensity -= $self->{extranegativeheight} if $minintensity < 0;
|
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
my $graph = GD::Graph::lines->new($self->{width},$self->{height});
|
160
|
|
|
|
|
|
|
$graph->{graph}->setThickness($self->{linewidth});
|
161
|
|
|
|
|
|
|
#
|
162
|
|
|
|
|
|
|
# It turns out that if we don't specify x_tick_number explicitly, it becomes
|
163
|
|
|
|
|
|
|
# quite messy to compute the conversion of x coordinates and requires the use
|
164
|
|
|
|
|
|
|
# of lots of undocumented GD::Graph internals
|
165
|
|
|
|
|
|
|
#
|
166
|
|
|
|
|
|
|
# We are playing a trick with skip_undef, so that GD::Graph doesn't try to plot
|
167
|
|
|
|
|
|
|
# our actual data ... we need to perform this plotting ourselves.
|
168
|
|
|
|
|
|
|
#
|
169
|
|
|
|
|
|
|
# We double the maximum intensity so as to leave (hopefully) enough vertical
|
170
|
|
|
|
|
|
|
# height for peak annotations
|
171
|
|
|
|
|
|
|
#
|
172
|
|
|
|
|
|
|
$graph->set(
|
173
|
|
|
|
|
|
|
title => $self->{title},
|
174
|
|
|
|
|
|
|
x_label => $self->{x_label},
|
175
|
|
|
|
|
|
|
x_label_position => 0.5,
|
176
|
|
|
|
|
|
|
skip_undef => 1,
|
177
|
|
|
|
|
|
|
x_tick_number => $self->{xticknumber},
|
178
|
|
|
|
|
|
|
x_min_value => $minmass,
|
179
|
|
|
|
|
|
|
x_max_value => $maxmass,
|
180
|
|
|
|
|
|
|
x_number_format => "%.1f",
|
181
|
|
|
|
|
|
|
y_number_format => "%.2f",
|
182
|
|
|
|
|
|
|
y_min_value => $minintensity * $self->{yaxismultiplier},
|
183
|
|
|
|
|
|
|
y_max_value => $maxintensity * $self->{yaxismultiplier},
|
184
|
|
|
|
|
|
|
y_label => $self->{y_label}) or die $graph->error;
|
185
|
|
|
|
|
|
|
|
186
|
|
|
|
|
|
|
$graph->set_x_axis_font(gdLargeFont);
|
187
|
|
|
|
|
|
|
$graph->set_y_axis_font(gdLargeFont);
|
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
#
|
190
|
|
|
|
|
|
|
# draw the axes and their labels, and subsequently use the computed geometry
|
191
|
|
|
|
|
|
|
# for scaling and translating our data points and annotations
|
192
|
|
|
|
|
|
|
#
|
193
|
|
|
|
|
|
|
my $im = $graph->plot(\@data_for_graph) or die $graph->error;
|
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
my %colors;
|
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
# make the background transparent and interlaced
|
198
|
|
|
|
|
|
|
$colors{'white'} = $im->colorAllocate(255,255,255);
|
199
|
|
|
|
|
|
|
$im->transparent($colors{'white'});
|
200
|
|
|
|
|
|
|
$im->interlaced('true');
|
201
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
for ($i = 0; $i <= $#{$self->{masses}}; $i++) {
|
203
|
|
|
|
|
|
|
my $pattern;
|
204
|
|
|
|
|
|
|
my $colorname;
|
205
|
|
|
|
|
|
|
my $match = 'other';
|
206
|
|
|
|
|
|
|
my $annot = $self->{annotations}[$i];
|
207
|
|
|
|
|
|
|
my $mass = $self->{masses}[$i];
|
208
|
|
|
|
|
|
|
my $intensity = $self->{intensities}[$i];
|
209
|
|
|
|
|
|
|
my $discardThisAnnotation = 0;
|
210
|
|
|
|
|
|
|
my $patternIndex;
|
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
# by convention, a leading @ means discard this annotation, but
|
213
|
|
|
|
|
|
|
# use it for purposes of coloring the peak
|
214
|
|
|
|
|
|
|
if ($annot =~ m/^@/) {
|
215
|
|
|
|
|
|
|
$discardThisAnnotation = 1;
|
216
|
|
|
|
|
|
|
$annot =~ s/^.//;
|
217
|
|
|
|
|
|
|
}
|
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
PATTERN:
|
220
|
|
|
|
|
|
|
for ($patternIndex = 0; $patternIndex < scalar(@{$self->{annotationsmatching}}); $patternIndex += 2) {
|
221
|
|
|
|
|
|
|
$pattern = $self->{annotationsmatching}[$patternIndex];
|
222
|
|
|
|
|
|
|
if (defined($annot) && $pattern && $annot =~ m/$pattern/) {
|
223
|
|
|
|
|
|
|
$match = $self->{annotationsmatching}[$patternIndex + 1];
|
224
|
|
|
|
|
|
|
last PATTERN;
|
225
|
|
|
|
|
|
|
}
|
226
|
|
|
|
|
|
|
}
|
227
|
|
|
|
|
|
|
|
228
|
|
|
|
|
|
|
$colorname = $self->{colormap}{$match};
|
229
|
|
|
|
|
|
|
_init_clr ($im, $colorname, \%colors) or carp "Unable to allocate color $colorname \n";
|
230
|
|
|
|
|
|
|
# print "match $match color $colorname \n";
|
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
# draw vertical mass peaks and their annotations, if any
|
233
|
|
|
|
|
|
|
_myline($self,$graph,$im,$mass,0,$mass,$intensity,$colors{$colorname});
|
234
|
|
|
|
|
|
|
|
235
|
|
|
|
|
|
|
my $gdfont = $fontmap{$self->{peakfontsize}};
|
236
|
|
|
|
|
|
|
$gdfont = gdMediumBoldFont unless $gdfont;
|
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
# for negative values we label all mass peaks starting at the
|
239
|
|
|
|
|
|
|
# bottom of the graph, since we lack the capability to
|
240
|
|
|
|
|
|
|
# compute the vertical height of the labels and don't want
|
241
|
|
|
|
|
|
|
# to require TrueType font availability in order to use
|
242
|
|
|
|
|
|
|
# GD's stringFT method
|
243
|
|
|
|
|
|
|
unless ($discardThisAnnotation) {
|
244
|
|
|
|
|
|
|
if ($intensity >= 0) {
|
245
|
|
|
|
|
|
|
_myannot($graph,$im,$mass,$intensity,$annot,$colors{$colorname},$self->{xlabeldelta},$self->{ylabeldelta},$gdfont);
|
246
|
|
|
|
|
|
|
} else {
|
247
|
|
|
|
|
|
|
_myannot($graph,$im,$mass,$minintensity*$self->{yaxismultiplier}*0.95,$annot,$colors{$colorname},$self->{xlabeldelta},0,$gdfont)
|
248
|
|
|
|
|
|
|
}
|
249
|
|
|
|
|
|
|
}
|
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
}
|
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
return $im->gif if ($self->{outputformat} eq 'gif');
|
254
|
|
|
|
|
|
|
return $im->jpeg if ($self->{outputformat} eq 'jpeg');
|
255
|
|
|
|
|
|
|
return $im->png;
|
256
|
|
|
|
|
|
|
|
257
|
|
|
|
|
|
|
}
|
258
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub _myline {
|
260
|
|
|
|
|
|
|
my($self,$graph,$img,$xb,$yb,$xe,$ye,$color) = @_;
|
261
|
|
|
|
|
|
|
my($xb2,$yb2,$xe2,$ye2);
|
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
($xb2,$yb2) = $graph->val_to_pixel($xb,$yb,1);
|
264
|
|
|
|
|
|
|
($xe2,$ye2) = $graph->val_to_pixel($xe,$ye,1);
|
265
|
|
|
|
|
|
|
$img->line($xb2,$yb2,$xe2,$ye2,$color);
|
266
|
|
|
|
|
|
|
$self->{_hotspots}{$xb} = ['line',$xb2,$yb2,$xe2,$ye2,$self->{linewidth}];
|
267
|
|
|
|
|
|
|
}
|
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
sub _myannot {
|
270
|
|
|
|
|
|
|
my($graph,$img,$x,$y,$annot,$color,$xlabeldelta,$ylabeldelta,$font) = @_;
|
271
|
|
|
|
|
|
|
my($x2,$y2) = $graph->val_to_pixel($x,$y,1);
|
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
$img->stringUp($font,$x2-$xlabeldelta,$y2-$ylabeldelta,$annot,$color);
|
274
|
|
|
|
|
|
|
}
|
275
|
|
|
|
|
|
|
|
276
|
|
|
|
|
|
|
# swiped from set_clr in GD::Graph
|
277
|
|
|
|
|
|
|
sub _init_clr ($$\%) {
|
278
|
|
|
|
|
|
|
my ($gd, $colorname, $colorsRef) = @_;
|
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
my @rgb = _rgb($colorname);
|
281
|
|
|
|
|
|
|
# All of this could potentially be done by using colorResolve
|
282
|
|
|
|
|
|
|
# The problem is that colorResolve doesn't return an error
|
283
|
|
|
|
|
|
|
# condition (-1) if it can't allocate a color. Instead it always
|
284
|
|
|
|
|
|
|
# returns 0.
|
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
# Check if this colour already exists on the canvas
|
287
|
|
|
|
|
|
|
my $i = $gd->colorExact(@rgb);
|
288
|
|
|
|
|
|
|
# if not, allocate a new one, and return its index
|
289
|
|
|
|
|
|
|
$i = $gd->colorAllocate(@rgb) if $i < 0;
|
290
|
|
|
|
|
|
|
# if this fails, we should use colorClosest.
|
291
|
|
|
|
|
|
|
$i = $gd->colorClosest(@rgb) if $i < 0;
|
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
$$colorsRef{$colorname} = $i unless $i < 0;
|
294
|
|
|
|
|
|
|
|
295
|
|
|
|
|
|
|
return $i;
|
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
}
|
298
|
|
|
|
|
|
|
1;
|
299
|
|
|
|
|
|
|
|
300
|
|
|
|
|
|
|
__END__
|