User Interaction

In terms of the Model-View-Controller (MVC) paradigm, the controller part is the tie between the model and the view. It handles the user interaction and accordingly changes state of both model and view. In yFiles FLEX, controllers are so-called "input modes."

There is a variety of specialized input modes available that handle specific user interaction aspects ranging from general keyboard input to mouse gestures within well-defined contexts. The functionality provided by these specialized input modes is combined by another kind of input mode that serves as a composite which enables full-featured handling of user interaction.

Class GraphEditorInputMode is the most comprehensive of these input modes, it enables all kinds of interaction with a graph and the model items contained therein. Class MainInputMode, which is the base type of GraphEditorInputMode, makes available common functionality like, e.g., tooltips, or context menu support.

Class GraphEditorInputMode

Class GraphEditorInputMode is a full-featured controller that makes available the functionality of a set of specialized input modes. It covers all aspects of user interaction with an IGraph instance displayed in a GraphCanvasComponent.

Figure 2.41. GraphEditorInputMode type hierarchy

GraphEditorInputMode type hierarchy.

General Features

GraphEditorInputMode provides convenient support for creating, modifying, and interacting with graph elements in an IGraph instance.

  • The IGraph instance that GraphEditorInputMode acts upon is accessible via the graph property.
  • The hitTestIterator property can be conveniently used for performing hit-tests.
  • Editing functionality can be easily customized for certain graph item types. Section "Customizing GraphEditorInputMode Interaction Functionality" describes the properties and callbacks provided by GraphEditorInputMode for this purpose.

GraphEditorInputMode also provides convenient support for hierarchically organized graphs, which includes, for example, grouping and ungrouping of nodes as well as re-parenting. Additional support for folding operations, like collapsing and expanding group nodes is available, too.

Upon initialization, GraphEditorInputMode creates and installs the input modes listed in Table 2.19, “Input modes used by GraphEditorInputMode (sorted by input mode priority)” as concurrent input modes. Note that the input modes are sorted according to their priority (see also the section called “Input Mode Priorities”).

Table 2.19. Input modes used by GraphEditorInputMode (sorted by input mode priority)

Type Name Description
HandleInputMode Manages the dragging of "handles," including the detection of beginning and ending of a drag, and canceling a drag. Also provides the visual feedback for handles when dragged.
KeyboardInputMode Recognizes key events.
ClickInputMode Recognizes mouse clicks, including double clicks.
MoveLabelInputMode Specialized instance that handles moving of labels to label candidate positions.
MoveInputMode Handles moving of canvas objects.
CreateBendInputMode Governs the creation of additional bends in edge paths.
CreateEdgeInputMode Governs the creation of edges in an IGraph.
MarqueeSelectionInputMode Provides the visual feedback for a rectangular selection box in the canvas.
NavigationInputMode Enables navigation in the graph structure.
ContextMenuInputMode Displays a context menu.
MouseHoverInputMode Displays a tooltip when the mouse rests in the canvas.
TextEditorInputMode Handles label editing.

Each of the input modes can be obtained or replaced using a like named property defined by GraphEditorInputMode. Also, a sorted list of input modes can be obtained via the sortedModes() method.

Graph element creation functionality is provided directly by GraphEditorInputMode as well as the input modes CreateBendInputMode and CreateEdgeInputMode. The former uses the method below whenever a node is created in the canvas by means of a mouse click gesture. Using the nodeCreationAllowed and nodeCreator properties, the actual node creation behavior can be easily customized.

createNode(clickPoint:IPoint):INode
Description Mouse gesture node creation method.

Bend and edge creation is governed by their respective input modes. However, CreateEdgeInputMode exposes convenient bend-related customization capabilities. For example, the bendCreationAllowed property can be used to disallow bend creation in the canvas.

Customizing GraphEditorInputMode Interaction Functionality

Class GraphEditorInputMode provides various properties and callbacks that allow for fine-grained control of the editing functionality by restricting the set of graph items the input mode may act upon to a subset of the available graph item types. Most of these configuration properties can be set using a combination of the graph item type constants defined in class GraphItemTypes.

The available customization properties of class GraphEditorInputMode are listed in Table 2.20, “GraphEditorInputMode customization”.

Table 2.20. GraphEditorInputMode customization

Name Purpose
labelEditingAllowed Whether interactive label editing is allowed. If set to true, label editing is triggered by the F2 key by default.
nodeCreationAllowed Whether the user is allowed to create nodes.
selectableItems A combination of GraphItemTypes that specifies the set of graph items that can be selected by the user.
marqueeSelectableItems A combination of GraphItemTypes that specifies the set of graph items that can be selected using the marquee selection tool.
clickSelectableItems A combination of GraphItemTypes that specifies the set of graph items that can be selected by mouse clicks.
showHandleItems A combination of GraphItemTypes that specifies the set of graph items that should show resize handles when selected.
shouldShowHandles() Callback that determines whether resize handles should be shown for the given item.
deletableItems A combination of GraphItemTypes that specifies the set of graph items that will be deleted when the Delete key is pressed.
shouldBeDeleted() Callback that determines whether the given item may be deleted.
movableItems A combination of GraphItemTypes that specifies the set of graph items that can be moved by the user.
shouldBeMovable() Callback that determines whether the given item may be moved.

