Skip to main content

Cell Handlers

What is a Cell Handler?

A cell handler is the object that provides the interactive controls displayed on a selected cell: the selection border, the square handles used to resize a vertex or to bend an edge, the label handle, and so on.

Handlers are not part of the rendering of a cell. They exist only while a cell is selected:

  • the SelectionCellsHandler plugin listens to selection changes,
  • when a cell becomes selected, it creates a handler for it,
  • when the cell is deselected, the handler is destroyed and its DOM elements are removed.

This means that a graph without the SelectionCellsHandler plugin has no handlers at all. This is the expected setup for read-only or visualization-only applications, and it also keeps the handler classes out of the bundle. See Choosing the Plugins to Use and the Tree-Shaking page.

maxGraph provides two families of handlers, both created by SelectionCellsHandler:

Cell typeHandler
VertexVertexHandler
EdgeEdgeHandler, or one of its subclasses ElbowEdgeHandler and EdgeSegmentHandler

VertexHandler

VertexHandler manages a selected vertex. It provides:

  • a selection border around the vertex,
  • resize handles, one per corner and one per side,
  • a rotation handle, disabled by default (see VertexHandlerConfig.rotationEnabled below),
  • a label handle, when the label is movable (graph.setVertexLabelsMovable(true)),
  • custom handles, an extension point for shape-specific controls. Subclasses override createCustomHandles() to return additional CellHandle instances, typically to edit a property of the shape directly on the diagram.

EdgeHandler

EdgeHandler manages a selected edge. It provides:

  • a selection outline along the edge,
  • terminal handles at both ends, used to reconnect the edge to another vertex,
  • bend handles on the waypoints, used to change the edge routing,
  • a label handle, when the label is movable (graph.setEdgeLabelsMovable(true)),
  • virtual bends, disabled by default: extra handles in the middle of each segment used to create new waypoints,
  • adding and removing bends with shift-click, both disabled by default.

Two subclasses adapt this behavior to edges whose routing is computed by an EdgeStyle:

  • ElbowEdgeHandler: for elbow-like routings, which expose a single intermediate handle.
  • EdgeSegmentHandler: for segment-based routings, which expose one handle per computed segment.

Which one is used is not decided by the handler itself, but by the EdgeStyle of the edge. See Choosing the handler of an edge.

Global Configuration

Handlers are configured with three global object literals, exported by the package. They apply to all Graph instances of the application, and are part of the global configuration of maxGraph.

warning

These objects are experimental. They are subject to change or removal in future versions without prior notice.

Since they are plain objects, configuring them is a matter of assigning properties, before or after creating the graph. Handlers read them when they are created, so a change only affects cells selected afterwards.

import { EdgeHandlerConfig, HandleConfig, VertexHandlerConfig } from '@maxgraph/core';

HandleConfig.fillColor = '#99ccff';
VertexHandlerConfig.rotationEnabled = true;
EdgeHandlerConfig.virtualBendsEnabled = true;

HandleConfig

Shared by VertexHandler and EdgeHandler, it configures the small squares used as handles. Available since 0.14.0.

PropertyDefaultDescription
fillColorHANDLE_FILLCOLORFill color of the handles. Use none for no color.
strokeColorHANDLE_STROKECOLORStroke color of the handles. Use none for no color.
sizeHANDLE_SIZESize of the handles.
labelFillColorLABEL_HANDLE_FILLCOLORFill color of the label handle. Use none for no color.
labelSizeLABEL_HANDLE_SIZESize of the label handle.
labelCursor (since 0.20.0)'default'Cursor displayed over the label handle.

VertexHandlerConfig

Available since 0.12.0.

PropertyDefaultDescription
rotationEnabledfalseDisplays the rotation handle.
selectionColorVERTEX_SELECTION_COLORColor of the selection border. Use none for no color.
selectionDashed (since 0.14.0)VERTEX_SELECTION_DASHEDDashed state of the selection border.
selectionStrokeWidth (since 0.14.0)VERTEX_SELECTION_STROKEWIDTHStroke width of the selection border.
cursorMovable (since 0.20.0)'move'Cursor displayed over a movable vertex.

EdgeHandlerConfig

Applies to EdgeHandler and its subclasses. Available since 0.14.0.

PropertyDefaultDescription
handleShape'square'Shape of the edge handles: 'square' or 'circle'.
connectFillColorCONNECT_HANDLE_FILLCOLORFill color of the connect handle. Use none for no color.
selectionColorEDGE_SELECTION_COLORColor of the selection outline. Use none for no color.
selectionDashedEDGE_SELECTION_DASHEDDashed state of the selection outline.
selectionStrokeWidthEDGE_SELECTION_STROKEWIDTHStroke width of the selection outline.
virtualBendsEnabled (since 0.15.0)falseAdds a virtual bend in the center of each segment, used to create new waypoints.
virtualBendOpacity (since 0.15.0)20Opacity of the virtual bends.
addBendOnShiftClickEnabled (since 0.15.0)falseAdds a bend when shift-clicking an edge.
removeBendOnShiftClickEnabled (since 0.15.0)falseRemoves a bend when shift-clicking it.
cursorBend (since 0.20.0)'crosshair'Cursor displayed over a movable bend.
cursorMovable (since 0.20.0)'move'Cursor displayed over a movable edge.
cursorTerminal (since 0.20.0)'pointer'Cursor displayed over a terminal handle.
cursorVirtualBend (since 0.20.0)'crosshair'Cursor displayed over a movable virtual bend.
note

