import {AnnotationsPlugin} from '@xeokit/xeokit-sdk/src/plugins/AnnotationsPlugin/AnnotationsPlugin.js'
AnnotationsPlugin
Extends:
Viewer plugin that creates Annotations.
- [Example 1: Create annotations with mouse]
- [Example 2: Click annotations to toggle labels]
- [Example 3: Hover annotations to show labels]
- [Example 4: Click annotations to fly to viewpoint]
- [Example 5: Create Annotations with externally-created elements]
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.
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:
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 |
surfaceOffset: Number: * 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 |
createAnnotation(params: Object): Annotation Creates an Annotation. |
|
public |
destroy() Destroys this AnnotationsPlugin. |
|
public |
destroyAnnotation(id: String) 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 |
destroy() Destroys this Plugin and removes it from its Viewer. |
|
public |
Logs an error message to the JavaScript developer console, prefixed with the ID of this Plugin. |
|
public |
Fires an event on this Plugin. |
|
public |
Returns true if there are any subscribers to the given event on this Plugin. |
|
public |
Logs a message to the JavaScript developer console, prefixed with the ID of this Plugin. |
|
public |
Cancels an event subscription that was previously made with Plugin#on or Plugin#once. |
|
public |
Subscribes to an event on this Plugin. |
|
public |
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 |
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#constructorParams:
Name | Type | Attribute | Description |
viewer | Viewer | The Viewer. |
|
cfg | Object | Plugin configuration. |
|
cfg.id | String |
|
Optional ID for this plugin, so that we can find it within Viewer#plugins. |
cfg.markerHTML | String |
|
HTML text template for Annotation markers. Defaults to |
cfg.labelHTML | String |
|
HTML text template for Annotation labels. Defaults to |
cfg.container | HTMLElement |
|
Container DOM element for markers and labels. Defaults to |
cfg.values | {String: String|Number} |
|
Map of default values to insert into the HTML templates for the marker and label. |
cfg.surfaceOffset | Number |
|
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.
Public Methods
public createAnnotation(params: Object): Annotation source
Creates an Annotation.
The Annotation is then registered by Annotation#id in AnnotationsPlugin#annotations.
Params:
Name | Type | Attribute | Description |
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 |
|
ID of pre-existing DOM element to render the marker. This overrides |
params.labelElementId | String |
|
ID of pre-existing DOM element to render the label. This overrides |
params.markerHTML | String |
|
HTML text template for the Annotation marker. Defaults to the marker HTML given to the AnnotationsPlugin constructor. Ignored if you provide |
params.labelHTML | String |
|
HTML text template for the Annotation label. Defaults to the label HTML given to the AnnotationsPlugin constructor. Ignored if you provide |
params.worldPos | Number[] |
|
World-space position of the Annotation marker, assigned to Annotation#worldPos. |
params.entity | Entity |
|
Optional Entity to associate the Annotation with. Causes Annotation#visible to be |
params.pickResult | PickResult |
|
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 |
params.occludable | Boolean |
|
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} |
|
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 |
|
Whether to initially show the Annotation marker. |
params.labelShown | Boolean |
|
Whether to initially show the Annotation label. |
params.eye | Number[] |
|
Optional World-space position for Camera#eye, used when this Annotation is associated with a Camera position. |
params.look | Number[] |
|
Optional World-space position for Camera#look, used when this Annotation is associated with a Camera position. |
params.up | Number[] |
|
Optional World-space position for Camera#up, used when this Annotation is associated with a Camera position. |
params.projection | String |
|
Optional projection type for Camera#projection, used when this Annotation is associated with a Camera position. |
public destroy() source
Destroys this AnnotationsPlugin.
Destroys all Annotations first.
Override:
Plugin#destroypublic destroyAnnotation(id: String) source
Destroys an Annotation.
Params:
Name | Type | Attribute | Description |
id | String | ID of Annotation to destroy. |
public getContainerElement(): * | HTMLElement | HTMLElement source
Gets the plugin's HTML container element, if any.
Return:
* | HTMLElement | HTMLElement |