Example 2.32, “Using the GraphItemTypes constants” shows how to use the GraphItemTypes constants to customize a GraphEditorInputMode instance.

Example 2.32. Using the GraphItemTypes constants

// don't show resize handles for any graph items
geim.showHandleItems = GraphItemTypes.NONE;

// only allow deletion of edges and nodes
geim.deletableItems = GraphItemTypes.EDGE | GraphItemTypes.NODE;

Support for Orthogonal Edge Paths

Support for orthogonal edge paths is an invaluable aid to a user for manual creation of orthogonal edge paths, but even more for maintaining orthogonal edge paths during manual editing operations. Without this support, moving a node can easily destroy orthogonality of incident edges thereby making a mess out of a once beautiful diagram.

GraphEditorInputMode, together with the specialized input modes it makes available, provides special support for orthogonal edge paths. Specifically, the following edge-related mouse gestures offer specialized behavior for this kind of paths:

  • edge creation (CreateEdgeInputMode, see below)
  • moving (orthogonal) edge segments and creating new bends/edge segments
  • moving the ports of an edge

Additionally, the following mouse gestures, which have an impact on edge paths, too, also offer specialized behavior:

  • resizing nodes
  • moving selected nodes/bends

To enable the support for orthogonal edge paths, an OrthogonalEdgeEditingContext can be set with GraphEditorInputMode by means of the following property:

orthogonalEdgeEditingContext:OrthogonalEdgeEditingContext
Description Convenience property for setting the orthogonal edge editing context and enabling support for orthogonal edge paths.

Support for orthogonal edge creation can be enabled independently on CreateEdgeInputMode using the following property:

orthogonalEdgeCreation:Boolean
Description Enables/disables orthogonal edge path support when creating an edge.

With the exception of CreateEdgeInputMode, class OrthogonalEdgeEditingContext handles orthogonal edge editing of all edges across the involved input modes. The actual edge editing behavior can be controlled using the following properties:

orthogonalEdgeEditing:Boolean
Description Enables/disables orthogonal edge path support.
movePorts:Boolean
Description Determines whether the source port (target port) should move along with the first (last) bend to retain orthogonality of the first (last) edge segment.

Individual edge editing behavior can be achieved by decorating the look-up of edges to return a custom IOrthogonalEdgeHelper implementation when this type is queried. Tutorial demo application OrthogonalEdgesDemo.mxml demonstrates this scheme.

Support for Interactive Snapping of Graph Elements

Interactive snapping of graph elements supports a user in the manual creation of a diagram or when manually re-arranging a diagram. It greatly reduces the time for properly aligning graph elements and helps getting clear and concise diagrams where nodes are equally distant from each other. Also, it simplifies retouching edges and helps getting orthogonal edge paths almost instantly.

Class GraphEditorInputMode, together with the specialized input modes it makes available, provides special support for interactive snapping of graph elements, which means visual and also "perceptible" feedback during a mouse drag gesture when graph elements

  • are aligned (top, bottom, left, right, middle),
  • are equidistant,
  • have the same width/height, or
  • have some specified distances from each other.

Specifically, the following mouse gestures offer specialized behavior:

Figure 2.42, “Visual feedback during mouse drag gestures with snapping support enabled” illustrates the visual feedback that is given during mouse drag gestures.

Figure 2.42. Visual feedback during mouse drag gestures with snapping support enabled

Visual feedback.
Visual feedback.
Visual feedback.
Two nodes aligned at their center coordinates (moving a node). Equidistant between two nodes (moving a node). Same widths (resizing a node).

During a mouse drag gesture, the "perceptible" feedback can be experienced as

  • a small jump, when dragging towards a coordinate where nodes are aligned, for example, and
  • a small halt/lag, when dragging away from that coordinate.

Such "coordinates of interest," i.e., where a node that is currently being moved and some other node(s) are aligned, for example, are called snap lines.

The support for interactive snapping of graph elements can be conveniently enabled on class GraphEditorInputMode by setting a GraphSnapContext using the following property:

snapContext:SnapContext
Description Convenience property for setting the snap context and enabling snapping support.

By default, snapping support then covers alignment, equidistance, and same node widths/heights, for example. Further customization, like setting preferred distances between graph elements, can be done through properties of GraphSnapContext:

nodeToNodeDistance:Number
nodeToEdgeDistance:Number
edgeToEdgeDistance:Number
Description Specifying preferred distances between graph elements.

