/
README_DEV.html
432 lines (390 loc) · 15.4 KB
/
README_DEV.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
<?xml version="1.0" encoding="iso-8859-1"?>
<!DOCTYPE html
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head>
<title>File: README.DEV</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
<meta http-equiv="Content-Script-Type" content="text/javascript" />
<link rel="stylesheet" href=".././rdoc-style.css" type="text/css" media="screen" />
<script type="text/javascript">
// <![CDATA[
function popupCode( url ) {
window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
}
function toggleCode( id ) {
if ( document.getElementById )
elem = document.getElementById( id );
else if ( document.all )
elem = eval( "document.all." + id );
else
return false;
elemStyle = elem.style;
if ( elemStyle.display != "block" ) {
elemStyle.display = "block"
} else {
elemStyle.display = "none"
}
return true;
}
// Make codeblocks hidden by default
document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
// ]]>
</script>
</head>
<body>
<div id="fileHeader">
<h1>README.DEV</h1>
<table class="header-table">
<tr class="top-aligned-row">
<td><strong>Path:</strong></td>
<td>README.DEV
</td>
</tr>
<tr class="top-aligned-row">
<td><strong>Last Update:</strong></td>
<td>Fri Oct 05 18:50:32 +0100 2007</td>
</tr>
</table>
</div>
<!-- banner header -->
<div id="bodyContent">
<div id="contextContent">
<div id="description">
<p>
Copyright (C) 2007 Jan Aerts <jan.aerts@bbsrc.ac.uk>
</p>
<h2>README for developers</h2>
<p>
This README is mainly meant to explain how the code works (rather than how
to <em>use</em> the library). It should help if you‘re interested in
contributing, or if you think you found a bug.
</p>
<h3>Overview</h3>
<p>
I‘ve tried to document as much as possible in the code itself, see
for example the comments that accompany the setting of the defaults for <a
href="../classes/Bio/Graphics.html">Bio::Graphics</a> in the panel.rb file.
However, the bigger picture can not be explained that way.
</p>
<h3>The files</h3>
<p>
There‘s one file for each class: panel, track, feature, ruler and
image_map. See the tutorial on a breakdown what each of these do. All of
these except the image_map make up a picture. The image_map is used to
describe the HTML map that can be created to make a picture clickable.
</p>
<p>
Classes are embedded in each other: instead of
</p>
<pre>
Bio::Graphics::Panel
Bio::Graphics::Ruler
Bio::Graphics::Track
Bio::Graphics::Feature
</pre>
<p>
we have:
</p>
<pre>
Bio::Graphics::Panel
Bio::Graphics::Panel::Ruler
Bio::Graphics::Panel::Track
Bio::Graphics::Panel::Track::Feature
</pre>
<p>
There‘s a reason for this. A track can only exist within the confines
of a panel (i.e. a panel is a container for tracks), and a feature can only
exist within the confines of a track. In addition, there are quite some
instances where information from the panel is necessary for the track, and
from the track for the features.
</p>
<h3>The workflow</h3>
<h4>1. Creating the panel</h4>
<p>
The user has to start with a
</p>
<pre>
my_panel = Bio::Graphics::Panel.new(length, width, clickable, display_start, display_stop)
</pre>
<p>
When this happens, among other things, the instance variable @tracks is
created that will later contain the actual Track objects. In addition,
there‘s @number_of_times_bumped. You‘ll later see that each
Track object also has its @number_of_times_bumped. The panel needs this
information to know how far it has to go down before it can start drawing a
track: the first track will be just below the ruler, but the vertical
coordinates of the second one depend on the height of all the ones that
were drawn previously. And <em>that</em> in turn is defined by the number
of times a feature would overlap with another one and therefore had to be
<em>bumped</em> down.
</p>
<p>
@display_start and @display_stop are used for zooming in on a region. Even
though the full @length of the sequence can be really long, setting
@display_start and @display_stop will only consider that region.
</p>
<p>
Then there is @rescale_factor, which plays a crucial role in drawing the
stuff: it tells the script how many basepairs are contained in one pixel.
This variable will be used <em>very</em> extensively in the drawing code.
</p>
<p>
So this covered the Panel#initialize…
</p>
<h4>2. Adding tracks to the panel</h4>
<p>
Because tracks are inherently part of a panel and cannot exist on their
own, they can only be created by using a Panel method rather than a Track
method.
</p>
<pre>
my_track_1 = my_panel.add_track(name, feature_colour = [0,0,1], feature_glyph = 'generic')
</pre>
<p>
This creates a new Track object and adds it to the @tracks array of the
Panel object. Several instance variables are set for the Track object,
including @features (which is an array of Feature objects for that track)
and @number_of_times_bumped. Every time a feature cannot be drawn because
it would overlap with another one, it will be ‘bumped’ down
until it can be drawn. This effectively results in <em>rows</em> that
contain the features. The @number_of_times_bumped is just the number of
rows (to be able to calculate the height of the track afterwards). I admit
that this variable should be renamed to something like
@number_of_feature_rows or something, because the value is actually the
number of times bumped + 1. In the example below, @number_of_times_bumped
is 3 (instead of 2). (I‘ll change that later…)
</p>
<pre>
------------------------------------------------------
******* **** ********* ***** *****
***** ********
**
</pre>
<p>
The Panel#add_track method returns the Track object itself, because the
latter has to be accessible to be able to assign features to it.
</p>
<h4>3. Adding features to a track</h4>
<p>
Same thing as adding a track to a panel: the feature can only be added by
the user by using the Track#add_feature method. Parameters are the name of
the feature, the location and the link.
</p>
<p>
The location of a feature can be something like
‘complement(join(10..20,50..70))’. To be able to parse this, I
use the Bio::Locations object from bioruby (see <a
href="http://www.bioruby.org">www.bioruby.org</a>). A Bio::Locations
(plural) object contains one or more Bio::Location (singular) objects,
which are the subfeatures: 10..20 and 50..70. It‘s these
Bio::Location objects we use to calculate the ultimate start and stop of
the feature.
</p>
<p>
The Track#add_feature method returns the Track object itself.
</p>
<p>
Now let‘s look at the other end: the Feature object that gets
created. In the Feature#initialize method, you‘ll notice, apart from
the obvious variables, the following instances variables:
@pixel_range_collection, @chopped_at_start, @chopped_at_stop,
@hidden_subfeatures_at_start and @hidden_subfeatures_at_stop. Let‘s
take these one by one:
</p>
<h5>@pixel_range_collection</h5>
<p>
Now <em>this</em> is the crucial bit: it will hold the information on what
pixels (on the horizontal axis) should be covered. This means that any part
of the feature that does not fall within the view is <em>not</em> in this
collection. Basically, for every subfeature (e.g. exon for a gene), the
location of that subfeature is compared to the region of the view. If a
subfeature is not in the view at all, its positions are discarded (but
other stuff does happen, see below); if a subfeature is at the left of the
picture but actually extends outwith the view, the start pixel will become
1. You get the picture. Also see the mini diagrams in the code itself.
</p>
<p>
These start and stop positions are used to create
Bio::Graphics::Panel::Track::PixelRange objects. Unspliced objects will
have an array @pixel_range_collection with just one element.
</p>
<h5>@chopped_at_start and @chopped_at_stop</h5>
<p>
Suppose you‘ve got a directed feature (so one with an arrow), and the
3’ end falls outside of the view. What would happen, is that the
3’ end that‘s out of view would be chopped of (that‘s
good), but also that the end of the glyph (which is <em>not</em> the end of
the feature) becomes an arrow. I don‘t want that. Instead, the arrow
should be removed.
</p>
<p>
That‘s where the @chopped_at_start and @chopped_at_stop come in. If
these are set to true (while building the @pixel_range_collection), the
arrow is not drawn.
</p>
<h5>@hidden_subfeatures_at_start and @hidden_subfeatures_at_stop</h5>
<p>
For spliced features, it might be that one or more of the subfeatures (e.g.
exons) lies outwith the view. We normally draw e.g. genes by drawing the
exons as boxes and connecting them with small lines. The drawing code
itself (see later) takes all exons within view and draws those connections.
However, if an exon is outside of the viewing area, this line is not drawn.
The @hidden_subfeatures_at_start and @hidden_subfeatures_at_stop are just
flags to capture this.
</p>
<h4>4. Drawing the thing</h4>
<p>
The Cairo library (<a
href="http://cairographics.org">cairographics.org</a>) is used for the
actual drawing. The main concepts in the Cairo drawing model are (please
also see <a
href="http://cairographics.org/tutorial">cairographics.org/tutorial</a>):
</p>
<ul>
<li><b>source</b>: the <em>paint</em> you‘ll be using
</li>
<li><b>destination</b>: the <em>surface</em> (Cairo::ImageSurface) that you
want to draw onto
</li>
<li><b>mask</b>: controls where you apply the source to the destination. Stuff
like ‘line_to’.
</li>
<li><b>context</b>: tracks one source, one mask and one destination.
</li>
</ul>
<p>
From the cairo tutorial: "Before you can start to draw something with
cairo, you need to create the context. <SNIP> When you create a cairo
context, it must be tied to a specific surface - for example, an image
surface if you want to create a PNG file." So that‘s what we
have to do: create a Cairo::ImageSurface and connect a Cairo::Context to
it.
</p>
<p>
Now let‘s walk through the code itself…
</p>
<p>
When a user draws a panel, the first thing that happens, is the creation of
a Cairo::ImageSurface (the <em>destination</em>). To be able to do this, we
need to know the dimensions. But there‘s a slight problem: we
can‘t know the height of the picture until it‘s actually drawn.
The way we‘ll circumvent this, is that we create a really high
picture (called "huge_panel_drawing") that we‘ll crop
afterwards.
</p>
<h5>Drawing the ruler</h5>
<p>
A ruler consists of a line with tickmarks on it. The major issue with
drawing the ruler, is determining the distance between those ticks. Suppose
we have zoomed into a small region, we‘d still want to see usable
ticks; and if we‘ve zoomed out to a huge region, we don‘t want
to have those ticks all bumping into each other.
</p>
<p>
To calculate the distance between consecutive ticks, we start with a
distance of 1 basepair, and increase it until the minimal distance
criterion is met. We also set the distance between major tickmarks (which
are the ones that will get a number). There‘s a small issue when you
actually start drawing the ticks. Most of the time, we don‘t want the
first tick on the very first basepair of the view. Suppose that would be
position 333 in the sequence. Then the numbers under the major tickmarks
would be: 343, 353, 363, 373 and so on. Instead, we want 350, 360, 370,
380. So we want to find the position of the first tick. If we‘ve
found that one, it‘s simple to add the rest of them.
</p>
<p>
The ruler height @height consists of the height of the ruler itself plus
the height of the numbers.
</p>
<h5>Drawing the tracks</h5>
<p>
Drawing each track starts out with the general header: a line above it and
the title. Obviously, the more challenging part is drawing the features
themselves.
</p>
<p>
First thing we have to do, is figure out what the <b>vertical</b>
<b>coordinates</b> of the glyph should be (i.e. the row). To keep track of
what parts of the screen are already occupied by features (so that we know
when a new feature has to be bumped down), I make use of a <b>grid</b>. The
grid is basically a hash with the keys being the row number, and the values
arrays of ranges. (These ranges use basepair units rather than pixels, but
that‘s completely arbitrary.) For each feature, we first check if we
can draw it at the top of the track (i.e. row 1) and if we can‘t move
it down a row at a time until there‘s room for it.
</p>
<p>
So for example, suppose we‘ve already drawn two features that have
the following positions: 100..150 and 200..225. The grid would then look
like this:
</p>
<pre>
grid = { 1 => [(100..150),(200..225)] }
</pre>
<p>
If we‘d like to draw a new feature from 125..175 (which overlaps the
first of the two ranges above), we see that row_available becomes false,
and the row number is increased. The grid after adding this feature looks
like:
</p>
<pre>
grid = { 1 => [(100..150),(200..225)],
2 => [(125..175)] }
</pre>
<p>
So now we know what the vertical coordinates of the glyph should be. Next
step is to check if there‘s reasons we would like to <b>change</b>
<b>the</b> <b>requested</b> <b>glyph</b> <b>type</b> <b>from</b>
<b>directed</b> <b>to</b> <b>undirected</b>. If the user asks for directed
glyphs (i.e. ones with an arrow at the end), but the view is zoomed
<em>way</em> out, there‘s no way the arrow will be visible. If
we‘d try to draw that arrow anyway, it would become bigger than the
feature itself. Another reason would be if the feature‘s 3’ end
extends outwith the picture.
</p>
<p>
Finally, we can <b>draw</b>. The actual drawing bit should be quite
self-explanatory (<em>move_to</em>, <em>line_to</em>, …).
</p>
<p>
For the spliced features (<em>spliced</em> itself and
<em>directed_spliced</em>), we first draw the components (i.e. the exons)
keeping track of the start and stop positions of the gaps (i.e. introns).
We then add the connections in those gaps. In addition, we draw a line that
extends to the side of the picture if there are exons out of view. This
flag was set when the feature was created (see above:
@hidden_subfeatures_at_start and @hidden_subfeatures_at_stop).
</p>
<p>
When the user wants a clickable map, we also have to record that this
region should be added to the image map.
</p>
<p>
When everything has been drawn, we finally know the number of rows for that
track (i.e. the number_of_times_bumped).
</p>
<h5>Finalizing the panel</h5>
<p>
So now we have a huge panel (see "huge_panel_drawing" above)
which is way to high. This is converted to a panel of the right size by
creating a new panel (i.e. the cairo destination), and then using the huge
panel as a source to be transferred on that new destination.
</p>
<p>
And we just write the PNG to a file. If the user wanted a clickable map,
also create the HTML file.
</p>
</div>
</div>
</div>
<!-- if includes -->
<div id="section">
<!-- if method_list -->
</div>
<div id="validator-badges">
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
</div>
</body>
</html>