Custom tracks

There are kinds of data which may be interesting to see together with annotation renderings, but that can not be expressed – or only in a complicated way – in GFF3 format. It may even be too difficult or counterintuitive to properly represent this data as typical AnnotationSketch box graphics. For example, this may be sequence data, numerical sequence analysis results, or other kinds of data which does not fit into the simple ‘genomic feature’ scheme. For an example, see Fig. 1.

[Example custom track]

Figure 1: Example AnnotationSketch output with a custom track at the bottom, displaying the GC content over a window size of 200 bp.

With custom tracks, AnnotationSketch provides a mechanism to use the internal drawing functionality to create user-defined output which can be tailored to fit this kind of data. A custom track looks just like a normal AnnotationSketch track, but is completely in control of the developer. While native AnnotationSketch primitives such as boxes can of course be used, the author of a custom track is not restricted to the layout algorithm and can draw anything anywhere (as long as it is provided by the Graphics class), taking arbitrary external data into account.

Anatomy of a custom track class

Simply put, custom tracks are classes which are derived from a CustomTrack base class and must implement a set of mandatory methods:

Optionally, a free() method can be implemented if the subclass needs to clean up any private space allocated by itself. These methods are then called by the rendering code in AnnotationSketch when a Diagram containing a custom track is laid out and rendered. No other constraints apply on such a class besides that these methods are implemented (in the scripting language bindings, the parent classes' constructor must be called once).

Writing an example custom track

Let's suppose we are not satisfied with the display of single base features, such as transposable element insertion sites or SNPs. Instead of a single line denoting the feature location, we would like to have a small triangle pointing at the location. Suppose we also do not have this data in an annotation graph, so we cannot use the built-in rendering functions. It is straightforward to write a small custom track class which does this for us.
This tutorial uses Python code for simplicity, but the general approach is common to all supported languages.

First, we need to define a class inheriting from CustomTrack, call the parent constructor to register the functions and set instance variables for the triangle sidelength and a dictionary containing the feature positions and a description:

class CustomTrackInsertions(CustomTrack):
  def __init__(self, sidelength, data):
    super(CustomTrackInsertions, self).__init__()
    self.sidelength = sidelength = data
We define the height to be 20 pixels:
  def get_height(self):
    return 20
As a track title, we set "Insertion site":
  def get_title(self):
    return "Insertion site"
The rendering code then calculates the triangle coordinates and draws the respective lines:
  def render(self, graphics, ypos, rng, style, error):
    height = (self.sidelength*math.sqrt(3))/2
    margins = graphics.get_xmargins()
    red = Color(1, 0, 0, 0.7)
    for pos, desc in
      drawpos = margins + (float(pos)-rng.start)/(rng.end-rng.start+1)         \
                  * (graphics.get_image_width()-2*margins)
      graphics.draw_line(drawpos-self.sidelength/2, ypos + height,             \
                         drawpos, ypos,                                        \
                         red, 1)
      graphics.draw_line(drawpos, ypos,                                        \
                         drawpos+self.sidelength/2, ypos + height,             \
                         red, 1)
      graphics.draw_line(drawpos-self.sidelength/2, ypos + height,             \
                         drawpos+self.sidelength/2, ypos + height,             \
                         red, 1)
      graphics.draw_text_centered(drawpos, ypos + height + 13, str(desc))
    return 0
For a Python custom track, that's it! No more code is necessary for this very simple custom track. We can now instantiate this class and attach the instance to a Diagram object:
diagram = Diagram.from_index(feature_index, seqid, range, style)
ctt = CustomTrackInsertions(15, {2000:"foo", 4400:"bar", 8000:"baz"})
Running layout and drawing functions on this diagram then produces the desired image:

[Example custom track]

Figure 2: The example insertion site custom track (at the bottom), displaying three sample data points.