View Decorations

A View Decoration shows application-generated graphics in a ScreenViewport in addition to the persistent (i.e. scene) geometry displayed by the Viewport itself. In contrast to the graphics from the persistent geometry (e.g. the Models), View Decorations must be re-evaluated every time a frame is rendered. In this sense, they decorate the frame with graphics that are only valid for a single frame.

View Decorators

The process of creating View Decorations starts by adding an object that implements the Decorator interface to the ViewManager via the ViewManager.addDecorator method. The most important part of the Decorate interface is the Decorator.decorate method, which is called every time iModel.js renders a frame for any ScreenViewport. The argument to the decorate method is a DecorateContext that supplies information about the ScreenViewport being rendered, as well as methods to create and save decoration graphics. The DecorateContext.viewport member holds the target viewport. If you wish to decorate only a single viewport, you must test this member against your intended viewport.

The job of the decorate method is to supply the graphics (the Decorations) for a single frame of a single ScreenViewport.

A Decorator remains active until you call ViewManager.dropDecorator (Note: ViewManager.addDecorator returns a method that calls this for you if you wish to use it.)

A InteractiveTool can also show decorations and does not need to call the ViewManager.addDecorator method to add itself. InteractiveTool.decorate is called for the active tool to add its decorations, InteractiveTool.decorate is not called when the tool is paused by another tool such as a ViewTool. To show decorations while paused, a tool can implement InteractiveTool.decorateSuspended.

Categories of View Decorations

Sometimes decorations are meant to intersperse with the scene geometry, and sometimes they are meant to display atop of it. For this reason, there are 3 broad categories of View Decorations:

  • View Graphic Decorations - are drawn using iModel.js render primitives into the WebGL context.
  • Canvas Decoration - are drawn onto the 2d canvas using CanvasRenderingContext2D. Canvas decorations are always on top of View Graphic Decorations
  • HTML Decorations - are HTMLElements that are added to the DOM. HTML decorations are always on top of Canvas Decorations.

Note that a single Decorator can create multiple Decorations, from any or all of the categories above.

View Graphic Decorations

View Graphic Decorations are drawn using the iModel.js rendering system through WebGL. There are 5 types of View Graphic Decorations, defined by the GraphicType enum.

Note that GraphicType.ViewOverlay performs the same function as Canvas Decorators and are generally less flexible and less efficient. Prefer Canvas Decorations instead.

You create View Graphic Decorations by calling DecorateContext.createGraphicBuilder on the context supplied to decorate, supplying the appropriate GraphicType. You then add one or more graphics to the GraphicBuilder using its methods. Finally, you add the completed graphics to the frame by calling DecorateContext.addDecorationFromBuilder.

The following example illustrates creating a view graphic decoration to show the IModel.projectExtents in spatial views:

  /** Add a world decoration to display 3d graphics showing the project extents interspersed with the scene graphics. */
  public decorate(context: DecorateContext): void {
    // Check view type, project extents is only applicable to show in spatial views.
    const vp = context.viewport;
    if (!vp.view.isSpatialView())
      return;

    const builder = context.createGraphicBuilder(GraphicType.WorldDecoration, undefined);
    // Set edge color to white or black depending on current view background color and set line weight to 2.
    builder.setSymbology(vp.getContrastToBackgroundColor(), ColorDef.black, 2);
    // Add range box edge geometry to builder.
    builder.addRangeBox(vp.iModel.projectExtents);
    context.addDecorationFromBuilder(builder);
  }

Pickable View Graphic Decorations

View Graphic Decorations are drawn into or atop the scene. To make your View Graphic Decorations pickable (i.e. allow the user to click on them to perform an action, or to give feedback when the cursor hovers over them), you must:

