Source: motifFinder/motifDrawer.js

/**
  * Constructs a motifDrawer
  *
  * @classdesc
  *
  * A MotifDrawer is responsible for calculating the significance of subgraphs read in from motifs.json, drawing
  * resulting motifs and listing corresponding views in the motifs tab. It uses the builderGraphs created during
  * reading of motifs.json to generate graphBuilders for drawing the motifs.
  *
  * @constructor
  */

function MotifDrawer(viewToCount, builderGraphs) {

    /* @private */
    this.searchbar = SearchBar.getInstance();

    /* @private */
    this.global = this.searchbar.getGlobal();

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

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

    /* @private */
    this.table = $("<td></td>");
}

/**
 * Draws the motifs calculated by calculateMotifs() and lists the corresponding execution labels beside the graph
 */
MotifDrawer.prototype.drawResults = function() {
    this.clearResults();

    var context = this;
    var results = this.calculateMotifs();
    var motifToViews = results.motifToViews;
    var motifs = this.sortMotifs(results.motifs);

    for (var index = 0; index < motifs.length; index++) {
        var motifIndex = motifs[index];
        var motifNum = index + 1;

        // Draw the motif
        var motifSVG = $("<svg width='0' height='0'></svg>")
        this.table.append($("<p></p>").text("Motif " + motifNum + " :"), "<br>", motifSVG, "<br>");
        var graphBuilder = new GraphBuilder(motifSVG, null, true);
        graphBuilder.convertFromBG(this.builderGraphs[motifIndex]);

        // Set the dimensions of the svg depending on the size of the motif
        var svgWidth = graphBuilder.getMaxNodeWidth() + 25;
        var svgHeight = graphBuilder.getMaxNodeHeight() + 25;
        motifSVG.css({"width": svgWidth, "height": svgHeight});

        // List the executions that contain this motif in sorted order
        var viewLabels = this.sortLabels(motifToViews[motifIndex], motifIndex);

        viewLabels.forEach(function(viewLabel) {
            var count = parseInt(context.viewToCount[viewLabel][motifIndex]);
            var numInstances = ": " + count + " instance";
            if (count > 1) {
                numInstances = numInstances.concat("s");
            }
            if (viewLabel == "") {
                // For a single execution where the label is "", just list the number of instances
                context.table.append($("<a></a>").text(numInstances.substring(2)).attr("href", motifIndex));
            } else {
                // For multiple executions, list the execution label along with the number of instances
                context.table.append($("<a></a>").text(viewLabel).attr("href", motifIndex), 
                    $("<span></span>").text(numInstances), "<br>");
            }
        });
        context.table.append("<br>");
    }
    if (context.table.find("a").length == 0) {
        context.table.append($("<p></p>").text("No motifs found"));
    }
    $(".motifResults").append(this.table);

    // Event handler when links in the motifs tab are clicked
    $(".motifResults a").on("click", function(e) {
        var motifIndex = $(this).attr("href");
        $("#motifIcon").remove();
        $(".motifResults a").removeClass("indent");

        var motifIcon = $('<label id="motifIcon"></label>').text("r");

        if (context.global.getViews().length > 1) {
            var viewLabel = $(this).text();
            context.global.setView("L", viewLabel);
        }
        // Indent the clicked on execution and any other executions under the same motif
        $(this).nextUntil("p", ":not(span)").addBack().addClass("indent");
        $(this).prevUntil("svg", ":not(span)").addClass("indent");
        $(this).before(motifIcon);

        // Clear any current searches and re-set to motif search
        if (context.searchbar.getMode() != SearchBar.MODE_MOTIF) {
            context.searchbar.clear();
            context.searchbar.setValue("#motif");
        }
        // Highlight the given motif in the clicked on execution
        context.highlightMotif(motifIndex);

        // Show the number of instances of the highlighted motif
        $("#searchbar").addClass("results");
        context.searchbar.countMotifs();

        e.preventDefault();
    });
}

