Snapping
When discussing snapping, it’s important to first define the terms snap reference and snap result.
A snap reference is a guide related to a fixed object that a moved object might snap to. Prominent examples of snap references are snap line and snap grid.
A snap result represents the position where the current position of a moved object actually snaps to a snap reference. A moved object can have multiple snap results at the same location, meaning it can snap to multiple snap references simultaneously. These snap references are then visualized.
You can set the color of the snap result visualization using the CSS classes that are provided by the default snap reference visualizations, see Styling Snap References.
Adding Custom Snap References
To add custom snap references for all elements, register a listener to the GraphSnapContext’s collect-snap-references event. The arguments of that event has a method to add additional snap references.
function addSnapLine(graphSnapContext, start, end) {
const snapLine = createSnapLine(start, end)
if (snapLine !== null) {
// add a snap line for all graph elements
graphSnapContext.addEventListener('collect-snap-references', (evt) =>
evt.addSnapReference(snapLine)
)
}
}
function createSnapLine(start, end) {
// calculate the center of the snap line
const center = start.add(end).multiply(0.5)
// create a vertical snap line
if (start.x === end.x) {
return new OrthogonalSnapLine(
SnapLineOrientation.VERTICAL,
SnapLineSnapTypes.CENTER,
SnapReferenceVisualizationType.FIXED_LINE,
center,
start.y,
end.y,
false,
50
)
}
// create a horizontal snap line
if (start.y === end.y) {
return new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.CENTER,
SnapReferenceVisualizationType.FIXED_LINE,
center,
start.x,
end.x,
false,
50
)
}
// we do not add non-orthogonal snap lines here
return null
}
function addSnapLine(
graphSnapContext: GraphSnapContext,
start: Point,
end: Point
): void {
const snapLine = createSnapLine(start, end)
if (snapLine !== null) {
// add a snap line for all graph elements
graphSnapContext.addEventListener('collect-snap-references', (evt) =>
evt.addSnapReference(snapLine)
)
}
}
function createSnapLine(start: Point, end: Point): OrthogonalSnapLine | null {
// calculate the center of the snap line
const center = start.add(end).multiply(0.5)
// create a vertical snap line
if (start.x === end.x) {
return new OrthogonalSnapLine(
SnapLineOrientation.VERTICAL,
SnapLineSnapTypes.CENTER,
SnapReferenceVisualizationType.FIXED_LINE,
center,
start.y,
end.y,
false,
50
)
}
// create a horizontal snap line
if (start.y === end.y) {
return new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.CENTER,
SnapReferenceVisualizationType.FIXED_LINE,
center,
start.x,
end.x,
false,
50
)
}
// we do not add non-orthogonal snap lines here
return null
}


Note that the snap line appears only if you move a graph element close to it. The following snippet illustrates how to always display the snap line.
function paintSnapLine(start, end) {
const lineElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'line'
)
lineElement.setAttribute('x1', String(start.x))
lineElement.setAttribute('y1', String(start.y))
lineElement.setAttribute('x2', String(end.x))
lineElement.setAttribute('y2', String(end.y))
lineElement.setAttribute('stroke', 'black')
const renderTree = graphComponent.renderTree
renderTree.createElement(
renderTree.backgroundGroup,
new SvgVisual(lineElement)
)
}
function paintSnapLine(start: Point, end: Point): void {
const lineElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'line'
)
lineElement.setAttribute('x1', String(start.x))
lineElement.setAttribute('y1', String(start.y))
lineElement.setAttribute('x2', String(end.x))
lineElement.setAttribute('y2', String(end.y))
lineElement.setAttribute('stroke', 'black')
const renderTree = graphComponent.renderTree
renderTree.createElement(
renderTree.backgroundGroup,
new SvgVisual(lineElement)
)
}


