Creating Edges
Edge creation is handled by CreateEdgeInputMode, mostly used as a child input mode of GraphEditorInputMode.
CreateEdgeInputMode allows you to customize edge creation in various ways using these properties:
- startOverCandidateOnly
- Specifies whether edge creation must begin directly from a start port candidate or if it can start anywhere over the port candidate’s owner (typically a node).
By default, this property is set to
true
, meaning edges can only be created by dragging from a specific candidate. - showPortCandidates
- showStartPortCandidateDelay
- Determines whether to show port candidates for start and end nodes and whether to display start port candidates (if enabled) only after a delay. By default, candidates are shown for both start and target ports, and the start port candidates are shown without delay.
- allowCreateBend
- Controls whether bends can be added during edge creation. Note that to disallow creating bends entirely, bend creation for existing edges must be disabled, too.
- edgeDefaults
- Specifies the defaults for newly-created edges, most notably their style. This works the same as setting defaults for new edges on a graph.
- forceSnapToCandidate
- Specifies whether the edge’s end should snap to the nearest end candidate if the mouse hovers over an end port candidate owner. This is enabled by default.
- allowSelfLoops
- minimumSelfLoopBendCount
- Specifies whether a self-loop, i.e. an edge that connects a node with itself, can be created, and how many bends it must have at least.
The actual edge creation is done in method createEdge which delegates to the callback set in edgeCreator. While it is possible to customize the actual edge creation by overriding createEdge, setting a new edgeCreator should be preferred:
// set a custom edge creator which creates a label on the new edge
graphEditorInputMode.createEdgeInputMode.edgeCreator = (
context,
graph,
sourceCandidate,
targetCandidate,
previewEdge
) => {
// the targetCandidate might be null if the edge creation ended prematurely
if (targetCandidate === null || previewEdge === null) {
// no target - no edge
return null
}
// get the source and target ports from the candidates
const sourcePort =
sourceCandidate.port || sourceCandidate.createPort(context)
const targetPort =
targetCandidate.port || targetCandidate.createPort(context)
// create the edge between the source and target port
const edge = graph.createEdge(sourcePort, targetPort, previewEdge.style)
// create a label
graph.addLabel(edge, 'A new edge')
// return the created edge
return edge
}
// set a custom edge creator which creates a label on the new edge
graphEditorInputMode.createEdgeInputMode.edgeCreator = (
context: IInputModeContext,
graph: IGraph,
sourceCandidate: IPortCandidate,
targetCandidate: IPortCandidate | null,
previewEdge: IEdge | null
) => {
// the targetCandidate might be null if the edge creation ended prematurely
if (targetCandidate === null || previewEdge === null) {
// no target - no edge
return null
}
// get the source and target ports from the candidates
const sourcePort =
sourceCandidate.port || sourceCandidate.createPort(context)
const targetPort =
targetCandidate.port || targetCandidate.createPort(context)
// create the edge between the source and target port
const edge = graph.createEdge(sourcePort, targetPort, previewEdge.style)
// create a label
graph.addLabel(edge, 'A new edge')
// return the created edge
return edge
}
If prematureEndHitTestable is not changed the
edgeCreator's targetCandidate
parameter is
never null
.
The following events occur during an edge creation gesture (in the order they might occur):
Event | Occurs when … |
---|---|
During edge creation, the cursor changes to indicate possible interactions. The following properties allow to change the cursor for each interaction:
- validBeginCursor
- The mouse cursor to show when edge creation can start. This is usually when hovering over a source port candidate.
- startPortOwnerDraggingCursor
- The mouse cursor to show when edge creation has started but the mouse is still hovering over the source port candidate owner.
- dragCursor
- The mouse cursor to show while edge creation when there is no valid location to create a bend. This is usually when hovering over a source port candidate.
- validBendCursor
- The mouse cursor to show when a bend can be added. This is usually when the mouse pointer is over an empty part of the canvas during edge creation.
- validEndCursor
- The mouse cursor to show when edge creation can finish. This is usually when hovering over a possible target node during edge creation.
Changing the Appearance of the Edge to be Created
During edge creation, a preview of the new edge is displayed. The IEdge instance that represents this preview edge can be retrieved via the previewEdge property. With the default edgeCreator, the newly created edge will adopt the preview’s appearance. You can change the appearance of the preview edge (e.g., setting a different style or adding labels) before the gesture using CreateEdgeInputMode's edgeDefaults or during the gesture using the graph provided by previewGraph.
The latter option allows for more complex customizations. By default, all properties of the preview edge are copied to the final edge that is created. This includes bends, style, labels, and the edge’s tag. Thus, a viable option for customizing the created edge can also be to change the preview edge during the gesture and have those changes copied automatically to the final edge. The changes are applied in real-time while the gesture is performed. In contrast, changes made by the edgeCreator take effect only after the edge creation gesture is completed.
// add an event handler for the gesture-starting event that adds a label to the preview edge
graphEditorInputMode.createEdgeInputMode.addEventListener(
'gesture-starting',
(_, sender) => {
const createEdgeInputMode = sender
createEdgeInputMode.previewGraph.addLabel(
createEdgeInputMode.previewEdge,
'A new edge'
)
}
)
The preview edge is reset to its default state after every edge creation gesture. Therefore, no further cleanup is necessary to prevent preview edge changes from persisting over multiple gestures.
Port Candidates and Port Candidate Providers
Fine-grained control over where edges can and cannot connect during interactive edge creation is possible using port candidates. The CreateEdgeInputMode retrieves an IPortCandidateProvider from the lookup of a potential port owner under the cursor and queries it for potential candidates for both source and target ports. The provider returns a set of port candidates, which represent potential connection points for the source and target ports of the edge to be created.
An IPortCandidate represents a potential port. This may be an existing port or a placeholder for a port that will be created, depending on the implementation. Additionally, an IPortCandidate has a validity that determines how (or if) edges can connect to it. The CreateEdgeInputMode can potentially connect the edge being created to a valid port candidate, but not to an invalid one.

