Source: visualization/visualGraph.js

/**
 * Constructs a VisualGraph that is the visualization of the specified graph.
 * The VisualGraph will have the layout and host permutation specified
 * 
 * @classdesc
 * 
 * A VisualGraph represents the visualization of a graph; it describes how the
 * graph is to be drawn. Note that the actual drawing logic is not part of this
 * class.
 * 
 * A VisualGraph is a composition of VisualNodes and VisualEdges. It provides
 * methods to get the corresponding VisualNode or VisualEdge from graph Nodes
 * 
 * @constructor
 * @param {Graph} graph The underlying Graph that this VisualGraph is a
 *            visualization of
 * @param {Layout} layout A layout object that is responsible for setting the
 *            positions of VisualNodes and Edges
 * @param {HostPermutation} hostPermutation
 */
function VisualGraph(graph, layout, hostPermutation) {

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

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

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

    /** @private */
    this.nodeIdToVisualNode = {};

    /** @private */
    this.links = {}; // A mapping of edge IDs to VisualEdges

    this.graph.addObserver(AddNodeEvent, this, function(event, g) {
        g.addVisualNodeByNode(event.getNewNode());
        g.removeVisualEdgeByNodes(event.getPrev(), event.getNext());
        g.addVisualEdgeByNodes(event.getPrev(), event.getNewNode());
        if (!event.getNext().isTail()) {
            g.addVisualEdgeByNodes(event.getNewNode(), event.getNext());
        }
    });

    this.graph.addObserver(RemoveNodeEvent, this, function(event, g) {
        g.removeVisualEdgeByNodes(event.getPrev(), event.getRemovedNode());
        g.removeVisualEdgeByNodes(event.getRemovedNode(), event.getNext());
        if (!event.getNext().isTail()) {
            g.addVisualEdgeByNodes(event.getPrev(), event.getNext());
        }
        g.removeVisualNodeByNode(event.getRemovedNode());
    });

    this.graph.addObserver(AddFamilyEvent, this, function(event, g) {
        g.addVisualEdgeByNodes(event.getParent(), event.getChild());
    });

    this.graph.addObserver(RemoveFamilyEvent, this, function(event, g) {
        g.removeVisualEdgeByNodes(event.getParent(), event.getChild());
    });

    this.graph.addObserver(RemoveHostEvent, this, function(event, g) {
        delete g.nodeIdToVisualNode[event.getHead().getId()];
    });

    // Create nodes
    var nodes = this.graph.getAllNodes();
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        if (!node.isTail()) {
            var visualNode = new VisualNode(node);
            this.nodeIdToVisualNode[node.getId()] = visualNode;
            visualNode.setFillColor(this.hostPermutation.getHostColor(visualNode.getHost()));
        }
    }

    // Create edges
    for (var i = 0; i < nodes.length; i++) {
        var node = nodes[i];
        var visualNode = this.nodeIdToVisualNode[node.getId()];

        if (node.isHead() || node.isTail()) {
            continue;
        }

        var prev = node.getPrev();
        var linkId = this.getEdgeId(node, prev);
        // These are edges between nodes along a host's timeline
        this.links[linkId] = new VisualEdge(this.nodeIdToVisualNode[prev.getId()], visualNode);

        var connect = node.getChildren();
        for (var j = 0; j < connect.length; j++) {
            var child = connect[j];
            var childLinkId = this.getEdgeId(node, child);
            // These are edges between nodes on different host timelines
            this.links[childLinkId] = new VisualEdge(visualNode, this.nodeIdToVisualNode[child.getId()]);
        }

    }

    this.layout.start(this, this.hostPermutation);
}

/**
 * Updates the VisualGraph and its layout
 */
VisualGraph.prototype.update = function() {
    this.layout.start(this, this.hostPermutation);
};

/**
 * Returns the underlying ModelGraph that this VisualGraph is a visualization of
 * 
 * @returns {ModelGraph} the underlying graph.
 */
VisualGraph.prototype.getGraph = function() {
    return this.graph;
};

/**
 * Returns the hosts associated with this VisualGraph as an array
 * 
 * @returns {Array<String>} The array of hosts
 */
