Basic Custom Node Styles
Create custom node visualizations by extending NodeStyleBase and implementing SVG rendering with typed visual caching.
Before Creating
Always query the yFiles MCP for current API:
yfiles:yfiles_get_symbol_details(name="NodeStyleBase")
yfiles:yfiles_get_symbol_details(name="TaggedSvgVisual")
yfiles:yfiles_list_members(name="NodeStyleBase")
yfiles:yfiles_search_documentation(query="custom node style")
Quick Start (Minimal)
The simplest possible style — no caching, no updateVisual. Suitable for prototyping but not recommended for larger graphs.
import { NodeStyleBase, SvgVisual, type IRenderContext, type INode } from '@yfiles/yfiles'
export class CustomNodeStyle extends NodeStyleBase {
protected createVisual(context: IRenderContext, node: INode): SvgVisual {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')
const { x, y, width, height } = node.layout
rect.setAttribute('x', String(x))
rect.setAttribute('y', String(y))
rect.setAttribute('width', String(width))
rect.setAttribute('height', String(height))
rect.setAttribute('fill', '#0b7189')
return new SvgVisual(rect)
}
}
Recommended Pattern (Typed Cache + updateVisual)
For production implementations, declare a Cache type and a named visual type using TaggedSvgVisual, then pass it as the type argument to NodeStyleBase. This enables typed access to visual.tag in updateVisual and avoids recreating DOM elements on every render frame.
import {
NodeStyleBase,
SvgVisual,
TaggedSvgVisual,
type IRenderContext,
type INode
} from '@yfiles/yfiles'
// 1. Declare the cache shape
type Cache = { width: number; height: number }
// 2. Declare the visual type — ties the SVG element type to the cache type
type CustomNodeStyleVisual = TaggedSvgVisual<SVGPathElement, Cache>
// 3. Pass the visual type as the type argument to NodeStyleBase
export class CustomNodeStyle extends NodeStyleBase<CustomNodeStyleVisual> {
protected createVisual(
context: IRenderContext,
node: INode
): CustomNodeStyleVisual {
const { x, y, width, height } = node.layout
const pathElement = document.createElementNS(
'http://www.w3.org/2000/svg',
'path'
)
// Render at (0,0) and position via transform — required for efficient updates
pathElement.setAttribute('d', createPathData(0, 0, width, height))
SvgVisual.setTranslate(pathElement, x, y)
pathElement.setAttribute('fill', '#0b7189')
pathElement.setAttribute('stroke', '#042d37')
// SvgVisual.from() creates a TaggedSvgVisual with the cache stored in .tag
return SvgVisual.from(pathElement, { width, height })
}
protected updateVisual(
context: IRenderContext,
oldVisual: CustomNodeStyleVisual,
node: INode
): CustomNodeStyleVisual {
const { x, y, width, height } = node.layout
const pathElement = oldVisual.svgElement
const cache = oldVisual.tag // fully typed as Cache
// Only rebuild the path when size actually changed
if (width !== cache.width || height !== cache.height) {
pathElement.setAttribute('d', createPathData(0, 0, width, height))
oldVisual.tag = { width, height }
}
// Always update position via transform
SvgVisual.setTranslate(pathElement, x, y)
return oldVisual
}
}
Core Concepts
- createVisual(): Creates the SVG visual (required)
- updateVisual(): Reuses the existing DOM element, updating only what changed (highly recommended for larger graphs)
- TaggedSvgVisual<TElement, TCache>: Typed wrapper that ties an SVG element type to a cache type via
.tag
- SvgVisual.from(element, cache): Convenience factory that creates a
TaggedSvgVisual in one call
- Render at origin + translate: Render the shape at
(0,0) and use SvgVisual.setTranslate() for position — only the transform needs updating when a node moves
Implementation Steps
- Declare a
Cache type with the values needed to detect changes in updateVisual
- Declare a
CustomNodeStyleVisual = TaggedSvgVisual<TElement, Cache> type alias
- Extend
NodeStyleBase<CustomNodeStyleVisual>
- In
createVisual(): render at (0,0), set translate, return SvgVisual.from(element, cache)
- In
updateVisual(): read oldVisual.tag (typed), update only changed attributes, always update translate
Related Skills
To extend this foundation:
/yfiles-nodestyle-configure — Make styles configurable and data-driven
/yfiles-nodestyle-interaction — Add hit testing and edge cropping
/yfiles-nodestyle-advanced — Viewport culling and group nodes
Additional Resources
references/examples.md — Complete examples: rectangle, custom path, group container, framework integration
references/reference.md — NodeStyleBase/SvgVisual API, SVG patterns, performance tips, common pitfalls
1---2name: yfiles-nodestyle-basic3description: This skill should be used when the user asks to "create a custom node style", "implement NodeStyleBase", "create a custom node visualization", "implement createVisual", "implement updateVisual", "render nodes with SVG", or mentions "custom rendering", "TaggedSvgVisual", or "visual caching".4---56# Basic Custom Node Styles78Create custom node visualizations by extending `NodeStyleBase` and implementing SVG rendering with typed visual caching.910## Before Creating1112Always query the yFiles MCP for current API:1314```15yfiles:yfiles_get_symbol_details(name="NodeStyleBase")16yfiles:yfiles_get_symbol_details(name="TaggedSvgVisual")17yfiles:yfiles_list_members(name="NodeStyleBase")18yfiles:yfiles_search_documentation(query="custom node style")19```2021## Quick Start (Minimal)2223The simplest possible style — no caching, no `updateVisual`. Suitable for prototyping but not recommended for larger graphs.2425```typescript26import { NodeStyleBase, SvgVisual, type IRenderContext, type INode } from '@yfiles/yfiles'2728export class CustomNodeStyle extends NodeStyleBase {29 protected createVisual(context: IRenderContext, node: INode): SvgVisual {30 const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect')31 const { x, y, width, height } = node.layout32 rect.setAttribute('x', String(x))33 rect.setAttribute('y', String(y))34 rect.setAttribute('width', String(width))35 rect.setAttribute('height', String(height))36 rect.setAttribute('fill', '#0b7189')37 return new SvgVisual(rect)38 }39}40```4142## Recommended Pattern (Typed Cache + updateVisual)4344For production implementations, declare a `Cache` type and a named visual type using `TaggedSvgVisual`, then pass it as the type argument to `NodeStyleBase`. This enables typed access to `visual.tag` in `updateVisual` and avoids recreating DOM elements on every render frame.4546```typescript47import {48 NodeStyleBase,49 SvgVisual,50 TaggedSvgVisual,51 type IRenderContext,52 type INode53} from '@yfiles/yfiles'5455// 1. Declare the cache shape56type Cache = { width: number; height: number }5758// 2. Declare the visual type — ties the SVG element type to the cache type59type CustomNodeStyleVisual = TaggedSvgVisual<SVGPathElement, Cache>6061// 3. Pass the visual type as the type argument to NodeStyleBase62export class CustomNodeStyle extends NodeStyleBase<CustomNodeStyleVisual> {63 protected createVisual(64 context: IRenderContext,65 node: INode66 ): CustomNodeStyleVisual {67 const { x, y, width, height } = node.layout68 const pathElement = document.createElementNS(69 'http://www.w3.org/2000/svg',70 'path'71 )72 // Render at (0,0) and position via transform — required for efficient updates73 pathElement.setAttribute('d', createPathData(0, 0, width, height))74 SvgVisual.setTranslate(pathElement, x, y)75 pathElement.setAttribute('fill', '#0b7189')76 pathElement.setAttribute('stroke', '#042d37')77 // SvgVisual.from() creates a TaggedSvgVisual with the cache stored in .tag78 return SvgVisual.from(pathElement, { width, height })79 }8081 protected updateVisual(82 context: IRenderContext,83 oldVisual: CustomNodeStyleVisual,84 node: INode85 ): CustomNodeStyleVisual {86 const { x, y, width, height } = node.layout87 const pathElement = oldVisual.svgElement88 const cache = oldVisual.tag // fully typed as Cache8990 // Only rebuild the path when size actually changed91 if (width !== cache.width || height !== cache.height) {92 pathElement.setAttribute('d', createPathData(0, 0, width, height))93 oldVisual.tag = { width, height }94 }9596 // Always update position via transform97 SvgVisual.setTranslate(pathElement, x, y)98 return oldVisual99 }100}101```102103## Core Concepts104105- **createVisual()**: Creates the SVG visual (required)106- **updateVisual()**: Reuses the existing DOM element, updating only what changed (highly recommended for larger graphs)107- **TaggedSvgVisual\<TElement, TCache\>**: Typed wrapper that ties an SVG element type to a cache type via `.tag`108- **SvgVisual.from(element, cache)**: Convenience factory that creates a `TaggedSvgVisual` in one call109- **Render at origin + translate**: Render the shape at `(0,0)` and use `SvgVisual.setTranslate()` for position — only the transform needs updating when a node moves110111## Implementation Steps1121131. Declare a `Cache` type with the values needed to detect changes in `updateVisual`1142. Declare a `CustomNodeStyleVisual = TaggedSvgVisual<TElement, Cache>` type alias1153. Extend `NodeStyleBase<CustomNodeStyleVisual>`1164. In `createVisual()`: render at `(0,0)`, set translate, return `SvgVisual.from(element, cache)`1175. In `updateVisual()`: read `oldVisual.tag` (typed), update only changed attributes, always update translate118119## Related Skills120121To extend this foundation:122- `/yfiles-nodestyle-configure` — Make styles configurable and data-driven123- `/yfiles-nodestyle-interaction` — Add hit testing and edge cropping124- `/yfiles-nodestyle-advanced` — Viewport culling and group nodes125126## Additional Resources127128- **`references/examples.md`** — Complete examples: rectangle, custom path, group container, framework integration129- **`references/reference.md`** — NodeStyleBase/SvgVisual API, SVG patterns, performance tips, common pitfalls