Class GraphSnapContext handles interactive snapping of graph elements across the involved input modes. Technically, when a drag gesture is recognized, GraphSnapContext determines fixed and moved graph elements and queries the look-up of the former for implementations of interface ISnapLineProvider to collect possible snap lines. The look-up of the latter set of graph elements gets queried for implementations of snap result providers, which enable selection of "matching" snap lines from the set of collected possible snap lines. See also Table 2.23, “User gestures and interfaces implementations in the look-up”.

Customizing the interactive snapping behavior beyond the GraphSnapContext properties can be done by decorating the look-up of model items to return custom implementations of snap line providers and/or snap result providers. Tutorial demo application CustomSnappingDemo.mxml shows how custom variants of both snap line and snap result providers can be added using the decorator scheme.

Customizing interactive snapping behavior during edge creation can be done by overriding the following method in CreatedEdgeInputMode:

GraphSnapContext also supports snapping of graph elements to a grid. The following properties can be used to set IGridConstraintProvider implementations for model items in order to control their grid snapping behavior:

Example 2.33, “Enabling grid snapping” shows the setup for grid snapping support similar to that in tutorial demo application SnapLineDemo.mxml. The GridInfo class conveniently holds the properties of a grid, such as spacing and origin.

Example 2.33. Enabling grid snapping

// 'gc' is of type com.yworks.ui.GraphCanvasComponent.

// Grid initialization.
var gridInfo:GridInfo = new GridInfo(50);

// Enabling grid snapping for graph elements.
var snapContext:GraphSnapContext = new GraphSnapContext();
var gcp:SimpleGridConstraintProvider =
    new SimpleGridConstraintProvider(gridInfo);
snapContext.nodeGridConstraintProvider = gcp;
snapContext.bendGridConstraintProvider = gcp;
snapContext.portGridConstraintProvider = gcp;

// Enabling snapping for graph elements.
var geim:GraphEditorInputMode = new GraphEditorInputMode();
geim.snapContext = snapContext;

gc.inputMode = geim;
Tutorial Demo Code

The following tutorial demo applications present both setup as well as customization of interactive snapping support:

Class MainInputMode

Class MainInputMode defines a powerful basis for controller implementations that need to handle a broad range of user gestures happening in the view. It makes available the functionality of a set of specialized input modes that are installed concurrently with the canvas.

Figure 2.43. MainInputMode type hierarchy

MainInputMode type hierarchy.

The direct base type of class MainInputMode is MultiplexingInputMode, an input mode that enables input modes to be installed with the canvas and be executed concurrently. As part of the concurrency amongst these input modes, MultiplexingInputMode can grant an input mode exclusive control over all canvas interaction.

Input modes that support concurrency are of type IConcurrentInputMode and are also referred to as "concurrent input modes." They can be added to the set of input modes that are managed by MultiplexingInputMode by means of the following method:

addConcurrent(inputMode:IConcurrentInputMode, priority:int):void
Description Adding concurrent input modes to a MultiplexingInputMode's concurrency controller.

Input Mode Priorities

MultiplexingInputMode also introduces the notion of priority with the input modes it installs into the canvas. The priority value of an input mode determines when it is installed, which in turn defines its position in the queue of input modes that are delivered user events. A low priority value means that an input mode comes early in this queue and thus processes an event prior to other input modes with a higher priority value that are also registered with that event.

Upon initialization, MainInputMode creates and installs the input modes listed in Table 2.21, “Input modes used by MainInputMode (sorted by input mode priority)” as concurrent input modes. Note that the input modes are sorted according to their priority.

Table 2.21. Input modes used by MainInputMode (sorted by input mode priority)

Type Name Description
HandleInputMode Manages the dragging of "handles," including the detection of beginning and ending of a drag, and canceling a drag. Also provides the visual feedback for handles when dragged.
KeyboardInputMode Recognizes key events.
ClickInputMode Recognizes mouse clicks, including double clicks.
MoveInputMode Handles moving of canvas objects.
MarqueeSelectionInputMode Provides the visual feedback for a rectangular selection box in the canvas.
ContextMenuInputMode Displays a context menu.
MouseHoverInputMode Displays a tooltip when the mouse rests in the canvas.

Each of the input modes can be obtained and changed using a like named property defined by class MainInputMode. Also, a sorted list of input modes can be obtained by means of the sortedModes() method.

Other Input Modes

yFiles FLEX offers a number of input modes which are not submodes of the GraphEditorInputMode or MainInputMode. Typically, these are input modes which enable less common user interactions such as hover effects. Table 2.22, “Input modes which are not used by GraphEditorInputMode or MainInputMode” lists these input modes.

Table 2.22. Input modes which are not used by GraphEditorInputMode or MainInputMode

Type Name Description
MagnifierInputMode Adds a magnifier (looking glass) effect to the canvas component.
MouseHoverInputMode Detects when the mouse hovers over the canvas. Usually used for displaying Tooltips.
MoveViewportInputMode Enables moving of the viewport (panning) by means of a simple mouse move gesture.
NodeEffectInputMode Adds hover effects to model items.
OverviewInputMode Links an overview component to a canvas.

Class MoveViewportInputMode

Class MoveViewportInputMode enables moving of the viewport by means of a simple mouse move gesture.

Creating Custom Context Menus

Context menus are handled by the ContextMenuInputMode. This mode is registered by default with the MainInputMode and can be retrieved via its contextMenuInputMode property. To add custom menu entries one has to register an event listener for CanvasContextMenuEvent.MENU_SELECTED events:

Example 2.34. Adding an event listener to the ContextMenuInputMode

// input mode is the MainInputMode or GraphEditorInputMode which is 
// registered at the GraphCanvasComponent
inputMode.contextMenuInputMode.addEventListener(
                CanvasContextMenuEvent.MENU_SELECT, 
                contextMenuSelectListener);

The listener will be called each time a context menu should be opened at the GraphCanvasComponent. As the context menu is actually the Flash Player's context menu it already contains a number of entries. Unless one wants to keep the original entries (such as "Zoom in" or "Loop"), he has to remove these entries first before adding his own custom entries:

var cm:ContextMenu = evt.contextMenuOwner.contextMenu as ContextMenu;
cm.hideBuiltInItems();

Note that not all entries can be removed, "About Flash Player" and "Settings" will always be visible. The debug version of Flash Player has even more non-removable items.

To add custom items, one has to create a ContextMenuItem instance, add an event listener for to that instance and add the item to the context menu:

var cItem:ContextMenuItem = new ContextMenuItem("Text to be displayed");
cItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT, onItemSelected);
cm.customItems.push(cItem);

Usually, one wants to display the context menu accordig to the item which is found at the mouse position. The following method will retrieve the first node which can be found at a given position or null, if none is found:

Example 2.35. Finding a node at a given location.

// returns the topmost node found at the given location
private function findHitNode( graphCanvas:CanvasComponent, 
                              hitX:Number,
                              hitY:Number ):INode {
  // get an iterator over all canvas objects at the given location
  var it:Iterator = graphCanvas.getCanvasObjects( hitX, hitY ).iterator();
  while( it.hasNext() ) {
     var hitObj:ICanvasObject = ICanvasObject( it.next() );
     // the model item can be retrieved from the userObject of the canvas object
     var hitNode:INode = hitObj.userObject as INode;
     if( null != hitNode ) {
       // return the first node which is found
       return hitNode;
     }
  }
  return null;
}

Putting all this together, the following example shows an event listener for CanvasContextMenuEvent.MENU_SELECT which will create a context menu according to some custom data which is mapped to the node via an IMapper as described in the section called “Mapping Data to Graph Elements”:

Example 2.36. A listener for CanvasContextMenuEvent.MENU_SELECT events.

private function contextMenuSelectListener(event:CanvasContextMenuEvent):void {
  // get the context menu
  var cm:ContextMenu = event.contextMenuOwner.contextMenu as ContextMenu;
  // hide Flash Player items
  cm.hideBuiltInItems();
  // find the topmost node at cursor position
  var node:INode = findHitNode(graphCanvas, event.worldX, event.worldY);
  // if there is one
  if (node != null) {
    // get associated data
    var map:IMapper = graphCanvas.graph.mapperRegistry.getMapper("MyMapper");
    var data:Object = map.lookupValue(node);
    if (data != null) {
      // if there is data associated with the node: create a menu item.
      // the item will be displayed with the text "Menu Entry" (the given text).
      var cItem:ContextMenuItem = new ContextMenuItem("Menu Entry");
      // add a listener which is executed when the menu item is selected.
      // This can be an anonymous function.
      cItem.addEventListener(ContextMenuEvent.MENU_ITEM_SELECT,
          function( event:ContextMenuEvent):void {
          // do something with node or data
        });
      cm.customItems.push(cItem);
    }
  }
}

Customizing Input Modes

The behavior of input modes can be easily customized by means of their properties, using custom callback methods, or by replacing specialized input modes by customized implementations thereof.

For example, to customize the text of the tooltip that is displayed by MouseHoverInputMode, an appropriate callback method can be registered with the input mode's textProvider property. Example 2.37, “Setting text providers for the tooltips displayed by concurrent MouseHoverInputMode instances” presents this scheme in the context of two concurrent MouseHoverInputModes.

Example 2.37. Setting text providers for the tooltips displayed by concurrent MouseHoverInputMode instances

// 'mainInputMode' is of type com.yworks.canvas.input.MainInputMode.

var nodeTextProvider:Function = 
    TooltipTextProviders.getGraphItemMapperTextProvider(
        INode, "node-description");
