const bridgeManager = new BridgeManager()
bridgeManager.canvasComponent = graphComponent
bridgeManager.addObstacleProvider(new GraphObstacleProvider())
When there are a lot of crossing edges, especially when they are orthogonal, it can be hard to determine where an edge actually leads.
Bridges, or line jumps, are a means to resolve the visual ambiguity induced by intersecting edge paths. Each segment of an edge path that intersects with at least one other segment (from either the same or another edge path) can be augmented with a bridge in one of a variety of different styles like gap, arc, and rectangle.
Bridge rendering is handled by the BridgeManager class. It can be enabled by assigning the GraphComponent to BridgeManager.canvasComponent property. To determine obstacles, the BridgeManager instance uses an implementation of the IObstacleProvider interface. We use the GraphObstacleProvider that by default incorporates the obstacle definitions returned by all edges from the current graph.
const bridgeManager = new BridgeManager()
bridgeManager.canvasComponent = graphComponent
bridgeManager.addObstacleProvider(new GraphObstacleProvider())
The BridgeManager uses the lookup mechanism to ask about the obstacles for each edge. The request is passed to the EdgeStyle’s lookup method. Our lookup method provides an IObstacleProvider implementation that returns the path of the edge. Now the BridgeManager can determinate where bridges have to be created.
protected lookup(edge: IEdge, type: Constructor<any>): any {
if (type === IObstacleProvider) {
const getPath = this.getPath.bind(this)
return new (class extends BaseClass(IObstacleProvider) {
getObstacles(context: IRenderContext): GeneralPath | null {
return getPath(edge)
}
})()
}
return super.lookup(edge, type)
}
So far we have used the cropped path for the edge visualization. Now we need to add bridges to this path where obstacles are present. Fortunately, BridgeManager can take care of this.
private createPathWithBridges(
path: GeneralPath,
context: IRenderContext
): GeneralPath {
const manager = context.lookup(BridgeManager)
return manager ? manager.addBridges(context, path) : path
}
The BridgeManager.getObstacleHash provides a hash code that describes the current obstacle locations.
private getObstacleHash(context: IRenderContext): number {
const manager = context.lookup(BridgeManager)
return manager ? manager.getObstacleHash(context) : 42
}
We cache that hash code in the createVisual method and use it in the updateVisual method to check for changes, and if so we update the data of the paths.
const newGeneralPath = super.getPath(edge)!
const newObstacleHash = this.getObstacleHash(context)
if (
!newGeneralPath.hasSameValue(cache.generalPath) ||
newObstacleHash !== cache.obstacleHash
) {
const croppedGeneralPath = super.cropPath(
edge,
IArrow.NONE,
IArrow.NONE,
newGeneralPath
)!
const pathWithBridges = this.createPathWithBridges(
croppedGeneralPath,
context
)
const pathData = pathWithBridges.createSvgPathData()
widePath.setAttribute('d', pathData)
thinPath.setAttribute('d', pathData)
cache.generalPath = newGeneralPath
cache.obstacleHash = newObstacleHash
}