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.
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 = dataWe define the height to be 20 pixels:
def get_height(self): return 20As 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 0For 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: