Source: motifFinder/motifNavigator.js

/**
 * Constructs a new motif navigator
 * 
 * @classdesc
 * 
 * A MotifNavigator helps jump between motifs (i.e. by scrolling the window
 * based on the location of the motif). Typical usage involves adding motifs to
 * navigate between using {@link MotifNavigator#addMotif}, calling
 * {@link MotifNavigator#start}, and then using {@link MotifNavigator#prev} and
 * {@link MotifNavigator#next} to jump between motifs.
 * 
 * @constructor
 */
function MotifNavigator() {

    /** @private */
    this.motifDatas = [];

    /** @private */
    this.index = -1;

    /** @private */
    this.wrap = true;

    /** @private */
    this.hasStarted = false;

};

/**
 * @static
 * @const
 */
MotifNavigator.TOP_SPACING = 100;

/**
 * Adds a group of motifs to navigate between
 * 
 * @param {VisualGraph} visualGraph The visual graph that contains the motif
 *        group
 * @param {MotifGroup} motifGroup The group of motifs to add
 */
MotifNavigator.prototype.addMotif = function(visualGraph, motifGroup) {

    if (this.hasStarted) {
        throw new Exception("MotifNavigator.prototype.addMotif: You cannot call this method after invoking MotifNavigator.prototype.start");
    }

    var motifs = motifGroup.getMotifs();
    for (var m = 0; m < motifs.length; m++) {
        var motif = motifs[m];
        var top = Number.POSITIVE_INFINITY;

        var nodes = motif.getNodes();
        for (var i = 0; i < nodes.length; i++) {
            var node = nodes[i];
            var visualNode = visualGraph.getVisualNodeByNode(node);
            top = Math.min(top, visualNode.getY());
        }

        var data = new MotifNavigatorData(top, motif, visualGraph);
        this.motifDatas.push(data);
    }

};

/**
 * Sets whether or not the navigation "wraps". If wrap mode is on, the navigator
 * will jump back to the first motif after the last one (or back to the last one
 * after the first if going in a reversed order). Otherwise, the navigator will
 * remain at the last motif.
 * 
 * @param {Boolean} wrap The wrap mode
 */
MotifNavigator.prototype.setWrap = function(wrap) {
    this.wrap = wrap;
};

/**
 * Starts the motif navigator. This method should be called after all motifs
 * have been added using {@link MotifNavigator#addMotif}.
 */
MotifNavigator.prototype.start = function() {
    this.motifDatas.sort(function(a, b) {
        return a.getTop() - b.getTop();
    });

    this.hasStarted = true;
};

/**
 * Gets the number of motifs this class navigates between
 * 
 * @returns {Integer} the number of motifs this class navigates between
 */
MotifNavigator.prototype.getNumMotifs = function() {
    return this.motifDatas.length;
};

/**
 * Jumps to the next motif. Motifs are ordered in ascending order of the
 * y-coordinate of their top-most node. Thus, this method jumps to the motif
 * with the next largest y-coordinate
 */
MotifNavigator.prototype.next = function() {

    if (!this.hasStarted) {
        throw new Exception("MotifNavigator.prototype.next: You cannot call this method before invoking MotifNavigator.prototype.start");
    }

    this.index++;

    if (this.index >= this.getNumMotifs()) {
        this.index = this.wrap ? 0 : this.getNumMotifs();
    }

    this.handleCurrent();
};

/**
 * Jumps to the previous motif. Motifs are ordered in ascending order of the
 * y-coordinate of their top-most node. Thus, this method jumps to the motif
 * with the next smallest y-coordinate
 */
MotifNavigator.prototype.prev = function() {

    if (!this.hasStarted) {
        throw new Exception("MotifNavigator.prototype.prev: You cannot call this method before invoking MotifNavigator.prototype.start");
    }

    this.index--;

    if (this.index < 0) {
        this.index = this.wrap ? this.getNumMotifs() - 1 : -1;
    }

    this.handleCurrent();
};

/**
 * Handles the currently selected motif
 * 
 * @private
 */
MotifNavigator.prototype.handleCurrent = function() {
    if (this.index >= this.getNumMotifs() || this.index < 0) {
        return;
    }

    var motifData = this.motifDatas[this.index];

    var position = motifData.getTop() - MotifNavigator.TOP_SPACING;
    position = Math.max(0, position);
    $(window).scrollTop(position);
};

/**
 * Constructs a new MotifNavigatorData with the specified data
 * 
 * @classdesc
 * 
 * MotifNavigatorData is a simple data transfer object
 * 
 * @constructor
 * @param {Number} top The smallest y coordinate among all the nodes in the
 *        motif
 * @param {Motif} Motif The motif itself
 * @param {VisualGraph} visualGraph The visual graph containing the motif
 */
function MotifNavigatorData(top, motif, visualGraph) {

    /** @private */
    this.top = top;

    /** @private */
    this.motif = motif;

    /** @private */
    this.visualGraph = visualGraph;
}

/**
 * Gets the smallest y coordinate among all the nodes in the motif
 * 
 * @returns {Number} the smallest y coordinate among all the nodes in the motif
 */
MotifNavigatorData.prototype.getTop = function() {
    return this.top;
};

/**
 * Gets the motif itself
 * 
 * @returns {Motif} the motif itself
 */
MotifNavigatorData.prototype.getMotif = function() {
    return this.motif;
};

/**
 * Gets the visual graph containing the motif
 * 
 * @returns {VisualGraph} the visual graph containing the motif
 */
MotifNavigatorData.prototype.getVisualGraph = function() {
    return this.visualGraph;
};