var nodeHoverMode:MouseHoverInputMode = 
    new MouseHoverInputMode(new ToolTip(), nodeTextProvider);
nodeHoverMode.showEffect = fadeIn;
nodeHoverMode.hideEffect = fadeOut;

mainInputMode.addConcurrent(nodeHoverMode, 120);
			
var edgeTextProvider:Function = 
    TooltipTextProviders.getGraphItemMapperTextProvider(
        IEdge, "edge-description");
var edgeHoverMode:MouseHoverInputMode = 
    new MouseHoverInputMode(new ToolTip(), edgeTextProvider);
edgeHoverMode.showEffect = fadeIn;
edgeHoverMode.hideEffect = fadeOut;

mainInputMode.addConcurrent(edgeHoverMode, 120);

Customizing User Interaction Behavior

Input modes that handle specific mouse gestures when a user interacts with the graph elements often either directly or indirectly use the look-up mechanism to query additional information in relation to the respective gesture. This information generally also affects the look and feel of the elements. Consequently, the look and feel of an application, i.e., the rendering and the behavior of graph elements whenever a user is interacting with them, can be conveniently customized by decorating their look-up. For instance, the selection decoration of nodes can be customized, or their resizing behavior can be changed to obey only discrete steps, simply by adding appropriate interface implementations to the look-up mechanism.

Example 2.38, “Decorating the look-up for nodes” shows how the look-up for nodes is decorated using the ILookupDecorator returned by the graph. A custom look-up chain link, which replaces the default look-up logic for nodes, is added to the look-up chain that is maintained by the graph. Whenever a node gets selected, the new logic then returns a custom ISelectionPaintable implementation.

More information on the look-up support provided by the graph structure implementation can be found in the section called “Look-up Support”.

Example 2.38. Decorating the look-up for nodes

// 'graph' is of type com.yworks.graph.model.IGraph.

// Decorate the look-up for nodes to change their default behavior.
var decorator:ILookupDecorator = 
  graph.lookup(ILookupDecorator) as ILookupDecorator;
if (decorator != null) {
  if (decorator.canDecorate(INode)) {
    decorator.addLookup(INode, 
      Lookups.addingLookupChainLink(ISelectionPaintable, 
                                    new MyCustomSelectionPaintable()));
  }
}

Table 2.23, “User gestures and interfaces implementations in the look-up” gives an overview on the interface implementations that are queried when certain user gestures are handled.

Table 2.23. User gestures and interfaces implementations in the look-up

Gesture Type Name Description
Edge creation IPortCandidateProvider Returns collections of ports that an edge can connect to. When a new edge is created, CreateEdgeInputMode queries the look-up of the source node and the target node for implementations of this interface.

Implementations of this interface include: EmptyPortsCandidateProvider, NodeCenterPortCandidateProvider, UnoccupiedPortCandidateProvider, PortCandidateProvider, and ShapeGeometryPortCandidateProvider.

Also, abstract class AbstractPortCandidateProvider is a convenience implementation of this interface.

Resizing IReshapeHandler Enables handling the overall process of resizing a model item (INode).
IReshapeHandleProvider Offers control over the resize behavior for a model item that is displayed in the canvas. When a model item (INode) gets resized, HandleInputMode queries the item's look-up for implementations of this interface.

Class ReshapeableHandles is a convenience implementation of this interface.

ISizeConstraintProvider Enables definition of minimum and maximum size constraints for items of arbitrary type. In a hierarchically organized graph, the size of group nodes is affected by implementations of this interface.

Class SizeConstraintProvider is a convenient default implementation that allows to specify minimum and maximum size constraints for model items (INode). This type is in the look-up for non-leaf nodes in a hierarchically organized graph.

ISnapLineProvider Adds possible snap lines for a model item. When interactive snapping support is enabled (i.e., when GraphSnapContext is available in the input mode context), GraphSnapContext queries the look-up of all model items (INode, IEdge, IPort) that do not get resized for an implementation of this interface. The gathered information is then filtered to include only those of visible model items.

Implementations of this interface include: NodeSnapLineProvider (by default in the look-up for INode instances) and EdgeSnapLineProvider (by default in the look-up for IEdge instances).

INodeReshapeSnapResultProvider Enables selection of "matching" snap lines from the set of collected possible snap lines. When interactive snapping support is enabled, GraphSnapContext queries the look-up of all nodes that get resized for implementations of this interface.
IOrthogonalEdgeHelper Offers control over orthogonal edge editing behavior. When orthogonal edge editing is enabled (i.e., when OrthogonalEdgeEditingContext is available in the input mode context), OrthogonalEdgeEditingContext queries the look-up of all edges incident to nodes that get resized for an implementation of this interface.

Class OrthogonalEdgeHelper is a convenient default implementation of this interface.

Selecting ISelectionPaintable Allows to install any number of ICanvasObject instances that make up the selection decoration for a model item that is displayed in the canvas. Whenever a model item gets selected, its look-up is queried by the SelectionPaintManager for implementations of this interface.

