The AnnotationSketch module

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

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:

- `get_height()`: Returns the amount of vertical space (in pixels or points) the custom track will occupy in the final image. Must return a numeric value.
- `get_title()`: Returns a title for the custom track which is displayed at the top of the track. Note that, unlike a track identifier string e.g. produced by a track selector function, the string returned by this function is not prepended by a file name.
- `render(graphics, ypos, range, style, error)`: Performs the actual rendering operations. As parameters, this function receives
  - a `Graphics` object to draw on,
  - the vertical offset `ypos` of the drawing area assigned to the custom track,
  - the `Range` of the sequence positions for which annotations are currently displayed,
  - a `Style` object which can be used to obtain style information specific to this custom track, and
  - an `Error` object which can be used to return an error message if the custom track needs to signal a problem.
  The `render()` method must return 0 if drawing was successful, or a negative value if an error occurred.

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
        self.data = 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 self.data.iteritems():
          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"})
    diagram.add_custom_track(ctt)
    ...

Running layout and drawing functions on this diagram then produces the desired image:

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