The following example illustrates creating a pickable view graphic decoration in order to supply a tooltip message when under the cursor:

  protected _decoId?: string;

  /** Add a pickable decoration that will display interspersed with the scene graphics. */
  public decorate(context: DecorateContext): void {
    const vp = context.viewport;
    if (!vp.view.isSpatialView())
      return;

    // Get next available Id to represent our decoration for it's life span.
    if (undefined === this._decoId)
      this._decoId = vp.iModel.transientIds.next;

    const builder = context.createGraphicBuilder(GraphicType.WorldDecoration, undefined, this._decoId);
    builder.setSymbology(vp.getContrastToBackgroundColor(), ColorDef.black, 2);
    builder.addRangeBox(vp.iModel.projectExtents);
    context.addDecorationFromBuilder(builder);
  }

  /** Return true if supplied Id represents a pickable decoration created by this decorator. */
  public testDecorationHit(id: string): boolean { return id === this._decoId; }

  /** Return localized tooltip message for the decoration identified by HitDetail.sourceId. */
  public async getDecorationToolTip(_hit: HitDetail): Promise<HTMLElement | string> { return "Project Extents"; }

Canvas Decorations

A CanvasDecoration is drawn atop the scene using CanvasRenderingContext2D. To add a CanvasDecoration, call DecorateContext.addCanvasDecoration from your Decorator.decorate method.

CanvasDecorators must implement CanvasDecoration.drawDecoration to supply visible graphics, by calling methods on CanvasRenderingContext2D.

CanvasDecorators may optionally include the member CanvasDecoration.position, that becomes the 0,0 point for your CanvasRenderingContext2D calls.

The following example illustrates creating a canvas decoration to show a plus symbol at the center of the view:

  /** Add a canvas decoration using CanvasRenderingContext2D to show a plus symbol. */
  public decorate(context: DecorateContext): void {
    const vp = context.viewport;
    const size = Math.floor(vp.pixelsPerInch * 0.25) + 0.5;
    const sizeOutline = size + 1;
    const position = context.viewport.npcToView(NpcCenter); position.x = Math.floor(position.x) + 0.5; position.y = Math.floor(position.y) + 0.5;
    const drawDecoration = (ctx: CanvasRenderingContext2D) => {
      // Show black outline (with shadow) around white line for good visibility regardless of view background color.
      ctx.beginPath();
      ctx.strokeStyle = "rgba(0,0,0,.5)";
      ctx.lineWidth = 3;
      ctx.moveTo(-sizeOutline, 0);
      ctx.lineTo(sizeOutline, 0);
      ctx.moveTo(0, -sizeOutline);
      ctx.lineTo(0, sizeOutline);
      ctx.stroke();

      ctx.beginPath();
      ctx.strokeStyle = "white";
      ctx.lineWidth = 1;
      ctx.shadowColor = "black";
      ctx.shadowBlur = 5;
      ctx.moveTo(-size, 0);
      ctx.lineTo(size, 0);
      ctx.moveTo(0, -size);
      ctx.lineTo(0, size);
      ctx.stroke();
    };
    context.addCanvasDecoration({ position, drawDecoration });
  }

Markers are a type of Canvas Decoration

Pickable Canvas Decorations

To make your CanvasDecorations pickable, implement CanvasDecoration.pick and return true if the supplied point lies within your decoration's region.

If you return true from your CanvasDecoration.pick method, you can implement:

HTML Decorations

HTML Decorations are simply HTMLElements that you add to the DOM on top of your views. In your Decorator.decorate method, use DecorateContext.addHtmlDecoration to add HTML Decorations.

HTML Decorators are appended to an HTMLDivElement called "overlay-decorators" that is created by ScreenViewport.create. All children of that Div are removed every frame, so you must re-add your HTML Decorator each time your Decorator.decorate method is called.

The "overlay-decorators" Div is stacked on top of the canvas, but behind the "overlay-tooltip" Div (where tooltips are displayed.)

Decoration Precedence

The order of precedence for Decorations is:

  1. GraphicType.ViewBackground decorations are drawn behind the scene
  2. GraphicType.Scene and GraphicType.WorldDecoration decorations are drawn in the scene
  3. GraphicType.WorldOverlay and GraphicType.ViewOverlay decorations are drawn on top of the scene
  4. Canvas Decorations are drawn on top of all View Graphic decorations
  5. HTML Decorations are drawn on top of all Canvas decorations
  6. The ToolTip is on top of all HTML decorations

Within a decoration type, the last decoration drawn is on top of earlier decorations.

Last Updated: 23 April, 2020