import {TreeViewPlugin} from '@xeokit/xeokit-sdk/src/plugins/TreeViewPlugin/TreeViewPlugin.js'
TreeViewPlugin
Extends:
A Viewer plugin that provides an HTML tree view to navigate the IFC elements in models.
Overview
- A fast HTML tree view, with zero external dependencies, that works with huge numbers of objects.
- Each tree node has a checkbox to control the visibility of its object.
- Has three hierarchy modes: "containment", "types" and "storeys".
- Automatically contains all models (that have metadata) that are currently in the Scene.
- Sorts tree nodes by default - spatially, from top-to-bottom for
IfcBuildingStorey
nodes, and alphanumerically for other nodes. - Allows custom CSS styling.
- Use ContextMenu to create a context menu for the tree nodes.
Credits
TreeViewPlugin is based on techniques described in Super Fast Tree View in JavaScript by Chris Smith.
Usage
In the example below, we'll add a TreeViewPlugin which, by default, will automatically show the structural hierarchy of the IFC elements in each model we load.
Then we'll use an XKTLoaderPlugin to load the Schependomlaan model from an .xkt file.
import {Viewer, XKTLoaderPlugin, TreeViewPlugin} from "xeokit-sdk.es.js";
const viewer = new Viewer({
canvasId: "myCanvas",
transparent: true
});
viewer.camera.eye = [-2.56, 8.38, 8.27];
viewer.camera.look = [13.44, 3.31, -14.83];
viewer.camera.up = [0.10, 0.98, -0.14];
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer")
});
const xktLoader = new XKTLoaderPlugin(viewer);
const model = xktLoader.load({
id: "myModel",
src: "./models/xkt/Schependomlaan.xkt",
edges: true
});
Manually Adding Models
Instead of adding models automatically, we can control which models appear in our TreeViewPlugin by adding them manually.
In the next example, we'll configure the TreeViewPlugin to not add models automatically. Then, once the model has loaded, we'll add it manually using TreeViewPlugin#addModel.
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer"),
autoAddModels: false // <<---------------- Don't auto-add models
});
const xktLoader = new XKTLoaderPlugin(viewer);
const model = xktLoader.load({
id: "myModel",
src: "./models/xkt/Schependomlaan.xkt",
edges: true
});
model.on("loaded", () => {
treeView.addModel(model.id);
});
Adding models manually also allows us to set some options for the model. For example, the rootName
option allows us to provide a custom name for
the root node, which is sometimes desirable when the model's "IfcProject" element's name is not suitable:
model.on("loaded", () => {
treeView.addModel(model.id, {
rootName: "Schependomlaan Model"
});
});
Initially Expanding the Hierarchy
We can also configure TreeViewPlugin to initially expand each model's nodes to a given depth.
Let's automatically expand the first three nodes from the root, for every model added:
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer"),
autoExpandDepth: 3
});
Showing a Node by ID
We can show a given node using its ID. This causes the TreeViewPlugin to collapse, then expand and scroll the node into view, then highlight the node.
See the documentation for the TreeViewPlugin#showNode method for more information, including how to define a custom highlighted appearance for the node using CSS.
Let's make the TreeViewPlugin show the node corresponding to whatever object Entity that we pick:
viewer.cameraControl.on("picked", function (e) {
var objectId = e.entity.id;
treeView.showNode(objectId);
});
This will de-highlight any node that was previously shown by this method.
Note that this method only works if the picked Entity is an object that belongs to a model that's represented in the TreeViewPlugin.
Customizing Appearance
We can customize the appearance of our TreeViewPlugin by defining custom CSS for its HTML elements. See our example's source code for an example of custom CSS rules.
Model Hierarchies
TreeViewPlugin has three hierarchies for organizing its nodes:
- "containment" - organizes the tree nodes to indicate the containment hierarchy of the MetaObjects.
- "types" - groups nodes by their IFC types.
- "storeys" - groups nodes within their
IfcBuildingStoreys
, and sub-groups them by their IFC types.
The table below shows what the hierarchies look like:
1. Containment Hierarchy | 2. Types Hierarchy | 3. Storeys Hierarchy |
---|---|---|
Let's create a TreeViewPlugin that groups nodes by their building stories and IFC types:
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer"),
hierarchy: "stories"
});
Sorting Nodes
TreeViewPlugin sorts its tree nodes by default. For a "storeys" hierarchy, it orders IfcBuildingStorey
nodes
spatially, with the node for the highest story at the top, down to the lowest at the bottom.
For all the hierarchy types ("containment", "classes" and "storeys"), TreeViewPlugin sorts the other node types alphanumerically on their titles.
If for some reason you need to prevent sorting, create your TreeViewPlugin with the option disabled, like so:
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer"),
hierarchy: "stories",
sortNodes: false // <<------ Disable node sorting
});
Note that, for all hierarchy modes, node sorting is only done for each model at the time that it is added to the TreeViewPlugin, and will not update dynamically if we later transform the Entitys corresponding to the nodes.
Pruning empty nodes
Sometimes a model contains subtrees of objects that don't have any geometry. These are models whose MetaModel contains trees of MetaObjects that don't have any Entitys in the Scene.
For these models, the tree view would contain nodes that don't do anything in the Scene when we interact with them, which is undesirable.
By default, TreeViewPlugin will not create nodes for those objects. However, we can override that behaviour if we want to have nodes for those objects (perhaps for debugging the model):
const treeView = new TreeViewPlugin(viewer, {
containerElement: document.getElementById("myTreeViewContainer"),
hierarchy: "stories",
pruneEmptyNodes: false // <<------ Create nodes for object subtrees without geometry
});
Context Menu
TreeViewPlugin fires a "contextmenu" event whenever we right-click on a tree node.
The event contains:
event
- the original contextmenu MouseEventviewer
- the ViewertreeViewPlugin
- the TreeViewPlugintreeViewNode
- the TreeViewNode representing the tree node
Let's use ContextMenu to show a simple context menu for the node we clicked.
import {ContextMenu} from "../src/extras/ContextMenu/ContextMenu.js";
const treeViewContextMenu = new ContextMenu({
items: [
[
[
{
title: "Hide",
doAction: function (context) {
context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
if (treeViewNode.objectId) {
const entity = context.viewer.scene.objects[treeViewNode.objectId];
if (entity) {
entity.visible = false;
}
}
});
}
},
{
title: "Hide all",
doAction: function (context) {
context.viewer.scene.setObjectsVisible(context.viewer.scene.visibleObjectIds, false);
}
}
],
[
{
title: "Show",
doAction: function (context) {
context.treeViewPlugin.withNodeTree(context.treeViewNode, (treeViewNode) => {
if (treeViewNode.objectId) {
const entity = context.viewer.scene.objects[treeViewNode.objectId];
if (entity) {
entity.visible = true;
entity.xrayed = false;
entity.selected = false;
}
}
});
}
},
{
title: "Show all",
doAction: function (context) {
const scene = context.viewer.scene;
scene.setObjectsVisible(scene.objectIds, true);
scene.setObjectsXRayed(scene.xrayedObjectIds, false);
scene.setObjectsSelected(scene.selectedObjectIds, false);
}
}
]
]
]
});
treeView.on("contextmenu", (e) => {
const event = e.event; // MouseEvent
const viewer = e.viewer; // Viewer
const treeViewPlugin = e.treeViewPlugin; // TreeViewPlugin
const treeViewNode = e.treeViewNode; // TreeViewNode
treeViewContextMenu.show(e.event.pageX, e.event.pageY);
treeViewContextMenu.context = {
viewer: e.viewer,
treeViewPlugin: e.treeViewPlugin,
treeViewNode: e.treeViewNode
};
});
Clicking Node Titles
TreeViewPlugin fires a "nodeTitleClicked" event whenever we left-click on a tree node.
Like the "contextmenu" event, this event contains:
event
- the original click MouseEventviewer
- the ViewertreeViewPlugin
- the TreeViewPlugintreeViewNode
- the TreeViewNode representing the tree node
Let's register a callback to isolate and fit-to-view the Entity(s) represented by the node. This callback is going to X-ray all the other Entitys, fly the camera to fit the Entity(s) for the clicked node, then hide the other Entitys.
treeView.on("nodeTitleClicked", (e) => {
const scene = viewer.scene;
const objectIds = [];
e.treeViewPlugin.withNodeTree(e.treeViewNode, (treeViewNode) => {
if (treeViewNode.objectId) {
objectIds.push(treeViewNode.objectId);
}
});
scene.setObjectsXRayed(scene.objectIds, true);
scene.setObjectsVisible(scene.objectIds, true);
scene.setObjectsXRayed(objectIds, false);
viewer.cameraFlight.flyTo({
aabb: scene.getAABB(objectIds),
duration: 0.5
}, () => {
setTimeout(function () {
scene.setObjectsVisible(scene.xrayedObjectIds, false);
scene.setObjectsXRayed(scene.xrayedObjectIds, false);
}, 500);
});
});
To make the cursor change to a pointer when we hover over the node titles, and also to make the titles change to blue, we'll also define this CSS for the <span>
elements
that represent the titles of our TreeViewPlugin nodes:
#treeViewContainer ul li span:hover {
color: blue;
cursor: pointer;
}
Constructor Summary
Public Constructor | ||
public |
constructor(viewer: Viewer, cfg: *) |
Member Summary
Public Members | ||
public |
Contains messages for any errors found while last rebuilding this TreeView. |
|
public set |
Sets how the nodes are organized within this tree view. |
|
public get |
Gets how the nodes are organized within this tree view. |
|
public |
True if errors were found generating this TreeView. |
Method Summary
Public Methods | ||
public |
Adds a model to this tree view. |
|
public |
collapse() Closes all the nodes in the tree. |
|
public |
destroy() Destroys this TreeViewPlugin. |
|
public |
expandToDepth(depth: Number) Expands the tree to the given depth. |
|
public |
removeModel(modelId: String) Removes a model from this tree view. |
|
public |
Highlights the tree view node that represents the given object Entity. |
|
public |
De-highlights the node previously shown with TreeViewPlugin#showNode. |
|
public |
withNodeTree(node: TreeViewNode, callback: Function) Iterates over a subtree of the tree view's TreeViewNodes, calling the given callback for each node in depth-first pre-order. |
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: *) source
Creates this Plugin and installs it into the given Viewer.
Override:
Plugin#constructorParams:
Name | Type | Attribute | Description |
viewer | Viewer | The Viewer. |
|
cfg | * | Plugin configuration. |
|
cfg.containerElementId | String |
|
ID of an existing HTML element to contain the TreeViewPlugin - either this or containerElement is mandatory. When both values are given, the element reference is always preferred to the ID. |
cfg.containerElement | HTMLElement | DOM element to contain the TreeViewPlugin. |
|
cfg.autoAddModels | Boolean |
|
When |
cfg.autoExpandDepth | Number |
|
Optional depth to which to initially expand the tree. |
cfg.hierarchy | String |
|
How to organize the tree nodes: "containment", "storeys" or "types". See the class documentation for details. |
cfg.sortNodes | Boolean |
|
When true, will sort the children of each node. For a "storeys" hierarchy, the
|
cfg.pruneEmptyNodes | Boolean |
|
When true, will not contain nodes that don't have content in the Scene. These are nodes whose MetaObjects don't have Entitys. |
cfg.renderService | RenderService |
|
Optional RenderService to use. Defaults to the TreeViewPlugin's default RenderService. |
Public Members
public errors: String[] source
Contains messages for any errors found while last rebuilding this TreeView.
public set hierarchy: String source
Sets how the nodes are organized within this tree view.
Accepted values are:
- "containment" - organizes the nodes to indicate the containment hierarchy of the IFC objects.
- "types" - groups the nodes within their IFC types.
- "storeys" - groups the nodes within
IfcBuildingStoreys
and sub-groups them by their IFC types.
This can be updated dynamically.
Default value is "containment".
Public Methods
public addModel(modelId: String, options: Object) source
Adds a model to this tree view.
The model will be automatically removed when destroyed.
To automatically add each model as it's created, instead of manually calling this method each time,
provide a autoAddModels: true
to the TreeViewPlugin constructor.
Params:
Name | Type | Attribute | Description |
modelId | String | ID of a model Entity in Scene#models. |
|
options | Object |
|
Options for model in the tree view. |
options.rootName | String |
|
Optional display name for the root node. Ordinary, for "containment" and "storeys" hierarchy types, the tree would derive the root node name from the model's "IfcProject" element name. This option allows to override that name when it is not suitable as a display name. |
public expandToDepth(depth: Number) source
Expands the tree to the given depth.
Collapses the tree first.
Params:
Name | Type | Attribute | Description |
depth | Number | Depth to expand to. |
public removeModel(modelId: String) source
Removes a model from this tree view.
Does nothing if model not currently in tree view.
Params:
Name | Type | Attribute | Description |
modelId | String | ID of a model Entity in Scene#models. |
public showNode(objectId: String): boolean source
Highlights the tree view node that represents the given object Entity.
This causes the tree view to collapse, then expand to reveal the node, then highlight the node.
If a node is previously highlighted, de-highlights that node and collapses the tree first.
Note that if the TreeViewPlugin was configured with pruneEmptyNodes: true
(default configuration), then the
node won't exist in the tree if it has no Entitys in the Scene. in that case, nothing will happen.
Within the DOM, the node is represented by an <li>
element. This method will add a .highlighted-node
class to
the element to make it appear highlighted, removing that class when de-highlighting it again. See the CSS rules
in the TreeViewPlugin examples for an example of that class.
public unShowNode() source
De-highlights the node previously shown with TreeViewPlugin#showNode.
Does nothing if no node is currently shown.
If the node is currently scrolled into view, keeps the node in view.
public withNodeTree(node: TreeViewNode, callback: Function) source
Iterates over a subtree of the tree view's TreeViewNodes, calling the given callback for each node in depth-first pre-order.
Params:
Name | Type | Attribute | Description |
node | TreeViewNode | Root of the subtree. |
|
callback | Function | Callback called at each TreeViewNode, with the TreeViewNode given as the argument. |