Implementations of this interface include: OrientedRectangleSelectionPaintable (by default in the look-up for ILabel instances), PointSelectionPaintable, RectangularHighlightPaintable, and RectangularSelectionPaintable (by default in the look-up for INode instances).

Edge reconnecting, port relocating IEdgePortCandidateProvider Returns collections of ports that an edge can connect to. When either end of an edge gets relocated, the look-up of the edge is queried for implementations of this interface.

Implementations of this interface include: DefaultEdgePortsCandidateProvider (by default in the look-up for IEdge instances), CurrentEdgePortsCandidateProvider, and AllCandidatesEdgePortCandidateProvider.

ISnapLineProvider Adds possible snap lines for a model item. When interactive snapping support is enabled (i.e., when GraphSnapContext is available in the input mode context), GraphSnapContext queries the look-up of all model items (INode, IEdge, IPort) that do not get moved for an implementation of this interface. The gathered information is then filtered to include only those of visible model items.

Implementations of this interface include: NodeSnapLineProvider (by default in the look-up for INode instances) and EdgeSnapLineProvider (by default in the look-up for IEdge instances).

IPortSnapResultProvider Enables selection of "matching" snap lines from the set of collected possible snap lines. When interactive snapping support is enabled, GraphSnapContext queries the look-up of the ports for implementations of this interface.
Moving IPositionHandler Provides callback methods that allow to control all parts of a mouse drag gesture. When a model item (INode, IPort, IBend, ILabel) gets moved, MoveInputMode and MoveLabelInputMode query the item's look-up for implementations of this interface.

Class DefaultPositionHandler is a convenient default implementation of this interface.

ISnapLineProvider Adds possible snap lines for a model item. When interactive snapping support is enabled (i.e., when GraphSnapContext is available in the input mode context), GraphSnapContext queries the look-up of all model items (INode, IEdge, IPort) that do not get moved for an implementation of this interface. The gathered information is then filtered to include only those of visible model items.

Implementations of this interface include: NodeSnapLineProvider (by default in the look-up for INode instances) and EdgeSnapLineProvider (by default in the look-up for IEdge instances).

INodeSnapResultProvider, IEdgeSnapResultProvider, and IBendSnapResultProvider Enable selection of "matching" snap lines from the set of collected possible snap lines. When interactive snapping support is enabled, GraphSnapContext queries the look-up of all model items (INode, IEdge, IBend) that get moved for implementations of the corresponding interface.
Moving an edge segment (orthogonal edge editing) IOrthogonalEdgeHelper Offers control over orthogonal edge editing behavior. When orthogonal edge editing is enabled (i.e., when OrthogonalEdgeEditingContext is available in the input mode context), OrthogonalEdgeEditingContext queries the look-up of all edges where segments get moved for an implementation of this interface.

Class OrthogonalEdgeHelper is a convenient default implementation of this interface.

Customization of the re-parenting gesture in a hierarchically organized graph, which can be controlled using interface IReparentNodeHandler, is described in the section called “Class GraphEditorInputMode”.

Example 2.39, “Customizing the way a selected node is rendered” presents a custom implementation for interface ISelectionPaintable that draws a gray rectangle around a selected node.

Example 2.39. Customizing the way a selected node is rendered

public class MyCustomSelectionPaintable implements ISelectionPaintable {
  public function installSelectionPaintables(
      userObject:Object, canvas:CanvasComponent, 
      selectionGroup:ICanvasObjectGroup):Array {
    var n:INode = INode(userObject);
    var rect:ShapePaintable = 
      ShapePaintable.createViewRectangle(new DecoratedRectangle(n.layout, 3));
    rect.stroke = new Stroke(0xA9A9A9, 2);
    
    return [canvas.addCanvasObject(rect,
      new MyDescriptor(rect), selectionGroup) ];
  }
}

public class MyDescriptor implements ICanvasObjectDescriptor {
  private var _creator:IDisplayObjectCreator;
  public function MyDescriptor(creator:IDisplayObjectCreator) {
      this._creator = creator;
  }
  public function getDisplayObjectCreator(for:Object):IDisplayObjectCreator {
      return this._creator;
  }
  public function getBoundsProvider(forUserObject:Object):IBoundsProvider {
      return null;
  }
  public function getHitTestable( forUserObject:Object ):IHitTestable {
      return null;
  }
  public function isDirty( obj:ICanvasObject ):Boolean {
      return true;
  }
}

public class DecoratedRectangle implements IRectangle {
  private var box:IRectangle;
  private var borderWidth:Number;

  public function DecoratedRectangle(box:IRectangle, borderWidth:Number) {
    this.box = box;
    this.borderWidth = borderWidth;
  }

  public function get x():Number { 
    return box.x - borderWidth;
  }
  public function get y():Number {
    return box.y - borderWidth;
  }

