├── example ├── img │ └── .gitignore ├── .gitattributes ├── tile.png ├── favicon.ico ├── tile-wide.png ├── css │ ├── home.css │ ├── main.css │ └── normalize.css ├── robots.txt ├── apple-touch-icon.png ├── .gitignore ├── .editorconfig ├── browserconfig.xml ├── crossdomain.xml ├── webpack.config.js ├── js │ ├── plugins.js │ ├── bipyridine_styles.js │ ├── bipyridine.sdf │ ├── main.jsx │ ├── settings.jsx │ ├── bipyridine_model_data.js │ └── vendor │ │ └── modernizr-2.8.3.min.js ├── index.html └── .htaccess ├── doc ├── close_screenshot.png └── example_screenshot.png ├── .babelrc ├── test ├── e2e │ ├── fixtures │ │ └── setup.js │ └── specs │ │ └── startup_spec.js ├── fixtures │ └── factories.js ├── utils │ ├── lib_utils_spec.js │ └── molecule_utils_spec.js └── components │ └── molecule_3d_spec.js ├── .gitignore ├── src ├── constants │ ├── shape_constants.js │ ├── selection_types_constants.js │ └── environment_constants.js ├── main.js ├── utils │ ├── lib_utils.js │ └── molecule_utils.js └── components │ └── molecule_3d.jsx ├── .eslintignore ├── .eslintrc ├── scripts └── download_selenium.js ├── .travis.yml ├── webpack.config.js ├── nightwatch.conf.js ├── package.json ├── karma.conf.js ├── README.md └── LICENSE.md /example/img/.gitignore: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /example/.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /example/tile.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/example/tile.png -------------------------------------------------------------------------------- /example/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/example/favicon.ico -------------------------------------------------------------------------------- /example/tile-wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/example/tile-wide.png -------------------------------------------------------------------------------- /example/css/home.css: -------------------------------------------------------------------------------- 1 | .container { 2 | display: flex; 3 | } 4 | 5 | .data { 6 | margin-left: 4rem; 7 | } 8 | -------------------------------------------------------------------------------- /doc/close_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/doc/close_screenshot.png -------------------------------------------------------------------------------- /doc/example_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/doc/example_screenshot.png -------------------------------------------------------------------------------- /example/robots.txt: -------------------------------------------------------------------------------- 1 | # www.robotstxt.org/ 2 | 3 | # Allow crawling of all content 4 | User-agent: * 5 | Disallow: 6 | -------------------------------------------------------------------------------- /example/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Autodesk/molecule-3d-for-react/HEAD/example/apple-touch-icon.png -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | "es2015", 4 | "es2016", 5 | "es2017", 6 | "stage-2", 7 | "react", 8 | ] 9 | } 10 | 11 | -------------------------------------------------------------------------------- /test/e2e/fixtures/setup.js: -------------------------------------------------------------------------------- 1 | module.exports = function setup(browser) { 2 | browser.windowSize('current', 1700, 1100); 3 | 4 | return browser; 5 | }; 6 | -------------------------------------------------------------------------------- /example/.gitignore: -------------------------------------------------------------------------------- 1 | # Include your project-specific ignores in this file 2 | # Read about how to use .gitignore: https://help.github.com/articles/ignoring-files 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | node_modules/ 3 | npm-debug.log 4 | reports/ 5 | selenium-debug.log 6 | screenshots/ 7 | .DS_Store 8 | phantomjsdriver.log 9 | .idea 10 | -------------------------------------------------------------------------------- /src/constants/shape_constants.js: -------------------------------------------------------------------------------- 1 | const shapeConstants = { 2 | ARROW: 'Arrow', 3 | SPHERE: 'Sphere', 4 | CYLINDER: 'Cylinder', 5 | }; 6 | 7 | export default shapeConstants; 8 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | example/js/bipyridine_model_data.js 2 | example/js/bipyridine_styles.js 3 | example/js/3aid_model_data.js 4 | example/js/3aid_styles.js 5 | example/js/orbital.js 6 | example/js/1kbu.js 7 | -------------------------------------------------------------------------------- /src/constants/selection_types_constants.js: -------------------------------------------------------------------------------- 1 | const selectionTypeConstants = { 2 | ATOM: 'Atom', 3 | RESIDUE: 'Residue', 4 | CHAIN: 'Chain', 5 | }; 6 | 7 | export default selectionTypeConstants; 8 | -------------------------------------------------------------------------------- /src/constants/environment_constants.js: -------------------------------------------------------------------------------- 1 | import keyMirror from 'keymirror'; 2 | 3 | const environmentConstants = keyMirror({ 4 | DEVELOPMENT: null, 5 | }); 6 | 7 | export default environmentConstants; 8 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "airbnb", 3 | "rules": { 4 | "react/jsx-no-bind": 0, 5 | "react/no-unused-prop-types": 1, 6 | }, 7 | "parser": "babel-eslint", 8 | "globals": { 9 | "window": true, 10 | "document": true, 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /example/.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | indent_size = 4 8 | indent_style = space 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | 12 | [*.md] 13 | trim_trailing_whitespace = false 14 | -------------------------------------------------------------------------------- /test/e2e/specs/startup_spec.js: -------------------------------------------------------------------------------- 1 | const setup = require('../fixtures/setup'); 2 | 3 | module.exports = { 4 | 'Startup Test': (browser) => { 5 | setup(browser) 6 | .url(browser.launchUrl) 7 | .waitForElementVisible('.molecule-3d', 1000, 'molecule-3d canvas element appears') 8 | .end(); 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /example/browserconfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /example/crossdomain.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /scripts/download_selenium.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const seleniumDownload = require('selenium-download'); 3 | 4 | const BINPATH = './node_modules/nightwatch/bin/'; 5 | 6 | /** 7 | * selenium-download does exactly what it's name suggests; 8 | * downloads (or updates) the version of Selenium (& chromedriver) 9 | * on your localhost where it will be used by Nightwatch. 10 | */ 11 | fs.stat(`${BINPATH}selenium.jar`, (err, stat) => { 12 | if (err || !stat || stat.size < 1) { 13 | seleniumDownload.ensure(BINPATH, (error) => { 14 | if (error) throw new Error(error); 15 | console.log('✔ Selenium & Chromedriver downloaded to:', BINPATH); 16 | }); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright 2016 Autodesk Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import Molecule3d from './components/molecule_3d.jsx'; 17 | 18 | export default Molecule3d; 19 | -------------------------------------------------------------------------------- /test/fixtures/factories.js: -------------------------------------------------------------------------------- 1 | const factories = { 2 | // 3dMol's glViewer class 3 | getGlViewer() { 4 | let model = null; 5 | return { 6 | addLabel: () => {}, 7 | addModel: () => { 8 | model = { 9 | selectedAtoms: () => [], 10 | }; 11 | }, 12 | clear: () => { 13 | model = null; 14 | }, 15 | fitSlab: () => {}, 16 | getModel: () => model, 17 | removeAllLabels: () => {}, 18 | removeAllShapes: () => {}, 19 | render: () => {}, 20 | setBackgroundColor: () => {}, 21 | setClickable: () => {}, 22 | setStyle: () => {}, 23 | setViewStyle: () => {}, 24 | zoom: () => {}, 25 | zoomTo: () => {}, 26 | }; 27 | }, 28 | }; 29 | 30 | export default factories; 31 | -------------------------------------------------------------------------------- /example/webpack.config.js: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | 3 | module.exports = { 4 | entry: { 5 | example: ['./example/js/main.jsx'], 6 | }, 7 | output: { 8 | path: path.resolve(__dirname, 'dist'), 9 | publicPath: '/js/', 10 | filename: 'bundle.[name].js', 11 | }, 12 | devServer: { 13 | port: '4000', 14 | hot: true, 15 | }, 16 | module: { 17 | loaders: [ 18 | { 19 | test: /\.jsx?$/, 20 | exclude: /(node_modules|bower_components)/, 21 | loader: 'babel', 22 | }, { 23 | test: /\.jsx?$/, 24 | exclude: /(node_modules|bower_components)/, 25 | loader: 'eslint-loader', 26 | }, { 27 | test: /\.scss$/, 28 | include: /example\/css/, 29 | loaders: ['style', 'css', 'sass'], 30 | }, 31 | ], 32 | }, 33 | devtool: 'source-map', 34 | }; 35 | -------------------------------------------------------------------------------- /example/js/plugins.js: -------------------------------------------------------------------------------- 1 | // Avoid `console` errors in browsers that lack a console. 2 | (function() { 3 | var method; 4 | var noop = function () {}; 5 | var methods = [ 6 | 'assert', 'clear', 'count', 'debug', 'dir', 'dirxml', 'error', 7 | 'exception', 'group', 'groupCollapsed', 'groupEnd', 'info', 'log', 8 | 'markTimeline', 'profile', 'profileEnd', 'table', 'time', 'timeEnd', 9 | 'timeline', 'timelineEnd', 'timeStamp', 'trace', 'warn' 10 | ]; 11 | var length = methods.length; 12 | var console = (window.console = window.console || {}); 13 | 14 | while (length--) { 15 | method = methods[length]; 16 | 17 | // Only stub undefined methods. 18 | if (!console[method]) { 19 | console[method] = noop; 20 | } 21 | } 22 | }()); 23 | 24 | // Place any jQuery/helper plugins in here. 25 | -------------------------------------------------------------------------------- /test/utils/lib_utils_spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before, after, beforeEach, afterEach */ 2 | 3 | import { expect } from 'chai'; 4 | import libUtils from '../../src/utils/lib_utils'; 5 | 6 | describe('libUtils', () => { 7 | describe('colorStringToNumber', () => { 8 | describe('when given invalid input', () => { 9 | it('returns the original input', () => { 10 | expect(libUtils.colorStringToNumber('blue')).to.equal('blue'); 11 | expect(libUtils.colorStringToNumber('abcdef')).to.equal('abcdef'); 12 | expect(libUtils.colorStringToNumber('#!bcdef')).to.equal('#!bcdef'); 13 | }); 14 | }); 15 | 16 | describe('when given a valid hash color', () => { 17 | it('returns the Number representation', () => { 18 | expect(libUtils.colorStringToNumber('#000000')).to.equal(0); 19 | expect(libUtils.colorStringToNumber('#ffffff')).to.equal(16777215); 20 | expect(libUtils.colorStringToNumber('#abcdef')).to.equal(0xabcdef); 21 | expect(libUtils.colorStringToNumber('#bada55')).to.equal(0xbada55); 22 | }); 23 | }); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 6.4.0 4 | addons: 5 | apt: 6 | packages: 7 | - oracle-java8-set-default 8 | script: 9 | - npm test 10 | - java -version 11 | - npm run setup-selenium 12 | - npm run example & 13 | - npm run e2e 14 | deploy: 15 | provider: npm 16 | email: justinjmccandless@gmail.com 17 | api_key: 18 | secure: cQQ7nxbqZEPBjd8iRLh8gQGTW9Ueh/c2MU/pVkJsaCA3aM90kJF/ekAJI0NAeipsT1vEXPs554vMK8KjTgNCbrklf8hdxj36sMmJPWR8/+yXdsSrPXCLurpemjhAQCtIdFteB13k62OREE8QD9IwQjqKRH63UJH8pIjmAN5VJe6t9mpaNP8lQfR+CTJvfgNLJcWuabFF3tXJ4BJB0aJYfeUPT86qoTY2vQPtPKOTC1urZx/+u4O0vawKYCJizxk5+NAVDndvBh5q0qf98OKeceRs7tdvhuQsCeN0OsM93oqbOsJ+sfO1xIuQ3aRbd+xWF3GE6kJH9mMryZ6uD+K+EwM12AoaFMFrPoV1RycnzHmN3cXr2BeFbVza4DTHCubfF0yRf2277ykOyTnXGcAy0ntKy+pj/V5Ftrl7pCikDEXBhppohEp/vaWaGUCPb6VhU4T5W5kdwt4GAD01D5q5LPJgIkxfjqzixixrIw0CkmNb7vOdKnuQoadC4i7YCf3MeMZnEqgB2EJTlCezQL7qZVJhRYNRL0uPaQ7g7WH30C77lhr3FfJc2q7gOVulv1XbZkHh2YF2hSTE/FuV4cgM/7tl8xABppKOqxuRk8DH7oXv6iFan+XzCKQkV6TcnWd1TweJPOiJhkF8S24RbUgAQDrPHsr76fBEntepfvZonLY= 19 | skip_cleanup: true 20 | on: 21 | tags: true 22 | repo: Autodesk/molecule-3d-for-react 23 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const InlineEnvironmentVariablesPlugin = require('inline-environment-variables-webpack-plugin'); 2 | const path = require('path'); 3 | 4 | module.exports = { 5 | entry: { 6 | app: ['./src/main.js'], 7 | }, 8 | output: { 9 | path: path.resolve(__dirname, 'dist'), 10 | publicPath: '/js/', 11 | filename: 'bundle.js', 12 | library: true, 13 | libraryTarget: 'commonjs2', 14 | }, 15 | externals: [ 16 | /^[a-z\-0-9]+$/, 17 | ], 18 | module: { 19 | loaders: [ 20 | { 21 | test: /\.jsx?$/, 22 | exclude: /(node_modules|bower_components)/, 23 | loader: 'babel', 24 | }, { 25 | test: /\.jsx?$/, 26 | exclude: /(node_modules|bower_components)/, 27 | loader: 'eslint-loader', 28 | }, { 29 | test: /\.scss$/, 30 | include: /example\/css/, 31 | loaders: ['style', 'css', 'sass'], 32 | }, 33 | { 34 | test: /\.json$/, 35 | loader: 'json-loader', 36 | }, 37 | ], 38 | }, 39 | plugins: [ 40 | new InlineEnvironmentVariablesPlugin(), 41 | ], 42 | devtool: 'source-map', 43 | }; 44 | -------------------------------------------------------------------------------- /nightwatch.conf.js: -------------------------------------------------------------------------------- 1 | const BINPATH = './node_modules/nightwatch/bin/'; 2 | 3 | module.exports = { 4 | src_folders: [ 5 | 'test/e2e', // Where you are storing your Nightwatch e2e/UAT tests 6 | ], 7 | output_folder: './reports', // reports (test outcome) output by nightwatch 8 | selenium: { // downloaded by selenium-download module (see readme) 9 | start_process: true, // tells nightwatch to start/stop the selenium process 10 | server_path: `${BINPATH}selenium.jar`, 11 | log_path: '', 12 | host: '127.0.0.1', 13 | port: 4444, // standard selenium port 14 | }, 15 | test_settings: { 16 | default: { 17 | screenshots: { 18 | enabled: false, 19 | path: './screenshots', 20 | }, 21 | globals: { 22 | waitForConditionTimeout: 5000, // sometimes internet is slow so wait. 23 | }, 24 | launch_url: 'http://localhost:4000', 25 | desiredCapabilities: { 26 | browserName: 'phantomjs', 27 | javascriptEnabled: true, 28 | acceptSslCerts: true, 29 | 'phantomjs.binary.path': './node_modules/.bin/phantomjs', 30 | 'phantomjs.cli.args': [], 31 | }, 32 | }, 33 | }, 34 | }; 35 | -------------------------------------------------------------------------------- /example/js/bipyridine_styles.js: -------------------------------------------------------------------------------- 1 | export default { 2 | 0: { visualization_type: 'stick', color: '#fedcba' }, 3 | 1: { visualization_type: 'stick', color: '#abcdef' }, 4 | 2: { visualization_type: 'stick', color: '#abcdef' }, 5 | 3: { visualization_type: 'stick', color: '#abcdef' }, 6 | 4: { visualization_type: 'stick', color: '#abcdef' }, 7 | 5: { visualization_type: 'stick', color: '#abcdef' }, 8 | 6: { visualization_type: 'stick', color: '#abcdef' }, 9 | 7: { visualization_type: 'stick', color: '#fedcba' }, 10 | 8: { visualization_type: 'stick', color: '#abcdef' }, 11 | 9: { visualization_type: 'stick', color: '#abcdef' }, 12 | 10: { visualization_type: 'stick', color: '#abcdef' }, 13 | 11: { visualization_type: 'stick', color: '#abcdef' }, 14 | 12: { visualization_type: 'stick', color: '#bada55' }, 15 | 13: { visualization_type: 'stick', color: '#bada55' }, 16 | 14: { visualization_type: 'stick', color: '#bada55' }, 17 | 15: { visualization_type: 'stick', color: '#bada55' }, 18 | 16: { visualization_type: 'stick', color: '#bada55' }, 19 | 17: { visualization_type: 'stick', color: '#bada55' }, 20 | 18: { visualization_type: 'stick', color: '#bada55' }, 21 | 19: { visualization_type: 'stick', color: '#bada55' }, 22 | }; 23 | -------------------------------------------------------------------------------- /example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 24 |
25 |

