Skip to content

Adding a new track type

Peter Kerpedjiev edited this page Jun 1, 2017 · 3 revisions

Creating a new track

Data tracks take input data tiles and display it within a browser. To create a new track type, it is necessary to go through a number of steps. For this tutorial, we'll create a new track which displays a box-plot.

Define a viewconfig section describing an instance of the new track

In order for HiGlass to display a new track, it needs to know that it should display the new track. As with all other tracks, this is part of the viewconfig. In production, viewconfigs can be generated by exporting the view. During development, they are either loaded from app/index.html (when viewing http://127.0.0.1:8080 after running npm start) or from app/scripts/testViewConfs.js when running the tests (npm run tests).

When creating a new track we recommend adding a test case to test/HiGlassComponentTest.jsx to ensure that it is created and functions properly. If this proves too troublesome, it's also possible to add the config for the new track in `app/index.html.

We'll be creating a new type of track called a horizontal-boxplot, so add the following section to the "top" view of the testViewConfig in app/index.html. The uid here should be unique to this instance of the track so we just give it some random string (xxyxx). The height specifies how high this track should be. The tilesetUid specifies the uid of the data source on the server. In this case we'll use data that exists on our public server (higlass.io).

{
    'uid': 'xxyxx', 
    type:'horizontal-boxplot',
    height: 40,
    tilesetUid: 'F2vbUeqhS86XkxuO1j2rPA',
    server: "http://higlass.io/api/v1" 
}

When index.html is loaded, it will create a HiGlass component using that viewconfig. When it gets to the "top" section and sees the definition of that track, it will try to render it. Since it doesn't yet exist, it won't be able to. To tell it how to render a horizontal-boxplot track, we have to create a class that can render it and associate it with the horizontal-boxplot track type.

Creating a class to render the new track type

To create the new track type, we'll use the horizontal-line track as a template. This track is defined in the app/scripts/HorizontalLine1DPixiTrack.js file. To begin, we'll copy this file:

cp app/scripts/HorizontalLine1DPixiTrack.js app/scripts/HorizontalBoxplotTrack.js

There's a lot of boilerplate in the track code but the important parts are in drawTile. In particular, the loop which iterates of tileValues does the actual drawing using the graphics.lineTo and graphics.moveTo function calls. These need to be changed to draw rectangles instead of lines. See the PIXI.js documentation to find the documentation for the drawRect function which needs to be called.

When polishing the track, the exportSVG method should also be implemented so that the view can be exported to SVG. This can be done after the new track is created and tested.

Associating a track type with a track rendering class

Now that we have a class which renders this track type, we need to associate it with the track name (horizontal-boxplot) which was used in the viewconfig. This resolution is done app/scripts/TrackRenderer.jsx. The easiest thing to do is to copy the example and plug in the newly created track names:

            case 'horizontal-boxplot':
                return new HorizontalBoxplotTrack(this.currentProps.pixiStage,
                                                     track.server,
                                                     track.tilesetUid,
                                                     handleTilesetInfoReceived,
                                                     track.options,
                                                     () => this.currentProps.onNewTilesLoaded(track.uid));

We also need to import HorizontalBoxplotTrack from its javascript file at the top of TrackRenderer.jsx:

import {HorizontalBoxplotTrack} from './HorizontalBoxplotTrack.js';

This should be enough to get the track to display if it's already specified in the viewconf. To make it discoverable and configurable, we need to add it to the list of known track types in app/scripts/config.js.

Making the track discoverable and configurable

To be able to add a track using the "Add Track" dialog (accessed using the plus sign icon in HiGlass), HiGlass needs to know what types of data it is capable of displaying. This is specified in app/scripts/config.js. For our new box-plot track, we'll just copy the config for horizontal-line and change it slightly:

    {
        type: 'horizontal-boxplot',
        datatype: ['vector'],
        local: false,
        orientation: '1d-horizontal',
        thumbnail:  null,
        availableOptions: [ 'labelPosition', 'labelColor', 'labelTextOpacity', 'labelBackgroundOpacity', 'axisPositionHorizontal', 'valueScaling' ],
        defaultOptions: {
            labelColor: 'black',
            labelPosition: 'topLeft',
            axisPositionHorizontal: 'right',
            valueScaling: 'linear'
        }
    }

This tells HiGlass, that whenever it encounters a tileset containing data of the type vector it can display it using horizontal-boxplot track. It tells it that it can be placed in horizontal orientation, which means it can only be added as a top or bottom track. The thumbnail option is null because we haven't specified a thumbnail for this track. The available options mean that we can set the position of the label (dataset name) position, color, opacity as well as the axis position and type of scaling (e.g. log or linear) of data as options. We also provide some default values for these options in case they're not specified.

That's it. The horizontal-boxplot track should now be ready to use. What follows in this page are some scattered thoughts on the nitty gritty topics of scales and tiles. They can be ignored for unless a more thorough understanding of the track operations is desired.

Advanced Track Topics (under construction)

Scales

Zoomed scales:

Horizontal tracks: this._xScale() Vertical tracks: this._yScale()

2D tracks: this._xScale() and this._yScale()

Original scales:

this._refXScale() this._refYScale()

To draw the data, it needs various scales. The HorizontalLine1DPixiTrack, for example, requires a valueScale that maps tile values to y positions within the track. This scale can be calculated in a number of different ways, but the simplest is to just use the maxVisibleValue() function of the track. This returns the maximum value present in the dense fields of all the visible tiles.

Other scaling methods may include... quantile scaling, log scaling, etc...

Custom tracks may require bespoke scaling methods. When drawing intervals, we may want to calculate what the maximum number of intervals that will be drawn on top of each other at any point will be. Then for each interval, we will want to calculate its y position.

If the track will rely on translations and zooms to move and rescale the content, it needs to set pMain = this.pMobile in its constructor and draw using the reference scales (this._refXScale and this._refYScale).

Implement the initTile function

This function is called when the tile is initially created.

It is especially useful for tracks that require heavy initial rendering and lighter transformations for zooming and panning. The HeatmapTiledPixiTrack, for example, creates the heatmap sprite and renders it in the initTile function. It omits the drawTile function because it wouldn't do anything and relies on the zoomed function to alter the graphic's translate and scale factor to change the view.

Implement the drawTile(tile) method:

Within the tile structure there is the tileData member which contains the data retrieved from the server. The tile object itself contains the following fields. The following is an example of a tile:

tile = {
    graphics: <Pixi.Graphics>,
    remoteId: "uid.4.3",
    tileId: "uid.4.3",
    tileData: {
        discrete: [[0,1,0],[0,3,0]],
        tileId: "uuid.0.0",
        tilePos: [3],
        zoomLevel: 4
    }

The tile object can also contain information that is relevant to its rendering. If it is meant to be displayed as text, then it can contain additional PIXI.Text objects which are simply rescaled when the tile is redrawn.

Implement a drawing method

There are two ways to draw the visible data:

  • Draw each individual tile:

Example: HeatmapTiledPlot: Each tile can be drawn completely independently of every other one.

  • Adjacent tiles required:

Example: HorizontalLine1DPixiTrack.js: To connect the lines between adjacent tiles, we need a draw method that looks at the adjacent tiles.

  • Draw all the tiles at once

Example: CNVIntervalTrack: We need to have all of the intervals that are visible ready so that we can create a layout where all the elements are considered and there's no overlaps.

Debugging notes

  • LeftTrackModifier switches out pBase so removing it requires removing its pBase from the stage, rather than the original track's
Clone this wiki locally