  public function get width():Number {
    return box.width + 2 * borderWidth;
  }
  public function get height():Number { 
    return box.height + 2 * borderWidth;
  }
}

Customizing Edge Creation using Port Candidates

Edges are connected to nodes via ports. During edge creation and edge relocation edges are linked to possible candidates for such ports, the port candidates. Table 2.24, “Methods and properties of IPortCandidate” gives an overview on the interface IPortCandidate.

Table 2.24. Methods and properties of IPortCandidate

Property/Method Description
owner The IPortOwner to create the port for.
candidateTag A user defined tag for this candidate (not used by the API)
validity A PortCandidateValidity describing the validity level of the port candidate as explained in Table 2.25, “Public Constants of PortCandidateValidity”
getPortCandidateAt() may be called for dynamic port candidates to provide another valid or invalid port candidate for the specified location
createInstance() may be called for non-dynamic port candidates and serves as a factory method to create a port which corresponds to this candidate
getInstance() Returns the (existing) port which is wrapped by this candidate or null
locationModelParameter() Returns the model parameter that will be used for the port if this candidate is chosen.

The validity of a port candidate determines if the candidate can be used to create a port to connect an edge to. Table 2.25, “Public Constants of PortCandidateValidity” gives an overview on the values this property may have.

Table 2.25. Public Constants of PortCandidateValidity

Value Description
VALID The candidate may be used to provide a port to connect an edge to it.
INVALID The candidate may NOT be used to provide a port to connect an edge to it. Invalid port candidates can be used to give feedback that a port candidate does exist but isn't valid for the edge creation.
DYNAMIC The candidate cannot be used to create a port directly. Instead it can be used to provide another (valid or invalid) port candidate for a specified location using the method getPortCandidateAt().

These candidates are provided by a IPortCandidateProvider. By creating a custom IPortCandidateProvider implementation one can restrict edge creation to his own rules. The IPortCandidateProvider is retrieved via a IPortOwner's lookup, thus the IPortCandidateProvider is related to a IPortOwner.

The API provides a set of IPortCandidateProvider which can be used for the most common tasks:

Table 2.26. Implementations of IPortCandidateProvider

Class Purpose
EmptyPortsCandidateProvider Always returns an empty set.
NodeCenterPortCandidateProvider Creates a set with one port candidate at the center of the node.
PortCandidateProvider Returns a list of port candidates which has been passed to this instance before.
ShapeGeometryPortCandidateProvider Returns a set of port candidates which are located along the outline of the node.
UnoccupiedPortCandidateProvider Returns a set with a port candidate for each unoccupied port of the node.

Creating a custom IPortCandidateProvider

The interface IPortCandidateProvider defines six methods which are listed in Table 2.27, “Methods defined by the interface IPortCandidateProvider”. All methods return a set of port candidates. Implementations have to determine the possible candidates and for each candidate wheter connecting to that candidate is allowed. If it is not allowed, that candidate can either be removed from the list or have its validity property set to PortCandidateValidity.INVALID. If no possible candidates are found, implementations can either return an empty list, e.g. instance() or a list which contains one or more invalid port candidates. Using invalid port candidates has the advantage that the user is given a visual feedback that connecting to this location is not allowed.

Table 2.27. Methods defined by the interface IPortCandidateProvider

Method Description Queried during
getCandidateSourcePortCandidates() Returns the source port candidates for the current node which apply for the provided (opposite) target port candidate. This method is currently not queried
getCandidateTargetPortCandidates() Returns the target port candidates for the current node which apply for the provided (opposite) source port candidate. Edge creation: possible target locations.
getEdgeSourcePortCandidates() Returns all port candidates the provided edge's source can connect to. Edge relocation: possible source locations at the current source node.
getEdgeTargetPortCandidates() Returns all port candidates the provided edge's target can connect to. Edge relocation: possible target locations at the current target node.
getGraphSourcePortCandidates() Returns all possible source port candidates. Edge creation: possible start points.
getGraphTargetPortCandidates() Returns all possible source port candidates. This method is currently not queried using the default settings.

Note that when using the AllCandidatesEdgePortCandidateProvider for edge relocation, getGraphSourcePortCandidates() and getGraphTargetPortCandidates() are used instead of getEdgeSourcePortCandidates() and getEdgeTargetPortCandidates() to return possible source and target locations.

Figure 2.44. Port candidates and edge creation: no candidates, an invalid candidate, a valid candidate.

Port candidates and edge creation: no candidates, an invalid candidate, a valid candidate.
Invalid port candidates add visual feedback to edge creation: the red square of the invalid port candidate in the middle shows explicitly that it is not allowed to connect to that node.

An abstract implementation, the AbstractPortCandidateProvider, can be used to create custom port candidate providers. It implements the methods of IPortCandidateProvider such that they delegate to the method getPortCandidates(). Additionally, it has some methods which faciliate the implementation of a custom provider:

