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 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.
GraphEditorInputMode provides convenient support for creating, modifying, and interacting with graph elements in an IGraph instance.
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.
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 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:
Additionally, the following mouse gestures, which have an impact on edge paths, too, also offer specialized behavior:
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.
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
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
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
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:
protected function getDummyEdgeSnapLines( graphSnapContext:GraphSnapContext, dummyEdgeSegmentSnapLineProvider:ISnapLineProvider):Iterable |
|
Description | Enables customization of the snapping behavior of an edge in creation. |
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:
nodeGridConstraintProvider:INodeGridConstraintProvider bendGridConstraintProvider:IBendGridConstraintProvider portGridConstraintProvider:IPortGridConstraintProvider |
|
Description | Allow to control the grid snapping behavior of nodes, bends, and ports. |
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;
The following tutorial demo applications present both setup as well as customization of interactive snapping support:
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.
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. |
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.
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 enables moving of the viewport by means of a simple mouse move gesture.
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); } } }
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);
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; } }
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. |
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.
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 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()); }
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.
Copyright ©2007-2015, yWorks GmbH. All rights reserved. |