Search code examples
htmlyfiles

Define a maximum area for a graph in yFiles


I am working on a dynamic graph editor, using yFiles. This means that the user can choose new nodes from a menu and drag them into the graph's area. However, I wish my graph to have a limited size on the x-axis. That is, the graph should be expandable with no limits vertically, but maintaining a maximum width. I have absolutely no clue about the way of doing that, so defining a maximum width for the graph.

I'd like to have a final effect like the orange rectangle in this demo. There, a PositionHandler is used, but I don't know how to integrate it.

Don't know if it can be of any help, but this is the snippet in which I define the interactive part of the graph.

'createEditorMode': function() {
    var /** yfiles.input.GraphSnapContext */ snapContext = new yfiles.input.GraphSnapContext();
    snapContext.enabled = false;
    snapContext.nodeGridConstraintProvider = new yfiles.input.SimpleGridConstraintProvider /** .<yfiles.graph.INode> */.FromGridInfo(this.gridInfo);
    snapContext.bendGridConstraintProvider = new yfiles.input.SimpleGridConstraintProvider /** .<yfiles.graph.IBend> */.FromGridInfo(this.gridInfo);
    snapContext.portGridConstraintProvider = new yfiles.input.SimpleGridConstraintProvider /** .<yfiles.graph.IPort> */.FromGridInfo(this.gridInfo);

    var graph = this.graphControl.graph;

    var /** yfiles.input.GraphEditorInputMode */ mode = new yfiles.input.GraphEditorInputMode();
    mode.snapContext = snapContext;
    mode.edgeCreationAllowed = false; //only one edge create
    mode.marqueeSelectionInputMode.enabled = true; //block selection
    mode.nodeCreator = null;

    var /** yfiles.input.OrthogonalEdgeEditingContext */ newOrthogonalEdgeEditingContext = new yfiles.input.OrthogonalEdgeEditingContext();
    newOrthogonalEdgeEditingContext.orthogonalEdgeEditing = true;

    mode.createEdgeInputMode.orthogonalEdgeCreation = true; //Enable edges' creation in orthogonal mode
    mode.orthogonalEdgeEditingContext = newOrthogonalEdgeEditingContext;
    mode.createBendModePriority = mode.moveModePriority - 1;// add a context// menu
    mode.clickSelectableItems = yfiles.graph.GraphItemTypes.NODE | yfiles.graph.GraphItemTypes.EDGE;
    mode.labelAddingAllowed = false;
    mode.labelEditingAllowed = false;
    mode.clipboardOperationsAllowed  = false;
    mode.undoOperationsAllowed = true;
    //mode.undoOperationsAllowed = false;
    mode.addItemClickedListener(yfiles.lang.delegate(this.onItemClicked, this));
    mode.addItemDoubleClickedListener(yfiles.lang.delegate(this.onItemDoubleClicked, this));

    this.contextMenu = new demo.ContextMenu();
    mode.contextMenuInputMode.menu = this.contextMenu;
    this.contextMenu.install(this.graphControl.div);
    this.contextMenu.addOpenedListener(function( /** Object */
        sender, /** yfiles.system.EventArgs */
        args) {
            var /** yfiles.system.CancelEventArgs */ cancelEventArgs = new yfiles.system.CancelEventArgs();
            mode.contextMenuInputMode.menuOpening(cancelEventArgs);
            if (cancelEventArgs.cancel) {
                (( /** @type {demo.ContextMenu} */ (sender))).visible = false;
            }
        }
    );
    this.contextMenu.addClosedListener(function( /** Object */ sender, /** yfiles.system.EventArgs */ args) {
        mode.contextMenuInputMode.menuClosed();
    });
    mode.contextMenuInputMode.addCloseMenuListener((function( /** Object */ o, /** yfiles.system.EventArgs */ args) {
        this.contextMenu.visible = false;
    }).bind(this));
    mode.contextMenuInputMode.addPopulateContextMenuListener(yfiles.lang.delegate(this.onPopulateContextMenu, this));
    return mode;
},

Solution

  • (Side note: If you have an active support subscription for your yFiles license, you can just ask our support team. While we do hang out on Stack Overflow at times, this is not really the best way of getting support.)


    There are a few things that come into this, actually. First of all, by default yFiles does not place any limits on how far the graph extends, although I'm fairly certain things get weird with coordinates around 2.1 million. So to ensure a maximum width of the graph requires you to change a number of different aspects:

    1. Preventing the user from dropping nodes in the wrong place

      You probably have a NodeDropInputMode somewhere, even though I don't see it in your configuration. NodeDropInputMode has a validDropHitTestable property, defining where a node can be dropped. You can simply disallow drops at x coordinates beyond what you want to allow.

      A slightly more involved process would clamp the x coordinate to valid values during the drag gesture. You can do so by subclassing NodeDropInputMode and overriding the setDragLocation method accordingly. This would also ensure that the preview “sticks” to the maximum x location, even if the mouse pointer goes beyond that instead of just disallowing the drop.

    2. Preventing the user from moving nodes outside the bounds

      Node movement can be customized with a custom IPositionHandler. You can find an example of how to do that in the input demos (a very similar example, actually).

    3. Preventing the user from resizing nodes so that they go out of bounds

      Resizing is constrained similar to node movement, albeit at the point where handles are created via IReshapeHandleProvider. There's a demo showing that as well.

    4. Ensuring that automatic layouts are restricted to a certain area

      Not all layout algorithms are capable of that. In fact, only our organic layout is capable of setting a suitable output restriction to force the layout into a rectangle.

    5. A bunch of other tiny things

      Preventing the user from moving bends outside the bounds must be done via a suitable handle, decorated to bends.

      Restricting label movement can be done via an appropriate label layout model.

      Ensuring that copy/paste or duplicate won't place the created items outside the bounds (they are usually created with a slight offset) can probably be done easiest by setting the pasteDelta on GEIM to (0, 0).

      A ViewportLimiter can be used to prevent the user from moving the viewport too far away from the graph.

      There are probably other places I missed that might place items or parts of them outside of your bounds.