documentationfor yFiles for HTML 2.6

NodeStyleBase<TVisualextends Visual>

An abstract base class that makes it possible to easily implement a custom INodeStyle.

Inheritance Hierarchy
NodeStyleBase
Implemented Interfaces

Remarks

The only method that needs to be implemented by subclasses is createVisual, however to improve rendering performance it is highly recommended to implement at least updateVisual, too.

This implementation differs from the straightforward INodeStyle implementation in that there is no visible separation between the style and its INodeStyleRenderer. Instead the renderer used by the base class is fixed and delegates all calls back to the style instance.

Examples

A very simple subclass of NodeStyleBase<TVisual> only needs a createVisual implementation:

/// <summary>
/// A custom node style that displays a node always as an exact circle inscribed in the node's
/// layout rectangle.
/// </summary>

class CircleNodeStyle extends NodeStyleBase {
  // The only method we actually *have* to implement

  /**
   * @param {!IRenderContext} context
   * @param {!INode} node
   * @returns {?Visual}
   */
  createVisual(context, node) {
    const size = Math.min(node.layout.width, node.layout.height)

    // we create a circular Element
    const circle = document.createElementNS(
      'http://www.w3.org/2000/svg',
      'ellipse'
    )
    circle.setAttribute('rx', (size / 2).toString())
    circle.setAttribute('ry', (size / 2).toString())
    circle.setAttribute('stroke', 'black')
    circle.setAttribute('fill', 'red')

    // We now need to tell yFiles where to place the circle. This is done with
    SvgVisual.setTranslate(circle, node.layout.x, node.layout.y)
    return new SvgVisual(circle)
  }
}/// <summary>
/// A custom node style that displays a node always as an exact circle inscribed in the node's
/// layout rectangle.
/// </summary>

class CircleNodeStyle extends NodeStyleBase {
  // The only method we actually *have* to implement

  createVisual(context: IRenderContext, node: INode): Visual | null {
    const size = Math.min(node.layout.width, node.layout.height)

    // we create a circular Element
    const circle = document.createElementNS(
      'http://www.w3.org/2000/svg',
      'ellipse'
    )
    circle.setAttribute('rx', (size / 2).toString())
    circle.setAttribute('ry', (size / 2).toString())
    circle.setAttribute('stroke', 'black')
    circle.setAttribute('fill', 'red')

    // We now need to tell yFiles where to place the circle. This is done with
    SvgVisual.setTranslate(circle, node.layout.x, node.layout.y)
    return new SvgVisual(circle)
  }
}

This style would show nodes as follows (the light gray rectangle shows the actual node layout in which the circle is centered): From here, more features can easily be added as needed. Once the application grows beyond the prototype stage, it's usually a good idea to implement updateVisual as well:

/**
 * @param {!IRenderContext} context
 * @param {!Visual} oldVisual
 * @param {!INode} node
 * @returns {?Visual}
 */
updateVisual(context, oldVisual, node) {
  // Our style does not have any options that can change, so the only thing left
  // to do here is to update location and size via SetCanvasArrangeRect
  const size = Math.min(node.layout.width, node.layout.height)

  const element = oldVisual.svgElement

  element.setAttribute('rx', (size / 2).toString())
  element.setAttribute('ry', (size / 2).toString())
  SvgVisual.setTranslate(element, node.layout.x, node.layout.y)

  return oldVisual
}updateVisual(
  context: IRenderContext,
  oldVisual: Visual,
  node: INode
): Visual | null {
  // Our style does not have any options that can change, so the only thing left
  // to do here is to update location and size via SetCanvasArrangeRect
  const size = Math.min(node.layout.width, node.layout.height)

  const element = (oldVisual as SvgVisual).svgElement

  element.setAttribute('rx', (size / 2).toString())
  element.setAttribute('ry', (size / 2).toString())
  SvgVisual.setTranslate(element, node.layout.x, node.layout.y)

  return oldVisual
}

Another common customization, especially when the node style doesn't display a rectangular shape, is to change how hit-testing works on the node. This affects where clicking would select the node. In our example with a circle that is often quite a bit smaller than the node's layout rectangle, it's especially noticeable that clicking anywhere within that rectangle would select the node, and not just on the circle: But we can change that:

/**
 * @param {!IInputModeContext} context
 * @param {!Point} location
 * @param {!INode} node
 * @returns {boolean}
 */
isHit(context, location, node) {
  const size = Math.min(node.layout.width, node.layout.height)
  const circleBounds = new Rect(node.layout.center, new Size(size, size))
  // Also observe the hit-test radius from the context so that clicking just outside
  // the bounds of the circle will still register as a hit.
  // This is less important with node styles, but for edges that are usually just
  // displayed as a line, it's very hard to hit that line otherwise.
  return GeomUtilities.ellipseContains(
    circleBounds,
    location,
    context.hitTestRadius
  )

  // In our case, since we know our visualization is always a circle, we also could have done the math
  // ourselves:

  // The point only hits the circle when the distance to the circle's center is less than
  // its radius (plus the hit-test radius again):

  // return node.layout.center.distanceTo(location) <= size / 2 + context.hitTestRadius;
}isHit(context: IInputModeContext, location: Point, node: INode): boolean {
  const size = Math.min(node.layout.width, node.layout.height)
  const circleBounds = new Rect(node.layout.center, new Size(size, size))
  // Also observe the hit-test radius from the context so that clicking just outside
  // the bounds of the circle will still register as a hit.
  // This is less important with node styles, but for edges that are usually just
  // displayed as a line, it's very hard to hit that line otherwise.
  return GeomUtilities.ellipseContains(
    circleBounds,
    location,
    context.hitTestRadius
  )

  // In our case, since we know our visualization is always a circle, we also could have done the math
  // ourselves:

  // The point only hits the circle when the distance to the circle's center is less than
  // its radius (plus the hit-test radius again):

  // return node.layout.center.distanceTo(location) <= size / 2 + context.hitTestRadius;
}

Now only points that lie within the circle would successfully hit the node:

Related Programming Samples

Custom Styles
Shows how to create custom styles for nodes, edges, labels, ports, and edge arrows.
01 Create A Rectangle
Create a simple node style using SVG
03 Render Performance
Optimize rendering performance of an SVG node style
07 Hit-Testing
Customize which area of a node can be hovered and clicked

Type Parameters

TVisual: Visual
The optional type of the created and updated by the and methods. This type argument can be omit, but specifying a more concrete type helps conveniently implementing with TypeScript.

Type Details

yfiles module
view-component
yfiles-umd modules
All view modules
Legacy UMD name
yfiles.styles.NodeStyleBase

See Also

Constructors

Properties

Methods