VisualGraph.prototype.getHosts = function() {
    return this.graph.getHosts();
};

/**
 * <p>
 * Returns all VisualNodes in this VisualGraph as an array.
 * </p>
 * 
 * <p>
 * There are no guarantees about the ordering of elements in the returned array.
 * Note that a new array is created to prevent access to the underlying one, so
 * this method takes linear time.
 * </p>
 * 
 * @returns {Array<VisualNode>} The array of VisualNodes
 */
VisualGraph.prototype.getVisualNodes = function() {
    var nodes = [];
    for (var id in this.nodeIdToVisualNode) {
        nodes.push(this.nodeIdToVisualNode[id]);
    }
    return nodes;
};

/**
 * <p>
 * Returns only start VisualNodes in this VisualGraph as an array. Start nodes
 * correspond to dummy head {@link ModelNode}s and may be drawn as a header
 * depending on the visualization
 * </p>
 * 
 * <p>
 * There are no guarantees about the ordering of elements in the returned array.
 * Note that a new array is created to prevent access to the underlying one, so
 * this method takes linear time.
 * </p>
 * 
 * @returns {Array<VisualNode>} The array of VisualNodes
 */
VisualGraph.prototype.getStartVisualNodes = function() {
    var nodes = [];
    for (var id in this.nodeIdToVisualNode) {
        var node = this.nodeIdToVisualNode[id];
        if (node.isStart()) {
            nodes.push(node);
        }
    }
    return nodes;
};

/**
 * <p>
 * Returns non-start VisualNodes in this VisualGraph as an array. Start nodes
 * correspond to dummy head {@link ModelNode}s and may be drawn as a header
 * depending on the visualization
 * </p>
 * 
 * <p>
 * There are no guarantees about the ordering of elements in the returned array.
 * Note that a new array is created to prevent access to the underlying one, so
 * this method takes linear time.
 * </p>
 * 
 * @returns {Array<VisualNode>} The array of VisualNodes
 */
VisualGraph.prototype.getNonStartVisualNodes = function() {
    var nodes = [];
    for (var id in this.nodeIdToVisualNode) {
        var node = this.nodeIdToVisualNode[id];
        if (!node.isStart()) {
            nodes.push(node);
        }
    }
    return nodes;
};

/**
 * <p>
 * Returns all VisualEdges in this VisualGraph as an array.
 * </p>
 * 
 * <p>
 * There are no guarantees about the ordering of elements in the returned array.
 * Note that a new array is created to prevent access to the underlying one, so
 * this method takes linear time.
 * </p>
 * 
 * @returns {Array<VisualEdge>} The array of VisualEdges.
 */
VisualGraph.prototype.getVisualEdges = function() {
    var edges = [];
    for (var id in this.links) {
        edges.push(this.links[id]);
    }
    return edges;
};

/**
 * Gets the VisualNode in this VisualGraph that is the visualization of the
 * {@link ModelNode} provided as a parameter. Returns null if no VisualNode
 * found
 * 
 * @param {ModelNode} node The node whose visualization within this graph will
 *            be returned
 * @returns {VisualNode} The VisualNode that is the visualization of node or
 *          null if none exists
 */
VisualGraph.prototype.getVisualNodeByNode = function(node) {
    var id = node.getId();
    if (!this.nodeIdToVisualNode[id]) {
        return null;
    }
    return this.nodeIdToVisualNode[id];
};

/**
 * Gets the VisualEdge in this VisualGraph that is the visualization of the edge
 * connecting node1 and node2. Note that getVisualEdgeByNodes(a, b) ==
 * getVisualEdgeByNodes(b, a)
 * 
 * @param {ModelNode} node1 One of the end nodes of the edge
 * @param {ModelNode} node2 One of the end nodes of the edge
 * @returns {VisualEdge} The VisualEdge that is the visualization of the edge
 *          between node1 and node2, or null if none exists
 */
VisualGraph.prototype.getVisualEdgeByNodes = function(node1, node2) {
    var linkId = this.getEdgeId(node1, node2);
    if (!this.links[linkId]) {
        return null;
    }
    return this.links[linkId];
};

