documentationfor yFiles for HTML 2.6

Resizing Nodes

Nodes are interactively resized by dragging a so-called resize handle. A handle is a small draggable object modeled by interface IHandle. The handle is responsible for translating a drag gesture into a change on its handled model item.

Actually moving the handles is managed by HandleInputMode, a child input mode of GraphEditorInputMode. In fact, it is up to the IHandle to translate the movement into a resize operation.

HandleInputMode not only handles resize operations but also move operations for elements which provide handles, e.g. bends or ports.

The items which are currently dragged (or to be dragged) can be retrieved via the affectedItems property. All events below are dispatched at a time 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.
DragStarted…​ a drag gesture is recognized, after the input mode is initialized for dragging.
Dragging…​ the mouse is moved during drag operations and before the actual drag is performed.
Dragged…​ the mouse is moved during drag operations and after the actual drag is 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.

HandleInputMode makes extensive use of the service locator pattern. Its behavior can be influenced to a large extent by modifying an item’s lookup to return custom implementations of the following interfaces.

The handles collection of a HandleInputMode which is configured by GraphEditorInputMode is updated upon changes in the selection: the lookup of all selected items is queried for implementations of

  • IReshapeHandleProvider to provide handles for each supported position. If none is found the lookup is queried for an
  • IHandleProvider to provide a set of arbitrary handles. Additionally, bends of selected edges are directly queried for an
  • IHandle which will be added directly.

IReshapeHandleProvider

An IReshapeHandleProvider provides specialized handles for resize operations. These handles are used differently depending on their position, e.g. a handle at the top border of a rectangle is usually used to move that border in vertical direction. The positions are defined by the HandlePositions enumeration.

IReshapeHandleProvider implementations have to implement two methods:

getAvailableHandles(context: IInputModeContext): HandlePositions
Provides the positions for available handles as HandlePositions
getHandle(context: IInputModeContext, position: HandlePositions): IHandle
Returns a handle for the given position.

The node’s lookup can be modified to return an instance of a custom IReshapeHandleProvider implementation. You can do this using the NodeDecorator’s reshapeHandleProviderDecorator:

Showing resize handles only at the corners of nodes
class CustomReshapeHandlerProvider extends ReshapeHandleProviderBase {
  originalProvider

  /**
   * @param {!IReshapeHandleProvider} originalProvider
   */
  constructor(originalProvider) {
    super()
    this.originalProvider = originalProvider
  }

  /**
   * @param {!IInputModeContext} context
   * @returns {!HandlePositions}
   */
  getAvailableHandles(context) {
    // Only show resize handles at the corners of the node
    return HandlePositions.CORNERS
  }

  /**
   * @param {!IInputModeContext} context
   * @param {!HandlePositions} position
   * @returns {!IHandle}
   */
  getHandle(context, position) {
    // Delegate the actual handle creation to the original provider
    return this.originalProvider.getHandle(context, position)
  }
}

graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setImplementationWrapper(
  (node, originalProvider) => new CustomReshapeHandlerProvider(originalProvider)
)
class CustomReshapeHandlerProvider extends ReshapeHandleProviderBase {
  originalProvider: IReshapeHandleProvider

  constructor(originalProvider: IReshapeHandleProvider) {
    super()
    this.originalProvider = originalProvider
  }

  getAvailableHandles(context: IInputModeContext): HandlePositions {
    // Only show resize handles at the corners of the node
    return HandlePositions.CORNERS
  }

  getHandle(context: IInputModeContext, position: HandlePositions): IHandle {
    // Delegate the actual handle creation to the original provider
    return this.originalProvider.getHandle(context, position)
  }
}

graph.decorator.nodeDecorator.reshapeHandleProviderDecorator.setImplementationWrapper(
  (node, originalProvider) => new CustomReshapeHandlerProvider(originalProvider!)
)

The Reshape Handle Provider Configuration and Reshape Handle Provider demos show how to use different IReshapeHandleProvider implementations to control the resize behavior of nodes and ports.

IHandleProvider

An IHandleProvider returns a list of handles for arbitrary purposes via its getHandles method.

Any model item’s lookup can be modified to return an instance of a custom IHandleProvider implementation. You can do this using the decorator’s handleProviderDecorator.

IHandle

IHandle is the actual interface which is used to promote a drag gesture into a related change on its handled model item. It provides a drag point for a drag operation and translates the drag operation into a change on the model item. This can be a change in the model item’s size as discussed in this section, or a move operation as handled in the previous section, or even something completely different, as long as it fits the “small handle that can be dragged” paradigm.

IHandle uses four methods to maintain its life cycle:

initializeDrag(context: IInputModeContext): void
Initializes the handle at the start of a drag operation. Implementers should allocate their 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.

Outside of this cycle, clicking the handle can be processed in handleClick(evt: ClickEventArgs): void.

Besides these methods IHandle also provides properties which can be used by HandleInputMode to determine the type of the handle and a preferred cursor to visualize its purpose.

Node resizing is mediated by IHandle implementations that delegate to an IReshapeHandler (see below) in the handleMove method.

IReshapeHandler

An IReshapeHandler manages reshaping, that is, changing the dimensions of an arbitrary item.

initializeReshape(context: IInputModeContext): void
Initializes the reshape handler at the start of a drag operation. Implementers should allocate required resources, remember the initial bounds, and, if applicable, initialize snapping and orthogonal edge editing.
handleReshape(context: IInputModeContext, originalBounds: Rect, newBounds: Rect): void
Called during a move gesture to update the current bounds.
cancelReshape(context: IInputModeContext, originalBounds: Rect): void
Called when a move gesture is finished. Implementers should restore the initial bounds and free all resources here.
reshapeFinished(context: IInputModeContext, originalBounds: Rect, newBounds: Rect): void
Called when a move gesture has finished successfully. Implementers should set the current bounds and free all resources here.

INodeSizeConstraintProvider

The default implementation of IReshapeHandler queries its node’s lookup for an implementation of INodeSizeConstraintProvider. Its methods can be implemented to restrict resize operations by providing a minimum and a maximum size. Furthermore, it can provide a rectangle which defines an area which must always be enclosed in the bounds to be resized.

The interface’s default implementation, NodeSizeConstraintProvider provides properties to define these values.

A node’s lookup can be decorated to return an implementation of INodeSizeConstraintProvider:

// nodes should not be smaller than 10x10 or larger than 100x100
graph.decorator.nodeDecorator.sizeConstraintProviderDecorator.setImplementation(
  new NodeSizeConstraintProvider(new Size(10, 10), new Size(100, 100), Rect.EMPTY)
)

Note that in certain circumstances, e.g. with grouping, there can already be an instance of INodeSizeConstraintProvider present in a node’s lookup, e.g. for constraining a group node’s size to its contents.

The demo application Size Constraint Provider shows how to implement and use different INodeSizeConstraintProvider implementations.