documentationfor yFiles for HTML 2.6

Moving Items

Moving graph items is handled by GraphEditorInputMode which delegates most of the work to its child input modes MoveInputMode in general and MoveLabelInputMode for labels in particular. The types of items that can be moved are defined by the movableItems property.

Some general customization options such as configurable properties are presented in section Moving Items. Moving labels is explained in a separate section below.

Moving items in general is handled by MoveInputMode. Some items such as bends or ports, however, are instead moved with a special IHandle, managed by HandleInputMode. Note that actually customizing the results of the gesture works the same in either case, as described below.

The items which are involved in the moving operation can be retrieved from property affectedItems. All events below are dispatched at a time where the affectedItems are valid, therefore it is safe to access it in event handlers for these events to determine the moved items.

Related events
Event Occurs when…​
DragStarting…​ a drag gesture is recognized, before the input mode is initialized for dragging.
QueryPositionHandler…​ a drag gesture is recognized. Queries for a custom IPositionHandler. If the event arguments are not handled, the IPositionHandler in the positionHandler property is used.
DragStarted…​ a drag gesture is recognized, after the input mode is initialized for dragging.
Dragging…​ a mouse move event occurs during drag operations, before the actual drag is performed.
Dragged…​ a mouse move event occurs during drag operations, after the actual drag has been performed.
DragCanceling…​ the drag gesture is canceled before the input mode has been cleaned up.
DragCanceled…​ the drag gesture is canceled after the input mode has been cleaned up.
DragFinishing…​ a drag gesture has ended successfully before the actual drag operations have been finished.
DragFinished…​ a drag gesture has ended successfully after the actual drag operations have been finished. At the time the event is raised the affectedItems are still valid.

If you want to be informed about a completed move operation, you can listen for DragFinished events.

The actual moving of the graph items is performed by a so-called position handler, modeled by implementations of the IPositionHandler interface. IPositionHandlers are similar to handles and both actually inherit the same parent interface. The implementation of an IPositionHandler is responsible for actually moving the item.

The default IPositionHandler is a composite handler which collects all movable item’s position handlers (and handles) from the item’s lookup. Modifying the lookup to return a custom position handler can be a means to further customize the move behavior. E.g., modifying a node’s lookup to hide the position handler prevents the node from being moved:

graph.decorator.nodeDecorator.positionHandlerDecorator.hideImplementation(shouldBeFixedPredicate)

In many cases wrapping the existing implementation is a good way of customizing movement of graph items, as shown below in Register a custom position handler as wrapper.

IPositionHandler uses four methods to maintain its life cycle:

initializeDrag(context: IInputModeContext): void
Initializes the position handler at the start of a drag operation. Implementers should allocate required resources, remember the initial position, and, if applicable, initialize snapping and orthogonal edge editing.
handleMove(context: IInputModeContext, originalLocation: Point, newLocation: Point): void
Called during a move gesture to update the current position.
cancelDrag(context: IInputModeContext, originalLocation: Point): void
Called when a move gesture is finished. Implementers should restore the initial position and free all resources here.
dragFinished(context: IInputModeContext, originalLocation: Point, newLocation: Point): void
Called when a move gesture has finished successfully. Implementers should set the current position and free all resources here.

Register a custom position handler as wrapper
graph.decorator.nodeDecorator.positionHandlerDecorator.setImplementationWrapper((node, handler) =>
  handler ? new ConstrainedPositionHandler(handler) : null
)
A constrained position handler wrapper
/**
 * A custom position handler which constrains the movement along the x-axis
 * this implementation wraps the default position handler and delegates most of the work to it
 */
class ConstrainedPositionHandler extends BaseClass(IPositionHandler) {
  // remember the last location
  lastLocation = null

  // set the wrapped handler in the constructor
  /**
   * @param {!IPositionHandler} handler
   */
  constructor(handler) {
    super()
    this.handler = handler
  }

  /**
   * Interface implementation: delegate to wrapped handler
   * @type {!IPoint}
   */
  get location() {
    return this.handler.location
  }

  /**
   * Initializes the drag
   * @param {!IInputModeContext} context
   */
  initializeDrag(context) {
    // delegate to the wrapped handler
    this.handler.initializeDrag(context)
    // remember the last location
    this.lastLocation = this.handler.location.toPoint()
  }

  /**
   * Handle the constrained move
   * @param {!IInputModeContext} context
   * @param {!Point} originalLocation
   * @param {!Point} newLocation
   */
  handleMove(context, originalLocation, newLocation) {
    // only move along the x-axis, keep the original y coordinate
    newLocation = new Point(newLocation.x, originalLocation.y)
    if (!this.lastLocation || !newLocation.equalsEps(this.lastLocation, 0)) {
      // delegate to the wrapped handler for the actual move
      this.handler.handleMove(context, originalLocation, newLocation)
      // remember the location
      this.lastLocation = newLocation
    }
  }

  /**
   * @param {!IInputModeContext} context
   * @param {!Point} originalLocation
   */
  cancelDrag(context, originalLocation) {
    // drag has been cancelled: delegate to the wrapped handler
    this.handler.cancelDrag(context, originalLocation)
  }

