Reference Source
public class | source

TreeViewPlugin

Extends:

Plugin → TreeViewPlugin

A Viewer plugin that provides an HTML tree view to navigate the IFC elements in models.

[Run this example]

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, along with an accompanying JSON IFC metadata file.

[Run this example]

import {Viewer} from "../src/viewer/Viewer.js";
import {XKTLoaderPlugin} from "../src/plugins/XKTLoaderPlugin/XKTLoaderPlugin.js";
import {TreeViewPlugin} from "../src/plugins/TreeViewPlugin/TreeViewPlugin.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/schependomlaan.xkt",
    metaModelSrc: "./metaModels/schependomlaan/metaModel.json",
    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/schependomlaan.xkt",
    metaModelSrc: "./metaModels/schependomlaan/metaModel.json",
    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. This assumes that the 3D World-space boundaries of the IfcBuildingStory elements are actually spatially sortable, which may not be the case in all IFC models. Some models may have IfcBuildingStory elements that contain objects that span multiple storeys, which would defeat this sorting.

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 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:

Let's use ContextMenu to show a simple context menu for the node we clicked.

[Run an example]

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:

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.

[Run an example]

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 set

Sets how the nodes are organized within this tree view.

public get

Gets how the nodes are organized within this tree view.

Method Summary

Public Methods
public

addModel(modelId: String, options: Object)

Adds a model to this tree view.

public

Collapses all trees within this tree view.

public

Destroys this TreeViewPlugin.

public

Expands the tree to the given depth.

public

removeModel(modelId: String)

Removes a model from this tree view.

public

showNode(objectId: String)

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

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)

Fires an event at this Plugin.

public

log(msg: String)

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

public

on(event: String, callback: Function)

Subscribes to an event fired at this Plugin.

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: *) source

Creates this Plugin and installs it into the given Viewer.

Override:

Plugin#constructor

Params:

NameTypeAttributeDescription
viewer Viewer

The Viewer.

cfg *

Plugin configuration.

cfg.containerElement HTMLElement

DOM element to contain the TreeViewPlugin.

cfg.autoAddModels Boolean
  • optional
  • default: true

When true (default), will automatically add each model as it's created. Set this false if you want to manually add models using TreeViewPlugin#addModel instead.

cfg.autoExpandDepth Number
  • optional

Optional depth to which to initially expand the tree.

cfg.hierarchy String
  • optional
  • default: "containment"

How to organize the tree nodes: "containment", "storeys" or "types". See the class documentation for details.

cfg.sortNodes Boolean
  • optional
  • default: true

When true, will sort the children of each node. For a "storeys" hierarchy, the IfcBuildingStorey nodes will be ordered spatially, from the highest storey down to the lowest, on the vertical World axis. For all hierarchy types, other node types will be ordered in the ascending alphanumeric order of their titles.

cfg.pruneEmptyNodes Boolean
  • optional
  • default: true

When true, will not contain nodes that don't have content in the Scene. These are nodes whose MetaObjects don't have Entitys.

Public Members

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 get hierarchy: String source

Gets how the nodes are organized within this tree view.

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:

NameTypeAttributeDescription
modelId String

ID of a model Entity in Scene#models.

options Object
  • optional

Options for model in the tree view.

options.rootName String
  • optional

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 collapse() source

Collapses all trees within this tree view.

public destroy() source

Destroys this TreeViewPlugin.

Override:

Plugin#destroy

public expandToDepth(depth: Number) source

Expands the tree to the given depth.

Collapses the tree first.

Params:

NameTypeAttributeDescription
depth Number

Depth to expand to.

public removeModel(modelId: String) source

Removes a model from this tree view.

Params:

NameTypeAttributeDescription
modelId String

ID of a model Entity in Scene#models.

public showNode(objectId: String) 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.

Params:

NameTypeAttributeDescription
objectId String

ID of the Entity.

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:

NameTypeAttributeDescription
node TreeViewNode

Root of the subtree.

callback Function

Callback called at each TreeViewNode, with the TreeViewNode given as the argument.