Track selector functions
This page explains how a special kind of function, called track selector function, can be used to customise the AnnotationSketch output by using arbitrary features of a block to assign blocks to tracks (and implicitly creating new tracks this way).Default: Top level type decides track membership
By default, for each block in a Diagram, its source filename and/or the type attribute of its top level element decides into which track the block is finally inserted during the layout phase. So by default, an annotation graph parsed from the GFF3 input file ‘example.gff3’ with gene, mRNA and exon type nodes will be rendered into two separate tracks (exon→mRNA collapsing enabled, see Fig. 1):
- example.gff3|gene and
- example.gff3|mRNA
While automatically determining tracks from the types actually present in the input annotations is convenient in many use cases, one could imagine cases in which more control about block handling may be desired. This leads to the question: How can one extract blocks with specific characteristics and assign them to a special track? The answer is simple: By overriding the default track identifier string, new tracks can be created and named on the fly as soon as a block satisfying user-defined rules is encountered.
Track selector functions
These rules take the form of track selector functions. Basically, a track selector function is a function which takes a block reference as an argument, and returns an appropriate track identifier string. For example, in Python the default track selector function would look like this:
def default_track_selector(block): return block.get_type()This function simply returns a string representation of the type of a block's top level element, creating the tracks just like depicted in Fig. 1.
For a very simple example, let's assume that we want to create separate tracks for all mRNAs on the plus strand and for all mRNAs on the minus strand. The idea now is to change the strand identifier for blocks of the mRNA type to include the strand as additional information, thus creating different track identifiers for plus and minus strand features. In Python, this track selector function would construct a new string which contains both the type and the strand:
def strand_track_selector(block): if block.get_type() == "mRNA": return "%s (%s strand)" % (block.get_type(), block.get_strand()) else: return block.get_type()Using this track selector function would produce the desired result of separate tracks for the mRNAs for each strand (see Fig. 2).
Figure 2: AnnotationSketch output with strand_track_selector() track selector function. This image now shows separate tracks for plus and minus strand features.
A track selector function can be set for a Diagram object using the diagram.set_track_selector_func() method. In C, its argument is a pointer to a function of the signature const char* (*GtTrackSelectorFunc)(GtBlock*, void*) where arbitrary data can be passed via the second void* argument. The Python set_track_selector_func() method directly accepts a Python function as an argument, while the Ruby version takes a Proc object:
... strand_track_selector = Proc.new { |block, data| "#{block.get_type} (#{block.get_strand} strand)" } ... diagram.set_track_selector_func(strand_track_selector) ...
Note that in Python and Ruby, it is also possible to reference data declared outside of the track selector function. For example, this can be used to filter blocks by pulling blocks whose description matches a pattern into a separate track:
... interesting_genes = ["First test gene", "another gene"] def filter_track_selector(block): if block.get_caption() in interesting_genes: return "interesting genes" else: return block.get_type() ... diagram.set_track_selector_func(filter_track_selector) ...This code results in the following image (Fig. 3):