Beautiful Code [174]
What configuration options should the track object support? I quickly came up with a small number of standard track options, the most important of which was glyph:
-glyph => 'glyph_type'
As described earlier, I wanted to be able to support a wide range of visual representations for features. The -glyph option was the end developer's way of accessing this range of representations. For example -glyph=>'box' should render features as simple rectangles, -glyph=>'oval' should render features as appropriately sized ovals, and -glyph=>'arrow' should draw arrows.
In addition to -glyph, other options included in the original design were:
-fgcolor
Foreground (line) color of features rendered in the track
-bgcolor
Background (fill) color of features rendered in the track
-bump
Whether to turn on collision control
-label
Whether to print each feature's name above it
-description
Whether to print each feature's description below it
-height
The height of each feature
-key
A label for the track as a whole
-tkcolor
Background color for the track
I was aware that fancy glyphs might have special-purpose options that the simple ones wouldn't, so I had to design the code library in such a way that the list of options passed to add_track() was extensible.
The Panel also needed to take options for such things as the desired image width and the conversion scale between pixels and base pairs. I made up a small number of panelspecific options that included:
-width
Width of the image, in pixels
-length
Length of the sequence segment, in base pairs
I could now flesh out the code story to show the specifics of each of the Bio::Graphics calls, as shown in Example 12-3.
Example 12-3. Detailed story for BioPerl::Graphics use (pseudocode)
Code View: Scroll / Show All
1 use Bio::Graphics::Panel;
2 $panel_object = Bio::Graphics::Panel>new(-width => 800,
-length => 50000);
3 $track1 = $panel_object->add_track(-glyph => 'box',
-height => 12,
-bump => 1,
-key => 'Protein-coding Genes');
4 $track2 = $panel_object->add_track(-glyph => 'triangle',
-height => 6,
-bump => 1,
-key => 'Sequence Variants');
5 $collection = Bio::SeqFeature::CollectionI->new(@args);
6 $iterator = $collection->get_seq_stream(@query_parameters);
7 $genes=0; $variations=0;
8 while ($feature = $iterator->next_seq) {
9 if ($feature->method eq 'gene') {
10 $track1->add_feature($feature);
11 $genes++;
12 } elsif ($feature->method eq 'variation') {
13 $track2->add_feature($feature);
14 $variations++;
15 }
16 }
17 $track1->configure(-bump => 0) if $genes > 100;
18 $track2->configure(-bump => 0) if $variations > 100;
19 print $panel_object->png;
12.2.3. Choosing Object Classes
The last major design task was selecting the main object classes that the developer would interact with. From the code story, it first seemed that there were two main classes:
Bio::Graphics::Panel
Objects of this class would represent the entire diagram and would typically be divided into a series of horizontal tracks. Bio::Graphics::Panel would be responsible for positioning each track in its drawing area and for converting feature coordinates (expressed in base pairs) into glyph coordinates (expressed in pixels).
Bio::Graphics::Track
Objects of this class would represent the tracks that make up the panel. Tracks would primarily be responsible for positioning and drawing glyphs.
What about glyphs? It seemed natural to me that glyphs should be objects and should contain the internal logic for drawing themselves. All glyphs should inherit from a generic Bio::Graphics::Glyph object that knew how to do basic drawing. As one called the Track object's add_feature() method, it would create new glyphs by calling the Glyph constructor like this:
$glyph = Bio::Graphics::Glyph->new(-feature=>$feature);
Then, when the Track needed to draw itself, it would have a draw() method similar to this:
sub draw {
@glyphs