addBendOnShiftClickEnabled and removeBendOnShiftClickEnabled are experimental and not recommended for production use.

Resetting the configuration

Each object has a matching reset function restoring the default values: resetHandleConfig, resetVertexHandlerConfig and resetEdgeHandlerConfig. These are useful in tests, and whenever several graphs with different configurations coexist in the same page.

Choosing the handler of an edge

The edge handler is selected from the EdgeStyle of the edge, through the handlerKind metadata declared when registering the style in the EdgeStyleRegistry. See Custom EdgeStyle.

Three handler kinds are built in:

handlerKindHandlerBuilt-in EdgeStyles
'default'EdgeHandlerentityRelationEdgeStyle, and any edge without an EdgeStyle
'elbow'ElbowEdgeHandlerelbowEdgeStyle, loopEdgeStyle, sideToSideEdgeStyle, topToBottomEdgeStyle
'segment'EdgeSegmentHandlermanhattanEdgeStyle, orthogonalEdgeStyle, segmentEdgeStyle

An EdgeStyle registered without a handlerKind, or with a kind that has no matching handler, falls back to the 'default' one.

warning

When registering a custom EdgeStyle, make sure to declare the correct handlerKind. A wrong value produces a handler that does not match the actual routing of the edge, for instance bend handles that cannot be moved meaningfully. Prefer the dedicated register*EdgeStyle helpers for the built-in styles, which set the metadata for you.

Configuring the handler factories

The handler classes instantiated by SelectionCellsHandler can be replaced per graph instance, which is how a custom VertexHandler or EdgeHandler subclass is plugged in.

Both methods are set on the plugin instance:

import { SelectionCellsHandler, VertexHandler } from '@maxgraph/core';

class MyVertexHandler extends VertexHandler {
// for instance, override createCustomHandles() to add shape specific controls
}

// Assume that the SelectionCellsHandler plugin has been registered on the graph. Then get it and set the factory:
const selectionCellsHandler = graph.getPlugin<SelectionCellsHandler>('SelectionCellsHandler')!;
selectionCellsHandler.setVertexHandlerFactory((state) => new MyVertexHandler(state));

For edges, the factory is registered per handlerKind, so a custom handler can be scoped to the edge styles it actually fits:

selectionCellsHandler.setEdgeHandlerFactory('elbow', (state) => new MyElbowEdgeHandler(state));

This also works for custom kinds. Register the EdgeStyle with your own kind, then declare the matching factory:

EdgeStyleRegistry.add('myEdgeStyle', MyEdgeStyle, { handlerKind: 'my-kind' });
selectionCellsHandler.setEdgeHandlerFactory('my-kind', (state) => new MyEdgeHandler(state));

A factory replaces the previous one for that kind, and only affects handlers created afterwards. Set it before the first selection occurs, typically right after creating the graph.

When the same implementation fits every edge, set it once for all kinds instead of repeating the call:

selectionCellsHandler.setEdgeHandlerFactoryForAllKinds((state) => new MyEdgeHandler(state));

This also covers the kinds registered afterwards, since any kind without a dedicated factory falls back to the 'default' one.

warning

setEdgeHandlerFactoryForAllKinds discards all the factories previously set with setEdgeHandlerFactory, including those declared for custom kinds. Call it first, then declare the per-kind factories that must differ.

Changed in 0.25.0

setVertexHandlerFactory and setEdgeHandlerFactory were introduced in version 0.25.0.

Previously, the handlers were configured by subclassing the graph and overriding factory methods on it. Those methods no longer exist on AbstractGraph, Graph and BaseGraph. See the Graph page for the current extension points of the graph classes, and the CHANGELOG for the detailed migration.

Examples and Demos

Configuration of the global handler objects, with vertex resize, edge bends, virtual bends and shift-click to add or remove a bend:

The story toggles the custom configuration with the customHandleDefaults control, so the effect of the HandleConfig, VertexHandlerConfig and EdgeHandlerConfig properties can be compared with the defaults.

A custom VertexHandler providing shape specific handles:

A custom EdgeHandler bound to a custom EdgeStyleHandlerKind, which is the complete flow described in Configuring the handler factories:

The story declares a WireEdgeHandler extending EdgeSegmentHandler, registers its EdgeStyle under a dedicated 'wire' kind, then binds the two together:

EdgeStyleRegistry.add('wireEdgeStyle', WireConnector, {
isOrthogonal: true,
handlerKind: 'wire',
});

selectionCellsHandler.setEdgeHandlerFactory('wire', (state) => new WireEdgeHandler(state));