Snapping
When talking about snapping we have to define the terms snap line and snap result, first. A snap line is a (mostly invisible) line related to a fixed object to which a moved object might snap. A snap result represents a snap line to which a moved object actually snaps at its current position. A moved object can have more than one snap result at the same location, i.e. snap to multiple snap lines at the same time. These snap lines are visualized.
You can set the color of the snap line visualization using the resource keySNAP_LINE_STROKE_KEY:
Alternatively, you can use the CSS classes that are provided by the default snap line visualizations, see Styling of Snap Lines.
Adding Custom Snap Lines
To add custom snap lines for all elements except labels, register a listener to the GraphSnapContext’s CollectSnapLines event. The arguments of that event have a method to add additional snap lines. Note that these snap lines always have to be orthogonal. If you also want to provide snap lines for labels, you should register an appropriate listener to the LabelSnapContext’s CollectSnapLines event. Snap lines for labels can also be non-orthogonal.
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.
/**
* @param {!Point} start
* @param {!Point} end
*/
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')
graphComponent.backgroundGroup.addChild(new SvgVisual(lineElement), ICanvasObjectDescriptor.VISUAL)
}
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')
graphComponent.backgroundGroup.addChild(new SvgVisual(lineElement), ICanvasObjectDescriptor.VISUAL)
}
Each graph item provides a default set of snap lines. A node, for example, provides snap lines at its borders and its center. You can change this behavior by decorating the default snap line provider. The following example shows how to add an additional snap line 10 pixels above the top border of each node.
const nodeDecorator = graphComponent.graph.decorator.nodeDecorator
nodeDecorator.snapLineProviderDecorator.setImplementationWrapper((node, wrapped) =>
ISnapLineProvider.create((context, evt, item) => {
// add default snap lines
if (wrapped !== null) {
wrapped.addSnapLines(context, evt, item)
}
// add a snap lines 10 pixels above the top border of a node
if (item instanceof INode) {
const layout = item.layout.toRect()
// calculate the center of the snap line
const center = new Point(layout.x + layout.width / 2, layout.y - 10)
const snapLine = new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.BOTTOM,
SnapLineVisualizationType.FIXED_LINE,
center,
layout.x - 50,
layout.maxX + 50,
node,
100
)
evt.addAdditionalSnapLine(snapLine)
}
})
)
const nodeDecorator = graphComponent.graph.decorator.nodeDecorator
nodeDecorator.snapLineProviderDecorator.setImplementationWrapper((node, wrapped) =>
ISnapLineProvider.create((context: GraphSnapContext, evt: CollectGraphSnapLinesEventArgs, item: IModelItem) => {
// add default snap lines
if (wrapped !== null) {
wrapped.addSnapLines(context, evt, item)
}
// add a snap lines 10 pixels above the top border of a node
if (item instanceof INode) {
const layout = item.layout.toRect()
// calculate the center of the snap line
const center = new Point(layout.x + layout.width / 2, layout.y - 10)
const snapLine = new OrthogonalSnapLine(
SnapLineOrientation.HORIZONTAL,
SnapLineSnapTypes.BOTTOM,
SnapLineVisualizationType.FIXED_LINE,
center,
layout.x - 50,
layout.maxX + 50,
node,
100
)
evt.addAdditionalSnapLine(snapLine)
}
})
)
GraphSnapContext provides a number of properties which determine for which objects snap lines should be collected.
Property | Description |
---|---|
Adding Custom Snap Results
In the same way you can modify the set of SnapResults collected during the move or edit operation. Grid snapping, for example, provides the center of a node as result to snap to the grid. In the next example we additionally specify the top left corner of the node as snap result.
const nodeDecorator = graphComponent.graph.decorator.nodeDecorator
class CustomNodeSnapResultProvider extends NodeSnapResultProvider {
/**
* @param {!GraphSnapContext} context
* @param {!CollectSnapResultsEventArgs} args
* @param {!Rect} suggestedLayout
* @param {!INode} node
*/
collectGridSnapResults(context, args, suggestedLayout, node) {
// add the default grid snap results (center of the node)
super.collectGridSnapResults(context, args, suggestedLayout, node)
// add the top left corner of the node as grid snap result
this.addGridSnapResult(context, args, suggestedLayout.topLeft, node)
}
}
nodeDecorator.nodeSnapResultProviderDecorator.setImplementation(new CustomNodeSnapResultProvider())
const nodeDecorator = graphComponent.graph.decorator.nodeDecorator
class CustomNodeSnapResultProvider extends NodeSnapResultProvider {
collectGridSnapResults(
context: GraphSnapContext,
args: CollectSnapResultsEventArgs,
suggestedLayout: Rect,
node: INode
): void {
// add the default grid snap results (center of the node)
super.collectGridSnapResults(context, args, suggestedLayout, node)
// add the top left corner of the node as grid snap result
this.addGridSnapResult(context, args, suggestedLayout.topLeft, node)
}
}
nodeDecorator.nodeSnapResultProviderDecorator.setImplementation(new CustomNodeSnapResultProvider())
GraphSnapContext provides a number of properties to determine which moved object should snap to a specific kind of snap lines, i.e. the actual kind of snap results to collect.
Property | Collect snap results for |
---|---|
The source code of the Custom Snapping demo shows how to customize snapping behavior and how to add custom snap lines.