Reference Source
public class | source

AnnotationsPlugin

Extends:

Plugin → AnnotationsPlugin

Viewer plugin that creates Annotations.

Overview

  • An Annotation is a 3D position with a label attached.
  • Annotations render themselves with HTML elements that float over the canvas; customize the appearance of individual Annotations using HTML template; configure default appearance by setting templates on the AnnotationsPlugin.
  • Dynamically insert data values into each Annotation's HTML templates; configure default values on the AnnotationsPlugin.
  • Optionally configure Annotation with externally-created DOM elements for markers and labels; these override templates and data values.
  • Optionally configure Annotations to hide themselves whenever occluded by Entitys.
  • Optionally configure each Annotation with a position we can jump or fly the Camera to.

Example 1: Loading a model and creating an annotation

In the example below, we'll use an XKTLoaderPlugin to load a model, and an AnnotationsPlugin to create an Annotation on it.

We'll configure the AnnotationsPlugin with default HTML templates for each Annotation's position (its "marker") and label, along with some default data values to insert into them.

When we create our Annotation, we'll give it some specific data values to insert into the templates, overriding some of the defaults we configured on the plugin. Note the correspondence between the placeholders in the templates and the keys in the values map.

We'll also configure the Annotation to hide itself whenever it's position is occluded by any Entitys (this is default behavior). The Scene periodically occlusion-tests all Annotations on every 20th "tick" (which represents a rendered frame). We can adjust that frequency via property Scene#ticksPerOcclusionTest.

Finally, we'll query the Annotation's position occlusion/visibility status, and subscribe to change events on those properties.

[Run example]

import {Viewer, XKTLoaderPlugin,AnnotationsPlugin} from "xeokit-sdk.es.js";

const viewer = new Viewer({
    canvasId: "myCanvas",
    transparent: true
});

viewer.scene.camera.eye = [-2.37, 18.97, -26.12];
viewer.scene.camera.look = [10.97, 5.82, -11.22];
viewer.scene.camera.up = [0.36, 0.83, 0.40];

const xktLoader = new XKTLoaderPlugin(viewer);

const annotations = new AnnotationsPlugin(viewer, {

     // Default HTML template for marker position
     markerHTML: "<div class='annotation-marker' style='background-color: {{markerBGColor}};'>{{glyph}}</div>",

     // Default HTML template for label
     labelHTML: "<div class='annotation-label' style='background-color: {{labelBGColor}};'>" +
     "<div class='annotation-title'>{{title}}</div><div class='annotation-desc'>{{description}}</div></div>",

     // Default values to insert into the marker and label templates
     values: {
         markerBGColor: "red",
         labelBGColor: "red",
         glyph: "X",
         title: "Untitled",
         description: "No description"
     }
});

const model = xktLoader.load({
     src: "./models/xkt/duplex/geometry.xkt"
});

model.on("loaded", () => {

     const entity = viewer.scene.meshes[""];

     // Create an annotation
     const myAnnotation1 = annotations.createAnnotation({

         id: "myAnnotation",

         entity: viewer.scene.objects["2O2Fr$t4X7Zf8NOew3FLOH"], // Optional, associate with an Entity

         worldPos: [0, 0, 0],        // 3D World-space position

         occludable: true,           // Optional, default, makes Annotation invisible when occluded by Entities
         markerShown: true,          // Optional, default is true, makes position visible (when not occluded)
         labelShown: true            // Optional, default is false, makes label visible (when not occluded)

         values: {                   // Optional, overrides AnnotationPlugin's defaults
             glyph: "A",
             title: "My Annotation",
             description: "This is my annotation."
         }
     });

     // Listen for change of the Annotation's 3D World-space position

     myAnnotation1.on("worldPos", function(worldPos) {
         //...
     });

     // Listen for change of the Annotation's 3D View-space position, which happens
     // when either worldPos was updated or the Camera was moved

     myAnnotation1.on("viewPos", function(viewPos) {
         //...
     });

     // Listen for change of the Annotation's 2D Canvas-space position, which happens
     // when worldPos or viewPos was updated, or Camera's projection was updated

     myAnnotation1.on("canvasPos", function(canvasPos) {
         //...
     });

     // Listen for change of Annotation visibility. The Annotation becomes invisible when it falls outside the canvas,
     // or its position is occluded by some Entity. Note that, when not occluded, the position is only
     // shown when Annotation#markerShown is true, and the label is only shown when Annotation#labelShown is true.

     myAnnotation1.on("visible", function(visible) { // Marker visibility has changed
         if (visible) {
             this.log("Annotation is visible");
         } else {
             this.log("Annotation is invisible");
         }
     });

     // Listen for destruction of the Annotation

     myAnnotation1.on("destroyed", () => {
         //...
     });
});

