Beautiful Code [175]
for $glyph (@glyphs) {
$glyph->draw;
}
# draw other stuff in the track, for example, its label
}
This subroutine starts by fetching a list of the Glyph objects that we created during add_feature(). It then invokes each glyph's draw() method to have it draw itself. Finally, it draws stuff specific to the track, such as the track label.
As I thought more about Bio::Graphics::Glyph, I realized that they had to embody a bit of cleverness. Recall that a sequence feature can have an internal structure with subfeatures, sub-subfeatures, and so forth, and that each of the components of the internal structure needs to be laid out using collision control, and then drawn according to user preferences. This layout and draw behavior is very glyph-like, and so it seemed to make sense to let glyphs contain subglyphs in parallel to the feature/subfeature structure. The Glyph new() routine would look something like this:
sub new {
$self = shift; # get self
$feature = shift; # get feature
for $subfeature ($feature->get_SeqFeatures) {
$subglyph = Bio::Graphics::Glyph->new(-feature=>$subfeature);
$self->add_subpart($subglyph);
}
}
For each of the feature's subfeatures we create a new subglyph, and add the subglyph to an internal list. Because we call new() recursively, if a subfeature has subfeatures itself, it creates another level of nested glyphs.
To draw itself and all its subglyphs, a top-level glyph's drawing routine would look something like this:
sub draw {
@subglyphs = $self->get_subparts( )
for $subglyph (@subglyphs) {
$subglyph->draw;
}
# draw ourself somehow
}
This bit of pseudocode calls get_subparts() to get all the subglyphs created by our constructor. It loops through each subglyph and calls its draw() methods. The code then does its own drawing.
At this point, I was struck by the fact that the Glyph draw() pseudocode routine was essentially identical to the Track draw() method shown earlier. I realized that I could unify the two classes by simply arranging for add_track() to create and manage a single internal feature object associated with the track. Subsequent calls to add_feature() would in fact add subfeatures to the feature.
I fooled around with some test code and found out that this worked quite well. In addition to the benefits of removing redundant drawing code, I was able to consolidate all the code dealing with passing and configuring track and glyph options. So, tracks became a subclass of Bio::Graphics::Glyph named Bio::Graphics::Glyph::track, and the Panel's add_track() method ended up looking like this (simplified somewhat):
sub add_track {
my $self = shift;
my $features = shift;
my @options = @_;
my $top_level_feature = Bio::Graphics::Feature->new(-type=>'track');
my $track_glyph =
Bio::Graphics::Glyph::track->new(\@options);
if ($features) {
$track_glyph->add_feature($_) foreach @$features;
}
$self->do_add_track($track_glyph);
return $track_glyph;
}
To accommodate the very first code story, in which the caller passes a list of features to add_track(), I allow the first argument to be a list of features. In the actual code, I do runtime type checking on the first argument to distinguish a list of features from the first option. This allows the caller to call add_track() either using the style from the first code story:
$panel->add_track(\@list_of_features,@options)
or using the style from the second code story:
$panel->add_track(@options)
add_track() then creates a new feature of type track using a lightweight feature class that I wrote for Bio::Graphics (this was necessary for performance reasons; the standard BioPerl feature objects are memory- and performance-intensive). This class is passed to the constructor for Bio::Graphics::Glyph::track.
If the list of features was provided, the method loops through the list and calls the track glyph's add_feature() method.
Lastly, the add_track() method adds the track to an internal list of tracks