Note that invalid port candidates are visually indicated, providing a better user experience than simply not providing a port candidate. They also distinguish the case “no edge may connect here” (no candidate) from “this edge may not connect here” (invalid candidate), similar to disabled UI controls.
A third kind of validity is DYNAMIC: a dynamic port candidate has no fixed position but will be placed at the current cursor location. This usually requires a gesture, such as holding down the Control key.
When edge creation is finished, the IPortCandidate’s port property is queried first. If the candidate represents an existing port, that port is used. Otherwise, the createPort method is called by the CreateEdgeInputMode to create and provide a suitable port.
IPortCandidates are provided by an IPortCandidateProvider, which is retrieved from the lookup of a potential port owner. During edge creation, the CreateEdgeInputMode calls the following methods:
Method | Description |
---|---|
Implementers must implement the methods above to return an appropriate list of candidates. This list may be empty (or contain only invalid candidates) if an edge should not connect to the owner associated with the provider.
The PortCandidateProviderBase class simplifies the IPortCandidateProvider interface. All its IPortCandidateProvider method implementations delegate to the getPortCandidates method and thus all return the same set of candidates. They can also be overridden to return something different, of course. Furthermore, PortCandidateProviderBase provides a set of convenience methods that you can use to easily create port candidates: the addExistingPorts, and createCandidate.
The following example shows a simple port candidate provider that allows edges to connect to existing ports, but only if the source port has the same tag as the target port:
class SameTagPortCandidateProvider extends PortCandidateProviderBase {
owner
// each instance is built for a specific port owner
constructor(owner) {
super()
this.owner = owner
}
// the port candidate list which is returned by default
getPortCandidates(context) {
// create a new list
const candidates = new List()
// add candidates for all existing ports
this.addExistingPorts(this.owner, candidates)
return candidates
}
// override this method to return candidates for each existing port
// but mark some as invalid
getTargetPortCandidates(context, source) {
return this.owner.ports
.map((port) => {
// create a candidate
const pc = new PortCandidate(port)
// mark it as invalid if the source port's user tag
// is not equal to the current port's user tag
pc.validity =
port.tag === source.port.tag
? PortCandidateValidity.VALID
: PortCandidateValidity.INVALID
return pc
})
.toList()
}
}
class SameTagPortCandidateProvider extends PortCandidateProviderBase {
// each instance is built for a specific port owner
constructor(private readonly owner: INode) {
super()
}
// the port candidate list which is returned by default
getPortCandidates(context: IInputModeContext): IEnumerable<IPortCandidate> {
// create a new list
const candidates = new List<IPortCandidate>()
// add candidates for all existing ports
this.addExistingPorts(this.owner, candidates)
return candidates
}
// override this method to return candidates for each existing port
// but mark some as invalid
getTargetPortCandidates(
context: IInputModeContext,
source: IPortCandidate
): IEnumerable<IPortCandidate> {
return this.owner.ports
.map((port) => {
// create a candidate
const pc = new PortCandidate(port)
// mark it as invalid if the source port's user tag
// is not equal to the current port's user tag
pc.validity =
port.tag === source.port!.tag
? PortCandidateValidity.VALID
: PortCandidateValidity.INVALID
return pc
})
.toList()
}
}
The port owner’s lookup must be modified to return an instance of this provider. You can do this using a NodeDecorator (or EdgeDecorator in the case of edge-to-edge connections):
graph.decorator.nodes.portCandidateProvider.addFactory(
(node) => new SameTagPortCandidateProvider(node)
)
By default, a node returns a port candidate provider that returns all unoccupied ports if there is at least one. Otherwise, it creates a new port candidate for the center of the node. yFiles for HTML already provides several IPortCandidateProvider implementations for the most common use cases, mostly available through factory methods on the IPortCandidateProvider interface:
Factory Method | Description |
---|---|
The Port Candidate Provider demo shows how to implement IPortCandidateProvider for different purposes.
Showing Port Candidates while Creating Edges
The CreateEdgeInputMode displays port candidates during edge creation. Its showPortCandidates property determines which port candidates should be displayed.
ShowPortCandidates value | Description |
---|---|
By default, start port candidates are displayed when the cursor hovers over a node (or port owner) that provides some. Similarly, during edge creation, end port candidates are displayed when the cursor hovers over a node (or port owner) that provides some.
graphEditorInputMode.createEdgeInputMode.showPortCandidates =
ShowPortCandidates.ALL