/**
 * Gets the width of the VisualGraph
 * 
 * @returns {Number} The width
 */
VisualGraph.prototype.getWidth = function() {
    return this.layout.getWidth();
};

/**
 * Gets the height of the VisualGraph
 * 
 * @returns {Number} The height
 */
VisualGraph.prototype.getHeight = function() {
    return this.layout.getHeight();
};

// ---------- Private methods below ----------

/**
 * Gets the edge ID of two Nodes. This is used to store a mapping of pairs of
 * {@link ModelNode}s to their {@link VisualEdge}
 * 
 * @private
 * @param {ModelNode} node1
 * @param {ModelNode} node2
 * @returns {String} The edge ID
 */
VisualGraph.prototype.getEdgeId = function(node1, node2) {
    return Math.min(node1.getId(), node2.getId()) + ":" + Math.max(node1.getId(), node2.getId());
};

/**
 * Creates a new VisualNode from a ModelNode and adds it to this VisualGraph.
 * The new node is returned.
 * 
 * @private
 * @param {ModelNode} node The graph node from which the VisualNode is created
 * @returns {VisualNode} the newly created VisualNode
 */
VisualGraph.prototype.addVisualNodeByNode = function(node) {
    if (!this.nodeIdToVisualNode[node.getId()]) {
        var visualNode = new VisualNode(node);
        visualNode.setFillColor(this.hostPermutation.getHostColor(visualNode.getHost()));
        this.nodeIdToVisualNode[node.getId()] = visualNode;
    }
    return this.nodeIdToVisualNode[node.getId()];
};

/**
 * Removes a the VisualNode representation of the given {@link ModelNode} from
 * the VisualGraph
 * 
 * @private
 * @param {ModelNode} node The node whose VisualNode should be removed.
 */
VisualGraph.prototype.removeVisualNodeByNode = function(node) {
    if (!this.nodeIdToVisualNode[node.getId()]) {
        return;
    }
    delete this.nodeIdToVisualNode[node.getId()];
};

/**
 * Adds to the VisualGraph a VisualEdge that represents the edge between the two
 * parameter ModelNodes. The newly created VisualEdge is returned.
 * 
 * @private
 * @param {ModelNode} node1 One of the end Nodes of the edge that a VisualEdge
 *            is being created for
 * @param {ModelNode} node2 One of the end Nodes of the edge that a VisualEdge
 *            is being created for
 * @returns {VisualEdge} The newly created VisualEdge
 */
VisualGraph.prototype.addVisualEdgeByNodes = function(node1, node2) {
    var edgeId = this.getEdgeId(node1, node2);

    visualNode1 = this.addVisualNodeByNode(node1);
    visualNode2 = this.addVisualNodeByNode(node2);

    var visualEdge = new VisualEdge(visualNode1, visualNode2);
    this.links[edgeId] = visualEdge;
    return visualEdge;
};

/**
 * Removes from the VisualGraph the {@link VisualEdge} representation of the
 * edge between the two parameter ModelNodes
 * 
 * @private
 * @param {ModelNode} node1 One of the end Nodes of the edge whose VisualEdge
 *            should be removed.
 * @param {ModelNode} node2 One of the end Nodes of the edge whose VisualEdge
 *            should be removed.
 */
VisualGraph.prototype.removeVisualEdgeByNodes = function(node1, node2) {
    var edgeId = this.getEdgeId(node1, node2);
    delete this.links[edgeId];
};

/**
 * Adds hidden family edges to the provided VisualNode
 * 
 * @private
 * @param {VisualNode} node
 */
VisualGraph.prototype.addHiddenEdgeToFamily = function(node) {
    var children = node.getChildren();
    for (var i = 0; i < children.length; i++) {
        this.getVisualNodeByNode(children[i]).setHasHiddenParent(true);
    }

    var parents = node.getParents();
    for (var i = 0; i < parents.length; i++) {
        this.getVisualNodeByNode(parents[i]).setHasHiddenChild(true);
    }

};