  /**
   * @param {!IInputModeContext} context
   * @param {!Point} originalLocation
   * @param {!Point} newLocation
   */
  dragFinished(context, originalLocation, newLocation) {
    // drag has been successfully finished: delegate to the wrapped handler with the constrained position
    this.handler.dragFinished(context, originalLocation, newLocation)
  }
}/**
 * A custom position handler which constrains the movement along the x-axis
 * this implementation wraps the default position handler and delegates most of the work to it
 */
class ConstrainedPositionHandler extends BaseClass(IPositionHandler) implements IPositionHandler {
  // remember the last location
  private lastLocation: Point | null = null

  // set the wrapped handler in the constructor
  constructor(private readonly handler: IPositionHandler) {
    super()
  }

  /**
   * Interface implementation: delegate to wrapped handler
   */
  get location(): IPoint {
    return this.handler.location
  }

  /**
   * Initializes the drag
   */
  initializeDrag(context: IInputModeContext): void {
    // delegate to the wrapped handler
    this.handler.initializeDrag(context)
    // remember the last location
    this.lastLocation = this.handler.location.toPoint()
  }

  /**
   * Handle the constrained move
   */
  handleMove(context: IInputModeContext, originalLocation: Point, newLocation: Point): void {
    // only move along the x-axis, keep the original y coordinate
    newLocation = new Point(newLocation.x, originalLocation.y)
    if (!this.lastLocation || !newLocation.equalsEps(this.lastLocation, 0)) {
      // delegate to the wrapped handler for the actual move
      this.handler.handleMove(context, originalLocation, newLocation)
      // remember the location
      this.lastLocation = newLocation
    }
  }

  cancelDrag(context: IInputModeContext, originalLocation: Point): void {
    // drag has been cancelled: delegate to the wrapped handler
    this.handler.cancelDrag(context, originalLocation)
  }

  dragFinished(context: IInputModeContext, originalLocation: Point, newLocation: Point): void {
    // drag has been successfully finished: delegate to the wrapped handler with the constrained position
    this.handler.dragFinished(context, originalLocation, newLocation)
  }
}

Customizing Grouping explains how moving nodes to a different parent node as part of a move gesture can be customized.

Moving Unselected Items

GraphEditorInputMode’s moveInputMode only handles items which are selected. That means that the default behavior for moving items is that an item has to be selected before it can be moved. To move items without the need to select them before GraphEditorInputMode provides another input mode. This mode is available via the moveUnselectedInputMode property. By default, this mode is disabled.

Since the moveUnselectedInputMode has the same starting gesture as the CreateEdgeInputMode, you need to take some precautions to avoid concurrency issues. If you don’t disable CreateEdgeInputMode completely, either

  • Set the moveUnselectedInputMode's priority higher (i.e. lower priority value) than the CreateEdgeInputMode’s priority. Using a custom modifier recognizer can disable the moveUnselectedInputMode temporarily and allow the user to create edges. See also the example below.
  • Switch between CreateEdgeInputMode and moveUnselectedInputMode by setting their enabled property in turn.
  • Use a custom hitTestable to restrict the draggable area of a node.

There might be other options. The following example shows how to configure the moveUnselectedInputMode with a higher priority than CreateEdgeInputMode and allow for disabling it by pressing the Shift ⇧ key while dragging.

// Disable the standard move input mode
graphEditorInputMode.moveInputMode.enabled = false
// Enable the move input mode for unselected items
const moveUnselectedInputMode = graphEditorInputMode.moveUnselectedInputMode
moveUnselectedInputMode.enabled = true

// Use a modifier recognizer for shift key not held down.
moveUnselectedInputMode.pressedRecognizer = EventRecognizers.createAndRecognizer(
  moveUnselectedInputMode.pressedRecognizer,
  EventRecognizers.inverse(KeyEventRecognizers.SHIFT_IS_DOWN)
)
moveUnselectedInputMode.hoverRecognizer = EventRecognizers.createAndRecognizer(
  moveUnselectedInputMode.hoverRecognizer,
  EventRecognizers.inverse(KeyEventRecognizers.SHIFT_IS_DOWN)
)
// Set a higher priority than CreateEdgeInputMode
moveUnselectedInputMode.priority = graphEditorInputMode.createEdgeInputMode.priority - 1

Moving Labels

Usually, a label cannot be moved freely. Most labels can only be moved to a discrete set of positions, determined by the label’s ILabelModel and ILabelModelParameter.

Multiple candidate positions for moving a label

image::displaying-the-graph_item-layout_labels_interior.png

GraphEditorInputMode supports moving labels via its child input mode MoveLabelInputMode. The MoveLabelInputMode dispatches the same events as the MoveInputMode. It also provides similar configuration options. Additionally,

movedLabel
provides access to the label which is currently handled. Similar to MoveInputMode’s affectedItems.
shouldMove
can be overridden to allow or disallow moving the given label.

Depending on the label’s lookup the label positions are determined in a different manner:

  • If the lookup returns an IPositionHandler this handler will be used. In that case no candidates will be shown and the label is freely movable.
  • If the lookup returns an ILabelModelParameterProvider this instance will be used to get the list of candidates. Visualizations will be rendered and the label can only be moved to these positions.

This behavior can be overridden by holding down the Ctrl key during drag or by setting useLabelModelParameterFinder to true: If the lookup returns an ILabelModelParameterFinder this instance will be used to calculate the closest candidate allowed by the label model. Depending on the label model this will allow the label to move freely along the positions allowed by the label model, e.g. moving along at a defined distance to an edge.