Let's query our Annotation's current position in the World, View and Canvas coordinate systems:

const worldPos  = myAnnotation.worldPos;  // [x,y,z]
const viewPos   = myAnnotation.viewPos;   // [x,y,z]
const canvasPos = myAnnotation.canvasPos; // [x,y]

We can query it's current visibility, which is false when its position is occluded by some Entity:

const visible = myAnnotation1.visible;

To listen for change events on our Annotation's position and visibility:

// World-space position changes when we assign a new value to Annotation#worldPos
myAnnotation1.on("worldPos", (worldPos) => {
    //...
});

// View-space position changes when either worldPos was updated or the Camera was moved
myAnnotation1.on("viewPos", (viewPos) => {
    //...
});

// Canvas-space position changes when worldPos or viewPos was updated, or Camera's projection was updated
myAnnotation1.on("canvasPos", (canvasPos) => {
    //...
});

// Annotation is invisible when its position falls off the canvas or is occluded by some Entity
myAnnotation1.on("visible", (visible) => {
    //...
});

Finally, let's dynamically update the values for a couple of placeholders in our Annotation's label:

myAnnotation1.setValues({
     title: "Here's a new title",
     description: "Here's a new description"
});

Example 2: Creating an Annotation with a unique appearance

Now let's create a second Annotation, this time with its own custom HTML label template, which includes an image. In the Annotation's values, we'll also provide a new title and description, custom colors for the marker and label, plus a URL for the image in the label template. To render its marker, the Annotation will fall back on the AnnotationPlugin's default marker template.

const myAnnotation2 = annotations.createAnnotation({

     id: "myAnnotation2",

     worldPos: [-0.163, 1.810, 7.977],

     occludable: true,
     markerShown: true,
     labelShown: true,

     // Custom label template is the same as the Annotation's, with the addition of an image element
     labelHTML: "<div class='annotation-label' style='background-color: {{labelBGColor}};'>\
         <div class='annotation-title'>{{title}}</div>\
         <div class='annotation-desc'>{{description}}</div>\
         <br><img alt='myImage' width='150px' height='100px' src='{{imageSrc}}'>\
         </div>",

     // Custom template values override all the AnnotationPlugin's defaults, and includes an additional value
     // for the image element's URL
     values: {
         glyph: "A3",
         title: "The West wall",
         description: "Annotations can contain<br>custom HTML like this<br>image:",
         markerBGColor: "green",
         labelBGColor: "green",
         imageSrc: "https://xeokit.io/img/docs/BIMServerLoaderPlugin/schependomlaan.png"
     }
});

Example 3: Creating an Annotation with a camera position

We can optionally configure each Annotation with a position to fly or jump the Camera to.

Let's create another Annotation, this time providing it with eye, look and up properties indicating a viewpoint on whatever it's annotating:

const myAnnotation3 = annotations.createAnnotation({

     id: "myAnnotation3",

     worldPos: [-0.163, 3.810, 7.977],

     eye: [0,0,-10],
     look: [-0.163, 3.810, 7.977],
     up: [0,1,0];

     occludable: true,
     markerShown: true,
     labelShown: true,

     labelHTML: "<div class='annotation-label' style='background-color: {{labelBGColor}};'>\
         <div class='annotation-title'>{{title}}</div>\
         <div class='annotation-desc'>{{description}}</div>\
         <br><img alt='myImage' width='150px' height='100px' src='{{imageSrc}}'>\
         </div>",

     values: {
         glyph: "A3",
         title: "The West wall",
         description: "Annotations can contain<br>custom HTML like this<br>image:",
         markerBGColor: "green",
         labelBGColor: "green",
         imageSrc: "https://xeokit.io/img/docs/BIMServerLoaderPlugin/schependomlaan.png"
     }
});

Now we can fly the Camera to the Annotation's viewpoint, like this:

viewer.cameraFlight.flyTo(myAnnotation3);

Or jump the Camera, like this:

viewer.cameraFlight.jumpTo(myAnnotation3);

Example 4: Creating an Annotation using externally-created DOM elements

Now let's create another Annotation, this time providing it with pre-existing DOM elements for its marker and label. Note that AnnotationsPlugin will ignore any markerHTML, labelHTML or values properties when provide markerElementId or labelElementId.

const myAnnotation2 = annotations.createAnnotation({

     id: "myAnnotation2",

     worldPos: [-0.163, 1.810, 7.977],

     occludable: true,
     markerShown: true,
     labelShown: true,

     markerElementId: "myMarkerElement",
     labelElementId: "myLabelElement"
});

Example 5: Creating annotations by clicking on objects