Table 2.28. Methods of AbstractPortCandidateProvider

Method Purpose
addExistingPorts() Adds all existing ports (not candidates!) of the portOwner to the provided List
createCallbackPort() Creates a port candidate for the given location which delegates its createInstance() method to this instance's createInstance() method. This method should not be overridden.
createInstance() Called by a port candidate which is created by createCallbackPort() when the candidate's createInstance() method is called. This implementation creates a new port at the candidate's location.
getPortCandidates() Called by the IPortCandidateProvider implementing methods. Has to be overridden to return an Iterable of possible port candidates.
setPortOwner() Sets the owner of the port candidates. Has to be set (preferrably in the constructor), otherwise the above methods won't work properly.

The API provides different ways to create instances of IPortCandidate:

Example 2.40. Creating IPortCandidate instances

// creates a candidate for the given port
var candidate:IPortCandidate = DefaultPortCandidate.create(port);

// creates a candidate which will create a port at the given location
// (called inside a class which extends AbstractPortCandidateProvider)
var candidate:IPortCandidate = createCallbackPort(graph, location); 

// creates an Iterable which contains a candidate
// at the center of the provided node
var candidates:Iterable = 
    new NodeCenterPortCandidateProvider(node).
                            createGraphTargetPortCandidates(context, graph);

Example code
Example 2.42, “A custom IPortCandidateProvider returned by a lookup chain link” shows how to create a custom port candidate provider. The provider is added as a lookup chain link (see also the section called “Look-up Mechanism”) as shown in Example 2.41, “Adding a custom provider via lookup”.

Example 2.41. Adding a custom provider via lookup

var decorator:ILookupDecorator = 
                    graph.lookup(ILookupDecorator) as ILookupDecorator;
if (decorator != null && decorator.canDecorate(INode)) {
	decorator.addLookup(INode, new PortCPChainLink());
}
The custom provider allows linking nodes with an edge only if their types match. The types are stored as arbitraty objects in the node's user tags (see also the section called “User Tags”). The example creates a candidate at the center and marks it as invalid if the types don't match.

Example 2.42. A custom IPortCandidateProvider returned by a lookup chain link

// Lookup chain link which returns a custom IPortCandidateProvider for nodes
public class PortCPChainLink extends AbstractContextLookupChainLink
{
  public override function lookupForItem(item:Object, type:Class):Object {
    if (item is INode && type == IPortCandidateProvider) {
        return new PortCandidateProviderExample( item as INode );
    }
    // looking for something other: continue in the chain
    return super.lookupForItem(item, type);
  }
}

// The actual provider
public class PortCandidateProviderExample extends AbstractPortCandidateProvider
{

  // Constructor has to set the owner of the candidates.
  public function PortCandidateProviderExample( node:INode ) {
      setPortOwner(node);
  }
	
  // Returns candidates for a target with a known source candidate.
  // This is queried at the end of edge creation.
  public override function getCandidateTargetPortCandidates(
                                            context:IInputModeContext, 
                                            graph:IGraph, 
                                            source:IPortCandidate):Iterable {
    // gets a list of candidates at the center. 
    // delegates to NodeCenterPortCandidateProvider.
    var candidates:Iterable = 
             new NodeCenterPortCandidateProvider(this.portOwner as INode)
                    .getCandidateTargetPortCandidates(context, graph, source);
    for (var it:Iterator = candidates.iterator(); it.hasNext();) {
      var candidate:DefaultPortCandidate = it.next() as DefaultPortCandidate;
      if (candidate != null) {
        // if both nodes to be connected have the same type: allow connection
        candidate.validity = (getType(this.portOwner) == getType(source.owner))
                              ? PortCandidateValidity.VALID
                              : PortCandidateValidity.INVALID;
      }
    }
    return candidates;
  }
	
  // Gets the type of the port owner (the user tag)
  private function getType(portOwner:IPortOwner):Object {
    var tagOwner:ITagOwner = portOwner.lookup(ITagOwner) as ITagOwner;
    if (tagOwner != null) {
      return tagOwner.tag;
    }
    return null;
  }
	
  // All other methods: return a candidate at the center.
  protected override function getPortCandidates(context:IInputModeContext, 
                                                graph:IGraph):Iterable {
    var layout:IRectangle = INode(this.portOwner).layout;
    // create a candidate which creates a new port when it is accepted.
    var candidate:IPortCandidate = createCallbackPort(
                        graph,
                        NodeScaledPortLocationModel.NODE_CENTER_ANCHORED);
    var list:ArrayList = new ArrayList();
    list.addItem(candidate);
    return list;
  }
}

Further information about creating custom IPortCandidateProviders can be found in a Knowledge Base article.

The PortCandidateProviderDemo demonstrates how to customize an IPortCandidateProvider for various needs.