@ccp-nc/crystvis-js
Version:
A Three.js based crystallographic visualisation tool
574 lines (480 loc) • 29.1 kB
HTML
<!DOCTYPE html><html lang="en" style="font-size:16px"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Source: visualizer.js</title><!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]--><script src="scripts/third-party/hljs.js" defer="defer"></script><script src="scripts/third-party/hljs-line-num.js" defer="defer"></script><script src="scripts/third-party/popper.js" defer="defer"></script><script src="scripts/third-party/tippy.js" defer="defer"></script><script src="scripts/third-party/tocbot.min.js"></script><script>var baseURL="/",locationPathname="";baseURL=(locationPathname=document.location.pathname).substr(0,locationPathname.lastIndexOf("/")+1)</script><link rel="stylesheet" href="styles/clean-jsdoc-theme.min.css"><svg aria-hidden="true" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" style="display:none"><defs><symbol id="copy-icon" viewbox="0 0 488.3 488.3"><g><path d="M314.25,85.4h-227c-21.3,0-38.6,17.3-38.6,38.6v325.7c0,21.3,17.3,38.6,38.6,38.6h227c21.3,0,38.6-17.3,38.6-38.6V124 C352.75,102.7,335.45,85.4,314.25,85.4z M325.75,449.6c0,6.4-5.2,11.6-11.6,11.6h-227c-6.4,0-11.6-5.2-11.6-11.6V124 c0-6.4,5.2-11.6,11.6-11.6h227c6.4,0,11.6,5.2,11.6,11.6V449.6z"/><path d="M401.05,0h-227c-21.3,0-38.6,17.3-38.6,38.6c0,7.5,6,13.5,13.5,13.5s13.5-6,13.5-13.5c0-6.4,5.2-11.6,11.6-11.6h227 c6.4,0,11.6,5.2,11.6,11.6v325.7c0,6.4-5.2,11.6-11.6,11.6c-7.5,0-13.5,6-13.5,13.5s6,13.5,13.5,13.5c21.3,0,38.6-17.3,38.6-38.6 V38.6C439.65,17.3,422.35,0,401.05,0z"/></g></symbol><symbol id="search-icon" viewBox="0 0 512 512"><g><g><path d="M225.474,0C101.151,0,0,101.151,0,225.474c0,124.33,101.151,225.474,225.474,225.474 c124.33,0,225.474-101.144,225.474-225.474C450.948,101.151,349.804,0,225.474,0z M225.474,409.323 c-101.373,0-183.848-82.475-183.848-183.848S124.101,41.626,225.474,41.626s183.848,82.475,183.848,183.848 S326.847,409.323,225.474,409.323z"/></g></g><g><g><path d="M505.902,476.472L386.574,357.144c-8.131-8.131-21.299-8.131-29.43,0c-8.131,8.124-8.131,21.306,0,29.43l119.328,119.328 c4.065,4.065,9.387,6.098,14.715,6.098c5.321,0,10.649-2.033,14.715-6.098C514.033,497.778,514.033,484.596,505.902,476.472z"/></g></g></symbol><symbol id="font-size-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11.246 15H4.754l-2 5H.6L7 4h2l6.4 16h-2.154l-2-5zm-.8-2L8 6.885 5.554 13h4.892zM21 12.535V12h2v8h-2v-.535a4 4 0 1 1 0-6.93zM19 18a2 2 0 1 0 0-4 2 2 0 0 0 0 4z"/></symbol><symbol id="add-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M11 11V5h2v6h6v2h-6v6h-2v-6H5v-2z"/></symbol><symbol id="minus-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M5 11h14v2H5z"/></symbol><symbol id="dark-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M10 7a7 7 0 0 0 12 4.9v.1c0 5.523-4.477 10-10 10S2 17.523 2 12 6.477 2 12 2h.1A6.979 6.979 0 0 0 10 7zm-6 5a8 8 0 0 0 15.062 3.762A9 9 0 0 1 8.238 4.938 7.999 7.999 0 0 0 4 12z"/></symbol><symbol id="light-theme-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 18a6 6 0 1 1 0-12 6 6 0 0 1 0 12zm0-2a4 4 0 1 0 0-8 4 4 0 0 0 0 8zM11 1h2v3h-2V1zm0 19h2v3h-2v-3zM3.515 4.929l1.414-1.414L7.05 5.636 5.636 7.05 3.515 4.93zM16.95 18.364l1.414-1.414 2.121 2.121-1.414 1.414-2.121-2.121zm2.121-14.85l1.414 1.415-2.121 2.121-1.414-1.414 2.121-2.121zM5.636 16.95l1.414 1.414-2.121 2.121-1.414-1.414 2.121-2.121zM23 11v2h-3v-2h3zM4 11v2H1v-2h3z"/></symbol><symbol id="reset-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M18.537 19.567A9.961 9.961 0 0 1 12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10c0 2.136-.67 4.116-1.81 5.74L17 12h3a8 8 0 1 0-2.46 5.772l.997 1.795z"/></symbol><symbol id="down-icon" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M12.7803 6.21967C13.0732 6.51256 13.0732 6.98744 12.7803 7.28033L8.53033 11.5303C8.23744 11.8232 7.76256 11.8232 7.46967 11.5303L3.21967 7.28033C2.92678 6.98744 2.92678 6.51256 3.21967 6.21967C3.51256 5.92678 3.98744 5.92678 4.28033 6.21967L8 9.93934L11.7197 6.21967C12.0126 5.92678 12.4874 5.92678 12.7803 6.21967Z"></path></symbol><symbol id="codepen-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M16.5 13.202L13 15.535v3.596L19.197 15 16.5 13.202zM14.697 12L12 10.202 9.303 12 12 13.798 14.697 12zM20 10.869L18.303 12 20 13.131V10.87zM19.197 9L13 4.869v3.596l3.5 2.333L19.197 9zM7.5 10.798L11 8.465V4.869L4.803 9 7.5 10.798zM4.803 15L11 19.131v-3.596l-3.5-2.333L4.803 15zM4 13.131L5.697 12 4 10.869v2.262zM2 9a1 1 0 0 1 .445-.832l9-6a1 1 0 0 1 1.11 0l9 6A1 1 0 0 1 22 9v6a1 1 0 0 1-.445.832l-9 6a1 1 0 0 1-1.11 0l-9-6A1 1 0 0 1 2 15V9z"/></symbol><symbol id="close-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M12 10.586l4.95-4.95 1.414 1.414-4.95 4.95 4.95 4.95-1.414 1.414-4.95-4.95-4.95 4.95-1.414-1.414 4.95-4.95-4.95-4.95L7.05 5.636z"/></symbol><symbol id="menu-icon" viewBox="0 0 24 24"><path fill="none" d="M0 0h24v24H0z"/><path d="M3 4h18v2H3V4zm0 7h18v2H3v-2zm0 7h18v2H3v-2z"/></symbol></defs></svg></head><body data-theme="dark"><div class="sidebar-container"><div class="sidebar" id="sidebar"><div class="sidebar-items-container"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="lib_model.module_js.html">lib/model.js</a></div><div class="sidebar-section-children"><a href="lib_modelview.module_js.html">lib/modelview.js</a></div><div class="sidebar-section-children"><a href="lib_visualizer.module_js.html">lib/visualizer.js</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="lib_model.module_js-AtomImage.html">AtomImage</a></div><div class="sidebar-section-children"><a href="lib_model.module_js-BondImage.html">BondImage</a></div><div class="sidebar-section-children"><a href="lib_model.module_js-Model.html">Model</a></div><div class="sidebar-section-children"><a href="lib_modelview.module_js-ModelView.html">ModelView</a></div><div class="sidebar-section-children"><a href="lib_visualizer.module_js-CrystVis.html">CrystVis</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-tutorials"><div>Tutorials</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="tutorial-Events.html">Events</a></div><div class="sidebar-section-children"><a href="tutorial-Queries.html">Queries</a></div><div class="sidebar-section-children"><a href="tutorial-ThreejsMigration.html">ThreejsMigration</a></div></div></div></div></div><div class="navbar-container" id="VuAckcnZhf"><nav class="navbar"><div class="navbar-left-items"></div><div class="navbar-right-items"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div><nav></nav></nav></div><div class="toc-container"><div class="toc-content"><span class="bold">On this page</span><div id="eed4d2a0bfd64539bb9df78095dec881"></div></div></div><div class="body-wrapper"><div class="main-content"><div class="main-wrapper"><section id="source-page" class="source-page"><header><h1 id="title" class="has-anchor">visualizer.js</h1></header><article><pre class="prettyprint source lang-js"><code>'use strict';
/**
* @fileoverview Class constituting the main object that plots crystals in the webpage
* @module
*/
import * as _ from 'lodash';
import {
Renderer as Renderer
} from './render.js';
// themes
import {themes} from './render.js';
import {
Loader as Loader
} from './loader.js';
import {
Model as Model
} from './model.js';
import {
ModelView as ModelView
} from './modelview.js';
import {
AtomMesh
} from './primitives/index.js';
import {
addStaticVar
} from './utils.js';
const model_parameter_defaults = {
supercell: [1, 1, 1],
molecularCrystal: false
};
/** An object providing a full interface to a renderer for crystallographic models */
class CrystVis {
/**
* An object providing a full interface to a renderer for crystallographic
* models
* @class
* @param {string} element CSS-style identifier for the HTML element to
* put the renderer in
* @param {int} width Window width
* @param {int} height Window height. If both this and width are
* set to 0, the window fits its context and
* automatically resizes with it
* @param {Object} rendererOptions Options for the renderer
*/
constructor(element, width = 0, height = 0, rendererOptions = {}) {
// Create a renderer
this._renderer = new Renderer(element, width, height, rendererOptions);
this._loader = new Loader();
this._models = {};
this._current_model = null;
this._current_mname = null;
this._displayed = null;
this._selected = null;
this._notifications = [];
// Handling events
this._atom_click_events = {};
this._atom_click_events[CrystVis.LEFT_CLICK] = this._defaultAtomLeftClick.bind(this);
this._atom_click_events[CrystVis.LEFT_CLICK + CrystVis.SHIFT_BUTTON] = this._defaultAtomShiftLeftClick.bind(this);
this._atom_click_events[CrystVis.LEFT_CLICK + CrystVis.CTRL_BUTTON] = this._defaultAtomCtrlLeftClick.bind(this);
this._atom_click_defaults = _.cloneDeep(this._atom_click_events);
this._atom_box_event = this._defaultAtomBox.bind(this);
this._renderer.addClickListener(this._handleAtomClick.bind(this),
this._renderer._groups.model, AtomMesh);
this._renderer.addSelBoxListener(this._handleAtomBox.bind(this),
this._renderer._groups.model, AtomMesh);
// Additional options
// Hidden (need dedicated setters)
this._hsel = false; // If true, highlight the selected atoms
// Vanilla (no get/set needed)
this.cifsymtol = 1e-2; // Parameter controlling the tolerance to symmetry when loading CIF files
}
/**
* List of loaded models
* @readonly
* @type {Array}
*/
get modelList() {
return Object.keys(this._models);
}
/**
* Currently loaded model
* @readonly
* @type {Model}
*/
get model() {
return this._current_model;
}
/**
* Name of the currently loaded model
* @readonly
* @type {String}
*/
get modelName() {
return this._current_mname;
}
/**
* Displayed atoms
* @type {ModelView}
*/
get displayed() {
return this._displayed;
}
set displayed(d) {
if (!(d instanceof ModelView)) {
throw new Error('.displayed must be set with a ModelView');
}
this._displayed.hide();
this._displayed = d;
this._displayed.show();
}
/**
* Selected atoms
* @type {ModelView}
*/
get selected() {
return this._selected;
}
set selected(s) {
if (!(s instanceof ModelView)) {
throw new Error('.selected must be set with a ModelView');
}
this._selected.setProperty('highlighted', false);
this._selected = s;
this._selected.setProperty('highlighted', this._hsel);
}
/** Whether the selected atoms should be highlighted with auras
* @type {bool}
*/
get highlightSelected() {
return this._hsel;
}
set highlightSelected(hs) {
this._hsel = hs;
if (this._selected) {
this._selected.setProperty('highlighted', this._hsel);
}
}
get notifications() {
return this._notifications;
}
set notifications(n) {
this._notifications = n;
}
/** Theme
* @type {object}
*/
get theme() {
return this._renderer.theme;
}
set theme(t) {
// if t is a string, try to find the corresponding theme
// from the list of themes
if (typeof t === 'string') {
if (themes[t]) {
t = themes[t];
} else {
throw new Error('Theme ' + t + ' not found');
}
}
this._renderer.theme = t;
}
/**
* Set a callback function for an event where a user clicks on an atom. The
* function should take as arguments the atom image for the clicked atom and
* the event object:
*
* function callback(atom, event) {
* ...
* }
*
* @param {Function} callback Callback function for the event. Passing "null" restores default behaviour
* @param {int} modifiers Click event. Use the following flags to define it:
*
* * CrystVis.LEFT_CLICK
* * CrystVis.RIGHT_CLICK
* * CrystVis.MIDDLE_CLICK
* * CrystVis.CTRL_BUTTON
* * CrystVis.ALT_BUTTON
* * CrystVis.SHIFT_BUTTON
* * CrystVis.CMD_BUTTON
*
* For example, CrystVis.LEFT_CLICK + CrystVis.SHIFT_BUTTON
* defines the event for a click while the Shift key is pressed.
*
*/
onAtomClick(callback = null, modifiers = CrystVis.LEFT_CLICK) {
// Check that event makes sense
var lc = modifiers & CrystVis.LEFT_CLICK;
var mc = modifiers & CrystVis.MIDDLE_CLICK;
var rc = modifiers & CrystVis.RIGHT_CLICK;
if (lc + mc + rc == 0) {
throw 'Can not set event without any click type';
}
if ((lc && mc) || (lc && rc) || (mc && rc)) {
throw 'Can not set event with two or more click types';
}
if (callback)
this._atom_click_events[modifiers] = callback.bind(this);
else
this._atom_click_events[modifiers] = this._atom_click_defaults[modifiers];
}
/**
* Set a callback function for an event where a user drags a box around multiple atoms.
* The function should take as arguments a ModelView including the atoms in the box:
*
* function callback(view) {
* ...
* }
*
* @param {Function} callback Callback function for the event. Passing "null" restores default behaviour
*/
onAtomBox(callback = null) {
if (callback)
this._atom_box_event = callback;
else
this._atom_box_event = this._defaultAtomBox.bind(this);
}
_defaultAtomLeftClick(atom, event) {
var i = atom.imgIndex;
this.selected = new ModelView(this._current_model, [i]);
}
_defaultAtomShiftLeftClick(atom, event) {
var i = atom.imgIndex;
this.selected = this.selected.or(new ModelView(this._current_model, [i]));
}
_defaultAtomCtrlLeftClick(atom, event) {
var i = atom.imgIndex;
this.selected = this.selected.xor(new ModelView(this._current_model, [i]));
}
_defaultAtomBox(view) {
this.selected = this.selected.xor(view);
console.log(view);
}
// Callback for when atoms are clicked
_handleAtomClick(alist, event) {
if (alist.length == 0) {
return;
}
let clicked = alist[0].image;
let modifiers = [CrystVis.LEFT_CLICK, CrystVis.MIDDLE_CLICK, CrystVis.RIGHT_CLICK][event.button];
modifiers += event.shiftKey * CrystVis.SHIFT_BUTTON;
modifiers += (event.ctrlKey || event.metaKey) * CrystVis.CTRL_BUTTON;
modifiers += event.altKey * CrystVis.ALT_BUTTON;
var callback = this._atom_click_events[modifiers];
if (callback)
callback(clicked, event);
}
// Callback for a whole box dragged over atoms
_handleAtomBox(alist) {
var indices = alist.map(function(a) {
return a.image.imgIndex;
});
var callback = this._atom_box_event;
if (callback)
callback(new ModelView(this._current_model, indices));
}
/**
* Center the camera on a given point
*
* @param {float[]} center Point in model space that the orbiting camera
* should be centred on and look at
* @param {float[]} shift Shift (in units of width/height of the canvas) with
* which the center of the camera should be rendered with
* respect to the center of the canvas
*/
centerCamera(center = [0, 0, 0], shift = [0, 0]) {
const renderer = this._renderer;
renderer.resetOrbitCenter(center[0], center[1], center[2]);
renderer.resetCameraCenter(shift[0], shift[1]);
}
/**
* Load one or more atomic models from a file's contents
*
* @param {String} contents The contents of the structure file
* @param {String} format The file's format (cif, xyz, etc.). Default is cif.
* @param {String} prefix Prefix to use when naming the models. Default is empty.
* @param {Object} parameters Loading parameters:
*
* - `supercell`: supercell size (only used if the structure is periodic)
* - `molecularCrystal`: if true, try to make the model load completing molecules across periodic boundaries
* - `useNMRActiveIsotopes`: if true, all isotopes are set by default to the most common one with non-zero spin
* - `vdwScaling`: scale van der Waals radii by a constant factor
* - `vdwElementScaling`: table of per-element factors to scale VdW radii by
*
* @return {Object} Names of the models we tried to load, and values of true/false for successful loading or not
*/
loadModels(contents, format = 'cif', prefix = null, parameters = {}) {
// clear existing notifications
this.clearNotifications();
parameters = _.merge(model_parameter_defaults, parameters);
// By default, it's cif
format = format.toLowerCase();
// By default, same as the format
prefix = prefix || format;
var structs = this._loader.load(contents, format, prefix);
var status = {};
if (this._loader.status == Loader.STATUS_ERROR) {
status[prefix] = this._loader.error_message;
// display error notification to user
this.addNotification('Error loading model: '+ prefix);
this.addNotification(this._loader.error_message);
return status;
}
// Now make unique names
for (var n in structs) {
var iter = 0;
var coll = true;
var nn = n;
while (coll) {
nn = n + (iter > 0 ? '_' + iter : '');
coll = nn in this._models;
iter++;
}
var s = structs[n];
if (!s) {
status[nn] = 'Model could not load properly';
this.addNotification('Model '+ nn + ' could not load properly');
continue;
}
this._models[nn] = new Model(s, parameters);
status[nn] = 0; // Success
}
return status;
}
/**
* Reload a model, possibly with new parameters
*
* @param {String} name Name of the model to reload.
* @param {Object} parameters Loading parameters as in .loadModels()
*/
reloadModel(name, parameters = {}) {
// clear existing notifications from scene
this.clearNotifications();
if (!(name in this._models)) {
throw 'The requested model does not exist';
}
var current = (this._current_mname == name);
if (current) {
// Hide the model to reload it later
this.displayModel();
}
var s = this._models[name]._atoms_base;
parameters = _.merge(model_parameter_defaults, parameters);
this._models[name] = new Model(s, parameters);
if (current) {
this.displayModel(name);
}
}
/**
* Render a model
*
* @param {String} name Name of the model to display. If empty, just
* clear the renderer window.
*/
displayModel(name = null) {
if (this._current_model) {
// clear notifications from previous model
this.clearNotifications();
this.selected = this._current_model.view([]);
this._current_model.renderer = null;
this._current_model = null;
this._current_mname = null;
}
this._renderer.clear();
if (!name) {
// If called with nothing, just quit here
return;
}
// if the model isn't in this._models
if (!(name in this._models) && Object.keys(this._models).length > 0) {
// in case the model does not exist, reset the orbit
this._renderer.resetOrbitCenter(5,5,5);
this.addNotification('The requested model does not exist.')
throw 'The requested model does not exist.';
}
var m = this._models[name];
m.renderer = this._renderer;
this._current_model = m;
this._current_mname = name;
this._displayed = m.find({
'cell': [
[0, 0, 0]
]
});
this._selected = new ModelView(m, []); // Empty
// Set the camera in a way that will center the model
var c = m.fracToAbs([0.5, 0.5, 0.5]);
this._renderer.resetOrbitCenter(c[0], c[1], c[2]);
this._displayed.show();
}
/**
* Erase a model from the recorded ones
*
* @param {String} name Name of the model to delete
*/
deleteModel(name) {
if (!(name in this._models)) {
throw 'The requested model does not exist!';
}
if (this._current_mname == name) {
this.displayModel();
}
delete this._models[name];
}
/**
* Add a primitive shape to the drawing
*
* @param {THREE.Object3D} p Primitive to add
*/
addPrimitive(p) {
this._renderer.add(p);
}
/**
* Remove a primitive shape from the drawing
*
* @param {THREE.Object3D} p Primitive to remove
*/
removePrimitive(p) {
this._renderer.remove(p);
}
/**
* Add a notification to the list of notifications to be displayed
*/
addNotification(n) {
this._notifications.push(n);
this.addNotifications();
}
/**
* Adds all notifications to the drawing
*
*/
addNotifications() {
// remove displayed notifications
// (doesn't remove them from this._notifications)
this._renderer.clearNotifications();
// add full list of notifications
this._renderer.addNotifications(this._notifications);
}
/**
* Removes notifications from the drawing
*/
clearNotifications() {
this._notifications = [];
this._renderer.clearNotifications();
}
/**
* Recover a data URL of a PNG screenshot of the current scene
*
* @return {String} A data URL of the PNG screenshot
*/
getScreenshotData(transparent = true, scale_pixels = 3) {
var renderer = this._renderer;
// save current alpha and antialias settings
var old_alpha = renderer._r.getClearAlpha();
var old_PixelRatio = renderer._r.getPixelRatio();
// set new alpha and antialias settings
renderer._r.setClearAlpha(transparent ? 0 : 1);
renderer._r.setPixelRatio(scale_pixels);
// Force a render
this._renderer._render();
// Grab the data from the canvas
var data = renderer._r.domElement.toDataURL();
// restore old alpha and antialias settings
renderer._r.setClearAlpha(old_alpha);
renderer._r.setPixelRatio(old_PixelRatio);
return data;
}
}
addStaticVar(CrystVis, 'LEFT_CLICK', 1);
addStaticVar(CrystVis, 'MIDDLE_CLICK', 2);
addStaticVar(CrystVis, 'RIGHT_CLICK', 4);
addStaticVar(CrystVis, 'ALT_BUTTON', 8);
addStaticVar(CrystVis, 'CTRL_BUTTON', 16);
addStaticVar(CrystVis, 'CMD_BUTTON', 16); // Alias for Mac users
addStaticVar(CrystVis, 'SHIFT_BUTTON', 32);
export {
CrystVis
}</code></pre></article></section></div></div></div><div class="search-container" id="PkfLWpAbet" style="display:none"><div class="wrapper" id="iCxFxjkHbP"><button class="icon-button search-close-button" id="VjLlGakifb" aria-label="close search"><svg><use xlink:href="#close-icon"></use></svg></button><div class="search-box-c"><svg><use xlink:href="#search-icon"></use></svg> <input type="text" id="vpcKVYIppa" class="search-input" placeholder="Search..." autofocus></div><div class="search-result-c" id="fWwVHRuDuN"><span class="search-result-c-text">Type anything to view search result</span></div></div></div><div class="mobile-menu-icon-container"><button class="icon-button" id="mobile-menu" data-isopen="false" aria-label="menu"><svg><use xlink:href="#menu-icon"></use></svg></button></div><div id="mobile-sidebar" class="mobile-sidebar-container"><div class="mobile-sidebar-wrapper"><div class="mobile-nav-links"></div><div class="mobile-sidebar-items-c"><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-modules"><div>Modules</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="lib_model.module_js.html">lib/model.js</a></div><div class="sidebar-section-children"><a href="lib_modelview.module_js.html">lib/modelview.js</a></div><div class="sidebar-section-children"><a href="lib_visualizer.module_js.html">lib/visualizer.js</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-classes"><div>Classes</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="lib_model.module_js-AtomImage.html">AtomImage</a></div><div class="sidebar-section-children"><a href="lib_model.module_js-BondImage.html">BondImage</a></div><div class="sidebar-section-children"><a href="lib_model.module_js-Model.html">Model</a></div><div class="sidebar-section-children"><a href="lib_modelview.module_js-ModelView.html">ModelView</a></div><div class="sidebar-section-children"><a href="lib_visualizer.module_js-CrystVis.html">CrystVis</a></div></div><div class="sidebar-section-title with-arrow" data-isopen="false" id="sidebar-tutorials"><div>Tutorials</div><svg><use xlink:href="#down-icon"></use></svg></div><div class="sidebar-section-children-container"><div class="sidebar-section-children"><a href="tutorial-Events.html">Events</a></div><div class="sidebar-section-children"><a href="tutorial-Queries.html">Queries</a></div><div class="sidebar-section-children"><a href="tutorial-ThreejsMigration.html">ThreejsMigration</a></div></div></div><div class="mobile-navbar-actions"><div class="navbar-right-item"><button class="icon-button search-button" aria-label="open-search"><svg><use xlink:href="#search-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button theme-toggle" aria-label="toggle-theme"><svg><use class="theme-svg-use" xlink:href="#light-theme-icon"></use></svg></button></div><div class="navbar-right-item"><button class="icon-button font-size" aria-label="change-font-size"><svg><use xlink:href="#font-size-icon"></use></svg></button></div></div></div></div><script type="text/javascript" src="scripts/core.min.js"></script><script src="scripts/search.min.js" defer="defer"></script><script src="scripts/third-party/fuse.js" defer="defer"></script><script type="text/javascript">var tocbotInstance=tocbot.init({tocSelector:"#eed4d2a0bfd64539bb9df78095dec881",contentSelector:".main-content",headingSelector:"h1, h2, h3",hasInnerContainers:!0,scrollContainer:".main-content",headingsOffset:130,onClick:bringLinkToView})</script></body></html>