UNPKG

3dmol

Version:

JavaScript/TypeScript molecular visualization library

606 lines (484 loc) 16.1 kB
/** * $3Dmol.StateManager - StateManager creates the space to preserve the state of the ui and sync it with the GLViewer * @constructor * @param {$3Dmol.GLViewer} glviewer StateManager is required to have interaction between glviewer and the ui. * @param {Object} config Loads the user defined parameters to generate the ui and handle state */ $3Dmol.StateManager = (function(){ function States(glviewer, config){ config = config || glviewer.getConfig(); config.ui = true; var canvas = $(glviewer.getCanvas()); var parentElement = $(glviewer.container); var height = parentElement.height(); var width = parentElement.width(); var offset = canvas.offset(); var uiOverlayConfig = { height : height, width : width, offset : offset, ui : config.ui || undefined } // Selection Handlers var selections = {}; // Surface handlers var surfaces = {}; // Label Handlers var labels = {}; var atomLabel = {}; /** * Add Selection from the ui to glviewer * * @function $3Dmol.StateManager#addSelection * @param {Object} spec Object that contains the output from the form * @param {String} sid If surface id being edited then sid is set to some string * @returns String */ this.addSelection = function(spec, sid = null){ var id = sid || makeid(4); var selectionSpec = { spec : spec, styles : {}, hidden : false }; if(sid == null) selections[id] = selectionSpec; else selections[id].spec = selectionSpec.spec; render(); return id; } /** * Return true if the selections contain at least one atom * * @function $3Dmol.StateManager#checkAtoms * @param {AtomSelectionSpec} sel Atom selection spec * @returns Boolean */ this.checkAtoms = function(sel){ var atoms = glviewer.selectedAtoms(sel); if( atoms.length > 0) return true return false; } /** * Toggle the hidden property of the selection * @function $3Dmol.StateManager#toggleHide * @param {String} sid Selection id */ this.toggleHide = function(sid){ selections[sid].hidden = !selections[sid].hidden; render(); } /** * Removes the selection * @param {String} id Selection id */ this.removeSelection = function(id) { delete selections[id]; render(); } /** * Add style and renders it into the viewport * * @function $3Dmol.StateManager#addStyle * @param {String} spec Output object of style form * @param {String} sid Selection Id * @param {String} stid Style Id * @returns String */ this.addStyle = function( spec, sid, stid = null){ var selection = selections[sid]; var styleSpec = { spec : spec, hidden : false } var id = null; if(stid == null) { id = makeid(4); selection.styles[id] = styleSpec } else { id = stid; selection.styles[id].spec = spec; } render(); return id; } /** * Removes the style specified by stid * * @function $3Dmol.StateManager#removeStyle * @param {String} sid Selection id * @param {String} stid Style Id */ this.removeStyle = function(sid, stid){ delete selections[sid].styles[stid]; render(); } /** * Toggle hidden property of a style * * @function $3Dmol.StateManager#toggleHideStyle * @param {String} sid Selection Id * @param {String} stid Style Id */ this.toggleHideStyle = function(sid, stid){ selections[sid].styles[stid].hidden = !selections[sid].styles[stid].hidden; render(); } /** * Adds surface to the viewport * * @function $3Dmol.StateManager#addSurface * @param {Object} property Surface output object * @param {Function} callback callback * @returns String */ this.addSurface = function(property, callback){ var id = makeid(4); property.id = id; var style = property.surfaceStyle.value; if(style == null) style = {}; var sel = (property.surfaceFor.value == 'all') ? { spec : {} } : selections[property.surfaceFor.value]; var generatorAtom = (property.surfaceOf.value == 'self')? sel.spec : {}; glviewer.addSurface( $3Dmol.SurfaceType[property.surfaceType.value], style, sel.spec, generatorAtom ).then((surfParam)=>{ surfaces[id] = surfParam[0]; if(callback != undefined) callback(id, surfParam[0]); }, ()=>{ }); return id; } /** * Removes surface from the viewport * @function $3Dmol.StateManager#removeSurface * @param {String} id Surface Id */ this.removeSurface = function(id){ glviewer.removeSurface(surfaces[id]) delete surfaces[id]; } /** * Edit the exisiting surface in the viewport * * @function $3Dmol.StateManager#editSurface * @param {Object} surfaceProperty Surface Style */ this.editSurface = function(surfaceProperty){ var style = surfaceProperty.surfaceStyle.value || {} var sel = (surfaceProperty.surfaceFor.value == 'all') ? { spec : {} } : selections[surfaceProperty.surfaceFor.value]; var generatorAtom = (surfaceProperty.surfaceOf.value == 'self')? sel.spec : {}; glviewer.removeSurface(surfaces[surfaceProperty.id]); glviewer.addSurface( $3Dmol.SurfaceType[surfaceProperty.surfaceType.value], style, sel.spec, generatorAtom ).then((surfId)=>{ surfaces[surfaceProperty.id] = surfId[0]; }); } /** * Returns the list of ids of selections that are created so far * @function $3Dmol.StateManager#getSelectionList * @returns <Array of selection ids> */ this.getSelectionList = function(){ return Object.keys(selections); } /** * Opens context menu when called from glviewer * * @function $3Dmol.StateManager#openContextMenu * @param {AtomSpec} atom Atom spec obtained from context menu event * @param {Number} x x coordinate of mouse on viewport * @param {Number} y y coordinate of mouse on the viewport */ this.openContextMenu = function(atom, x, y){ var atomExist = false; if(atom){ atomExist = Object.keys(atomLabel).find((i)=>{ if (i == atom.index) return true; else return false; }); if(atomExist != undefined ) atomExist = true; else atomExist = false; } if(this.ui) this.ui.tools.contextMenu.show(x, y, atom, atomExist); } glviewer.userContextMenuHandler = this.openContextMenu.bind(this); /** * Adds Label to the viewport specific to the selection * @function $3Dmol.StateManager#addLabel * @param {Object} labelValue Output object from label form of Context Menu */ this.addLabel = function(labelValue){ labels[labelValue.sel.value] = labels[labelValue.sel.value] || []; var labelProp = $3Dmol.labelStyles[labelValue.style.value]; var selection = selections[labelValue.sel.value]; var offset = labels[labelValue.sel.value].length; labelProp['screenOffset'] = new $3Dmol.Vector2(0, -1*offset*35); labels[labelValue.sel.value].push(glviewer.addLabel(labelValue.text.value, labelProp, selection.spec)); this.ui.tools.contextMenu.hide(); } /** * Adds atom label to the viewport * * @function $3Dmol.StateManager#addAtomLabel * @param {Object} labelValue Output object from propertyMenu form of Context Menu * @param {AtomSpec} atom Atom spec that are to be added in the label */ this.addAtomLabel = function(labelValue, atom, styleName='milk'){ var atomExist = Object.keys(atomLabel).find((i)=>{ if (i == atom.index) return true; else return false; }); if(atomExist != undefined ) atomExist = true; else atomExist = false; if(atomExist){ this.removeAtomLabel(atom); } atomLabel[atom.index] = atomLabel[atom.index] || null; var labelProp = $3Dmol.deepCopy($3Dmol.labelStyles[styleName]); labelProp.position = { x : atom.x, y : atom.y, z : atom.z } var labelText = []; for (let key in labelValue){ labelText.push(`${key} : ${labelValue[key]}`); } labelText = labelText.join('\n'); atomLabel[atom.index] = glviewer.addLabel(labelText, labelProp); } /** * Executes hide context menu and process the label if needed * * @function $3Dmol.StateManager#exitContextMenu * @param {Boolean} processContextMenu Specify the need to process the values in the context menu */ this.exitContextMenu = function(processContextMenu = false){ if(this.ui) { this.ui.tools.contextMenu.hide(processContextMenu); } } glviewer.container.addEventListener('wheel', this.exitContextMenu.bind(this), { passive: false }); /** * Removes the label specific to the selection * * (under development) */ this.removeLabel = function(){ // Add code to remove label this.ui.tools.contextMenu.hide(); } /** * Removes the atom label from the viewpoer * @function $3Dmol.StateManager#removeAtomLabel * @param {AtomSpec} atom Atom spec */ this.removeAtomLabel = function(atom){ var label = atomLabel[atom.index]; glviewer.removeLabel(label); delete atomLabel[atom.index]; this.ui.tools.contextMenu.hide(); } /** * Add model to the viewport * @function $3Dmol.StateManager#addModel * @param {Object} modelDesc Model Toolbar output */ this.addModel = function(modelDesc){ glviewer.removeAllModels(); glviewer.removeAllSurfaces(); glviewer.removeAllLabels(); glviewer.removeAllShapes(); var query = modelDesc.urlType.value + ':' + modelDesc.url.value; $3Dmol.download(query, glviewer, {}, ()=>{ this.ui.tools.modelToolBar.setModel(modelDesc.url.value.toUpperCase()); }); // Remove all Selections selections = {}; surfaces = {}; atomLabel = {}; labels = {}; // Reset UI this.ui.tools.selectionBox.empty(); this.ui.tools.surfaceMenu.empty(); } // State Management helper function function findSelectionBySpec(spec){ var ids = Object.keys(selections); var matchingObjectIds = null; for(var i = 0; i < ids.length; i++){ var lookSelection = selections[ids[i]].spec; var match = true; // looking for same parameters length var parameters = Object.keys(spec); if( Object.keys(lookSelection).length == parameters.length){ for(var j = 0; j < parameters.length; j++){ if( lookSelection[parameters[j]] != spec[parameters[j]]){ match = false; break; } } } else { match = false; } if(match){ matchingObjectIds = ids[i]; break; } } return matchingObjectIds; } // State managment function /** * Updates the state variable for selections and styles and trigger ui to show the * ui elements for these selections and styles. * * @function $3Dmol.StateManager#createSelectionAndStyle * @param {AtomSelectionSpec} selSpec Atom Selection Spec * @param {AtomStyleSpec} styleSpec Atom Style Spec */ this.createSelectionAndStyle = function(selSpec, styleSpec){ var selId = findSelectionBySpec(selSpec); if(selId == null){ selId = this.addSelection(selSpec); } var styleId = null; if(Object.keys(styleSpec).length != 0){ styleId = this.addStyle(styleSpec, selId); } this.ui.tools.selectionBox.editSelection(selId, selSpec, styleId, styleSpec); }; /** * Creates selection and add surface with reference to that selection * and triggers updates in the ui * @function $3Dmol.StateManager#createSurface * @param {String} surfaceType Type of surface to be created * @param {AtomSelectionSpec} sel Atom selection spec * @param {AtomStyleSpec} style Atom style spec * @param {String} sid selection id */ this.createSurface = function(surfaceType, sel, style, sid){ var selId = findSelectionBySpec(sel); if(selId == null){ selId = this.addSelection(); } this.ui.tools.selectionBox.editSelection(selId, sel, null); surfaceType = Object.keys(style)[0]; var surfaceInput = { surfaceType : { value : surfaceType }, surfaceStyle : { value : style[surfaceType], }, surfaceOf : { value : 'self' }, surfaceFor : { value : selId } } var surfId = makeid(4); surfaces[surfId] = sid; this.ui.tools.surfaceMenu.addSurface(surfId, surfaceInput); // Create Surface UI }; /** * Sets the value of title in ModelToolBar * @function $3Dmol.StateManager#setModelTitle * @param {String} title Model title */ this.setModelTitle = function(title){ this.ui.tools.modelToolBar.setModel(title); } canvas.on('click', ()=>{ if(this.ui && this.ui.tools.contextMenu.hidden == false){ this.ui.tools.contextMenu.hide(); } }); // Setting up UI generation /** * Generates the ui and returns its reference * @returns $3Dmol.UI */ this.showUI = function(){ var ui = new $3Dmol.UI(this, uiOverlayConfig, parentElement); return ui; }; if(config.ui == true){ this.ui = this.showUI(); } this.initiateUI = function(){ this.ui = new $3Dmol.UI(this, uiOverlayConfig, parentElement); render(); } /** * Updates the UI on viewport change * * @function $3Dmol.StateManager#updateUI */ this.updateUI = function(){ if(this.ui){ this.ui.resize(); } }; window.addEventListener("resize",this.updateUI.bind(this)); if (typeof (window.ResizeObserver) !== "undefined") { this.divwatcher = new window.ResizeObserver(this.updateUI.bind(this)); this.divwatcher.observe(glviewer.container); } // UI changes function render(){ // glviewer.(); glviewer.setStyle({}); let selList = Object.keys(selections); selList.forEach( (selKey) =>{ var sel = selections[selKey]; if( !sel.hidden ) { var styleList = Object.keys(sel.styles); styleList.forEach((styleKey)=>{ var style = sel.styles[styleKey]; if( !style.hidden){ glviewer.addStyle(sel.spec, style.spec); } }); glviewer.setClickable(sel.spec, true, ()=>{}); glviewer.enableContextMenu(sel.spec, true); } else { glviewer.setClickable(sel.spec, false, ()=>{}); glviewer.enableContextMenu(sel.spec, false); } }) glviewer.render(); } function makeid(length) { var result = ''; var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; var charactersLength = characters.length; for ( var i = 0; i < length; i++ ) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } } return States; })()