Skip to content

Instantly share code, notes, and snippets.

@davidjgraph
Created July 29, 2014 13:27

Revisions

  1. David Benson created this gist Jul 29, 2014.
    314 changes: 314 additions & 0 deletions gistfile1.js
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,314 @@
    /**
    * Explore plugin.
    */
    Draw.loadPlugin(function(ui)
    {
    // Adds resource for action
    mxResources.parse('exploreFromHere=Explore from here...');

    // Max number of edges per page
    var pageSize = 20;

    var uiCreatePopupMenu = ui.menus.createPopupMenu;
    ui.menus.createPopupMenu = function(menu, cell, evt)
    {
    uiCreatePopupMenu.apply(this, arguments);

    var graph = ui.editor.graph;

    if (graph.model.isVertex(graph.getSelectionCell()))
    {
    this.addMenuItems(menu, ['-', 'exploreFromHere'], null, evt);
    }
    };

    // Adds action
    ui.actions.addAction('exploreFromHere', function()
    {
    var sourceGraph = ui.editor.graph;

    var container = document.createElement('div');
    container.style.position = 'absolute';
    container.style.display = 'block';
    container.style.background = '#ffffff';
    container.style.width = '100%';
    container.style.height = '100%';
    container.style.zIndex = 2;

    var deleteImage = document.createElement('img');
    deleteImage.setAttribute('src', IMAGE_PATH + '/delete.png');
    deleteImage.style.position = 'absolute';
    deleteImage.style.right = '10px';
    deleteImage.style.top = '10px';
    container.appendChild(deleteImage);
    document.body.appendChild(container);

    // Global variable to make sure each cell in a response has
    // a unique ID throughout the complete life of the program,
    // in a real-life setup each cell should have an external
    // ID on the business object or else the cell ID should be
    // globally unique for the lifetime of the graph model.
    var requestId = 0;

    function main(container)
    {
    // Checks if browser is supported
    if (!mxClient.isBrowserSupported())
    {
    // Displays an error message if the browser is
    // not supported.
    mxUtils.error('Browser is not supported!', 200, false);
    }
    else
    {
    // Creates the graph inside the given container
    var graph = new Graph(container);
    graph.setCellsResizable(false);

    // Shows hand cursor for all vertices
    graph.getCursorForCell = function(cell)
    {
    if (this.model.isVertex(cell))
    {
    return 'pointer';
    }

    return null;
    };

    graph.getFoldingImage = function()
    {
    return null;
    };

    mxEvent.addListener(deleteImage, 'click', function()
    {
    container.parentNode.removeChild(container);
    sourceGraph.setSelectionCell(sourceGraph.model.getCell(graph.rootCell.sourceCellId));

    // FIXME: Does not work
    sourceGraph.scrollCellToVisible(sourceGraph.getSelectionCell());
    });


    // Disables all built-in interactions
    graph.setEnabled(false);

    // Handles clicks on cells
    graph.click = function(me)
    {
    var evt = me.getEvent();
    var cell = me.getCell();

    if (cell != null)
    {
    load(graph, cell);
    }
    };

    // Gets the default parent for inserting new cells. This
    // is normally the first child of the root (ie. layer 0).
    var parent = graph.getDefaultParent();

    var cx = graph.container.scrollWidth / 2;
    var cy = graph.container.scrollHeight / 3;

    var sourceCell = sourceGraph.getSelectionCell();
    graph.model.beginUpdate();
    var cell = graph.importCells([sourceCell])[0];
    cell.sourceCellId = sourceCell.id;
    cell.geometry.x = cx - cell.geometry.width / 2;
    cell.geometry.y = cy - cell.geometry.height / 2;
    graph.model.endUpdate();

    // Animates the changes in the graph model
    graph.getModel().addListener(mxEvent.CHANGE, function(sender, evt)
    {
    var changes = evt.getProperty('edit').changes;
    var prev = mxText.prototype.enableBoundingBox;
    mxText.prototype.enableBoundingBox = false;
    graph.labelsVisible = false;

    mxEffects.animateChanges(graph, changes, function()
    {
    mxText.prototype.prev = true;
    graph.labelsVisible = true;
    graph.refresh();
    graph.setSelectionCell(graph.rootCell);
    graph.tooltipHandler.hide();
    });
    });

    load(graph, cell);
    }
    };

    // Loads the links for the given cell into the given graph
    // by requesting the respective data in the server-side
    // (implemented for this demo using the server-function)
    function load(graph, cell)
    {
    if (graph.getModel().isVertex(cell))
    {
    var cx = graph.container.scrollWidth / 2;
    var cy = graph.container.scrollHeight / 3;

    // Gets the default parent for inserting new cells. This
    // is normally the first child of the root (ie. layer 0).
    var parent = graph.getDefaultParent();
    graph.rootCell = cell.referenceCell || cell;

    // Adds cells to the model in a single step
    graph.getModel().beginUpdate();
    try
    {
    var cells = rootChanged(graph, cell);

    // Removes all cells except the new root
    for (var key in graph.getModel().cells)
    {
    var tmp = graph.getModel().getCell(key);

    if (tmp != graph.rootCell && graph.getModel().isVertex(tmp))
    {
    graph.removeCells([tmp]);
    }
    }

    // Merges the response model with the client model
    //graph.getModel().mergeChildren(model.getRoot().getChildAt(0), parent);
    graph.addCells(cells);

    // Moves the given cell to the center
    var geo = graph.getModel().getGeometry(graph.rootCell);

    if (geo != null)
    {
    geo = geo.clone();

    geo.x = cx - geo.width / 2;
    geo.y = cy - geo.height / 3;
    graph.getModel().setGeometry(graph.rootCell, geo);
    }

    // Creates a list of the new vertices, if there is more
    // than the center vertex which might have existed
    // previously, then this needs to be changed to analyze
    // the target model before calling mergeChildren above
    var vertices = [];

    for (var key in graph.getModel().cells)
    {
    var tmp = graph.getModel().getCell(key);

    if (tmp != graph.rootCell && graph.getModel().isVertex(tmp) &&
    graph.getModel().getParent(tmp) == graph.getDefaultParent())
    {
    vertices.push(tmp);

    // Changes the initial location "in-place"
    // to get a nice animation effect from the
    // center to the radius of the circle
    var geo = graph.getModel().getGeometry(tmp);

    if (geo != null)
    {
    geo.x = cx - geo.width / 2;
    geo.y = cy - geo.height / 2;
    }
    }
    }

    // Arranges the response in a circle
    var cellCount = vertices.length;
    var phi = 2 * Math.PI / cellCount;
    var r = Math.min(graph.container.scrollWidth / 3 - 80,
    graph.container.scrollHeight / 3 - 80);

    for (var i = 0; i < cellCount; i++)
    {
    var geo = graph.getModel().getGeometry(vertices[i]);

    if (geo != null)
    {
    geo = geo.clone();
    geo.x += r * Math.sin(i * phi);
    geo.y += r * Math.cos(i * phi);

    graph.getModel().setGeometry(vertices[i], geo);
    }
    }

    // Keeps parallel edges apart
    var layout = new mxParallelEdgeLayout(graph);
    layout.spacing = 60;
    layout.execute(graph.getDefaultParent());
    }
    finally
    {
    // Updates the display
    graph.getModel().endUpdate();
    }
    }
    };

    // Gets the edges from the source cell and adds the targets
    function rootChanged(graph, cell)
    {
    // TODO: Keep existing cells, probably best via XML to redirect IDs
    var realCell = cell.referenceCell || cell;
    var sourceCell = sourceGraph.model.getCell(realCell.sourceCellId);
    var edges = sourceGraph.getEdges(sourceCell, null, true, true, false, true);
    var cells = edges;

    // Paging by selecting a window in the edges array
    if (cell.startIndex != null || (pageSize > 0 && edges.length > pageSize))
    {
    var start = cell.startIndex || 0;

    cells = edges.slice(Math.max(0, start), Math.min(edges.length, start + pageSize));
    }

    cells = cells.concat(sourceGraph.getOpposites(cells, sourceCell));
    var clones = graph.cloneCells(cells);

    var edgeStyle = ';curved=1;noEdgeStyle=1;entryX=none;entryY=none;exitX=none;exitY=none;';
    var btnStyle = 'fillColor=green;fontColor=white;strokeColor=green;';

    for (var i = 0; i < cells.length; i++)
    {
    clones[i].sourceCellId = cells[i].id;

    if (graph.model.isEdge(clones[i]))
    {
    // Removes waypoints, edge styles, constraints and centers the label
    clones[i].geometry.x = 0;
    clones[i].geometry.y = 0;
    clones[i].geometry.points = null;
    clones[i].setStyle(clones[i].getStyle() + edgeStyle);
    clones[i].setTerminal(realCell, clones[i].getTerminal(true) == null);
    }
    }

    if (cell.startIndex > 0)
    {
    var backCell = graph.createVertex(null, null, 'Back...', 0, 0, 80, 30, btnStyle);
    backCell.referenceCell = realCell;
    backCell.startIndex = Math.max(0, (cell.startIndex || 0) - pageSize);
    clones.splice(0, 0, backCell);
    }

    if (edges.length > (cell.startIndex || 0) + pageSize)
    {
    var moreCell = graph.createVertex(null, null, 'More...', 0, 0, 80, 30, btnStyle);
    moreCell.referenceCell = realCell;
    moreCell.startIndex = (cell.startIndex || 0) + pageSize;
    clones.splice(0, 0, moreCell);
    }

    return clones;
    };

    main(container);
    });
    });