Comparing version 0.0.2 to 0.1.0
@@ -0,16 +1,44 @@ | ||
import 'babel-polyfill'; | ||
import Backbone from 'backbone'; | ||
import { MolWidget3DModel, MolWidget3DView } from '../../src/main.js'; | ||
const bipyridine = require('raw!./bipyridine.sdf'); | ||
import ExampleSettingsView from './example_settings_view'; | ||
// import modelData from './3aid_model_data'; | ||
// import styles from './3aid_styles'; | ||
import modelData from './bipyridine_model_data'; | ||
import styles from './bipyridine_styles'; | ||
import orbital from './orbital'; | ||
import selectionTypesConstants from '../../src/constants/selection_types_constants'; | ||
Backbone.sync = () => {}; | ||
// Set up nbmolviz3d | ||
const model = new MolWidget3DModel({ | ||
model_data: bipyridine, | ||
model_data_format: 'sdf', | ||
model_data: modelData, | ||
styles, | ||
shape: { | ||
type: 'Arrow', | ||
start: { | ||
x: 0, | ||
y: 0, | ||
z: -2.5, | ||
}, | ||
end: { | ||
x: 0, | ||
y: 0, | ||
z: 3, | ||
}, | ||
}, | ||
selection_type: selectionTypesConstants.ATOM, | ||
orbital, | ||
}); | ||
const view = new MolWidget3DView({ | ||
model, | ||
el: document.querySelector('.app'), | ||
el: document.querySelector('.nbmolviz3d'), | ||
}); | ||
view.render(); | ||
view.render(); | ||
// Set up example settings controls | ||
const settingsView = new ExampleSettingsView({ | ||
model, | ||
}); | ||
document.querySelector('.data').appendChild(settingsView.render().el); |
{ | ||
"name": "nbmolviz3d", | ||
"version": "0.0.2", | ||
"version": "0.1.0", | ||
"description": "3D molecule visualization", | ||
@@ -13,3 +13,4 @@ "main": "dist/bundle.js", | ||
"backbone": "^1.3.3", | ||
"jquery": "^3.1.0" | ||
"jquery": "^3.1.0", | ||
"keymirror": "^0.1.1" | ||
}, | ||
@@ -19,2 +20,3 @@ "devDependencies": { | ||
"babel-loader": "^6.2.4", | ||
"babel-polyfill": "^6.13.0", | ||
"babel-preset-es2015": "^6.6.0", | ||
@@ -27,2 +29,3 @@ "chai": "^3.5.0", | ||
"eslint-plugin-import": "^1.6.1", | ||
"inline-environment-variables-webpack-plugin": "^1.1.0", | ||
"karma": "^0.13.22", | ||
@@ -33,2 +36,3 @@ "karma-mocha": "^0.2.2", | ||
"mocha": "^2.4.5", | ||
"nightwatch": "^0.9.8", | ||
"node-sass": "^3.6.0", | ||
@@ -38,2 +42,3 @@ "phantomjs-prebuilt": "^2.1.7", | ||
"sass-loader": "^3.2.0", | ||
"selenium-binaries": "0.8.0", | ||
"sinon": "^2.0.0-pre.2", | ||
@@ -48,3 +53,4 @@ "style-loader": "^0.13.1", | ||
"watch": "webpack -p --config webpack.config.js --watch", | ||
"example": "webpack-dev-server --content-base example/ --config example/webpack.config.js --progress --colors", | ||
"example": "NODE_ENV=DEVELOPMENT webpack-dev-server --content-base example/ --config example/webpack.config.js --progress --colors", | ||
"e2e": "./node_modules/nightwatch/bin/nightwatch", | ||
"prepublish": "npm run build" | ||
@@ -51,0 +57,0 @@ }, |
@@ -29,2 +29,24 @@ # nbmolviz3d | ||
### model_data {Object} | ||
JSON data representing the actual molecular input. Of the form: | ||
{ | ||
atoms: [{ | ||
serial, | ||
name, | ||
elem, | ||
mass_magnitude, | ||
residue_index, | ||
esidue_name, | ||
chain, | ||
positions, | ||
momenta, | ||
}, ... ], | ||
bonds: [{ | ||
atom1_index, | ||
atom2_index, | ||
bond_order, | ||
}, ... ], | ||
} | ||
### background_color {String} ['#73757C'] | ||
@@ -36,8 +58,40 @@ The background color of the visualization. | ||
### model_data {String} | ||
The actual text of the molecule input file. | ||
### atom_labels_shown {Boolean} [false] | ||
Indicates whether or not to show text labels on all atoms. | ||
### model_data_format {String} | ||
The file format of `model_data`, for example `sdf`. | ||
### styles {Array of Objects} [[]] | ||
An array indicating how to style individual atoms. Atoms are indicated by index, so the first style in this array corresponds to the first atom in `model_data.atoms`. Of the form: | ||
[ | ||
{ | ||
visualization_type: 'stick'|'sphere'|'cartoon', | ||
color: '#abcdef', | ||
}, ... | ||
] | ||
### selected_atom_indices {Array of Numbers} [[]] | ||
An array of atom indices indicating which atoms should be visually selected. | ||
### selection_type {String} ['Atom'] | ||
A string indicating whether clicks select atoms ('Atom'), residues ('Residue'), or chains ('Chain'). | ||
### shape {Object} [{}] | ||
Indicates a shape to display in the visualization using 3Dmol.js's [addShape method](http://3dmol.csb.pitt.edu/doc/$3Dmol.GLViewer.html#addShape). For example: | ||
{ | ||
type: 'Sphere', | ||
x: 0, | ||
y: 0, | ||
z: 0, | ||
} | ||
### orbital {Object} [{}] | ||
Indicates an orbital to display using 3Dmol.js's [addIsosurface method](http://3dmol.csb.pitt.edu/doc/$3Dmol.GLViewer.html#addIsosurface). Of the type: | ||
{ | ||
cube_file, | ||
iso_val, | ||
opacity, | ||
} | ||
## Development | ||
@@ -44,0 +98,0 @@ A typical development flow might be to run the example while editing the code, where you'll want any changes to be immediately reflected in the example running in the browser. In that case you should run: |
@@ -17,26 +17,26 @@ /** | ||
import Backbone from 'backbone'; | ||
import selectionTypesConstants from '../constants/selection_types_constants'; | ||
const MolWidget3DModel = Backbone.Model.extend({ | ||
defaults: { | ||
_model_name: 'MolWidget3DModel', | ||
_view_name: 'MolWidget3DView', | ||
_model_module: 'nbmolviz-js', | ||
_view_module: 'nbmolviz-js', | ||
_width: '500px', | ||
_height: '500px', | ||
viewerId: '', | ||
click_selection: -1, | ||
background_color: '0x73757C', | ||
atom_labels_shown: false, | ||
background_color: '#73757C', | ||
background_opacity: 1.0, | ||
color: null, | ||
font_family: '', | ||
font_size: '', | ||
font_style: '', | ||
font_weight: '', | ||
layout: undefined, | ||
msg_throttle: 3, | ||
visible: true, | ||
model_data: '', | ||
model_data_format: '', | ||
visualization_style: 'stick', | ||
height: '500px', | ||
model_data: { atoms: [], bonds: [] }, | ||
orbital: { | ||
cube_file: '', | ||
iso_val: null, | ||
opacity: null, | ||
}, | ||
styles: [], | ||
selected_atom_indices: [], | ||
selection_type: selectionTypesConstants.ATOM, | ||
shape: { | ||
type: '', | ||
x: null, | ||
y: null, | ||
z: null, | ||
}, | ||
width: '500px', | ||
}, | ||
@@ -43,0 +43,0 @@ }); |
@@ -20,184 +20,33 @@ /** | ||
const $3Dmol = require('../vendor/3Dmol'); | ||
import environmentConstants from '../constants/environment_constants'; | ||
import libUtils from '../utils/lib_utils'; | ||
import moleculeUtils from '../utils/molecule_utils'; | ||
function processCubeFile(cubeData, uuid) { | ||
const volumeData = new $3Dmol.VolumeData(cubeData, 'cube'); | ||
this.pyObjects[uuid] = volumeData; | ||
} | ||
const DEFAULT_VISUALIZATION_TYPE = 'stick'; | ||
const DEFAULT_FONT_SIZE = 14; | ||
const ORBITAL_COLOR_POSITIVE = 0xff0000; | ||
const ORBITAL_COLOR_NEGATIVE = 0x0000ff; | ||
function batchCommands(commands) { | ||
const results = []; | ||
const viewer = this; | ||
commands.forEach((cmd) => { | ||
const fn = viewer[cmd[0]]; | ||
const args = cmd[1]; | ||
fn.apply(viewer, args); | ||
// results.push( fn.apply(viewer, args)); | ||
}); | ||
return results; // results are disabled because they sometimes lead to recursive JSON. | ||
} | ||
const MolWidget3DView = Backbone.View.extend({ | ||
initialize() { | ||
if (process.env.NODE_ENV === environmentConstants.DEVELOPMENT) { | ||
if (!window.nbmolviz3d) { | ||
window.nbmolviz3d = []; | ||
} | ||
function renderPyShape(shape, spec, uuid, clickable) { | ||
const newSpec = spec; | ||
if (clickable === true) { | ||
newSpec.clickable = true; | ||
newSpec.callback = this.widget.setSelectionTrait; | ||
} | ||
const newShape = this[`add${shape}`](newSpec); | ||
newShape.pyid = uuid; | ||
this.pyObjects[uuid] = newShape; | ||
} | ||
function removePyShape(shapeId) { | ||
const shape = this.pyObjects[shapeId]; | ||
this.removeShape(shape); | ||
} | ||
/* | ||
function drawBond(atom1, atom2, order, spec) { | ||
const newSpec = spec; | ||
newSpec.start = { | ||
x: atom1.x, | ||
y: atom1.y, | ||
z: atom1.z, | ||
}; | ||
newSpec.end = { | ||
x: atom1.x, | ||
y: atom1.y, | ||
z: atom1.z, | ||
}; | ||
} | ||
*/ | ||
function adjustClipping(minimum) { | ||
const slab = this.getSlab(); | ||
if (slab.near > -minimum) slab.near = -minimum; | ||
if (slab.far < minimum) slab.far = minimum; | ||
this.setSlab(slab.near, slab.far); | ||
} | ||
function setAtomColor(atomJson, color) { | ||
const atoms = this.selectedAtoms(atomJson); | ||
atoms.map((atom) => { | ||
const newAtom = atom; | ||
for (const s of Object.keys(newAtom.style)) { | ||
newAtom.style[s].color = color; | ||
window.nbmolviz3d.push(this); | ||
} | ||
return newAtom; | ||
}); | ||
this.forceRedraw(); | ||
} | ||
function unsetAtomColor(atomJSON) { | ||
const atoms = this.selectedAtoms(atomJSON); | ||
atoms.map((atom) => { | ||
const newAtom = atom; | ||
for (const s of Object.keys(newAtom.style)) { | ||
newAtom.style[s].color = undefined; | ||
} | ||
return newAtom; | ||
}); | ||
this.forceRedraw(); | ||
} | ||
function setColorArray(mapping) { | ||
const atoms = this.selectedAtoms(); | ||
for (const color of Object.keys(mapping)) { | ||
if (!mapping.hasOwnProperty(color)) continue; | ||
mapping[color].index.forEach((ind) => { // this is probably fragile | ||
const atom = atoms[ind]; | ||
if (atom.index !== ind) { | ||
throw new Error(`selectedAtoms()[${ind}].index != ${ind}`); | ||
} | ||
const style = atom.style; | ||
for (const s of Object.keys(style)) { | ||
if (style.hasOwnProperty(s)) { | ||
style[s].color = color; | ||
} | ||
} | ||
}); | ||
} | ||
this.forceRedraw(); | ||
} | ||
function renderPyLabel(text, spec, uuid) { | ||
const label = this.addLabel(text, spec); | ||
this.pyObjects[uuid] = label; | ||
} | ||
function removePyLabel(labelId) { | ||
const label = this.pyObjects[labelId]; | ||
this.removeLabel(label); | ||
} | ||
function drawIsosurface(dataId, shapeId, spec) { | ||
const data = this.pyObjects[dataId]; | ||
const shape = this.addIsosurface(data, spec); | ||
this.pyObjects[shapeId] = shape; | ||
} | ||
function addFrameFromList(positionList) { | ||
const oldatoms = this.selectedAtoms({}); | ||
const newatoms = []; | ||
for (let i = 0; i < oldatoms.length; i++) { | ||
const atom = jQuery.extend({}, oldatoms[i]); | ||
atom.x = positionList[i][0]; | ||
atom.y = positionList[i][1]; | ||
atom.z = positionList[i][2]; | ||
newatoms.push(atom); | ||
} | ||
const model = this.getModel(0); | ||
return model.addFrame(newatoms); | ||
} | ||
function setPositions(positionList) { | ||
const atoms = this.selectedAtoms(); | ||
for (let i = 0; i < atoms.length; i++) { | ||
const atom = atoms[i]; | ||
atom.x = positionList[i][0]; | ||
atom.y = positionList[i][1]; | ||
atom.z = positionList[i][2]; | ||
} | ||
this.forceRedraw(); | ||
} | ||
function forceRedraw() { | ||
// relies on adding the forceRedraw method | ||
this.getModel().forceRedraw(); | ||
} | ||
function makeAtomsClickable() { | ||
this.setClickable({}, true, this.widget.setSelectionTrait); | ||
} | ||
function setBonds(bonds) { | ||
const atoms = this.selectedAtoms(); | ||
bonds.forEach((bond) => { | ||
const a = atoms[bond.index]; | ||
a.bonds = bond.nbr; | ||
a.bondOrder = bond.order; | ||
}); | ||
} | ||
const MolWidget3DView = Backbone.View.extend({ | ||
initialize() { | ||
this.model.on('change', this.render.bind(this)); | ||
window.nbmolviz3d = this; | ||
}, | ||
render() { | ||
document.last_3d_widget = this; | ||
render(event) { | ||
const modelDataChanged = !event || Object.keys(event.changed).indexOf('model_data') !== -1; | ||
this.messages = []; | ||
this.viewerId = this.model.get('viewerId'); | ||
this.mydiv = this.mydiv || document.createElement('div'); | ||
this.mydiv.classList.add('nbmolviz3d'); | ||
this.mydiv.style.width = this.model.get('_width'); | ||
this.mydiv.style.height = this.model.get('_height'); | ||
this.mydiv.style.width = this.model.get('width'); | ||
this.mydiv.style.height = this.model.get('height'); | ||
this.mydiv.style.position = 'relative'; | ||
@@ -209,3 +58,3 @@ | ||
this.viewer = this.renderViewer(); | ||
this.glviewer = this.renderViewer(modelDataChanged); | ||
@@ -217,64 +66,111 @@ if (this.send) { | ||
renderViewer() { | ||
const glviewer = $3Dmol.viewers[this.viewerId] ||$3Dmol.createViewer(jQuery(this.mydiv), { | ||
renderViewer(modelDataChanged) { | ||
const glviewer = this.glviewer || $3Dmol.createViewer(jQuery(this.mydiv), { | ||
defaultcolors: $3Dmol.rasmolElementColors, | ||
}); | ||
if (typeof($3Dmol.widgets) === 'undefined') { | ||
$3Dmol.widgets = {}; | ||
} | ||
$3Dmol.viewers[this.viewerId] = glviewer; | ||
$3Dmol.widgets[this.viewerId] = this; | ||
$3Dmol.last_viewer = glviewer; | ||
$3Dmol.last_widget = this; | ||
// Maybe want to remove this monkeypatching some day ... | ||
glviewer.setColorArray = setColorArray; | ||
glviewer.processCubeFile = processCubeFile; | ||
glviewer.pyObjects = {}; | ||
glviewer.addFrameFromList = addFrameFromList; | ||
glviewer.drawIsosurface = drawIsosurface; | ||
glviewer.widget = this; | ||
glviewer.makeAtomsClickable = makeAtomsClickable; | ||
glviewer.renderPyShape = renderPyShape; | ||
glviewer.renderPyLabel = renderPyLabel; | ||
glviewer.removePyShape = removePyShape; | ||
glviewer.removePyLabel = removePyLabel; | ||
glviewer.setAtomColor = setAtomColor; | ||
glviewer.setPositions = setPositions; | ||
glviewer.forceRedraw = forceRedraw; | ||
glviewer.unsetAtomColor = unsetAtomColor; | ||
glviewer.batchCommands = batchCommands; | ||
glviewer.setBonds = setBonds; | ||
glviewer.adjustClipping = adjustClipping; | ||
document.last_3dmol_viewer = glviewer; // for debugging | ||
glviewer.clear(); | ||
const modelData = this.model.get('model_data'); | ||
if (!modelData) { | ||
// If no model data, just show a green sphere (the main 3dmol example) | ||
glviewer.addSphere({ radius: 10, color: 'green' }); | ||
} else { | ||
glviewer.addModel(modelData, this.model.get('model_data_format'), { | ||
if (modelData) { | ||
glviewer.addModel(moleculeUtils.modelDataToCDJSON(modelData), 'json', { | ||
keepH: true, | ||
}); | ||
// Hack in chain and residue data, since it's not supported by chemdoodle json | ||
glviewer.getModel().selectedAtoms().forEach((atom) => { | ||
const modifiedAtom = atom; | ||
modifiedAtom.atom = modelData.atoms[atom.serial].name; | ||
modifiedAtom.chain = modelData.atoms[atom.serial].chain; | ||
modifiedAtom.resi = modelData.atoms[atom.serial].residue_index; | ||
modifiedAtom.resn = modelData.atoms[atom.serial].residue_name; | ||
}); | ||
} | ||
glviewer.setStyle({}, { [this.model.get('visualization_style')]: {} }); | ||
glviewer.setBackgroundColor(this.model.get('background_color'), this.model.get('background_opacity')); | ||
glviewer.zoomTo(); | ||
glviewer.makeAtomsClickable(); | ||
const styles = this.model.get('styles'); | ||
modelData.atoms.forEach((atom, i) => { | ||
const style = styles[i] || {}; | ||
const libStyle = {}; | ||
const visualizationType = style.visualization_type || DEFAULT_VISUALIZATION_TYPE; | ||
libStyle[visualizationType] = {}; | ||
Object.keys(style).forEach((styleKey) => { | ||
libStyle[visualizationType][styleKey] = style[styleKey]; | ||
}); | ||
if (this.model.get('selected_atom_indices').indexOf(atom.serial) !== -1) { | ||
libStyle[visualizationType].color = 0x1FF3FE; | ||
} | ||
if (typeof libStyle[visualizationType].color === 'string') { | ||
libStyle[visualizationType].color = libUtils.colorStringToNumber( | ||
libStyle[visualizationType].color | ||
); | ||
} | ||
if (this.model.get('atom_labels_shown')) { | ||
glviewer.addLabel(atom.name, { | ||
fontSize: DEFAULT_FONT_SIZE, | ||
position: { | ||
x: atom.positions[0], | ||
y: atom.positions[1], | ||
z: atom.positions[2], | ||
}, | ||
}); | ||
} | ||
glviewer.setStyle({ serial: atom.serial }, libStyle); | ||
}); | ||
// Shape | ||
const shape = this.model.get('shape'); | ||
if (shape.type) { | ||
glviewer[`add${shape.type}`](libUtils.getShapeSpec(shape, this.setSelectionTrait)); | ||
} | ||
// Orbital | ||
const orbital = this.model.get('orbital'); | ||
if (orbital.cube_file) { | ||
const volumeData = new $3Dmol.VolumeData(orbital.cube_file, 'cube'); | ||
glviewer.addIsosurface(volumeData, { | ||
isoVal: orbital.iso_val, | ||
color: ORBITAL_COLOR_POSITIVE, | ||
opacity: orbital.opacity, | ||
}); | ||
glviewer.addIsosurface(volumeData, { | ||
isoVal: -orbital.iso_val, | ||
color: ORBITAL_COLOR_NEGATIVE, | ||
opacity: orbital.opacity, | ||
}); | ||
} | ||
glviewer.setBackgroundColor( | ||
libUtils.colorStringToNumber(this.model.get('background_color')), | ||
this.model.get('background_opacity') | ||
); | ||
glviewer.setClickable({}, true, this.onClick.bind(this)); | ||
glviewer.render(); | ||
glviewer.zoom(0.8, 2000); | ||
if (modelDataChanged) { | ||
glviewer.zoomTo(); | ||
glviewer.zoom(0.8, 2000); | ||
} | ||
return glviewer; | ||
}, | ||
setSelectionTrait() { | ||
const result = { | ||
model: this.model, | ||
index: this.index, | ||
serial: this.serial, | ||
pyid: this.pyid, | ||
}; | ||
this.model.set('_click_selection', result); | ||
onClick(glAtom) { | ||
const atoms = this.model.get('model_data').atoms; | ||
const atom = atoms[glAtom.serial]; | ||
const selectionType = this.model.get('selection_type'); | ||
const selectedAtomIndices = this.model.get('selected_atom_indices'); | ||
const newSelectedAtomIndices = moleculeUtils.addSelection( | ||
atoms, | ||
selectedAtomIndices, | ||
atom, | ||
selectionType | ||
); | ||
this.model.set('selected_atom_indices', newSelectedAtomIndices); | ||
this.model.save(); | ||
@@ -281,0 +177,0 @@ }, |
@@ -0,1 +1,2 @@ | ||
const InlineEnvironmentVariablesPlugin = require('inline-environment-variables-webpack-plugin'); | ||
const path = require('path'); | ||
@@ -34,3 +35,6 @@ | ||
}, | ||
plugins: [ | ||
new InlineEnvironmentVariablesPlugin(), | ||
], | ||
devtool: 'source-map', | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
1669992
61
25660
124
3
26
3
+ Addedkeymirror@^0.1.1
+ Addedkeymirror@0.1.1(transitive)