Each graph item provides a default set of snap references. A node, for example, provides snap lines at its borders and its center. You can change this behavior by decorating the default snap reference provider. The following example shows how to add a snap line 10 pixels above the top border of each node.
const nodeDecorator = graphComponent.graph.decorator.nodes
nodeDecorator.snapReferenceProvider.addWrapperFactory((node, wrapped) =>
ISnapReferenceProvider.create((context, evt) => {
// add default snap references
if (wrapped !== null) {
wrapped.addSnapReferences(context, evt)
}
// add a snap lines 10 pixels above the top border of a node
const layout = node.layout.toRect()
const center = new Point(layout.centerX, layout.y - 10)
const snapLine = new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.BOTTOM,
SnapReferenceVisualizationType.FIXED_LINE,
center,
layout.x - 50,
layout.maxX + 50,
false,
100
)
evt.addSnapReference(snapLine)
})
)
const nodeDecorator = graphComponent.graph.decorator.nodes
nodeDecorator.snapReferenceProvider.addWrapperFactory((node, wrapped) =>
ISnapReferenceProvider.create(
(context: GraphSnapContext, evt: CollectSnapReferencesEventArgs) => {
// add default snap references
if (wrapped !== null) {
wrapped.addSnapReferences(context, evt)
}
// add a snap lines 10 pixels above the top border of a node
const layout = node.layout.toRect()
const center = new Point(layout.centerX, layout.y - 10)
const snapLine = new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.BOTTOM,
SnapReferenceVisualizationType.FIXED_LINE,
center,
layout.x - 50,
layout.maxX + 50,
false,
100
)
evt.addSnapReference(snapLine)
}
)
)


GraphSnapContext provides a number of properties which determine for which objects snap references should be collected.
Property | Default Value | Description |
---|---|---|
true | ||
true | ||
true | ||
true | ||
true | ||
false | ||
true | ||
0 | ||
-1 | ||
0 | ||
true | ||
40 |
Some of the settings are tailored to be used with a moved label:
Property | Default Value | Description |
---|---|---|
true | ||
true | ||
true | ||
true | ||
false | ||
true | ||
true | ||
true | ||
false | ||
true |
In addition, the following settings determine which and how items are snapped:
Property | Default Value | Description |
---|---|---|
Adding Custom Snap Results
Similar to adding custom snap references, you can modify the set of SnapResults collected during a move or edit operation. For example, grid snapping provides the center of a node as a result to snap to the grid. The following example shows how to also specify the top left corner of the node as a snap result.
class CustomNodeSnapResultProvider extends NodeSnapResultProvider {
collectGridSnapResults(context, evt, snapGrid, suggestedLayout, node) {
// add the default grid snap results (center of the node)
super.collectGridSnapResults(
context,
evt,
snapGrid,
suggestedLayout,
node
)
// add the top left corner of the node as grid snap result
this.collectGridSnapResult(
context,
evt,
snapGrid,
suggestedLayout.topLeft,
node
)
}
}
const nodeDecorator = graphComponent.graph.decorator.nodes
nodeDecorator.snapResultProvider.addConstant(
new CustomNodeSnapResultProvider()
)
class CustomNodeSnapResultProvider extends NodeSnapResultProvider {
protected collectGridSnapResults(
context: GraphSnapContext,
evt: CollectSnapResultsEventArgs,
snapGrid: SnapGrid,
suggestedLayout: Rect,
node: INode
) {
// add the default grid snap results (center of the node)
super.collectGridSnapResults(
context,
evt,
snapGrid,
suggestedLayout,
node
)
// add the top left corner of the node as grid snap result
this.collectGridSnapResult(
context,
evt,
snapGrid,
suggestedLayout.topLeft,
node
)
}
}
const nodeDecorator = graphComponent.graph.decorator.nodes
nodeDecorator.snapResultProvider.addConstant(
new CustomNodeSnapResultProvider()
)


The Custom Snapping source code demo shows how to customize snapping behavior and how to add custom snap references.