AnnotationsPlugin makes it easy to create Annotations on the surfaces of Entitys as we click on them.

The AnnotationsPlugin#createAnnotation method can accept a PickResult returned by Scene#pick, from which it initializes the Annotation's Annotation#worldPos and Annotation#entity. Note that this only works when Scene#pick was configured to perform a 3D surface-intersection pick (see Scene#pick for more info).

Let's now extend our example to create an Annotation wherever we click on the surface of of our model:

[Run example]

var i = 1; // Used to create unique Annotation IDs

viewer.scene.input.on("mouseclicked", (coords) => {

    var pickRecord = viewer.scene.pick({
        canvasPos: coords,
        pickSurface: true  // <<------ This causes picking to find the intersection point on the entity
    });

    if (pickRecord) {

        const annotation = annotations.createAnnotation({
             id: "myAnnotationOnClick" + i,
             pickRecord: pickRecord,
             occludable: true,           // Optional, default is true
             markerShown: true,          // Optional, default is true
             labelShown: true,           // Optional, default is true
             values: {                   // HTML template values
                 glyph: "A" + i,
                 title: "My annotation " + i,
                 description: "My description " + i
             },
});

        i++;
     }
});

Note that when the Annotation is occludable, there is potential for the Annotation#worldPos to become visually embedded within the surface of its Entity when viewed from a distance. This happens as a result of limited GPU accuracy GPU accuracy, especially when the near and far view-space clipping planes, specified by Perspective#near and Perspective#far, or Ortho#near and Perspective#far, are far away from each other.

To prevent this, we can offset Annotations from their Entity surfaces by an amount that we set on AnnotationsPlugin#surfaceOffset:

annotations.surfaceOffset = 0.3; // Default value

Annotations subsequently created with AnnotationsPlugin#createAnnotation using a PickResult will then be offset by that amount.

Another thing we can do to prevent this unwanted occlusion is keep the distance between the view-space clipping planes to a minimum, which improves the accuracy of the Annotation occlusion test. In general, a good default value for Perspective#far and Ortho#far is around 2.000.

Constructor Summary

Public Constructor
public

constructor(viewer: Viewer, cfg: Object)

Member Summary

Public Members
public

annotations: {String: Annotation}

The Annotations created by AnnotationsPlugin#createAnnotation, each mapped to its Annotation#id.

public set

surfaceOffset(surfaceOffset: Number)

Sets the amount by which each Annotation is offset from the surface of its Entity, when we create the Annotation by supplying a PickResult to AnnotationsPlugin#createAnnotation.

public get

Gets the amount by which an Annotation is offset from the surface of its Entity when created by AnnotationsPlugin#createAnnotation, when we create the Annotation by supplying a PickResult to AnnotationsPlugin#createAnnotation.

Method Summary

Public Methods
public

clear()

Destroys all Annotations.

public

Creates an Annotation.

public

Destroys this AnnotationsPlugin.

public

Destroys an Annotation.

public

getContainerElement(): * | HTMLElement | HTMLElement

Gets the plugin's HTML container element, if any.

Inherited Summary

From class Plugin
public

ID for this Plugin, unique within its Viewer.

public

The Viewer that contains this Plugin.

public

Destroys this Plugin and removes it from its Viewer.

public

error(msg: String)

Logs an error message to the JavaScript developer console, prefixed with the ID of this Plugin.

public

fire(event: String, value: Object, forget: Boolean)

Fires an event on this Plugin.

public

Returns true if there are any subscribers to the given event on this Plugin.

public

log(msg: String)

Logs a message to the JavaScript developer console, prefixed with the ID of this Plugin.

public

off(subId: String)

Cancels an event subscription that was previously made with Plugin#on or Plugin#once.

public

on(event: String, callback: Function, scope: Object): String

Subscribes to an event on this Plugin.

public

once(event: String, callback: Function, scope: Object)

Subscribes to the next occurrence of the given event, then un-subscribes as soon as the event is subIdd.

public

scheduleTask(task: *)

Schedule a task to perform on the next browser interval

public

warn(msg: String)

Logs a warning message to the JavaScript developer console, prefixed with the ID of this Plugin.

Public Constructors

public constructor(viewer: Viewer, cfg: Object) source

Creates this Plugin and installs it into the given Viewer.

Override:

Plugin#constructor

Params:

NameTypeAttributeDescription
viewer Viewer

The Viewer.

cfg Object

Plugin configuration.

cfg.id String
  • optional
  • default: "Annotations"

Optional ID for this plugin, so that we can find it within Viewer#plugins.

cfg.markerHTML String
  • optional

HTML text template for Annotation markers. Defaults to <div></div>. Ignored on Annotations configured with a markerElementId.

