documentationfor yFiles for HTML 3.0.0.1

Resizing Nodes

Nodes are resized interactively by dragging a resize handle. A handle is a small, draggable object modeled by the IHandle interface. The handle translates a drag gesture into a change on the corresponding model item.

Moving the handles is managed by HandleInputMode, a child input mode of GraphEditorInputMode. The IHandle interface translates the movement into a resize operation.

HandleInputMode handles not only resize operations but also move operations for elements that provide handles, such as bends or ports.

The items that are currently dragged (or to be dragged) can be retrieved via the affectedItems property. All events listed below are dispatched when the affectedItems property is valid. Therefore, it is safe to access this property in event handlers for these events to determine the moved items.

Related events
Event Occurs when
drag-starting…​ a drag gesture is recognized, before the input mode is initialized for dragging.
drag-started…​ 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.
drag-canceling…​ the drag gesture is canceled before the input mode has been cleaned up.
drag-canceled…​ the drag gesture is canceled after the input mode has been cleaned up.
drag-finishing…​ a drag gesture has ended successfully before the actual drag operations have been finished.
drag-finished…​ a drag gesture has ended successfully after the actual drag operations have been finished. At the time the event is raised the affectedItems property is still valid.

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

The handles collection of a HandleInputMode that 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 behave differently depending on their position. For example, a handle at the top border of a rectangle is typically used to move that border in a vertical direction. The positions are defined by the HandlePositions enumeration.

IReshapeHandleProvider implementations must implement the following two methods:

getAvailableHandles(context: IInputModeContext): HandlePositions
Provides the positions for available handles as an HandlePositions enumeration.
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 reshapeHandleProvider:

Showing resize handles only at the corners of nodes
graph.decorator.nodes.reshapeHandleProvider.addWrapperFactory(
  (_, provider) => new CustomReshapeHandlerProvider(provider)
)

class CustomReshapeHandlerProvider extends ReshapeHandleProviderBase {
  originalProvider

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

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

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

graph.decorator.nodes.reshapeHandleProvider.addWrapperFactory(
  (_, provider) => new CustomReshapeHandlerProvider(provider!)
)

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)
  }
}

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 various purposes via its getHandles method.

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

IHandle

IHandle is the interface used to initiate a drag gesture that results in a change to its associated 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, 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” concept.

IHandle uses four methods to manage its life cycle:

initializeDrag(context: IInputModeContext): void
Initializes the handle at the start of a drag operation. Implementers should allocate the resources they require, 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 handle position and change the handled model item.
cancelDrag(context: IInputModeContext, originalLocation: Point): void
Called when a move gesture is canceled. Implementers should restore the initial state and free all resources here.
dragFinished(context: IInputModeContext, originalLocation: Point, newLocation: Point): void
Called when a move gesture finishes successfully. Implementers should apply the change to the handled model item 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 that HandleInputMode can use 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 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 canceled. 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.nodes.sizeConstraintProvider.addConstant(
  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.