Creating Edge-to-Edge Connections
The CreateEdgeInputMode also supports creating edges that start or end at other edges. Note that not all features of yFiles for HTML fully support edge-to-edge connections. See Edge to Edge Connections for limitations.

To enable creating an edge from another edge, set allowEdgeToEdgeConnections
to true
. Additionally, edges must return a IPortCandidateProvider in their lookup.
graphEditorInputMode.createEdgeInputMode.allowEdgeToEdgeConnections = true
graph.decorator.edges.portCandidateProvider.addFactory((edge) =>
IPortCandidateProvider.fromPortDefaults(edge)
)
Example: Triggering Edge Creation Gesture Programmatically
There are scenarios in which you might want to trigger the edge creation gesture of the CreateEdgeInputMode programmatically. For example, you might want it to start automatically after another action has finished, such as immediately after creating a new node. Or, you might want it to start when the user clicks a button in the context menu or on the toolbar.
The CreateEdgeInputMode provides the startEdgeCreation method to start edge creation programmatically.
If you want to disable gesture-based edge creation, setting beginRecognizer to NEVER is sufficient.
function configureInteraction() {
// register a GraphEditorInputMode with a CreateEdgeInputMode that does
// not allow gesture based edge creation
const inputMode = new GraphEditorInputMode()
inputMode.createEdgeInputMode.beginRecognizer = EventRecognizers.NEVER
graphComponent.inputMode = inputMode
}
async function startEdgeCreation(inputMode, node) {
// starts edge creation on the given node
const edge = await inputMode.createEdgeInputMode.startEdgeCreation(
new PortCandidate(node, FreeNodePortLocationModel.CENTER)
)
if (edge == null) {
// if edge creation has been cancelled the created edge is null
return
}
}
function configureInteraction(): void {
// register a GraphEditorInputMode with a CreateEdgeInputMode that does
// not allow gesture based edge creation
const inputMode = new GraphEditorInputMode()
inputMode.createEdgeInputMode.beginRecognizer = EventRecognizers.NEVER
graphComponent.inputMode = inputMode
}
async function startEdgeCreation(
inputMode: GraphEditorInputMode,
node: INode
): Promise<void> {
// starts edge creation on the given node
const edge = await inputMode.createEdgeInputMode.startEdgeCreation(
new PortCandidate(node, FreeNodePortLocationModel.CENTER)
)
if (edge == null) {
// if edge creation has been cancelled the created edge is null
return
}
}
startEdgeCreation asynchronously returns the created edge
or null
if edge creation has been cancelled.
Example: Starting Edge Creation Inside the Source Node
By default, the CreateEdgeInputMode only starts the edge creation gesture after the mouse pointer leaves the source node. In some cases, it is preferable to start immediately when dragging inside the source node. This is useful, for example, if edges should be created between two overlapping nodes, or for creating self-loops without the mouse having to leave and re-enter the node.
To configure when to start edge creation, the CreateEdgeInputMode provides the property startPortOwnerDraggingFinishedRecognizer.
To immediately start edge creation upon dragging from the source node, assign ALWAYS.
function configureInteraction(graphComponent) {
// register a GraphEditorInputMode with a CreateEdgeInputMode that starts edge creation
// immediately inside the source node
const mode = new GraphEditorInputMode()
graphComponent.inputMode = mode
// support self-loops without bends
mode.createEdgeInputMode.minimumSelfLoopBendCount = 0
// start edge creation immediately
mode.createEdgeInputMode.startPortOwnerDraggingFinishedRecognizer =
EventRecognizers.ALWAYS
}
function configureInteraction(graphComponent: GraphComponent): void {
// register a GraphEditorInputMode with a CreateEdgeInputMode that starts edge creation
// immediately inside the source node
const mode = new GraphEditorInputMode()
graphComponent.inputMode = mode
// support self-loops without bends
mode.createEdgeInputMode.minimumSelfLoopBendCount = 0
// start edge creation immediately
mode.createEdgeInputMode.startPortOwnerDraggingFinishedRecognizer =
EventRecognizers.ALWAYS
}
Example: Configuring CreateEdgeInputMode to Create Subtrees
In this example, we demonstrate how to use various settings to configure CreateEdgeInputMode for a different behavior.
By default, CreateEdgeInputMode can only connect two existing nodes. However, it is possible to let edge creation end anywhere (“prematurely”) and create a new target node at the endpoint.
First, we configure CreateEdgeInputMode not to end at other nodes by setting the endHitTestable to IHitTestable.NEVER. Instead, we support ending at any location (“prematurely”) by setting the prematureEndHitTestable to IHitTestable.ALWAYS.
// never search for target ports
createEdgeInputMode.endHitTestable = IHitTestable.NEVER
// any location is a valid target location
createEdgeInputMode.prematureEndHitTestable = IHitTestable.ALWAYS
Since we don’t want to connect to other nodes, we have to turn off snapping to targets. We also turn off showing possible targets.
// don't react to target ports
createEdgeInputMode.forceSnapToCandidate = false
createEdgeInputMode.snapToEndCandidate = false
// don't even show them
createEdgeInputMode.showPortCandidates = ShowPortCandidates.START
// ending at the same node is no option, either
createEdgeInputMode.allowSelfLoops = false
// don't highlight target nodes
graphComponent.graph.decorator.nodes.highlightRenderer.hide()
Furthermore, we create a new node at the target of the edge. Therefore, we want to show the new node in the preview. Actually, we always have a target node during edge creation. However, it is invisible because it has size (0,0) and the invisible VOID_NODE_STYLE. To show the preview target node, we have to set the defaults of the previewGraph:
// provide a default size
createEdgeInputMode.previewGraph.nodeDefaults.size =
graphComponent.graph.nodeDefaults.size
// set a style to become visible
createEdgeInputMode.previewGraph.nodeDefaults.style =
graphComponent.graph.nodeDefaults.style
Finally, we have to use an edgeCreator that not only creates a new edge but also a target node to connect the edge to:
// let the EdgeCreator create a new target node and connect the new edge to it
createEdgeInputMode.edgeCreator = (
context,
graph,
startPortCandidate,
endPortCandidate,
previewEdge
) => {
// copy the style from the preview node
const previewEndNode = createEdgeInputMode.previewEndNode
const node = graph.createNode(
previewEndNode.layout.toRect(),
previewEndNode.style,
previewEndNode.tag
)
// create a port at the center
const targetPort = graph.addPort(
node,
createEdgeInputMode.previewEndNodePort.locationParameter
)
// create the edge from the source port candidate to the new node
return graph.createEdge(
startPortCandidate.createPort(context),
targetPort,
previewEdge.style
)
}
// let the EdgeCreator create a new target node and connect the new edge to it
createEdgeInputMode.edgeCreator = (
context: any,
graph: IGraph,
startPortCandidate: any,
endPortCandidate: any,
previewEdge: any
) => {
// copy the style from the preview node
const previewEndNode = createEdgeInputMode.previewEndNode
const node = graph.createNode(
previewEndNode.layout.toRect(),
previewEndNode.style,
previewEndNode.tag
)
// create a port at the center
const targetPort = graph.addPort(
node,
createEdgeInputMode.previewEndNodePort.locationParameter
)
// create the edge from the source port candidate to the new node
return graph.createEdge(
startPortCandidate.createPort(context),
targetPort,
previewEdge.style
)
}
That’s it: now the re-configured CreateEdgeInputMode no longer connects two existing nodes. Instead, it creates a new connected node.
To run a layout after each edge creation, you can listen to the edge-created event.
The Tree Layout demo shows how to configure CreateEdgeInputMode to create sub trees.