molecule-3d-for-react

26 |
27 |
28 |
29 | 30 | 31 | 32 | 33 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /example/js/bipyridine.sdf: -------------------------------------------------------------------------------- 1 | 2 | OpenBabel08091616573D 3 | 4 | 20 21 0 0 0 0 0 0 0 0999 V2000 5 | -0.7015 -1.2104 0.0598 N 0 0 0 0 0 0 0 0 0 0 0 0 6 | -1.4095 -0.0497 0.0357 C 0 0 0 0 0 0 0 0 0 0 0 0 7 | -0.7311 1.1783 0.0646 C 0 0 0 0 0 0 0 0 0 0 0 0 8 | 0.6605 1.2269 -0.0027 C 0 0 0 0 0 0 0 0 0 0 0 0 9 | 1.3724 0.0469 -0.0163 C 0 0 0 0 0 0 0 0 0 0 0 0 10 | 0.6575 -1.1366 0.0579 C 0 0 0 0 0 0 0 0 0 0 0 0 11 | -2.9039 -0.1126 -0.1283 C 0 0 0 0 0 0 0 0 0 0 0 0 12 | -3.5029 -1.3283 -0.2388 N 0 0 0 0 0 0 0 0 0 0 0 0 13 | -4.8314 -1.3682 -0.5322 C 0 0 0 0 0 0 0 0 0 0 0 0 14 | -5.6084 -0.2481 -0.7751 C 0 0 0 0 0 0 0 0 0 0 0 0 15 | -5.0146 0.9870 -0.6303 C 0 0 0 0 0 0 0 0 0 0 0 0 16 | -3.6706 1.0545 -0.2661 C 0 0 0 0 0 0 0 0 0 0 0 0 17 | -1.2549 2.1214 0.1109 H 0 0 0 0 0 0 0 0 0 0 0 0 18 | 1.1641 2.1886 -0.0484 H 0 0 0 0 0 0 0 0 0 0 0 0 19 | 2.4567 0.0509 -0.0673 H 0 0 0 0 0 0 0 0 0 0 0 0 20 | 1.1771 -2.0879 0.1185 H 0 0 0 0 0 0 0 0 0 0 0 0 21 | -5.2721 -2.3596 -0.5697 H 0 0 0 0 0 0 0 0 0 0 0 0 22 | -6.6538 -0.3342 -1.0547 H 0 0 0 0 0 0 0 0 0 0 0 0 23 | -5.5730 1.9036 -0.7995 H 0 0 0 0 0 0 0 0 0 0 0 0 24 | -3.2478 2.0379 -0.1250 H 0 0 0 0 0 0 0 0 0 0 0 0 25 | 2 1 2 0 0 0 0 26 | 3 2 1 0 0 0 0 27 | 4 3 2 0 0 0 0 28 | 5 4 1 0 0 0 0 29 | 6 1 1 0 0 0 0 30 | 6 5 2 0 0 0 0 31 | 7 2 1 0 0 0 0 32 | 8 7 2 0 0 0 0 33 | 9 8 1 0 0 0 0 34 | 10 9 2 0 0 0 0 35 | 11 10 1 0 0 0 0 36 | 12 7 1 0 0 0 0 37 | 12 11 2 0 0 0 0 38 | 13 3 1 0 0 0 0 39 | 14 4 1 0 0 0 0 40 | 15 5 1 0 0 0 0 41 | 16 6 1 0 0 0 0 42 | 17 9 1 0 0 0 0 43 | 18 10 1 0 0 0 0 44 | 19 11 1 0 0 0 0 45 | 20 12 1 0 0 0 0 46 | M END 47 | $$$$ 48 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "molecule-3d-for-react", 3 | "version": "0.4.5", 4 | "description": "3D molecule visualization with React and 3Dmol.js", 5 | "engines": { 6 | "node": "6.9.1" 7 | }, 8 | "main": "dist/bundle.js", 9 | "files": [ 10 | "dist/" 11 | ], 12 | "keywords": [ 13 | "molecule", 14 | "react", 15 | "chemical", 16 | "visualization", 17 | "3d" 18 | ], 19 | "dependencies": { 20 | "3dmol": "^1.0.10", 21 | "babel-polyfill": "^6.13.0", 22 | "immutable": "^3.8.1", 23 | "jquery": "^3.1.0", 24 | "keymirror": "^0.1.1", 25 | "react": "^15.3.2", 26 | "react-dom": "^15.3.2" 27 | }, 28 | "devDependencies": { 29 | "babel-core": "^6.7.7", 30 | "babel-eslint": "^7.0.0", 31 | "babel-loader": "^6.2.4", 32 | "babel-preset-es2015": "^6.16.0", 33 | "babel-preset-es2016": "^6.16.0", 34 | "babel-preset-es2017": "^6.16.0", 35 | "babel-preset-react": "^6.11.1", 36 | "babel-preset-stage-2": "^6.17.0", 37 | "chai": "^3.5.0", 38 | "css-loader": "^0.23.1", 39 | "eslint": "^3.5.0", 40 | "eslint-config-airbnb": "^11.1.0", 41 | "eslint-loader": "^1.3.0", 42 | "eslint-plugin-import": "^1.15.0", 43 | "eslint-plugin-jsx-a11y": "^2.2.2", 44 | "eslint-plugin-react": "^6.2.2", 45 | "inline-environment-variables-webpack-plugin": "^1.1.0", 46 | "karma": "^0.13.22", 47 | "karma-mocha": "^0.2.2", 48 | "karma-phantomjs-launcher": "^1.0.0", 49 | "karma-webpack": "^1.7.0", 50 | "mocha": "^2.4.5", 51 | "nightwatch": "^0.9.8", 52 | "node-sass": "^3.6.0", 53 | "phantomjs": "^2.1.7", 54 | "phantomjs-prebuilt": "^2.1.7", 55 | "raw-loader": "^0.5.1", 56 | "react-addons-test-utils": "^15.3.2", 57 | "sass-loader": "^3.2.0", 58 | "selenium-download": "^2.0.10", 59 | "sinon": "^2.0.0-pre.2", 60 | "style-loader": "^0.13.1", 61 | "webpack": "^1.13.2", 62 | "webpack-dev-server": "^1.16.2", 63 | "enzyme": "^2.7.1", 64 | "json-loader": "^0.5.4" 65 | }, 66 | "scripts": { 67 | "test": "karma start --single-run", 68 | "tdd": "karma start", 69 | "build": "webpack -p --config webpack.config.js", 70 | "watch": "webpack -p --config webpack.config.js --watch", 71 | "example": "NODE_ENV=DEVELOPMENT webpack-dev-server --content-base example/ --config example/webpack.config.js --progress --colors", 72 | "e2e": "./node_modules/nightwatch/bin/nightwatch", 73 | "setup-selenium": "node scripts/download_selenium.js", 74 | "prepublish": "npm run build" 75 | }, 76 | "author": "Autodesk Bio/Nano", 77 | "license": "Apache 2.0", 78 | "repository": { 79 | "type": "git", 80 | "url": "https://github.com/Autodesk/molecule-3d-for-react.git" 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | // Karma configuration 2 | // Generated on Sat Apr 30 2016 13:33:59 GMT-0700 (PDT) 3 | 4 | module.exports = function(config) { 5 | config.set({ 6 | 7 | // base path that will be used to resolve all patterns (eg. files, exclude) 8 | basePath: '', 9 | 10 | 11 | // frameworks to use 12 | // available frameworks: https://npmjs.org/browse/keyword/karma-adapter 13 | frameworks: ['mocha'], 14 | 15 | 16 | // list of files / patterns to load in the browser 17 | files: [ 18 | 'node_modules/babel-polyfill/dist/polyfill.min.js', 19 | 'test/**/*_spec.js' 20 | ], 21 | 22 | 23 | // list of files to exclude 24 | exclude: [ 25 | ], 26 | 27 | 28 | // preprocess matching files before serving them to the browser 29 | // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor 30 | preprocessors: { 31 | 'test/*_spec.js': ['webpack'], 32 | 'test/**/*_spec.js': ['webpack'] 33 | }, 34 | 35 | 36 | webpack: { 37 | externals: { 38 | "react/lib/ExecutionEnvironment": true, 39 | "react/lib/ReactContext": true, 40 | "react/addons": true 41 | }, 42 | resolve: { 43 | extensions: ['', '.js', '.jsx'], 44 | }, 45 | module: { 46 | loaders: [{ 47 | test: /\.(js|jsx)$/, exclude: /(bower_components|node_modules)/, 48 | loader: 'babel-loader' 49 | }, { 50 | test: /\.scss$/, 51 | include: /example\/css/, 52 | loaders: ['style', 'css', 'sass'], 53 | }, { 54 | test: /\.json$/, 55 | loader: 'json', 56 | }] 57 | } 58 | }, 59 | 60 | 61 | // test results reporter to use 62 | // possible values: 'dots', 'progress' 63 | // available reporters: https://npmjs.org/browse/keyword/karma-reporter 64 | reporters: ['progress'], 65 | 66 | 67 | // web server port 68 | port: 9876, 69 | 70 | 71 | // enable / disable colors in the output (reporters and logs) 72 | colors: true, 73 | 74 | 75 | // level of logging 76 | // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG 77 | logLevel: config.LOG_INFO, 78 | 79 | 80 | // enable / disable watching file and executing tests whenever any file changes 81 | autoWatch: true, 82 | 83 | 84 | // start these browsers 85 | // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher 86 | browsers: ['PhantomJS'], 87 | 88 | 89 | // Continuous Integration mode 90 | // if true, Karma captures browsers, runs the tests and exits 91 | singleRun: false, 92 | 93 | // Concurrency level 94 | // how many browser should be started simultaneous 95 | concurrency: Infinity 96 | }) 97 | } 98 | -------------------------------------------------------------------------------- /src/utils/lib_utils.js: -------------------------------------------------------------------------------- 1 | import shapeConstants from '../constants/shape_constants'; 2 | 3 | const DEFAULT_VISUALIZATION_TYPE = 'stick'; 4 | 5 | /** 6 | * Utils for working with the 3dmol.js library 7 | */ 8 | const libUtils = { 9 | /** 10 | * Given a color string (like #abcdef), return its Number representation 11 | * If invalid input given, return the input 12 | * @param colorString {String} 13 | * @returns {Number} 14 | */ 15 | colorStringToNumber(colorString) { 16 | if (colorString.length !== 4 && colorString.length !== 7) { 17 | return colorString; 18 | } 19 | if (colorString[0] !== '#') { 20 | return colorString; 21 | } 22 | 23 | const colorInt = parseInt(colorString.substr(1, colorString.length - 1), 16); 24 | 25 | if (isNaN(colorInt)) { 26 | return colorString; 27 | } 28 | 29 | return colorInt; 30 | }, 31 | 32 | /** 33 | * Given a shape object from the main model, return a shape spec ready to go into 3Dmol.js 34 | * @param shape {Object} 35 | * @returns {Object} 36 | */ 37 | getShapeSpec(shape, callback) { 38 | let color; 39 | if (shape.color) { 40 | color = libUtils.colorStringToNumber(shape.color); 41 | } 42 | 43 | const shapeSpec = Object.assign({}, { 44 | alpha: 0.8, 45 | callback, 46 | clickable: false, 47 | color: 0x00FE03, 48 | radius: shape.radius, 49 | }, shape, { color }); 50 | 51 | if (shape.type === shapeConstants.ARROW) { 52 | shapeSpec.start = shape.start; 53 | shapeSpec.end = shape.end; 54 | } else if (shape.type === shapeConstants.SPHERE) { 55 | shapeSpec.center = shape.center; 56 | } else if (shape.type === shapeConstants.CYLINDER) { 57 | shapeSpec.fromCap = true; 58 | shapeSpec.toCap = true; 59 | shapeSpec.start = shape.start; 60 | shapeSpec.end = shape.end; 61 | } else { 62 | throw new Error('Invalid shape type.'); 63 | } 64 | 65 | return shapeSpec; 66 | }, 67 | 68 | /** 69 | * Get the style object needed by 3dMol for the given atom 70 | * @param atom {Object} 71 | * @returns {Object} 72 | */ 73 | getLibStyle(atom, selected, atomLabelsShown, style = {}) { 74 | const libStyle = {}; 75 | const visualizationType = style.visualization_type || DEFAULT_VISUALIZATION_TYPE; 76 | 77 | libStyle[visualizationType] = {}; 78 | Object.entries(style).forEach(([styleKey, styleValue]) => { 79 | if (styleKey !== 'visualization_type') { 80 | libStyle[visualizationType][styleKey] = styleValue; 81 | } 82 | }); 83 | 84 | if (selected) { 85 | libStyle[visualizationType].color = 0x1FF3FE; 86 | } 87 | 88 | if (typeof libStyle[visualizationType].color === 'string') { 89 | libStyle[visualizationType].color = libUtils.colorStringToNumber( 90 | libStyle[visualizationType].color 91 | ); 92 | } 93 | 94 | return libStyle; 95 | }, 96 | }; 97 | 98 | export default libUtils; 99 | -------------------------------------------------------------------------------- /example/js/main.jsx: -------------------------------------------------------------------------------- 1 | import 'babel-polyfill'; 2 | import React from 'react'; 3 | import { render } from 'react-dom'; 4 | import Molecule3d from '../../src/main.js'; 5 | import Settings from './settings.jsx'; 6 | // import modelData from './1kbu'; 7 | // import modelData from './3aid_model_data'; 8 | // import styles from './3aid_styles'; 9 | // import modelData from './dna_model_data'; 10 | import modelData from './bipyridine_model_data'; 11 | import styles from './bipyridine_styles'; 12 | import orbital from './orbital'; 13 | 14 | const shapes = [{ 15 | type: 'Arrow', 16 | color: '#00ff00', 17 | start: { 18 | x: 0, 19 | y: 0, 20 | z: -2.5, 21 | }, 22 | end: { 23 | x: 0, 24 | y: 0, 25 | z: 3, 26 | }, 27 | }]; 28 | const labels = [ 29 | { 30 | backgroundColor: '0x000000', 31 | backgroundOpacity: 1.0, 32 | borderColor: 'black', 33 | fontColor: '0xffffff', 34 | fontSize: 14, 35 | position: { 36 | x: 0, 37 | y: 0, 38 | z: 3, 39 | }, 40 | text: 'I\'m a label', 41 | }, 42 | ]; 43 | 44 | class Example extends React.Component { 45 | constructor(props) { 46 | super(props); 47 | 48 | this.state = { 49 | labels, 50 | modelData, 51 | styles, 52 | shapes, 53 | orbital, 54 | selectedAtomIds: [], 55 | atomLabelsShown: false, 56 | backgroundColor: undefined, 57 | backgroundOpacity: undefined, 58 | selectionType: undefined, 59 | }; 60 | } 61 | 62 | onChangeMolecule = (newModelData, newStyles) => { 63 | this.setState({ modelData: newModelData, styles: newStyles }); 64 | } 65 | 66 | onChangeSelection = (newSelectedAtomIds) => { 67 | this.setState({ selectedAtomIds: newSelectedAtomIds }); 68 | } 69 | 70 | onChangeModelData = (newModelData) => { 71 | this.setState({ modelData: newModelData }); 72 | } 73 | 74 | onChangeStyles = (newStyles) => { 75 | this.setState({ styles: newStyles }); 76 | } 77 | 78 | onChangeShapes = (newShapes) => { 79 | this.setState({ shapes: newShapes }); 80 | } 81 | 82 | onChangeBackgroundColor = (newBackgroundColor) => { 83 | this.setState({ backgroundColor: newBackgroundColor }); 84 | } 85 | 86 | onChangeBackgroundOpacity = (newBackgroundOpacity) => { 87 | this.setState({ backgroundOpacity: newBackgroundOpacity }); 88 | } 89 | 90 | onChangeSelectionType = (newSelectionType) => { 91 | this.setState({ selectionType: newSelectionType }); 92 | } 93 | 94 | onChangeAtomLabelsShown = (newAtomLabelsShown) => { 95 | this.setState({ atomLabelsShown: newAtomLabelsShown }); 96 | } 97 | 98 | onChangeOrbital = (newOrbital) => { 99 | this.setState({ orbital: newOrbital }); 100 | } 101 | 102 | onChangeLabels = (newLabels) => { 103 | this.setState({ labels: newLabels }); 104 | } 105 | 106 | 107 | render() { 108 | return ( 109 |
110 | 114 | 128 |
129 | ); 130 | } 131 | } 132 | 133 | render( 134 | , 135 | document.querySelector('.container') 136 | ); 137 | -------------------------------------------------------------------------------- /src/utils/molecule_utils.js: -------------------------------------------------------------------------------- 1 | import { Set as ISet } from 'immutable'; 2 | import selectionTypesConstants from '../constants/selection_types_constants'; 3 | 4 | const moleculeUtils = { 5 | /** 6 | * Given molecule model data, return a JSON object in ChemDoodle format 7 | * @param modelData {Object} 8 | * @returns {String} 9 | */ 10 | modelDataToCDJSON(modelData) { 11 | const atoms = modelData.atoms.map(atom => ({ 12 | l: atom.elem, 13 | x: atom.positions[0], 14 | y: atom.positions[1], 15 | z: atom.positions[2], 16 | mass: atom.mass_magnitude, 17 | })); 18 | 19 | const bonds = modelData.bonds.map(bond => ({ 20 | b: bond.atom1_index, 21 | e: bond.atom2_index, 22 | o: bond.bond_order, 23 | })); 24 | 25 | return { 26 | m: [{ 27 | a: atoms, 28 | b: bonds, 29 | }], 30 | }; 31 | }, 32 | 33 | /** 34 | * Return a new selection of atoms considering a clicked atom, the current selection type, and 35 | * the currently selected atoms 36 | * @param atoms {Array of Atoms} 37 | * @param selectedAtoms {Array of Atoms} 38 | * @param clickedAtom {Atom} 39 | * @param selectionType {String} 40 | * @returns {Array of Atoms} 41 | */ 42 | addSelection(atoms, selectedAtoms, clickedAtom, selectionType) { 43 | let selectedAtomsOut = selectedAtoms.slice(); 44 | const clickedIndex = selectedAtoms.indexOf(clickedAtom.serial); 45 | const toggleOn = clickedIndex === -1; 46 | 47 | if (selectionType === selectionTypesConstants.ATOM) { 48 | if (toggleOn) { 49 | selectedAtomsOut.push(clickedAtom.serial); 50 | } else { 51 | selectedAtomsOut.splice(clickedIndex, 1); 52 | } 53 | 54 | return selectedAtomsOut; 55 | } 56 | 57 | if (toggleOn) { 58 | atoms.forEach((atom) => { 59 | if (moleculeUtils.isSameGroup(clickedAtom, atom, selectionType)) { 60 | selectedAtomsOut.push(atom.serial); 61 | } 62 | }); 63 | } else { 64 | selectedAtomsOut = selectedAtomsOut.filter((atomSerial) => { 65 | const atom = atoms[atomSerial]; 66 | return !moleculeUtils.isSameGroup(clickedAtom, atom, selectionType); 67 | }); 68 | } 69 | 70 | return selectedAtomsOut; 71 | }, 72 | 73 | /** 74 | * Returns a boolean indicating if the given atoms are of the same type (residue or chain) 75 | * @param atomA {Atom} 76 | * @param atomB {Atom} 77 | * @param selectionType {String} 78 | * @returns {Boolean} 79 | */ 80 | isSameGroup(atomA, atomB, selectionType) { 81 | if (selectionType === selectionTypesConstants.RESIDUE) { 82 | return atomA.residue_index === atomB.residue_index; 83 | } 84 | if (selectionType === selectionTypesConstants.CHAIN) { 85 | return atomA.chain === atomB.chain; 86 | } 87 | 88 | throw new Error('selectionType must be either residue or chain'); 89 | }, 90 | 91 | /** 92 | * Checks to see if each modelData contains the same atoms and bonds. 93 | * Saves time by not checking every last piece of data right now. 94 | * Currently checks atom and bond ids, and atom positions 95 | * @param modalDataA {Object} 96 | * @param modelDataB {Object} 97 | * @returns {Boolean} 98 | */ 99 | modelDataEquivalent(modelDataA, modelDataB) { 100 | if (!modelDataA || !modelDataB) { 101 | return false; 102 | } 103 | 104 | const atomIdsA = new ISet(modelDataA.atoms.map(atom => atom.serial)); 105 | const atomIdsB = new ISet(modelDataB.atoms.map(atom => atom.serial)); 106 | const bondRelationsA = new ISet(modelDataA.bonds.map(bond => 107 | `${bond.atom1_index}=>${bond.atom2_index}`) 108 | ); 109 | const bondRelationsB = new ISet(modelDataB.bonds.map(bond => 110 | `${bond.atom1_index}=>${bond.atom2_index}`) 111 | ); 112 | 113 | const haveSameAtoms = atomIdsA.equals(atomIdsB); 114 | const haveSameBonds = bondRelationsA.equals(bondRelationsB); 115 | 116 | if (!haveSameAtoms || !haveSameBonds) { 117 | return false; 118 | } 119 | 120 | const atomIdsToPositions = new Map(); 121 | for (const atom of modelDataA.atoms) { 122 | atomIdsToPositions.set(atom.serial, atom.positions || []); 123 | } 124 | return modelDataB.atoms.every(atom => 125 | atomIdsToPositions.get(atom.serial).every((position, index) => { 126 | const positionsAtomB = atom.positions || []; 127 | return positionsAtomB[index] === position; 128 | }) 129 | ); 130 | }, 131 | }; 132 | 133 | export default moleculeUtils; 134 | -------------------------------------------------------------------------------- /test/components/molecule_3d_spec.js: -------------------------------------------------------------------------------- 1 | /* global describe, it, before, after, beforeEach, afterEach */ 2 | 3 | import React from 'react'; 4 | import ReactTestUtils from 'react-addons-test-utils'; 5 | import { expect } from 'chai'; 6 | import sinon from 'sinon'; 7 | import { mount } from 'enzyme'; 8 | import Molecule3d from '../../src/components/molecule_3d'; 9 | import bipyridineModelData from '../../example/js/bipyridine_model_data'; 10 | import threeAidModelData from '../../example/js/3aid_model_data'; 11 | import factories from '../fixtures/factories'; 12 | 13 | const $3Dmol = require('3dmol'); 14 | 15 | describe('Molecule3d', () => { 16 | let modelData; 17 | let modelData2; 18 | let emptyModelData; 19 | let renderer; 20 | let glViewer; 21 | 22 | beforeEach(() => { 23 | modelData = bipyridineModelData; 24 | modelData2 = threeAidModelData; 25 | emptyModelData = { atoms: [], bonds: [] }; 26 | renderer = ReactTestUtils.createRenderer(); 27 | 28 | glViewer = factories.getGlViewer(); 29 | sinon.stub($3Dmol, 'createViewer').callsFake(() => glViewer); 30 | }); 31 | 32 | afterEach(() => { 33 | $3Dmol.createViewer.restore(); 34 | }); 35 | 36 | describe('render', () => { 37 | it('renders a div', () => { 38 | renderer.render(React.createElement(Molecule3d, { modelData })); 39 | const result = renderer.getRenderOutput(); 40 | expect(result.type).to.equal('div'); 41 | }); 42 | }); 43 | 44 | describe('onRenderNewData', () => { 45 | it('calls onRenderNewData for initial modelData', () => { 46 | const callback = sinon.spy(); 47 | mount() 48 | expect(callback.callCount).to.equal(1); 49 | expect(callback.calledWith(glViewer)).to.equal(true); 50 | }); 51 | 52 | it('calls onRenderNewData when modelData changed', () => { 53 | const callback = sinon.spy(); 54 | const wrapper = mount(); 55 | wrapper.setProps({ modelData: modelData2 }); 56 | expect(callback.callCount).to.equal(2); 57 | expect(callback.calledWith(glViewer)).to.equal(true); 58 | }); 59 | 60 | it('doesn\'t call onRenderNewData when modelData not changed', () => { 61 | const callback = sinon.spy(); 62 | const wrapper = mount(); 63 | wrapper.setProps({ modelData, atomLabelsShown: true }); // Need a changed property to force re-render 64 | expect(callback.callCount).to.equal(1); 65 | expect(callback.calledWith(glViewer)).to.equal(true); 66 | }); 67 | 68 | it('doesn\'t call onRenderNewData when empty modelData supplied', () => { 69 | const callback = sinon.spy(); 70 | mount(); 71 | expect(callback.callCount).to.equal(0); 72 | }); 73 | }); 74 | 75 | describe('render3dMol', () => { 76 | beforeEach(() => { 77 | sinon.spy(glViewer, 'addLabel'); 78 | }); 79 | 80 | describe('when atomLabelsShown is true', () => { 81 | beforeEach(() => { 82 | ReactTestUtils.renderIntoDocument(React.createElement(Molecule3d, { 83 | modelData, 84 | atomLabelsShown: true, 85 | })); 86 | }); 87 | 88 | it('adds a label for each atom', () => { 89 | expect(glViewer.addLabel.callCount).to.equal(modelData.atoms.length); 90 | }); 91 | }); 92 | 93 | describe('when atomLabelsShown is false', () => { 94 | beforeEach(() => { 95 | sinon.spy(glViewer, 'removeAllLabels'); 96 | 97 | ReactTestUtils.renderIntoDocument(React.createElement(Molecule3d, { 98 | modelData, 99 | atomLabelsShown: false, 100 | })); 101 | }); 102 | 103 | it('removes all labels', () => { 104 | expect(glViewer.addLabel.called).to.equal(false); 105 | expect(glViewer.removeAllLabels.calledOnce).to.equal(true); 106 | }); 107 | }); 108 | 109 | describe('when initially loading empty modelData', () => { 110 | beforeEach(() => { 111 | modelData = { 112 | atoms: [], 113 | bonds: [], 114 | }; 115 | }); 116 | 117 | it('doesn\'t render glviewer', () => { 118 | const wrapper = mount(); 119 | expect(wrapper.node.glviewer).to.equal(undefined); 120 | }); 121 | }); 122 | 123 | describe('when emptying modelData after set', () => { 124 | it('removes all viewer models', () => { 125 | const wrapper = mount(); 126 | expect(wrapper.node.glviewer.getModel()).to.not.equal(null); 127 | wrapper.setProps({ modelData: emptyModelData }); 128 | expect(wrapper.node.glviewer.getModel()).to.equal(null); 129 | }); 130 | }); 131 | 132 | describe('when reloading modelData after emptying', () => { 133 | it('removes all viewer models and adds new ones in', () => { 134 | const wrapper = mount(); 135 | expect(wrapper.node.glviewer.getModel()).to.not.equal(null); 136 | wrapper.setProps({ modelData: { atoms: [], bonds: [] } }); 137 | expect(wrapper.node.glviewer.getModel()).to.equal(null); 138 | wrapper.setProps({ modelData }); 139 | expect(wrapper.node.glviewer.getModel()).to.not.equal(null); 140 | }); 141 | }); 142 | 143 | describe('when loading partially complete modelData', () => { 144 | beforeEach(() => { 145 | modelData = { 146 | atoms: modelData.atoms, 147 | bonds: [], 148 | }; 149 | sinon.spy(glViewer, 'addModel'); 150 | }); 151 | 152 | it('tries to render', () => { 153 | const wrapper = mount(); 154 | expect(wrapper.node.glviewer).to.equal(glViewer); 155 | }); 156 | }); 157 | }); 158 | }); 159 | -------------------------------------------------------------------------------- /example/css/main.css: -------------------------------------------------------------------------------- 1 | /*! HTML5 Boilerplate v5.3.0 | MIT License | https://html5boilerplate.com/ */ 2 | 3 | /* 4 | * What follows is the result of much research on cross-browser styling. 5 | * Credit left inline and big thanks to Nicolas Gallagher, Jonathan Neal, 6 | * Kroc Camen, and the H5BP dev community and team. 7 | */ 8 | 9 | /* ========================================================================== 10 | Base styles: opinionated defaults 11 | ========================================================================== */ 12 | 13 | html { 14 | color: #222; 15 | font-size: 1em; 16 | line-height: 1.4; 17 | } 18 | 19 | /* 20 | * Remove text-shadow in selection highlight: 21 | * https://twitter.com/miketaylr/status/12228805301 22 | * 23 | * These selection rule sets have to be separate. 24 | * Customize the background color to match your design. 25 | */ 26 | 27 | ::-moz-selection { 28 | background: #b3d4fc; 29 | text-shadow: none; 30 | } 31 | 32 | ::selection { 33 | background: #b3d4fc; 34 | text-shadow: none; 35 | } 36 | 37 | /* 38 | * A better looking default horizontal rule 39 | */ 40 | 41 | hr { 42 | display: block; 43 | height: 1px; 44 | border: 0; 45 | border-top: 1px solid #ccc; 46 | margin: 1em 0; 47 | padding: 0; 48 | } 49 | 50 | /* 51 | * Remove the gap between audio, canvas, iframes, 52 | * images, videos and the bottom of their containers: 53 | * https://github.com/h5bp/html5-boilerplate/issues/440 54 | */ 55 | 56 | audio, 57 | canvas, 58 | iframe, 59 | img, 60 | svg, 61 | video { 62 | vertical-align: middle; 63 | } 64 | 65 | /* 66 | * Remove default fieldset styles. 67 | */ 68 | 69 | fieldset { 70 | border: 0; 71 | margin: 0; 72 | padding: 0; 73 | } 74 | 75 | /* 76 | * Allow only vertical resizing of textareas. 77 | */ 78 | 79 | textarea { 80 | resize: vertical; 81 | } 82 | 83 | /* ========================================================================== 84 | Browser Upgrade Prompt 85 | ========================================================================== */ 86 | 87 | .browserupgrade { 88 | margin: 0.2em 0; 89 | background: #ccc; 90 | color: #000; 91 | padding: 0.2em 0; 92 | } 93 | 94 | /* ========================================================================== 95 | Author's custom styles 96 | ========================================================================== */ 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | /* ========================================================================== 115 | Helper classes 116 | ========================================================================== */ 117 | 118 | /* 119 | * Hide visually and from screen readers 120 | */ 121 | 122 | .hidden { 123 | display: none !important; 124 | } 125 | 126 | /* 127 | * Hide only visually, but have it available for screen readers: 128 | * http://snook.ca/archives/html_and_css/hiding-content-for-accessibility 129 | */ 130 | 131 | .visuallyhidden { 132 | border: 0; 133 | clip: rect(0 0 0 0); 134 | height: 1px; 135 | margin: -1px; 136 | overflow: hidden; 137 | padding: 0; 138 | position: absolute; 139 | width: 1px; 140 | } 141 | 142 | /* 143 | * Extends the .visuallyhidden class to allow the element 144 | * to be focusable when navigated to via the keyboard: 145 | * https://www.drupal.org/node/897638 146 | */ 147 | 148 | .visuallyhidden.focusable:active, 149 | .visuallyhidden.focusable:focus { 150 | clip: auto; 151 | height: auto; 152 | margin: 0; 153 | overflow: visible; 154 | position: static; 155 | width: auto; 156 | } 157 | 158 | /* 159 | * Hide visually and from screen readers, but maintain layout 160 | */ 161 | 162 | .invisible { 163 | visibility: hidden; 164 | } 165 | 166 | /* 167 | * Clearfix: contain floats 168 | * 169 | * For modern browsers 170 | * 1. The space content is one way to avoid an Opera bug when the 171 | * `contenteditable` attribute is included anywhere else in the document. 172 | * Otherwise it causes space to appear at the top and bottom of elements 173 | * that receive the `clearfix` class. 174 | * 2. The use of `table` rather than `block` is only necessary if using 175 | * `:before` to contain the top-margins of child elements. 176 | */ 177 | 178 | .clearfix:before, 179 | .clearfix:after { 180 | content: " "; /* 1 */ 181 | display: table; /* 2 */ 182 | } 183 | 184 | .clearfix:after { 185 | clear: both; 186 | } 187 | 188 | /* ========================================================================== 189 | EXAMPLE Media Queries for Responsive Design. 190 | These examples override the primary ('mobile first') styles. 191 | Modify as content requires. 192 | ========================================================================== */ 193 | 194 | @media only screen and (min-width: 35em) { 195 | /* Style adjustments for viewports that meet the condition */ 196 | } 197 | 198 | @media print, 199 | (-webkit-min-device-pixel-ratio: 1.25), 200 | (min-resolution: 1.25dppx), 201 | (min-resolution: 120dpi) { 202 | /* Style adjustments for high resolution devices */ 203 | } 204 | 205 | /* ========================================================================== 206 | Print styles. 207 | Inlined to avoid the additional HTTP request: 208 | http://www.phpied.com/delay-loading-your-print-css/ 209 | ========================================================================== */ 210 | 211 | @media print { 212 | *, 213 | *:before, 214 | *:after, 215 | *:first-letter, 216 | *:first-line { 217 | background: transparent !important; 218 | color: #000 !important; /* Black prints faster: 219 | http://www.sanbeiji.com/archives/953 */ 220 | box-shadow: none !important; 221 | text-shadow: none !important; 222 | } 223 | 224 | a, 225 | a:visited { 226 | text-decoration: underline; 227 | } 228 | 229 | a[href]:after { 230 | content: " (" attr(href) ")"; 231 | } 232 | 233 | abbr[title]:after { 234 | content: " (" attr(title) ")"; 235 | } 236 | 237 | /* 238 | * Don't show links that are fragment identifiers, 239 | * or use the `javascript:` pseudo protocol 240 | */ 241 | 242 | a[href^="#"]:after, 243 | a[href^="javascript:"]:after { 244 | content: ""; 245 | } 246 | 247 | pre, 248 | blockquote { 249 | border: 1px solid #999; 250 | page-break-inside: avoid; 251 | } 252 | 253 | /* 254 | * Printing Tables: 255 | * http://css-discuss.incutio.com/wiki/Printing_Tables 256 | */ 257 | 258 | thead { 259 | display: table-header-group; 260 | } 261 | 262 | tr, 263 | img { 264 | page-break-inside: avoid; 265 | } 266 | 267 | img { 268 | max-width: 100% !important; 269 | } 270 | 271 | p, 272 | h2, 273 | h3 { 274 | orphans: 3; 275 | widows: 3; 276 | } 277 | 278 | h2, 279 | h3 { 280 | page-break-after: avoid; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Molecule3d 2 | [![Build Status](https://travis-ci.org/Autodesk/molecule-3d-for-react.svg?branch=master)](https://travis-ci.org/Autodesk/molecule-3d-for-react) 3 | 4 | A data-bound React wrapper for [3dmol.js](http://3dmol.csb.pitt.edu) that visualizes any molecule in 3D. 5 | 6 | screen shot 7 | 8 | ## Installation 9 | 10 | npm install molecule-3d-for-react 11 | 12 | ## Usage 13 | 19 | 20 | See below for the full spec of what data can be passed in, and check out [example/js/main.js](https://github.com/Autodesk/molecule-3d-for-react/blob/master/example/js/main.js) for a working example. 21 | 22 | ## Props 23 | In order to set up your molecule visualization, just pass in the proper props data to the React component. Here are all of the parameters with explanations: 24 | 25 | ### modelData {Object} Required 26 | JSON data representing the actual molecular input. Of the form: 27 | 28 | { 29 | atoms: [{ 30 | serial, 31 | name, 32 | elem, 33 | mass_magnitude, 34 | residue_index, 35 | esidue_name, 36 | chain, 37 | positions, 38 | momenta, 39 | }, ... ], 40 | bonds: [{ 41 | atom1_index, 42 | atom2_index, 43 | bond_order, 44 | }, ... ], 45 | } 46 | 47 | An example of full working modelData for a real molecule can be found in [example/js/bipyridine_model_data.js](https://github.com/Autodesk/molecule-3d-for-react/blob/master/example/js/bipyridine_model_data.js). 48 | 49 | ### backgroundColor {String} ['#73757C'] 50 | The background color of the visualization. 51 | 52 | ### backgroundOpacity {Number 0-1} [1.0] 53 | The opacity of the background. 54 | 55 | ### atomLabelsShown {Boolean} [false] 56 | Indicates whether or not to show text labels on all atoms. 57 | 58 | ### styles {Array of Objects} [[]] 59 | 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: 60 | 61 | [ 62 | { 63 | visualization_type: 'stick'|'sphere'|'cartoon', 64 | color: '#abcdef', 65 | }, ... 66 | ] 67 | 68 | An example of a styles array for the bipyridine molecule can be found in [example/js/bipyridine_styles.js](https://github.com/Autodesk/molecule-3d-for-react/blob/master/example/js/bipyridine_styles.js). 69 | 70 | ### selectedAtomIds {Array of Numbers} [[]] 71 | An array of atom indices indicating which atoms should be visually selected. 72 | 73 | ### selectionType {String} ['Atom'] 74 | A string indicating whether clicks select atoms ('Atom'), residues ('Residue'), or chains ('Chain'). 75 | 76 | ### shapes {Array of Objects} [[]] 77 | Indicates any shapes to display in the visualization using 3Dmol.js's [addShape method](http://3dmol.csb.pitt.edu/doc/$3Dmol.GLViewer.html#addShape). For example: 78 | 79 | [{ 80 | type: 'Sphere', 81 | x: 0, 82 | y: 0, 83 | z: 0, 84 | }] 85 | 86 | ### labels {Array of Objects} [[]] 87 | Labels to draw using 3Dmol.js's [addLabel method](http://3dmol.csb.pitt.edu/doc/$3Dmol.GLViewer.html#addLabel). The text for the field is expected as `label.text`. For example: 88 | 89 | ```javascript 90 | [{'backgroundColor': '0x000000', 91 | 'backgroundOpacity': 1.0, 92 | 'borderColor': 'black', 93 | 'fontColor': '0xffffff', 94 | 'fontSize': 14, 95 | 'position': 96 | {'x': -1.84, 97 | 'y': 8.30, 98 | 'z': 33.87}, 99 | 'text': 'PRO1'}] 100 | ``` 101 | 102 | ### orbital {Object} [{}] 103 | Indicates an orbital to display using 3Dmol.js's [addIsosurface method](http://3dmol.csb.pitt.edu/doc/$3Dmol.GLViewer.html#addIsosurface). Of the type: 104 | 105 | { 106 | cube_file, 107 | iso_val, 108 | opacity, 109 | } 110 | 111 | ### onRenderNewData {function} [(glviewer) => {}] 112 | A callback for when the modelData has changed and it has been re-rendered in the viewer. 113 | 114 | ## Example 115 | 116 | screen shot 117 | 118 | An example is included which provides data-bound inputs that you can play with to see how they affect the visualization. To run it, use the command: 119 | 120 | npm run example 121 | 122 | ## What about 2d? 123 | Take a look at our sister project, [molecule-2d-for-react](https://github.com/Autodesk/molecule-2d-for-react), for a React component with a similar interface that renders a 2d visualization. 124 | 125 | 126 | ## Development 127 | Running the example above will also set up a typical development flow, where any changes to the code will be immediately reflected in the browser. 128 | 129 | ### Development within another project 130 | If you're using this in another project and want to make changes to this repository locally and see them reflected in your other project, first you'll need to do some setup. You can point your other project to use the local copy of molecule-3d-for-react like this: 131 | 132 | cd ~/path/to/molecule-3d-for-react 133 | npm link 134 | cd ~/path/to/other-project 135 | npm link molecule-3d-for-react 136 | 137 | See [this great blog post](http://justjs.com/posts/npm-link-developing-your-own-npm-modules-without-tears) for more info on `npm link`. 138 | 139 | Once you've linked your other project, you'll need to build molecule-3d-for-react (and likely your other project, too) every time you want your changes to reflect in your other project. You can do this manually with `npm run build`. If you want to rebuild molecule-3d-for-react automatically every time a change is made, run `npm run watch`. 140 | 141 | ### Running Tests 142 | Unit tests can be run with: 143 | 144 | npm test 145 | 146 | End-to-end tests can be run with: 147 | 148 | npm run setup-selenium 149 | npm run e2e 150 | 151 | ### Releasing a new version 152 | Travis automatically publishes any new tagged commit to NPM. The best way to take advantage of this is to first create a new tagged commit using `npm version`: 153 | 154 | npm version patch -m "Upgrade to %s for reasons" 155 | 156 | Then push that commit to a new release branch, push the tag with `git push origin --tags` and open a pull request on Github. When you see that Travis has succeeded in deploying, merge it to master. 157 | 158 | ## License 159 | 160 | Copyright 2016 Autodesk Inc. 161 | 162 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at 163 | 164 | http://www.apache.org/licenses/LICENSE-2.0 165 | 166 | Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. 167 | 168 | ## Contributing 169 | This project is developed and maintained by the [Molecular Design Toolkit](https://github.com/autodesk/molecular-design-toolkit) project. Please see that project's CONTRIBUTING document for details. 170 | -------------------------------------------------------------------------------- /example/js/settings.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Map as IMap } from 'immutable'; 3 | import aidModelData from './3aid_model_data'; 4 | import aidStyles from './3aid_styles'; 5 | import bipyridineModelData from './bipyridine_model_data'; 6 | import bipyridineStyles from './bipyridine_styles'; 7 | import selectionTypesConstants from '../../src/constants/selection_types_constants'; 8 | 9 | class Settings extends React.Component { 10 | 11 | static defaultProps = { 12 | atomLabelsShown: false, 13 | backgroundOpacity: 1.0, 14 | backgroundColor: '', 15 | labels: [], 16 | orbital: {}, 17 | selectedAtomIds: [], 18 | selectionType: selectionTypesConstants.ATOM, 19 | shapes: [], 20 | styles: [], 21 | } 22 | 23 | static propTypes = { 24 | atomLabelsShown: React.PropTypes.bool, 25 | backgroundColor: React.PropTypes.string, 26 | backgroundOpacity: React.PropTypes.number, 27 | labels: React.PropTypes.arrayOf(React.PropTypes.shape({ 28 | backgroundColor: React.PropTypes.string, 29 | backgroundOpacity: React.PropTypes.number, 30 | borderColor: React.PropTypes.string, 31 | fontColor: React.PropTypes.string, 32 | fontSize: React.PropTypes.number, 33 | position: { 34 | x: React.PropTypes.number, 35 | y: React.PropTypes.number, 36 | z: React.PropTypes.number, 37 | }, 38 | text: React.PropTypes.string, 39 | })), 40 | modelData: React.PropTypes.oneOfType([ 41 | React.PropTypes.instanceOf(IMap), 42 | React.PropTypes.object, 43 | ]).isRequired, 44 | shapes: React.PropTypes.arrayOf(React.PropTypes.object), 45 | styles: React.PropTypes.objectOf(React.PropTypes.object), 46 | onChangeMolecule: React.PropTypes.func.isRequired, 47 | onChangeSelection: React.PropTypes.func.isRequired, 48 | onChangeModelData: React.PropTypes.func.isRequired, 49 | onChangeStyles: React.PropTypes.func.isRequired, 50 | onChangeShapes: React.PropTypes.func.isRequired, 51 | onChangeBackgroundColor: React.PropTypes.func.isRequired, 52 | onChangeBackgroundOpacity: React.PropTypes.func.isRequired, 53 | onChangeSelectionType: React.PropTypes.func.isRequired, 54 | onChangeAtomLabelsShown: React.PropTypes.func.isRequired, 55 | onChangeOrbital: React.PropTypes.func.isRequired, 56 | onChangeLabels: React.PropTypes.func.isRequired, 57 | orbital: React.PropTypes.shape({ 58 | iso_val: React.PropTypes.number, 59 | opacity: React.PropTypes.number, 60 | cube_file: React.PropTypes.string, 61 | }), 62 | selectedAtomIds: React.PropTypes.arrayOf(React.PropTypes.number), 63 | selectionType: React.PropTypes.string, 64 | } 65 | 66 | constructor(props) { 67 | super(props); 68 | 69 | this.state = { 70 | labels: JSON.stringify(props.labels), 71 | selectedAtomIds: JSON.stringify(props.selectedAtomIds), 72 | modelData: JSON.stringify(props.modelData), 73 | shapes: JSON.stringify(props.shapes), 74 | styles: JSON.stringify(props.styles), 75 | backgroundColor: props.backgroundColor, 76 | backgroundOpacity: props.backgroundOpacity, 77 | orbital: JSON.stringify(props.orbital), 78 | selectionType: props.selectionType, 79 | atomLabelsShown: props.atomLabelsShown, 80 | }; 81 | } 82 | 83 | componentWillReceiveProps(nextProps) { 84 | this.setState({ 85 | selectedAtomIds: JSON.stringify(nextProps.selectedAtomIds), 86 | styles: JSON.stringify(nextProps.styles), 87 | shapes: JSON.stringify(nextProps.shapes), 88 | modelData: JSON.stringify(nextProps.modelData), 89 | backgroundColor: nextProps.backgroundColor, 90 | backgroundOpacity: nextProps.backgroundOpacity, 91 | orbital: JSON.stringify(nextProps.orbital), 92 | selectionType: nextProps.selectionType, 93 | atomLabelsShown: nextProps.atomLabelsShown, 94 | }); 95 | } 96 | 97 | onClickSmallButton = () => { 98 | this.props.onChangeMolecule(bipyridineModelData, bipyridineStyles); 99 | } 100 | 101 | onClickLargeButton = () => { 102 | this.props.onChangeMolecule(aidModelData, aidStyles); 103 | } 104 | 105 | onBlurSelection = (event) => { 106 | this.props.onChangeSelection(JSON.parse(event.target.value)); 107 | } 108 | 109 | onChangeSelection = (event) => { 110 | this.setState({ selectedAtomIds: event.target.value }); 111 | } 112 | 113 | onChangeModelData = (event) => { 114 | this.setState({ modelData: event.target.value }); 115 | } 116 | 117 | onBlurModelData = (event) => { 118 | this.props.onChangeModelData(JSON.parse(event.target.value)); 119 | } 120 | 121 | onChangeStyles = (event) => { 122 | this.setState({ styles: event.target.value }); 123 | } 124 | 125 | onBlurStyles = (event) => { 126 | this.props.onChangeStyles(JSON.parse(event.target.value)); 127 | } 128 | 129 | onChangeShapes = (event) => { 130 | this.setState({ shapes: event.target.value }); 131 | } 132 | 133 | onBlurShapes = (event) => { 134 | this.props.onChangeShapes(JSON.parse(event.target.value)); 135 | } 136 | 137 | onChangeBackgroundColor = (event) => { 138 | this.setState({ backgroundColor: event.target.value }); 139 | } 140 | 141 | onBlurBGColor = (event) => { 142 | this.props.onChangeBackgroundColor(event.target.value); 143 | } 144 | 145 | onChangeBackgroundOpacity = (event) => { 146 | this.setState({ backgroundOpacity: event.target.value }); 147 | } 148 | 149 | onBlurBGOpacity = (event) => { 150 | this.props.onChangeBackgroundOpacity(parseFloat(event.target.value)); 151 | } 152 | 153 | onChangeSelectionType = (event) => { 154 | this.props.onChangeSelectionType(event.target.value); 155 | } 156 | 157 | onChangeLabelsInput = (event) => { 158 | this.props.onChangeAtomLabelsShown(event.target.checked); 159 | } 160 | 161 | onChangeOrbital = (event) => { 162 | this.setState({ orbital: event.target.value }); 163 | } 164 | 165 | onBlurOrbitalInput = (event) => { 166 | this.props.onChangeOrbital(JSON.parse(event.target.value)); 167 | } 168 | 169 | onChangeLabels = (event) => { 170 | this.setState({ labels: event.target.value }); 171 | } 172 | 173 | onBlurLabelsInput = (event) => { 174 | this.props.onChangeLabels(JSON.parse(event.target.value)); 175 | } 176 | 177 | render() { 178 | return ( 179 |
180 | 181 | 182 | 183 |

selectedAtomIds

184 | 189 | 190 |

modelData

191 |