// the values we use to render the graphics
type Cache = { width: number; height: number; text: string }
// the type of visual we create and update in CustomLabelStyle
type CustomLabelStyleVisual = TaggedSvgVisual<SVGGElement, Cache>
Until now, we have only implemented createVisual, which creates a new DOM element for each render frame. This is not an efficient approach and will result in performance issues for large graphs.
By overriding updateVisual, we can optimize the rendering performance in case no visualization-relevant data of the node has changed. If the label text or size changes, we have to update the text and the background path data. This approach will greatly improve the rendering performance for gestures such as panning and zooming the viewport as well as moving nodes.
To be able to update the visualization in updateVisual, we have to store the values with which the initial visualization was created. In this case, this is the label text and size. To get proper type-checking, we first declare the type of the data cache. This is where the yFiles' utility type TaggedSvgVisual comes in handy:
// the values we use to render the graphics
type Cache = { width: number; height: number; text: string }
// the type of visual we create and update in CustomLabelStyle
type CustomLabelStyleVisual = TaggedSvgVisual<SVGGElement, Cache>
With this type declaration, we can augment the class declaration for our label style. LabelStyleBase comes with an optional type argument which specifies the exact type for the visual returned by createVisual. This type argument ensures that updateVisual expects the type of visual that is created in createVisual. Although this is not strictly necessary, it helps with the TypeScript implementation:
export class CustomLabelStyle extends LabelStyleBase<CustomLabelStyleVisual> {
To properly implement the interface and store the cache value with the visual, we adjust the createVisual method, first.
protected createVisual(
context: IRenderContext,
label: ILabel
): CustomLabelStyleVisual {
// create an SVG text element that displays the label text
const textElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'text'
)
const labelSize = label.layout.toSize()
TextRenderSupport.addText(textElement, label.text, font)
textElement.setAttribute('transform', `translate(${padding} ${padding})`)
// add a background shape
const backgroundPathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
)
backgroundPathElement.setAttribute(
'd',
this.createBackgroundShapeData(labelSize)
)
backgroundPathElement.setAttribute('stroke', '#aaa')
backgroundPathElement.setAttribute('fill', '#fffecd')
const gElement = document.createElementNS('http://www.w3.org/2000/svg', 'g')
gElement.appendChild(backgroundPathElement)
gElement.appendChild(textElement)
// move text to label location
const transform = LabelStyleBase.createLayoutTransform(
context,
label.layout,
true
)
transform.applyTo(gElement)
const cache = {
width: labelSize.width,
height: labelSize.height,
text: label.text
}
return SvgVisual.from(gElement, cache)
Now, we are ready to add the updateVisual implementation. Thanks to type parameter, we can let our IDE create the matching signature for the updateVisual method. In case the label text or size has changed, we update the text content and the background path data and the cache data. Finally, we update the label transform to move the label to the correct location.
protected updateVisual(
context: IRenderContext,
oldVisual: CustomLabelStyleVisual,
label: ILabel
): CustomLabelStyleVisual {
const gElement = oldVisual.svgElement
const labelSize = label.layout.toSize()
// get the cache object we stored in createVisual
const cache = oldVisual.tag
// check if the label size or text has changed
if (
labelSize.width !== cache.width ||
labelSize.height !== cache.height ||
label.text !== cache.text
) {
// get the path and text element
const backgroundPath = gElement.children.item(0)
const textElement = gElement.children.item(1)
if (backgroundPath) {
backgroundPath.setAttribute(
'd',
this.createBackgroundShapeData(labelSize)
)
}
if (textElement instanceof SVGTextElement) {
TextRenderSupport.addText(textElement, label.text, font)
}
// update the cache with the new values
cache.width = labelSize.width
cache.height = labelSize.height
cache.text = label.text
}
// move text to label location
const transform = LabelStyleBase.createLayoutTransform(
context,
label.layout,
true
)
transform.applyTo(gElement)
return oldVisual
}
This code re-uses the initial SVG elements and only updates the necessary attributes.
When the style gets more complex, there may be a point where some updates are difficult to implement or are not worth the effort. It is perfectly valid to give up at some point and call createVisual again if there are too many changes or the update code gets too complex.
|
Note
|
Although implementing updateVisual is technically optional, it is highly recommended for larger graphs. Refraining from an efficient implementation may result in low frame-rates during animations and interactive gestures. |