cfg.labelHTML String
  • optional

HTML text template for Annotation labels. Defaults to <div></div>. Ignored on Annotations configured with a labelElementId.

cfg.container HTMLElement
  • optional

Container DOM element for markers and labels. Defaults to document.body.

cfg.values {String: String|Number}
  • optional
  • default: {}

Map of default values to insert into the HTML templates for the marker and label.

cfg.surfaceOffset Number
  • optional
  • default: 0.3

The amount by which each Annotation is offset from the surface of its Entity when we create the Annotation by supplying a PickResult to AnnotationsPlugin#createAnnotation.

Public Members

public annotations: {String: Annotation} source

The Annotations created by AnnotationsPlugin#createAnnotation, each mapped to its Annotation#id.

public set surfaceOffset(surfaceOffset: Number) source

Sets the amount by which each Annotation is offset from the surface of its Entity, when we create the Annotation by supplying a PickResult to AnnotationsPlugin#createAnnotation.

See the class comments for more info.

This is 0.3 by default.

public get surfaceOffset: Number: * source

Gets the amount by which an Annotation is offset from the surface of its Entity when created by AnnotationsPlugin#createAnnotation, when we create the Annotation by supplying a PickResult to AnnotationsPlugin#createAnnotation.

This is 0.3 by default.

Return:

Number

The surface offset.

Public Methods

public clear() source

Destroys all Annotations.

public createAnnotation(params: Object): Annotation source

Creates an Annotation.

The Annotation is then registered by Annotation#id in AnnotationsPlugin#annotations.

Params:

NameTypeAttributeDescription
params Object

Annotation configuration.

params.id String

Unique ID to assign to Annotation#id. The Annotation will be registered by this in AnnotationsPlugin#annotations and Scene.components. Must be unique among all components in the Viewer.

params.markerElementId String
  • optional

ID of pre-existing DOM element to render the marker. This overrides markerHTML and does not support values (data is baked into the label DOM element).

params.labelElementId String
  • optional

ID of pre-existing DOM element to render the label. This overrides labelHTML and does not support values (data is baked into the label DOM element).

params.markerHTML String
  • optional

HTML text template for the Annotation marker. Defaults to the marker HTML given to the AnnotationsPlugin constructor. Ignored if you provide markerElementId.

params.labelHTML String
  • optional

HTML text template for the Annotation label. Defaults to the label HTML given to the AnnotationsPlugin constructor. Ignored if you provide labelElementId.

params.worldPos Number[]
  • optional
  • default: [0,0,0]

World-space position of the Annotation marker, assigned to Annotation#worldPos.

params.entity Entity
  • optional

Optional Entity to associate the Annotation with. Causes Annotation#visible to be false whenever Entity#visible is also false.

params.pickResult PickResult
  • optional

Sets the Annotation's World-space position and direction vector from the given PickResult's PickResult#worldPos and PickResult#worldNormal, and the Annotation's Entity from PickResult#entity. Causes worldPos and entity parameters to be ignored, if they are also given.

params.occludable Boolean
  • optional
  • default: false

Indicates whether or not the Annotation marker and label are hidden whenever the marker occluded by Entitys in the Scene. The Scene periodically occlusion-tests all Annotations on every 20th "tick" (which represents a rendered frame). We can adjust that frequency via property Scene#ticksPerOcclusionTest.

params.values {String: String|Number}
  • optional
  • default: {}

Map of values to insert into the HTML templates for the marker and label. These will be inserted in addition to any values given to the AnnotationsPlugin constructor.

params.markerShown Boolean
  • optional
  • default: true

Whether to initially show the Annotation marker.

params.labelShown Boolean
  • optional
  • default: false

Whether to initially show the Annotation label.

params.eye Number[]
  • optional

Optional World-space position for Camera#eye, used when this Annotation is associated with a Camera position.

params.look Number[]
  • optional

Optional World-space position for Camera#look, used when this Annotation is associated with a Camera position.

params.up Number[]
  • optional

Optional World-space position for Camera#up, used when this Annotation is associated with a Camera position.

params.projection String
  • optional

Optional projection type for Camera#projection, used when this Annotation is associated with a Camera position.

Return:

Annotation

The new Annotation.

public destroy() source

Destroys this AnnotationsPlugin.

Destroys all Annotations first.

Override:

Plugin#destroy

public destroyAnnotation(id: String) source

Destroys an Annotation.

Params:

NameTypeAttributeDescription
id String

ID of Annotation to destroy.

public getContainerElement(): * | HTMLElement | HTMLElement source

Gets the plugin's HTML container element, if any.

Return:

* | HTMLElement | HTMLElement