# 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`

We will call the second part (after the “|”) of these track titles track identifier strings in the rest of this document.

Figure 1: Default AnnotationSketch output for a simple GFF3 file with simple exon→mRNA collapsing.

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):

Figure 3: AnnotationSketch output with `filter_track_selector()` track selector function. This image now shows a separate track for features with a specific caption.