/**
 * Calculates significance of subgraphs and returns an array of indices corresponding to subgraphs in motifs.json
 * that were calculated to be motifs. Also returns a mapping from motifs to views containing that motif.
 */ 
MotifDrawer.prototype.calculateMotifs = function() {
    var views = this.global.getViews();
    var viewToCount = this.viewToCount;

    // Get the number of motifs that are read in from motifs.json
    var numMotifs = viewToCount[views[0].getLabel()].length;
    var motifToViews = {};
    var motifs = [];

    views.forEach(function(view) {
        var label = view.getLabel();
        var motifsCount = viewToCount[label];

        // Iterate through the count for each motif for this view
        for (var motifIndex=0; motifIndex < numMotifs; motifIndex++) {
            // If this view has this motif, add its label to motifToViews[motifIndex]
            if (motifsCount[motifIndex] > 0) {
                if (motifToViews[motifIndex]) {
                    motifToViews[motifIndex].push(label);
                } else {
                    motifToViews[motifIndex] = [label];
                }
                // If a view has more than 5 instances of a subgraph, count it as a motif
                if (motifsCount[motifIndex] > 5 && motifs.indexOf(motifIndex) < 0) {
                    motifs.push(motifIndex);
                }
            }
        }
    });
    
    for (var motifIndex=0; motifIndex < numMotifs; motifIndex++) {
        // If more than 50% of the executions have this subgraph, count it as a motif. Note that if 
        // motifToViews[motifIndex] doesn't exist, then no executions contain the subgraph at this motifIndex
        if (motifToViews[motifIndex]) {
            if ((motifToViews[motifIndex].length > (Math.ceil((views.length)/2))) && (motifs.indexOf(motifIndex) < 0)) {
                motifs.push(motifIndex);
            }
        }
    }

    return {
        motifs: motifs,
        motifToViews: motifToViews
    };
}

/**
 * Sorts the view labels by descending number of instances of the motif at the given motifIndex
 *
 * @param {Array<String>} labels The labels for the views
 * @param {Number} motifIndex the index of the relevant motif in motifs.json
 * 
 * @returns {Array<String>} labels The sorted labels
 */
MotifDrawer.prototype.sortLabels = function(labels, motifIndex) {
    var viewToCount = this.viewToCount;
    labels.sort(function(a,b) {
        var countA = viewToCount[a][motifIndex];
        var countB = viewToCount[b][motifIndex];
        return countB - countA;
    });
    return labels;
}

/**
 * Sorts the given motifs in the order they appear in motifs.json
 *
 * @param {Array<Number>} motifs Indices of motifs
 * @returns {Array<Number>} motifs The sorted motifs
 */
MotifDrawer.prototype.sortMotifs = function(motifs) {
    motifs.sort(function(a,b) {
        return a - b;
    });
    return motifs;
}

/**
 * This function is responsible for highlighting the motif at the given motifIndex in the active views
 *
 * @param {Number} motifIndex The index of the relevant motif in motifs.json
 */
MotifDrawer.prototype.highlightMotif = function(motifIndex) {

    var controller = this.global.getController();
    var views = this.global.getActiveViews();
    var viewL = views[0];
    var viewR = views[1];

    var finder = new CustomMotifFinder(this.builderGraphs[motifIndex]);
    viewL.getTransformer().highlightMotif(finder, false);
    // Redraw the view to apply the transformation to the graph and to the log lines
    viewL.draw("L");
    controller.bindLines(viewL.getLogTable().find(".line:not(.more)"));

    if (this.global.getPairwiseView()) {
        viewR.getTransformer().highlightMotif(finder, false);
        viewR.draw("R");
        controller.bindLines(viewR.getLogTable().find(".line:not(.more)"));
    }
}

/**
 * Clears the results table
 */
MotifDrawer.prototype.clearResults = function() {
    $(".motifResults td").empty();
    $(".motifResults td:empty").remove();
}