diff --git a/ippisite/ippidb/static/smilesdrawer/smiles-drawer.js b/ippisite/ippidb/static/smilesdrawer/smiles-drawer.js
new file mode 100644
index 0000000000000000000000000000000000000000..729f7afadf0746d3614967b0706105cf52989890
--- /dev/null
+++ b/ippisite/ippidb/static/smilesdrawer/smiles-drawer.js
@@ -0,0 +1,11286 @@
+(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
+'use strict';
+
+//@ts-check
+var Drawer = require('./src/Drawer');
+var Parser = require('./src/Parser');
+
+// Detect SSR (server side rendering)
+var canUseDOM = !!(typeof window !== 'undefined' && window.document && window.document.createElement);
+
+/**
+ * The SmilesDrawer namespace.
+ * @typicalname SmilesDrawer
+ */
+var SmilesDrawer = {
+  Version: '1.0.0'
+};
+
+SmilesDrawer.Drawer = Drawer;
+SmilesDrawer.Parser = Parser;
+
+/**
+* Cleans a SMILES string (removes non-valid characters)
+*
+* @static
+* @param {String} smiles A SMILES string.
+* @returns {String} The clean SMILES string.
+*/
+SmilesDrawer.clean = function (smiles) {
+  return smiles.replace(/[^A-Za-z0-9@\.\+\-\?!\(\)\[\]\{\}/\\=#\$:\*]/g, '');
+};
+
+/**
+* Applies the smiles drawer draw function to each canvas element that has a smiles string in the data-smiles attribute.
+*
+* @static
+* @param {Object} options SmilesDrawer options.
+* @param {String} [selector='canvas[data-smiles]'] Selectors for the draw areas (canvas elements).
+* @param {String} [themeName='light'] The theme to apply.
+* @param {Function} [onError='null'] A callback function providing an error object.
+*/
+SmilesDrawer.apply = function (options) {
+  var selector = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 'canvas[data-smiles]';
+  var themeName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'light';
+  var onError = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+
+  var smilesDrawer = new Drawer(options);
+  var elements = document.querySelectorAll(selector);
+
+  var _loop = function _loop() {
+    var element = elements[i];
+
+    SmilesDrawer.parse(element.getAttribute('data-smiles'), function (tree) {
+      smilesDrawer.draw(tree, element, themeName, false);
+    }, function (err) {
+      if (onError) {
+        onError(err);
+      }
+    });
+  };
+
+  for (var i = 0; i < elements.length; i++) {
+    _loop();
+  }
+};
+
+/**
+* Parses the entered smiles string.
+* 
+* @static
+* @param {String} smiles A SMILES string.
+* @param {Function} successCallback A callback that is called on success with the parse tree.
+* @param {Function} errorCallback A callback that is called with the error object on error.
+*/
+SmilesDrawer.parse = function (smiles, successCallback, errorCallback) {
+  try {
+    if (successCallback) {
+      successCallback(Parser.parse(smiles));
+    }
+  } catch (err) {
+    if (errorCallback) {
+      errorCallback(err);
+    }
+  }
+};
+
+if (canUseDOM) {
+  window.SmilesDrawer = SmilesDrawer;
+}
+
+// There be dragons (polyfills)
+
+if (!Array.prototype.fill) {
+  Object.defineProperty(Array.prototype, 'fill', {
+    value: function value(_value) {
+
+      // Steps 1-2.
+      if (this == null) {
+        throw new TypeError('this is null or not defined');
+      }
+
+      var O = Object(this);
+
+      // Steps 3-5.
+      var len = O.length >>> 0;
+
+      // Steps 6-7.
+      var start = arguments[1];
+      var relativeStart = start >> 0;
+
+      // Step 8.
+      var k = relativeStart < 0 ? Math.max(len + relativeStart, 0) : Math.min(relativeStart, len);
+
+      // Steps 9-10.
+      var end = arguments[2];
+      var relativeEnd = end === undefined ? len : end >> 0;
+
+      // Step 11.
+      var final = relativeEnd < 0 ? Math.max(len + relativeEnd, 0) : Math.min(relativeEnd, len);
+
+      // Step 12.
+      while (k < final) {
+        O[k] = _value;
+        k++;
+      }
+
+      // Step 13.
+      return O;
+    }
+  });
+}
+
+module.exports = SmilesDrawer;
+
+},{"./src/Drawer":5,"./src/Parser":10}],2:[function(require,module,exports){
+'use strict';
+
+var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+
+/** 
+ * A static class containing helper functions for array-related tasks. 
+ */
+var ArrayHelper = function () {
+    function ArrayHelper() {
+        _classCallCheck(this, ArrayHelper);
+    }
+
+    _createClass(ArrayHelper, null, [{
+        key: 'clone',
+
+        /**
+         * Clone an array or an object. If an object is passed, a shallow clone will be created.
+         *
+         * @static
+         * @param {*} arr The array or object to be cloned.
+         * @returns {*} A clone of the array or object.
+         */
+        value: function clone(arr) {
+            var out = Array.isArray(arr) ? Array() : {};
+
+            for (var key in arr) {
+                var value = arr[key];
+
+                if (typeof value.clone === 'function') {
+                    out[key] = value.clone();
+                } else {
+                    out[key] = (typeof value === 'undefined' ? 'undefined' : _typeof(value)) === 'object' ? ArrayHelper.clone(value) : value;
+                }
+            }
+
+            return out;
+        }
+
+        /**
+         * Returns a boolean indicating whether or not the two arrays contain the same elements.
+         * Only supports 1d, non-nested arrays.
+         *
+         * @static
+         * @param {Array} arrA An array.
+         * @param {Array} arrB An array.
+         * @returns {Boolean} A boolean indicating whether or not the two arrays contain the same elements.
+         */
+
+    }, {
+        key: 'equals',
+        value: function equals(arrA, arrB) {
+            if (arrA.length !== arrB.length) {
+                return false;
+            }
+
+            var tmpA = arrA.slice().sort();
+            var tmpB = arrB.slice().sort();
+
+            for (var i = 0; i < tmpA.length; i++) {
+                if (tmpA[i] !== tmpB[i]) {
+                    return false;
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Returns a string representation of an array. If the array contains objects with an id property, the id property is printed for each of the elements.
+         *
+         * @static
+         * @param {Object[]} arr An array.
+         * @param {*} arr[].id If the array contains an object with the property 'id', the properties value is printed. Else, the array elements value is printend.
+         * @returns {String} A string representation of the array.
+         */
+
+    }, {
+        key: 'print',
+        value: function print(arr) {
+            if (arr.length == 0) {
+                return '';
+            }
+
+            var s = '(';
+
+            for (var i = 0; i < arr.length; i++) {
+                s += arr[i].id ? arr[i].id + ', ' : arr[i] + ', ';
+            }
+
+            s = s.substring(0, s.length - 2);
+
+            return s + ')';
+        }
+
+        /**
+         * Run a function for each element in the array. The element is supplied as an argument for the callback function
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {Function} callback The callback function that is called for each element.
+         */
+
+    }, {
+        key: 'each',
+        value: function each(arr, callback) {
+            for (var i = 0; i < arr.length; i++) {
+                callback(arr[i]);
+            }
+        }
+
+        /**
+         * Return the array element from an array containing objects, where a property of the object is set to a given value.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {(String|Number)} property A property contained within an object in the array.
+         * @param {(String|Number)} value The value of the property.
+         * @returns {*} The array element matching the value.
+         */
+
+    }, {
+        key: 'get',
+        value: function get(arr, property, value) {
+            for (var i = 0; i < arr.length; i++) {
+                if (arr[i][property] == value) {
+                    return arr[i];
+                }
+            }
+        }
+
+        /**
+         * Checks whether or not an array contains a given value. the options object passed as a second argument can contain three properties. value: The value to be searched for. property: The property that is to be searched for a given value. func: A function that is used as a callback to return either true or false in order to do a custom comparison.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {Object} options See method description.
+         * @param {*} options.value The value for which to check.
+         * @param {String} [options.property=undefined] The property on which to check.
+         * @param {Function} [options.func=undefined] A custom property function.
+         * @returns {Boolean} A boolean whether or not the array contains a value.
+         */
+
+    }, {
+        key: 'contains',
+        value: function contains(arr, options) {
+            if (!options.property && !options.func) {
+                for (var i = 0; i < arr.length; i++) {
+                    if (arr[i] == options.value) {
+                        return true;
+                    }
+                }
+            } else if (options.func) {
+                for (var _i = 0; _i < arr.length; _i++) {
+                    if (options.func(arr[_i])) {
+                        return true;
+                    }
+                }
+            } else {
+                for (var _i2 = 0; _i2 < arr.length; _i2++) {
+                    if (arr[_i2][options.property] == options.value) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Returns an array containing the intersection between two arrays. That is, values that are common to both arrays.
+         *
+         * @static
+         * @param {Array} arrA An array.
+         * @param {Array} arrB An array.
+         * @returns {Array} The intersecting vlaues.
+         */
+
+    }, {
+        key: 'intersection',
+        value: function intersection(arrA, arrB) {
+            var intersection = new Array();
+
+            for (var i = 0; i < arrA.length; i++) {
+                for (var j = 0; j < arrB.length; j++) {
+                    if (arrA[i] === arrB[j]) {
+                        intersection.push(arrA[i]);
+                    }
+                }
+            }
+
+            return intersection;
+        }
+
+        /**
+         * Returns an array of unique elements contained in an array.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @returns {Array} An array of unique elements contained within the array supplied as an argument.
+         */
+
+    }, {
+        key: 'unique',
+        value: function unique(arr) {
+            var contains = {};
+            return arr.filter(function (i) {
+                // using !== instead of hasOwnProperty (http://andrew.hedges.name/experiments/in/)
+                return contains[i] !== undefined ? false : contains[i] = true;
+            });
+        }
+
+        /**
+         * Count the number of occurences of a value in an array.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {*} value A value to be counted.
+         * @returns {Number} The number of occurences of a value in the array.
+         */
+
+    }, {
+        key: 'count',
+        value: function count(arr, value) {
+            var count = 0;
+
+            for (var i = 0; i < arr.length; i++) {
+                if (arr[i] === value) {
+                    count++;
+                }
+            }
+
+            return count;
+        }
+
+        /**
+         * Toggles the value of an array. If a value is not contained in an array, the array returned will contain all the values of the original array including the value. If a value is contained in an array, the array returned will contain all the values of the original array excluding the value.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {*} value A value to be toggled.
+         * @returns {Array} The toggled array.
+         */
+
+    }, {
+        key: 'toggle',
+        value: function toggle(arr, value) {
+            var newArr = Array();
+
+            var removed = false;
+            for (var i = 0; i < arr.length; i++) {
+                // Do not copy value if it exists
+                if (arr[i] !== value) {
+                    newArr.push(arr[i]);
+                } else {
+                    // The element was not copied to the new array, which
+                    // means it was removed
+                    removed = true;
+                }
+            }
+
+            // If the element was not removed, then it was not in the array
+            // so add it
+            if (!removed) {
+                newArr.push(value);
+            }
+
+            return newArr;
+        }
+
+        /**
+         * Remove a value from an array.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {*} value A value to be removed.
+         * @returns {Array} A new array with the element with a given value removed.
+         */
+
+    }, {
+        key: 'remove',
+        value: function remove(arr, value) {
+            var tmp = Array();
+
+            for (var i = 0; i < arr.length; i++) {
+                if (arr[i] !== value) {
+                    tmp.push(arr[i]);
+                }
+            }
+
+            return tmp;
+        }
+
+        /**
+         * Remove a value from an array with unique values.
+         *
+         * @static
+         * @param {Array} arr An array.
+         * @param {*} value A value to be removed.
+         * @returns {Array} An array with the element with a given value removed.
+         */
+
+    }, {
+        key: 'removeUnique',
+        value: function removeUnique(arr, value) {
+            var index = arr.indexOf(value);
+
+            if (index > -1) {
+                arr.splice(index, 1);
+            }
+
+            return arr;
+        }
+
+        /**
+         * Remove all elements contained in one array from another array.
+         *
+         * @static
+         * @param {Array} arrA The array to be filtered.
+         * @param {Array} arrB The array containing elements that will be removed from the other array.
+         * @returns {Array} The filtered array.
+         */
+
+    }, {
+        key: 'removeAll',
+        value: function removeAll(arrA, arrB) {
+            return arrA.filter(function (item) {
+                return arrB.indexOf(item) === -1;
+            });
+        }
+
+        /**
+         * Merges two arrays and returns the result. The first array will be appended to the second array.
+         *
+         * @static
+         * @param {Array} arrA An array.
+         * @param {Array} arrB An array.
+         * @returns {Array} The merged array.
+         */
+
+    }, {
+        key: 'merge',
+        value: function merge(arrA, arrB) {
+            var arr = new Array(arrA.length + arrB.length);
+
+            for (var i = 0; i < arrA.length; i++) {
+                arr[i] = arrA[i];
+            }
+
+            for (var _i3 = 0; _i3 < arrB.length; _i3++) {
+                arr[arrA.length + _i3] = arrB[_i3];
+            }
+
+            return arr;
+        }
+
+        /**
+         * Checks whether or not an array contains all the elements of another array, without regard to the order.
+         *
+         * @static
+         * @param {Array} arrA An array.
+         * @param {Array} arrB An array.
+         * @returns {Boolean} A boolean indicating whether or not both array contain the same elements.
+         */
+
+    }, {
+        key: 'containsAll',
+        value: function containsAll(arrA, arrB) {
+            var containing = 0;
+            for (var i = 0; i < arrA.length; i++) {
+                for (var j = 0; j < arrB.length; j++) {
+                    if (arrA[i] === arrB[j]) {
+                        containing++;
+                    }
+                }
+            }
+
+            return containing === arrB.length;
+        }
+
+        /**
+         * Sort an array of atomic number information. Where the number is indicated as x, x.y, x.y.z, ...
+         *
+         * @param {Object[]} arr An array of vertex ids with their associated atomic numbers.
+         * @param {Number} arr[].vertexId A vertex id.
+         * @param {String} arr[].atomicNumber The atomic number associated with the vertex id.
+         * @returns {Object[]} The array sorted by atomic number. Example of an array entry: { atomicNumber: 2, vertexId: 5 }.
+         */
+
+    }, {
+        key: 'sortByAtomicNumberDesc',
+        value: function sortByAtomicNumberDesc(arr) {
+            var map = arr.map(function (e, i) {
+                return { index: i, value: e.atomicNumber.split('.').map(Number) };
+            });
+
+            map.sort(function (a, b) {
+                var min = Math.min(b.value.length, a.value.length);
+                var i = 0;
+
+                while (i < min && b.value[i] === a.value[i]) {
+                    i++;
+                }
+
+                return i === min ? b.value.length - a.value.length : b.value[i] - a.value[i];
+            });
+
+            return map.map(function (e) {
+                return arr[e.index];
+            });
+        }
+
+        /**
+         * Copies a an n-dimensional array.
+         * 
+         * @param {Array} arr The array to be copied.
+         * @returns {Array} The copy.
+         */
+
+    }, {
+        key: 'deepCopy',
+        value: function deepCopy(arr) {
+            var newArr = Array();
+
+            for (var i = 0; i < arr.length; i++) {
+                var item = arr[i];
+
+                if (item instanceof Array) {
+                    newArr[i] = ArrayHelper.deepCopy(item);
+                } else {
+                    newArr[i] = item;
+                }
+            }
+
+            return newArr;
+        }
+    }]);
+
+    return ArrayHelper;
+}();
+
+module.exports = ArrayHelper;
+
+},{}],3:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var ArrayHelper = require('./ArrayHelper');
+var Vertex = require('./Vertex');
+var Ring = require('./Ring');
+
+/** 
+ * A class representing an atom.
+ * 
+ * @property {String} element The element symbol of this atom. Single-letter symbols are always uppercase. Examples: H, C, F, Br, Si, ...
+ * @property {Boolean} drawExplicit A boolean indicating whether or not this atom is drawn explicitly (for example, a carbon atom). This overrides the default behaviour.
+ * @property {Object[]} ringbonds An array containing the ringbond ids and bond types as specified in the original SMILE.
+ * @property {String} branchBond The branch bond as defined in the SMILES.
+ * @property {Number} ringbonds[].id The ringbond id as defined in the SMILES.
+ * @property {String} ringbonds[].bondType The bond type of the ringbond as defined in the SMILES.
+ * @property {Number[]} rings The ids of rings which contain this atom.
+ * @property {String} bondType The bond type associated with this array. Examples: -, =, #, ...
+ * @property {Boolean} isBridge A boolean indicating whether or not this atom is part of a bridge in a bridged ring (contained by the largest ring).
+ * @property {Boolean} isBridgeNode A boolean indicating whether or not this atom is a bridge node (a member of the largest ring in a bridged ring which is connected to a bridge-atom).
+ * @property {Number[]} originalRings Used to back up rings when they are replaced by a bridged ring.
+ * @property {Number} bridgedRing The id of the bridged ring if the atom is part of a bridged ring.
+ * @property {Number[]} anchoredRings The ids of the rings that are anchored to this atom. The centers of anchored rings are translated when this atom is translated.
+ * @property {Object} bracket If this atom is defined as a bracket atom in the original SMILES, this object contains all the bracket information. Example: { hcount: {Number}, charge: ['--', '-', '+', '++'], isotope: {Number} }.
+ * @property {Number} plane Specifies on which "plane" the atoms is in stereochemical deptictions (-1 back, 0 middle, 1 front).
+ * @property {Object[]} attachedPseudoElements A map with containing information for pseudo elements or concatinated elements. The key is comprised of the element symbol and the hydrogen count.
+ * @property {String} attachedPseudoElement[].element The element symbol.
+ * @property {Number} attachedPseudoElement[].count The number of occurences that match the key.
+ * @property {Number} attachedPseudoElement[].hyrogenCount The number of hydrogens attached to each atom matching the key.
+ * @property {Boolean} hasAttachedPseudoElements A boolean indicating whether or not this attom will be drawn with an attached pseudo element or concatinated elements.
+ * @property {Boolean} isDrawn A boolean indicating whether or not this atom is drawn. In contrast to drawExplicit, the bond is drawn neither.
+ * @property {Boolean} isConnectedToRing A boolean indicating whether or not this atom is directly connected (but not a member of) a ring.
+ * @property {String[]} neighbouringElements An array containing the element symbols of neighbouring atoms.
+ * @property {Boolean} isPartOfAromaticRing A boolean indicating whether or not this atom is part of an explicitly defined aromatic ring. Example: c1ccccc1.
+ * @property {Number} bondCount The number of bonds in which this atom is participating.
+ * @property {String} chirality The chirality of this atom if it is a stereocenter (R or S).
+ * @property {Number} priority The priority of this atom acording to the CIP rules, where 0 is the highest priority.
+ * @property {Boolean} mainChain A boolean indicating whether or not this atom is part of the main chain (used for chirality).
+ * @property {String} hydrogenDirection The direction of the hydrogen, either up or down. Only for stereocenters with and explicit hydrogen.
+ * @property {Number} subtreeDepth The depth of the subtree coming from a stereocenter.
+ */
+
+var Atom = function () {
+  /**
+   * The constructor of the class Atom.
+   *
+   * @param {String} element The one-letter code of the element.
+   * @param {String} [bondType='-'] The type of the bond associated with this atom.
+   */
+  function Atom(element) {
+    var bondType = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : '-';
+
+    _classCallCheck(this, Atom);
+
+    this.element = element.length === 1 ? element.toUpperCase() : element;
+    this.drawExplicit = false;
+    this.ringbonds = Array();
+    this.rings = Array();
+    this.bondType = bondType;
+    this.branchBond = null;
+    this.isBridge = false;
+    this.isBridgeNode = false;
+    this.originalRings = Array();
+    this.bridgedRing = null;
+    this.anchoredRings = Array();
+    this.bracket = null;
+    this.plane = 0;
+    this.attachedPseudoElements = {};
+    this.hasAttachedPseudoElements = false;
+    this.isDrawn = true;
+    this.isConnectedToRing = false;
+    this.neighbouringElements = Array();
+    this.isPartOfAromaticRing = element !== this.element;
+    this.bondCount = 0;
+    this.chirality = '';
+    this.isStereoCenter = false;
+    this.priority = 0;
+    this.mainChain = false;
+    this.hydrogenDirection = 'down';
+    this.subtreeDepth = 1;
+    this.hasHydrogen = false;
+  }
+
+  /**
+   * Adds a neighbouring element to this atom.
+   * 
+   * @param {String} element A string representing an element.
+   */
+
+
+  _createClass(Atom, [{
+    key: 'addNeighbouringElement',
+    value: function addNeighbouringElement(element) {
+      this.neighbouringElements.push(element);
+    }
+
+    /**
+     * Attaches a pseudo element (e.g. Ac) to the atom.
+     * @param {String} element The element identifier (e.g. Br, C, ...).
+     * @param {String} previousElement The element that is part of the main chain (not the terminals that are converted to the pseudo element or concatinated).
+     * @param {Number} [hydrogenCount=0] The number of hydrogens for the element.
+     * @param {Number} [charge=0] The charge for the element.
+     */
+
+  }, {
+    key: 'attachPseudoElement',
+    value: function attachPseudoElement(element, previousElement) {
+      var hydrogenCount = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+      var charge = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
+
+      if (hydrogenCount === null) {
+        hydrogenCount = 0;
+      }
+
+      if (charge === null) {
+        charge = 0;
+      }
+
+      var key = hydrogenCount + element + charge;
+
+      if (this.attachedPseudoElements[key]) {
+        this.attachedPseudoElements[key].count += 1;
+      } else {
+        this.attachedPseudoElements[key] = {
+          element: element,
+          count: 1,
+          hydrogenCount: hydrogenCount,
+          previousElement: previousElement,
+          charge: charge
+        };
+      }
+
+      this.hasAttachedPseudoElements = true;
+    }
+
+    /**
+     * Returns the attached pseudo elements sorted by hydrogen count (ascending).
+     *
+     * @returns {Object} The sorted attached pseudo elements.
+     */
+
+  }, {
+    key: 'getAttachedPseudoElements',
+    value: function getAttachedPseudoElements() {
+      var ordered = {};
+      var that = this;
+
+      Object.keys(this.attachedPseudoElements).sort().forEach(function (key) {
+        ordered[key] = that.attachedPseudoElements[key];
+      });
+
+      return ordered;
+    }
+
+    /**
+     * Returns the number of attached pseudo elements.
+     *
+     * @returns {Number} The number of attached pseudo elements.
+     */
+
+  }, {
+    key: 'getAttachedPseudoElementsCount',
+    value: function getAttachedPseudoElementsCount() {
+      return Object.keys(this.attachedPseudoElements).length;
+    }
+
+    /**
+     * Returns whether this atom is a heteroatom (not C and not H).
+     *
+     * @returns {Boolean} A boolean indicating whether this atom is a heteroatom.
+     */
+
+  }, {
+    key: 'isHeteroAtom',
+    value: function isHeteroAtom() {
+      return this.element !== 'C' && this.element !== 'H';
+    }
+
+    /**
+     * Defines this atom as the anchor for a ring. When doing repositionings of the vertices and the vertex associated with this atom is moved, the center of this ring is moved as well.
+     *
+     * @param {Number} ringId A ring id.
+     */
+
+  }, {
+    key: 'addAnchoredRing',
+    value: function addAnchoredRing(ringId) {
+      if (!ArrayHelper.contains(this.anchoredRings, {
+        value: ringId
+      })) {
+        this.anchoredRings.push(ringId);
+      }
+    }
+
+    /**
+     * Returns the number of ringbonds (breaks in rings to generate the MST of the smiles) within this atom is connected to.
+     *
+     * @returns {Number} The number of ringbonds this atom is connected to.
+     */
+
+  }, {
+    key: 'getRingbondCount',
+    value: function getRingbondCount() {
+      return this.ringbonds.length;
+    }
+
+    /**
+     * Backs up the current rings.
+     */
+
+  }, {
+    key: 'backupRings',
+    value: function backupRings() {
+      this.originalRings = Array(this.rings.length);
+
+      for (var i = 0; i < this.rings.length; i++) {
+        this.originalRings[i] = this.rings[i];
+      }
+    }
+
+    /**
+     * Restores the most recent backed up rings.
+     */
+
+  }, {
+    key: 'restoreRings',
+    value: function restoreRings() {
+      this.rings = Array(this.originalRings.length);
+
+      for (var i = 0; i < this.originalRings.length; i++) {
+        this.rings[i] = this.originalRings[i];
+      }
+    }
+
+    /**
+     * Checks whether or not two atoms share a common ringbond id. A ringbond is a break in a ring created when generating the spanning tree of a structure.
+     *
+     * @param {Atom} atomA An atom.
+     * @param {Atom} atomB An atom.
+     * @returns {Boolean} A boolean indicating whether or not two atoms share a common ringbond.
+     */
+
+  }, {
+    key: 'haveCommonRingbond',
+    value: function haveCommonRingbond(atomA, atomB) {
+      for (var i = 0; i < atomA.ringbonds.length; i++) {
+        for (var j = 0; j < atomB.ringbonds.length; j++) {
+          if (atomA.ringbonds[i].id == atomB.ringbonds[j].id) {
+            return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Check whether or not the neighbouring elements of this atom equal the supplied array.
+     * 
+     * @param {String[]} arr An array containing all the elements that are neighbouring this atom. E.g. ['C', 'O', 'O', 'N']
+     * @returns {Boolean} A boolean indicating whether or not the neighbours match the supplied array of elements.
+     */
+
+  }, {
+    key: 'neighbouringElementsEqual',
+    value: function neighbouringElementsEqual(arr) {
+      if (arr.length !== this.neighbouringElements.length) {
+        return false;
+      }
+
+      arr.sort();
+      this.neighbouringElements.sort();
+
+      for (var i = 0; i < this.neighbouringElements.length; i++) {
+        if (arr[i] !== this.neighbouringElements[i]) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    /**
+     * Get the atomic number of this atom.
+     * 
+     * @returns {Number} The atomic number of this atom.
+     */
+
+  }, {
+    key: 'getAtomicNumber',
+    value: function getAtomicNumber() {
+      return Atom.atomicNumbers[this.element];
+    }
+
+    /**
+     * Get the maximum number of bonds for this atom.
+     * 
+     * @returns {Number} The maximum number of bonds of this atom.
+     */
+
+  }, {
+    key: 'getMaxBonds',
+    value: function getMaxBonds() {
+      return Atom.maxBonds[this.element];
+    }
+
+    /**
+     * A map mapping element symbols to their maximum bonds.
+     */
+
+  }], [{
+    key: 'maxBonds',
+    get: function get() {
+      return {
+        'H': 1,
+        'C': 4,
+        'N': 3,
+        'O': 2,
+        'P': 3,
+        'S': 2,
+        'B': 3,
+        'F': 1,
+        'I': 1,
+        'Cl': 1,
+        'Br': 1
+      };
+    }
+
+    /**
+     * A map mapping element symbols to the atomic number.
+     */
+
+  }, {
+    key: 'atomicNumbers',
+    get: function get() {
+      return {
+        'H': 1,
+        'He': 2,
+        'Li': 3,
+        'Be': 4,
+        'B': 5,
+        'b': 5,
+        'C': 6,
+        'c': 6,
+        'N': 7,
+        'n': 7,
+        'O': 8,
+        'o': 8,
+        'F': 9,
+        'Ne': 10,
+        'Na': 11,
+        'Mg': 12,
+        'Al': 13,
+        'Si': 14,
+        'P': 15,
+        'p': 15,
+        'S': 16,
+        's': 16,
+        'Cl': 17,
+        'Ar': 18,
+        'K': 19,
+        'Ca': 20,
+        'Sc': 21,
+        'Ti': 22,
+        'V': 23,
+        'Cr': 24,
+        'Mn': 25,
+        'Fe': 26,
+        'Co': 27,
+        'Ni': 28,
+        'Cu': 29,
+        'Zn': 30,
+        'Ga': 31,
+        'Ge': 32,
+        'As': 33,
+        'Se': 34,
+        'Br': 35,
+        'Kr': 36,
+        'Rb': 37,
+        'Sr': 38,
+        'Y': 39,
+        'Zr': 40,
+        'Nb': 41,
+        'Mo': 42,
+        'Tc': 43,
+        'Ru': 44,
+        'Rh': 45,
+        'Pd': 46,
+        'Ag': 47,
+        'Cd': 48,
+        'In': 49,
+        'Sn': 50,
+        'Sb': 51,
+        'Te': 52,
+        'I': 53,
+        'Xe': 54,
+        'Cs': 55,
+        'Ba': 56,
+        'La': 57,
+        'Ce': 58,
+        'Pr': 59,
+        'Nd': 60,
+        'Pm': 61,
+        'Sm': 62,
+        'Eu': 63,
+        'Gd': 64,
+        'Tb': 65,
+        'Dy': 66,
+        'Ho': 67,
+        'Er': 68,
+        'Tm': 69,
+        'Yb': 70,
+        'Lu': 71,
+        'Hf': 72,
+        'Ta': 73,
+        'W': 74,
+        'Re': 75,
+        'Os': 76,
+        'Ir': 77,
+        'Pt': 78,
+        'Au': 79,
+        'Hg': 80,
+        'Tl': 81,
+        'Pb': 82,
+        'Bi': 83,
+        'Po': 84,
+        'At': 85,
+        'Rn': 86,
+        'Fr': 87,
+        'Ra': 88,
+        'Ac': 89,
+        'Th': 90,
+        'Pa': 91,
+        'U': 92,
+        'Np': 93,
+        'Pu': 94,
+        'Am': 95,
+        'Cm': 96,
+        'Bk': 97,
+        'Cf': 98,
+        'Es': 99,
+        'Fm': 100,
+        'Md': 101,
+        'No': 102,
+        'Lr': 103,
+        'Rf': 104,
+        'Db': 105,
+        'Sg': 106,
+        'Bh': 107,
+        'Hs': 108,
+        'Mt': 109,
+        'Ds': 110,
+        'Rg': 111,
+        'Cn': 112,
+        'Uut': 113,
+        'Uuq': 114,
+        'Uup': 115,
+        'Uuh': 116,
+        'Uus': 117,
+        'Uuo': 118
+      };
+    }
+
+    /**
+     * A map mapping element symbols to the atomic mass.
+     */
+
+  }, {
+    key: 'mass',
+    get: function get() {
+      return {
+        'H': 1,
+        'He': 2,
+        'Li': 3,
+        'Be': 4,
+        'B': 5,
+        'b': 5,
+        'C': 6,
+        'c': 6,
+        'N': 7,
+        'n': 7,
+        'O': 8,
+        'o': 8,
+        'F': 9,
+        'Ne': 10,
+        'Na': 11,
+        'Mg': 12,
+        'Al': 13,
+        'Si': 14,
+        'P': 15,
+        'p': 15,
+        'S': 16,
+        's': 16,
+        'Cl': 17,
+        'Ar': 18,
+        'K': 19,
+        'Ca': 20,
+        'Sc': 21,
+        'Ti': 22,
+        'V': 23,
+        'Cr': 24,
+        'Mn': 25,
+        'Fe': 26,
+        'Co': 27,
+        'Ni': 28,
+        'Cu': 29,
+        'Zn': 30,
+        'Ga': 31,
+        'Ge': 32,
+        'As': 33,
+        'Se': 34,
+        'Br': 35,
+        'Kr': 36,
+        'Rb': 37,
+        'Sr': 38,
+        'Y': 39,
+        'Zr': 40,
+        'Nb': 41,
+        'Mo': 42,
+        'Tc': 43,
+        'Ru': 44,
+        'Rh': 45,
+        'Pd': 46,
+        'Ag': 47,
+        'Cd': 48,
+        'In': 49,
+        'Sn': 50,
+        'Sb': 51,
+        'Te': 52,
+        'I': 53,
+        'Xe': 54,
+        'Cs': 55,
+        'Ba': 56,
+        'La': 57,
+        'Ce': 58,
+        'Pr': 59,
+        'Nd': 60,
+        'Pm': 61,
+        'Sm': 62,
+        'Eu': 63,
+        'Gd': 64,
+        'Tb': 65,
+        'Dy': 66,
+        'Ho': 67,
+        'Er': 68,
+        'Tm': 69,
+        'Yb': 70,
+        'Lu': 71,
+        'Hf': 72,
+        'Ta': 73,
+        'W': 74,
+        'Re': 75,
+        'Os': 76,
+        'Ir': 77,
+        'Pt': 78,
+        'Au': 79,
+        'Hg': 80,
+        'Tl': 81,
+        'Pb': 82,
+        'Bi': 83,
+        'Po': 84,
+        'At': 85,
+        'Rn': 86,
+        'Fr': 87,
+        'Ra': 88,
+        'Ac': 89,
+        'Th': 90,
+        'Pa': 91,
+        'U': 92,
+        'Np': 93,
+        'Pu': 94,
+        'Am': 95,
+        'Cm': 96,
+        'Bk': 97,
+        'Cf': 98,
+        'Es': 99,
+        'Fm': 100,
+        'Md': 101,
+        'No': 102,
+        'Lr': 103,
+        'Rf': 104,
+        'Db': 105,
+        'Sg': 106,
+        'Bh': 107,
+        'Hs': 108,
+        'Mt': 109,
+        'Ds': 110,
+        'Rg': 111,
+        'Cn': 112,
+        'Uut': 113,
+        'Uuq': 114,
+        'Uup': 115,
+        'Uuh': 116,
+        'Uus': 117,
+        'Uuo': 118
+      };
+    }
+  }]);
+
+  return Atom;
+}();
+
+module.exports = Atom;
+
+},{"./ArrayHelper":2,"./Ring":11,"./Vertex":15}],4:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var MathHelper = require('./MathHelper');
+var Vector2 = require('./Vector2');
+var Line = require('./Line');
+var Vertex = require('./Vertex');
+var Ring = require('./Ring');
+
+/** 
+ * A class wrapping a canvas element.
+ * 
+ * @property {HTMLElement} canvas The HTML element for the canvas associated with this CanvasWrapper instance.
+ * @property {CanvasRenderingContext2D} ctx The CanvasRenderingContext2D of the canvas associated with this CanvasWrapper instance.
+ * @property {Object} colors The colors object as defined in the SmilesDrawer options.
+ * @property {Object} opts The SmilesDrawer options.
+ * @property {Number} drawingWidth The width of the canvas.
+ * @property {Number} drawingHeight The height of the canvas.
+ * @property {Number} offsetX The horizontal offset required for centering the drawing.
+ * @property {Number} offsetY The vertical offset required for centering the drawing.
+ * @property {Number} fontLarge The large font size in pt.
+ * @property {Number} fontSmall The small font size in pt.
+ */
+
+var CanvasWrapper = function () {
+    /**
+     * The constructor for the class CanvasWrapper.
+     *
+     * @param {(String|HTMLElement)} target The canvas id or the canvas HTMLElement.
+     * @param {Object} theme A theme from the smiles drawer options.
+     * @param {Object} options The smiles drawer options object.
+     */
+    function CanvasWrapper(target, theme, options) {
+        _classCallCheck(this, CanvasWrapper);
+
+        if (typeof target === 'string' || target instanceof String) {
+            this.canvas = document.getElementById(target);
+        } else {
+            this.canvas = target;
+        }
+
+        this.ctx = this.canvas.getContext('2d');
+        this.colors = theme;
+        this.opts = options;
+        this.drawingWidth = 0.0;
+        this.drawingHeight = 0.0;
+        this.offsetX = 0.0;
+        this.offsetY = 0.0;
+
+        this.fontLarge = this.opts.fontSizeLarge + 'pt Helvetica, Arial, sans-serif';
+        this.fontSmall = this.opts.fontSizeSmall + 'pt Helvetica, Arial, sans-serif';
+
+        this.updateSize(this.opts.width, this.opts.height);
+
+        this.ctx.font = this.fontLarge;
+        this.hydrogenWidth = this.ctx.measureText('H').width;
+        this.halfHydrogenWidth = this.hydrogenWidth / 2.0;
+        this.halfBondThickness = this.opts.bondThickness / 2.0;
+
+        // TODO: Find out why clear was here.
+        // this.clear();
+    }
+
+    /**
+     * Update the width and height of the canvas
+     * 
+     * @param {Number} width 
+     * @param {Number} height 
+     */
+
+
+    _createClass(CanvasWrapper, [{
+        key: 'updateSize',
+        value: function updateSize(width, height) {
+            this.devicePixelRatio = window.devicePixelRatio || 1;
+            this.backingStoreRatio = this.ctx.webkitBackingStorePixelRatio || this.ctx.mozBackingStorePixelRatio || this.ctx.msBackingStorePixelRatio || this.ctx.oBackingStorePixelRatio || this.ctx.backingStorePixelRatio || 1;
+            this.ratio = this.devicePixelRatio / this.backingStoreRatio;
+
+            if (this.ratio !== 1) {
+                this.canvas.width = width * this.ratio;
+                this.canvas.height = height * this.ratio;
+                this.canvas.style.width = width + 'px';
+                this.canvas.style.height = height + 'px';
+                this.ctx.setTransform(this.ratio, 0, 0, this.ratio, 0, 0);
+            } else {
+                this.canvas.width = width * this.ratio;
+                this.canvas.height = height * this.ratio;
+            }
+        }
+
+        /**
+         * Sets a provided theme.
+         *
+         * @param {Object} theme A theme from the smiles drawer options.
+         */
+
+    }, {
+        key: 'setTheme',
+        value: function setTheme(theme) {
+            this.colors = theme;
+        }
+
+        /**
+         * Scale the canvas based on vertex positions.
+         *
+         * @param {Vertex[]} vertices An array of vertices containing the vertices associated with the current molecule.
+         */
+
+    }, {
+        key: 'scale',
+        value: function scale(vertices) {
+            // Figure out the final size of the image
+            var maxX = -Number.MAX_VALUE;
+            var maxY = -Number.MAX_VALUE;
+            var minX = Number.MAX_VALUE;
+            var minY = Number.MAX_VALUE;
+
+            for (var i = 0; i < vertices.length; i++) {
+                if (!vertices[i].value.isDrawn) {
+                    continue;
+                }
+
+                var p = vertices[i].position;
+
+                if (maxX < p.x) maxX = p.x;
+                if (maxY < p.y) maxY = p.y;
+                if (minX > p.x) minX = p.x;
+                if (minY > p.y) minY = p.y;
+            }
+
+            // Add padding
+            var padding = this.opts.padding;
+            maxX += padding;
+            maxY += padding;
+            minX -= padding;
+            minY -= padding;
+
+            this.drawingWidth = maxX - minX;
+            this.drawingHeight = maxY - minY;
+
+            var scaleX = this.canvas.offsetWidth / this.drawingWidth;
+            var scaleY = this.canvas.offsetHeight / this.drawingHeight;
+
+            var scale = scaleX < scaleY ? scaleX : scaleY;
+
+            this.ctx.scale(scale, scale);
+
+            this.offsetX = -minX;
+            this.offsetY = -minY;
+
+            // Center
+            if (scaleX < scaleY) {
+                this.offsetY += this.canvas.offsetHeight / (2.0 * scale) - this.drawingHeight / 2.0;
+            } else {
+                this.offsetX += this.canvas.offsetWidth / (2.0 * scale) - this.drawingWidth / 2.0;
+            }
+        }
+
+        /**
+         * Resets the transform of the canvas.
+         */
+
+    }, {
+        key: 'reset',
+        value: function reset() {
+            this.ctx.setTransform(1, 0, 0, 1, 0, 0);
+        }
+
+        /**
+         * Returns the hex code of a color associated with a key from the current theme.
+         *
+         * @param {String} key The color key in the theme (e.g. C, N, BACKGROUND, ...).
+         * @returns {String} A color hex value.
+         */
+
+    }, {
+        key: 'getColor',
+        value: function getColor(key) {
+            key = key.toUpperCase();
+
+            if (key in this.colors) {
+                return this.colors[key];
+            }
+
+            return this.colors['C'];
+        }
+
+        /**
+         * Draws a circle to a canvas context.
+         * @param {Number} x The x coordinate of the circles center.
+         * @param {Number} y The y coordinate of the circles center.
+         * @param {Number} radius The radius of the circle
+         * @param {String} color A hex encoded color.
+         * @param {Boolean} [fill=true] Whether to fill or stroke the circle.
+         * @param {Boolean} [debug=false] Draw in debug mode.
+         * @param {String} [debugText=''] A debug message.
+         */
+
+    }, {
+        key: 'drawCircle',
+        value: function drawCircle(x, y, radius, color) {
+            var fill = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : true;
+            var debug = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
+            var debugText = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : '';
+
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            ctx.save();
+            ctx.lineWidth = 1.5;
+            ctx.beginPath();
+            ctx.arc(x + offsetX, y + offsetY, radius, 0, MathHelper.twoPI, true);
+            ctx.closePath();
+
+            if (debug) {
+                if (fill) {
+                    ctx.fillStyle = '#f00';
+                    ctx.fill();
+                } else {
+                    ctx.strokeStyle = '#f00';
+                    ctx.stroke();
+                }
+
+                this.drawDebugText(x, y, debugText);
+            } else {
+                if (fill) {
+                    ctx.fillStyle = color;
+                    ctx.fill();
+                } else {
+                    ctx.strokeStyle = color;
+                    ctx.stroke();
+                }
+            }
+
+            ctx.restore();
+        }
+
+        /**
+         * Draw a line to a canvas.
+         *
+         * @param {Line} line A line.
+         * @param {Boolean} [dashed=false] Whether or not the line is dashed.
+         * @param {Number} [alpha=1.0] The alpha value of the color.
+         */
+
+    }, {
+        key: 'drawLine',
+        value: function drawLine(line) {
+            var dashed = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+            var alpha = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1.0;
+
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            // Add a shadow behind the line
+            var shortLine = line.clone().shorten(4.0);
+
+            var l = shortLine.getLeftVector().clone();
+            var r = shortLine.getRightVector().clone();
+
+            l.x += offsetX;
+            l.y += offsetY;
+
+            r.x += offsetX;
+            r.y += offsetY;
+
+            // Draw the "shadow"
+            if (!dashed) {
+                ctx.save();
+                ctx.globalCompositeOperation = 'destination-out';
+                ctx.beginPath();
+                ctx.moveTo(l.x, l.y);
+                ctx.lineTo(r.x, r.y);
+                ctx.lineCap = 'round';
+                ctx.lineWidth = this.opts.bondThickness + 1.2;
+                ctx.strokeStyle = this.getColor('BACKGROUND');
+                ctx.stroke();
+                ctx.globalCompositeOperation = 'source-over';
+                ctx.restore();
+            }
+
+            l = line.getLeftVector().clone();
+            r = line.getRightVector().clone();
+
+            l.x += offsetX;
+            l.y += offsetY;
+
+            r.x += offsetX;
+            r.y += offsetY;
+
+            ctx.save();
+            ctx.beginPath();
+            ctx.moveTo(l.x, l.y);
+            ctx.lineTo(r.x, r.y);
+            ctx.lineCap = 'round';
+            ctx.lineWidth = this.opts.bondThickness;
+
+            var gradient = this.ctx.createLinearGradient(l.x, l.y, r.x, r.y);
+            gradient.addColorStop(0.4, this.getColor(line.getLeftElement()) || this.getColor('C'));
+            gradient.addColorStop(0.6, this.getColor(line.getRightElement()) || this.getColor('C'));
+
+            if (dashed) {
+                ctx.setLineDash([1, 1.5]);
+                ctx.lineWidth = this.opts.bondThickness / 1.5;
+            }
+
+            if (alpha < 1.0) {
+                ctx.globalAlpha = alpha;
+            }
+
+            ctx.strokeStyle = gradient;
+
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        /**
+         * Draw a wedge on the canvas.
+         *
+         * @param {Line} line A line.
+         * @param {Number} width The wedge width.
+         */
+
+    }, {
+        key: 'drawWedge',
+        value: function drawWedge(line) {
+            var width = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 1.0;
+
+            if (isNaN(line.from.x) || isNaN(line.from.y) || isNaN(line.to.x) || isNaN(line.to.y)) {
+                return;
+            }
+
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            // Add a shadow behind the line
+            var shortLine = line.clone().shorten(5.0);
+
+            var l = shortLine.getLeftVector().clone();
+            var r = shortLine.getRightVector().clone();
+
+            l.x += offsetX;
+            l.y += offsetY;
+
+            r.x += offsetX;
+            r.y += offsetY;
+
+            l = line.getLeftVector().clone();
+            r = line.getRightVector().clone();
+
+            l.x += offsetX;
+            l.y += offsetY;
+
+            r.x += offsetX;
+            r.y += offsetY;
+
+            ctx.save();
+
+            var normals = Vector2.normals(l, r);
+
+            normals[0].normalize();
+            normals[1].normalize();
+
+            var isRightChiralCenter = line.getRightChiral();
+
+            var start = l;
+            var end = r;
+
+            if (isRightChiralCenter) {
+                start = r;
+                end = l;
+            }
+
+            var t = Vector2.add(start, Vector2.multiplyScalar(normals[0], this.halfBondThickness));
+            var u = Vector2.add(end, Vector2.multiplyScalar(normals[0], 1.5 + this.halfBondThickness));
+            var v = Vector2.add(end, Vector2.multiplyScalar(normals[1], 1.5 + this.halfBondThickness));
+            var w = Vector2.add(start, Vector2.multiplyScalar(normals[1], this.halfBondThickness));
+
+            ctx.beginPath();
+            ctx.moveTo(t.x, t.y);
+            ctx.lineTo(u.x, u.y);
+            ctx.lineTo(v.x, v.y);
+            ctx.lineTo(w.x, w.y);
+
+            var gradient = this.ctx.createRadialGradient(r.x, r.y, this.opts.bondLength, r.x, r.y, 0);
+            gradient.addColorStop(0.4, this.getColor(line.getLeftElement()) || this.getColor('C'));
+            gradient.addColorStop(0.6, this.getColor(line.getRightElement()) || this.getColor('C'));
+
+            ctx.fillStyle = gradient;
+
+            ctx.fill();
+            ctx.restore();
+        }
+
+        /**
+         * Draw a dashed wedge on the canvas.
+         *
+         * @param {Line} line A line.
+         */
+
+    }, {
+        key: 'drawDashedWedge',
+        value: function drawDashedWedge(line) {
+            if (isNaN(line.from.x) || isNaN(line.from.y) || isNaN(line.to.x) || isNaN(line.to.y)) {
+                return;
+            }
+
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            var l = line.getLeftVector().clone();
+            var r = line.getRightVector().clone();
+
+            l.x += offsetX;
+            l.y += offsetY;
+
+            r.x += offsetX;
+            r.y += offsetY;
+
+            ctx.save();
+
+            var normals = Vector2.normals(l, r);
+
+            normals[0].normalize();
+            normals[1].normalize();
+
+            var isRightChiralCenter = line.getRightChiral();
+
+            var start = void 0;
+            var end = void 0;
+            var sStart = void 0;
+            var sEnd = void 0;
+
+            var shortLine = line.clone();
+
+            if (isRightChiralCenter) {
+                start = r;
+                end = l;
+
+                shortLine.shortenRight(1.0);
+
+                sStart = shortLine.getRightVector().clone();
+                sEnd = shortLine.getLeftVector().clone();
+            } else {
+                start = l;
+                end = r;
+
+                shortLine.shortenLeft(1.0);
+
+                sStart = shortLine.getLeftVector().clone();
+                sEnd = shortLine.getRightVector().clone();
+            }
+
+            sStart.x += offsetX;
+            sStart.y += offsetY;
+            sEnd.x += offsetX;
+            sEnd.y += offsetY;
+
+            var dir = Vector2.subtract(end, start).normalize();
+            ctx.strokeStyle = this.getColor('C');
+            ctx.lineCap = 'round';
+            ctx.lineWidth = this.opts.bondThickness;
+            ctx.beginPath();
+            var length = line.getLength();
+            var step = 1.25 / (length / (this.opts.bondThickness * 3.0));
+
+            var changed = false;
+            for (var t = 0.0; t < 1.0; t += step) {
+                var to = Vector2.multiplyScalar(dir, t * length);
+                var startDash = Vector2.add(start, to);
+                var width = 1.5 * t;
+                var dashOffset = Vector2.multiplyScalar(normals[0], width);
+
+                if (!changed && t > 0.5) {
+                    ctx.stroke();
+                    ctx.beginPath();
+                    ctx.strokeStyle = this.getColor(line.getRightElement()) || this.getColor('C');
+                    changed = true;
+                }
+
+                startDash.subtract(dashOffset);
+                ctx.moveTo(startDash.x, startDash.y);
+                startDash.add(Vector2.multiplyScalar(dashOffset, 2.0));
+                ctx.lineTo(startDash.x, startDash.y);
+            }
+
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        /**
+         * Draws a debug text message at a given position
+         *
+         * @param {Number} x The x coordinate.
+         * @param {Number} y The y coordinate.
+         * @param {String} text The debug text.
+         */
+
+    }, {
+        key: 'drawDebugText',
+        value: function drawDebugText(x, y, text) {
+            var ctx = this.ctx;
+
+            ctx.save();
+            ctx.font = '5px Droid Sans, sans-serif';
+            ctx.textAlign = 'start';
+            ctx.textBaseline = 'top';
+            ctx.fillStyle = '#ff0000';
+            ctx.fillText(text, x + this.offsetX, y + this.offsetY);
+            ctx.restore();
+        }
+
+        /**
+         * Draw a ball to the canvas.
+         *
+         * @param {Number} x The x position of the text.
+         * @param {Number} y The y position of the text.
+         * @param {String} elementName The name of the element (single-letter).
+         */
+
+    }, {
+        key: 'drawBall',
+        value: function drawBall(x, y, elementName) {
+            var ctx = this.ctx;
+
+            ctx.save();
+            ctx.beginPath();
+            ctx.arc(x + this.offsetX, y + this.offsetY, this.opts.bondLength / 4.5, 0, MathHelper.twoPI, false);
+            ctx.fillStyle = this.getColor(elementName);
+            ctx.fill();
+            ctx.restore();
+        }
+
+        /**
+         * Draw a point to the canvas.
+         *
+         * @param {Number} x The x position of the point.
+         * @param {Number} y The y position of the point.
+         * @param {String} elementName The name of the element (single-letter).
+         */
+
+    }, {
+        key: 'drawPoint',
+        value: function drawPoint(x, y, elementName) {
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            ctx.save();
+            ctx.globalCompositeOperation = 'destination-out';
+            ctx.beginPath();
+            ctx.arc(x + offsetX, y + offsetY, 1.5, 0, MathHelper.twoPI, true);
+            ctx.closePath();
+            ctx.fill();
+            ctx.globalCompositeOperation = 'source-over';
+
+            ctx.beginPath();
+            ctx.arc(x + this.offsetX, y + this.offsetY, 0.75, 0, MathHelper.twoPI, false);
+            ctx.fillStyle = this.getColor(elementName);
+            ctx.fill();
+            ctx.restore();
+        }
+
+        /**
+         * Draw a text to the canvas.
+         *
+         * @param {Number} x The x position of the text.
+         * @param {Number} y The y position of the text.
+         * @param {String} elementName The name of the element (single-letter).
+         * @param {Number} hydrogens The number of hydrogen atoms.
+         * @param {String} direction The direction of the text in relation to the associated vertex.
+         * @param {Boolean} isTerminal A boolean indicating whether or not the vertex is terminal.
+         * @param {Number} charge The charge of the atom.
+         * @param {Number} isotope The isotope number.
+         * @param {Object} attachedPseudoElement A map with containing information for pseudo elements or concatinated elements. The key is comprised of the element symbol and the hydrogen count.
+         * @param {String} attachedPseudoElement.element The element symbol.
+         * @param {Number} attachedPseudoElement.count The number of occurences that match the key.
+         * @param {Number} attachedPseudoElement.hyrogenCount The number of hydrogens attached to each atom matching the key.
+         */
+
+    }, {
+        key: 'drawText',
+        value: function drawText(x, y, elementName, hydrogens, direction, isTerminal, charge, isotope) {
+            var attachedPseudoElement = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : {};
+
+            var ctx = this.ctx;
+            var offsetX = this.offsetX;
+            var offsetY = this.offsetY;
+
+            ctx.save();
+
+            ctx.textAlign = 'start';
+            ctx.textBaseline = 'alphabetic';
+
+            var pseudoElementHandled = false;
+
+            // Charge
+            var chargeText = '';
+            var chargeWidth = 0;
+
+            if (charge) {
+                chargeText = this.getChargeText(charge);
+
+                ctx.font = this.fontSmall;
+                chargeWidth = ctx.measureText(chargeText).width;
+            }
+
+            var isotopeText = '0';
+            var isotopeWidth = 0;
+
+            if (isotope > 0) {
+                isotopeText = isotope.toString();
+                ctx.font = this.fontSmall;
+                isotopeWidth = ctx.measureText(isotopeText).width;
+            }
+
+            // TODO: Better handle exceptions
+            // Exception for nitro (draw nitro as NO2 instead of N+O-O)
+            if (charge === 1 && elementName === 'N' && attachedPseudoElement.hasOwnProperty('0O') && attachedPseudoElement.hasOwnProperty('0O-1')) {
+                attachedPseudoElement = { '0O': { element: 'O', count: 2, hydrogenCount: 0, previousElement: 'C', charge: '' } };
+                charge = 0;
+            }
+
+            ctx.font = this.fontLarge;
+            ctx.fillStyle = this.getColor('BACKGROUND');
+
+            var dim = ctx.measureText(elementName);
+
+            dim.totalWidth = dim.width + chargeWidth;
+            dim.height = parseInt(this.fontLarge, 10);
+
+            var r = dim.width > this.opts.fontSizeLarge ? dim.width : this.opts.fontSizeLarge;
+            r /= 1.5;
+
+            ctx.globalCompositeOperation = 'destination-out';
+            ctx.beginPath();
+            ctx.arc(x + offsetX, y + offsetY, r, 0, MathHelper.twoPI, true);
+            ctx.closePath();
+            ctx.fill();
+            ctx.globalCompositeOperation = 'source-over';
+
+            var cursorPos = -dim.width / 2.0;
+            var cursorPosLeft = -dim.width / 2.0;
+
+            ctx.fillStyle = this.getColor(elementName);
+            ctx.fillText(elementName, x + offsetX + cursorPos, y + this.opts.halfFontSizeLarge + offsetY);
+            cursorPos += dim.width;
+
+            if (charge) {
+                ctx.font = this.fontSmall;
+                ctx.fillText(chargeText, x + offsetX + cursorPos, y - this.opts.fifthFontSizeSmall + offsetY);
+                cursorPos += chargeWidth;
+            }
+
+            if (isotope > 0) {
+                ctx.font = this.fontSmall;
+                ctx.fillText(isotopeText, x + offsetX + cursorPosLeft - isotopeWidth, y - this.opts.fifthFontSizeSmall + offsetY);
+                cursorPosLeft -= isotopeWidth;
+            }
+
+            ctx.font = this.fontLarge;
+
+            var hydrogenWidth = 0;
+            var hydrogenCountWidth = 0;
+
+            if (hydrogens === 1) {
+                var hx = x + offsetX;
+                var hy = y + offsetY + this.opts.halfFontSizeLarge;
+
+                hydrogenWidth = this.hydrogenWidth;
+                cursorPosLeft -= hydrogenWidth;
+
+                if (direction === 'left') {
+                    hx += cursorPosLeft;
+                } else if (direction === 'right') {
+                    hx += cursorPos;
+                } else if (direction === 'up' && isTerminal) {
+                    hx += cursorPos;
+                } else if (direction === 'down' && isTerminal) {
+                    hx += cursorPos;
+                } else if (direction === 'up' && !isTerminal) {
+                    hy -= this.opts.fontSizeLarge + this.opts.quarterFontSizeLarge;
+                    hx -= this.halfHydrogenWidth;
+                } else if (direction === 'down' && !isTerminal) {
+                    hy += this.opts.fontSizeLarge + this.opts.quarterFontSizeLarge;
+                    hx -= this.halfHydrogenWidth;
+                }
+
+                ctx.fillText('H', hx, hy);
+
+                cursorPos += hydrogenWidth;
+            } else if (hydrogens > 1) {
+                var _hx = x + offsetX;
+                var _hy = y + offsetY + this.opts.halfFontSizeLarge;
+
+                hydrogenWidth = this.hydrogenWidth;
+                ctx.font = this.fontSmall;
+                hydrogenCountWidth = ctx.measureText(hydrogens).width;
+                cursorPosLeft -= hydrogenWidth + hydrogenCountWidth;
+
+                if (direction === 'left') {
+                    _hx += cursorPosLeft;
+                } else if (direction === 'right') {
+                    _hx += cursorPos;
+                } else if (direction === 'up' && isTerminal) {
+                    _hx += cursorPos;
+                } else if (direction === 'down' && isTerminal) {
+                    _hx += cursorPos;
+                } else if (direction === 'up' && !isTerminal) {
+                    _hy -= this.opts.fontSizeLarge + this.opts.quarterFontSizeLarge;
+                    _hx -= this.halfHydrogenWidth;
+                } else if (direction === 'down' && !isTerminal) {
+                    _hy += this.opts.fontSizeLarge + this.opts.quarterFontSizeLarge;
+                    _hx -= this.halfHydrogenWidth;
+                }
+
+                ctx.font = this.fontLarge;
+                ctx.fillText('H', _hx, _hy);
+
+                ctx.font = this.fontSmall;
+                ctx.fillText(hydrogens, _hx + this.halfHydrogenWidth + hydrogenCountWidth, _hy + this.opts.fifthFontSizeSmall);
+
+                cursorPos += hydrogenWidth + this.halfHydrogenWidth + hydrogenCountWidth;
+            }
+
+            if (pseudoElementHandled) {
+                ctx.restore();
+                return;
+            }
+
+            for (var key in attachedPseudoElement) {
+                if (!attachedPseudoElement.hasOwnProperty(key)) {
+                    continue;
+                }
+
+                var openParenthesisWidth = 0;
+                var closeParenthesisWidth = 0;
+
+                var element = attachedPseudoElement[key].element;
+                var elementCount = attachedPseudoElement[key].count;
+                var hydrogenCount = attachedPseudoElement[key].hydrogenCount;
+                var elementCharge = attachedPseudoElement[key].charge;
+
+                ctx.font = this.fontLarge;
+
+                if (elementCount > 1 && hydrogenCount > 0) {
+                    openParenthesisWidth = ctx.measureText('(').width;
+                    closeParenthesisWidth = ctx.measureText(')').width;
+                }
+
+                var elementWidth = ctx.measureText(element).width;
+                var elementCountWidth = 0;
+
+                var elementChargeText = '';
+                var elementChargeWidth = 0;
+
+                hydrogenWidth = 0;
+
+                if (hydrogenCount > 0) {
+                    hydrogenWidth = this.hydrogenWidth;
+                }
+
+                ctx.font = this.fontSmall;
+
+                if (elementCount > 1) {
+                    elementCountWidth = ctx.measureText(elementCount).width;
+                }
+
+                if (elementCharge !== 0) {
+                    elementChargeText = this.getChargeText(elementCharge);
+                    elementChargeWidth = ctx.measureText(elementChargeText).width;
+                }
+
+                hydrogenCountWidth = 0;
+
+                if (hydrogenCount > 1) {
+                    hydrogenCountWidth = ctx.measureText(hydrogenCount).width;
+                }
+
+                ctx.font = this.fontLarge;
+
+                var _hx2 = x + offsetX;
+                var _hy2 = y + offsetY + this.opts.halfFontSizeLarge;
+
+                ctx.fillStyle = this.getColor(element);
+
+                if (elementCount > 0) {
+                    cursorPosLeft -= elementCountWidth;
+                }
+
+                if (elementCount > 1 && hydrogenCount > 0) {
+                    if (direction === 'left') {
+                        cursorPosLeft -= closeParenthesisWidth;
+                        ctx.fillText(')', _hx2 + cursorPosLeft, _hy2);
+                    } else {
+                        ctx.fillText('(', _hx2 + cursorPos, _hy2);
+                        cursorPos += openParenthesisWidth;
+                    }
+                }
+
+                if (direction === 'left') {
+                    cursorPosLeft -= elementWidth;
+                    ctx.fillText(element, _hx2 + cursorPosLeft, _hy2);
+                } else {
+                    ctx.fillText(element, _hx2 + cursorPos, _hy2);
+                    cursorPos += elementWidth;
+                }
+
+                if (hydrogenCount > 0) {
+                    if (direction === 'left') {
+                        cursorPosLeft -= hydrogenWidth + hydrogenCountWidth;
+                        ctx.fillText('H', _hx2 + cursorPosLeft, _hy2);
+
+                        if (hydrogenCount > 1) {
+                            ctx.font = this.fontSmall;
+                            ctx.fillText(hydrogenCount, _hx2 + cursorPosLeft + hydrogenWidth, _hy2 + this.opts.fifthFontSizeSmall);
+                        }
+                    } else {
+                        ctx.fillText('H', _hx2 + cursorPos, _hy2);
+                        cursorPos += hydrogenWidth;
+
+                        if (hydrogenCount > 1) {
+                            ctx.font = this.fontSmall;
+                            ctx.fillText(hydrogenCount, _hx2 + cursorPos, _hy2 + this.opts.fifthFontSizeSmall);
+                            cursorPos += hydrogenCountWidth;
+                        }
+                    }
+                }
+
+                ctx.font = this.fontLarge;
+
+                if (elementCount > 1 && hydrogenCount > 0) {
+                    if (direction === 'left') {
+                        cursorPosLeft -= openParenthesisWidth;
+                        ctx.fillText('(', _hx2 + cursorPosLeft, _hy2);
+                    } else {
+                        ctx.fillText(')', _hx2 + cursorPos, _hy2);
+                        cursorPos += closeParenthesisWidth;
+                    }
+                }
+
+                ctx.font = this.fontSmall;
+
+                if (elementCount > 1) {
+                    if (direction === 'left') {
+                        ctx.fillText(elementCount, _hx2 + cursorPosLeft + openParenthesisWidth + closeParenthesisWidth + hydrogenWidth + hydrogenCountWidth + elementWidth, _hy2 + this.opts.fifthFontSizeSmall);
+                    } else {
+                        ctx.fillText(elementCount, _hx2 + cursorPos, _hy2 + this.opts.fifthFontSizeSmall);
+                        cursorPos += elementCountWidth;
+                    }
+                }
+
+                if (elementCharge !== 0) {
+                    if (direction === 'left') {
+                        ctx.fillText(elementChargeText, _hx2 + cursorPosLeft + openParenthesisWidth + closeParenthesisWidth + hydrogenWidth + hydrogenCountWidth + elementWidth, y - this.opts.fifthFontSizeSmall + offsetY);
+                    } else {
+                        ctx.fillText(elementChargeText, _hx2 + cursorPos, y - this.opts.fifthFontSizeSmall + offsetY);
+                        cursorPos += elementChargeWidth;
+                    }
+                }
+            }
+
+            ctx.restore();
+        }
+
+        /**
+         * Translate the integer indicating the charge to the appropriate text.
+         * @param {Number} charge The integer indicating the charge.
+         * @returns {String} A string representing a charge.
+         */
+
+    }, {
+        key: 'getChargeText',
+        value: function getChargeText(charge) {
+            if (charge === 1) {
+                return '+';
+            } else if (charge === 2) {
+                return '2+';
+            } else if (charge === -1) {
+                return '-';
+            } else if (charge === -2) {
+                return '2-';
+            } else {
+                return '';
+            }
+        }
+
+        /**
+         * Draws a dubug dot at a given coordinate and adds text.
+         *
+         * @param {Number} x The x coordinate.
+         * @param {Number} y The y coordindate.
+         * @param {String} [debugText=''] A string.
+         * @param {String} [color='#f00'] A color in hex form.
+         */
+
+    }, {
+        key: 'drawDebugPoint',
+        value: function drawDebugPoint(x, y) {
+            var debugText = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : '';
+            var color = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : '#f00';
+
+            this.drawCircle(x, y, 2, color, true, true, debugText);
+        }
+
+        /**
+         * Draws a ring inside a provided ring, indicating aromaticity.
+         *
+         * @param {Ring} ring A ring.
+         */
+
+    }, {
+        key: 'drawAromaticityRing',
+        value: function drawAromaticityRing(ring) {
+            var ctx = this.ctx;
+            var radius = MathHelper.apothemFromSideLength(this.opts.bondLength, ring.getSize());
+
+            ctx.save();
+            ctx.strokeStyle = this.getColor('C');
+            ctx.lineWidth = this.opts.bondThickness;
+            ctx.beginPath();
+            ctx.arc(ring.center.x + this.offsetX, ring.center.y + this.offsetY, radius - this.opts.bondSpacing, 0, Math.PI * 2, true);
+            ctx.closePath();
+            ctx.stroke();
+            ctx.restore();
+        }
+
+        /**
+         * Clear the canvas.
+         *
+         */
+
+    }, {
+        key: 'clear',
+        value: function clear() {
+            this.ctx.clearRect(0, 0, this.canvas.offsetWidth, this.canvas.offsetHeight);
+        }
+    }]);
+
+    return CanvasWrapper;
+}();
+
+module.exports = CanvasWrapper;
+
+},{"./Line":8,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],5:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var MathHelper = require('./MathHelper');
+var ArrayHelper = require('./ArrayHelper');
+var Vector2 = require('./Vector2');
+var Line = require('./Line');
+var Vertex = require('./Vertex');
+var Edge = require('./Edge');
+var Atom = require('./Atom');
+var Ring = require('./Ring');
+var RingConnection = require('./RingConnection');
+var CanvasWrapper = require('./CanvasWrapper');
+var Graph = require('./Graph');
+var SSSR = require('./SSSR');
+
+/** 
+ * The main class of the application representing the smiles drawer 
+ * 
+ * @property {Graph} graph The graph associated with this SmilesDrawer.Drawer instance.
+ * @property {Number} ringIdCounter An internal counter to keep track of ring ids.
+ * @property {Number} ringConnectionIdCounter An internal counter to keep track of ring connection ids.
+ * @property {CanvasWrapper} canvasWrapper The CanvasWrapper associated with this SmilesDrawer.Drawer instance.
+ * @property {Number} totalOverlapScore The current internal total overlap score.
+ * @property {Object} defaultOptions The default options.
+ * @property {Object} opts The merged options.
+ * @property {Object} theme The current theme.
+ */
+
+var Drawer = function () {
+  /**
+   * The constructor for the class SmilesDrawer.
+   *
+   * @param {Object} options An object containing custom values for different options. It is merged with the default options.
+   */
+  function Drawer(options) {
+    _classCallCheck(this, Drawer);
+
+    this.graph = null;
+    this.doubleBondConfigCount = 0;
+    this.doubleBondConfig = null;
+    this.ringIdCounter = 0;
+    this.ringConnectionIdCounter = 0;
+    this.canvasWrapper = null;
+    this.totalOverlapScore = 0;
+
+    this.defaultOptions = {
+      width: 500,
+      height: 500,
+      bondThickness: 0.6,
+      bondLength: 15,
+      shortBondLength: 0.85,
+      bondSpacing: 0.18 * 15,
+      atomVisualization: 'default',
+      isomeric: true,
+      debug: false,
+      terminalCarbons: false,
+      explicitHydrogens: false,
+      overlapSensitivity: 0.42,
+      overlapResolutionIterations: 1,
+      compactDrawing: true,
+      fontSizeLarge: 5,
+      fontSizeSmall: 3,
+      padding: 20.0,
+      themes: {
+        dark: {
+          C: '#fff',
+          O: '#e74c3c',
+          N: '#3498db',
+          F: '#27ae60',
+          CL: '#16a085',
+          BR: '#d35400',
+          I: '#8e44ad',
+          P: '#d35400',
+          S: '#f1c40f',
+          B: '#e67e22',
+          SI: '#e67e22',
+          H: '#fff',
+          BACKGROUND: '#141414'
+        },
+        light: {
+          C: '#222',
+          O: '#e74c3c',
+          N: '#3498db',
+          F: '#27ae60',
+          CL: '#16a085',
+          BR: '#d35400',
+          I: '#8e44ad',
+          P: '#d35400',
+          S: '#f1c40f',
+          B: '#e67e22',
+          SI: '#e67e22',
+          H: '#222',
+          BACKGROUND: '#fff'
+        }
+      }
+    };
+
+    this.opts = this.extend(true, this.defaultOptions, options);
+    this.opts.halfBondSpacing = this.opts.bondSpacing / 2.0;
+    this.opts.bondLengthSq = this.opts.bondLength * this.opts.bondLength;
+    this.opts.halfFontSizeLarge = this.opts.fontSizeLarge / 2.0;
+    this.opts.quarterFontSizeLarge = this.opts.fontSizeLarge / 4.0;
+    this.opts.fifthFontSizeSmall = this.opts.fontSizeSmall / 5.0;
+
+    // Set the default theme.
+    this.theme = this.opts.themes.dark;
+  }
+
+  /**
+   * A helper method to extend the default options with user supplied ones.
+   */
+
+
+  _createClass(Drawer, [{
+    key: 'extend',
+    value: function extend() {
+      var that = this;
+      var extended = {};
+      var deep = false;
+      var i = 0;
+      var length = arguments.length;
+
+      if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') {
+        deep = arguments[0];
+        i++;
+      }
+
+      var merge = function merge(obj) {
+        for (var prop in obj) {
+          if (Object.prototype.hasOwnProperty.call(obj, prop)) {
+            if (deep && Object.prototype.toString.call(obj[prop]) === '[object Object]') {
+              extended[prop] = that.extend(true, extended[prop], obj[prop]);
+            } else {
+              extended[prop] = obj[prop];
+            }
+          }
+        }
+      };
+
+      for (; i < length; i++) {
+        var obj = arguments[i];
+        merge(obj);
+      }
+
+      return extended;
+    }
+  }, {
+    key: 'draw',
+
+
+    /**
+     * Draws the parsed smiles data to a canvas element.
+     *
+     * @param {Object} data The tree returned by the smiles parser.
+     * @param {(String|HTMLElement)} target The id of the HTML canvas element the structure is drawn to - or the element itself.
+     * @param {String} themeName='dark' The name of the theme to use. Built-in themes are 'light' and 'dark'.
+     * @param {Boolean} infoOnly=false Only output info on the molecule without drawing anything to the canvas.
+     */
+    value: function draw(data, target) {
+      var themeName = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 'light';
+      var infoOnly = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+
+      this.data = data;
+      this.infoOnly = infoOnly;
+
+      if (!this.infoOnly) {
+        this.canvasWrapper = new CanvasWrapper(target, this.opts.themes[themeName], this.opts);
+      }
+
+      this.ringIdCounter = 0;
+      this.ringConnectionIdCounter = 0;
+
+      this.graph = new Graph(data, this.opts.isomeric);
+      this.rings = Array();
+      this.ringConnections = Array();
+
+      this.originalRings = Array();
+      this.originalRingConnections = Array();
+
+      this.bridgedRing = false;
+
+      // Reset those, in case the previous drawn SMILES had a dangling \ or /
+      this.doubleBondConfigCount = null;
+      this.doubleBondConfig = null;
+
+      this.initRings();
+      this.initHydrogens();
+
+      if (!this.infoOnly) {
+        this.position();
+
+        // Restore the ring information (removes bridged rings and replaces them with the original, multiple, rings)
+        this.restoreRingInformation();
+
+        // Atoms bonded to the same ring atom
+        this.resolvePrimaryOverlaps();
+
+        var overlapScore = this.getOverlapScore();
+
+        this.totalOverlapScore = this.getOverlapScore().total;
+
+        for (var o = 0; o < this.opts.overlapResolutionIterations; o++) {
+          for (var i = 0; i < this.graph.edges.length; i++) {
+            var edge = this.graph.edges[i];
+            if (this.isEdgeRotatable(edge)) {
+              var subTreeDepthA = this.graph.getTreeDepth(edge.sourceId, edge.targetId);
+              var subTreeDepthB = this.graph.getTreeDepth(edge.targetId, edge.sourceId);
+
+              // Only rotate the shorter subtree
+              var a = edge.targetId;
+              var b = edge.sourceId;
+
+              if (subTreeDepthA > subTreeDepthB) {
+                a = edge.sourceId;
+                b = edge.targetId;
+              }
+
+              var subTreeOverlap = this.getSubtreeOverlapScore(b, a, overlapScore.vertexScores);
+              if (subTreeOverlap.value > this.opts.overlapSensitivity) {
+                var vertexA = this.graph.vertices[a];
+                var vertexB = this.graph.vertices[b];
+                var neighboursB = vertexB.getNeighbours(a);
+
+                if (neighboursB.length === 1) {
+                  var neighbour = this.graph.vertices[neighboursB[0]];
+                  var angle = neighbour.position.getRotateAwayFromAngle(vertexA.position, vertexB.position, MathHelper.toRad(120));
+
+                  this.rotateSubtree(neighbour.id, vertexB.id, angle, vertexB.position);
+                  // If the new overlap is bigger, undo change
+                  var newTotalOverlapScore = this.getOverlapScore().total;
+
+                  if (newTotalOverlapScore > this.totalOverlapScore) {
+                    this.rotateSubtree(neighbour.id, vertexB.id, -angle, vertexB.position);
+                  } else {
+                    this.totalOverlapScore = newTotalOverlapScore;
+                  }
+                } else if (neighboursB.length === 2) {
+                  // Switch places / sides
+                  // If vertex a is in a ring, do nothing
+                  if (vertexB.value.rings.length !== 0 && vertexA.value.rings.length !== 0) {
+                    continue;
+                  }
+
+                  var neighbourA = this.graph.vertices[neighboursB[0]];
+                  var neighbourB = this.graph.vertices[neighboursB[1]];
+
+                  if (neighbourA.value.rings.length === 1 && neighbourB.value.rings.length === 1) {
+                    // Both neighbours in same ring. TODO: does this create problems with wedges? (up = down and vice versa?)
+                    if (neighbourA.value.rings[0] !== neighbourB.value.rings[0]) {
+                      continue;
+                    }
+                    // TODO: Rotate circle
+                  } else if (neighbourA.value.rings.length !== 0 || neighbourB.value.rings.length !== 0) {
+                    continue;
+                  } else {
+                    var angleA = neighbourA.position.getRotateAwayFromAngle(vertexA.position, vertexB.position, MathHelper.toRad(120));
+                    var angleB = neighbourB.position.getRotateAwayFromAngle(vertexA.position, vertexB.position, MathHelper.toRad(120));
+
+                    this.rotateSubtree(neighbourA.id, vertexB.id, angleA, vertexB.position);
+                    this.rotateSubtree(neighbourB.id, vertexB.id, angleB, vertexB.position);
+
+                    var _newTotalOverlapScore = this.getOverlapScore().total;
+
+                    if (_newTotalOverlapScore > this.totalOverlapScore) {
+                      this.rotateSubtree(neighbourA.id, vertexB.id, -angleA, vertexB.position);
+                      this.rotateSubtree(neighbourB.id, vertexB.id, -angleB, vertexB.position);
+                    } else {
+                      this.totalOverlapScore = _newTotalOverlapScore;
+                    }
+                  }
+                }
+
+                overlapScore = this.getOverlapScore();
+              }
+            }
+          }
+        }
+
+        this.resolveSecondaryOverlaps(overlapScore.scores);
+
+        if (this.opts.isomeric) {
+          this.annotateStereochemistry();
+        }
+
+        // Initialize pseudo elements or shortcuts
+        if (this.opts.compactDrawing && this.opts.atomVisualization === 'default') {
+          this.initPseudoElements();
+        }
+
+        this.rotateDrawing();
+
+        // Set the canvas to the appropriate size
+        this.canvasWrapper.scale(this.graph.vertices);
+
+        // Do the actual drawing
+        this.drawEdges(this.opts.debug);
+        this.drawVertices(this.opts.debug);
+        this.canvasWrapper.reset();
+      }
+    }
+
+    /**
+     * Returns the number of rings this edge is a part of.
+     *
+     * @param {Number} edgeId The id of an edge.
+     * @returns {Number} The number of rings the provided edge is part of.
+     */
+
+  }, {
+    key: 'edgeRingCount',
+    value: function edgeRingCount(edgeId) {
+      var edge = this.graph.edges[edgeId];
+      var a = this.graph.vertices[edge.sourceId];
+      var b = this.graph.vertices[edge.targetId];
+
+      return Math.min(a.value.rings.length, b.value.rings.length);
+    }
+
+    /**
+     * Returns an array containing the bridged rings associated with this  molecule.
+     *
+     * @returns {Ring[]} An array containing all bridged rings associated with this molecule.
+     */
+
+  }, {
+    key: 'getBridgedRings',
+    value: function getBridgedRings() {
+      var bridgedRings = Array();
+
+      for (var i = 0; i < this.rings.length; i++) {
+        if (this.rings[i].isBridged) {
+          bridgedRings.push(this.rings[i]);
+        }
+      }
+
+      return bridgedRings;
+    }
+
+    /**
+     * Returns an array containing all fused rings associated with this molecule.
+     *
+     * @returns {Ring[]} An array containing all fused rings associated with this molecule.
+     */
+
+  }, {
+    key: 'getFusedRings',
+    value: function getFusedRings() {
+      var fusedRings = Array();
+
+      for (var i = 0; i < this.rings.length; i++) {
+        if (this.rings[i].isFused) {
+          fusedRings.push(this.rings[i]);
+        }
+      }
+
+      return fusedRings;
+    }
+
+    /**
+     * Returns an array containing all spiros associated with this molecule.
+     *
+     * @returns {Ring[]} An array containing all spiros associated with this molecule.
+     */
+
+  }, {
+    key: 'getSpiros',
+    value: function getSpiros() {
+      var spiros = Array();
+
+      for (var i = 0; i < this.rings.length; i++) {
+        if (this.rings[i].isSpiro) {
+          spiros.push(this.rings[i]);
+        }
+      }
+
+      return spiros;
+    }
+
+    /**
+     * Returns a string containing a semicolon and new-line separated list of ring properties: Id; Members Count; Neighbours Count; IsSpiro; IsFused; IsBridged; Ring Count (subrings of bridged rings)
+     *
+     * @returns {String} A string as described in the method description.
+     */
+
+  }, {
+    key: 'printRingInfo',
+    value: function printRingInfo() {
+      var result = '';
+      for (var i = 0; i < this.rings.length; i++) {
+        var ring = this.rings[i];
+
+        result += ring.id + ';';
+        result += ring.members.length + ';';
+        result += ring.neighbours.length + ';';
+        result += ring.isSpiro ? 'true;' : 'false;';
+        result += ring.isFused ? 'true;' : 'false;';
+        result += ring.isBridged ? 'true;' : 'false;';
+        result += ring.rings.length + ';';
+        result += '\n';
+      }
+
+      return result;
+    }
+
+    /**
+     * Rotates the drawing to make the widest dimension horizontal.
+     */
+
+  }, {
+    key: 'rotateDrawing',
+    value: function rotateDrawing() {
+      // Rotate the vertices to make the molecule align horizontally
+      // Find the longest distance
+      var a = 0;
+      var b = 0;
+      var maxDist = 0;
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertexA = this.graph.vertices[i];
+
+        if (!vertexA.value.isDrawn) {
+          continue;
+        }
+
+        for (var j = i + 1; j < this.graph.vertices.length; j++) {
+          var vertexB = this.graph.vertices[j];
+
+          if (!vertexB.value.isDrawn) {
+            continue;
+          }
+
+          var dist = vertexA.position.distanceSq(vertexB.position);
+
+          if (dist > maxDist) {
+            maxDist = dist;
+            a = i;
+            b = j;
+          }
+        }
+      }
+
+      var angle = -Vector2.subtract(this.graph.vertices[a].position, this.graph.vertices[b].position).angle();
+
+      if (!isNaN(angle)) {
+        // Round to 30 degrees
+        var remainder = angle % 0.523599;
+
+        // Round either up or down in 30 degree steps
+        if (remainder < 0.2617995) {
+          angle = angle - remainder;
+        } else {
+          angle += 0.523599 - remainder;
+        }
+
+        // Finally, rotate everything
+        for (var i = 0; i < this.graph.vertices.length; i++) {
+          if (i === b) {
+            continue;
+          }
+
+          this.graph.vertices[i].position.rotateAround(angle, this.graph.vertices[b].position);
+        }
+
+        for (var i = 0; i < this.rings.length; i++) {
+          this.rings[i].center.rotateAround(angle, this.graph.vertices[b].position);
+        }
+      }
+    }
+
+    /**
+     * Returns the total overlap score of the current molecule.
+     *
+     * @returns {Number} The overlap score.
+     */
+
+  }, {
+    key: 'getTotalOverlapScore',
+    value: function getTotalOverlapScore() {
+      return this.totalOverlapScore;
+    }
+
+    /**
+     * Returns the ring count of the current molecule.
+     *
+     * @returns {Number} The ring count.
+     */
+
+  }, {
+    key: 'getRingCount',
+    value: function getRingCount() {
+      return this.rings.length;
+    }
+
+    /**
+     * Checks whether or not the current molecule  a bridged ring.
+     *
+     * @returns {Boolean} A boolean indicating whether or not the current molecule  a bridged ring.
+     */
+
+  }, {
+    key: 'hasBridgedRing',
+    value: function hasBridgedRing() {
+      return this.bridgedRing;
+    }
+
+    /**
+     * Returns the number of heavy atoms (non-hydrogen) in the current molecule.
+     *
+     * @returns {Number} The heavy atom count.
+     */
+
+  }, {
+    key: 'getHeavyAtomCount',
+    value: function getHeavyAtomCount() {
+      var hac = 0;
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        if (this.graph.vertices[i].value.element !== 'H') {
+          hac++;
+        }
+      }
+
+      return hac;
+    }
+
+    /**
+     * Returns the molecular formula of the loaded molecule as a string.
+     * 
+     * @returns {String} The molecular formula.
+     */
+
+  }, {
+    key: 'getMolecularFormula',
+    value: function getMolecularFormula() {
+      var molecularFormula = '';
+      var counts = new Map();
+
+      // Initialize element count
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var atom = this.graph.vertices[i].value;
+
+        if (counts.has(atom.element)) {
+          counts.set(atom.element, counts.get(atom.element) + 1);
+        } else {
+          counts.set(atom.element, 1);
+        }
+
+        // Hydrogens attached to a chiral center were added as vertices,
+        // those in non chiral brackets are added here
+        if (atom.bracket && !atom.bracket.chirality) {
+          if (counts.has('H')) {
+            counts.set('H', counts.get('H') + atom.bracket.hcount);
+          } else {
+            counts.set('H', atom.bracket.hcount);
+          }
+        }
+
+        // Add the implicit hydrogens according to valency, exclude
+        // bracket atoms as they were handled and always have the number
+        // of hydrogens specified explicitly
+        if (!atom.bracket) {
+          var nHydrogens = Atom.maxBonds[atom.element] - atom.bondCount;
+
+          if (atom.isPartOfAromaticRing) {
+            nHydrogens--;
+          }
+
+          if (counts.has('H')) {
+            counts.set('H', counts.get('H') + nHydrogens);
+          } else {
+            counts.set('H', nHydrogens);
+          }
+        }
+      }
+
+      if (counts.has('C')) {
+        var count = counts.get('C');
+        molecularFormula += 'C' + (count > 1 ? count : '');
+        counts.delete('C');
+      }
+
+      if (counts.has('H')) {
+        var _count = counts.get('H');
+        molecularFormula += 'H' + (_count > 1 ? _count : '');
+        counts.delete('H');
+      }
+
+      var elements = Object.keys(Atom.atomicNumbers).sort();
+
+      elements.map(function (e) {
+        if (counts.has(e)) {
+          var _count2 = counts.get(e);
+          molecularFormula += e + (_count2 > 1 ? _count2 : '');
+        }
+      });
+
+      return molecularFormula;
+    }
+
+    /**
+     * Returns the type of the ringbond (e.g. '=' for a double bond). The ringbond represents the break in a ring introduced when creating the MST. If the two vertices supplied as arguments are not part of a common ringbond, the method returns null.
+     *
+     * @param {Vertex} vertexA A vertex.
+     * @param {Vertex} vertexB A vertex.
+     * @returns {(String|null)} Returns the ringbond type or null, if the two supplied vertices are not connected by a ringbond.
+     */
+
+  }, {
+    key: 'getRingbondType',
+    value: function getRingbondType(vertexA, vertexB) {
+      // Checks whether the two vertices are the ones connecting the ring
+      // and what the bond type should be.
+      if (vertexA.value.getRingbondCount() < 1 || vertexB.value.getRingbondCount() < 1) {
+        return null;
+      }
+
+      for (var i = 0; i < vertexA.value.ringbonds.length; i++) {
+        for (var j = 0; j < vertexB.value.ringbonds.length; j++) {
+          // if(i != j) continue;
+          if (vertexA.value.ringbonds[i].id === vertexB.value.ringbonds[j].id) {
+            // If the bonds are equal, it doesn't matter which bond is returned.
+            // if they are not equal, return the one that is not the default ("-")
+            if (vertexA.value.ringbonds[i].bondType === '-') {
+              return vertexB.value.ringbonds[j].bond;
+            } else {
+              return vertexA.value.ringbonds[i].bond;
+            }
+          }
+        }
+      }
+
+      return null;
+    }
+
+    /**
+     * Initializes rings and ringbonds for the current molecule.
+     */
+
+  }, {
+    key: 'initRings',
+    value: function initRings() {
+      var openBonds = new Map();
+
+      // Close the open ring bonds (spanning tree -> graph)
+      for (var i = this.graph.vertices.length - 1; i >= 0; i--) {
+        var vertex = this.graph.vertices[i];
+
+        if (vertex.value.ringbonds.length === 0) {
+          continue;
+        }
+
+        for (var j = 0; j < vertex.value.ringbonds.length; j++) {
+          var ringbondId = vertex.value.ringbonds[j].id;
+          var ringbondBond = vertex.value.ringbonds[j].bond;
+
+          // If the other ringbond id has not been discovered,
+          // add it to the open bonds map and continue.
+          // if the other ringbond id has already been discovered,
+          // create a bond between the two atoms.
+          if (!openBonds.has(ringbondId)) {
+            openBonds.set(ringbondId, [vertex.id, ringbondBond]);
+          } else {
+            var sourceVertexId = vertex.id;
+            var targetVertexId = openBonds.get(ringbondId)[0];
+            var targetRingbondBond = openBonds.get(ringbondId)[1];
+            var edge = new Edge(sourceVertexId, targetVertexId, 1);
+            edge.setBondType(targetRingbondBond || ringbondBond || '-');
+            var edgeId = this.graph.addEdge(edge);
+            var targetVertex = this.graph.vertices[targetVertexId];
+
+            vertex.addRingbondChild(targetVertexId, j);
+            vertex.value.addNeighbouringElement(targetVertex.value.element);
+            targetVertex.addRingbondChild(sourceVertexId, j);
+            targetVertex.value.addNeighbouringElement(vertex.value.element);
+            vertex.edges.push(edgeId);
+            targetVertex.edges.push(edgeId);
+
+            openBonds.delete(ringbondId);
+          }
+        }
+      }
+
+      // Get the rings in the graph (the SSSR)
+      var rings = SSSR.getRings(this.graph);
+
+      if (rings === null) {
+        return;
+      }
+
+      for (var i = 0; i < rings.length; i++) {
+        var ringVertices = [].concat(_toConsumableArray(rings[i]));
+        var ringId = this.addRing(new Ring(ringVertices));
+
+        // Add the ring to the atoms
+        for (var j = 0; j < ringVertices.length; j++) {
+          this.graph.vertices[ringVertices[j]].value.rings.push(ringId);
+        }
+      }
+
+      // Find connection between rings
+      // Check for common vertices and create ring connections. This is a bit
+      // ugly, but the ringcount is always fairly low (< 100)
+      for (var i = 0; i < this.rings.length - 1; i++) {
+        for (var j = i + 1; j < this.rings.length; j++) {
+          var a = this.rings[i];
+          var b = this.rings[j];
+          var ringConnection = new RingConnection(a, b);
+
+          // If there are no vertices in the ring connection, then there
+          // is no ring connection
+          if (ringConnection.vertices.size > 0) {
+            this.addRingConnection(ringConnection);
+          }
+        }
+      }
+
+      // Add neighbours to the rings
+      for (var i = 0; i < this.rings.length; i++) {
+        var ring = this.rings[i];
+        ring.neighbours = RingConnection.getNeighbours(this.ringConnections, ring.id);
+      }
+
+      // Anchor the ring to one of it's members, so that the ring center will always
+      // be tied to a single vertex when doing repositionings
+      for (var i = 0; i < this.rings.length; i++) {
+        var _ring = this.rings[i];
+        this.graph.vertices[_ring.members[0]].value.addAnchoredRing(_ring.id);
+      }
+
+      // Backup the ring information to restore after placing the bridged ring.
+      // This is needed in order to identify aromatic rings and stuff like this in
+      // rings that are member of the superring.
+      this.backupRingInformation();
+
+      // Replace rings contained by a larger bridged ring with a bridged ring
+      while (this.rings.length > 0) {
+        var id = -1;
+        for (var i = 0; i < this.rings.length; i++) {
+          var _ring3 = this.rings[i];
+
+          if (this.isPartOfBridgedRing(_ring3.id) && !_ring3.isBridged) {
+            id = _ring3.id;
+          }
+        }
+
+        if (id === -1) {
+          break;
+        }
+
+        var _ring2 = this.getRing(id);
+
+        var involvedRings = this.getBridgedRingRings(_ring2.id);
+
+        this.bridgedRing = true;
+        this.createBridgedRing(involvedRings, _ring2.members[0]);
+
+        // Remove the rings
+        for (var i = 0; i < involvedRings.length; i++) {
+          this.removeRing(involvedRings[i]);
+        }
+      }
+    }
+  }, {
+    key: 'initHydrogens',
+    value: function initHydrogens() {
+      // Do not draw hydrogens except when they are connected to a stereocenter connected to two or more rings.
+      if (!this.opts.explicitHydrogens) {
+        for (var i = 0; i < this.graph.vertices.length; i++) {
+          var vertex = this.graph.vertices[i];
+
+          if (vertex.value.element !== 'H') {
+            continue;
+          }
+
+          // Hydrogens should have only one neighbour, so just take the first
+          // Also set hasHydrogen true on connected atom
+          var neighbour = this.graph.vertices[vertex.neighbours[0]];
+          neighbour.value.hasHydrogen = true;
+
+          if (!neighbour.value.isStereoCenter || neighbour.value.rings.length < 2 && !neighbour.value.bridgedRing || neighbour.value.bridgedRing && neighbour.value.originalRings.length < 2) {
+            vertex.value.isDrawn = false;
+          }
+        }
+      }
+    }
+
+    /**
+     * Returns all rings connected by bridged bonds starting from the ring with the supplied ring id.
+     *
+     * @param {Number} ringId A ring id.
+     * @returns {Number[]} An array containing all ring ids of rings part of a bridged ring system.
+     */
+
+  }, {
+    key: 'getBridgedRingRings',
+    value: function getBridgedRingRings(ringId) {
+      var involvedRings = Array();
+      var that = this;
+
+      var recurse = function recurse(r) {
+        var ring = that.getRing(r);
+
+        involvedRings.push(r);
+
+        for (var i = 0; i < ring.neighbours.length; i++) {
+          var n = ring.neighbours[i];
+
+          if (involvedRings.indexOf(n) === -1 && n !== r && RingConnection.isBridge(that.ringConnections, that.graph.vertices, r, n)) {
+            recurse(n);
+          }
+        }
+      };
+
+      recurse(ringId);
+
+      return ArrayHelper.unique(involvedRings);
+    }
+
+    /**
+     * Checks whether or not a ring is part of a bridged ring.
+     *
+     * @param {Number} ringId A ring id.
+     * @returns {Boolean} A boolean indicating whether or not the supplied ring (by id) is part of a bridged ring system.
+     */
+
+  }, {
+    key: 'isPartOfBridgedRing',
+    value: function isPartOfBridgedRing(ringId) {
+      for (var i = 0; i < this.ringConnections.length; i++) {
+        if (this.ringConnections[i].containsRing(ringId) && this.ringConnections[i].isBridge(this.graph.vertices)) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Creates a bridged ring.
+     *
+     * @param {Number[]} ringIds An array of ids of rings involved in the bridged ring.
+     * @param {Number} sourceVertexId The vertex id to start the bridged ring discovery from.
+     * @returns {Ring} The bridged ring.
+     */
+
+  }, {
+    key: 'createBridgedRing',
+    value: function createBridgedRing(ringIds, sourceVertexId) {
+      var ringMembers = new Set();
+      var vertices = new Set();
+      var neighbours = new Set();
+
+      for (var i = 0; i < ringIds.length; i++) {
+        var _ring4 = this.getRing(ringIds[i]);
+        _ring4.isPartOfBridged = true;
+
+        for (var j = 0; j < _ring4.members.length; j++) {
+          vertices.add(_ring4.members[j]);
+        }
+
+        for (var j = 0; j < _ring4.neighbours.length; j++) {
+          var id = _ring4.neighbours[j];
+
+          if (ringIds.indexOf(id) === -1) {
+            neighbours.add(_ring4.neighbours[j]);
+          }
+        }
+      }
+
+      // A vertex is part of the bridged ring if it only belongs to
+      // one of the rings (or to another ring
+      // which is not part of the bridged ring).
+      var leftovers = new Set();
+
+      var _iteratorNormalCompletion = true;
+      var _didIteratorError = false;
+      var _iteratorError = undefined;
+
+      try {
+        for (var _iterator = vertices[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+          var _id = _step.value;
+
+          var _vertex = this.graph.vertices[_id];
+          var intersection = ArrayHelper.intersection(ringIds, _vertex.value.rings);
+
+          if (_vertex.value.rings.length === 1 || intersection.length === 1) {
+            ringMembers.add(_vertex.id);
+          } else {
+            leftovers.add(_vertex.id);
+          }
+        }
+
+        // Vertices can also be part of multiple rings and lay on the bridged ring,
+        // however, they have to have at least two neighbours that are not part of
+        // two rings
+      } catch (err) {
+        _didIteratorError = true;
+        _iteratorError = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion && _iterator.return) {
+            _iterator.return();
+          }
+        } finally {
+          if (_didIteratorError) {
+            throw _iteratorError;
+          }
+        }
+      }
+
+      var tmp = Array();
+      var insideRing = Array();
+
+      var _iteratorNormalCompletion2 = true;
+      var _didIteratorError2 = false;
+      var _iteratorError2 = undefined;
+
+      try {
+        for (var _iterator2 = leftovers[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+          var _id2 = _step2.value;
+
+          var _vertex2 = this.graph.vertices[_id2];
+          var onRing = false;
+
+          for (var _j = 0; _j < _vertex2.edges.length; _j++) {
+            if (this.edgeRingCount(_vertex2.edges[_j]) === 1) {
+              onRing = true;
+            }
+          }
+
+          if (onRing) {
+            _vertex2.value.isBridgeNode = true;
+            ringMembers.add(_vertex2.id);
+          } else {
+            _vertex2.value.isBridge = true;
+            ringMembers.add(_vertex2.id);
+          }
+        }
+
+        // Create the ring
+      } catch (err) {
+        _didIteratorError2 = true;
+        _iteratorError2 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion2 && _iterator2.return) {
+            _iterator2.return();
+          }
+        } finally {
+          if (_didIteratorError2) {
+            throw _iteratorError2;
+          }
+        }
+      }
+
+      var ring = new Ring([].concat(_toConsumableArray(ringMembers)));
+
+      ring.isBridged = true;
+      ring.neighbours = [].concat(_toConsumableArray(neighbours));
+
+      for (var i = 0; i < ringIds.length; i++) {
+        ring.rings.push(this.getRing(ringIds[i]).clone());
+      }
+
+      this.addRing(ring);
+
+      for (var i = 0; i < ring.members.length; i++) {
+        this.graph.vertices[ring.members[i]].value.bridgedRing = ring.id;
+      }
+
+      // Atoms inside the ring are no longer part of a ring but are now
+      // associated with the bridged ring
+      for (var i = 0; i < insideRing.length; i++) {
+        var vertex = this.graph.vertices[insideRing[i]];
+        vertex.value.rings = Array();
+      }
+
+      // Remove former rings from members of the bridged ring and add the bridged ring
+      var _iteratorNormalCompletion3 = true;
+      var _didIteratorError3 = false;
+      var _iteratorError3 = undefined;
+
+      try {
+        for (var _iterator3 = ringMembers[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+          var _id3 = _step3.value;
+
+          var _vertex3 = this.graph.vertices[_id3];
+          _vertex3.value.rings = ArrayHelper.removeAll(_vertex3.value.rings, ringIds);
+          _vertex3.value.rings.push(ring.id);
+        }
+
+        // Remove all the ring connections no longer used
+      } catch (err) {
+        _didIteratorError3 = true;
+        _iteratorError3 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion3 && _iterator3.return) {
+            _iterator3.return();
+          }
+        } finally {
+          if (_didIteratorError3) {
+            throw _iteratorError3;
+          }
+        }
+      }
+
+      for (var i = 0; i < ringIds.length; i++) {
+        for (var j = i + 1; j < ringIds.length; j++) {
+          this.removeRingConnectionsBetween(ringIds[i], ringIds[j]);
+        }
+      }
+
+      // Update the ring connections and add this ring to the neighbours neighbours
+      var _iteratorNormalCompletion4 = true;
+      var _didIteratorError4 = false;
+      var _iteratorError4 = undefined;
+
+      try {
+        for (var _iterator4 = neighbours[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+          var _id4 = _step4.value;
+
+          var connections = this.getRingConnections(_id4, ringIds);
+
+          for (var j = 0; j < connections.length; j++) {
+            this.getRingConnection(connections[j]).updateOther(ring.id, _id4);
+          }
+
+          this.getRing(_id4).neighbours.push(ring.id);
+        }
+      } catch (err) {
+        _didIteratorError4 = true;
+        _iteratorError4 = err;
+      } finally {
+        try {
+          if (!_iteratorNormalCompletion4 && _iterator4.return) {
+            _iterator4.return();
+          }
+        } finally {
+          if (_didIteratorError4) {
+            throw _iteratorError4;
+          }
+        }
+      }
+
+      return ring;
+    }
+
+    /**
+     * Checks whether or not two vertices are in the same ring.
+     *
+     * @param {Vertex} vertexA A vertex.
+     * @param {Vertex} vertexB A vertex.
+     * @returns {Boolean} A boolean indicating whether or not the two vertices are in the same ring.
+     */
+
+  }, {
+    key: 'areVerticesInSameRing',
+    value: function areVerticesInSameRing(vertexA, vertexB) {
+      // This is a little bit lighter (without the array and push) than
+      // getCommonRings().length > 0
+      for (var i = 0; i < vertexA.value.rings.length; i++) {
+        for (var j = 0; j < vertexB.value.rings.length; j++) {
+          if (vertexA.value.rings[i] === vertexB.value.rings[j]) {
+            return true;
+          }
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Returns an array of ring ids shared by both vertices.
+     *
+     * @param {Vertex} vertexA A vertex.
+     * @param {Vertex} vertexB A vertex.
+     * @returns {Number[]} An array of ids of rings shared by the two vertices.
+     */
+
+  }, {
+    key: 'getCommonRings',
+    value: function getCommonRings(vertexA, vertexB) {
+      var commonRings = Array();
+
+      for (var i = 0; i < vertexA.value.rings.length; i++) {
+        for (var j = 0; j < vertexB.value.rings.length; j++) {
+          if (vertexA.value.rings[i] == vertexB.value.rings[j]) {
+            commonRings.push(vertexA.value.rings[i]);
+          }
+        }
+      }
+
+      return commonRings;
+    }
+
+    /**
+     * Returns the aromatic or largest ring shared by the two vertices.
+     *
+     * @param {Vertex} vertexA A vertex.
+     * @param {Vertex} vertexB A vertex.
+     * @returns {(Ring|null)} If an aromatic common ring exists, that ring, else the largest (non-aromatic) ring, else null.
+     */
+
+  }, {
+    key: 'getLargestOrAromaticCommonRing',
+    value: function getLargestOrAromaticCommonRing(vertexA, vertexB) {
+      var commonRings = this.getCommonRings(vertexA, vertexB);
+      var maxSize = 0;
+      var largestCommonRing = null;
+
+      for (var i = 0; i < commonRings.length; i++) {
+        var ring = this.getRing(commonRings[i]);
+        var size = ring.getSize();
+
+        if (ring.isBenzeneLike(this.graph.vertices)) {
+          return ring;
+        } else if (size > maxSize) {
+          maxSize = size;
+          largestCommonRing = ring;
+        }
+      }
+
+      return largestCommonRing;
+    }
+
+    /**
+     * Returns an array of vertices positioned at a specified location.
+     *
+     * @param {Vector2} position The position to search for vertices.
+     * @param {Number} radius The radius within to search.
+     * @param {Number} excludeVertexId A vertex id to be excluded from the search results.
+     * @returns {Number[]} An array containing vertex ids in a given location.
+     */
+
+  }, {
+    key: 'getVerticesAt',
+    value: function getVerticesAt(position, radius, excludeVertexId) {
+      var locals = Array();
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+
+        if (vertex.id === excludeVertexId || !vertex.positioned) {
+          continue;
+        }
+
+        var distance = position.distanceSq(vertex.position);
+
+        if (distance <= radius * radius) {
+          locals.push(vertex.id);
+        }
+      }
+
+      return locals;
+    }
+
+    /**
+     * Returns the closest vertex (connected as well as unconnected).
+     *
+     * @param {Vertex} vertex The vertex of which to find the closest other vertex.
+     * @returns {Vertex} The closest vertex.
+     */
+
+  }, {
+    key: 'getClosestVertex',
+    value: function getClosestVertex(vertex) {
+      var minDist = 99999;
+      var minVertex = null;
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var v = this.graph.vertices[i];
+
+        if (v.id === vertex.id) {
+          continue;
+        }
+
+        var distSq = vertex.position.distanceSq(v.position);
+
+        if (distSq < minDist) {
+          minDist = distSq;
+          minVertex = v;
+        }
+      }
+
+      return minVertex;
+    }
+
+    /**
+     * Add a ring to this representation of a molecule.
+     *
+     * @param {Ring} ring A new ring.
+     * @returns {Number} The ring id of the new ring.
+     */
+
+  }, {
+    key: 'addRing',
+    value: function addRing(ring) {
+      ring.id = this.ringIdCounter++;
+      this.rings.push(ring);
+
+      return ring.id;
+    }
+
+    /**
+     * Removes a ring from the array of rings associated with the current molecule.
+     *
+     * @param {Number} ringId A ring id.
+     */
+
+  }, {
+    key: 'removeRing',
+    value: function removeRing(ringId) {
+      this.rings = this.rings.filter(function (item) {
+        return item.id !== ringId;
+      });
+
+      // Also remove ring connections involving this ring
+      this.ringConnections = this.ringConnections.filter(function (item) {
+        return item.firstRingId !== ringId && item.secondRingId !== ringId;
+      });
+
+      // Remove the ring as neighbour of other rings
+      for (var i = 0; i < this.rings.length; i++) {
+        var r = this.rings[i];
+        r.neighbours = r.neighbours.filter(function (item) {
+          return item !== ringId;
+        });
+      }
+    }
+
+    /**
+     * Gets a ring object from the array of rings associated with the current molecule by its id. The ring id is not equal to the index, since rings can be added and removed when processing bridged rings.
+     *
+     * @param {Number} ringId A ring id.
+     * @returns {Ring} A ring associated with the current molecule.
+     */
+
+  }, {
+    key: 'getRing',
+    value: function getRing(ringId) {
+      for (var i = 0; i < this.rings.length; i++) {
+        if (this.rings[i].id == ringId) {
+          return this.rings[i];
+        }
+      }
+    }
+
+    /**
+     * Add a ring connection to this representation of a molecule.
+     *
+     * @param {RingConnection} ringConnection A new ringConnection.
+     * @returns {Number} The ring connection id of the new ring connection.
+     */
+
+  }, {
+    key: 'addRingConnection',
+    value: function addRingConnection(ringConnection) {
+      ringConnection.id = this.ringConnectionIdCounter++;
+      this.ringConnections.push(ringConnection);
+
+      return ringConnection.id;
+    }
+
+    /**
+     * Removes a ring connection from the array of rings connections associated with the current molecule.
+     *
+     * @param {Number} ringConnectionId A ring connection id.
+     */
+
+  }, {
+    key: 'removeRingConnection',
+    value: function removeRingConnection(ringConnectionId) {
+      this.ringConnections = this.ringConnections.filter(function (item) {
+        return item.id !== ringConnectionId;
+      });
+    }
+
+    /**
+     * Removes all ring connections between two vertices.
+     *
+     * @param {Number} vertexIdA A vertex id.
+     * @param {Number} vertexIdB A vertex id.
+     */
+
+  }, {
+    key: 'removeRingConnectionsBetween',
+    value: function removeRingConnectionsBetween(vertexIdA, vertexIdB) {
+      var toRemove = Array();
+      for (var i = 0; i < this.ringConnections.length; i++) {
+        var ringConnection = this.ringConnections[i];
+
+        if (ringConnection.firstRingId === vertexIdA && ringConnection.secondRingId === vertexIdB || ringConnection.firstRingId === vertexIdB && ringConnection.secondRingId === vertexIdA) {
+          toRemove.push(ringConnection.id);
+        }
+      }
+
+      for (var i = 0; i < toRemove.length; i++) {
+        this.removeRingConnection(toRemove[i]);
+      }
+    }
+
+    /**
+     * Get a ring connection with a given id.
+     * 
+     * @param {Number} id 
+     * @returns {RingConnection} The ring connection with the specified id.
+     */
+
+  }, {
+    key: 'getRingConnection',
+    value: function getRingConnection(id) {
+      for (var i = 0; i < this.ringConnections.length; i++) {
+        if (this.ringConnections[i].id == id) {
+          return this.ringConnections[i];
+        }
+      }
+    }
+
+    /**
+     * Get the ring connections between a ring and a set of rings.
+     *
+     * @param {Number} ringId A ring id.
+     * @param {Number[]} ringIds An array of ring ids.
+     * @returns {Number[]} An array of ring connection ids.
+     */
+
+  }, {
+    key: 'getRingConnections',
+    value: function getRingConnections(ringId, ringIds) {
+      var ringConnections = Array();
+
+      for (var i = 0; i < this.ringConnections.length; i++) {
+        var rc = this.ringConnections[i];
+
+        for (var j = 0; j < ringIds.length; j++) {
+          var id = ringIds[j];
+
+          if (rc.firstRingId === ringId && rc.secondRingId === id || rc.firstRingId === id && rc.secondRingId === ringId) {
+            ringConnections.push(rc.id);
+          }
+        }
+      }
+
+      return ringConnections;
+    }
+
+    /**
+     * Returns the overlap score of the current molecule based on its positioned vertices. The higher the score, the more overlaps occur in the structure drawing.
+     *
+     * @returns {Object} Returns the total overlap score and the overlap score of each vertex sorted by score (higher to lower). Example: { total: 99, scores: [ { id: 0, score: 22 }, ... ]  }
+     */
+
+  }, {
+    key: 'getOverlapScore',
+    value: function getOverlapScore() {
+      var total = 0.0;
+      var overlapScores = new Float32Array(this.graph.vertices.length);
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        overlapScores[i] = 0;
+      }
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var j = this.graph.vertices.length;
+        while (--j > i) {
+          var a = this.graph.vertices[i];
+          var b = this.graph.vertices[j];
+
+          if (!a.value.isDrawn || !b.value.isDrawn) {
+            continue;
+          }
+
+          var dist = Vector2.subtract(a.position, b.position).lengthSq();
+
+          if (dist < this.opts.bondLengthSq) {
+            var weighted = (this.opts.bondLength - Math.sqrt(dist)) / this.opts.bondLength;
+            total += weighted;
+            overlapScores[i] += weighted;
+            overlapScores[j] += weighted;
+          }
+        }
+      }
+
+      var sortable = Array();
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        sortable.push({
+          id: i,
+          score: overlapScores[i]
+        });
+      }
+
+      sortable.sort(function (a, b) {
+        return b.score - a.score;
+      });
+
+      return {
+        total: total,
+        scores: sortable,
+        vertexScores: overlapScores
+      };
+    }
+
+    /**
+     * When drawing a double bond, choose the side to place the double bond. E.g. a double bond should always been drawn inside a ring.
+     *
+     * @param {Vertex} vertexA A vertex.
+     * @param {Vertex} vertexB A vertex.
+     * @param {Vector2[]} sides An array containing the two normals of the line spanned by the two provided vertices.
+     * @returns {Object} Returns an object containing the following information: {
+            totalSideCount: Counts the sides of each vertex in the molecule, is an array [ a, b ],
+            totalPosition: Same as position, but based on entire molecule,
+            sideCount: Counts the sides of each neighbour, is an array [ a, b ],
+            position: which side to position the second bond, is 0 or 1, represents the index in the normal array. This is based on only the neighbours
+            anCount: the number of neighbours of vertexA,
+            bnCount: the number of neighbours of vertexB
+        }
+     */
+
+  }, {
+    key: 'chooseSide',
+    value: function chooseSide(vertexA, vertexB, sides) {
+      // Check which side has more vertices
+      // Get all the vertices connected to the both ends
+      var an = vertexA.getNeighbours(vertexB.id);
+      var bn = vertexB.getNeighbours(vertexA.id);
+      var anCount = an.length;
+      var bnCount = bn.length;
+
+      // All vertices connected to the edge vertexA to vertexB
+      var tn = ArrayHelper.merge(an, bn);
+
+      // Only considering the connected vertices
+      var sideCount = [0, 0];
+
+      for (var i = 0; i < tn.length; i++) {
+        var v = this.graph.vertices[tn[i]].position;
+
+        if (v.sameSideAs(vertexA.position, vertexB.position, sides[0])) {
+          sideCount[0]++;
+        } else {
+          sideCount[1]++;
+        }
+      }
+
+      // Considering all vertices in the graph, this is to resolve ties
+      // from the above side counts
+      var totalSideCount = [0, 0];
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var _v = this.graph.vertices[i].position;
+
+        if (_v.sameSideAs(vertexA.position, vertexB.position, sides[0])) {
+          totalSideCount[0]++;
+        } else {
+          totalSideCount[1]++;
+        }
+      }
+
+      return {
+        totalSideCount: totalSideCount,
+        totalPosition: totalSideCount[0] > totalSideCount[1] ? 0 : 1,
+        sideCount: sideCount,
+        position: sideCount[0] > sideCount[1] ? 0 : 1,
+        anCount: anCount,
+        bnCount: bnCount
+      };
+    }
+
+    /**
+     * Sets the center for a ring.
+     *
+     * @param {Ring} ring A ring.
+     */
+
+  }, {
+    key: 'setRingCenter',
+    value: function setRingCenter(ring) {
+      var ringSize = ring.getSize();
+      var total = new Vector2(0, 0);
+
+      for (var i = 0; i < ringSize; i++) {
+        total.add(this.graph.vertices[ring.members[i]].position);
+      }
+
+      ring.center = total.divide(ringSize);
+    }
+
+    /**
+     * Gets the center of a ring contained within a bridged ring and containing a given vertex.
+     *
+     * @param {Ring} ring A bridged ring.
+     * @param {Vertex} vertex A vertex.
+     * @returns {Vector2} The center of the subring that containing the vertex.
+     */
+
+  }, {
+    key: 'getSubringCenter',
+    value: function getSubringCenter(ring, vertex) {
+      var rings = vertex.value.originalRings;
+      var center = ring.center;
+      var smallest = Number.MAX_VALUE;
+
+      // Always get the smallest ring.
+      for (var i = 0; i < rings.length; i++) {
+        for (var j = 0; j < ring.rings.length; j++) {
+          if (rings[i] === ring.rings[j].id) {
+            if (ring.rings[j].getSize() < smallest) {
+              center = ring.rings[j].center;
+              smallest = ring.rings[j].getSize();
+            }
+          }
+        }
+      }
+
+      return center;
+    }
+
+    /**
+     * Draw the actual edges as bonds to the canvas.
+     *
+     * @param {Boolean} debug A boolean indicating whether or not to draw debug helpers.
+     */
+
+  }, {
+    key: 'drawEdges',
+    value: function drawEdges(debug) {
+      var that = this;
+      var drawn = Array(this.graph.edges.length);
+      drawn.fill(false);
+
+      this.graph.traverseBF(0, function (vertex) {
+        var edges = that.graph.getEdges(vertex.id);
+        for (var i = 0; i < edges.length; i++) {
+          var edgeId = edges[i];
+          if (!drawn[edgeId]) {
+            drawn[edgeId] = true;
+            that.drawEdge(edgeId, debug);
+          }
+        }
+      });
+
+      // Draw ring for implicitly defined aromatic rings
+      if (!this.bridgedRing) {
+        for (var i = 0; i < this.rings.length; i++) {
+          var ring = this.rings[i];
+
+          if (this.isRingAromatic(ring)) {
+            this.canvasWrapper.drawAromaticityRing(ring);
+          }
+        }
+      }
+    }
+
+    /**
+     * Draw the an edge as a bonds to the canvas.
+     *
+     * @param {Number} edgeId An edge id.
+     * @param {Boolean} debug A boolean indicating whether or not to draw debug helpers.
+     */
+
+  }, {
+    key: 'drawEdge',
+    value: function drawEdge(edgeId, debug) {
+      var that = this;
+      var edge = this.graph.edges[edgeId];
+      var vertexA = this.graph.vertices[edge.sourceId];
+      var vertexB = this.graph.vertices[edge.targetId];
+      var elementA = vertexA.value.element;
+      var elementB = vertexB.value.element;
+
+      if ((!vertexA.value.isDrawn || !vertexB.value.isDrawn) && this.opts.atomVisualization === 'default') {
+        return;
+      }
+
+      var a = vertexA.position;
+      var b = vertexB.position;
+      var normals = this.getEdgeNormals(edge);
+
+      // Create a point on each side of the line
+      var sides = ArrayHelper.clone(normals);
+
+      sides[0].multiplyScalar(10).add(a);
+      sides[1].multiplyScalar(10).add(a);
+
+      if (edge.bondType === '=' || this.getRingbondType(vertexA, vertexB) === '=' || edge.isPartOfAromaticRing && this.bridgedRing) {
+        // Always draw double bonds inside the ring
+        var inRing = this.areVerticesInSameRing(vertexA, vertexB);
+        var s = this.chooseSide(vertexA, vertexB, sides);
+
+        if (inRing) {
+          // Always draw double bonds inside a ring
+          // if the bond is shared by two rings, it is drawn in the larger
+          // problem: smaller ring is aromatic, bond is still drawn in larger -> fix this
+          var lcr = this.getLargestOrAromaticCommonRing(vertexA, vertexB);
+          var center = lcr.center;
+
+          normals[0].multiplyScalar(that.opts.bondSpacing);
+          normals[1].multiplyScalar(that.opts.bondSpacing);
+
+          // Choose the normal that is on the same side as the center
+          var line = null;
+
+          if (center.sameSideAs(vertexA.position, vertexB.position, Vector2.add(a, normals[0]))) {
+            line = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+          } else {
+            line = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+          }
+
+          line.shorten(this.opts.bondLength - this.opts.shortBondLength * this.opts.bondLength);
+
+          // The shortened edge
+          if (edge.isPartOfAromaticRing) {
+            this.canvasWrapper.drawLine(line, true);
+          } else {
+            this.canvasWrapper.drawLine(line);
+          }
+
+          // The normal edge
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+        } else if (edge.center || vertexA.isTerminal() && vertexB.isTerminal()) {
+          normals[0].multiplyScalar(that.opts.halfBondSpacing);
+          normals[1].multiplyScalar(that.opts.halfBondSpacing);
+
+          var lineA = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+          var lineB = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+
+          this.canvasWrapper.drawLine(lineA);
+          this.canvasWrapper.drawLine(lineB);
+        } else if (s.anCount == 0 && s.bnCount > 1 || s.bnCount == 0 && s.anCount > 1) {
+          // Both lines are the same length here
+          // Add the spacing to the edges (which are of unit length)
+          normals[0].multiplyScalar(that.opts.halfBondSpacing);
+          normals[1].multiplyScalar(that.opts.halfBondSpacing);
+
+          var _lineA = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+          var _lineB = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+
+          this.canvasWrapper.drawLine(_lineA);
+          this.canvasWrapper.drawLine(_lineB);
+        } else if (s.sideCount[0] > s.sideCount[1]) {
+          normals[0].multiplyScalar(that.opts.bondSpacing);
+          normals[1].multiplyScalar(that.opts.bondSpacing);
+
+          var _line = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+
+          _line.shorten(this.opts.bondLength - this.opts.shortBondLength * this.opts.bondLength);
+          this.canvasWrapper.drawLine(_line);
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+        } else if (s.sideCount[0] < s.sideCount[1]) {
+          normals[0].multiplyScalar(that.opts.bondSpacing);
+          normals[1].multiplyScalar(that.opts.bondSpacing);
+
+          var _line2 = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+
+          _line2.shorten(this.opts.bondLength - this.opts.shortBondLength * this.opts.bondLength);
+          this.canvasWrapper.drawLine(_line2);
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+        } else if (s.totalSideCount[0] > s.totalSideCount[1]) {
+          normals[0].multiplyScalar(that.opts.bondSpacing);
+          normals[1].multiplyScalar(that.opts.bondSpacing);
+
+          var _line3 = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+
+          _line3.shorten(this.opts.bondLength - this.opts.shortBondLength * this.opts.bondLength);
+          this.canvasWrapper.drawLine(_line3);
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+        } else if (s.totalSideCount[0] <= s.totalSideCount[1]) {
+          normals[0].multiplyScalar(that.opts.bondSpacing);
+          normals[1].multiplyScalar(that.opts.bondSpacing);
+
+          var _line4 = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+
+          _line4.shorten(this.opts.bondLength - this.opts.shortBondLength * this.opts.bondLength);
+          this.canvasWrapper.drawLine(_line4);
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+        } else {}
+      } else if (edge.bondType === '#') {
+        normals[0].multiplyScalar(that.opts.bondSpacing / 1.5);
+        normals[1].multiplyScalar(that.opts.bondSpacing / 1.5);
+
+        var _lineA2 = new Line(Vector2.add(a, normals[0]), Vector2.add(b, normals[0]), elementA, elementB);
+        var _lineB2 = new Line(Vector2.add(a, normals[1]), Vector2.add(b, normals[1]), elementA, elementB);
+
+        this.canvasWrapper.drawLine(_lineA2);
+        this.canvasWrapper.drawLine(_lineB2);
+
+        this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB));
+      } else if (edge.bondType === '.') {
+        // TODO: Something... maybe... version 2?
+      } else {
+        var isChiralCenterA = vertexA.value.isStereoCenter;
+        var isChiralCenterB = vertexB.value.isStereoCenter;
+
+        if (edge.wedge === 'up') {
+          this.canvasWrapper.drawWedge(new Line(a, b, elementA, elementB, isChiralCenterA, isChiralCenterB));
+        } else if (edge.wedge === 'down') {
+          this.canvasWrapper.drawDashedWedge(new Line(a, b, elementA, elementB, isChiralCenterA, isChiralCenterB));
+        } else {
+          this.canvasWrapper.drawLine(new Line(a, b, elementA, elementB, isChiralCenterA, isChiralCenterB));
+        }
+      }
+
+      if (debug) {
+        var midpoint = Vector2.midpoint(a, b);
+        this.canvasWrapper.drawDebugText(midpoint.x, midpoint.y, 'e: ' + edgeId);
+      }
+    }
+
+    /**
+     * Draws the vertices representing atoms to the canvas.
+     *
+     * @param {Boolean} debug A boolean indicating whether or not to draw debug messages to the canvas.
+     */
+
+  }, {
+    key: 'drawVertices',
+    value: function drawVertices(debug) {
+      var i = this.graph.vertices.length;
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+        var atom = vertex.value;
+        var charge = 0;
+        var isotope = 0;
+        var bondCount = vertex.value.bondCount;
+        var element = atom.element;
+        var hydrogens = Atom.maxBonds[element] - bondCount;
+        var dir = vertex.getTextDirection(this.graph.vertices);
+        var isTerminal = this.opts.terminalCarbons || element !== 'C' || atom.hasAttachedPseudoElements ? vertex.isTerminal() : false;
+        var isCarbon = atom.element === 'C';
+
+        // This is a HACK to remove all hydrogens from nitrogens in aromatic rings, as this
+        // should be the most common state. This has to be fixed by kekulization
+        if (atom.element === 'N' && atom.isPartOfAromaticRing) {
+          hydrogens = 0;
+        }
+
+        if (atom.bracket) {
+          hydrogens = atom.bracket.hcount;
+          charge = atom.bracket.charge;
+          isotope = atom.bracket.isotope;
+        }
+
+        if (this.opts.atomVisualization === 'allballs') {
+          this.canvasWrapper.drawBall(vertex.position.x, vertex.position.y, element);
+        } else if (atom.isDrawn && (!isCarbon || atom.drawExplicit || isTerminal || atom.hasAttachedPseudoElements) || this.graph.vertices.length === 1) {
+          if (this.opts.atomVisualization === 'default') {
+            this.canvasWrapper.drawText(vertex.position.x, vertex.position.y, element, hydrogens, dir, isTerminal, charge, isotope, atom.getAttachedPseudoElements());
+          } else if (this.opts.atomVisualization === 'balls') {
+            this.canvasWrapper.drawBall(vertex.position.x, vertex.position.y, element);
+          }
+        } else if (vertex.getNeighbourCount() === 2 && vertex.forcePositioned == true) {
+          // If there is a carbon which bonds are in a straight line, draw a dot
+          var a = this.graph.vertices[vertex.neighbours[0]].position;
+          var b = this.graph.vertices[vertex.neighbours[1]].position;
+          var angle = Vector2.threePointangle(vertex.position, a, b);
+
+          if (Math.abs(Math.PI - angle) < 0.1) {
+            this.canvasWrapper.drawPoint(vertex.position.x, vertex.position.y, element);
+          }
+        }
+
+        if (debug) {
+          var value = 'v: ' + vertex.id + ' ' + ArrayHelper.print(atom.ringbonds);
+          this.canvasWrapper.drawDebugText(vertex.position.x, vertex.position.y, value);
+        } else {
+          // this.canvasWrapper.drawDebugText(vertex.position.x, vertex.position.y, vertex.value.chirality);
+        }
+      }
+
+      // Draw the ring centers for debug purposes
+      if (this.opts.debug) {
+        for (var i = 0; i < this.rings.length; i++) {
+          var center = this.rings[i].center;
+          this.canvasWrapper.drawDebugPoint(center.x, center.y, 'r: ' + this.rings[i].id);
+        }
+      }
+    }
+
+    /**
+     * Position the vertices according to their bonds and properties.
+     */
+
+  }, {
+    key: 'position',
+    value: function position() {
+      var startVertex = null;
+
+      // Always start drawing at a bridged ring if there is one
+      // If not, start with a ring
+      // else, start with 0
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        if (this.graph.vertices[i].value.bridgedRing !== null) {
+          startVertex = this.graph.vertices[i];
+          break;
+        }
+      }
+
+      for (var i = 0; i < this.rings.length; i++) {
+        if (this.rings[i].isBridged) {
+          startVertex = this.graph.vertices[this.rings[i].members[0]];
+        }
+      }
+
+      if (this.rings.length > 0 && startVertex === null) {
+        startVertex = this.graph.vertices[this.rings[0].members[0]];
+      }
+
+      if (startVertex === null) {
+        startVertex = this.graph.vertices[0];
+      }
+
+      this.createNextBond(startVertex, null, 0.0);
+    }
+
+    /**
+     * Stores the current information associated with rings.
+     */
+
+  }, {
+    key: 'backupRingInformation',
+    value: function backupRingInformation() {
+      this.originalRings = Array();
+      this.originalRingConnections = Array();
+
+      for (var i = 0; i < this.rings.length; i++) {
+        this.originalRings.push(this.rings[i]);
+      }
+
+      for (var i = 0; i < this.ringConnections.length; i++) {
+        this.originalRingConnections.push(this.ringConnections[i]);
+      }
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        this.graph.vertices[i].value.backupRings();
+      }
+    }
+
+    /**
+     * Restores the most recently backed up information associated with rings.
+     */
+
+  }, {
+    key: 'restoreRingInformation',
+    value: function restoreRingInformation() {
+      // Get the subring centers from the bridged rings
+      var bridgedRings = this.getBridgedRings();
+
+      this.rings = Array();
+      this.ringConnections = Array();
+
+      for (var i = 0; i < bridgedRings.length; i++) {
+        var bridgedRing = bridgedRings[i];
+
+        for (var j = 0; j < bridgedRing.rings.length; j++) {
+          var ring = bridgedRing.rings[j];
+          this.originalRings[ring.id].center = ring.center;
+        }
+      }
+
+      for (var i = 0; i < this.originalRings.length; i++) {
+        this.rings.push(this.originalRings[i]);
+      }
+
+      for (var i = 0; i < this.originalRingConnections.length; i++) {
+        this.ringConnections.push(this.originalRingConnections[i]);
+      }
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        this.graph.vertices[i].value.restoreRings();
+      }
+    }
+
+    // TODO: This needs some cleaning up
+
+    /**
+     * Creates a new ring, that is, positiones all the vertices inside a ring.
+     *
+     * @param {Ring} ring The ring to position.
+     * @param {(Vector2|null)} [center=null] The center of the ring to be created.
+     * @param {(Vertex|null)} [startVertex=null] The first vertex to be positioned inside the ring.
+     * @param {(Vertex|null)} [previousVertex=null] The last vertex that was positioned.
+     * @param {Boolean} [previousVertex=false] A boolean indicating whether or not this ring was force positioned already - this is needed after force layouting a ring, in order to draw rings connected to it.
+     */
+
+  }, {
+    key: 'createRing',
+    value: function createRing(ring) {
+      var center = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+      var startVertex = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+      var previousVertex = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+
+      if (ring.positioned) {
+        return;
+      }
+
+      center = center ? center : new Vector2(0, 0);
+
+      var orderedNeighbours = ring.getOrderedNeighbours(this.ringConnections);
+      var startingAngle = startVertex ? Vector2.subtract(startVertex.position, center).angle() : 0;
+
+      var radius = MathHelper.polyCircumradius(this.opts.bondLength, ring.getSize());
+      var angle = MathHelper.centralAngle(ring.getSize());
+
+      ring.centralAngle = angle;
+
+      var a = startingAngle;
+      var that = this;
+      var startVertexId = startVertex ? startVertex.id : null;
+
+      if (ring.members.indexOf(startVertexId) === -1) {
+        if (startVertex) {
+          startVertex.positioned = false;
+        }
+
+        startVertexId = ring.members[0];
+      }
+
+      // If the ring is bridged, then draw the vertices inside the ring
+      // using a force based approach
+      if (ring.isBridged) {
+        this.graph.kkLayout(ring.members.slice(), center, startVertex.id, ring, this.opts.bondLength);
+        ring.positioned = true;
+
+        // Update the center of the bridged ring
+        this.setRingCenter(ring);
+        center = ring.center;
+
+        // Setting the centers for the subrings
+        for (var i = 0; i < ring.rings.length; i++) {
+          this.setRingCenter(ring.rings[i]);
+        }
+      } else {
+        ring.eachMember(this.graph.vertices, function (v) {
+          var vertex = that.graph.vertices[v];
+
+          if (!vertex.positioned) {
+            vertex.setPosition(center.x + Math.cos(a) * radius, center.y + Math.sin(a) * radius);
+          }
+
+          a += angle;
+
+          if (!ring.isBridged || ring.rings.length < 3) {
+            vertex.angle = a;
+            vertex.positioned = true;
+          }
+        }, startVertexId, previousVertex ? previousVertex.id : null);
+      }
+
+      ring.positioned = true;
+      ring.center = center;
+
+      // Draw neighbours in decreasing order of connectivity
+      for (var i = 0; i < orderedNeighbours.length; i++) {
+        var neighbour = this.getRing(orderedNeighbours[i].neighbour);
+
+        if (neighbour.positioned) {
+          continue;
+        }
+
+        var vertices = RingConnection.getVertices(this.ringConnections, ring.id, neighbour.id);
+
+        if (vertices.length === 2) {
+          // This ring is a fused ring
+          ring.isFused = true;
+          neighbour.isFused = true;
+
+          var vertexA = this.graph.vertices[vertices[0]];
+          var vertexB = this.graph.vertices[vertices[1]];
+
+          // Get middle between vertex A and B
+          var midpoint = Vector2.midpoint(vertexA.position, vertexB.position);
+
+          // Get the normals to the line between A and B
+          var normals = Vector2.normals(vertexA.position, vertexB.position);
+
+          // Normalize the normals
+          normals[0].normalize();
+          normals[1].normalize();
+
+          // Set length from middle of side to center (the apothem)
+          var r = MathHelper.polyCircumradius(this.opts.bondLength, neighbour.getSize());
+          var apothem = MathHelper.apothem(r, neighbour.getSize());
+
+          normals[0].multiplyScalar(apothem).add(midpoint);
+          normals[1].multiplyScalar(apothem).add(midpoint);
+
+          // Pick the normal which results in a larger distance to the previous center
+          // Also check whether it's inside another ring
+          var nextCenter = normals[0];
+          if (Vector2.subtract(center, normals[1]).lengthSq() > Vector2.subtract(center, normals[0]).lengthSq()) {
+            nextCenter = normals[1];
+          }
+
+          // Get the vertex (A or B) which is in clock-wise direction of the other
+          var posA = Vector2.subtract(vertexA.position, nextCenter);
+          var posB = Vector2.subtract(vertexB.position, nextCenter);
+
+          if (posA.clockwise(posB) === -1) {
+            if (!neighbour.positioned) {
+              this.createRing(neighbour, nextCenter, vertexA, vertexB);
+            }
+          } else {
+            if (!neighbour.positioned) {
+              this.createRing(neighbour, nextCenter, vertexB, vertexA);
+            }
+          }
+        } else if (vertices.length === 1) {
+          // This ring is a spiro
+          ring.isSpiro = true;
+          neighbour.isSpiro = true;
+
+          var _vertexA = this.graph.vertices[vertices[0]];
+
+          // Get the vector pointing from the shared vertex to the new centpositioner
+          var _nextCenter = Vector2.subtract(center, _vertexA.position);
+
+          _nextCenter.invert();
+          _nextCenter.normalize();
+
+          // Get the distance from the vertex to the center
+          var _r = MathHelper.polyCircumradius(this.opts.bondLength, neighbour.getSize());
+
+          _nextCenter.multiplyScalar(_r);
+          _nextCenter.add(_vertexA.position);
+
+          if (!neighbour.positioned) {
+            this.createRing(neighbour, _nextCenter, _vertexA);
+          }
+        }
+      }
+
+      // Next, draw atoms that are not part of a ring that are directly attached to this ring
+      for (var i = 0; i < ring.members.length; i++) {
+        var ringMember = this.graph.vertices[ring.members[i]];
+        var ringMemberNeighbours = ringMember.neighbours;
+
+        // If there are multiple, the ovlerap will be resolved in the appropriate step
+        for (var j = 0; j < ringMemberNeighbours.length; j++) {
+          var v = this.graph.vertices[ringMemberNeighbours[j]];
+
+          if (v.positioned) {
+            continue;
+          }
+
+          v.value.isConnectedToRing = true;
+          this.createNextBond(v, ringMember, 0.0);
+        }
+      }
+    }
+
+    /**
+     * Rotate an entire subtree by an angle around a center.
+     *
+     * @param {Number} vertexId A vertex id (the root of the sub-tree).
+     * @param {Number} parentVertexId A vertex id in the previous direction of the subtree that is to rotate.
+     * @param {Number} angle An angle in randians.
+     * @param {Vector2} center The rotational center.
+     */
+
+  }, {
+    key: 'rotateSubtree',
+    value: function rotateSubtree(vertexId, parentVertexId, angle, center) {
+      var that = this;
+
+      this.graph.traverseTree(vertexId, parentVertexId, function (vertex) {
+        vertex.position.rotateAround(angle, center);
+
+        for (var i = 0; i < vertex.value.anchoredRings.length; i++) {
+          var ring = that.rings[vertex.value.anchoredRings[i]];
+
+          if (ring) {
+            ring.center.rotateAround(angle, center);
+          }
+        }
+      });
+    }
+
+    /**
+     * Gets the overlap score of a subtree.
+     *
+     * @param {Number} vertexId A vertex id (the root of the sub-tree).
+     * @param {Number} parentVertexId A vertex id in the previous direction of the subtree.
+     * @param {Number[]} vertexOverlapScores An array containing the vertex overlap scores indexed by vertex id.
+     * @returns {Object} An object containing the total overlap score and the center of mass of the subtree weighted by overlap score { value: 0.2, center: new Vector2() }.
+     */
+
+  }, {
+    key: 'getSubtreeOverlapScore',
+    value: function getSubtreeOverlapScore(vertexId, parentVertexId, vertexOverlapScores) {
+      var that = this;
+      var score = 0;
+      var center = new Vector2(0, 0);
+      var count = 0;
+
+      this.graph.traverseTree(vertexId, parentVertexId, function (vertex) {
+        if (!vertex.value.isDrawn) {
+          return;
+        }
+
+        var s = vertexOverlapScores[vertex.id];
+        if (s > that.opts.overlapSensitivity) {
+          score += s;
+          count++;
+        }
+
+        var position = that.graph.vertices[vertex.id].position.clone();
+        position.multiplyScalar(s);
+        center.add(position);
+      });
+
+      center.divide(score);
+
+      return {
+        value: score / count,
+        center: center
+      };
+    }
+
+    /**
+     * Returns the current (positioned vertices so far) center of mass.
+     * 
+     * @returns {Vector2} The current center of mass.
+     */
+
+  }, {
+    key: 'getCurrentCenterOfMass',
+    value: function getCurrentCenterOfMass() {
+      var total = new Vector2(0, 0);
+      var count = 0;
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+
+        if (vertex.positioned) {
+          total.add(vertex.position);
+          count++;
+        }
+      }
+
+      return total.divide(count);
+    }
+
+    /**
+     * Returns the current (positioned vertices so far) center of mass in the neighbourhood of a given position.
+     *
+     * @param {Vector2} vec The point at which to look for neighbours.
+     * @param {Number} [r=currentBondLength*2.0] The radius of vertices to include.
+     * @returns {Vector2} The current center of mass.
+     */
+
+  }, {
+    key: 'getCurrentCenterOfMassInNeigbourhood',
+    value: function getCurrentCenterOfMassInNeigbourhood(vec) {
+      var r = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this.opts.bondLength * 2.0;
+
+      var total = new Vector2(0, 0);
+      var count = 0;
+      var rSq = r * r;
+
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+
+        if (vertex.positioned && vec.distanceSq(vertex.position) < rSq) {
+          total.add(vertex.position);
+          count++;
+        }
+      }
+
+      return total.divide(count);
+    }
+
+    /**
+     * Resolve primary (exact) overlaps, such as two vertices that are connected to the same ring vertex.
+     */
+
+  }, {
+    key: 'resolvePrimaryOverlaps',
+    value: function resolvePrimaryOverlaps() {
+      var overlaps = Array();
+      var done = Array(this.graph.vertices.length);
+
+      // Looking for overlaps created by two bonds coming out of a ring atom, which both point straight
+      // away from the ring and are thus perfectly overlapping.
+      for (var i = 0; i < this.rings.length; i++) {
+        var ring = this.rings[i];
+
+        for (var j = 0; j < ring.members.length; j++) {
+          var vertex = this.graph.vertices[ring.members[j]];
+
+          if (done[vertex.id]) {
+            continue;
+          }
+
+          done[vertex.id] = true;
+
+          var nonRingNeighbours = this.getNonRingNeighbours(vertex.id);
+
+          if (nonRingNeighbours.length > 1) {
+            // Look for rings where there are atoms with two bonds outside the ring (overlaps)
+            var rings = Array();
+
+            for (var k = 0; k < vertex.value.rings.length; k++) {
+              rings.push(vertex.value.rings[k]);
+            }
+
+            overlaps.push({
+              common: vertex,
+              rings: rings,
+              vertices: nonRingNeighbours
+            });
+          } else if (nonRingNeighbours.length === 1 && vertex.value.rings.length === 2) {
+            // Look for bonds coming out of joined rings to adjust the angle, an example is: C1=CC(=CC=C1)[C@]12SCCN1CC1=CC=CC=C21
+            // where the angle has to be adjusted to account for fused ring
+            var _rings = Array();
+
+            for (var k = 0; k < vertex.value.rings.length; k++) {
+              _rings.push(vertex.value.rings[k]);
+            }
+
+            overlaps.push({
+              common: vertex,
+              rings: _rings,
+              vertices: nonRingNeighbours
+            });
+          }
+        }
+      }
+
+      for (var i = 0; i < overlaps.length; i++) {
+        var overlap = overlaps[i];
+
+        if (overlap.vertices.length === 2) {
+          var a = overlap.vertices[0];
+          var b = overlap.vertices[1];
+
+          if (!a.value.isDrawn || !b.value.isDrawn) {
+            continue;
+          }
+
+          var angle = (2 * Math.PI - this.getRing(overlap.rings[0]).getAngle()) / 6.0;
+
+          this.rotateSubtree(a.id, overlap.common.id, angle, overlap.common.position);
+          this.rotateSubtree(b.id, overlap.common.id, -angle, overlap.common.position);
+
+          // Decide which way to rotate the vertices depending on the effect it has on the overlap score
+          var overlapScore = this.getOverlapScore();
+          var subTreeOverlapA = this.getSubtreeOverlapScore(a.id, overlap.common.id, overlapScore.vertexScores);
+          var subTreeOverlapB = this.getSubtreeOverlapScore(b.id, overlap.common.id, overlapScore.vertexScores);
+          var total = subTreeOverlapA.value + subTreeOverlapB.value;
+
+          this.rotateSubtree(a.id, overlap.common.id, -2.0 * angle, overlap.common.position);
+          this.rotateSubtree(b.id, overlap.common.id, 2.0 * angle, overlap.common.position);
+
+          overlapScore = this.getOverlapScore();
+          subTreeOverlapA = this.getSubtreeOverlapScore(a.id, overlap.common.id, overlapScore.vertexScores);
+          subTreeOverlapB = this.getSubtreeOverlapScore(b.id, overlap.common.id, overlapScore.vertexScores);
+
+          if (subTreeOverlapA.value + subTreeOverlapB.value > total) {
+            this.rotateSubtree(a.id, overlap.common.id, 2.0 * angle, overlap.common.position);
+            this.rotateSubtree(b.id, overlap.common.id, -2.0 * angle, overlap.common.position);
+          }
+        } else if (overlap.vertices.length === 1) {
+          if (overlap.rings.length === 2) {
+            // TODO: Implement for more overlap resolution
+            // console.log(overlap);
+          }
+        }
+      }
+    }
+
+    /**
+     * Resolve secondary overlaps. Those overlaps are due to the structure turning back on itself.
+     *
+     * @param {Object[]} scores An array of objects sorted descending by score.
+     * @param {Number} scores[].id A vertex id.
+     * @param {Number} scores[].score The overlap score associated with the vertex id.
+     */
+
+  }, {
+    key: 'resolveSecondaryOverlaps',
+    value: function resolveSecondaryOverlaps(scores) {
+      for (var i = 0; i < scores.length; i++) {
+        if (scores[i].score > this.opts.overlapSensitivity) {
+          var vertex = this.graph.vertices[scores[i].id];
+
+          if (vertex.isTerminal()) {
+            var closest = this.getClosestVertex(vertex);
+
+            if (closest) {
+              // If one of the vertices is the first one, the previous vertex is not the central vertex but the dummy
+              // so take the next rather than the previous, which is vertex 1
+              var closestPosition = null;
+
+              if (closest.isTerminal()) {
+                closestPosition = closest.id === 0 ? this.graph.vertices[1].position : closest.previousPosition;
+              } else {
+                closestPosition = closest.id === 0 ? this.graph.vertices[1].position : closest.position;
+              }
+
+              var vertexPreviousPosition = vertex.id === 0 ? this.graph.vertices[1].position : vertex.previousPosition;
+
+              vertex.position.rotateAwayFrom(closestPosition, vertexPreviousPosition, MathHelper.toRad(20));
+            }
+          }
+        }
+      }
+    }
+
+    /**
+     * Get the last non-null or 0 angle vertex.
+     * @param {Number} vertexId A vertex id.
+     * @returns {Vertex} The last vertex with an angle that was not 0 or null.
+     */
+
+  }, {
+    key: 'getLastVertexWithAngle',
+    value: function getLastVertexWithAngle(vertexId) {
+      var angle = 0;
+      var vertex = null;
+
+      while (!angle && vertexId) {
+        vertex = this.graph.vertices[vertexId];
+        angle = vertex.angle;
+        vertexId = vertex.parentVertexId;
+      }
+
+      return vertex;
+    }
+
+    /**
+     * Positiones the next vertex thus creating a bond.
+     *
+     * @param {Vertex} vertex A vertex.
+     * @param {Vertex} [previousVertex=null] The previous vertex which has been positioned.
+     * @param {Number} [angle=0.0] The (global) angle of the vertex.
+     * @param {Boolean} [originShortest=false] Whether the origin is the shortest subtree in the branch.
+     * @param {Boolean} [skipPositioning=false] Whether or not to skip positioning and just check the neighbours.
+     */
+
+  }, {
+    key: 'createNextBond',
+    value: function createNextBond(vertex) {
+      var previousVertex = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : null;
+      var angle = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0.0;
+      var originShortest = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+      var skipPositioning = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
+
+      if (vertex.positioned && !skipPositioning) {
+        return;
+      }
+
+      // If the double bond config was set on this vertex, do not check later
+      var doubleBondConfigSet = false;
+
+      // Keeping track of configurations around double bonds
+      if (previousVertex) {
+        var edge = this.graph.getEdge(vertex.id, previousVertex.id);
+
+        if ((edge.bondType === '/' || edge.bondType === '\\') && ++this.doubleBondConfigCount % 2 === 1) {
+          if (this.doubleBondConfig === null) {
+            this.doubleBondConfig = edge.bondType;
+            doubleBondConfigSet = true;
+
+            // Switch if the bond is a branch bond and previous vertex is the first
+            // TODO: Why is it different with the first vertex?
+            if (previousVertex.parentVertexId === null && vertex.value.branchBond) {
+              if (this.doubleBondConfig === '/') {
+                this.doubleBondConfig = '\\';
+              } else if (this.doubleBondConfig === '\\') {
+                this.doubleBondConfig = '/';
+              }
+            }
+          }
+        }
+      }
+
+      // If the current node is the member of one ring, then point straight away
+      // from the center of the ring. However, if the current node is a member of
+      // two rings, point away from the middle of the centers of the two rings
+      if (!skipPositioning) {
+        if (!previousVertex) {
+          // Add a (dummy) previous position if there is no previous vertex defined
+          // Since the first vertex is at (0, 0), create a vector at (bondLength, 0)
+          // and rotate it by 90°
+
+          var dummy = new Vector2(this.opts.bondLength, 0);
+          dummy.rotate(MathHelper.toRad(-60));
+
+          vertex.previousPosition = dummy;
+          vertex.setPosition(this.opts.bondLength, 0);
+          vertex.angle = MathHelper.toRad(-60);
+
+          // Do not position the vertex if it belongs to a bridged ring that is positioned using a layout algorithm.
+          if (vertex.value.bridgedRing === null) {
+            vertex.positioned = true;
+          }
+        } else if (previousVertex.value.rings.length > 0) {
+          var neighbours = previousVertex.neighbours;
+          var joinedVertex = null;
+          var pos = new Vector2(0.0, 0.0);
+
+          if (previousVertex.value.bridgedRing === null && previousVertex.value.rings.length > 1) {
+            for (var i = 0; i < neighbours.length; i++) {
+              var neighbour = this.graph.vertices[neighbours[i]];
+              if (ArrayHelper.containsAll(neighbour.value.rings, previousVertex.value.rings)) {
+                joinedVertex = neighbour;
+                break;
+              }
+            }
+          }
+
+          if (joinedVertex === null) {
+            for (var i = 0; i < neighbours.length; i++) {
+              var v = this.graph.vertices[neighbours[i]];
+
+              if (v.positioned && this.areVerticesInSameRing(v, previousVertex)) {
+                pos.add(Vector2.subtract(v.position, previousVertex.position));
+              }
+            }
+
+            pos.invert().normalize().multiplyScalar(this.opts.bondLength).add(previousVertex.position);
+          } else {
+            pos = joinedVertex.position.clone().rotateAround(Math.PI, previousVertex.position);
+          }
+
+          vertex.previousPosition = previousVertex.position;
+          vertex.setPositionFromVector(pos);
+          vertex.positioned = true;
+        } else {
+          // If the previous vertex was not part of a ring, draw a bond based
+          // on the global angle of the previous bond
+          var _v2 = new Vector2(this.opts.bondLength, 0);
+
+          _v2.rotate(angle);
+          _v2.add(previousVertex.position);
+
+          vertex.setPositionFromVector(_v2);
+          vertex.previousPosition = previousVertex.position;
+          vertex.positioned = true;
+        }
+      }
+
+      // Go to next vertex
+      // If two rings are connected by a bond ...
+      if (vertex.value.bridgedRing !== null) {
+        var nextRing = this.getRing(vertex.value.bridgedRing);
+
+        if (!nextRing.positioned) {
+          var nextCenter = Vector2.subtract(vertex.previousPosition, vertex.position);
+
+          nextCenter.invert();
+          nextCenter.normalize();
+
+          var r = MathHelper.polyCircumradius(this.opts.bondLength, nextRing.members.length);
+          nextCenter.multiplyScalar(r);
+          nextCenter.add(vertex.position);
+
+          this.createRing(nextRing, nextCenter, vertex);
+        }
+      } else if (vertex.value.rings.length > 0) {
+        var _nextRing = this.getRing(vertex.value.rings[0]);
+
+        if (!_nextRing.positioned) {
+          var _nextCenter2 = Vector2.subtract(vertex.previousPosition, vertex.position);
+
+          _nextCenter2.invert();
+          _nextCenter2.normalize();
+
+          var _r2 = MathHelper.polyCircumradius(this.opts.bondLength, _nextRing.getSize());
+
+          _nextCenter2.multiplyScalar(_r2);
+          _nextCenter2.add(vertex.position);
+
+          this.createRing(_nextRing, _nextCenter2, vertex);
+        }
+      } else {
+        // Draw the non-ring vertices connected to this one  
+        var isStereoCenter = vertex.value.isStereoCenter;
+        var tmpNeighbours = vertex.getNeighbours();
+        var _neighbours = Array();
+
+        // Remove neighbours that are not drawn
+        for (var i = 0; i < tmpNeighbours.length; i++) {
+          if (this.graph.vertices[tmpNeighbours[i]].value.isDrawn) {
+            _neighbours.push(tmpNeighbours[i]);
+          }
+        }
+
+        // Remove the previous vertex (which has already been drawn)
+        if (previousVertex) {
+          _neighbours = ArrayHelper.remove(_neighbours, previousVertex.id);
+        }
+
+        var previousAngle = vertex.getAngle();
+
+        if (_neighbours.length === 1) {
+          var nextVertex = this.graph.vertices[_neighbours[0]];
+
+          // Make a single chain always cis except when there's a tribble (yes, this is a Star Trek reference) bond
+          // or if there are successive double bonds. Added a ring check because if there is an aromatic ring the ring bond inside the ring counts as a double bond and leads to =-= being straight.
+          if (vertex.value.bondType === '#' || previousVertex && previousVertex.value.bondType === '#' || vertex.value.bondType === '=' && previousVertex && previousVertex.value.rings.length === 0 && previousVertex.value.bondType === '=' && vertex.value.branchBond !== '-') {
+            vertex.value.drawExplicit = false;
+
+            if (previousVertex) {
+              var straightEdge1 = this.graph.getEdge(vertex.id, previousVertex.id);
+              straightEdge1.center = true;
+            }
+
+            var straightEdge2 = this.graph.getEdge(vertex.id, nextVertex.id);
+            straightEdge2.center = true;
+
+            if (vertex.value.bondType === '#' || previousVertex && previousVertex.value.bondType === '#') {
+              nextVertex.angle = 0.0;
+            }
+
+            nextVertex.drawExplicit = true;
+
+            this.createNextBond(nextVertex, vertex, previousAngle + nextVertex.angle);
+          } else if (previousVertex && previousVertex.value.rings.length > 0) {
+            // If coming out of a ring, always draw away from the center of mass
+            var proposedAngleA = MathHelper.toRad(60);
+            var proposedAngleB = -proposedAngleA;
+
+            var proposedVectorA = new Vector2(this.opts.bondLength, 0);
+            var proposedVectorB = new Vector2(this.opts.bondLength, 0);
+
+            proposedVectorA.rotate(proposedAngleA).add(vertex.position);
+            proposedVectorB.rotate(proposedAngleB).add(vertex.position);
+
+            // let centerOfMass = this.getCurrentCenterOfMassInNeigbourhood(vertex.position, 100);
+            var centerOfMass = this.getCurrentCenterOfMass();
+            var distanceA = proposedVectorA.distanceSq(centerOfMass);
+            var distanceB = proposedVectorB.distanceSq(centerOfMass);
+
+            nextVertex.angle = distanceA < distanceB ? proposedAngleB : proposedAngleA;
+
+            this.createNextBond(nextVertex, vertex, previousAngle + nextVertex.angle);
+          } else {
+            var a = vertex.angle;
+            // Take the min and max if the previous angle was in a 4-neighbourhood (90° angles)
+            // TODO: If a is null or zero, it should be checked whether or not this one should go cis or trans, that is,
+            //       it should go into the oposite direction of the last non-null or 0 previous vertex / angle.
+            if (previousVertex && previousVertex.neighbours.length > 3) {
+              if (a > 0) {
+                a = Math.min(1.0472, a);
+              } else if (a < 0) {
+                a = Math.max(-1.0472, a);
+              } else {
+                a = 1.0472;
+              }
+            } else if (!a) {
+              var _v3 = this.getLastVertexWithAngle(vertex.id);
+              a = _v3.angle;
+
+              if (!a) {
+                a = 1.0472;
+              }
+            }
+
+            // Handle configuration around double bonds
+            if (previousVertex && !doubleBondConfigSet) {
+              var bondType = this.graph.getEdge(vertex.id, nextVertex.id).bondType;
+
+              if (bondType === '/') {
+                if (this.doubleBondConfig === '/') {
+                  // Nothing to do since it will be trans per default
+                } else if (this.doubleBondConfig === '\\') {
+                  a = -a;
+                }
+                this.doubleBondConfig = null;
+              } else if (bondType === '\\') {
+                if (this.doubleBondConfig === '/') {
+                  a = -a;
+                } else if (this.doubleBondConfig === '\\') {
+                  // Nothing to do since it will be trans per default
+                }
+                this.doubleBondConfig = null;
+              }
+            }
+
+            if (originShortest) {
+              nextVertex.angle = a;
+            } else {
+              nextVertex.angle = -a;
+            }
+
+            this.createNextBond(nextVertex, vertex, previousAngle + nextVertex.angle);
+          }
+        } else if (_neighbours.length === 2) {
+          // If the previous vertex comes out of a ring, it doesn't have an angle set
+          var _a = vertex.angle;
+
+          if (!_a) {
+            _a = 1.0472;
+          }
+
+          // Check for the longer subtree - always go with cis for the longer subtree
+          var subTreeDepthA = this.graph.getTreeDepth(_neighbours[0], vertex.id);
+          var subTreeDepthB = this.graph.getTreeDepth(_neighbours[1], vertex.id);
+
+          var l = this.graph.vertices[_neighbours[0]];
+          var _r3 = this.graph.vertices[_neighbours[1]];
+
+          l.value.subtreeDepth = subTreeDepthA;
+          _r3.value.subtreeDepth = subTreeDepthB;
+
+          // Also get the subtree for the previous direction (this is important when
+          // the previous vertex is the shortest path)
+          var subTreeDepthC = this.graph.getTreeDepth(previousVertex ? previousVertex.id : null, vertex.id);
+          if (previousVertex) {
+            previousVertex.value.subtreeDepth = subTreeDepthC;
+          }
+
+          var cis = 0;
+          var trans = 1;
+
+          // Carbons go always cis
+          if (_r3.value.element === 'C' && l.value.element !== 'C' && subTreeDepthB > 1 && subTreeDepthA < 5) {
+            cis = 1;
+            trans = 0;
+          } else if (_r3.value.element !== 'C' && l.value.element === 'C' && subTreeDepthA > 1 && subTreeDepthB < 5) {
+            cis = 0;
+            trans = 1;
+          } else if (subTreeDepthB > subTreeDepthA) {
+            cis = 1;
+            trans = 0;
+          }
+
+          var cisVertex = this.graph.vertices[_neighbours[cis]];
+          var transVertex = this.graph.vertices[_neighbours[trans]];
+
+          var edgeCis = this.graph.getEdge(vertex.id, cisVertex.id);
+          var edgeTrans = this.graph.getEdge(vertex.id, transVertex.id);
+
+          // If the origin tree is the shortest, make them the main chain
+          var _originShortest = false;
+          if (subTreeDepthC < subTreeDepthA && subTreeDepthC < subTreeDepthB) {
+            _originShortest = true;
+          }
+
+          transVertex.angle = _a;
+          cisVertex.angle = -_a;
+
+          if (this.doubleBondConfig === '\\') {
+            if (transVertex.value.branchBond === '\\') {
+              transVertex.angle = -_a;
+              cisVertex.angle = _a;
+            }
+          } else if (this.doubleBondConfig === '/') {
+            if (transVertex.value.branchBond === '/') {
+              transVertex.angle = -_a;
+              cisVertex.angle = _a;
+            }
+          }
+
+          this.createNextBond(transVertex, vertex, previousAngle + transVertex.angle, _originShortest);
+          this.createNextBond(cisVertex, vertex, previousAngle + cisVertex.angle, _originShortest);
+        } else if (_neighbours.length === 3) {
+          // The vertex with the longest sub-tree should always go straight
+          var d1 = this.graph.getTreeDepth(_neighbours[0], vertex.id);
+          var d2 = this.graph.getTreeDepth(_neighbours[1], vertex.id);
+          var d3 = this.graph.getTreeDepth(_neighbours[2], vertex.id);
+
+          var s = this.graph.vertices[_neighbours[0]];
+          var _l = this.graph.vertices[_neighbours[1]];
+          var _r4 = this.graph.vertices[_neighbours[2]];
+
+          s.value.subtreeDepth = d1;
+          _l.value.subtreeDepth = d2;
+          _r4.value.subtreeDepth = d3;
+
+          if (d2 > d1 && d2 > d3) {
+            s = this.graph.vertices[_neighbours[1]];
+            _l = this.graph.vertices[_neighbours[0]];
+            _r4 = this.graph.vertices[_neighbours[2]];
+          } else if (d3 > d1 && d3 > d2) {
+            s = this.graph.vertices[_neighbours[2]];
+            _l = this.graph.vertices[_neighbours[0]];
+            _r4 = this.graph.vertices[_neighbours[1]];
+          }
+
+          // Create a cross if more than one subtree is of length > 1
+          // or the vertex is connected to a ring
+          if (previousVertex && previousVertex.value.rings.length < 1 && s.value.rings.length < 1 && _l.value.rings.length < 1 && _r4.value.rings.length < 1 && this.graph.getTreeDepth(_l.id, vertex.id) === 1 && this.graph.getTreeDepth(_r4.id, vertex.id) === 1 && this.graph.getTreeDepth(s.id, vertex.id) > 1) {
+
+            s.angle = -vertex.angle;
+            if (vertex.angle >= 0) {
+              _l.angle = MathHelper.toRad(30);
+              _r4.angle = MathHelper.toRad(90);
+            } else {
+              _l.angle = -MathHelper.toRad(30);
+              _r4.angle = -MathHelper.toRad(90);
+            }
+
+            this.createNextBond(s, vertex, previousAngle + s.angle);
+            this.createNextBond(_l, vertex, previousAngle + _l.angle);
+            this.createNextBond(_r4, vertex, previousAngle + _r4.angle);
+          } else {
+            s.angle = 0.0;
+            _l.angle = MathHelper.toRad(90);
+            _r4.angle = -MathHelper.toRad(90);
+
+            this.createNextBond(s, vertex, previousAngle + s.angle);
+            this.createNextBond(_l, vertex, previousAngle + _l.angle);
+            this.createNextBond(_r4, vertex, previousAngle + _r4.angle);
+          }
+        } else if (_neighbours.length === 4) {
+          // The vertex with the longest sub-tree should always go to the reflected opposide direction
+          var _d = this.graph.getTreeDepth(_neighbours[0], vertex.id);
+          var _d2 = this.graph.getTreeDepth(_neighbours[1], vertex.id);
+          var _d3 = this.graph.getTreeDepth(_neighbours[2], vertex.id);
+          var d4 = this.graph.getTreeDepth(_neighbours[3], vertex.id);
+
+          var w = this.graph.vertices[_neighbours[0]];
+          var x = this.graph.vertices[_neighbours[1]];
+          var y = this.graph.vertices[_neighbours[2]];
+          var z = this.graph.vertices[_neighbours[3]];
+
+          w.value.subtreeDepth = _d;
+          x.value.subtreeDepth = _d2;
+          y.value.subtreeDepth = _d3;
+          z.value.subtreeDepth = d4;
+
+          if (_d2 > _d && _d2 > _d3 && _d2 > d4) {
+            w = this.graph.vertices[_neighbours[1]];
+            x = this.graph.vertices[_neighbours[0]];
+            y = this.graph.vertices[_neighbours[2]];
+            z = this.graph.vertices[_neighbours[3]];
+          } else if (_d3 > _d && _d3 > _d2 && _d3 > d4) {
+            w = this.graph.vertices[_neighbours[2]];
+            x = this.graph.vertices[_neighbours[0]];
+            y = this.graph.vertices[_neighbours[1]];
+            z = this.graph.vertices[_neighbours[3]];
+          } else if (d4 > _d && d4 > _d2 && d4 > _d3) {
+            w = this.graph.vertices[_neighbours[3]];
+            x = this.graph.vertices[_neighbours[0]];
+            y = this.graph.vertices[_neighbours[1]];
+            z = this.graph.vertices[_neighbours[2]];
+          }
+
+          w.angle = -MathHelper.toRad(36);
+          x.angle = MathHelper.toRad(36);
+          y.angle = -MathHelper.toRad(108);
+          z.angle = MathHelper.toRad(108);
+
+          this.createNextBond(w, vertex, previousAngle + w.angle);
+          this.createNextBond(x, vertex, previousAngle + x.angle);
+          this.createNextBond(y, vertex, previousAngle + y.angle);
+          this.createNextBond(z, vertex, previousAngle + z.angle);
+        }
+      }
+    }
+
+    /**
+     * Gets the vetex sharing the edge that is the common bond of two rings.
+     *
+     * @param {Vertex} vertex A vertex.
+     * @returns {(Number|null)} The id of a vertex sharing the edge that is the common bond of two rings with the vertex provided or null, if none.
+     */
+
+  }, {
+    key: 'getCommonRingbondNeighbour',
+    value: function getCommonRingbondNeighbour(vertex) {
+      var neighbours = vertex.neighbours;
+
+      for (var i = 0; i < neighbours.length; i++) {
+        var neighbour = this.graph.vertices[neighbours[i]];
+
+        if (ArrayHelper.containsAll(neighbour.value.rings, vertex.value.rings)) {
+          return neighbour;
+        }
+      }
+
+      return null;
+    }
+
+    /**
+     * Check if a vector is inside any ring.
+     *
+     * @param {Vector2} vec A vector.
+     * @returns {Boolean} A boolean indicating whether or not the point (vector) is inside any of the rings associated with the current molecule.
+     */
+
+  }, {
+    key: 'isPointInRing',
+    value: function isPointInRing(vec) {
+      for (var i = 0; i < this.rings.length; i++) {
+        var ring = this.rings[i];
+
+        if (!ring.positioned) {
+          continue;
+        }
+
+        var radius = MathHelper.polyCircumradius(this.opts.bondLength, ring.getSize());
+        var radiusSq = radius * radius;
+
+        if (vec.distanceSq(ring.center) < radiusSq) {
+          return true;
+        }
+      }
+
+      return false;
+    }
+
+    /**
+     * Check whether or not an edge is part of a ring.
+     *
+     * @param {Edge} edge An edge.
+     * @returns {Boolean} A boolean indicating whether or not the edge is part of a ring.
+     */
+
+  }, {
+    key: 'isEdgeInRing',
+    value: function isEdgeInRing(edge) {
+      var source = this.graph.vertices[edge.sourceId];
+      var target = this.graph.vertices[edge.targetId];
+
+      return this.areVerticesInSameRing(source, target);
+    }
+
+    /**
+     * Check whether or not an edge is rotatable.
+     *
+     * @param {Edge} edge An edge.
+     * @returns {Boolean} A boolean indicating whether or not the edge is rotatable.
+     */
+
+  }, {
+    key: 'isEdgeRotatable',
+    value: function isEdgeRotatable(edge) {
+      var vertexA = this.graph.vertices[edge.sourceId];
+      var vertexB = this.graph.vertices[edge.targetId];
+
+      // Only single bonds are rotatable
+      if (edge.bondType !== '-') {
+        return false;
+      }
+
+      // Do not rotate edges that have a further single bond to each side - do that!
+      // If the bond is terminal, it doesn't make sense to rotate it
+      // if (vertexA.getNeighbourCount() + vertexB.getNeighbourCount() < 5) {
+      //   return false;
+      // }
+
+      if (vertexA.isTerminal() || vertexB.isTerminal()) {
+        return false;
+      }
+
+      // Ringbonds are not rotatable
+      if (vertexA.value.rings.length > 0 && vertexB.value.rings.length > 0 && this.areVerticesInSameRing(vertexA, vertexB)) {
+        return false;
+      }
+
+      return true;
+    }
+
+    /**
+     * Check whether or not a ring is an implicitly defined aromatic ring (lower case smiles).
+     *
+     * @param {Ring} ring A ring.
+     * @returns {Boolean} A boolean indicating whether or not a ring is implicitly defined as aromatic.
+     */
+
+  }, {
+    key: 'isRingAromatic',
+    value: function isRingAromatic(ring) {
+      for (var i = 0; i < ring.members.length; i++) {
+        var vertex = this.graph.vertices[ring.members[i]];
+
+        if (!vertex.value.isPartOfAromaticRing) {
+          return false;
+        }
+      }
+
+      return true;
+    }
+
+    /**
+     * Get the normals of an edge.
+     *
+     * @param {Edge} edge An edge.
+     * @returns {Vector2[]} An array containing two vectors, representing the normals.
+     */
+
+  }, {
+    key: 'getEdgeNormals',
+    value: function getEdgeNormals(edge) {
+      var v1 = this.graph.vertices[edge.sourceId].position;
+      var v2 = this.graph.vertices[edge.targetId].position;
+
+      // Get the normalized normals for the edge
+      var normals = Vector2.units(v1, v2);
+
+      return normals;
+    }
+
+    /**
+     * Returns an array of vertices that are neighbouring a vertix but are not members of a ring (including bridges).
+     *
+     * @param {Number} vertexId A vertex id.
+     * @returns {Vertex[]} An array of vertices.
+     */
+
+  }, {
+    key: 'getNonRingNeighbours',
+    value: function getNonRingNeighbours(vertexId) {
+      var nrneighbours = Array();
+      var vertex = this.graph.vertices[vertexId];
+      var neighbours = vertex.neighbours;
+
+      for (var i = 0; i < neighbours.length; i++) {
+        var neighbour = this.graph.vertices[neighbours[i]];
+        var nIntersections = ArrayHelper.intersection(vertex.value.rings, neighbour.value.rings).length;
+
+        if (nIntersections === 0 && neighbour.value.isBridge == false) {
+          nrneighbours.push(neighbour);
+        }
+      }
+
+      return nrneighbours;
+    }
+
+    /**
+     * Annotaed stereochemistry information for visualization.
+     */
+
+  }, {
+    key: 'annotateStereochemistry',
+    value: function annotateStereochemistry() {
+      var maxDepth = 10;
+
+      // For each stereo-center
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+
+        if (!vertex.value.isStereoCenter) {
+          continue;
+        }
+
+        var neighbours = vertex.getNeighbours();
+        var nNeighbours = neighbours.length;
+        var priorities = Array(nNeighbours);
+
+        for (var j = 0; j < nNeighbours; j++) {
+          var visited = new Uint8Array(this.graph.vertices.length);
+          var priority = Array(Array());
+          visited[vertex.id] = 1;
+
+          this.visitStereochemistry(neighbours[j], vertex.id, visited, priority, maxDepth, 0);
+
+          // Sort each level according to atomic number
+          for (var k = 0; k < priority.length; k++) {
+            priority[k].sort(function (a, b) {
+              return b - a;
+            });
+          }
+
+          priorities[j] = [j, priority];
+        }
+
+        var maxLevels = 0;
+        var maxEntries = 0;
+        for (var j = 0; j < priorities.length; j++) {
+          if (priorities[j][1].length > maxLevels) {
+            maxLevels = priorities[j][1].length;
+          }
+
+          for (var k = 0; k < priorities[j][1].length; k++) {
+            if (priorities[j][1][k].length > maxEntries) {
+              maxEntries = priorities[j][1][k].length;
+            }
+          }
+        }
+
+        for (var j = 0; j < priorities.length; j++) {
+          var diff = maxLevels - priorities[j][1].length;
+          for (var k = 0; k < diff; k++) {
+            priorities[j][1].push([]);
+          }
+
+          // Break ties by the position in the SMILES string as per specification
+          priorities[j][1].push([neighbours[j]]);
+
+          // Make all same length. Fill with zeroes.
+          for (var k = 0; k < priorities[j][1].length; k++) {
+            var _diff = maxEntries - priorities[j][1][k].length;
+
+            for (var l = 0; l < _diff; l++) {
+              priorities[j][1][k].push(0);
+            }
+          }
+        }
+
+        priorities.sort(function (a, b) {
+          for (var j = 0; j < a[1].length; j++) {
+            for (var k = 0; k < a[1][j].length; k++) {
+              if (a[1][j][k] > b[1][j][k]) {
+                return -1;
+              } else if (a[1][j][k] < b[1][j][k]) {
+                return 1;
+              }
+            }
+          }
+
+          return 0;
+        });
+
+        var order = new Uint8Array(nNeighbours);
+        for (var j = 0; j < nNeighbours; j++) {
+          order[j] = priorities[j][0];
+          vertex.value.priority = j;
+        }
+
+        // Check the angles between elements 0 and 1, and 0 and 2 to determine whether they are
+        // drawn cw or ccw
+        // TODO: OC(Cl)=[C@]=C(C)F currently fails here, however this is, IMHO, not a valid SMILES.
+        var posA = this.graph.vertices[neighbours[order[0]]].position;
+        var posB = this.graph.vertices[neighbours[order[1]]].position;
+        var posC = this.graph.vertices[neighbours[order[2]]].position;
+
+        var cwA = posA.relativeClockwise(posB, vertex.position);
+        var cwB = posA.relativeClockwise(posC, vertex.position);
+
+        // If the second priority is clockwise from the first, the ligands are drawn clockwise, since
+        // The hydrogen can be drawn on either side
+        var isCw = cwA === -1;
+
+        var rotation = vertex.value.bracket.chirality === '@' ? -1 : 1;
+        var rs = MathHelper.parityOfPermutation(order) * rotation === 1 ? 'R' : 'S';
+
+        // Flip the hydrogen direction when the drawing doesn't match the chirality.
+        var wedgeA = 'down';
+        var wedgeB = 'up';
+        if (isCw && rs !== 'R' || !isCw && rs !== 'S') {
+          vertex.value.hydrogenDirection = 'up';
+          wedgeA = 'up';
+          wedgeB = 'down';
+        }
+
+        if (vertex.value.hasHydrogen) {
+          this.graph.getEdge(vertex.id, neighbours[order[order.length - 1]]).wedge = wedgeA;
+        }
+
+        // Get the shortest subtree to flip up / down. Ignore lowest priority
+        // The rules are following:
+        // 1. Do not draw wedge between two stereocenters
+        // 2. Heteroatoms
+        // 3. Draw outside ring
+        // 4. Shortest subtree
+
+        var wedgeOrder = new Array(neighbours.length - 1);
+        var showHydrogen = vertex.value.rings.length > 1 && vertex.value.hasHydrogen;
+        var offset = vertex.value.hasHydrogen ? 1 : 0;
+
+        for (var j = 0; j < order.length - offset; j++) {
+          wedgeOrder[j] = new Uint32Array(2);
+          var neighbour = this.graph.vertices[neighbours[order[j]]];
+          wedgeOrder[j][0] += neighbour.value.isStereoCenter ? 0 : 100000;
+          // wedgeOrder[j][0] += neighbour.value.rings.length > 0 ? 0 : 10000;
+          // Only add if in same ring, unlike above
+          wedgeOrder[j][0] += this.areVerticesInSameRing(neighbour, vertex) ? 0 : 10000;
+          wedgeOrder[j][0] += neighbour.value.isHeteroAtom() ? 1000 : 0;
+          wedgeOrder[j][0] -= neighbour.value.subtreeDepth === 0 ? 1000 : 0;
+          wedgeOrder[j][0] += 1000 - neighbour.value.subtreeDepth;
+          wedgeOrder[j][1] = neighbours[order[j]];
+        }
+
+        wedgeOrder.sort(function (a, b) {
+          if (a[0] > b[0]) {
+            return -1;
+          } else if (a[0] < b[0]) {
+            return 1;
+          }
+          return 0;
+        });
+
+        // If all neighbours are in a ring, do not draw wedge, the hydrogen will be drawn.
+        if (!showHydrogen) {
+          var wedgeId = wedgeOrder[0][1];
+
+          if (vertex.value.hasHydrogen) {
+            this.graph.getEdge(vertex.id, wedgeId).wedge = wedgeB;
+          } else {
+            var wedge = wedgeB;
+
+            for (var j = order.length - 1; j >= 0; j--) {
+              if (wedge === wedgeA) {
+                wedge = wedgeB;
+              } else {
+                wedge = wedgeA;
+              }
+              if (neighbours[order[j]] === wedgeId) {
+                break;
+              }
+            }
+
+            this.graph.getEdge(vertex.id, wedgeId).wedge = wedge;
+          }
+        }
+
+        vertex.value.chirality = rs;
+      }
+    }
+
+    /**
+     * 
+     * 
+     * @param {Number} vertexId The id of a vertex.
+     * @param {(Number|null)} previousVertexId The id of the parent vertex of the vertex.
+     * @param {Uint8Array} visited An array containing the visited flag for all vertices in the graph.
+     * @param {Array} priority An array of arrays storing the atomic numbers for each level.
+     * @param {Number} maxDepth The maximum depth.
+     * @param {Number} depth The current depth.
+     */
+
+  }, {
+    key: 'visitStereochemistry',
+    value: function visitStereochemistry(vertexId, previousVertexId, visited, priority, maxDepth, depth) {
+      var parentAtomicNumber = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : 0;
+
+      visited[vertexId] = 1;
+      var vertex = this.graph.vertices[vertexId];
+      var atomicNumber = vertex.value.getAtomicNumber();
+
+      if (priority.length <= depth) {
+        priority.push(Array());
+      }
+
+      for (var i = 0; i < this.graph.getEdge(vertexId, previousVertexId).weight; i++) {
+        priority[depth].push(parentAtomicNumber * 1000 + atomicNumber);
+      }
+
+      var neighbours = this.graph.vertices[vertexId].neighbours;
+
+      for (var i = 0; i < neighbours.length; i++) {
+        if (visited[neighbours[i]] !== 1 && depth < maxDepth - 1) {
+          this.visitStereochemistry(neighbours[i], vertexId, visited.slice(), priority, maxDepth, depth + 1, atomicNumber);
+        }
+      }
+
+      // Valences are filled with hydrogens and passed to the next level.
+      if (depth < maxDepth - 1) {
+        var bonds = 0;
+
+        for (var i = 0; i < neighbours.length; i++) {
+          bonds += this.graph.getEdge(vertexId, neighbours[i]).weight;
+        }
+
+        for (var i = 0; i < vertex.value.getMaxBonds() - bonds; i++) {
+          if (priority.length <= depth + 1) {
+            priority.push(Array());
+          }
+
+          priority[depth + 1].push(atomicNumber * 1000 + 1);
+        }
+      }
+    }
+
+    /**
+     * Creates pseudo-elements (such as Et, Me, Ac, Bz, ...) at the position of the carbon sets
+     * the involved atoms not to be displayed.
+     */
+
+  }, {
+    key: 'initPseudoElements',
+    value: function initPseudoElements() {
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var vertex = this.graph.vertices[i];
+        var neighbourIds = vertex.neighbours;
+        var neighbours = Array(neighbourIds.length);
+
+        for (var j = 0; j < neighbourIds.length; j++) {
+          neighbours[j] = this.graph.vertices[neighbourIds[j]];
+        }
+
+        // Ignore atoms that have less than 3 neighbours, except if
+        // the vertex is connected to a ring and has two neighbours
+        if (vertex.getNeighbourCount() < 3 || vertex.value.rings.length > 0) {
+          continue;
+        }
+
+        // TODO: This exceptions should be handled more elegantly (via config file?)
+
+        // Ignore phosphates (especially for triphosphates)
+        if (vertex.value.element === 'P') {
+          continue;
+        }
+
+        // Ignore also guanidine
+        if (vertex.value.element === 'C' && neighbours.length === 3 && neighbours[0].value.element === 'N' && neighbours[1].value.element === 'N' && neighbours[2].value.element === 'N') {
+          continue;
+        }
+
+        // Continue if there are less than two heteroatoms
+        // or if a neighbour has more than 1 neighbour
+        var heteroAtomCount = 0;
+        var ctn = 0;
+
+        for (var j = 0; j < neighbours.length; j++) {
+          var neighbour = neighbours[j];
+          var neighbouringElement = neighbour.value.element;
+          var neighbourCount = neighbour.getNeighbourCount();
+
+          if (neighbouringElement !== 'C' && neighbouringElement !== 'H' && neighbourCount === 1) {
+            heteroAtomCount++;
+          }
+
+          if (neighbourCount > 1) {
+            ctn++;
+          }
+        }
+
+        if (ctn > 1 || heteroAtomCount < 2) {
+          continue;
+        }
+
+        // Get the previous atom (the one which is not terminal)
+        var previous = null;
+
+        for (var j = 0; j < neighbours.length; j++) {
+          var _neighbour = neighbours[j];
+
+          if (_neighbour.getNeighbourCount() > 1) {
+            previous = _neighbour;
+          }
+        }
+
+        for (var j = 0; j < neighbours.length; j++) {
+          var _neighbour2 = neighbours[j];
+
+          if (_neighbour2.getNeighbourCount() > 1) {
+            continue;
+          }
+
+          _neighbour2.value.isDrawn = false;
+
+          var hydrogens = Atom.maxBonds[_neighbour2.value.element] - _neighbour2.value.bondCount;
+          var charge = '';
+
+          if (_neighbour2.value.bracket) {
+            hydrogens = _neighbour2.value.bracket.hcount;
+            charge = _neighbour2.value.bracket.charge || 0;
+          }
+
+          vertex.value.attachPseudoElement(_neighbour2.value.element, previous ? previous.value.element : null, hydrogens, charge);
+        }
+      }
+
+      // The second pass
+      for (var i = 0; i < this.graph.vertices.length; i++) {
+        var _vertex4 = this.graph.vertices[i];
+        var atom = _vertex4.value;
+        var element = atom.element;
+
+        if (element === 'C' || element === 'H' || !atom.isDrawn) {
+          continue;
+        }
+
+        var _neighbourIds = _vertex4.neighbours;
+        var _neighbours2 = Array(_neighbourIds.length);
+
+        for (var j = 0; j < _neighbourIds.length; j++) {
+          _neighbours2[j] = this.graph.vertices[_neighbourIds[j]];
+        }
+
+        for (var j = 0; j < _neighbours2.length; j++) {
+          var _neighbour3 = _neighbours2[j].value;
+
+          if (!_neighbour3.hasAttachedPseudoElements || _neighbour3.getAttachedPseudoElementsCount() !== 2) {
+            continue;
+          }
+
+          var pseudoElements = _neighbour3.getAttachedPseudoElements();
+
+          if (pseudoElements.hasOwnProperty('0O') && pseudoElements.hasOwnProperty('3C')) {
+            _neighbour3.isDrawn = false;
+            _vertex4.value.attachPseudoElement('Ac', '', 0);
+          }
+        }
+      }
+    }
+  }]);
+
+  return Drawer;
+}();
+
+module.exports = Drawer;
+
+},{"./ArrayHelper":2,"./Atom":3,"./CanvasWrapper":4,"./Edge":6,"./Graph":7,"./Line":8,"./MathHelper":9,"./Ring":11,"./RingConnection":12,"./SSSR":13,"./Vector2":14,"./Vertex":15}],6:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+
+/** 
+ * A class representing an edge. 
+ * 
+ * @property {Number} id The id of this edge.
+ * @property {Number} sourceId The id of the source vertex.
+ * @property {Number} targetId The id of the target vertex.
+ * @property {Number} weight The weight of this edge. That is, the degree of the bond (single bond = 1, double bond = 2, etc).
+ * @property {String} [bondType='-'] The bond type of this edge.
+ * @property {Boolean} [isPartOfAromaticRing=false] Whether or not this edge is part of an aromatic ring.
+ * @property {Boolean} [center=false] Wheter or not the bond is centered. For example, this affects straight double bonds.
+ * @property {String} [wedge=''] Wedge direction. Either '', 'up' or 'down'
+ */
+var Edge = function () {
+    /**
+     * The constructor for the class Edge.
+     *
+     * @param {Number} sourceId A vertex id.
+     * @param {Number} targetId A vertex id.
+     * @param {Number} [weight=1] The weight of the edge.
+     */
+    function Edge(sourceId, targetId) {
+        var weight = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 1;
+
+        _classCallCheck(this, Edge);
+
+        this.id = null;
+        this.sourceId = sourceId;
+        this.targetId = targetId;
+        this.weight = weight;
+        this.bondType = '-';
+        this.isPartOfAromaticRing = false;
+        this.center = false;
+        this.wedge = '';
+    }
+
+    /**
+     * Set the bond type of this edge. This also sets the edge weight.
+     * @param {String} bondType 
+     */
+
+
+    _createClass(Edge, [{
+        key: 'setBondType',
+        value: function setBondType(bondType) {
+            this.bondType = bondType;
+            this.weight = Edge.bonds[bondType];
+        }
+
+        /**
+         * An object mapping the bond type to the number of bonds.
+         *
+         * @returns {Object} The object containing the map.
+         */
+
+    }], [{
+        key: 'bonds',
+        get: function get() {
+            return {
+                '-': 1,
+                '/': 1,
+                '\\': 1,
+                '=': 2,
+                '#': 3,
+                '$': 4
+            };
+        }
+    }]);
+
+    return Edge;
+}();
+
+module.exports = Edge;
+
+},{}],7:[function(require,module,exports){
+'use strict';
+
+var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var MathHelper = require('./MathHelper');
+var Vector2 = require('./Vector2');
+var Vertex = require('./Vertex');
+var Edge = require('./Edge');
+var Ring = require('./Ring');
+var Atom = require('./Atom');
+
+/** 
+ * A class representing the molecular graph. 
+ * 
+ * @property {Vertex[]} vertices The vertices of the graph.
+ * @property {Edge[]} edges The edges of this graph.
+ * @property {Object} vertexIdsToEdgeId A map mapping vertex ids to the edge between the two vertices. The key is defined as vertexAId + '_' + vertexBId.
+ * @property {Boolean} isometric A boolean indicating whether or not the SMILES associated with this graph is isometric.
+ */
+
+var Graph = function () {
+  /**
+   * The constructor of the class Graph.
+   * 
+   * @param {Object} parseTree A SMILES parse tree.
+   * @param {Boolean} [isomeric=false] A boolean specifying whether or not the SMILES is isomeric.
+   */
+  function Graph(parseTree) {
+    var isomeric = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+
+    _classCallCheck(this, Graph);
+
+    this.vertices = Array();
+    this.edges = Array();
+    this.vertexIdsToEdgeId = {};
+    this.isomeric = isomeric;
+
+    // Used for the bridge detection algorithm
+    this._time = 0;
+    this._init(parseTree);
+  }
+
+  /**
+   * PRIVATE FUNCTION. Initializing the graph from the parse tree.
+   *
+   * @param {Object} node The current node in the parse tree.
+   * @param {Number} parentVertexId=null The id of the previous vertex.
+   * @param {Boolean} isBranch=false Whether or not the bond leading to this vertex is a branch bond. Branches are represented by parentheses in smiles (e.g. CC(O)C).
+   */
+
+
+  _createClass(Graph, [{
+    key: '_init',
+    value: function _init(node) {
+      var order = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+      var parentVertexId = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+      var isBranch = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
+
+      // Create a new vertex object
+      var atom = new Atom(node.atom.element ? node.atom.element : node.atom, node.bond);
+
+      atom.branchBond = node.branchBond;
+      atom.ringbonds = node.ringbonds;
+      atom.bracket = node.atom.element ? node.atom : null;
+
+      var vertex = new Vertex(atom);
+      var parentVertex = this.vertices[parentVertexId];
+
+      this.addVertex(vertex);
+
+      // Add the id of this node to the parent as child
+      if (parentVertexId !== null) {
+        vertex.setParentVertexId(parentVertexId);
+        vertex.value.addNeighbouringElement(parentVertex.value.element);
+        parentVertex.addChild(vertex.id);
+        parentVertex.value.addNeighbouringElement(atom.element);
+
+        // In addition create a spanningTreeChildren property, which later will
+        // not contain the children added through ringbonds
+        parentVertex.spanningTreeChildren.push(vertex.id);
+
+        // Add edge between this node and its parent
+        var edge = new Edge(parentVertexId, vertex.id, 1);
+        var vertexId = null;
+
+        if (isBranch) {
+          edge.setBondType(vertex.value.branchBond || '-');
+          vertexId = vertex.id;
+          edge.setBondType(vertex.value.branchBond || '-');
+          vertexId = vertex.id;
+        } else {
+          edge.setBondType(parentVertex.value.bondType || '-');
+          vertexId = parentVertex.id;
+        }
+
+        var edgeId = this.addEdge(edge);
+      }
+
+      var offset = node.ringbondCount + 1;
+
+      if (atom.bracket) {
+        offset += atom.bracket.hcount;
+      }
+
+      var stereoHydrogens = 0;
+      if (atom.bracket && atom.bracket.chirality) {
+        atom.isStereoCenter = true;
+        stereoHydrogens = atom.bracket.hcount;
+        for (var i = 0; i < stereoHydrogens; i++) {
+          this._init({
+            atom: 'H',
+            isBracket: 'false',
+            branches: Array(),
+            branchCount: 0,
+            ringbonds: Array(),
+            ringbondCount: false,
+            next: null,
+            hasNext: false,
+            bond: '-'
+          }, i, vertex.id, true);
+        }
+      }
+
+      for (var i = 0; i < node.branchCount; i++) {
+        this._init(node.branches[i], i + offset, vertex.id, true);
+      }
+
+      if (node.hasNext) {
+        this._init(node.next, node.branchCount + offset, vertex.id);
+      }
+    }
+
+    /**
+     * Clears all the elements in this graph (edges and vertices).
+     */
+
+  }, {
+    key: 'clear',
+    value: function clear() {
+      this.vertices = Array();
+      this.edges = Array();
+      this.vertexIdsToEdgeId = {};
+    }
+
+    /**
+     * Add a vertex to the graph.
+     *
+     * @param {Vertex} vertex A new vertex.
+     * @returns {Number} The vertex id of the new vertex.
+     */
+
+  }, {
+    key: 'addVertex',
+    value: function addVertex(vertex) {
+      vertex.id = this.vertices.length;
+      this.vertices.push(vertex);
+
+      return vertex.id;
+    }
+
+    /**
+     * Add an edge to the graph.
+     *
+     * @param {Edge} edge A new edge.
+     * @returns {Number} The edge id of the new edge.
+     */
+
+  }, {
+    key: 'addEdge',
+    value: function addEdge(edge) {
+      var source = this.vertices[edge.sourceId];
+      var target = this.vertices[edge.targetId];
+
+      edge.id = this.edges.length;
+      this.edges.push(edge);
+
+      this.vertexIdsToEdgeId[edge.sourceId + '_' + edge.targetId] = edge.id;
+      this.vertexIdsToEdgeId[edge.targetId + '_' + edge.sourceId] = edge.id;
+      edge.isPartOfAromaticRing = source.value.isPartOfAromaticRing && target.value.isPartOfAromaticRing;
+
+      source.value.bondCount += edge.weight;
+      target.value.bondCount += edge.weight;
+
+      source.edges.push(edge.id);
+      target.edges.push(edge.id);
+
+      return edge.id;
+    }
+
+    /**
+     * Returns the edge between two given vertices.
+     *
+     * @param {Number} vertexIdA A vertex id.
+     * @param {Number} vertexIdB A vertex id.
+     * @returns {(Edge|null)} The edge or, if no edge can be found, null.
+     */
+
+  }, {
+    key: 'getEdge',
+    value: function getEdge(vertexIdA, vertexIdB) {
+      var edgeId = this.vertexIdsToEdgeId[vertexIdA + '_' + vertexIdB];
+
+      return edgeId === undefined ? null : this.edges[edgeId];
+    }
+
+    /**
+     * Returns the ids of edges connected to a vertex.
+     *
+     * @param {Number} vertexId A vertex id.
+     * @returns {Number[]} An array containing the ids of edges connected to the vertex.
+     */
+
+  }, {
+    key: 'getEdges',
+    value: function getEdges(vertexId) {
+      var edgeIds = Array();
+      var vertex = this.vertices[vertexId];
+
+      for (var i = 0; i < vertex.neighbours.length; i++) {
+        edgeIds.push(this.vertexIdsToEdgeId[vertexId + '_' + vertex.neighbours[i]]);
+      }
+
+      return edgeIds;
+    }
+
+    /**
+     * Check whether or not two vertices are connected by an edge.
+     *
+     * @param {Number} vertexIdA A vertex id.
+     * @param {Number} vertexIdB A vertex id.
+     * @returns {Boolean} A boolean indicating whether or not two vertices are connected by an edge.
+     */
+
+  }, {
+    key: 'hasEdge',
+    value: function hasEdge(vertexIdA, vertexIdB) {
+      return this.vertexIdsToEdgeId[vertexIdA + '_' + vertexIdB] !== undefined;
+    }
+
+    /**
+     * Returns an array containing the vertex ids of this graph.
+     * 
+     * @returns {Number[]} An array containing all vertex ids of this graph.
+     */
+
+  }, {
+    key: 'getVertexList',
+    value: function getVertexList() {
+      var arr = [this.vertices.length];
+
+      for (var i = 0; i < this.vertices.length; i++) {
+        arr[i] = this.vertices[i].id;
+      }
+
+      return arr;
+    }
+
+    /**
+     * Returns an array containing source, target arrays of this graphs edges.
+     * 
+     * @returns {Array[]} An array containing source, target arrays of this graphs edges. Example: [ [ 2, 5 ], [ 6, 9 ] ].
+     */
+
+  }, {
+    key: 'getEdgeList',
+    value: function getEdgeList() {
+      var arr = Array(this.edges.length);
+
+      for (var i = 0; i < this.edges.length; i++) {
+        arr[i] = [this.edges[i].sourceId, this.edges[i].targetId];
+      }
+
+      return arr;
+    }
+
+    /**
+     * Get the adjacency matrix of the graph.
+     * 
+     * @returns {Array[]} The adjancency matrix of the molecular graph.
+     */
+
+  }, {
+    key: 'getAdjacencyMatrix',
+    value: function getAdjacencyMatrix() {
+      var length = this.vertices.length;
+      var adjacencyMatrix = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        adjacencyMatrix[i] = new Array(length);
+        adjacencyMatrix[i].fill(0);
+      }
+
+      for (var i = 0; i < this.edges.length; i++) {
+        var edge = this.edges[i];
+
+        adjacencyMatrix[edge.sourceId][edge.targetId] = 1;
+        adjacencyMatrix[edge.targetId][edge.sourceId] = 1;
+      }
+
+      return adjacencyMatrix;
+    }
+
+    /**
+     * Get the adjacency matrix of the graph with all bridges removed (thus the components). Thus the remaining vertices are all part of ring systems.
+     * 
+     * @returns {Array[]} The adjancency matrix of the molecular graph with all bridges removed.
+     */
+
+  }, {
+    key: 'getComponentsAdjacencyMatrix',
+    value: function getComponentsAdjacencyMatrix() {
+      var length = this.vertices.length;
+      var adjacencyMatrix = Array(length);
+      var bridges = this.getBridges();
+
+      for (var i = 0; i < length; i++) {
+        adjacencyMatrix[i] = new Array(length);
+        adjacencyMatrix[i].fill(0);
+      }
+
+      for (var i = 0; i < this.edges.length; i++) {
+        var edge = this.edges[i];
+
+        adjacencyMatrix[edge.sourceId][edge.targetId] = 1;
+        adjacencyMatrix[edge.targetId][edge.sourceId] = 1;
+      }
+
+      for (var i = 0; i < bridges.length; i++) {
+        adjacencyMatrix[bridges[i][0]][bridges[i][1]] = 0;
+        adjacencyMatrix[bridges[i][1]][bridges[i][0]] = 0;
+      }
+
+      return adjacencyMatrix;
+    }
+
+    /**
+     * Get the adjacency matrix of a subgraph.
+     * 
+     * @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
+     * @returns {Array[]} The adjancency matrix of the subgraph.
+     */
+
+  }, {
+    key: 'getSubgraphAdjacencyMatrix',
+    value: function getSubgraphAdjacencyMatrix(vertexIds) {
+      var length = vertexIds.length;
+      var adjacencyMatrix = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        adjacencyMatrix[i] = new Array(length);
+        adjacencyMatrix[i].fill(0);
+
+        for (var j = 0; j < length; j++) {
+          if (i === j) {
+            continue;
+          }
+
+          if (this.hasEdge(vertexIds[i], vertexIds[j])) {
+            adjacencyMatrix[i][j] = 1;
+          }
+        }
+      }
+
+      return adjacencyMatrix;
+    }
+
+    /**
+     * Get the distance matrix of the graph.
+     * 
+     * @returns {Array[]} The distance matrix of the graph.
+     */
+
+  }, {
+    key: 'getDistanceMatrix',
+    value: function getDistanceMatrix() {
+      var length = this.vertices.length;
+      var adja = this.getAdjacencyMatrix();
+      var dist = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        dist[i] = Array(length);
+        dist[i].fill(Infinity);
+      }
+
+      for (var i = 0; i < length; i++) {
+        for (var j = 0; j < length; j++) {
+          if (adja[i][j] === 1) {
+            dist[i][j] = 1;
+          }
+        }
+      }
+
+      for (var k = 0; k < length; k++) {
+        for (var i = 0; i < length; i++) {
+          for (var j = 0; j < length; j++) {
+            if (dist[i][j] > dist[i][k] + dist[k][j]) {
+              dist[i][j] = dist[i][k] + dist[k][j];
+            }
+          }
+        }
+      }
+
+      return dist;
+    }
+
+    /**
+     * Get the distance matrix of a subgraph.
+     * 
+     * @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
+     * @returns {Array[]} The distance matrix of the subgraph.
+     */
+
+  }, {
+    key: 'getSubgraphDistanceMatrix',
+    value: function getSubgraphDistanceMatrix(vertexIds) {
+      var length = vertexIds.length;
+      var adja = this.getSubgraphAdjacencyMatrix(vertexIds);
+      var dist = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        dist[i] = Array(length);
+        dist[i].fill(Infinity);
+      }
+
+      for (var i = 0; i < length; i++) {
+        for (var j = 0; j < length; j++) {
+          if (adja[i][j] === 1) {
+            dist[i][j] = 1;
+          }
+        }
+      }
+
+      for (var k = 0; k < length; k++) {
+        for (var i = 0; i < length; i++) {
+          for (var j = 0; j < length; j++) {
+            if (dist[i][j] > dist[i][k] + dist[k][j]) {
+              dist[i][j] = dist[i][k] + dist[k][j];
+            }
+          }
+        }
+      }
+
+      return dist;
+    }
+
+    /**
+     * Get the adjacency list of the graph.
+     * 
+     * @returns {Array[]} The adjancency list of the graph.
+     */
+
+  }, {
+    key: 'getAdjacencyList',
+    value: function getAdjacencyList() {
+      var length = this.vertices.length;
+      var adjacencyList = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        adjacencyList[i] = [];
+
+        for (var j = 0; j < length; j++) {
+          if (i === j) {
+            continue;
+          }
+
+          if (this.hasEdge(this.vertices[i].id, this.vertices[j].id)) {
+            adjacencyList[i].push(j);
+          }
+        }
+      }
+
+      return adjacencyList;
+    }
+
+    /**
+     * Get the adjacency list of a subgraph.
+     * 
+     * @param {Number[]} vertexIds An array containing the vertex ids contained within the subgraph.
+     * @returns {Array[]} The adjancency list of the subgraph.
+     */
+
+  }, {
+    key: 'getSubgraphAdjacencyList',
+    value: function getSubgraphAdjacencyList(vertexIds) {
+      var length = vertexIds.length;
+      var adjacencyList = Array(length);
+
+      for (var i = 0; i < length; i++) {
+        adjacencyList[i] = Array();
+
+        for (var j = 0; j < length; j++) {
+          if (i === j) {
+            continue;
+          }
+
+          if (this.hasEdge(vertexIds[i], vertexIds[j])) {
+            adjacencyList[i].push(j);
+          }
+        }
+      }
+
+      return adjacencyList;
+    }
+
+    /**
+     * Returns an array containing the edge ids of bridges. A bridge splits the graph into multiple components when removed.
+     * 
+     * @returns {Number[]} An array containing the edge ids of the bridges.
+     */
+
+  }, {
+    key: 'getBridges',
+    value: function getBridges() {
+      var length = this.vertices.length;
+      var visited = new Array(length);
+      var disc = new Array(length);
+      var low = new Array(length);
+      var parent = new Array(length);
+      var adj = this.getAdjacencyList();
+      var outBridges = Array();
+
+      visited.fill(false);
+      parent.fill(null);
+      this._time = 0;
+
+      for (var i = 0; i < length; i++) {
+        if (!visited[i]) {
+          this._bridgeDfs(i, visited, disc, low, parent, adj, outBridges);
+        }
+      }
+
+      return outBridges;
+    }
+
+    /**
+     * Traverses the graph in breadth-first order.
+     * 
+     * @param {Number} startVertexId The id of the starting vertex.
+     * @param {Function} callback The callback function to be called on every vertex.
+     */
+
+  }, {
+    key: 'traverseBF',
+    value: function traverseBF(startVertexId, callback) {
+      var length = this.vertices.length;
+      var visited = new Array(length);
+
+      visited.fill(false);
+
+      var queue = [startVertexId];
+
+      while (queue.length > 0) {
+        // JavaScripts shift() is O(n) ... bad JavaScript, bad!
+        var u = queue.shift();
+        var vertex = this.vertices[u];
+
+        callback(vertex);
+
+        for (var i = 0; i < vertex.neighbours.length; i++) {
+          var v = vertex.neighbours[i];
+          if (!visited[v]) {
+            visited[v] = true;
+            queue.push(v);
+          }
+        }
+      }
+    }
+
+    /**
+     * Get the depth of a subtree in the direction opposite to the vertex specified as the parent vertex.
+     *
+     * @param {Number} vertexId A vertex id.
+     * @param {Number} parentVertexId The id of a neighbouring vertex.
+     * @returns {Number} The depth of the sub-tree.
+     */
+
+  }, {
+    key: 'getTreeDepth',
+    value: function getTreeDepth(vertexId, parentVertexId) {
+      if (vertexId === null || parentVertexId === null) {
+        return 0;
+      }
+
+      var neighbours = this.vertices[vertexId].getSpanningTreeNeighbours(parentVertexId);
+      var max = 0;
+
+      for (var i = 0; i < neighbours.length; i++) {
+        var childId = neighbours[i];
+        var d = this.getTreeDepth(childId, vertexId);
+
+        if (d > max) {
+          max = d;
+        }
+      }
+
+      return max + 1;
+    }
+
+    /**
+     * Traverse a sub-tree in the graph.
+     *
+     * @param {Number} vertexId A vertex id.
+     * @param {Number} parentVertexId A neighbouring vertex.
+     * @param {Function} callback The callback function that is called with each visited as an argument.
+     * @param {Number} [maxDepth=Number.MAX_SAFE_INTEGER] The maximum depth of the recursion.
+     * @param {Boolean} [ignoreFirst=false] Whether or not to ignore the starting vertex supplied as vertexId in the callback.
+     * @param {Number} [depth=1] The current depth in the tree.
+     * @param {Uint8Array} [visited=null] An array holding a flag on whether or not a node has been visited.
+     */
+
+  }, {
+    key: 'traverseTree',
+    value: function traverseTree(vertexId, parentVertexId, callback) {
+      var maxDepth = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : Number.MAX_SAFE_INTEGER;
+      var ignoreFirst = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
+      var depth = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : 1;
+      var visited = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : null;
+
+      if (visited === null) {
+        visited = new Uint8Array(this.vertices.length);
+      }
+
+      if (depth > maxDepth + 1 || visited[vertexId] === 1) {
+        return;
+      }
+
+      visited[vertexId] = 1;
+
+      var vertex = this.vertices[vertexId];
+      var neighbours = vertex.getNeighbours(parentVertexId);
+
+      if (!ignoreFirst || depth > 1) {
+        callback(vertex);
+      }
+
+      for (var i = 0; i < neighbours.length; i++) {
+        this.traverseTree(neighbours[i], vertexId, callback, maxDepth, ignoreFirst, depth + 1, visited);
+      }
+    }
+
+    /**
+     * Positiones the (sub)graph using Kamada and Kawais algorithm for drawing general undirected graphs. https://pdfs.semanticscholar.org/b8d3/bca50ccc573c5cb99f7d201e8acce6618f04.pdf
+     * 
+     * @param {Number[]} vertexIds An array containing vertexIds to be placed using the force based layout.
+     * @param {Vector2} center The center of the layout.
+     * @param {Number} startVertexId A vertex id. Should be the starting vertex - e.g. the first to be positioned and connected to a previously place vertex.
+     * @param {Ring} ring The bridged ring associated with this force-based layout.
+     */
+
+  }, {
+    key: 'kkLayout',
+    value: function kkLayout(vertexIds, center, startVertexId, ring, bondLength) {
+      var edgeStrength = bondLength;
+
+      // Add vertices that are directly connected to the ring
+      var i = vertexIds.length;
+      while (i--) {
+        var vertex = this.vertices[vertexIds[i]];
+        var j = vertex.neighbours.length;
+      }
+
+      var matDist = this.getSubgraphDistanceMatrix(vertexIds);
+      var length = vertexIds.length;
+
+      // Initialize the positions. Place all vertices on a ring around the center
+      var radius = MathHelper.polyCircumradius(500, length);
+      var angle = MathHelper.centralAngle(length);
+      var a = 0.0;
+      var arrPositionX = new Float32Array(length);
+      var arrPositionY = new Float32Array(length);
+      var arrPositioned = Array(length);
+
+      i = length;
+      while (i--) {
+        var _vertex = this.vertices[vertexIds[i]];
+        if (!_vertex.positioned) {
+          arrPositionX[i] = center.x + Math.cos(a) * radius;
+          arrPositionY[i] = center.y + Math.sin(a) * radius;
+        } else {
+          arrPositionX[i] = _vertex.position.x;
+          arrPositionY[i] = _vertex.position.y;
+        }
+        arrPositioned[i] = _vertex.positioned;
+        a += angle;
+      }
+
+      // Create the matrix containing the lengths
+      var matLength = Array(length);
+      i = length;
+      while (i--) {
+        matLength[i] = new Array(length);
+        var j = length;
+        while (j--) {
+          matLength[i][j] = bondLength * matDist[i][j];
+        }
+      }
+
+      // Create the matrix containing the spring strenghts
+      var matStrength = Array(length);
+      i = length;
+      while (i--) {
+        matStrength[i] = Array(length);
+        var j = length;
+        while (j--) {
+          matStrength[i][j] = edgeStrength * Math.pow(matDist[i][j], -2.0);
+        }
+      }
+
+      // Create the matrix containing the energies
+      var matEnergy = Array(length);
+      var arrEnergySumX = new Float32Array(length);
+      var arrEnergySumY = new Float32Array(length);
+      i = length;
+      while (i--) {
+        matEnergy[i] = Array(length);
+      }
+
+      i = length;
+      var ux = void 0,
+          uy = void 0,
+          dEx = void 0,
+          dEy = void 0,
+          vx = void 0,
+          vy = void 0,
+          denom = void 0;
+
+      while (i--) {
+        ux = arrPositionX[i];
+        uy = arrPositionY[i];
+        dEx = 0.0;
+        dEy = 0.0;
+        var _j = length;
+        while (_j--) {
+          if (i === _j) {
+            continue;
+          }
+          vx = arrPositionX[_j];
+          vy = arrPositionY[_j];
+          denom = 1.0 / Math.sqrt((ux - vx) * (ux - vx) + (uy - vy) * (uy - vy));
+          matEnergy[i][_j] = [matStrength[i][_j] * (ux - vx - matLength[i][_j] * (ux - vx) * denom), matStrength[i][_j] * (uy - vy - matLength[i][_j] * (uy - vy) * denom)];
+          matEnergy[_j][i] = matEnergy[i][_j];
+          dEx += matEnergy[i][_j][0];
+          dEy += matEnergy[i][_j][1];
+        }
+        arrEnergySumX[i] = dEx;
+        arrEnergySumY[i] = dEy;
+      }
+
+      // Utility functions, maybe inline them later
+      var energy = function energy(index) {
+        return [arrEnergySumX[index] * arrEnergySumX[index] + arrEnergySumY[index] * arrEnergySumY[index], arrEnergySumX[index], arrEnergySumY[index]];
+      };
+
+      var highestEnergy = function highestEnergy() {
+        var maxEnergy = 0.0;
+        var maxEnergyId = 0;
+        var maxDEX = 0.0;
+        var maxDEY = 0.0;
+
+        i = length;
+        while (i--) {
+          var _energy = energy(i),
+              _energy2 = _slicedToArray(_energy, 3),
+              _delta = _energy2[0],
+              _dEX = _energy2[1],
+              _dEY = _energy2[2];
+
+          if (_delta > maxEnergy && arrPositioned[i] === false) {
+            maxEnergy = _delta;
+            maxEnergyId = i;
+            maxDEX = _dEX;
+            maxDEY = _dEY;
+          }
+        }
+
+        return [maxEnergyId, maxEnergy, maxDEX, maxDEY];
+      };
+
+      var update = function update(index, dEX, dEY) {
+        var dxx = 0.0;
+        var dyy = 0.0;
+        var dxy = 0.0;
+        var ux = arrPositionX[index];
+        var uy = arrPositionY[index];
+        var arrL = matLength[index];
+        var arrK = matStrength[index];
+
+        i = length;
+        while (i--) {
+          if (i === index) {
+            continue;
+          }
+
+          var _vx = arrPositionX[i];
+          var _vy = arrPositionY[i];
+          var l = arrL[i];
+          var k = arrK[i];
+          var m = (ux - _vx) * (ux - _vx);
+          var _denom = 1.0 / Math.pow(m + (uy - _vy) * (uy - _vy), 1.5);
+
+          dxx += k * (1 - l * (uy - _vy) * (uy - _vy) * _denom);
+          dyy += k * (1 - l * m * _denom);
+          dxy += k * (l * (ux - _vx) * (uy - _vy) * _denom);
+        }
+
+        // Prevent division by zero
+        if (dxx === 0) {
+          dxx = 0.1;
+        }
+
+        if (dyy === 0) {
+          dyy = 0.1;
+        }
+
+        if (dxy === 0) {
+          dxy = 0.1;
+        }
+
+        var dy = dEX / dxx + dEY / dxy;
+        dy /= dxy / dxx - dyy / dxy; // had to split this onto two lines because the syntax highlighter went crazy.
+        var dx = -(dxy * dy + dEX) / dxx;
+
+        arrPositionX[index] += dx;
+        arrPositionY[index] += dy;
+
+        // Update the energies
+        var arrE = matEnergy[index];
+        dEX = 0.0;
+        dEY = 0.0;
+
+        ux = arrPositionX[index];
+        uy = arrPositionY[index];
+
+        var vx = void 0,
+            vy = void 0,
+            prevEx = void 0,
+            prevEy = void 0,
+            denom = void 0;
+
+        i = length;
+        while (i--) {
+          if (index === i) {
+            continue;
+          }
+          vx = arrPositionX[i];
+          vy = arrPositionY[i];
+          // Store old energies
+          prevEx = arrE[i][0];
+          prevEy = arrE[i][1];
+          denom = 1.0 / Math.sqrt((ux - vx) * (ux - vx) + (uy - vy) * (uy - vy));
+          dx = arrK[i] * (ux - vx - arrL[i] * (ux - vx) * denom);
+          dy = arrK[i] * (uy - vy - arrL[i] * (uy - vy) * denom);
+
+          arrE[i] = [dx, dy];
+          dEX += dx;
+          dEY += dy;
+          arrEnergySumX[i] += dx - prevEx;
+          arrEnergySumY[i] += dy - prevEy;
+        }
+        arrEnergySumX[index] = dEX;
+        arrEnergySumY[index] = dEY;
+      };
+
+      // Setting parameters
+      var threshold = 0.1;
+      var innerThreshold = 0.1;
+      var maxIteration = 2000;
+      var maxInnerIteration = 50;
+      var maxEnergy = 1e9;
+
+      // Setting up variables for the while loops
+      var maxEnergyId = 0;
+      var dEX = 0.0;
+      var dEY = 0.0;
+      var delta = 0.0;
+      var iteration = 0;
+      var innerIteration = 0;
+
+      while (maxEnergy > threshold && maxIteration > iteration) {
+        iteration++;
+
+        var _highestEnergy = highestEnergy();
+
+        var _highestEnergy2 = _slicedToArray(_highestEnergy, 4);
+
+        maxEnergyId = _highestEnergy2[0];
+        maxEnergy = _highestEnergy2[1];
+        dEX = _highestEnergy2[2];
+        dEY = _highestEnergy2[3];
+
+        delta = maxEnergy;
+        innerIteration = 0;
+        while (delta > innerThreshold && maxInnerIteration > innerIteration) {
+          innerIteration++;
+          update(maxEnergyId, dEX, dEY);
+
+          var _energy3 = energy(maxEnergyId);
+
+          var _energy4 = _slicedToArray(_energy3, 3);
+
+          delta = _energy4[0];
+          dEX = _energy4[1];
+          dEY = _energy4[2];
+        }
+      }
+
+      i = length;
+      while (i--) {
+        var index = vertexIds[i];
+        var _vertex2 = this.vertices[index];
+        _vertex2.position.x = arrPositionX[i];
+        _vertex2.position.y = arrPositionY[i];
+        _vertex2.positioned = true;
+        _vertex2.forcePositioned = true;
+      }
+    }
+
+    /**
+     * PRIVATE FUNCTION used by getBridges().
+     */
+
+  }, {
+    key: '_bridgeDfs',
+    value: function _bridgeDfs(u, visited, disc, low, parent, adj, outBridges) {
+      visited[u] = true;
+      disc[u] = low[u] = ++this._time;
+
+      for (var i = 0; i < adj[u].length; i++) {
+        var v = adj[u][i];
+
+        if (!visited[v]) {
+          parent[v] = u;
+
+          this._bridgeDfs(v, visited, disc, low, parent, adj, outBridges);
+
+          low[u] = Math.min(low[u], low[v]);
+
+          // If low > disc, we have a bridge
+          if (low[v] > disc[u]) {
+            outBridges.push([u, v]);
+          }
+        } else if (v !== parent[u]) {
+          low[u] = Math.min(low[u], disc[v]);
+        }
+      }
+    }
+
+    /**
+     * Returns the connected components of the graph.
+     * 
+     * @param {Array[]} adjacencyMatrix An adjacency matrix.
+     * @returns {Set[]} Connected components as sets.
+     */
+
+  }], [{
+    key: 'getConnectedComponents',
+    value: function getConnectedComponents(adjacencyMatrix) {
+      var length = adjacencyMatrix.length;
+      var visited = new Array(length);
+      var components = new Array();
+      var count = 0;
+
+      visited.fill(false);
+
+      for (var u = 0; u < length; u++) {
+        if (!visited[u]) {
+          var component = Array();
+          visited[u] = true;
+          component.push(u);
+          count++;
+          Graph._ccGetDfs(u, visited, adjacencyMatrix, component);
+          if (component.length > 1) {
+            components.push(component);
+          }
+        }
+      }
+
+      return components;
+    }
+
+    /**
+     * Returns the number of connected components for the graph. 
+     * 
+     * @param {Array[]} adjacencyMatrix An adjacency matrix.
+     * @returns {Number} The number of connected components of the supplied graph.
+     */
+
+  }, {
+    key: 'getConnectedComponentCount',
+    value: function getConnectedComponentCount(adjacencyMatrix) {
+      var length = adjacencyMatrix.length;
+      var visited = new Array(length);
+      var count = 0;
+
+      visited.fill(false);
+
+      for (var u = 0; u < length; u++) {
+        if (!visited[u]) {
+          visited[u] = true;
+          count++;
+          Graph._ccCountDfs(u, visited, adjacencyMatrix);
+        }
+      }
+
+      return count;
+    }
+
+    /**
+     * PRIVATE FUNCTION used by getConnectedComponentCount().
+     */
+
+  }, {
+    key: '_ccCountDfs',
+    value: function _ccCountDfs(u, visited, adjacencyMatrix) {
+      for (var v = 0; v < adjacencyMatrix[u].length; v++) {
+        var c = adjacencyMatrix[u][v];
+
+        if (!c || visited[v] || u === v) {
+          continue;
+        }
+
+        visited[v] = true;
+        Graph._ccCountDfs(v, visited, adjacencyMatrix);
+      }
+    }
+
+    /**
+     * PRIVATE FUNCTION used by getConnectedComponents().
+     */
+
+  }, {
+    key: '_ccGetDfs',
+    value: function _ccGetDfs(u, visited, adjacencyMatrix, component) {
+      for (var v = 0; v < adjacencyMatrix[u].length; v++) {
+        var c = adjacencyMatrix[u][v];
+
+        if (!c || visited[v] || u === v) {
+          continue;
+        }
+
+        visited[v] = true;
+        component.push(v);
+        Graph._ccGetDfs(v, visited, adjacencyMatrix, component);
+      }
+    }
+  }]);
+
+  return Graph;
+}();
+
+module.exports = Graph;
+
+},{"./Atom":3,"./Edge":6,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],8:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var Vector2 = require('./Vector2');
+
+/** 
+ * A class representing a line.
+ * 
+ * @property {Vector2} from The Vector2 defining the start of the line.
+ * @property {Vector2} to The Vector2 defining the end of the line.
+ * @property {String} elementFrom The element symbol associated with the start of the line.
+ * @property {String} elementTo The element symbol associated with the end of the line.
+ * @property {Boolean} chiralFrom A boolean indicating whether or not the source atom is a chiral center.
+ * @property {Boolean} chiralTo A boolean indicating whether or tno the target atom is a chiral center.
+ */
+
+var Line = function () {
+    /**
+     * The constructor for the class Line.
+     *
+     * @param {Vector2} [from=new Vector2(0, 0)] A vector marking the beginning of the line.
+     * @param {Vector2} [to=new Vector2(0, 0)] A vector marking the end of the line.
+     * @param {string} [elementFrom=null] A one-letter representation of the element associated with the vector marking the beginning of the line.
+     * @param {string} [elementTo=null] A one-letter representation of the element associated with the vector marking the end of the line.
+     * @param {Boolean} [chiralFrom=false] Whether or not the from atom is a chiral center.
+     * @param {Boolean} [chiralTo=false] Whether or not the to atom is a chiral center.
+     */
+    function Line() {
+        var from = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : new Vector2(0, 0);
+        var to = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : new Vector2(0, 0);
+        var elementFrom = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
+        var elementTo = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : null;
+        var chiralFrom = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : false;
+        var chiralTo = arguments.length > 5 && arguments[5] !== undefined ? arguments[5] : false;
+
+        _classCallCheck(this, Line);
+
+        this.from = from;
+        this.to = to;
+        this.elementFrom = elementFrom;
+        this.elementTo = elementTo;
+        this.chiralFrom = chiralFrom;
+        this.chiralTo = chiralTo;
+    }
+
+    /**
+     * Clones this line and returns the clone.
+     *
+     * @returns {Line} A clone of this line.
+     */
+
+
+    _createClass(Line, [{
+        key: 'clone',
+        value: function clone() {
+            return new Line(this.from.clone(), this.to.clone(), this.elementFrom, this.elementTo);
+        }
+
+        /**
+         * Returns the length of this line.
+         *
+         * @returns {Number} The length of this line.
+         */
+
+    }, {
+        key: 'getLength',
+        value: function getLength() {
+            return Math.sqrt(Math.pow(this.to.x - this.from.x, 2) + Math.pow(this.to.y - this.from.y, 2));
+        }
+
+        /**
+         * Returns the angle of the line in relation to the coordinate system (the x-axis).
+         *
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'getAngle',
+        value: function getAngle() {
+            // Get the angle between the line and the x-axis
+            var diff = Vector2.subtract(this.getRightVector(), this.getLeftVector());
+            return diff.angle();
+        }
+
+        /**
+         * Returns the right vector (the vector with the larger x value).
+         *
+         * @returns {Vector2} The right vector.
+         */
+
+    }, {
+        key: 'getRightVector',
+        value: function getRightVector() {
+            // Return the vector with the larger x value (the right one)
+            if (this.from.x < this.to.x) {
+                return this.to;
+            } else {
+                return this.from;
+            }
+        }
+
+        /**
+         * Returns the left vector (the vector with the smaller x value).
+         *
+         * @returns {Vector2} The left vector.
+         */
+
+    }, {
+        key: 'getLeftVector',
+        value: function getLeftVector() {
+            // Return the vector with the smaller x value (the left one)
+            if (this.from.x < this.to.x) {
+                return this.from;
+            } else {
+                return this.to;
+            }
+        }
+
+        /**
+         * Returns the element associated with the right vector (the vector with the larger x value).
+         *
+         * @returns {String} The element associated with the right vector.
+         */
+
+    }, {
+        key: 'getRightElement',
+        value: function getRightElement() {
+            if (this.from.x < this.to.x) {
+                return this.elementTo;
+            } else {
+                return this.elementFrom;
+            }
+        }
+
+        /**
+         * Returns the element associated with the left vector (the vector with the smaller x value).
+         *
+         * @returns {String} The element associated with the left vector.
+         */
+
+    }, {
+        key: 'getLeftElement',
+        value: function getLeftElement() {
+            if (this.from.x < this.to.x) {
+                return this.elementFrom;
+            } else {
+                return this.elementTo;
+            }
+        }
+
+        /**
+         * Returns whether or not the atom associated with the right vector (the vector with the larger x value) is a chiral center.
+         *
+         * @returns {Boolean} Whether or not the atom associated with the right vector is a chiral center.
+         */
+
+    }, {
+        key: 'getRightChiral',
+        value: function getRightChiral() {
+            if (this.from.x < this.to.x) {
+                return this.chiralTo;
+            } else {
+                return this.chiralFrom;
+            }
+        }
+
+        /**
+         * Returns whether or not the atom associated with the left vector (the vector with the smaller x value) is a chiral center.
+         *
+         * @returns {Boolean} Whether or not the atom  associated with the left vector is a chiral center.
+         */
+
+    }, {
+        key: 'getLeftChiral',
+        value: function getLeftChiral() {
+            if (this.from.x < this.to.x) {
+                return this.chiralFrom;
+            } else {
+                return this.chiralTo;
+            }
+        }
+
+        /**
+         * Set the value of the right vector.
+         *
+         * @param {Number} x The x value.
+         * @param {Number} y The y value.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'setRightVector',
+        value: function setRightVector(x, y) {
+            if (this.from.x < this.to.x) {
+                this.to.x = x;
+                this.to.y = y;
+            } else {
+                this.from.x = x;
+                this.from.y = y;
+            }
+
+            return this;
+        }
+
+        /**
+         * Set the value of the left vector.
+         *
+         * @param {Number} x The x value.
+         * @param {Number} y The y value.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'setLeftVector',
+        value: function setLeftVector(x, y) {
+            if (this.from.x < this.to.x) {
+                this.from.x = x;
+                this.from.y = y;
+            } else {
+                this.to.x = x;
+                this.to.y = y;
+            }
+
+            return this;
+        }
+
+        /**
+         * Rotates this line to be aligned with the x-axis. The center of rotation is the left vector.
+         *
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'rotateToXAxis',
+        value: function rotateToXAxis() {
+            var left = this.getLeftVector();
+
+            this.setRightVector(left.x + this.getLength(), left.y);
+
+            return this;
+        }
+
+        /**
+         * Rotate the line by a given value (in radians). The center of rotation is the left vector.
+         *
+         * @param {Number} theta The angle (in radians) to rotate the line.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'rotate',
+        value: function rotate(theta) {
+            var l = this.getLeftVector();
+            var r = this.getRightVector();
+            var sinTheta = Math.sin(theta);
+            var cosTheta = Math.cos(theta);
+
+            var x = cosTheta * (r.x - l.x) - sinTheta * (r.y - l.y) + l.x;
+            var y = sinTheta * (r.x - l.x) - cosTheta * (r.y - l.y) + l.y;
+
+            this.setRightVector(x, y);
+
+            return this;
+        }
+
+        /**
+         * Shortens this line from the "from" direction by a given value (in pixels).
+         *
+         * @param {Number} by The length in pixels to shorten the vector by.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'shortenFrom',
+        value: function shortenFrom(by) {
+            var f = Vector2.subtract(this.to, this.from);
+
+            f.normalize();
+            f.multiplyScalar(by);
+
+            this.from.add(f);
+
+            return this;
+        }
+
+        /**
+         * Shortens this line from the "to" direction by a given value (in pixels).
+         *
+         * @param {Number} by The length in pixels to shorten the vector by.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'shortenTo',
+        value: function shortenTo(by) {
+            var f = Vector2.subtract(this.from, this.to);
+
+            f.normalize();
+            f.multiplyScalar(by);
+
+            this.to.add(f);
+
+            return this;
+        }
+
+        /**
+         * Shorten the right side.
+         *
+         * @param {Number} by The length in pixels to shorten the vector by.
+         * @returns {Line} Returns itself.
+         */
+
+    }, {
+        key: 'shortenRight',
+        value: function shortenRight(by) {
+            if (this.from.x < this.to.x) {
+                this.shortenTo(by);
+            } else {
+                this.shortenFrom(by);
+            }
+
+            return this;
+        }
+
+        /**
+         * Shorten the left side.
+         * 
+         * @param {Number} by The length in pixels to shorten the vector by.
+         * @returns {Line} Returns itself.
+         */
+
+    }, {
+        key: 'shortenLeft',
+        value: function shortenLeft(by) {
+            if (this.from.x < this.to.x) {
+                this.shortenFrom(by);
+            } else {
+                this.shortenTo(by);
+            }
+
+            return this;
+        }
+
+        /**
+         * Shortens this line from both directions by a given value (in pixels).
+         *
+         * @param {Number} by The length in pixels to shorten the vector by.
+         * @returns {Line} This line.
+         */
+
+    }, {
+        key: 'shorten',
+        value: function shorten(by) {
+            var f = Vector2.subtract(this.from, this.to);
+
+            f.normalize();
+            f.multiplyScalar(by / 2.0);
+
+            this.to.add(f);
+            this.from.subtract(f);
+
+            return this;
+        }
+    }]);
+
+    return Line;
+}();
+
+module.exports = Line;
+
+},{"./Vector2":14}],9:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+/** 
+ * A static class containing helper functions for math-related tasks. 
+ */
+var MathHelper = function () {
+    function MathHelper() {
+        _classCallCheck(this, MathHelper);
+    }
+
+    _createClass(MathHelper, null, [{
+        key: 'round',
+
+        /**
+         * Rounds a value to a given number of decimals.
+         *
+         * @static
+         * @param {Number} value A number.
+         * @param {Number} decimals The number of decimals.
+         * @returns {Number} A number rounded to a given number of decimals.
+         */
+        value: function round(value, decimals) {
+            decimals = decimals ? decimals : 1;
+            return Number(Math.round(value + 'e' + decimals) + 'e-' + decimals);
+        }
+
+        /**
+         * Returns the means of the angles contained in an array. In radians.
+         *
+         * @static
+         * @param {Number[]} arr An array containing angles (in radians).
+         * @returns {Number} The mean angle in radians.
+         */
+
+    }, {
+        key: 'meanAngle',
+        value: function meanAngle(arr) {
+            var sin = 0.0;
+            var cos = 0.0;
+
+            for (var i = 0; i < arr.length; i++) {
+                sin += Math.sin(arr[i]);
+                cos += Math.cos(arr[i]);
+            }
+
+            return Math.atan2(sin / arr.length, cos / arr.length);
+        }
+
+        /**
+         * Returns the inner angle of a n-sided regular polygon.
+         *
+         * @static
+         * @param {Number} n Number of sides of a regular polygon.
+         * @returns {Number} The inner angle of a given regular polygon.
+         */
+
+    }, {
+        key: 'innerAngle',
+        value: function innerAngle(n) {
+            return MathHelper.toRad((n - 2) * 180 / n);
+        }
+
+        /**
+         * Returns the circumradius of a n-sided regular polygon with a given side-length.
+         *
+         * @static
+         * @param {Number} s The side length of the regular polygon.
+         * @param {Number} n The number of sides.
+         * @returns {Number} The circumradius of the regular polygon.
+         */
+
+    }, {
+        key: 'polyCircumradius',
+        value: function polyCircumradius(s, n) {
+            return s / (2 * Math.sin(Math.PI / n));
+        }
+
+        /**
+         * Returns the apothem of a regular n-sided polygon based on its radius.
+         *
+         * @static
+         * @param {Number} r The radius.
+         * @param {Number} n The number of edges of the regular polygon.
+         * @returns {Number} The apothem of a n-sided polygon based on its radius.
+         */
+
+    }, {
+        key: 'apothem',
+        value: function apothem(r, n) {
+            return r * Math.cos(Math.PI / n);
+        }
+    }, {
+        key: 'apothemFromSideLength',
+        value: function apothemFromSideLength(s, n) {
+            var r = MathHelper.polyCircumradius(s, n);
+
+            return MathHelper.apothem(r, n);
+        }
+
+        /**
+         * The central angle of a n-sided regular polygon. In radians.
+         *
+         * @static
+         * @param {Number} n The number of sides of the regular polygon.
+         * @returns {Number} The central angle of the n-sided polygon in radians.
+         */
+
+    }, {
+        key: 'centralAngle',
+        value: function centralAngle(n) {
+            return MathHelper.toRad(360 / n);
+        }
+
+        /**
+         * Convertes radians to degrees.
+         *
+         * @static
+         * @param {Number} rad An angle in radians.
+         * @returns {Number} The angle in degrees.
+         */
+
+    }, {
+        key: 'toDeg',
+        value: function toDeg(rad) {
+            return rad * MathHelper.degFactor;
+        }
+
+        /**
+         * Converts degrees to radians.
+         *
+         * @static
+         * @param {Number} deg An angle in degrees.
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'toRad',
+        value: function toRad(deg) {
+            return deg * MathHelper.radFactor;
+        }
+
+        /**
+         * Returns the parity of the permutation (1 or -1)
+         * @param {(Array|Uint8Array)} arr An array containing the permutation.
+         * @returns {Number} The parity of the permutation (1 or -1), where 1 means even and -1 means odd.
+         */
+
+    }, {
+        key: 'parityOfPermutation',
+        value: function parityOfPermutation(arr) {
+            var visited = new Uint8Array(arr.length);
+            var evenLengthCycleCount = 0;
+
+            var traverseCycle = function traverseCycle(i) {
+                var cycleLength = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+
+                if (visited[i] === 1) {
+                    return cycleLength;
+                }
+
+                cycleLength++;
+
+                visited[i] = 1;
+                return traverseCycle(arr[i], cycleLength);
+            };
+
+            for (var i = 0; i < arr.length; i++) {
+                if (visited[i] === 1) {
+                    continue;
+                }
+
+                var cycleLength = traverseCycle(i);
+                evenLengthCycleCount += 1 - cycleLength % 2;
+            }
+
+            return evenLengthCycleCount % 2 ? -1 : 1;
+        }
+
+        /** The factor to convert degrees to radians. */
+
+    }, {
+        key: 'radFactor',
+        get: function get() {
+            return Math.PI / 180.0;
+        }
+
+        /** The factor to convert radians to degrees. */
+
+    }, {
+        key: 'degFactor',
+        get: function get() {
+            return 180.0 / Math.PI;
+        }
+
+        /** Two times PI. */
+
+    }, {
+        key: 'twoPI',
+        get: function get() {
+            return 2.0 * Math.PI;
+        }
+    }]);
+
+    return MathHelper;
+}();
+
+module.exports = MathHelper;
+
+},{}],10:[function(require,module,exports){
+"use strict";
+
+// WHEN REPLACING, CHECK FOR:
+// KEEP THIS WHEN REGENERATING THE PARSER !!
+
+module.exports = function () {
+  "use strict";
+
+  /*
+   * Generated by PEG.js 0.10.0.
+   *
+   * http://pegjs.org/
+   */
+
+  function peg$subclass(child, parent) {
+    function ctor() {
+      this.constructor = child;
+    }
+    ctor.prototype = parent.prototype;
+    child.prototype = new ctor();
+  }
+
+  function peg$SyntaxError(message, expected, found, location) {
+    this.message = message;
+    this.expected = expected;
+    this.found = found;
+    this.location = location;
+    this.name = "SyntaxError";
+
+    if (typeof Error.captureStackTrace === "function") {
+      Error.captureStackTrace(this, peg$SyntaxError);
+    }
+  }
+
+  peg$subclass(peg$SyntaxError, Error);
+
+  peg$SyntaxError.buildMessage = function (expected, found) {
+    var DESCRIBE_EXPECTATION_FNS = {
+      literal: function literal(expectation) {
+        return "\"" + literalEscape(expectation.text) + "\"";
+      },
+
+      "class": function _class(expectation) {
+        var escapedParts = "",
+            i;
+
+        for (i = 0; i < expectation.parts.length; i++) {
+          escapedParts += expectation.parts[i] instanceof Array ? classEscape(expectation.parts[i][0]) + "-" + classEscape(expectation.parts[i][1]) : classEscape(expectation.parts[i]);
+        }
+
+        return "[" + (expectation.inverted ? "^" : "") + escapedParts + "]";
+      },
+
+      any: function any(expectation) {
+        return "any character";
+      },
+
+      end: function end(expectation) {
+        return "end of input";
+      },
+
+      other: function other(expectation) {
+        return expectation.description;
+      }
+    };
+
+    function hex(ch) {
+      return ch.charCodeAt(0).toString(16).toUpperCase();
+    }
+
+    function literalEscape(s) {
+      return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\0/g, '\\0').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/[\x00-\x0F]/g, function (ch) {
+        return '\\x0' + hex(ch);
+      }).replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) {
+        return '\\x' + hex(ch);
+      });
+    }
+
+    function classEscape(s) {
+      return s.replace(/\\/g, '\\\\').replace(/\]/g, '\\]').replace(/\^/g, '\\^').replace(/-/g, '\\-').replace(/\0/g, '\\0').replace(/\t/g, '\\t').replace(/\n/g, '\\n').replace(/\r/g, '\\r').replace(/[\x00-\x0F]/g, function (ch) {
+        return '\\x0' + hex(ch);
+      }).replace(/[\x10-\x1F\x7F-\x9F]/g, function (ch) {
+        return '\\x' + hex(ch);
+      });
+    }
+
+    function describeExpectation(expectation) {
+      return DESCRIBE_EXPECTATION_FNS[expectation.type](expectation);
+    }
+
+    function describeExpected(expected) {
+      var descriptions = new Array(expected.length),
+          i,
+          j;
+
+      for (i = 0; i < expected.length; i++) {
+        descriptions[i] = describeExpectation(expected[i]);
+      }
+
+      descriptions.sort();
+
+      if (descriptions.length > 0) {
+        for (i = 1, j = 1; i < descriptions.length; i++) {
+          if (descriptions[i - 1] !== descriptions[i]) {
+            descriptions[j] = descriptions[i];
+            j++;
+          }
+        }
+        descriptions.length = j;
+      }
+
+      switch (descriptions.length) {
+        case 1:
+          return descriptions[0];
+
+        case 2:
+          return descriptions[0] + " or " + descriptions[1];
+
+        default:
+          return descriptions.slice(0, -1).join(", ") + ", or " + descriptions[descriptions.length - 1];
+      }
+    }
+
+    function describeFound(found) {
+      return found ? "\"" + literalEscape(found) + "\"" : "end of input";
+    }
+
+    return "Expected " + describeExpected(expected) + " but " + describeFound(found) + " found.";
+  };
+
+  function peg$parse(input, options) {
+    options = options !== void 0 ? options : {};
+
+    // KEEP THIS WHEN REGENERATING THE PARSER !!
+    var nOpenParentheses = input.split('(').length - 1;
+    var nCloseParentheses = input.split(')').length - 1;
+
+    if (nOpenParentheses !== nCloseParentheses) {
+      throw peg$buildSimpleError('The number of opening parentheses does not match the number of closing parentheses.', 0);
+    }
+    // KEEP THIS WHEN REGENERATING THE PARSER !!
+
+    var peg$FAILED = {},
+        peg$startRuleFunctions = {
+      chain: peg$parsechain
+    },
+        peg$startRuleFunction = peg$parsechain,
+        peg$c0 = function peg$c0(s) {
+      var branches = [];
+      var rings = [];
+
+      for (var i = 0; i < s[1].length; i++) {
+        branches.push(s[1][i]);
+      }
+
+      for (var i = 0; i < s[2].length; i++) {
+        var bond = s[2][i][0] ? s[2][i][0] : '-';
+        rings.push({
+          'bond': bond,
+          'id': s[2][i][1]
+        });
+      }
+
+      for (var i = 0; i < s[3].length; i++) {
+        branches.push(s[3][i]);
+      }
+
+      for (var i = 0; i < s[6].length; i++) {
+        branches.push(s[6][i]);
+      }
+
+      return {
+        'atom': s[0],
+        'isBracket': s[0].element ? true : false,
+        'branches': branches,
+        'branchCount': branches.length,
+        'ringbonds': rings,
+        'ringbondCount': rings.length,
+        'bond': s[4] ? s[4] : '-',
+        'next': s[5],
+        'hasNext': s[5] ? true : false
+      };
+
+      return s;
+    },
+        peg$c1 = "(",
+        peg$c2 = peg$literalExpectation("(", false),
+        peg$c3 = ")",
+        peg$c4 = peg$literalExpectation(")", false),
+        peg$c5 = function peg$c5(b) {
+      var bond = b[1] ? b[1] : '-';
+      b[2].branchBond = bond;
+      return b[2];
+    },
+        peg$c6 = function peg$c6(a) {
+      return a;
+    },
+        peg$c7 = /^[\-=#$:\/\\.]/,
+        peg$c8 = peg$classExpectation(["-", "=", "#", "$", ":", "/", "\\", "."], false, false),
+        peg$c9 = function peg$c9(b) {
+      return b;
+    },
+        peg$c10 = "[",
+        peg$c11 = peg$literalExpectation("[", false),
+        peg$c12 = "se",
+        peg$c13 = peg$literalExpectation("se", false),
+        peg$c14 = "as",
+        peg$c15 = peg$literalExpectation("as", false),
+        peg$c16 = "]",
+        peg$c17 = peg$literalExpectation("]", false),
+        peg$c18 = function peg$c18(b) {
+      return {
+        'isotope': b[1],
+        'element': b[2],
+        'chirality': b[3],
+        'hcount': b[4],
+        'charge': b[5],
+        'class': b[6]
+      };
+    },
+        peg$c19 = "B",
+        peg$c20 = peg$literalExpectation("B", false),
+        peg$c21 = "r",
+        peg$c22 = peg$literalExpectation("r", false),
+        peg$c23 = "C",
+        peg$c24 = peg$literalExpectation("C", false),
+        peg$c25 = "l",
+        peg$c26 = peg$literalExpectation("l", false),
+        peg$c27 = /^[NOPSFI]/,
+        peg$c28 = peg$classExpectation(["N", "O", "P", "S", "F", "I"], false, false),
+        peg$c29 = function peg$c29(o) {
+      if (o.length > 1) return o.join('');
+      return o;
+    },
+        peg$c30 = /^[bcnops]/,
+        peg$c31 = peg$classExpectation(["b", "c", "n", "o", "p", "s"], false, false),
+        peg$c32 = "*",
+        peg$c33 = peg$literalExpectation("*", false),
+        peg$c34 = function peg$c34(w) {
+      return w;
+    },
+        peg$c35 = /^[A-Z]/,
+        peg$c36 = peg$classExpectation([["A", "Z"]], false, false),
+        peg$c37 = /^[a-z]/,
+        peg$c38 = peg$classExpectation([["a", "z"]], false, false),
+        peg$c39 = function peg$c39(e) {
+      return e.join('');
+    },
+        peg$c40 = "%",
+        peg$c41 = peg$literalExpectation("%", false),
+        peg$c42 = /^[1-9]/,
+        peg$c43 = peg$classExpectation([["1", "9"]], false, false),
+        peg$c44 = /^[0-9]/,
+        peg$c45 = peg$classExpectation([["0", "9"]], false, false),
+        peg$c46 = function peg$c46(r) {
+      if (r.length == 1) return Number(r);
+      return Number(r.join('').replace('%', ''));
+    },
+        peg$c47 = "@",
+        peg$c48 = peg$literalExpectation("@", false),
+        peg$c49 = "TH",
+        peg$c50 = peg$literalExpectation("TH", false),
+        peg$c51 = /^[12]/,
+        peg$c52 = peg$classExpectation(["1", "2"], false, false),
+        peg$c53 = "AL",
+        peg$c54 = peg$literalExpectation("AL", false),
+        peg$c55 = "SP",
+        peg$c56 = peg$literalExpectation("SP", false),
+        peg$c57 = /^[1-3]/,
+        peg$c58 = peg$classExpectation([["1", "3"]], false, false),
+        peg$c59 = "TB",
+        peg$c60 = peg$literalExpectation("TB", false),
+        peg$c61 = "OH",
+        peg$c62 = peg$literalExpectation("OH", false),
+        peg$c63 = function peg$c63(c) {
+      if (!c[1]) return '@';
+      if (c[1] == '@') return '@@';
+
+      return c[1].join('').replace(',', '');
+    },
+        peg$c64 = function peg$c64(c) {
+      return c;
+    },
+        peg$c65 = "+",
+        peg$c66 = peg$literalExpectation("+", false),
+        peg$c67 = function peg$c67(c) {
+      if (!c[1]) return 1;
+      if (c[1] != '+') return Number(c[1].join(''));
+      return 2;
+    },
+        peg$c68 = "-",
+        peg$c69 = peg$literalExpectation("-", false),
+        peg$c70 = function peg$c70(c) {
+      if (!c[1]) return -1;
+      if (c[1] != '-') return -Number(c[1].join(''));
+      return -2;
+    },
+        peg$c71 = "H",
+        peg$c72 = peg$literalExpectation("H", false),
+        peg$c73 = function peg$c73(h) {
+      if (h[1]) return Number(h[1]);
+      return 1;
+    },
+        peg$c74 = ":",
+        peg$c75 = peg$literalExpectation(":", false),
+        peg$c76 = /^[0]/,
+        peg$c77 = peg$classExpectation(["0"], false, false),
+        peg$c78 = function peg$c78(c) {
+      return Number(c[1][0] + c[1][1].join(''));
+    },
+        peg$c79 = function peg$c79(i) {
+      return Number(i.join(''));
+    },
+        peg$currPos = 0,
+        peg$savedPos = 0,
+        peg$posDetailsCache = [{
+      line: 1,
+      column: 1
+    }],
+        peg$maxFailPos = 0,
+        peg$maxFailExpected = [],
+        peg$silentFails = 0,
+        peg$result;
+
+    if ("startRule" in options) {
+      if (!(options.startRule in peg$startRuleFunctions)) {
+        throw new Error("Can't start parsing from rule \"" + options.startRule + "\".");
+      }
+
+      peg$startRuleFunction = peg$startRuleFunctions[options.startRule];
+    }
+
+    function text() {
+      return input.substring(peg$savedPos, peg$currPos);
+    }
+
+    function location() {
+      return peg$computeLocation(peg$savedPos, peg$currPos);
+    }
+
+    function expected(description, location) {
+      location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos);
+
+      throw peg$buildStructuredError([peg$otherExpectation(description)], input.substring(peg$savedPos, peg$currPos), location);
+    }
+
+    function error(message, location) {
+      location = location !== void 0 ? location : peg$computeLocation(peg$savedPos, peg$currPos);
+
+      throw peg$buildSimpleError(message, location);
+    }
+
+    function peg$literalExpectation(text, ignoreCase) {
+      return {
+        type: "literal",
+        text: text,
+        ignoreCase: ignoreCase
+      };
+    }
+
+    function peg$classExpectation(parts, inverted, ignoreCase) {
+      return {
+        type: "class",
+        parts: parts,
+        inverted: inverted,
+        ignoreCase: ignoreCase
+      };
+    }
+
+    function peg$anyExpectation() {
+      return {
+        type: "any"
+      };
+    }
+
+    function peg$endExpectation() {
+      return {
+        type: "end"
+      };
+    }
+
+    function peg$otherExpectation(description) {
+      return {
+        type: "other",
+        description: description
+      };
+    }
+
+    function peg$computePosDetails(pos) {
+      var details = peg$posDetailsCache[pos],
+          p;
+
+      if (details) {
+        return details;
+      } else {
+        p = pos - 1;
+        while (!peg$posDetailsCache[p]) {
+          p--;
+        }
+
+        details = peg$posDetailsCache[p];
+        details = {
+          line: details.line,
+          column: details.column
+        };
+
+        while (p < pos) {
+          if (input.charCodeAt(p) === 10) {
+            details.line++;
+            details.column = 1;
+          } else {
+            details.column++;
+          }
+
+          p++;
+        }
+
+        peg$posDetailsCache[pos] = details;
+        return details;
+      }
+    }
+
+    function peg$computeLocation(startPos, endPos) {
+      var startPosDetails = peg$computePosDetails(startPos),
+          endPosDetails = peg$computePosDetails(endPos);
+
+      return {
+        start: {
+          offset: startPos,
+          line: startPosDetails.line,
+          column: startPosDetails.column
+        },
+        end: {
+          offset: endPos,
+          line: endPosDetails.line,
+          column: endPosDetails.column
+        }
+      };
+    }
+
+    function peg$fail(expected) {
+      if (peg$currPos < peg$maxFailPos) {
+        return;
+      }
+
+      if (peg$currPos > peg$maxFailPos) {
+        peg$maxFailPos = peg$currPos;
+        peg$maxFailExpected = [];
+      }
+
+      peg$maxFailExpected.push(expected);
+    }
+
+    function peg$buildSimpleError(message, location) {
+      return new peg$SyntaxError(message, null, null, location);
+    }
+
+    function peg$buildStructuredError(expected, found, location) {
+      return new peg$SyntaxError(peg$SyntaxError.buildMessage(expected, found), expected, found, location);
+    }
+
+    function peg$parsechain() {
+      var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      s2 = peg$parseatom();
+      if (s2 !== peg$FAILED) {
+        s3 = [];
+        s4 = peg$parsebranch();
+        while (s4 !== peg$FAILED) {
+          s3.push(s4);
+          s4 = peg$parsebranch();
+        }
+        if (s3 !== peg$FAILED) {
+          s4 = [];
+          s5 = peg$currPos;
+          s6 = peg$parsebond();
+          if (s6 === peg$FAILED) {
+            s6 = null;
+          }
+          if (s6 !== peg$FAILED) {
+            s7 = peg$parsering();
+            if (s7 !== peg$FAILED) {
+              s6 = [s6, s7];
+              s5 = s6;
+            } else {
+              peg$currPos = s5;
+              s5 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s5;
+            s5 = peg$FAILED;
+          }
+          while (s5 !== peg$FAILED) {
+            s4.push(s5);
+            s5 = peg$currPos;
+            s6 = peg$parsebond();
+            if (s6 === peg$FAILED) {
+              s6 = null;
+            }
+            if (s6 !== peg$FAILED) {
+              s7 = peg$parsering();
+              if (s7 !== peg$FAILED) {
+                s6 = [s6, s7];
+                s5 = s6;
+              } else {
+                peg$currPos = s5;
+                s5 = peg$FAILED;
+              }
+            } else {
+              peg$currPos = s5;
+              s5 = peg$FAILED;
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            s5 = [];
+            s6 = peg$parsebranch();
+            while (s6 !== peg$FAILED) {
+              s5.push(s6);
+              s6 = peg$parsebranch();
+            }
+            if (s5 !== peg$FAILED) {
+              s6 = peg$parsebond();
+              if (s6 === peg$FAILED) {
+                s6 = null;
+              }
+              if (s6 !== peg$FAILED) {
+                s7 = peg$parsechain();
+                if (s7 === peg$FAILED) {
+                  s7 = null;
+                }
+                if (s7 !== peg$FAILED) {
+                  s8 = [];
+                  s9 = peg$parsebranch();
+                  while (s9 !== peg$FAILED) {
+                    s8.push(s9);
+                    s9 = peg$parsebranch();
+                  }
+                  if (s8 !== peg$FAILED) {
+                    s2 = [s2, s3, s4, s5, s6, s7, s8];
+                    s1 = s2;
+                  } else {
+                    peg$currPos = s1;
+                    s1 = peg$FAILED;
+                  }
+                } else {
+                  peg$currPos = s1;
+                  s1 = peg$FAILED;
+                }
+              } else {
+                peg$currPos = s1;
+                s1 = peg$FAILED;
+              }
+            } else {
+              peg$currPos = s1;
+              s1 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c0(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsebranch() {
+      var s0, s1, s2, s3, s4, s5;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 40) {
+        s2 = peg$c1;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c2);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        s3 = peg$parsebond();
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s4 = peg$parsechain();
+          if (s4 !== peg$FAILED) {
+            if (input.charCodeAt(peg$currPos) === 41) {
+              s5 = peg$c3;
+              peg$currPos++;
+            } else {
+              s5 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c4);
+              }
+            }
+            if (s5 !== peg$FAILED) {
+              s2 = [s2, s3, s4, s5];
+              s1 = s2;
+            } else {
+              peg$currPos = s1;
+              s1 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c5(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseatom() {
+      var s0, s1;
+
+      s0 = peg$currPos;
+      s1 = peg$parseorganicsymbol();
+      if (s1 === peg$FAILED) {
+        s1 = peg$parsearomaticsymbol();
+        if (s1 === peg$FAILED) {
+          s1 = peg$parsebracketatom();
+          if (s1 === peg$FAILED) {
+            s1 = peg$parsewildcard();
+          }
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c6(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsebond() {
+      var s0, s1;
+
+      s0 = peg$currPos;
+      if (peg$c7.test(input.charAt(peg$currPos))) {
+        s1 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c8);
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c9(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsebracketatom() {
+      var s0, s1, s2, s3, s4, s5, s6, s7, s8, s9;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 91) {
+        s2 = peg$c10;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c11);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        s3 = peg$parseisotope();
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          if (input.substr(peg$currPos, 2) === peg$c12) {
+            s4 = peg$c12;
+            peg$currPos += 2;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c13);
+            }
+          }
+          if (s4 === peg$FAILED) {
+            if (input.substr(peg$currPos, 2) === peg$c14) {
+              s4 = peg$c14;
+              peg$currPos += 2;
+            } else {
+              s4 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c15);
+              }
+            }
+            if (s4 === peg$FAILED) {
+              s4 = peg$parsearomaticsymbol();
+              if (s4 === peg$FAILED) {
+                s4 = peg$parseelementsymbol();
+                if (s4 === peg$FAILED) {
+                  s4 = peg$parsewildcard();
+                }
+              }
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            s5 = peg$parsechiral();
+            if (s5 === peg$FAILED) {
+              s5 = null;
+            }
+            if (s5 !== peg$FAILED) {
+              s6 = peg$parsehcount();
+              if (s6 === peg$FAILED) {
+                s6 = null;
+              }
+              if (s6 !== peg$FAILED) {
+                s7 = peg$parsecharge();
+                if (s7 === peg$FAILED) {
+                  s7 = null;
+                }
+                if (s7 !== peg$FAILED) {
+                  s8 = peg$parseclass();
+                  if (s8 === peg$FAILED) {
+                    s8 = null;
+                  }
+                  if (s8 !== peg$FAILED) {
+                    if (input.charCodeAt(peg$currPos) === 93) {
+                      s9 = peg$c16;
+                      peg$currPos++;
+                    } else {
+                      s9 = peg$FAILED;
+                      if (peg$silentFails === 0) {
+                        peg$fail(peg$c17);
+                      }
+                    }
+                    if (s9 !== peg$FAILED) {
+                      s2 = [s2, s3, s4, s5, s6, s7, s8, s9];
+                      s1 = s2;
+                    } else {
+                      peg$currPos = s1;
+                      s1 = peg$FAILED;
+                    }
+                  } else {
+                    peg$currPos = s1;
+                    s1 = peg$FAILED;
+                  }
+                } else {
+                  peg$currPos = s1;
+                  s1 = peg$FAILED;
+                }
+              } else {
+                peg$currPos = s1;
+                s1 = peg$FAILED;
+              }
+            } else {
+              peg$currPos = s1;
+              s1 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c18(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseorganicsymbol() {
+      var s0, s1, s2, s3;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 66) {
+        s2 = peg$c19;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c20);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (input.charCodeAt(peg$currPos) === 114) {
+          s3 = peg$c21;
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c22);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 === peg$FAILED) {
+        s1 = peg$currPos;
+        if (input.charCodeAt(peg$currPos) === 67) {
+          s2 = peg$c23;
+          peg$currPos++;
+        } else {
+          s2 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c24);
+          }
+        }
+        if (s2 !== peg$FAILED) {
+          if (input.charCodeAt(peg$currPos) === 108) {
+            s3 = peg$c25;
+            peg$currPos++;
+          } else {
+            s3 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c26);
+            }
+          }
+          if (s3 === peg$FAILED) {
+            s3 = null;
+          }
+          if (s3 !== peg$FAILED) {
+            s2 = [s2, s3];
+            s1 = s2;
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+        if (s1 === peg$FAILED) {
+          if (peg$c27.test(input.charAt(peg$currPos))) {
+            s1 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s1 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c28);
+            }
+          }
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c29(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsearomaticsymbol() {
+      var s0, s1;
+
+      s0 = peg$currPos;
+      if (peg$c30.test(input.charAt(peg$currPos))) {
+        s1 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c31);
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c6(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsewildcard() {
+      var s0, s1;
+
+      s0 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 42) {
+        s1 = peg$c32;
+        peg$currPos++;
+      } else {
+        s1 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c33);
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c34(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseelementsymbol() {
+      var s0, s1, s2, s3;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (peg$c35.test(input.charAt(peg$currPos))) {
+        s2 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c36);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (peg$c37.test(input.charAt(peg$currPos))) {
+          s3 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c38);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c39(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsering() {
+      var s0, s1, s2, s3, s4;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 37) {
+        s2 = peg$c40;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c41);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (peg$c42.test(input.charAt(peg$currPos))) {
+          s3 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c43);
+          }
+        }
+        if (s3 !== peg$FAILED) {
+          if (peg$c44.test(input.charAt(peg$currPos))) {
+            s4 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c45);
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            s2 = [s2, s3, s4];
+            s1 = s2;
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 === peg$FAILED) {
+        if (peg$c44.test(input.charAt(peg$currPos))) {
+          s1 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s1 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c45);
+          }
+        }
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c46(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsechiral() {
+      var s0, s1, s2, s3, s4, s5, s6;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 64) {
+        s2 = peg$c47;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c48);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (input.charCodeAt(peg$currPos) === 64) {
+          s3 = peg$c47;
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c48);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = peg$currPos;
+          if (input.substr(peg$currPos, 2) === peg$c49) {
+            s4 = peg$c49;
+            peg$currPos += 2;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c50);
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            if (peg$c51.test(input.charAt(peg$currPos))) {
+              s5 = input.charAt(peg$currPos);
+              peg$currPos++;
+            } else {
+              s5 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c52);
+              }
+            }
+            if (s5 !== peg$FAILED) {
+              s4 = [s4, s5];
+              s3 = s4;
+            } else {
+              peg$currPos = s3;
+              s3 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s3;
+            s3 = peg$FAILED;
+          }
+          if (s3 === peg$FAILED) {
+            s3 = peg$currPos;
+            if (input.substr(peg$currPos, 2) === peg$c53) {
+              s4 = peg$c53;
+              peg$currPos += 2;
+            } else {
+              s4 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c54);
+              }
+            }
+            if (s4 !== peg$FAILED) {
+              if (peg$c51.test(input.charAt(peg$currPos))) {
+                s5 = input.charAt(peg$currPos);
+                peg$currPos++;
+              } else {
+                s5 = peg$FAILED;
+                if (peg$silentFails === 0) {
+                  peg$fail(peg$c52);
+                }
+              }
+              if (s5 !== peg$FAILED) {
+                s4 = [s4, s5];
+                s3 = s4;
+              } else {
+                peg$currPos = s3;
+                s3 = peg$FAILED;
+              }
+            } else {
+              peg$currPos = s3;
+              s3 = peg$FAILED;
+            }
+            if (s3 === peg$FAILED) {
+              s3 = peg$currPos;
+              if (input.substr(peg$currPos, 2) === peg$c55) {
+                s4 = peg$c55;
+                peg$currPos += 2;
+              } else {
+                s4 = peg$FAILED;
+                if (peg$silentFails === 0) {
+                  peg$fail(peg$c56);
+                }
+              }
+              if (s4 !== peg$FAILED) {
+                if (peg$c57.test(input.charAt(peg$currPos))) {
+                  s5 = input.charAt(peg$currPos);
+                  peg$currPos++;
+                } else {
+                  s5 = peg$FAILED;
+                  if (peg$silentFails === 0) {
+                    peg$fail(peg$c58);
+                  }
+                }
+                if (s5 !== peg$FAILED) {
+                  s4 = [s4, s5];
+                  s3 = s4;
+                } else {
+                  peg$currPos = s3;
+                  s3 = peg$FAILED;
+                }
+              } else {
+                peg$currPos = s3;
+                s3 = peg$FAILED;
+              }
+              if (s3 === peg$FAILED) {
+                s3 = peg$currPos;
+                if (input.substr(peg$currPos, 2) === peg$c59) {
+                  s4 = peg$c59;
+                  peg$currPos += 2;
+                } else {
+                  s4 = peg$FAILED;
+                  if (peg$silentFails === 0) {
+                    peg$fail(peg$c60);
+                  }
+                }
+                if (s4 !== peg$FAILED) {
+                  if (peg$c42.test(input.charAt(peg$currPos))) {
+                    s5 = input.charAt(peg$currPos);
+                    peg$currPos++;
+                  } else {
+                    s5 = peg$FAILED;
+                    if (peg$silentFails === 0) {
+                      peg$fail(peg$c43);
+                    }
+                  }
+                  if (s5 !== peg$FAILED) {
+                    if (peg$c44.test(input.charAt(peg$currPos))) {
+                      s6 = input.charAt(peg$currPos);
+                      peg$currPos++;
+                    } else {
+                      s6 = peg$FAILED;
+                      if (peg$silentFails === 0) {
+                        peg$fail(peg$c45);
+                      }
+                    }
+                    if (s6 === peg$FAILED) {
+                      s6 = null;
+                    }
+                    if (s6 !== peg$FAILED) {
+                      s4 = [s4, s5, s6];
+                      s3 = s4;
+                    } else {
+                      peg$currPos = s3;
+                      s3 = peg$FAILED;
+                    }
+                  } else {
+                    peg$currPos = s3;
+                    s3 = peg$FAILED;
+                  }
+                } else {
+                  peg$currPos = s3;
+                  s3 = peg$FAILED;
+                }
+                if (s3 === peg$FAILED) {
+                  s3 = peg$currPos;
+                  if (input.substr(peg$currPos, 2) === peg$c61) {
+                    s4 = peg$c61;
+                    peg$currPos += 2;
+                  } else {
+                    s4 = peg$FAILED;
+                    if (peg$silentFails === 0) {
+                      peg$fail(peg$c62);
+                    }
+                  }
+                  if (s4 !== peg$FAILED) {
+                    if (peg$c42.test(input.charAt(peg$currPos))) {
+                      s5 = input.charAt(peg$currPos);
+                      peg$currPos++;
+                    } else {
+                      s5 = peg$FAILED;
+                      if (peg$silentFails === 0) {
+                        peg$fail(peg$c43);
+                      }
+                    }
+                    if (s5 !== peg$FAILED) {
+                      if (peg$c44.test(input.charAt(peg$currPos))) {
+                        s6 = input.charAt(peg$currPos);
+                        peg$currPos++;
+                      } else {
+                        s6 = peg$FAILED;
+                        if (peg$silentFails === 0) {
+                          peg$fail(peg$c45);
+                        }
+                      }
+                      if (s6 === peg$FAILED) {
+                        s6 = null;
+                      }
+                      if (s6 !== peg$FAILED) {
+                        s4 = [s4, s5, s6];
+                        s3 = s4;
+                      } else {
+                        peg$currPos = s3;
+                        s3 = peg$FAILED;
+                      }
+                    } else {
+                      peg$currPos = s3;
+                      s3 = peg$FAILED;
+                    }
+                  } else {
+                    peg$currPos = s3;
+                    s3 = peg$FAILED;
+                  }
+                }
+              }
+            }
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c63(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsecharge() {
+      var s0, s1;
+
+      s0 = peg$currPos;
+      s1 = peg$parseposcharge();
+      if (s1 === peg$FAILED) {
+        s1 = peg$parsenegcharge();
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c64(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseposcharge() {
+      var s0, s1, s2, s3, s4, s5;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 43) {
+        s2 = peg$c65;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c66);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (input.charCodeAt(peg$currPos) === 43) {
+          s3 = peg$c65;
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c66);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = peg$currPos;
+          if (peg$c42.test(input.charAt(peg$currPos))) {
+            s4 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c43);
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            if (peg$c44.test(input.charAt(peg$currPos))) {
+              s5 = input.charAt(peg$currPos);
+              peg$currPos++;
+            } else {
+              s5 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c45);
+              }
+            }
+            if (s5 === peg$FAILED) {
+              s5 = null;
+            }
+            if (s5 !== peg$FAILED) {
+              s4 = [s4, s5];
+              s3 = s4;
+            } else {
+              peg$currPos = s3;
+              s3 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s3;
+            s3 = peg$FAILED;
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c67(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsenegcharge() {
+      var s0, s1, s2, s3, s4, s5;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 45) {
+        s2 = peg$c68;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c69);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (input.charCodeAt(peg$currPos) === 45) {
+          s3 = peg$c68;
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c69);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = peg$currPos;
+          if (peg$c42.test(input.charAt(peg$currPos))) {
+            s4 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c43);
+            }
+          }
+          if (s4 !== peg$FAILED) {
+            if (peg$c44.test(input.charAt(peg$currPos))) {
+              s5 = input.charAt(peg$currPos);
+              peg$currPos++;
+            } else {
+              s5 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c45);
+              }
+            }
+            if (s5 === peg$FAILED) {
+              s5 = null;
+            }
+            if (s5 !== peg$FAILED) {
+              s4 = [s4, s5];
+              s3 = s4;
+            } else {
+              peg$currPos = s3;
+              s3 = peg$FAILED;
+            }
+          } else {
+            peg$currPos = s3;
+            s3 = peg$FAILED;
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c70(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parsehcount() {
+      var s0, s1, s2, s3;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 72) {
+        s2 = peg$c71;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c72);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (peg$c44.test(input.charAt(peg$currPos))) {
+          s3 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c45);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c73(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseclass() {
+      var s0, s1, s2, s3, s4, s5, s6;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (input.charCodeAt(peg$currPos) === 58) {
+        s2 = peg$c74;
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c75);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        s3 = peg$currPos;
+        if (peg$c42.test(input.charAt(peg$currPos))) {
+          s4 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s4 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c43);
+          }
+        }
+        if (s4 !== peg$FAILED) {
+          s5 = [];
+          if (peg$c44.test(input.charAt(peg$currPos))) {
+            s6 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s6 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c45);
+            }
+          }
+          while (s6 !== peg$FAILED) {
+            s5.push(s6);
+            if (peg$c44.test(input.charAt(peg$currPos))) {
+              s6 = input.charAt(peg$currPos);
+              peg$currPos++;
+            } else {
+              s6 = peg$FAILED;
+              if (peg$silentFails === 0) {
+                peg$fail(peg$c45);
+              }
+            }
+          }
+          if (s5 !== peg$FAILED) {
+            s4 = [s4, s5];
+            s3 = s4;
+          } else {
+            peg$currPos = s3;
+            s3 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s3;
+          s3 = peg$FAILED;
+        }
+        if (s3 === peg$FAILED) {
+          if (peg$c76.test(input.charAt(peg$currPos))) {
+            s3 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s3 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c77);
+            }
+          }
+        }
+        if (s3 !== peg$FAILED) {
+          s2 = [s2, s3];
+          s1 = s2;
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c78(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    function peg$parseisotope() {
+      var s0, s1, s2, s3, s4;
+
+      s0 = peg$currPos;
+      s1 = peg$currPos;
+      if (peg$c42.test(input.charAt(peg$currPos))) {
+        s2 = input.charAt(peg$currPos);
+        peg$currPos++;
+      } else {
+        s2 = peg$FAILED;
+        if (peg$silentFails === 0) {
+          peg$fail(peg$c43);
+        }
+      }
+      if (s2 !== peg$FAILED) {
+        if (peg$c44.test(input.charAt(peg$currPos))) {
+          s3 = input.charAt(peg$currPos);
+          peg$currPos++;
+        } else {
+          s3 = peg$FAILED;
+          if (peg$silentFails === 0) {
+            peg$fail(peg$c45);
+          }
+        }
+        if (s3 === peg$FAILED) {
+          s3 = null;
+        }
+        if (s3 !== peg$FAILED) {
+          if (peg$c44.test(input.charAt(peg$currPos))) {
+            s4 = input.charAt(peg$currPos);
+            peg$currPos++;
+          } else {
+            s4 = peg$FAILED;
+            if (peg$silentFails === 0) {
+              peg$fail(peg$c45);
+            }
+          }
+          if (s4 === peg$FAILED) {
+            s4 = null;
+          }
+          if (s4 !== peg$FAILED) {
+            s2 = [s2, s3, s4];
+            s1 = s2;
+          } else {
+            peg$currPos = s1;
+            s1 = peg$FAILED;
+          }
+        } else {
+          peg$currPos = s1;
+          s1 = peg$FAILED;
+        }
+      } else {
+        peg$currPos = s1;
+        s1 = peg$FAILED;
+      }
+      if (s1 !== peg$FAILED) {
+        peg$savedPos = s0;
+        s1 = peg$c79(s1);
+      }
+      s0 = s1;
+
+      return s0;
+    }
+
+    peg$result = peg$startRuleFunction();
+
+    if (peg$result !== peg$FAILED && peg$currPos === input.length) {
+      return peg$result;
+    } else {
+      if (peg$result !== peg$FAILED && peg$currPos < input.length) {
+        peg$fail(peg$endExpectation());
+      }
+
+      throw peg$buildStructuredError(peg$maxFailExpected, peg$maxFailPos < input.length ? input.charAt(peg$maxFailPos) : null, peg$maxFailPos < input.length ? peg$computeLocation(peg$maxFailPos, peg$maxFailPos + 1) : peg$computeLocation(peg$maxFailPos, peg$maxFailPos));
+    }
+  }
+
+  return {
+    SyntaxError: peg$SyntaxError,
+    parse: peg$parse
+  };
+}();
+
+},{}],11:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var ArrayHelper = require('./ArrayHelper');
+var Vector2 = require('./Vector2');
+var Vertex = require('./Vertex');
+var RingConnection = require('./RingConnection');
+
+/** 
+ * A class representing a ring.
+ * 
+ * @property {Number} id The id of this ring.
+ * @property {Number[]} members An array containing the vertex ids of the ring members.
+ * @property {Number[]} edges An array containing the edge ids of the edges between the ring members.
+ * @property {Number[]} insiders An array containing the vertex ids of the vertices contained within the ring if it is a bridged ring.
+ * @property {Number[]} neighbours An array containing the ids of neighbouring rings.
+ * @property {Boolean} positioned A boolean indicating whether or not this ring has been positioned.
+ * @property {Vector2} center The center of this ring.
+ * @property {Ring[]} rings The rings contained within this ring if this ring is bridged.
+ * @property {Boolean} isBridged A boolean whether or not this ring is bridged.
+ * @property {Boolean} isPartOfBridged A boolean whether or not this ring is part of a bridge ring.
+ * @property {Boolean} isSpiro A boolean whether or not this ring is part of a spiro.
+ * @property {Boolean} isFused A boolean whether or not this ring is part of a fused ring.
+ * @property {Number} centralAngle The central angle of this ring.
+ * @property {Boolean} canFlip A boolean indicating whether or not this ring allows flipping of attached vertices to the inside of the ring.
+ */
+
+var Ring = function () {
+    /**
+     * The constructor for the class Ring.
+     *
+     * @param {Number[]} members An array containing the vertex ids of the members of the ring to be created.
+     */
+    function Ring(members) {
+        _classCallCheck(this, Ring);
+
+        this.id = null;
+        this.members = members;
+        this.edges = [];
+        this.insiders = [];
+        this.neighbours = [];
+        this.positioned = false;
+        this.center = new Vector2(0, 0);
+        this.rings = [];
+        this.isBridged = false;
+        this.isPartOfBridged = false;
+        this.isSpiro = false;
+        this.isFused = false;
+        this.centralAngle = 0.0;
+        this.canFlip = true;
+    }
+
+    /**
+     * Clones this ring and returns the clone.
+     *
+     * @returns {Ring} A clone of this ring.
+     */
+
+
+    _createClass(Ring, [{
+        key: 'clone',
+        value: function clone() {
+            var clone = new Ring(this.members);
+
+            clone.id = this.id;
+            clone.insiders = ArrayHelper.clone(this.insiders);
+            clone.neighbours = ArrayHelper.clone(this.neighbours);
+            clone.positioned = this.positioned;
+            clone.center = this.center.clone();
+            clone.rings = ArrayHelper.clone(this.rings);
+            clone.isBridged = this.isBridged;
+            clone.isPartOfBridged = this.isPartOfBridged;
+            clone.isSpiro = this.isSpiro;
+            clone.isFused = this.isFused;
+            clone.centralAngle = this.centralAngle;
+            clone.canFlip = this.canFlip;
+
+            return clone;
+        }
+
+        /**
+         * Returns the size (number of members) of this ring.
+         *
+         * @returns {Number} The size (number of members) of this ring.
+         */
+
+    }, {
+        key: 'getSize',
+        value: function getSize() {
+            return this.members.length;
+        }
+
+        /**
+         * Gets the polygon representation (an array of the ring-members positional vectors) of this ring.
+         *
+         * @param {Vertex[]} vertices An array of vertices representing the current molecule.
+         * @returns {Vector2[]} An array of the positional vectors of the ring members.
+         */
+
+    }, {
+        key: 'getPolygon',
+        value: function getPolygon(vertices) {
+            var polygon = [];
+
+            for (var i = 0; i < this.members.length; i++) {
+                polygon.push(vertices[this.members[i]].position);
+            }
+
+            return polygon;
+        }
+
+        /**
+         * Returns the angle of this ring in relation to the coordinate system.
+         *
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'getAngle',
+        value: function getAngle() {
+            return Math.PI - this.centralAngle;
+        }
+
+        /**
+         * Loops over the members of this ring from a given start position in a direction opposite to the vertex id passed as the previousId.
+         *
+         * @param {Vertex[]} vertices The vertices associated with the current molecule.
+         * @param {Function} callback A callback with the current vertex id as a parameter.
+         * @param {Number} startVertexId The vertex id of the start vertex.
+         * @param {Number} previousVertexId The vertex id of the previous vertex (the loop calling the callback function will run in the opposite direction of this vertex).
+         */
+
+    }, {
+        key: 'eachMember',
+        value: function eachMember(vertices, callback, startVertexId, previousVertexId) {
+            startVertexId = startVertexId || startVertexId === 0 ? startVertexId : this.members[0];
+            var current = startVertexId;
+            var max = 0;
+
+            while (current != null && max < 100) {
+                var prev = current;
+
+                callback(prev);
+                current = vertices[current].getNextInRing(vertices, this.id, previousVertexId);
+                previousVertexId = prev;
+
+                // Stop while loop when arriving back at the start vertex
+                if (current == startVertexId) {
+                    current = null;
+                }
+
+                max++;
+            }
+        }
+
+        /**
+         * Returns an array containing the neighbouring rings of this ring ordered by ring size.
+         *
+         * @param {RingConnection[]} ringConnections An array of ring connections associated with the current molecule.
+         * @returns {Object[]} An array of neighbouring rings sorted by ring size. Example: { n: 5, neighbour: 1 }.
+         */
+
+    }, {
+        key: 'getOrderedNeighbours',
+        value: function getOrderedNeighbours(ringConnections) {
+            var orderedNeighbours = Array(this.neighbours.length);
+
+            for (var i = 0; i < this.neighbours.length; i++) {
+                var vertices = RingConnection.getVertices(ringConnections, this.id, this.neighbours[i]);
+
+                orderedNeighbours[i] = {
+                    n: vertices.length,
+                    neighbour: this.neighbours[i]
+                };
+            }
+
+            orderedNeighbours.sort(function (a, b) {
+                // Sort highest to lowest
+                return b.n - a.n;
+            });
+
+            return orderedNeighbours;
+        }
+
+        /**
+         * Check whether this ring is an implicitly defined benzene-like (e.g. C1=CC=CC=C1) with 6 members and 3 double bonds.
+         *
+         * @param {Vertex[]} vertices An array of vertices associated with the current molecule.
+         * @returns {Boolean} A boolean indicating whether or not this ring is an implicitly defined benzene-like.
+         */
+
+    }, {
+        key: 'isBenzeneLike',
+        value: function isBenzeneLike(vertices) {
+            var db = this.getDoubleBondCount(vertices);
+            var length = this.members.length;
+
+            return db === 3 && length === 6 || db === 2 && length === 5;
+        }
+
+        /**
+         * Get the number of double bonds inside this ring.
+         *
+         * @param {Vertex[]} vertices An array of vertices associated with the current molecule.
+         * @returns {Number} The number of double bonds inside this ring.
+         */
+
+    }, {
+        key: 'getDoubleBondCount',
+        value: function getDoubleBondCount(vertices) {
+            var doubleBondCount = 0;
+
+            for (var i = 0; i < this.members.length; i++) {
+                var atom = vertices[this.members[i]].value;
+
+                if (atom.bondType === '=' || atom.branchBond === '=') {
+                    doubleBondCount++;
+                }
+            }
+
+            return doubleBondCount;
+        }
+
+        /**
+         * Checks whether or not this ring contains a member with a given vertex id.
+         *
+         * @param {Number} vertexId A vertex id.
+         * @returns {Boolean} A boolean indicating whether or not this ring contains a member with the given vertex id.
+         */
+
+    }, {
+        key: 'contains',
+        value: function contains(vertexId) {
+            for (var i = 0; i < this.members.length; i++) {
+                if (this.members[i] == vertexId) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }]);
+
+    return Ring;
+}();
+
+module.exports = Ring;
+
+},{"./ArrayHelper":2,"./RingConnection":12,"./Vector2":14,"./Vertex":15}],12:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var Vertex = require('./Vertex');
+var Ring = require('./Ring');
+
+/** 
+ * A class representing a ring connection.
+ * 
+ * @property {Number} id The id of this ring connection.
+ * @property {Number} firstRingId A ring id.
+ * @property {Number} secondRingId A ring id.
+ * @property {Set<Number>} vertices A set containing the vertex ids participating in the ring connection.
+ */
+
+var RingConnection = function () {
+    /**
+     * The constructor for the class RingConnection.
+     *
+     * @param {Ring} firstRing A ring.
+     * @param {Ring} secondRing A ring.
+     */
+    function RingConnection(firstRing, secondRing) {
+        _classCallCheck(this, RingConnection);
+
+        this.id = null;
+        this.firstRingId = firstRing.id;
+        this.secondRingId = secondRing.id;
+        this.vertices = new Set();
+
+        for (var m = 0; m < firstRing.members.length; m++) {
+            var c = firstRing.members[m];
+
+            for (var n = 0; n < secondRing.members.length; n++) {
+                var d = secondRing.members[n];
+
+                if (c === d) {
+                    this.addVertex(c);
+                }
+            }
+        }
+    }
+
+    /**
+     * Adding a vertex to the ring connection.
+     *
+     * @param {Number} vertexId A vertex id.
+     */
+
+
+    _createClass(RingConnection, [{
+        key: 'addVertex',
+        value: function addVertex(vertexId) {
+            this.vertices.add(vertexId);
+        }
+
+        /**
+         * Update the ring id of this ring connection that is not the ring id supplied as the second argument.
+         *
+         * @param {Number} ringId A ring id. The new ring id to be set.
+         * @param {Number} otherRingId A ring id. The id that is NOT to be updated.
+         */
+
+    }, {
+        key: 'updateOther',
+        value: function updateOther(ringId, otherRingId) {
+            if (this.firstRingId === otherRingId) {
+                this.secondRingId = ringId;
+            } else {
+                this.firstRingId = ringId;
+            }
+        }
+
+        /**
+         * Returns a boolean indicating whether or not a ring with a given id is participating in this ring connection.
+         * 
+         * @param {Number} ringId A ring id.
+         * @returns {Boolean} A boolean indicating whether or not a ring with a given id participates in this ring connection.
+         */
+
+    }, {
+        key: 'containsRing',
+        value: function containsRing(ringId) {
+            return this.firstRingId === ringId || this.secondRingId === ringId;
+        }
+
+        /**
+         * Checks whether or not this ring connection is a bridge in a bridged ring.
+         *
+         * @param {Vertex[]} vertices The array of vertices associated with the current molecule.
+         * @returns {Boolean} A boolean indicating whether or not this ring connection is a bridge.
+         */
+
+    }, {
+        key: 'isBridge',
+        value: function isBridge(vertices) {
+            if (this.vertices.size > 2) {
+                return true;
+            }
+
+            var _iteratorNormalCompletion = true;
+            var _didIteratorError = false;
+            var _iteratorError = undefined;
+
+            try {
+                for (var _iterator = this.vertices[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+                    var vertexId = _step.value;
+
+                    if (vertices[vertexId].value.rings.length > 2) {
+                        return true;
+                    }
+                }
+            } catch (err) {
+                _didIteratorError = true;
+                _iteratorError = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion && _iterator.return) {
+                        _iterator.return();
+                    }
+                } finally {
+                    if (_didIteratorError) {
+                        throw _iteratorError;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether or not two rings are connected by a bridged bond.
+         *
+         * @static
+         * @param {RingConnection[]} ringConnections An array of ring connections containing the ring connections associated with the current molecule.
+         * @param {Vertex[]} vertices An array of vertices containing the vertices associated with the current molecule.
+         * @param {Number} firstRingId A ring id.
+         * @param {Number} secondRingId A ring id.
+         * @returns {Boolean} A boolean indicating whether or not two rings ar connected by a bridged bond.
+         */
+
+    }], [{
+        key: 'isBridge',
+        value: function isBridge(ringConnections, vertices, firstRingId, secondRingId) {
+            var ringConnection = null;
+
+            for (var i = 0; i < ringConnections.length; i++) {
+                ringConnection = ringConnections[i];
+
+                if (ringConnection.firstRingId === firstRingId && ringConnection.secondRingId === secondRingId || ringConnection.firstRingId === secondRingId && ringConnection.secondRingId === firstRingId) {
+                    return ringConnection.isBridge(vertices);
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Retruns the neighbouring rings of a given ring.
+         *
+         * @static
+         * @param {RingConnection[]} ringConnections An array of ring connections containing ring connections associated with the current molecule.
+         * @param {Number} ringId A ring id.
+         * @returns {Number[]} An array of ring ids of neighbouring rings.
+         */
+
+    }, {
+        key: 'getNeighbours',
+        value: function getNeighbours(ringConnections, ringId) {
+            var neighbours = [];
+
+            for (var i = 0; i < ringConnections.length; i++) {
+                var ringConnection = ringConnections[i];
+
+                if (ringConnection.firstRingId === ringId) {
+                    neighbours.push(ringConnection.secondRingId);
+                } else if (ringConnection.secondRingId === ringId) {
+                    neighbours.push(ringConnection.firstRingId);
+                }
+            }
+
+            return neighbours;
+        }
+
+        /**
+         * Returns an array of vertex ids associated with a given ring connection.
+         *
+         * @static
+         * @param {RingConnection[]} ringConnections An array of ring connections containing ring connections associated with the current molecule.
+         * @param {Number} firstRingId A ring id.
+         * @param {Number} secondRingId A ring id.
+         * @returns {Number[]} An array of vertex ids associated with the ring connection.
+         */
+
+    }, {
+        key: 'getVertices',
+        value: function getVertices(ringConnections, firstRingId, secondRingId) {
+            for (var i = 0; i < ringConnections.length; i++) {
+                var ringConnection = ringConnections[i];
+                if (ringConnection.firstRingId === firstRingId && ringConnection.secondRingId === secondRingId || ringConnection.firstRingId === secondRingId && ringConnection.secondRingId === firstRingId) {
+                    return [].concat(_toConsumableArray(ringConnection.vertices));
+                }
+            }
+        }
+    }]);
+
+    return RingConnection;
+}();
+
+module.exports = RingConnection;
+
+},{"./Ring":11,"./Vertex":15}],13:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _toConsumableArray(arr) { if (Array.isArray(arr)) { for (var i = 0, arr2 = Array(arr.length); i < arr.length; i++) { arr2[i] = arr[i]; } return arr2; } else { return Array.from(arr); } }
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var Graph = require('./Graph');
+
+/** A class encapsulating the functionality to find the smallest set of smallest rings in a graph. */
+
+var SSSR = function () {
+    function SSSR() {
+        _classCallCheck(this, SSSR);
+    }
+
+    _createClass(SSSR, null, [{
+        key: 'getRings',
+
+        /**
+         * Returns an array containing arrays, each representing a ring from the smallest set of smallest rings in the graph.
+         * 
+         * @param {Graph} graph A Graph object.
+         * @returns {Array[]} An array containing arrays, each representing a ring from the smallest set of smallest rings in the group.
+         */
+        value: function getRings(graph) {
+            var adjacencyMatrix = graph.getComponentsAdjacencyMatrix();
+            if (adjacencyMatrix.length === 0) {
+                return null;
+            }
+
+            var connectedComponents = Graph.getConnectedComponents(adjacencyMatrix);
+            var rings = Array();
+
+            for (var i = 0; i < connectedComponents.length; i++) {
+                var connectedComponent = connectedComponents[i];
+                var ccAdjacencyMatrix = graph.getSubgraphAdjacencyMatrix([].concat(_toConsumableArray(connectedComponent)));
+
+                var arrBondCount = new Uint16Array(ccAdjacencyMatrix.length);
+                var arrRingCount = new Uint16Array(ccAdjacencyMatrix.length);
+
+                for (var j = 0; j < ccAdjacencyMatrix.length; j++) {
+                    arrRingCount[j] = 0;
+                    arrBondCount[j] = 0;
+
+                    for (var k = 0; k < ccAdjacencyMatrix[j].length; k++) {
+                        arrBondCount[j] += ccAdjacencyMatrix[j][k];
+                    }
+                }
+
+                // Get the edge number and the theoretical number of rings in SSSR
+                var nEdges = 0;
+
+                for (var j = 0; j < ccAdjacencyMatrix.length; j++) {
+                    for (var k = j + 1; k < ccAdjacencyMatrix.length; k++) {
+                        nEdges += ccAdjacencyMatrix[j][k];
+                    }
+                }
+
+                var nSssr = nEdges - ccAdjacencyMatrix.length + 1;
+
+                // console.log(nEdges, ccAdjacencyMatrix.length, nSssr);
+                // console.log(SSSR.getEdgeList(ccAdjacencyMatrix));
+                // console.log(ccAdjacencyMatrix);
+
+                // If all vertices have 3 incident edges, calculate with different formula (see Euler)
+                var allThree = true;
+                for (var j = 0; j < arrBondCount.length; j++) {
+                    if (arrBondCount[j] !== 3) {
+                        allThree = false;
+                    }
+                }
+
+                if (allThree) {
+                    nSssr = 2.0 + nEdges - ccAdjacencyMatrix.length;
+                }
+
+                // All vertices are part of one ring if theres only one ring.
+                if (nSssr === 1) {
+                    rings.push([].concat(_toConsumableArray(connectedComponent)));
+                    continue;
+                }
+
+                var _SSSR$getPathIncluded = SSSR.getPathIncludedDistanceMatrices(ccAdjacencyMatrix),
+                    d = _SSSR$getPathIncluded.d,
+                    pe = _SSSR$getPathIncluded.pe,
+                    pe_prime = _SSSR$getPathIncluded.pe_prime;
+
+                var c = SSSR.getRingCandidates(d, pe, pe_prime);
+                var sssr = SSSR.getSSSR(c, d, ccAdjacencyMatrix, pe, pe_prime, arrBondCount, arrRingCount, nSssr);
+
+                for (var j = 0; j < sssr.length; j++) {
+                    var ring = Array(sssr[j].size);
+                    var index = 0;
+
+                    var _iteratorNormalCompletion = true;
+                    var _didIteratorError = false;
+                    var _iteratorError = undefined;
+
+                    try {
+                        for (var _iterator = sssr[j][Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
+                            var val = _step.value;
+
+                            // Get the original id of the vertex back
+                            ring[index++] = connectedComponent[val];
+                        }
+                    } catch (err) {
+                        _didIteratorError = true;
+                        _iteratorError = err;
+                    } finally {
+                        try {
+                            if (!_iteratorNormalCompletion && _iterator.return) {
+                                _iterator.return();
+                            }
+                        } finally {
+                            if (_didIteratorError) {
+                                throw _iteratorError;
+                            }
+                        }
+                    }
+
+                    rings.push(ring);
+                }
+            }
+
+            return rings;
+        }
+
+        /**
+         * Creates a printable string from a matrix (2D array).
+         * 
+         * @param {Array[]} matrix A 2D array.
+         * @returns {String} A string representing the matrix.
+         */
+
+    }, {
+        key: 'matrixToString',
+        value: function matrixToString(matrix) {
+            var str = '';
+
+            for (var i = 0; i < matrix.length; i++) {
+                for (var j = 0; j < matrix[i].length; j++) {
+                    str += matrix[i][j] + ' ';
+                }
+
+                str += '\n';
+            }
+
+            return str;
+        }
+
+        /**
+         * Returnes the two path-included distance matrices used to find the sssr.
+         * 
+         * @param {Array[]} adjacencyMatrix An adjacency matrix.
+         * @returns {Object} The path-included distance matrices. { p1, p2 }
+         */
+
+    }, {
+        key: 'getPathIncludedDistanceMatrices',
+        value: function getPathIncludedDistanceMatrices(adjacencyMatrix) {
+            var length = adjacencyMatrix.length;
+            var d = Array(length);
+            var pe = Array(length);
+            var pe_prime = Array(length);
+            var l = 0;
+            var m = 0;
+            var n = 0;
+
+            var i = length;
+            while (i--) {
+                d[i] = Array(length);
+                pe[i] = Array(length);
+                pe_prime[i] = Array(length);
+
+                var j = length;
+                while (j--) {
+                    d[i][j] = i === j || adjacencyMatrix[i][j] === 1 ? adjacencyMatrix[i][j] : Number.POSITIVE_INFINITY;
+
+                    if (d[i][j] === 1) {
+                        pe[i][j] = [[[i, j]]];
+                    } else {
+                        pe[i][j] = Array();
+                    }
+
+                    pe_prime[i][j] = Array();
+                }
+            }
+
+            var k = length;
+            var j;
+            while (k--) {
+                i = length;
+                while (i--) {
+                    j = length;
+                    while (j--) {
+                        var previousPathLength = d[i][j];
+                        var newPathLength = d[i][k] + d[k][j];
+
+                        if (previousPathLength > newPathLength) {
+                            var l, m, n;
+                            if (previousPathLength === newPathLength + 1) {
+                                pe_prime[i][j] = [pe[i][j].length];
+                                l = pe[i][j].length;
+                                while (l--) {
+                                    pe_prime[i][j][l] = [pe[i][j][l].length];
+                                    m = pe[i][j][l].length;
+                                    while (m--) {
+                                        pe_prime[i][j][l][m] = [pe[i][j][l][m].length];
+                                        n = pe[i][j][l][m].length;
+                                        while (n--) {
+                                            pe_prime[i][j][l][m][n] = [pe[i][j][l][m][0], pe[i][j][l][m][1]];
+                                        }
+                                    }
+                                }
+                            } else {
+                                pe_prime[i][j] = Array();
+                            }
+
+                            d[i][j] = newPathLength;
+
+                            pe[i][j] = [[]];
+
+                            l = pe[i][k][0].length;
+                            while (l--) {
+                                pe[i][j][0].push(pe[i][k][0][l]);
+                            }
+
+                            l = pe[k][j][0].length;
+                            while (l--) {
+                                pe[i][j][0].push(pe[k][j][0][l]);
+                            }
+                        } else if (previousPathLength === newPathLength) {
+                            if (pe[i][k].length && pe[k][j].length) {
+                                var l;
+                                if (pe[i][j].length) {
+                                    var tmp = Array();
+
+                                    l = pe[i][k][0].length;
+                                    while (l--) {
+                                        tmp.push(pe[i][k][0][l]);
+                                    }
+
+                                    l = pe[k][j][0].length;
+                                    while (l--) {
+                                        tmp.push(pe[k][j][0][l]);
+                                    }
+
+                                    pe[i][j].push(tmp);
+                                } else {
+                                    var _tmp = Array();
+                                    l = pe[i][k][0].length;
+                                    while (l--) {
+                                        _tmp.push(pe[i][k][0][l]);
+                                    }
+
+                                    l = pe[k][j][0].length;
+                                    while (l--) {
+                                        _tmp.push(pe[k][j][0][l]);
+                                    }
+
+                                    pe[i][j][0] = _tmp;
+                                }
+                            }
+                        } else if (previousPathLength === newPathLength - 1) {
+                            var l;
+                            if (pe_prime[i][j].length) {
+                                var _tmp2 = Array();
+
+                                l = pe[i][k][0].length;
+                                while (l--) {
+                                    _tmp2.push(pe[i][k][0][l]);
+                                }
+
+                                l = pe[k][j][0].length;
+                                while (l--) {
+                                    _tmp2.push(pe[k][j][0][l]);
+                                }
+
+                                pe_prime[i][j].push(_tmp2);
+                            } else {
+                                var _tmp3 = Array();
+
+                                l = pe[i][k][0].length;
+                                while (l--) {
+                                    _tmp3.push(pe[i][k][0][l]);
+                                }
+
+                                l = pe[k][j][0].length;
+                                while (l--) {
+                                    _tmp3.push(pe[k][j][0][l]);
+                                }
+
+                                pe_prime[i][j][0] = _tmp3;
+                            }
+                        }
+                    }
+                }
+            }
+
+            return {
+                d: d,
+                pe: pe,
+                pe_prime: pe_prime
+            };
+        }
+
+        /**
+         * Get the ring candidates from the path-included distance matrices.
+         * 
+         * @param {Array[]} d The distance matrix.
+         * @param {Array[]} pe A matrix containing the shortest paths.
+         * @param {Array[]} pe_prime A matrix containing the shortest paths + one vertex.
+         * @returns {Array[]} The ring candidates.
+         */
+
+    }, {
+        key: 'getRingCandidates',
+        value: function getRingCandidates(d, pe, pe_prime) {
+            var length = d.length;
+            var candidates = Array();
+            var c = 0;
+
+            for (var i = 0; i < length; i++) {
+                for (var j = 0; j < length; j++) {
+                    if (d[i][j] === 0 || pe[i][j].length === 1 && pe_prime[i][j] === 0) {
+                        continue;
+                    } else {
+                        // c is the number of vertices in the cycle.
+                        if (pe_prime[i][j].length !== 0) {
+                            c = 2 * (d[i][j] + 0.5);
+                        } else {
+                            c = 2 * d[i][j];
+                        }
+
+                        if (c !== Infinity) {
+                            candidates.push([c, pe[i][j], pe_prime[i][j]]);
+                        }
+                    }
+                }
+            }
+
+            // Candidates have to be sorted by c
+            candidates.sort(function (a, b) {
+                return a[0] - b[0];
+            });
+
+            return candidates;
+        }
+
+        /**
+         * Searches the candidates for the smallest set of smallest rings.
+         * 
+         * @param {Array[]} c The candidates.
+         * @param {Array[]} d The distance matrix.
+         * @param {Array[]} adjacencyMatrix An adjacency matrix.
+         * @param {Array[]} pe A matrix containing the shortest paths.
+         * @param {Array[]} pe_prime A matrix containing the shortest paths + one vertex.
+         * @param {Uint16Array} arrBondCount A matrix containing the bond count of each vertex.
+         * @param {Uint16Array} arrRingCount A matrix containing the number of rings associated with each vertex.
+         * @param {Number} nsssr The theoretical number of rings in the graph.
+         * @returns {Set[]} The smallest set of smallest rings.
+         */
+
+    }, {
+        key: 'getSSSR',
+        value: function getSSSR(c, d, adjacencyMatrix, pe, pe_prime, arrBondCount, arrRingCount, nsssr) {
+            var cSssr = Array();
+            var allBonds = Array();
+
+            for (var i = 0; i < c.length; i++) {
+                if (c[i][0] % 2 !== 0) {
+                    for (var j = 0; j < c[i][2].length; j++) {
+                        var bonds = c[i][1][0].concat(c[i][2][j]);
+                        // Some bonds are added twice, resulting in [[u, v], [u, v]] instead of [u, v].
+                        // TODO: This is a workaround, fix later. Probably should be a set rather than an array, however the computational overhead
+                        //       is probably bigger compared to leaving it like this.
+                        for (var k = 0; k < bonds.length; k++) {
+                            if (bonds[k][0].constructor === Array) bonds[k] = bonds[k][0];
+                        }
+
+                        var atoms = SSSR.bondsToAtoms(bonds);
+
+                        if (SSSR.getBondCount(atoms, adjacencyMatrix) === atoms.size && !SSSR.pathSetsContain(cSssr, atoms, bonds, allBonds, arrBondCount, arrRingCount)) {
+                            cSssr.push(atoms);
+                            allBonds = allBonds.concat(bonds);
+                        }
+
+                        if (cSssr.length > nsssr) {
+                            return cSssr;
+                        }
+                    }
+                } else {
+                    for (var _j = 0; _j < c[i][1].length - 1; _j++) {
+                        var _bonds = c[i][1][_j].concat(c[i][1][_j + 1]);
+                        // Some bonds are added twice, resulting in [[u, v], [u, v]] instead of [u, v].
+                        // TODO: This is a workaround, fix later. Probably should be a set rather than an array, however the computational overhead
+                        //       is probably bigger compared to leaving it like this.
+                        for (var k = 0; k < _bonds.length; k++) {
+                            if (_bonds[k][0].constructor === Array) _bonds[k] = _bonds[k][0];
+                        }
+
+                        var _atoms = SSSR.bondsToAtoms(_bonds);
+
+                        if (SSSR.getBondCount(_atoms, adjacencyMatrix) === _atoms.size && !SSSR.pathSetsContain(cSssr, _atoms, _bonds, allBonds, arrBondCount, arrRingCount)) {
+                            cSssr.push(_atoms);
+                            allBonds = allBonds.concat(_bonds);
+                        }
+
+                        if (cSssr.length > nsssr) {
+                            return cSssr;
+                        }
+                    }
+                }
+            }
+
+            return cSssr;
+        }
+
+        /**
+         * Returns the number of edges in a graph defined by an adjacency matrix.
+         * 
+         * @param {Array[]} adjacencyMatrix An adjacency matrix.
+         * @returns {Number} The number of edges in the graph defined by the adjacency matrix.
+         */
+
+    }, {
+        key: 'getEdgeCount',
+        value: function getEdgeCount(adjacencyMatrix) {
+            var edgeCount = 0;
+            var length = adjacencyMatrix.length;
+
+            var i = length - 1;
+            while (i--) {
+                var j = length;
+                while (j--) {
+                    if (adjacencyMatrix[i][j] === 1) {
+                        edgeCount++;
+                    }
+                }
+            }
+
+            return edgeCount;
+        }
+
+        /**
+         * Returns an edge list constructed form an adjacency matrix.
+         * 
+         * @param {Array[]} adjacencyMatrix An adjacency matrix.
+         * @returns {Array[]} An edge list. E.g. [ [ 0, 1 ], ..., [ 16, 2 ] ]
+         */
+
+    }, {
+        key: 'getEdgeList',
+        value: function getEdgeList(adjacencyMatrix) {
+            var length = adjacencyMatrix.length;
+            var edgeList = Array();
+
+            var i = length - 1;
+            while (i--) {
+                var j = length;
+                while (j--) {
+                    if (adjacencyMatrix[i][j] === 1) {
+                        edgeList.push([i, j]);
+                    }
+                }
+            }
+
+            return edgeList;
+        }
+
+        /**
+         * Return a set of vertex indices contained in an array of bonds.
+         * 
+         * @param {Array} bonds An array of bonds. A bond is defined as [ sourceVertexId, targetVertexId ].
+         * @returns {Set<Number>} An array of vertices.
+         */
+
+    }, {
+        key: 'bondsToAtoms',
+        value: function bondsToAtoms(bonds) {
+            var atoms = new Set();
+
+            var i = bonds.length;
+            while (i--) {
+                atoms.add(bonds[i][0]);
+                atoms.add(bonds[i][1]);
+            }
+            return atoms;
+        }
+
+        /**
+        * Returns the number of bonds within a set of atoms.
+        * 
+        * @param {Set<Number>} atoms An array of atom ids.
+        * @param {Array[]} adjacencyMatrix An adjacency matrix.
+        * @returns {Number} The number of bonds in a set of atoms.
+        */
+
+    }, {
+        key: 'getBondCount',
+        value: function getBondCount(atoms, adjacencyMatrix) {
+            var count = 0;
+            var _iteratorNormalCompletion2 = true;
+            var _didIteratorError2 = false;
+            var _iteratorError2 = undefined;
+
+            try {
+                for (var _iterator2 = atoms[Symbol.iterator](), _step2; !(_iteratorNormalCompletion2 = (_step2 = _iterator2.next()).done); _iteratorNormalCompletion2 = true) {
+                    var u = _step2.value;
+                    var _iteratorNormalCompletion3 = true;
+                    var _didIteratorError3 = false;
+                    var _iteratorError3 = undefined;
+
+                    try {
+                        for (var _iterator3 = atoms[Symbol.iterator](), _step3; !(_iteratorNormalCompletion3 = (_step3 = _iterator3.next()).done); _iteratorNormalCompletion3 = true) {
+                            var v = _step3.value;
+
+                            if (u === v) {
+                                continue;
+                            }
+                            count += adjacencyMatrix[u][v];
+                        }
+                    } catch (err) {
+                        _didIteratorError3 = true;
+                        _iteratorError3 = err;
+                    } finally {
+                        try {
+                            if (!_iteratorNormalCompletion3 && _iterator3.return) {
+                                _iterator3.return();
+                            }
+                        } finally {
+                            if (_didIteratorError3) {
+                                throw _iteratorError3;
+                            }
+                        }
+                    }
+                }
+            } catch (err) {
+                _didIteratorError2 = true;
+                _iteratorError2 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion2 && _iterator2.return) {
+                        _iterator2.return();
+                    }
+                } finally {
+                    if (_didIteratorError2) {
+                        throw _iteratorError2;
+                    }
+                }
+            }
+
+            return count / 2;
+        }
+
+        /**
+         * Checks whether or not a given path already exists in an array of paths.
+         * 
+         * @param {Set[]} pathSets An array of sets each representing a path.
+         * @param {Set<Number>} pathSet A set representing a path.
+         * @param {Array[]} bonds The bonds associated with the current path.
+         * @param {Array[]} allBonds All bonds currently associated with rings in the SSSR set.
+         * @param {Uint16Array} arrBondCount A matrix containing the bond count of each vertex.
+         * @param {Uint16Array} arrRingCount A matrix containing the number of rings associated with each vertex.
+         * @returns {Boolean} A boolean indicating whether or not a give path is contained within a set.
+         */
+
+    }, {
+        key: 'pathSetsContain',
+        value: function pathSetsContain(pathSets, pathSet, bonds, allBonds, arrBondCount, arrRingCount) {
+            var i = pathSets.length;
+            while (i--) {
+                if (SSSR.isSupersetOf(pathSet, pathSets[i])) {
+                    return true;
+                }
+
+                if (pathSets[i].size !== pathSet.size) {
+                    continue;
+                }
+
+                if (SSSR.areSetsEqual(pathSets[i], pathSet)) {
+                    return true;
+                }
+            }
+
+            // Check if the edges from the candidate are already all contained within the paths of the set of paths.
+            // TODO: For some reason, this does not replace the isSupersetOf method above -> why?
+            var count = 0;
+            var allContained = false;
+            i = bonds.length;
+            while (i--) {
+                var j = allBonds.length;
+                while (j--) {
+                    if (bonds[i][0] === allBonds[j][0] && bonds[i][1] === allBonds[j][1] || bonds[i][1] === allBonds[j][0] && bonds[i][0] === allBonds[j][1]) {
+                        count++;
+                    }
+
+                    if (count === bonds.length) {
+                        allContained = true;
+                    }
+                }
+            }
+
+            // If all the bonds and thus vertices are already contained within other rings
+            // check if there's one vertex with ringCount < bondCount
+            var specialCase = false;
+            if (allContained) {
+                var _iteratorNormalCompletion4 = true;
+                var _didIteratorError4 = false;
+                var _iteratorError4 = undefined;
+
+                try {
+                    for (var _iterator4 = pathSet[Symbol.iterator](), _step4; !(_iteratorNormalCompletion4 = (_step4 = _iterator4.next()).done); _iteratorNormalCompletion4 = true) {
+                        var element = _step4.value;
+
+                        if (arrRingCount[element] < arrBondCount[element]) {
+                            specialCase = true;
+                            break;
+                        }
+                    }
+                } catch (err) {
+                    _didIteratorError4 = true;
+                    _iteratorError4 = err;
+                } finally {
+                    try {
+                        if (!_iteratorNormalCompletion4 && _iterator4.return) {
+                            _iterator4.return();
+                        }
+                    } finally {
+                        if (_didIteratorError4) {
+                            throw _iteratorError4;
+                        }
+                    }
+                }
+            }
+
+            if (allContained && !specialCase) {
+                return true;
+            }
+
+            // Update the ring counts for the vertices
+            var _iteratorNormalCompletion5 = true;
+            var _didIteratorError5 = false;
+            var _iteratorError5 = undefined;
+
+            try {
+                for (var _iterator5 = pathSet[Symbol.iterator](), _step5; !(_iteratorNormalCompletion5 = (_step5 = _iterator5.next()).done); _iteratorNormalCompletion5 = true) {
+                    var _element = _step5.value;
+
+                    arrRingCount[_element]++;
+                }
+            } catch (err) {
+                _didIteratorError5 = true;
+                _iteratorError5 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion5 && _iterator5.return) {
+                        _iterator5.return();
+                    }
+                } finally {
+                    if (_didIteratorError5) {
+                        throw _iteratorError5;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        /**
+         * Checks whether or not two sets are equal (contain the same elements).
+         * 
+         * @param {Set<Number>} setA A set.
+         * @param {Set<Number>} setB A set.
+         * @returns {Boolean} A boolean indicating whether or not the two sets are equal.
+         */
+
+    }, {
+        key: 'areSetsEqual',
+        value: function areSetsEqual(setA, setB) {
+            if (setA.size !== setB.size) {
+                return false;
+            }
+
+            var _iteratorNormalCompletion6 = true;
+            var _didIteratorError6 = false;
+            var _iteratorError6 = undefined;
+
+            try {
+                for (var _iterator6 = setA[Symbol.iterator](), _step6; !(_iteratorNormalCompletion6 = (_step6 = _iterator6.next()).done); _iteratorNormalCompletion6 = true) {
+                    var element = _step6.value;
+
+                    if (!setB.has(element)) {
+                        return false;
+                    }
+                }
+            } catch (err) {
+                _didIteratorError6 = true;
+                _iteratorError6 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion6 && _iterator6.return) {
+                        _iterator6.return();
+                    }
+                } finally {
+                    if (_didIteratorError6) {
+                        throw _iteratorError6;
+                    }
+                }
+            }
+
+            return true;
+        }
+
+        /**
+         * Checks whether or not a set (setA) is a superset of another set (setB).
+         * 
+         * @param {Set<Number>} setA A set.
+         * @param {Set<Number>} setB A set.
+         * @returns {Boolean} A boolean indicating whether or not setB is a superset of setA.
+         */
+
+    }, {
+        key: 'isSupersetOf',
+        value: function isSupersetOf(setA, setB) {
+            var _iteratorNormalCompletion7 = true;
+            var _didIteratorError7 = false;
+            var _iteratorError7 = undefined;
+
+            try {
+                for (var _iterator7 = setB[Symbol.iterator](), _step7; !(_iteratorNormalCompletion7 = (_step7 = _iterator7.next()).done); _iteratorNormalCompletion7 = true) {
+                    var element = _step7.value;
+
+                    if (!setA.has(element)) {
+                        return false;
+                    }
+                }
+            } catch (err) {
+                _didIteratorError7 = true;
+                _iteratorError7 = err;
+            } finally {
+                try {
+                    if (!_iteratorNormalCompletion7 && _iterator7.return) {
+                        _iterator7.return();
+                    }
+                } finally {
+                    if (_didIteratorError7) {
+                        throw _iteratorError7;
+                    }
+                }
+            }
+
+            return true;
+        }
+    }]);
+
+    return SSSR;
+}();
+
+module.exports = SSSR;
+
+},{"./Graph":7}],14:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+
+/** 
+ * A class representing a 2D vector.
+ * 
+ * @property {Number} x The x component of the vector.
+ * @property {Number} y The y component of the vector.
+ */
+var Vector2 = function () {
+    /**
+     * The constructor of the class Vector2.
+     *
+     * @param {(Number|Vector2)} x The initial x coordinate value or, if the single argument, a Vector2 object.
+     * @param {Number} y The initial y coordinate value.
+     */
+    function Vector2(x, y) {
+        _classCallCheck(this, Vector2);
+
+        if (arguments.length == 0) {
+            this.x = 0;
+            this.y = 0;
+        } else if (arguments.length == 1) {
+            this.x = x.x;
+            this.y = x.y;
+        } else {
+            this.x = x;
+            this.y = y;
+        }
+    }
+
+    /**
+     * Clones this vector and returns the clone.
+     *
+     * @returns {Vector2} The clone of this vector.
+     */
+
+
+    _createClass(Vector2, [{
+        key: 'clone',
+        value: function clone() {
+            return new Vector2(this.x, this.y);
+        }
+
+        /**
+         * Returns a string representation of this vector.
+         *
+         * @returns {String} A string representation of this vector.
+         */
+
+    }, {
+        key: 'toString',
+        value: function toString() {
+            return '(' + this.x + ',' + this.y + ')';
+        }
+
+        /**
+         * Add the x and y coordinate values of a vector to the x and y coordinate values of this vector.
+         *
+         * @param {Vector2} vec Another vector.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'add',
+        value: function add(vec) {
+            this.x += vec.x;
+            this.y += vec.y;
+
+            return this;
+        }
+
+        /**
+         * Subtract the x and y coordinate values of a vector from the x and y coordinate values of this vector.
+         *
+         * @param {Vector2} vec Another vector.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'subtract',
+        value: function subtract(vec) {
+            this.x -= vec.x;
+            this.y -= vec.y;
+
+            return this;
+        }
+
+        /**
+         * Divide the x and y coordinate values of this vector by a scalar.
+         *
+         * @param {Number} scalar The scalar.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'divide',
+        value: function divide(scalar) {
+            this.x /= scalar;
+            this.y /= scalar;
+
+            return this;
+        }
+
+        /**
+         * Multiply the x and y coordinate values of this vector by the values of another vector.
+         *
+         * @param {Vector2} v A vector.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'multiply',
+        value: function multiply(v) {
+            this.x *= v.x;
+            this.y *= v.y;
+
+            return this;
+        }
+
+        /**
+         * Multiply the x and y coordinate values of this vector by a scalar.
+         *
+         * @param {Number} scalar The scalar.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'multiplyScalar',
+        value: function multiplyScalar(scalar) {
+            this.x *= scalar;
+            this.y *= scalar;
+
+            return this;
+        }
+
+        /**
+         * Inverts this vector. Same as multiply(-1.0).
+         *
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'invert',
+        value: function invert() {
+            this.x = -this.x;
+            this.y = -this.y;
+
+            return this;
+        }
+
+        /**
+         * Returns the angle of this vector in relation to the coordinate system.
+         *
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'angle',
+        value: function angle() {
+            return Math.atan2(this.y, this.x);
+        }
+
+        /**
+         * Returns the euclidean distance between this vector and another vector.
+         *
+         * @param {Vector2} vec A vector.
+         * @returns {Number} The euclidean distance between the two vectors.
+         */
+
+    }, {
+        key: 'distance',
+        value: function distance(vec) {
+            return Math.sqrt((vec.x - this.x) * (vec.x - this.x) + (vec.y - this.y) * (vec.y - this.y));
+        }
+
+        /**
+         * Returns the squared euclidean distance between this vector and another vector. When only the relative distances of a set of vectors are needed, this is is less expensive than using distance(vec).
+         *
+         * @param {Vector2} vec Another vector.
+         * @returns {Number} The squared euclidean distance of the two vectors.
+         */
+
+    }, {
+        key: 'distanceSq',
+        value: function distanceSq(vec) {
+            return (vec.x - this.x) * (vec.x - this.x) + (vec.y - this.y) * (vec.y - this.y);
+        }
+
+        /**
+         * Checks whether or not this vector is in a clockwise or counter-clockwise rotational direction compared to another vector in relation to the coordinate system.
+         *
+         * @param {Vector2} vec Another vector.
+         * @returns {Number} Returns -1, 0 or 1 if the vector supplied as an argument is clockwise, neutral or counter-clockwise respectively to this vector in relation to the coordinate system.
+         */
+
+    }, {
+        key: 'clockwise',
+        value: function clockwise(vec) {
+            var a = this.y * vec.x;
+            var b = this.x * vec.y;
+
+            if (a > b) {
+                return -1;
+            } else if (a === b) {
+                return 0;
+            }
+
+            return 1;
+        }
+
+        /**
+         * Checks whether or not this vector is in a clockwise or counter-clockwise rotational direction compared to another vector in relation to an arbitrary third vector.
+         *
+         * @param {Vector2} center The central vector.
+         * @param {Vector2} vec Another vector.
+         * @returns {Number} Returns -1, 0 or 1 if the vector supplied as an argument is clockwise, neutral or counter-clockwise respectively to this vector in relation to an arbitrary third vector.
+         */
+
+    }, {
+        key: 'relativeClockwise',
+        value: function relativeClockwise(center, vec) {
+            var a = (this.y - center.y) * (vec.x - center.x);
+            var b = (this.x - center.x) * (vec.y - center.y);
+
+            if (a > b) {
+                return -1;
+            } else if (a === b) {
+                return 0;
+            }
+
+            return 1;
+        }
+
+        /**
+         * Rotates this vector by a given number of radians around the origin of the coordinate system.
+         *
+         * @param {Number} angle The angle in radians to rotate the vector.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'rotate',
+        value: function rotate(angle) {
+            var tmp = new Vector2(0, 0);
+            var cosAngle = Math.cos(angle);
+            var sinAngle = Math.sin(angle);
+
+            tmp.x = this.x * cosAngle - this.y * sinAngle;
+            tmp.y = this.x * sinAngle + this.y * cosAngle;
+
+            this.x = tmp.x;
+            this.y = tmp.y;
+
+            return this;
+        }
+
+        /**
+         * Rotates this vector around another vector.
+         *
+         * @param {Number} angle The angle in radians to rotate the vector.
+         * @param {Vector2} vec The vector which is used as the rotational center.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'rotateAround',
+        value: function rotateAround(angle, vec) {
+            var s = Math.sin(angle);
+            var c = Math.cos(angle);
+
+            this.x -= vec.x;
+            this.y -= vec.y;
+
+            var x = this.x * c - this.y * s;
+            var y = this.x * s + this.y * c;
+
+            this.x = x + vec.x;
+            this.y = y + vec.y;
+
+            return this;
+        }
+
+        /**
+         * Rotate a vector around a given center to the same angle as another vector (so that the two vectors and the center are in a line, with both vectors on one side of the center), keeps the distance from this vector to the center.
+         *
+         * @param {Vector2} vec The vector to rotate this vector to.
+         * @param {Vector2} center The rotational center.
+         * @param {Number} [offsetAngle=0.0] An additional amount of radians to rotate the vector.
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'rotateTo',
+        value: function rotateTo(vec, center) {
+            var offsetAngle = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0.0;
+
+            // Problem if this is first position
+            this.x += 0.001;
+            this.y -= 0.001;
+
+            var a = Vector2.subtract(this, center);
+            var b = Vector2.subtract(vec, center);
+            var angle = Vector2.angle(b, a);
+
+            this.rotateAround(angle + offsetAngle, center);
+
+            return this;
+        }
+
+        /**
+         * Rotates the vector away from a specified vector around a center.
+         * 
+         * @param {Vector2} vec The vector this one is rotated away from.
+         * @param {Vector2} center The rotational center.
+         * @param {Number} angle The angle by which to rotate.
+         */
+
+    }, {
+        key: 'rotateAwayFrom',
+        value: function rotateAwayFrom(vec, center, angle) {
+            this.rotateAround(angle, center);
+
+            var distSqA = this.distanceSq(vec);
+
+            this.rotateAround(-2.0 * angle, center);
+
+            var distSqB = this.distanceSq(vec);
+
+            // If it was rotated towards the other vertex, rotate in other direction by same amount.
+            if (distSqB < distSqA) {
+                this.rotateAround(2.0 * angle, center);
+            }
+        }
+
+        /**
+         * Returns the angle in radians used to rotate this vector away from a given vector.
+         * 
+         * @param {Vector2} vec The vector this one is rotated away from.
+         * @param {Vector2} center The rotational center.
+         * @param {Number} angle The angle by which to rotate.
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'getRotateAwayFromAngle',
+        value: function getRotateAwayFromAngle(vec, center, angle) {
+            var tmp = this.clone();
+
+            tmp.rotateAround(angle, center);
+
+            var distSqA = tmp.distanceSq(vec);
+
+            tmp.rotateAround(-2.0 * angle, center);
+
+            var distSqB = tmp.distanceSq(vec);
+
+            if (distSqB < distSqA) {
+                return angle;
+            } else {
+                return -angle;
+            }
+        }
+
+        /**
+         * Returns the angle in radians used to rotate this vector towards a given vector.
+         * 
+         * @param {Vector2} vec The vector this one is rotated towards to.
+         * @param {Vector2} center The rotational center.
+         * @param {Number} angle The angle by which to rotate.
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'getRotateTowardsAngle',
+        value: function getRotateTowardsAngle(vec, center, angle) {
+            var tmp = this.clone();
+
+            tmp.rotateAround(angle, center);
+
+            var distSqA = tmp.distanceSq(vec);
+
+            tmp.rotateAround(-2.0 * angle, center);
+
+            var distSqB = tmp.distanceSq(vec);
+
+            if (distSqB > distSqA) {
+                return angle;
+            } else {
+                return -angle;
+            }
+        }
+
+        /**
+         * Gets the angles between this vector and another vector around a common center of rotation.
+         *
+         * @param {Vector2} vec Another vector.
+         * @param {Vector2} center The center of rotation.
+         * @returns {Number} The angle between this vector and another vector around a center of rotation in radians.
+         */
+
+    }, {
+        key: 'getRotateToAngle',
+        value: function getRotateToAngle(vec, center) {
+            var a = Vector2.subtract(this, center);
+            var b = Vector2.subtract(vec, center);
+            var angle = Vector2.angle(b, a);
+
+            return Number.isNaN(angle) ? 0.0 : angle;
+        }
+
+        /**
+         * Checks whether a vector lies within a polygon spanned by a set of vectors.
+         *
+         * @param {Vector2[]} polygon An array of vectors spanning the polygon.
+         * @returns {Boolean} A boolean indicating whether or not this vector is within a polygon.
+         */
+
+    }, {
+        key: 'isInPolygon',
+        value: function isInPolygon(polygon) {
+            var inside = false;
+
+            // Its not always a given, that the polygon is convex (-> sugars)
+            for (var i = 0, j = polygon.length - 1; i < polygon.length; j = i++) {
+                if (polygon[i].y > this.y != polygon[j].y > this.y && this.x < (polygon[j].x - polygon[i].x) * (this.y - polygon[i].y) / (polygon[j].y - polygon[i].y) + polygon[i].x) {
+                    inside = !inside;
+                }
+            }
+
+            return inside;
+        }
+
+        /**
+         * Returns the length of this vector.
+         *
+         * @returns {Number} The length of this vector.
+         */
+
+    }, {
+        key: 'length',
+        value: function length() {
+            return Math.sqrt(this.x * this.x + this.y * this.y);
+        }
+
+        /**
+         * Returns the square of the length of this vector.
+         *
+         * @returns {Number} The square of the length of this vector.
+         */
+
+    }, {
+        key: 'lengthSq',
+        value: function lengthSq() {
+            return this.x * this.x + this.y * this.y;
+        }
+
+        /**
+         * Normalizes this vector.
+         *
+         * @returns {Vector2} Returns itself.
+         */
+
+    }, {
+        key: 'normalize',
+        value: function normalize() {
+            this.divide(this.length());
+
+            return this;
+        }
+
+        /**
+         * Returns a normalized copy of this vector.
+         *
+         * @returns {Vector2} A normalized copy of this vector.
+         */
+
+    }, {
+        key: 'normalized',
+        value: function normalized() {
+            return Vector2.divideScalar(this, this.length());
+        }
+
+        /**
+         * Calculates which side of a line spanned by two vectors this vector is.
+         *
+         * @param {Vector2} vecA A vector.
+         * @param {Vector2} vecB A vector.
+         * @returns {Number} A number indicating the side of this vector, given a line spanned by two other vectors.
+         */
+
+    }, {
+        key: 'whichSide',
+        value: function whichSide(vecA, vecB) {
+            return (this.x - vecA.x) * (vecB.y - vecA.y) - (this.y - vecA.y) * (vecB.x - vecA.x);
+        }
+
+        /**
+         * Checks whether or not this vector is on the same side of a line spanned by two vectors as another vector.
+         *
+         * @param {Vector2} vecA A vector spanning the line.
+         * @param {Vector2} vecB A vector spanning the line.
+         * @param {Vector2} vecC A vector to check whether or not it is on the same side as this vector.
+         * @returns {Boolean} Returns a boolean indicating whether or not this vector is on the same side as another vector.
+         */
+
+    }, {
+        key: 'sameSideAs',
+        value: function sameSideAs(vecA, vecB, vecC) {
+            var d = this.whichSide(vecA, vecB);
+            var dRef = vecC.whichSide(vecA, vecB);
+
+            return d < 0 && dRef < 0 || d == 0 && dRef == 0 || d > 0 && dRef > 0;
+        }
+
+        /**
+         * Adds two vectors and returns the result as a new vector.
+         *
+         * @static
+         * @param {Vector2} vecA A summand.
+         * @param {Vector2} vecB A summand.
+         * @returns {Vector2} Returns the sum of two vectors.
+         */
+
+    }], [{
+        key: 'add',
+        value: function add(vecA, vecB) {
+            return new Vector2(vecA.x + vecB.x, vecA.y + vecB.y);
+        }
+
+        /**
+         * Subtracts one vector from another and returns the result as a new vector.
+         *
+         * @static
+         * @param {Vector2} vecA The minuend.
+         * @param {Vector2} vecB The subtrahend.
+         * @returns {Vector2} Returns the difference of two vectors.
+         */
+
+    }, {
+        key: 'subtract',
+        value: function subtract(vecA, vecB) {
+            return new Vector2(vecA.x - vecB.x, vecA.y - vecB.y);
+        }
+
+        /**
+         * Multiplies two vectors (value by value) and returns the result.
+         *
+         * @static
+         * @param {Vector2} vecA A vector.
+         * @param {Vector2} vecB A vector.
+         * @returns {Vector2} Returns the product of two vectors.
+         */
+
+    }, {
+        key: 'multiply',
+        value: function multiply(vecA, vecB) {
+            return new Vector2(vecA.x * vecB.x, vecA.y * vecB.y);
+        }
+
+        /**
+         * Multiplies two vectors (value by value) and returns the result.
+         *
+         * @static
+         * @param {Vector2} vec A vector.
+         * @param {Number} scalar A scalar.
+         * @returns {Vector2} Returns the product of two vectors.
+         */
+
+    }, {
+        key: 'multiplyScalar',
+        value: function multiplyScalar(vec, scalar) {
+            return new Vector2(vec.x, vec.y).multiplyScalar(scalar);
+        }
+
+        /**
+         * Returns the midpoint of a line spanned by two vectors.
+         *
+         * @static
+         * @param {Vector2} vecA A vector spanning the line.
+         * @param {Vector2} vecB A vector spanning the line.
+         * @returns {Vector2} The midpoint of the line spanned by two vectors.
+         */
+
+    }, {
+        key: 'midpoint',
+        value: function midpoint(vecA, vecB) {
+            return new Vector2((vecA.x + vecB.x) / 2, (vecA.y + vecB.y) / 2);
+        }
+
+        /**
+         * Returns the normals of a line spanned by two vectors.
+         *
+         * @static
+         * @param {Vector2} vecA A vector spanning the line.
+         * @param {Vector2} vecB A vector spanning the line.
+         * @returns {Vector2[]} An array containing the two normals, each represented by a vector.
+         */
+
+    }, {
+        key: 'normals',
+        value: function normals(vecA, vecB) {
+            var delta = Vector2.subtract(vecB, vecA);
+
+            return [new Vector2(-delta.y, delta.x), new Vector2(delta.y, -delta.x)];
+        }
+
+        /**
+         * Returns the unit (normalized normal) vectors of a line spanned by two vectors.
+         *
+         * @static
+         * @param {Vector2} vecA A vector spanning the line.
+         * @param {Vector2} vecB A vector spanning the line.
+         * @returns {Vector2[]} An array containing the two unit vectors.
+         */
+
+    }, {
+        key: 'units',
+        value: function units(vecA, vecB) {
+            var delta = Vector2.subtract(vecB, vecA);
+
+            return [new Vector2(-delta.y, delta.x).normalize(), new Vector2(delta.y, -delta.x).normalize()];
+        }
+
+        /**
+         * Divides a vector by another vector and returns the result as new vector.
+         *
+         * @static
+         * @param {Vector2} vecA The dividend.
+         * @param {Vector2} vecB The divisor.
+         * @returns {Vector2} The fraction of the two vectors.
+         */
+
+    }, {
+        key: 'divide',
+        value: function divide(vecA, vecB) {
+            return new Vector2(vecA.x / vecB.x, vecA.y / vecB.y);
+        }
+
+        /**
+         * Divides a vector by a scalar and returns the result as new vector.
+         *
+         * @static
+         * @param {Vector2} vecA The dividend.
+         * @param {Number} s The scalar.
+         * @returns {Vector2} The fraction of the two vectors.
+         */
+
+    }, {
+        key: 'divideScalar',
+        value: function divideScalar(vecA, s) {
+            return new Vector2(vecA.x / s, vecA.y / s);
+        }
+
+        /**
+         * Returns the dot product of two vectors.
+         *
+         * @static
+         * @param {Vector2} vecA A vector.
+         * @param {Vector2} vecB A vector.
+         * @returns {Number} The dot product of two vectors.
+         */
+
+    }, {
+        key: 'dot',
+        value: function dot(vecA, vecB) {
+            return vecA.x * vecB.x + vecA.y * vecB.y;
+        }
+
+        /**
+         * Returns the angle between two vectors.
+         *
+         * @static
+         * @param {Vector2} vecA A vector.
+         * @param {Vector2} vecB A vector.
+         * @returns {Number} The angle between two vectors in radians.
+         */
+
+    }, {
+        key: 'angle',
+        value: function angle(vecA, vecB) {
+            var dot = Vector2.dot(vecA, vecB);
+
+            return Math.acos(dot / (vecA.length() * vecB.length()));
+        }
+
+        /**
+         * Returns the angle between two vectors based on a third vector in between.
+         *
+         * @static
+         * @param {Vector2} vecA A vector.
+         * @param {Vector2} vecB A (central) vector.
+         * @param {Vector2} vecC A vector.
+         * @returns {Number} The angle in radians.
+         */
+
+    }, {
+        key: 'threePointangle',
+        value: function threePointangle(vecA, vecB, vecC) {
+            var ab = Vector2.subtract(vecB, vecA);
+            var bc = Vector2.subtract(vecC, vecB);
+            var abLength = vecA.distance(vecB);
+            var bcLength = vecB.distance(vecC);
+
+            return Math.acos(Vector2.dot(ab, bc) / (abLength * bcLength));
+        }
+
+        /**
+         * Returns the scalar projection of a vector on another vector.
+         *
+         * @static
+         * @param {Vector2} vecA The vector to be projected.
+         * @param {Vector2} vecB The vector to be projection upon.
+         * @returns {Number} The scalar component.
+         */
+
+    }, {
+        key: 'scalarProjection',
+        value: function scalarProjection(vecA, vecB) {
+            var unit = vecB.normalized();
+
+            return Vector2.dot(vecA, unit);
+        }
+
+        /**
+        * Returns the average vector (normalized) of the input vectors.
+        *
+        * @static
+        * @param {Array} vecs An array containing vectors.
+        * @returns {Vector2} The resulting vector (normalized).
+        */
+
+    }, {
+        key: 'averageDirection',
+        value: function averageDirection(vecs) {
+            var avg = new Vector2(0.0, 0.0);
+
+            for (var i = 0; i < vecs.length; i++) {
+                var vec = vecs[i];
+                avg.add(vec);
+            }
+
+            return avg.normalize();
+        }
+    }]);
+
+    return Vector2;
+}();
+
+module.exports = Vector2;
+
+},{}],15:[function(require,module,exports){
+'use strict';
+
+var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
+
+function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
+
+//@ts-check
+var MathHelper = require('./MathHelper');
+var ArrayHelper = require('./ArrayHelper');
+var Vector2 = require('./Vector2');
+var Atom = require('./Atom');
+
+/** 
+ * A class representing a vertex.
+ * 
+ * @property {Number} id The id of this vertex.
+ * @property {Atom} value The atom associated with this vertex.
+ * @property {Vector2} position The position of this vertex.
+ * @property {Vector2} previousPosition The position of the previous vertex.
+ * @property {Number|null} parentVertexId The id of the previous vertex.
+ * @property {Number[]} children The ids of the children of this vertex.
+ * @property {Number[]} spanningTreeChildren The ids of the children of this vertex as defined in the spanning tree defined by the SMILES.
+ * @property {Number[]} edges The ids of edges associated with this vertex.
+ * @property {Boolean} positioned A boolean indicating whether or not this vertex has been positioned.
+ * @property {Number} angle The angle of this vertex.
+ * @property {Number} dir The direction of this vertex.
+ * @property {Number} neighbourCount The number of neighbouring vertices.
+ * @property {Number[]} neighbours The vertex ids of neighbouring vertices.
+ * @property {String[]} neighbouringElements The element symbols associated with neighbouring vertices.
+ * @property {Boolean} forcePositioned A boolean indicating whether or not this vertex was positioned using a force-based approach.
+ */
+
+var Vertex = function () {
+  /**
+   * The constructor for the class Vertex.
+   *
+   * @param {Atom} value The value associated with this vertex.
+   * @param {Number} [x=0] The initial x coordinate of the positional vector of this vertex.
+   * @param {Number} [y=0] The initial y coordinate of the positional vector of this vertex.
+   */
+  function Vertex(value) {
+    var x = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
+    var y = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : 0;
+
+    _classCallCheck(this, Vertex);
+
+    this.id = null;
+    this.value = value;
+    this.position = new Vector2(x ? x : 0, y ? y : 0);
+    this.previousPosition = new Vector2(0, 0);
+    this.parentVertexId = null;
+    this.children = Array();
+    this.spanningTreeChildren = Array();
+    this.edges = Array();
+    this.positioned = false;
+    this.angle = null;
+    this.dir = 1.0;
+    this.neighbourCount = 0;
+    this.neighbours = Array();
+    this.neighbouringElements = Array();
+    this.forcePositioned = false;
+  }
+
+  /**
+   * Set the 2D coordinates of the vertex.
+   * 
+   * @param {Number} x The x component of the coordinates.
+   * @param {Number} y The y component of the coordinates.
+   * 
+   */
+
+
+  _createClass(Vertex, [{
+    key: 'setPosition',
+    value: function setPosition(x, y) {
+      this.position.x = x;
+      this.position.y = y;
+    }
+
+    /**
+     * Set the 2D coordinates of the vertex from a Vector2.
+     * 
+     * @param {Vector2} v A 2D vector.
+     * 
+     */
+
+  }, {
+    key: 'setPositionFromVector',
+    value: function setPositionFromVector(v) {
+      this.position.x = v.x;
+      this.position.y = v.y;
+    }
+
+    /**
+     * Add a child vertex id to this vertex.
+     * @param {Number} vertexId The id of a vertex to be added as a child to this vertex.
+     */
+
+  }, {
+    key: 'addChild',
+    value: function addChild(vertexId) {
+      this.children.push(vertexId);
+      this.neighbours.push(vertexId);
+
+      this.neighbourCount++;
+    }
+
+    /**
+     * Add a child vertex id to this vertex as the second child of the neighbours array,
+     * except this vertex is the first vertex of the SMILE string, then it is added as the first.
+     * This is used to get the correct ordering of neighbours for parity calculations.
+     * If a hydrogen is implicitly attached to the chiral center, insert as the third child.
+     * @param {Number} vertexId The id of a vertex to be added as a child to this vertex.
+     * @param {Number} ringbondIndex The index of the ringbond.
+     */
+
+  }, {
+    key: 'addRingbondChild',
+    value: function addRingbondChild(vertexId, ringbondIndex) {
+      this.children.push(vertexId);
+
+      if (this.value.bracket) {
+        var index = 1;
+
+        if (this.id === 0 && this.value.bracket.hcount === 0) {
+          index = 0;
+        }
+
+        if (this.value.bracket.hcount === 1 && ringbondIndex === 0) {
+          index = 2;
+        }
+
+        if (this.value.bracket.hcount === 1 && ringbondIndex === 1) {
+          if (this.neighbours.length < 3) {
+            index = 2;
+          } else {
+            index = 3;
+          }
+        }
+
+        if (this.value.bracket.hcount === null && ringbondIndex === 0) {
+          index = 1;
+        }
+
+        if (this.value.bracket.hcount === null && ringbondIndex === 1) {
+          if (this.neighbours.length < 3) {
+            index = 1;
+          } else {
+            index = 2;
+          }
+        }
+
+        this.neighbours.splice(index, 0, vertexId);
+      } else {
+        this.neighbours.push(vertexId);
+      }
+
+      this.neighbourCount++;
+    }
+
+    /**
+     * Set the vertex id of the parent.
+     * 
+     * @param {Number} parentVertexId The parents vertex id.
+     */
+
+  }, {
+    key: 'setParentVertexId',
+    value: function setParentVertexId(parentVertexId) {
+      this.neighbourCount++;
+      this.parentVertexId = parentVertexId;
+      this.neighbours.push(parentVertexId);
+    }
+
+    /**
+     * Returns true if this vertex is terminal (has no parent or child vertices), otherwise returns false. Always returns true if associated value has property hasAttachedPseudoElements set to true.
+     *
+     * @returns {Boolean} A boolean indicating whether or not this vertex is terminal.
+     */
+
+  }, {
+    key: 'isTerminal',
+    value: function isTerminal() {
+      if (this.value.hasAttachedPseudoElements) {
+        return true;
+      }
+
+      return this.parentVertexId === null && this.children.length < 2 || this.children.length === 0;
+    }
+
+    /**
+     * Clones this vertex and returns the clone.
+     *
+     * @returns {Vertex} A clone of this vertex.
+     */
+
+  }, {
+    key: 'clone',
+    value: function clone() {
+      var clone = new Vertex(this.value, this.position.x, this.position.y);
+      clone.id = this.id;
+      clone.previousPosition = new Vector2(this.previousPosition.x, this.previousPosition.y);
+      clone.parentVertexId = this.parentVertexId;
+      clone.children = ArrayHelper.clone(this.children);
+      clone.spanningTreeChildren = ArrayHelper.clone(this.spanningTreeChildren);
+      clone.edges = ArrayHelper.clone(this.edges);
+      clone.positioned = this.positioned;
+      clone.angle = this.angle;
+      clone.forcePositioned = this.forcePositioned;
+      return clone;
+    }
+
+    /**
+     * Returns true if this vertex and the supplied vertex both have the same id, else returns false.
+     *
+     * @param {Vertex} vertex The vertex to check.
+     * @returns {Boolean} A boolean indicating whether or not the two vertices have the same id.
+     */
+
+  }, {
+    key: 'equals',
+    value: function equals(vertex) {
+      return this.id === vertex.id;
+    }
+
+    /**
+     * Returns the angle of this vertexes positional vector. If a reference vector is supplied in relations to this vector, else in relations to the coordinate system.
+     *
+     * @param {Vector2} [referenceVector=null] - The reference vector.
+     * @param {Boolean} [returnAsDegrees=false] - If true, returns angle in degrees, else in radians.
+     * @returns {Number} The angle of this vertex.
+     */
+
+  }, {
+    key: 'getAngle',
+    value: function getAngle() {
+      var referenceVector = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+      var returnAsDegrees = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
+
+      var u = null;
+
+      if (!referenceVector) {
+        u = Vector2.subtract(this.position, this.previousPosition);
+      } else {
+        u = Vector2.subtract(this.position, referenceVector);
+      }
+
+      if (returnAsDegrees) {
+        return MathHelper.toDeg(u.angle());
+      }
+
+      return u.angle();
+    }
+
+    /**
+     * Returns the suggested text direction when text is added at the position of this vertex.
+     *
+     * @param {Vertex[]} vertices The array of vertices for the current molecule.
+     * @returns {String} The suggested direction of the text.
+     */
+
+  }, {
+    key: 'getTextDirection',
+    value: function getTextDirection(vertices) {
+      var neighbours = this.getDrawnNeighbours(vertices);
+      var angles = Array();
+
+      for (var i = 0; i < neighbours.length; i++) {
+        angles.push(this.getAngle(vertices[neighbours[i]].position));
+      }
+
+      var textAngle = MathHelper.meanAngle(angles);
+
+      // Round to 0, 90, 180 or 270 degree
+      var halfPi = Math.PI / 2.0;
+      textAngle = Math.round(Math.round(textAngle / halfPi) * halfPi);
+
+      if (textAngle === 2) {
+        return 'down';
+      } else if (textAngle === -2) {
+        return 'up';
+      } else if (textAngle === 0 || textAngle === -0) {
+        return 'right'; // is checking for -0 necessary?
+      } else if (textAngle === 3 || textAngle === -3) {
+        return 'left';
+      } else {
+        return 'down'; // default to down
+      }
+    }
+
+    /**
+     * Returns an array of ids of neighbouring vertices.
+     *
+     * @param {Number} [vertexId=null] If a value is supplied, the vertex with this id is excluded from the returned indices.
+     * @returns {Number[]} An array containing the ids of neighbouring vertices.
+     */
+
+  }, {
+    key: 'getNeighbours',
+    value: function getNeighbours() {
+      var vertexId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+
+      if (vertexId === null) {
+        return this.neighbours.slice();
+      }
+
+      var arr = Array();
+
+      for (var i = 0; i < this.neighbours.length; i++) {
+        if (this.neighbours[i] !== vertexId) {
+          arr.push(this.neighbours[i]);
+        }
+      }
+
+      return arr;
+    }
+
+    /**
+     * Returns an array of ids of neighbouring vertices that will be drawn (vertex.value.isDrawn === true).
+     * 
+     * @param {Vertex[]} vertices An array containing the vertices associated with the current molecule.
+     * @returns {Number[]} An array containing the ids of neighbouring vertices that will be drawn.
+     */
+
+  }, {
+    key: 'getDrawnNeighbours',
+    value: function getDrawnNeighbours(vertices) {
+      var arr = Array();
+
+      for (var i = 0; i < this.neighbours.length; i++) {
+        if (vertices[this.neighbours[i]].value.isDrawn) {
+          arr.push(this.neighbours[i]);
+        }
+      }
+
+      return arr;
+    }
+
+    /**
+     * Returns the number of neighbours of this vertex.
+     *
+     * @returns {Number} The number of neighbours.
+     */
+
+  }, {
+    key: 'getNeighbourCount',
+    value: function getNeighbourCount() {
+      return this.neighbourCount;
+    }
+
+    /**
+     * Returns a list of ids of vertices neighbouring this one in the original spanning tree, excluding the ringbond connections.
+     *
+     * @param {Number} [vertexId=null] If supplied, the vertex with this id is excluded from the array returned.
+     * @returns {Number[]} An array containing the ids of the neighbouring vertices.
+     */
+
+  }, {
+    key: 'getSpanningTreeNeighbours',
+    value: function getSpanningTreeNeighbours() {
+      var vertexId = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : null;
+
+      var neighbours = Array();
+
+      for (var i = 0; i < this.spanningTreeChildren.length; i++) {
+        if (vertexId === undefined || vertexId != this.spanningTreeChildren[i]) {
+          neighbours.push(this.spanningTreeChildren[i]);
+        }
+      }
+
+      if (this.parentVertexId != null) {
+        if (vertexId === undefined || vertexId != this.parentVertexId) {
+          neighbours.push(this.parentVertexId);
+        }
+      }
+
+      return neighbours;
+    }
+
+    /**
+     * Gets the next vertex in the ring in opposide direction to the supplied vertex id.
+     *
+     * @param {Vertex[]} vertices The array of vertices for the current molecule.
+     * @param {Number} ringId The id of the ring containing this vertex.
+     * @param {Number} previousVertexId The id of the previous vertex. The next vertex will be opposite from the vertex with this id as seen from this vertex.
+     * @returns {Number} The id of the next vertex in the ring.
+     */
+
+  }, {
+    key: 'getNextInRing',
+    value: function getNextInRing(vertices, ringId, previousVertexId) {
+      var neighbours = this.getNeighbours();
+
+      for (var i = 0; i < neighbours.length; i++) {
+        if (ArrayHelper.contains(vertices[neighbours[i]].value.rings, {
+          value: ringId
+        }) && neighbours[i] != previousVertexId) {
+          return neighbours[i];
+        }
+      }
+
+      return null;
+    }
+  }]);
+
+  return Vertex;
+}();
+
+module.exports = Vertex;
+
+},{"./ArrayHelper":2,"./Atom":3,"./MathHelper":9,"./Vector2":14}]},{},[1])
+
diff --git a/ippisite/ippidb/static/smilesdrawer/smiles-drawer.min.js b/ippisite/ippidb/static/smilesdrawer/smiles-drawer.min.js
index b365b33178a6ae1e477c4af11ca46b08b6dd38e5..e915a9c734e6c75e1712ab8152c102310c471eea 100644
--- a/ippisite/ippidb/static/smilesdrawer/smiles-drawer.min.js
+++ b/ippisite/ippidb/static/smilesdrawer/smiles-drawer.min.js
@@ -1,2 +1,2 @@
-(function d(g,e,t){function a(i,r){if(!e[i]){if(!g[i]){var o="function"==typeof require&&require;if(!r&&o)return o(i,!0);if(n)return n(i,!0);var s=new Error("Cannot find module '"+i+"'");throw s.code="MODULE_NOT_FOUND",s}var h=e[i]={exports:{}};g[i][0].call(h.exports,function(t){var e=g[i][1][t];return a(e?e:t)},h,h.exports,d,g,e,t)}return e[i].exports}for(var n="function"==typeof require&&require,i=0;i<t.length;i++)a(t[i]);return a})({1:[function(e){"use strict";function t(e){return e&&e.__esModule?e:{default:e}}var n=Math.min,i=Math.max,a=e("./src/Drawer"),r=t(a),o=e("./src/Parser"),l=t(o);window.SmilesDrawer={Version:"1.0.0"},window.SmilesDrawer.Drawer=r.default,window.SmilesDrawer.Parser=l.default,window.SmilesDrawer.clean=function(e){return e.replace(/[^A-Za-z0-9@\.\+\-\?!\(\)\[\]\{\}/\\=#\$:\*]/g,"")},window.SmilesDrawer.apply=function(e){for(var t=1<arguments.length&&arguments[1]!==void 0?arguments[1]:"canvas[data-smiles]",n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:"light",a=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null,o=new r.default(e),l=document.querySelectorAll(t),s=function(){var e=l[d];SmilesDrawer.parse(e.getAttribute("data-smiles"),function(t){o.draw(t,e,n,!1)},function(e){a&&a(e)})},d=0;d<l.length;d++)s()},window.SmilesDrawer.parse=function(e,t,n){try{t&&t(l.default.parse(e))}catch(e){n&&n(e)}},Array.prototype.fill||Object.defineProperty(Array.prototype,"fill",{value:function(e){if(null==this)throw new TypeError("this is null or not defined");for(var t=Object(this),a=t.length>>>0,r=arguments[1],o=r>>0,l=0>o?i(a+o,0):n(o,a),s=arguments[2],d=void 0===s?a:s>>0,g=0>d?i(a+d,0):n(d,a);l<g;)t[l]=e,l++;return t}})},{"./src/Drawer":5,"./src/Parser":10}],2:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=function(){function e(){i(this,e)}return r(e,null,[{key:"clone",value:function(t){var n=Array.isArray(t)?[]:{};for(var i in t){var r=t[i];n[i]="function"==typeof r.clone?r.clone():"object"===("undefined"==typeof r?"undefined":a(r))?e.clone(r):r}return n}},{key:"equals",value:function(e,t){if(e.length!==t.length)return!1;for(var n=e.slice().sort(),a=t.slice().sort(),r=0;r<n.length;r++)if(n[r]!==a[r])return!1;return!0}},{key:"print",value:function(e){if(0==e.length)return"";for(var t="(",n=0;n<e.length;n++)t+=e[n].id?e[n].id+", ":e[n]+", ";return t=t.substring(0,t.length-2),t+")"}},{key:"each",value:function(e,t){for(var n=0;n<e.length;n++)t(e[n])}},{key:"get",value:function(e,t,n){for(var a=0;a<e.length;a++)if(e[a][t]==n)return e[a]}},{key:"contains",value:function(e,t){if(!t.property&&!t.func){for(var n=0;n<e.length;n++)if(e[n]==t.value)return!0;}else if(t.func){for(var i=0;i<e.length;i++)if(t.func(e[i]))return!0;}else for(var a=0;a<e.length;a++)if(e[a][t.property]==t.value)return!0;return!1}},{key:"intersection",value:function(e,t){for(var n=[],a=0;a<e.length;a++)for(var i=0;i<t.length;i++)e[a]===t[i]&&n.push(e[a]);return n}},{key:"unique",value:function(e){var t={};return e.filter(function(e){return void 0===t[e]&&(t[e]=!0)})}},{key:"count",value:function(e,t){for(var n=0,a=0;a<e.length;a++)e[a]===t&&n++;return n}},{key:"toggle",value:function(e,t){for(var n=[],a=!1,r=0;r<e.length;r++)e[r]===t?a=!0:n.push(e[r]);return a||n.push(t),n}},{key:"remove",value:function(e,t){for(var n=[],a=0;a<e.length;a++)e[a]!==t&&n.push(e[a]);return n}},{key:"removeUnique",value:function(e,t){var n=e.indexOf(t);return-1<n&&e.splice(n,1),e}},{key:"removeAll",value:function(e,t){return e.filter(function(e){return-1===t.indexOf(e)})}},{key:"merge",value:function(e,t){for(var n=Array(e.length+t.length),a=0;a<e.length;a++)n[a]=e[a];for(var i=0;i<t.length;i++)n[e.length+i]=t[i];return n}},{key:"containsAll",value:function(e,t){for(var n=0,a=0;a<e.length;a++)for(var i=0;i<t.length;i++)e[a]===t[i]&&n++;return n===t.length}},{key:"sortByAtomicNumberDesc",value:function(t){var e=t.map(function(t,e){return{index:e,value:t.atomicNumber.split(".").map(Number)}});return e.sort(function(e,t){for(var n=Math.min(t.value.length,e.value.length),a=0;a<n&&t.value[a]===e.value[a];)a++;return a===n?t.value.length-e.value.length:t.value[a]-e.value[a]}),e.map(function(n){return t[n.index]})}},{key:"deepCopy",value:function(t){for(var n=[],a=0,i;a<t.length;a++)i=t[a],n[a]=i instanceof Array?e.deepCopy(i):i;return n}}]),e}();n.default=o},{}],3:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=e("./ArrayHelper"),l=i(o),s=e("./Vertex"),d=i(s),g=e("./Ring"),u=i(g),h=function(){function e(t){var n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:"-";a(this,e),this.element=1===t.length?t.toUpperCase():t,this.drawExplicit=!1,this.ringbonds=[],this.rings=[],this.bondType=n,this.branchBond=null,this.isBridge=!1,this.isBridgeNode=!1,this.originalRings=[],this.bridgedRing=null,this.anchoredRings=[],this.bracket=null,this.plane=0,this.attachedPseudoElements={},this.hasAttachedPseudoElements=!1,this.isDrawn=!0,this.isConnectedToRing=!1,this.neighbouringElements=[],this.isPartOfAromaticRing=t!==this.element,this.bondCount=0,this.chirality="",this.isStereoCenter=!1,this.priority=0,this.mainChain=!1,this.hydrogenDirection="down",this.subtreeDepth=1,this.hasHydrogen=!1}return r(e,[{key:"addNeighbouringElement",value:function(e){this.neighbouringElements.push(e)}},{key:"attachPseudoElement",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0,i=3<arguments.length&&void 0!==arguments[3]?arguments[3]:0;null===n&&(n=0),null===i&&(i=0);var a=n+e+i;this.attachedPseudoElements[a]?this.attachedPseudoElements[a].count+=1:this.attachedPseudoElements[a]={element:e,count:1,hydrogenCount:n,previousElement:t,charge:i},this.hasAttachedPseudoElements=!0}},{key:"getAttachedPseudoElements",value:function(){var e={},t=this;return Object.keys(this.attachedPseudoElements).sort().forEach(function(n){e[n]=t.attachedPseudoElements[n]}),e}},{key:"getAttachedPseudoElementsCount",value:function(){return Object.keys(this.attachedPseudoElements).length}},{key:"isHeteroAtom",value:function(){return"C"!==this.element&&"H"!==this.element}},{key:"addAnchoredRing",value:function(e){l.default.contains(this.anchoredRings,{value:e})||this.anchoredRings.push(e)}},{key:"getRingbondCount",value:function(){return this.ringbonds.length}},{key:"backupRings",value:function(){this.originalRings=Array(this.rings.length);for(var e=0;e<this.rings.length;e++)this.originalRings[e]=this.rings[e]}},{key:"restoreRings",value:function(){this.rings=Array(this.originalRings.length);for(var e=0;e<this.originalRings.length;e++)this.rings[e]=this.originalRings[e]}},{key:"haveCommonRingbond",value:function(e,t){for(var n=0;n<e.ringbonds.length;n++)for(var i=0;i<t.ringbonds.length;i++)if(e.ringbonds[n].id==t.ringbonds[i].id)return!0;return!1}},{key:"neighbouringElementsEqual",value:function(e){if(e.length!==this.neighbouringElements.length)return!1;e.sort(),this.neighbouringElements.sort();for(var t=0;t<this.neighbouringElements.length;t++)if(e[t]!==this.neighbouringElements[t])return!1;return!0}},{key:"getAtomicNumber",value:function(){return e.atomicNumbers[this.element]}},{key:"getMaxBonds",value:function(){return e.maxBonds[this.element]}}],[{key:"maxBonds",get:function(){return{C:4,N:3,O:2,P:3,S:2,B:3,F:1,I:1,Cl:1,Br:1}}},{key:"atomicNumbers",get:function(){return{H:1,He:2,Li:3,Be:4,B:5,b:5,C:6,c:6,N:7,n:7,O:8,o:8,F:9,Ne:10,Na:11,Mg:12,Al:13,Si:14,P:15,p:15,S:16,s:16,Cl:17,Ar:18,K:19,Ca:20,Sc:21,Ti:22,V:23,Cr:24,Mn:25,Fe:26,Co:27,Ni:28,Cu:29,Zn:30,Ga:31,Ge:32,As:33,Se:34,Br:35,Kr:36,Rb:37,Sr:38,Y:39,Zr:40,Nb:41,Mo:42,Tc:43,Ru:44,Rh:45,Pd:46,Ag:47,Cd:48,In:49,Sn:50,Sb:51,Te:52,I:53,Xe:54,Cs:55,Ba:56,La:57,Ce:58,Pr:59,Nd:60,Pm:61,Sm:62,Eu:63,Gd:64,Tb:65,Dy:66,Ho:67,Er:68,Tm:69,Yb:70,Lu:71,Hf:72,Ta:73,W:74,Re:75,Os:76,Ir:77,Pt:78,Au:79,Hg:80,Tl:81,Pb:82,Bi:83,Po:84,At:85,Rn:86,Fr:87,Ra:88,Ac:89,Th:90,Pa:91,U:92,Np:93,Pu:94,Am:95,Cm:96,Bk:97,Cf:98,Es:99,Fm:100,Md:101,No:102,Lr:103,Rf:104,Db:105,Sg:106,Bh:107,Hs:108,Mt:109,Ds:110,Rg:111,Cn:112,Uut:113,Uuq:114,Uup:115,Uuh:116,Uus:117,Uuo:118}}},{key:"mass",get:function(){return{H:1,He:2,Li:3,Be:4,B:5,b:5,C:6,c:6,N:7,n:7,O:8,o:8,F:9,Ne:10,Na:11,Mg:12,Al:13,Si:14,P:15,p:15,S:16,s:16,Cl:17,Ar:18,K:19,Ca:20,Sc:21,Ti:22,V:23,Cr:24,Mn:25,Fe:26,Co:27,Ni:28,Cu:29,Zn:30,Ga:31,Ge:32,As:33,Se:34,Br:35,Kr:36,Rb:37,Sr:38,Y:39,Zr:40,Nb:41,Mo:42,Tc:43,Ru:44,Rh:45,Pd:46,Ag:47,Cd:48,In:49,Sn:50,Sb:51,Te:52,I:53,Xe:54,Cs:55,Ba:56,La:57,Ce:58,Pr:59,Nd:60,Pm:61,Sm:62,Eu:63,Gd:64,Tb:65,Dy:66,Ho:67,Er:68,Tm:69,Yb:70,Lu:71,Hf:72,Ta:73,W:74,Re:75,Os:76,Ir:77,Pt:78,Au:79,Hg:80,Tl:81,Pb:82,Bi:83,Po:84,At:85,Rn:86,Fr:87,Ra:88,Ac:89,Th:90,Pa:91,U:92,Np:93,Pu:94,Am:95,Cm:96,Bk:97,Cf:98,Es:99,Fm:100,Md:101,No:102,Lr:103,Rf:104,Db:105,Sg:106,Bh:107,Hs:108,Mt:109,Ds:110,Rg:111,Cn:112,Uut:113,Uuq:114,Uup:115,Uuh:116,Uus:117,Uuo:118}}}]),e}();n.default=h},{"./ArrayHelper":2,"./Ring":11,"./Vertex":15}],4:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Number.MAX_VALUE;Object.defineProperty(n,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=e("./MathHelper"),s=i(l),d=e("./Vector2"),g=i(d),u=e("./Line"),h=i(u),c=e("./Vertex"),p=i(c),v=e("./Ring"),f=i(v),y=function(){function e(t,n,i){a(this,e),this.canvas="string"==typeof t||t instanceof String?document.getElementById(t):t,this.ctx=this.canvas.getContext("2d"),this.colors=n,this.opts=i,this.drawingWidth=0,this.drawingHeight=0,this.offsetX=0,this.offsetY=0,this.fontLarge=this.opts.fontSizeLarge+"pt Helvetica, Arial, sans-serif",this.fontSmall=this.opts.fontSizeSmall+"pt Helvetica, Arial, sans-serif",this.updateSize(this.opts.width,this.opts.height),this.ctx.font=this.fontLarge,this.hydrogenWidth=this.ctx.measureText("H").width,this.halfHydrogenWidth=this.hydrogenWidth/2,this.halfBondThickness=this.opts.bondThickness/2}return o(e,[{key:"updateSize",value:function(e,t){this.devicePixelRatio=window.devicePixelRatio||1,this.backingStoreRatio=this.ctx.webkitBackingStorePixelRatio||this.ctx.mozBackingStorePixelRatio||this.ctx.msBackingStorePixelRatio||this.ctx.oBackingStorePixelRatio||this.ctx.backingStorePixelRatio||1,this.ratio=this.devicePixelRatio/this.backingStoreRatio,1===this.ratio?(this.canvas.width=e*this.ratio,this.canvas.height=t*this.ratio):(this.canvas.width=e*this.ratio,this.canvas.height=t*this.ratio,this.canvas.style.width=e+"px",this.canvas.style.height=t+"px",this.ctx.setTransform(this.ratio,0,0,this.ratio,0,0))}},{key:"setTheme",value:function(e){this.colors=e}},{key:"scale",value:function(e){for(var t=-r,n=-r,a=r,o=r,l=0;l<e.length;l++)if(e[l].value.isDrawn){var i=e[l].position;t<i.x&&(t=i.x),n<i.y&&(n=i.y),a>i.x&&(a=i.x),o>i.y&&(o=i.y)}var s=20;t+=s,n+=s,a-=s,o-=s,this.drawingWidth=t-a,this.drawingHeight=n-o;var d=this.canvas.offsetWidth/this.drawingWidth,g=this.canvas.offsetHeight/this.drawingHeight,u=d<g?d:g;this.ctx.scale(u,u),this.offsetX=-a,this.offsetY=-o,d<g?this.offsetY+=this.canvas.offsetHeight/(2*u)-this.drawingHeight/2:this.offsetX+=this.canvas.offsetWidth/(2*u)-this.drawingWidth/2}},{key:"reset",value:function(){this.ctx.setTransform(1,0,0,1,0,0)}},{key:"getColor",value:function(e){return e=e.toUpperCase(),e in this.colors?this.colors[e]:this.colors.C}},{key:"drawCircle",value:function(e,t,n,i){var a=4<arguments.length&&void 0!==arguments[4]?arguments[4]:!0,r=5<arguments.length&&void 0!==arguments[5]&&arguments[5],o=6<arguments.length&&void 0!==arguments[6]?arguments[6]:"",l=this.ctx,d=this.offsetX,g=this.offsetY;l.save(),l.lineWidth=1.5,l.beginPath(),l.arc(e+d,t+g,n,0,s.default.twoPI,!0),l.closePath(),r?(a?(l.fillStyle="#f00",l.fill()):(l.strokeStyle="#f00",l.stroke()),this.drawDebugText(e,t,o)):a?(l.fillStyle=i,l.fill()):(l.strokeStyle=i,l.stroke()),l.restore()}},{key:"drawLine",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:1,i=this.ctx,a=this.offsetX,o=this.offsetY,s=e.clone().shorten(4),d=s.getLeftVector().clone(),l=s.getRightVector().clone();d.x+=a,d.y+=o,l.x+=a,l.y+=o,t||(i.save(),i.globalCompositeOperation="destination-out",i.beginPath(),i.moveTo(d.x,d.y),i.lineTo(l.x,l.y),i.lineCap="round",i.lineWidth=this.opts.bondThickness+1.2,i.strokeStyle=this.getColor("BACKGROUND"),i.stroke(),i.globalCompositeOperation="source-over",i.restore()),d=e.getLeftVector().clone(),l=e.getRightVector().clone(),d.x+=a,d.y+=o,l.x+=a,l.y+=o,i.save(),i.beginPath(),i.moveTo(d.x,d.y),i.lineTo(l.x,l.y),i.lineCap="round",i.lineWidth=this.opts.bondThickness;var r=this.ctx.createLinearGradient(d.x,d.y,l.x,l.y);r.addColorStop(0.4,this.getColor(e.getLeftElement())||this.getColor("C")),r.addColorStop(0.6,this.getColor(e.getRightElement())||this.getColor("C")),t&&(i.setLineDash([1,1]),i.lineWidth=this.opts.bondThickness),1>n&&(i.globalAlpha=n),i.strokeStyle=r,i.stroke(),i.restore()}},{key:"drawWedge",value:function(e){1<arguments.length&&void 0!==arguments[1]?arguments[1]:1;if(!(isNaN(e.from.x)||isNaN(e.from.y)||isNaN(e.to.x)||isNaN(e.to.y))){var n=this.ctx,i=this.offsetX,a=this.offsetY,o=e.clone().shorten(5),s=o.getLeftVector().clone(),l=o.getRightVector().clone();s.x+=i,s.y+=a,l.x+=i,l.y+=a,s=e.getLeftVector().clone(),l=e.getRightVector().clone(),s.x+=i,s.y+=a,l.x+=i,l.y+=a,n.save();var r=g.default.normals(s,l);r[0].normalize(),r[1].normalize();var d=e.getRightChiral(),h=s,c=l;d&&(h=l,c=s);var p=g.default.add(h,g.default.multiplyScalar(r[0],this.halfBondThickness)),t=g.default.add(c,g.default.multiplyScalar(r[0],1.5+this.halfBondThickness)),u=g.default.add(c,g.default.multiplyScalar(r[1],1.5+this.halfBondThickness)),v=g.default.add(h,g.default.multiplyScalar(r[1],this.halfBondThickness));n.beginPath(),n.moveTo(p.x,p.y),n.lineTo(t.x,t.y),n.lineTo(u.x,u.y),n.lineTo(v.x,v.y);var f=this.ctx.createRadialGradient(l.x,l.y,this.opts.bondLength,l.x,l.y,0);f.addColorStop(0.4,this.getColor(e.getLeftElement())||this.getColor("C")),f.addColorStop(0.6,this.getColor(e.getRightElement())||this.getColor("C")),n.fillStyle=f,n.fill(),n.restore()}}},{key:"drawDashedWedge",value:function(e){if(!(isNaN(e.from.x)||isNaN(e.from.y)||isNaN(e.to.x)||isNaN(e.to.y))){var n=this.ctx,i=this.offsetX,a=this.offsetY,o=e.getLeftVector().clone(),l=e.getRightVector().clone();o.x+=i,o.y+=a,l.x+=i,l.y+=a,n.save();var r=g.default.normals(o,l);r[0].normalize(),r[1].normalize();var s=e.getRightChiral(),d=e.clone(),u,h,c,p;s?(u=l,h=o,d.shortenRight(1),c=d.getRightVector().clone(),p=d.getLeftVector().clone()):(u=o,h=l,d.shortenLeft(1),c=d.getLeftVector().clone(),p=d.getRightVector().clone()),c.x+=i,c.y+=a,p.x+=i,p.y+=a;var v=g.default.subtract(h,u).normalize();n.strokeStyle=this.getColor("C"),n.lineCap="round",n.lineWidth=this.opts.bondThickness,n.beginPath();for(var f=e.getLength(),y=1.25/(f/(3*this.opts.bondThickness)),m=!1,b=0;1>b;b+=y){var t=g.default.multiplyScalar(v,b*f),k=g.default.add(u,t),x=1.5*b,C=g.default.multiplyScalar(r[0],x);!m&&0.5<b&&(n.stroke(),n.beginPath(),n.strokeStyle=this.getColor(e.getRightElement())||this.getColor("C"),m=!0),k.subtract(C),n.moveTo(k.x,k.y),k.add(g.default.multiplyScalar(C,2)),n.lineTo(k.x,k.y)}n.stroke(),n.restore()}}},{key:"drawDebugText",value:function(e,t,n){var i=this.ctx;i.save(),i.font="5px Droid Sans, sans-serif",i.textAlign="start",i.textBaseline="top",i.fillStyle="#ff0000",i.fillText(n,e+this.offsetX,t+this.offsetY),i.restore()}},{key:"drawBall",value:function(e,t,n){var i=this.ctx;i.save(),i.beginPath(),i.arc(e+this.offsetX,t+this.offsetY,this.opts.bondLength/4.5,0,s.default.twoPI,!1),i.fillStyle=this.getColor(n),i.fill(),i.restore()}},{key:"drawPoint",value:function(e,t,n){var i=this.ctx,a=this.offsetX,r=this.offsetY;i.save(),i.globalCompositeOperation="destination-out",i.beginPath(),i.arc(e+a,t+r,1.5,0,s.default.twoPI,!0),i.closePath(),i.fill(),i.globalCompositeOperation="source-over",i.beginPath(),i.arc(e+this.offsetX,t+this.offsetY,0.75,0,s.default.twoPI,!1),i.fillStyle=this.getColor(n),i.fill(),i.restore()}},{key:"drawText",value:function(e,t,n,i,a,o,l,d){var g=8<arguments.length&&void 0!==arguments[8]?arguments[8]:{},u=this.ctx,h=this.offsetX,c=this.offsetY;u.save(),u.textAlign="start",u.textBaseline="alphabetic";var p="",v=0;l&&(p=this.getChargeText(l),u.font=this.fontSmall,v=u.measureText(p).width);var f="0",y=0;0<d&&(f=d.toString(),u.font=this.fontSmall,y=u.measureText(f).width),1===l&&"N"===n&&g.hasOwnProperty("0O")&&g.hasOwnProperty("0O-1")&&(g={"0O":{element:"O",count:2,hydrogenCount:0,previousElement:"C",charge:""}},l=0),u.font=this.fontLarge,u.fillStyle=this.getColor("BACKGROUND");var m=u.measureText(n);m.totalWidth=m.width+v,m.height=parseInt(this.fontLarge,10);var b=m.width>this.opts.fontSizeLarge?m.width:this.opts.fontSizeLarge;b/=1.5,u.globalCompositeOperation="destination-out",u.beginPath(),u.arc(e+h,t+c,b,0,s.default.twoPI,!0),u.closePath(),u.fill(),u.globalCompositeOperation="source-over";var r=-m.width/2,k=-m.width/2;u.fillStyle=this.getColor(n),u.fillText(n,e+h+r,t+this.opts.halfFontSizeLarge+c),r+=m.width,l&&(u.font=this.fontSmall,u.fillText(p,e+h+r,t-this.opts.fifthFontSizeSmall+c),r+=v),0<d&&(u.font=this.fontSmall,u.fillText(f,e+h+k-y,t-this.opts.fifthFontSizeSmall+c),k-=y),u.font=this.fontLarge;var x=0,C=0;if(1===i){var S=e+h,R=t+c+this.opts.halfFontSizeLarge;x=this.hydrogenWidth,k-=x,"left"===a?S+=k:"right"===a?S+=r:"up"===a&&o?S+=r:"down"===a&&o?S+=r:"up"!==a||o?"down"===a&&!o&&(R+=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,S-=this.halfHydrogenWidth):(R-=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,S-=this.halfHydrogenWidth),u.fillText("H",S,R),r+=x}else if(1<i){var A=e+h,j=t+c+this.opts.halfFontSizeLarge;x=this.hydrogenWidth,u.font=this.fontSmall,C=u.measureText(i).width,k-=x+C,"left"===a?A+=k:"right"===a?A+=r:"up"===a&&o?A+=r:"down"===a&&o?A+=r:"up"!==a||o?"down"===a&&!o&&(j+=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,A-=this.halfHydrogenWidth):(j-=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,A-=this.halfHydrogenWidth),u.font=this.fontLarge,u.fillText("H",A,j),u.font=this.fontSmall,u.fillText(i,A+this.halfHydrogenWidth+C,j+this.opts.fifthFontSizeSmall),r+=x+this.halfHydrogenWidth+C}for(var T in g)if(g.hasOwnProperty(T)){var w=0,P=0,I=g[T].element,B=g[T].count,L=g[T].hydrogenCount,E=g[T].charge;u.font=this.fontLarge,1<B&&0<L&&(w=u.measureText("(").width,P=u.measureText(")").width);var D=u.measureText(I).width,N=0,_="",O=0;x=0,0<L&&(x=this.hydrogenWidth),u.font=this.fontSmall,1<B&&(N=u.measureText(B).width),0!==E&&(_=this.getChargeText(E),O=u.measureText(_).width),C=0,1<L&&(C=u.measureText(L).width),u.font=this.fontLarge;var V=e+h,z=t+c+this.opts.halfFontSizeLarge;u.fillStyle=this.getColor(I),0<B&&(k-=N),1<B&&0<L&&("left"===a?(k-=P,u.fillText(")",V+k,z)):(u.fillText("(",V+r,z),r+=w)),"left"===a?(k-=D,u.fillText(I,V+k,z)):(u.fillText(I,V+r,z),r+=D),0<L&&("left"===a?(k-=x+C,u.fillText("H",V+k,z),1<L&&(u.font=this.fontSmall,u.fillText(L,V+k+x,z+this.opts.fifthFontSizeSmall))):(u.fillText("H",V+r,z),r+=x,1<L&&(u.font=this.fontSmall,u.fillText(L,V+r,z+this.opts.fifthFontSizeSmall),r+=C))),u.font=this.fontLarge,1<B&&0<L&&("left"===a?(k-=w,u.fillText("(",V+k,z)):(u.fillText(")",V+r,z),r+=P)),u.font=this.fontSmall,1<B&&("left"===a?u.fillText(B,V+k+w+P+x+C+D,z+this.opts.fifthFontSizeSmall):(u.fillText(B,V+r,z+this.opts.fifthFontSizeSmall),r+=N)),0!==E&&("left"===a?u.fillText(_,V+k+w+P+x+C+D,t-this.opts.fifthFontSizeSmall+c):(u.fillText(_,V+r,t-this.opts.fifthFontSizeSmall+c),r+=O))}u.restore()}},{key:"getChargeText",value:function(e){return 1===e?"+":2===e?"2+":-1===e?"-":-2===e?"2-":""}},{key:"drawDebugPoint",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:"",i=3<arguments.length&&void 0!==arguments[3]?arguments[3]:"#f00";this.drawCircle(e,t,2,i,!0,!0,n)}},{key:"drawAromaticityRing",value:function(e){var t=this.ctx,n=s.default.apothemFromSideLength(this.opts.bondLength,e.getSize());t.save(),t.strokeStyle=this.getColor("C"),t.lineWidth=this.opts.bondThickness,t.beginPath(),t.arc(e.center.x+this.offsetX,e.center.y+this.offsetY,n-this.opts.bondSpacing,0,2*Math.PI,!0),t.closePath(),t.stroke(),t.restore()}},{key:"clear",value:function(){this.ctx.clearRect(0,0,this.canvas.offsetWidth,this.canvas.offsetHeight)}}]),e}();n.default=y},{"./Line":8,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],5:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var o=Math.PI,l=Math.min;Object.defineProperty(n,"__esModule",{value:!0});var s=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),d=e("./MathHelper"),g=i(d),u=e("./ArrayHelper"),h=i(u),c=e("./Vector2"),p=i(c),v=e("./Line"),f=i(v),y=e("./Vertex"),m=i(y),b=e("./Edge"),k=i(b),x=e("./Atom"),C=i(x),S=e("./Ring"),R=i(S),A=e("./RingConnection"),T=i(A),j=e("./CanvasWrapper"),w=i(j),P=e("./Graph"),I=i(P),B=e("./SSSR"),L=i(B),E=function(){function e(t){r(this,e),this.graph=null,this.doubleBondConfigCount=0,this.doubleBondConfig=null,this.ringIdCounter=0,this.ringConnectionIdCounter=0,this.canvasWrapper=null,this.totalOverlapScore=0,this.defaultOptions={width:500,height:500,bondThickness:0.6,bondLength:15,shortBondLength:0.85,bondSpacing:0.18*14.4,atomVisualization:"default",isomeric:!0,debug:!1,terminalCarbons:!1,explicitHydrogens:!1,overlapSensitivity:0.42,overlapResolutionIterations:1,compactDrawing:!0,fontSizeLarge:5,fontSizeSmall:3,themes:{dark:{C:"#fff",O:"#e74c3c",N:"#3498db",F:"#27ae60",CL:"#16a085",BR:"#d35400",I:"#8e44ad",P:"#d35400",S:"#f1c40f",B:"#e67e22",SI:"#e67e22",H:"#fff",BACKGROUND:"#141414"},light:{C:"#222",O:"#e74c3c",N:"#3498db",F:"#27ae60",CL:"#16a085",BR:"#d35400",I:"#8e44ad",P:"#d35400",S:"#f1c40f",B:"#e67e22",SI:"#e67e22",H:"#222",BACKGROUND:"#fff"}}},this.opts=this.extend(!0,this.defaultOptions,t),this.opts.halfBondSpacing=this.opts.bondSpacing/2,this.opts.bondLengthSq=this.opts.bondLength*this.opts.bondLength,this.opts.halfFontSizeLarge=this.opts.fontSizeLarge/2,this.opts.quarterFontSizeLarge=this.opts.fontSizeLarge/4,this.opts.fifthFontSizeSmall=this.opts.fontSizeSmall/5,this.theme=this.opts.themes.dark}return s(e,[{key:"extend",value:function(){var e=this,t={},n=!1,a=0,i=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(n=arguments[0],a++);for(var r=function(i){for(var a in i)Object.prototype.hasOwnProperty.call(i,a)&&(t[a]=n&&"[object Object]"===Object.prototype.toString.call(i[a])?e.extend(!0,t[a],i[a]):i[a])},o;a<i;a++)o=arguments[a],r(o);return t}},{key:"draw",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:"light",r=3<arguments.length&&void 0!==arguments[3]&&arguments[3];if(this.data=e,this.canvasWrapper=new w.default(t,this.opts.themes[n],this.opts),this.infoOnly=r,this.ringIdCounter=0,this.ringConnectionIdCounter=0,this.graph=new I.default(e,this.opts.isomeric),this.rings=[],this.ringConnections=[],this.originalRings=[],this.originalRingConnections=[],this.bridgedRing=!1,this.doubleBondConfigCount=null,this.doubleBondConfig=null,this.initRings(),this.initHydrogens(),!this.infoOnly){this.position(),this.restoreRingInformation(),this.resolvePrimaryOverlaps();var l=this.getOverlapScore();this.totalOverlapScore=this.getOverlapScore().total;for(var s=0;s<this.opts.overlapResolutionIterations;s++)for(var o=0,i;o<this.graph.edges.length;o++)if(i=this.graph.edges[o],this.isEdgeRotatable(i)){var d=this.graph.getTreeDepth(i.sourceId,i.targetId),u=this.graph.getTreeDepth(i.targetId,i.sourceId),h=i.targetId,a=i.sourceId;d>u&&(h=i.sourceId,a=i.targetId);var c=this.getSubtreeOverlapScore(a,h,l.vertexScores);if(c.value>this.opts.overlapSensitivity){var p=this.graph.vertices[h],v=this.graph.vertices[a],f=v.getNeighbours(h);if(1===f.length){var y=this.graph.vertices[f[0]],m=y.position.getRotateAwayFromAngle(p.position,v.position,g.default.toRad(120));this.rotateSubtree(y.id,v.id,m,v.position);var b=this.getOverlapScore().total;b>this.totalOverlapScore?this.rotateSubtree(y.id,v.id,-m,v.position):this.totalOverlapScore=b}else if(2===f.length){if(0!==v.value.rings.length&&0!==p.value.rings.length)continue;var k=this.graph.vertices[f[0]],x=this.graph.vertices[f[1]];if(1===k.value.rings.length&&1===x.value.rings.length){if(k.value.rings[0]!==x.value.rings[0])continue;}else if(0!==k.value.rings.length||0!==x.value.rings.length)continue;else{var C=k.position.getRotateAwayFromAngle(p.position,v.position,g.default.toRad(120)),S=x.position.getRotateAwayFromAngle(p.position,v.position,g.default.toRad(120));this.rotateSubtree(k.id,v.id,C,v.position),this.rotateSubtree(x.id,v.id,S,v.position);var R=this.getOverlapScore().total;R>this.totalOverlapScore?(this.rotateSubtree(k.id,v.id,-C,v.position),this.rotateSubtree(x.id,v.id,-S,v.position)):this.totalOverlapScore=R}}l=this.getOverlapScore()}}this.resolveSecondaryOverlaps(l.scores),this.opts.isomeric&&this.annotateStereochemistry(),this.opts.compactDrawing&&"default"===this.opts.atomVisualization&&this.initPseudoElements(),this.rotateDrawing(),this.canvasWrapper.scale(this.graph.vertices),this.drawEdges(this.opts.debug),this.drawVertices(this.opts.debug),this.canvasWrapper.reset()}}},{key:"edgeRingCount",value:function(e){var t=this.graph.edges[e],n=this.graph.vertices[t.sourceId],i=this.graph.vertices[t.targetId];return l(n.value.rings.length,i.value.rings.length)}},{key:"getBridgedRings",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isBridged&&e.push(this.rings[t]);return e}},{key:"getFusedRings",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isFused&&e.push(this.rings[t]);return e}},{key:"getSpiros",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isSpiro&&e.push(this.rings[t]);return e}},{key:"printRingInfo",value:function(){for(var e="",t=0,n;t<this.rings.length;t++)n=this.rings[t],e+=n.id+";",e+=n.members.length+";",e+=n.neighbours.length+";",e+=n.isSpiro?"true;":"false;",e+=n.isFused?"true;":"false;",e+=n.isBridged?"true;":"false;",e+=n.rings.length+";",e+="\n";return e}},{key:"rotateDrawing",value:function(){for(var e=0,t=0,n=0,a=0,i;a<this.graph.vertices.length;a++)if(i=this.graph.vertices[a],!!i.value.isDrawn)for(var r=a+1,o;r<this.graph.vertices.length;r++)if(o=this.graph.vertices[r],!!o.value.isDrawn){var l=i.position.distanceSq(o.position);l>n&&(n=l,e=a,t=r)}var s=-p.default.subtract(this.graph.vertices[e].position,this.graph.vertices[t].position).angle();if(!isNaN(s)){var d=s%0.523599;0.2617995>d?s-=d:s+=0.523599-d;for(var a=0;a<this.graph.vertices.length;a++)a!==t&&this.graph.vertices[a].position.rotateAround(s,this.graph.vertices[t].position);for(var a=0;a<this.rings.length;a++)this.rings[a].center.rotateAround(s,this.graph.vertices[t].position)}}},{key:"getTotalOverlapScore",value:function(){return this.totalOverlapScore}},{key:"getRingCount",value:function(){return this.rings.length}},{key:"hasBridgedRing",value:function(){return this.bridgedRing}},{key:"getHeavyAtomCount",value:function(){for(var e=0,t=0;t<this.graph.vertices.length;t++)"H"!==this.graph.vertices[t].value.element&&e++;return e}},{key:"getRingbondType",value:function(e,t){if(1>e.value.getRingbondCount()||1>t.value.getRingbondCount())return null;for(var n=0;n<e.value.ringbonds.length;n++)for(var i=0;i<t.value.ringbonds.length;i++)if(e.value.ringbonds[n].id===t.value.ringbonds[i].id)return"-"===e.value.ringbonds[n].bondType?t.value.ringbonds[i].bond:e.value.ringbonds[n].bond;return null}},{key:"initRings",value:function(){for(var e=new Map,t=this.graph.vertices.length-1,n;0<=t;t--)if(n=this.graph.vertices[t],0!==n.value.ringbonds.length)for(var i=0,r;i<n.value.ringbonds.length;i++)if(r=n.value.ringbonds[i].id,!e.has(r))e.set(r,n.id);else{var o=n.id,l=e.get(r),s=this.graph.addEdge(new k.default(o,l,1)),d=this.graph.vertices[l];n.addRingbondChild(l,i),n.value.addNeighbouringElement(d.value.element),d.addRingbondChild(o,i),d.value.addNeighbouringElement(n.value.element),n.edges.push(s),d.edges.push(s),e.delete(r)}var g=L.default.getRings(this.graph);if(null!==g){for(var t=0;t<g.length;t++)for(var u=[].concat(a(g[t])),h=this.addRing(new R.default(u)),i=0;i<u.length;i++)this.graph.vertices[u[i]].value.rings.push(h);for(var t=0;t<this.rings.length-1;t++)for(var i=t+1;i<this.rings.length;i++){var c=this.rings[t],p=this.rings[i],v=new T.default(c,p);0<v.vertices.size&&this.addRingConnection(v)}for(var t=0,f;t<this.rings.length;t++)f=this.rings[t],f.neighbours=T.default.getNeighbours(this.ringConnections,f.id);for(var t=0,y;t<this.rings.length;t++)y=this.rings[t],this.graph.vertices[y.members[0]].value.addAnchoredRing(y.id);for(this.backupRingInformation();0<this.rings.length;){for(var m=-1,t=0,b;t<this.rings.length;t++)b=this.rings[t],this.isPartOfBridgedRing(b.id)&&!b.isBridged&&(m=b.id);if(-1===m)break;var x=this.getRing(m),C=this.getBridgedRingRings(x.id);this.bridgedRing=!0,this.createBridgedRing(C,x.members[0]);for(var t=0;t<C.length;t++)this.removeRing(C[t])}}}},{key:"initHydrogens",value:function(){if(!this.opts.explicitHydrogens)for(var e=0,t;e<this.graph.vertices.length;e++)if(t=this.graph.vertices[e],"H"===t.value.element){var n=this.graph.vertices[t.neighbours[0]];n.value.hasHydrogen=!0,(!n.value.isStereoCenter||2>n.value.rings.length&&!n.value.bridgedRing||n.value.bridgedRing&&2>n.value.originalRings.length)&&(t.value.isDrawn=!1)}}},{key:"getBridgedRingRings",value:function(e){var t=[],a=this;return function e(o){var r=a.getRing(o);t.push(o);for(var l=0,i;l<r.neighbours.length;l++)i=r.neighbours[l],-1===t.indexOf(i)&&i!==o&&T.default.isBridge(a.ringConnections,a.graph.vertices,o,i)&&e(i)}(e),h.default.unique(t)}},{key:"isPartOfBridgedRing",value:function(e){for(var t=0;t<this.ringConnections.length;t++)if(this.ringConnections[t].containsRing(e)&&this.ringConnections[t].isBridge(this.graph.vertices))return!0;return!1}},{key:"createBridgedRing",value:function(e){for(var t=new Set,n=new Set,r=new Set,o=0,i;o<e.length;o++){i=this.getRing(e[o]),i.isPartOfBridged=!0;for(var l=0;l<i.members.length;l++)n.add(i.members[l]);for(var l=0,s;l<i.neighbours.length;l++)s=i.neighbours[l],-1===e.indexOf(s)&&r.add(i.neighbours[l])}var d=new Set,g=!0,u=!1,c;try{for(var p=n[Symbol.iterator](),v;!(g=(v=p.next()).done);g=!0){var f=v.value,y=this.graph.vertices[f],m=h.default.intersection(e,y.value.rings);1===y.value.rings.length||1===m.length?t.add(y.id):d.add(y.id)}}catch(e){u=!0,c=e}finally{try{!g&&p.return&&p.return()}finally{if(u)throw c}}var b=[],k=!0,x=!1,C;try{for(var S=d[Symbol.iterator](),A;!(k=(A=S.next()).done);k=!0){for(var j=A.value,T=this.graph.vertices[j],w=!1,P=0;P<T.edges.length;P++)1===this.edgeRingCount(T.edges[P])&&(w=!0);w?(T.value.isBridgeNode=!0,t.add(T.id)):(T.value.isBridge=!0,t.add(T.id))}}catch(e){x=!0,C=e}finally{try{!k&&S.return&&S.return()}finally{if(x)throw C}}var I=new R.default([].concat(a(t)));I.isBridged=!0,I.neighbours=[].concat(a(r));for(var o=0;o<e.length;o++)I.rings.push(this.getRing(e[o]).clone());this.addRing(I);for(var o=0;o<I.members.length;o++)this.graph.vertices[I.members[o]].value.bridgedRing=I.id;for(var o=0,B;o<b.length;o++)B=this.graph.vertices[b[o]],B.value.rings=[];var L=!0,E=!1,D;try{for(var N=t[Symbol.iterator](),_;!(L=(_=N.next()).done);L=!0){var O=_.value,V=this.graph.vertices[O];V.value.rings=h.default.removeAll(V.value.rings,e),V.value.rings.push(I.id)}}catch(e){E=!0,D=e}finally{try{!L&&N.return&&N.return()}finally{if(E)throw D}}for(var o=0;o<e.length;o++)for(var l=o+1;l<e.length;l++)this.removeRingConnectionsBetween(e[o],e[l]);var z=!0,H=!1,F;try{for(var W=r[Symbol.iterator](),M;!(z=(M=W.next()).done);z=!0){for(var q=M.value,U=this.getRingConnections(q,e),l=0;l<U.length;l++)this.getRingConnection(U[l]).updateOther(I.id,q);this.getRing(q).neighbours.push(I.id)}}catch(e){H=!0,F=e}finally{try{!z&&W.return&&W.return()}finally{if(H)throw F}}return I}},{key:"areVerticesInSameRing",value:function(e,t){for(var n=0;n<e.value.rings.length;n++)for(var i=0;i<t.value.rings.length;i++)if(e.value.rings[n]===t.value.rings[i])return!0;return!1}},{key:"getCommonRings",value:function(e,t){for(var n=[],a=0;a<e.value.rings.length;a++)for(var i=0;i<t.value.rings.length;i++)e.value.rings[a]==t.value.rings[i]&&n.push(e.value.rings[a]);return n}},{key:"getLargestOrAromaticCommonRing",value:function(e,t){for(var n=this.getCommonRings(e,t),a=0,r=null,o=0;o<n.length;o++){var i=this.getRing(n[o]),l=i.getSize();if(i.isBenzeneLike(this.graph.vertices))return i;l>a&&(a=l,r=i)}return r}},{key:"getVerticesAt",value:function(e,t,n){for(var a=[],r=0,i;r<this.graph.vertices.length;r++)if(i=this.graph.vertices[r],i.id!==n&&i.positioned){var o=e.distanceSq(i.position);o<=t*t&&a.push(i.id)}return a}},{key:"getClosestVertex",value:function(e){for(var t=99999,n=null,a=0,i;a<this.graph.vertices.length;a++)if(i=this.graph.vertices[a],i.id!==e.id){var r=e.position.distanceSq(i.position);r<t&&(t=r,n=i)}return n}},{key:"addRing",value:function(e){return e.id=this.ringIdCounter++,this.rings.push(e),e.id}},{key:"removeRing",value:function(e){this.rings=this.rings.filter(function(t){return t.id!==e}),this.ringConnections=this.ringConnections.filter(function(t){return t.firstRingId!==e&&t.secondRingId!==e});for(var t=0,n;t<this.rings.length;t++)n=this.rings[t],n.neighbours=n.neighbours.filter(function(t){return t!==e})}},{key:"getRing",value:function(e){for(var t=0;t<this.rings.length;t++)if(this.rings[t].id==e)return this.rings[t]}},{key:"addRingConnection",value:function(e){return e.id=this.ringConnectionIdCounter++,this.ringConnections.push(e),e.id}},{key:"removeRingConnection",value:function(e){this.ringConnections=this.ringConnections.filter(function(t){return t.id!==e})}},{key:"removeRingConnectionsBetween",value:function(e,t){for(var n=[],a=0,i;a<this.ringConnections.length;a++)i=this.ringConnections[a],(i.firstRingId===e&&i.secondRingId===t||i.firstRingId===t&&i.secondRingId===e)&&n.push(i.id);for(var a=0;a<n.length;a++)this.removeRingConnection(n[a])}},{key:"getRingConnection",value:function(e){for(var t=0;t<this.ringConnections.length;t++)if(this.ringConnections[t].id==e)return this.ringConnections[t]}},{key:"getRingConnections",value:function(e,t){for(var n=[],a=0,i;a<this.ringConnections.length;a++){i=this.ringConnections[a];for(var r=0,o;r<t.length;r++)o=t[r],(i.firstRingId===e&&i.secondRingId===o||i.firstRingId===o&&i.secondRingId===e)&&n.push(i.id)}return n}},{key:"getOverlapScore",value:function(){for(var e=0,t=new Float32Array(this.graph.vertices.length),n=0;n<this.graph.vertices.length;n++)t[n]=0;for(var n=0,r;n<this.graph.vertices.length;n++)for(r=this.graph.vertices.length;--r>n;){var o=this.graph.vertices[n],a=this.graph.vertices[r];if(o.value.isDrawn&&a.value.isDrawn){var l=p.default.subtract(o.position,a.position).lengthSq();if(l<this.opts.bondLengthSq){var s=(this.opts.bondLength-Math.sqrt(l))/this.opts.bondLength;e+=s,t[n]+=s,t[r]+=s}}}for(var d=[],n=0;n<this.graph.vertices.length;n++)d.push({id:n,score:t[n]});return d.sort(function(e,t){return t.score-e.score}),{total:e,scores:d,vertexScores:t}}},{key:"chooseSide",value:function(e,t,n){for(var a=e.getNeighbours(t.id),r=t.getNeighbours(e.id),o=a.length,l=r.length,s=h.default.merge(a,r),d=[0,0],g=0,i;g<s.length;g++)i=this.graph.vertices[s[g]].position,i.sameSideAs(e.position,t.position,n[0])?d[0]++:d[1]++;for(var u=[0,0],g=0,c;g<this.graph.vertices.length;g++)c=this.graph.vertices[g].position,c.sameSideAs(e.position,t.position,n[0])?u[0]++:u[1]++;return{totalSideCount:u,totalPosition:u[0]>u[1]?0:1,sideCount:d,position:d[0]>d[1]?0:1,anCount:o,bnCount:l}}},{key:"setRingCenter",value:function(e){for(var t=e.getSize(),n=new p.default(0,0),a=0;a<t;a++)n.add(this.graph.vertices[e.members[a]].position);e.center=n.divide(t)}},{key:"getSubringCenter",value:function(e,t){for(var n=t.value.originalRings,a=e.center,r=Number.MAX_VALUE,o=0;o<n.length;o++)for(var i=0;i<e.rings.length;i++)n[o]===e.rings[i].id&&e.rings[i].getSize()<r&&(a=e.rings[i].center,r=e.rings[i].getSize());return a}},{key:"drawEdges",value:function(e){var t=this,n=Array(this.graph.edges.length);if(n.fill(!1),this.graph.traverseBF(0,function(a){for(var r=t.graph.getEdges(a.id),o=0,i;o<r.length;o++)i=r[o],n[i]||(n[i]=!0,t.drawEdge(i,e))}),!this.bridgedRing)for(var a=0,i;a<this.rings.length;a++)i=this.rings[a],this.isRingAromatic(i)&&this.canvasWrapper.drawAromaticityRing(i)}},{key:"drawEdge",value:function(e,t){var n=this,i=this.graph.edges[e],r=this.graph.vertices[i.sourceId],o=this.graph.vertices[i.targetId],l=r.value.element,d=o.value.element;if(r.value.isDrawn&&o.value.isDrawn||"default"!==this.opts.atomVisualization){var g=r.position,a=o.position,u=this.getEdgeNormals(i),c=h.default.clone(u);if(c[0].multiplyScalar(10).add(g),c[1].multiplyScalar(10).add(g),"="===i.bondType||"="===this.getRingbondType(r,o)||i.isPartOfAromaticRing&&this.bridgedRing){var v=this.areVerticesInSameRing(r,o),y=this.chooseSide(r,o,c);if(v){var s=this.getLargestOrAromaticCommonRing(r,o),m=s.center;u[0].multiplyScalar(n.opts.bondSpacing),u[1].multiplyScalar(n.opts.bondSpacing);var b=null;b=m.sameSideAs(r.position,o.position,p.default.add(g,u[0]))?new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d):new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d),b.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),i.isPartOfAromaticRing?this.canvasWrapper.drawLine(b,!0):this.canvasWrapper.drawLine(b),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else if(i.center||r.isTerminal()&&o.isTerminal()){u[0].multiplyScalar(n.opts.halfBondSpacing),u[1].multiplyScalar(n.opts.halfBondSpacing);var k=new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d),x=new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d);this.canvasWrapper.drawLine(k),this.canvasWrapper.drawLine(x)}else if(0==y.anCount&&1<y.bnCount||0==y.bnCount&&1<y.anCount){u[0].multiplyScalar(n.opts.halfBondSpacing),u[1].multiplyScalar(n.opts.halfBondSpacing);var C=new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d),S=new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d);this.canvasWrapper.drawLine(C),this.canvasWrapper.drawLine(S)}else if(y.sideCount[0]>y.sideCount[1]){u[0].multiplyScalar(n.opts.bondSpacing),u[1].multiplyScalar(n.opts.bondSpacing);var R=new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d);R.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(R),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else if(y.sideCount[0]<y.sideCount[1]){u[0].multiplyScalar(n.opts.bondSpacing),u[1].multiplyScalar(n.opts.bondSpacing);var A=new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d);A.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(A),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else if(y.totalSideCount[0]>y.totalSideCount[1]){u[0].multiplyScalar(n.opts.bondSpacing),u[1].multiplyScalar(n.opts.bondSpacing);var j=new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d);j.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(j),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else if(y.totalSideCount[0]<=y.totalSideCount[1]){u[0].multiplyScalar(n.opts.bondSpacing),u[1].multiplyScalar(n.opts.bondSpacing);var T=new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d);T.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(T),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else;}else if("#"===i.bondType){u[0].multiplyScalar(n.opts.bondSpacing/1.5),u[1].multiplyScalar(n.opts.bondSpacing/1.5);var w=new f.default(p.default.add(g,u[0]),p.default.add(a,u[0]),l,d),P=new f.default(p.default.add(g,u[1]),p.default.add(a,u[1]),l,d);this.canvasWrapper.drawLine(w),this.canvasWrapper.drawLine(P),this.canvasWrapper.drawLine(new f.default(g,a,l,d))}else if("."===i.bondType);else{var I=r.value.isStereoCenter,B=o.value.isStereoCenter;"up"===i.wedge?this.canvasWrapper.drawWedge(new f.default(g,a,l,d,I,B)):"down"===i.wedge?this.canvasWrapper.drawDashedWedge(new f.default(g,a,l,d,I,B)):this.canvasWrapper.drawLine(new f.default(g,a,l,d,I,B))}if(t){var L=p.default.midpoint(g,a);this.canvasWrapper.drawDebugText(L.x,L.y,"e: "+e)}}}},{key:"drawVertices",value:function(e){for(var t=this.graph.vertices.length,t=0;t<this.graph.vertices.length;t++){var n=this.graph.vertices[t],i=n.value,r=0,l=0,s=this.getBondCount(n),d=i.element,g=C.default.maxBonds[d]-s,u=n.getTextDirection(this.graph.vertices),c=(this.opts.terminalCarbons||"C"!==d||i.hasAttachedPseudoElements)&&n.isTerminal(),v="C"===i.element;if(i.bracket&&(g=i.bracket.hcount,r=i.bracket.charge,l=i.bracket.isotope),"allballs"===this.opts.atomVisualization)this.canvasWrapper.drawBall(n.position.x,n.position.y,d);else if(i.isDrawn&&(!v||i.drawExplicit||c||i.hasAttachedPseudoElements)||1===this.graph.vertices.length)"default"===this.opts.atomVisualization?this.canvasWrapper.drawText(n.position.x,n.position.y,d,g,u,c,r,l,i.getAttachedPseudoElements()):"balls"===this.opts.atomVisualization&&this.canvasWrapper.drawBall(n.position.x,n.position.y,d);else if(2===n.getNeighbourCount()&&!0==n.forcePositioned){var f=this.graph.vertices[n.neighbours[0]].position,a=this.graph.vertices[n.neighbours[1]].position,y=p.default.threePointangle(n.position,f,a);0.1>Math.abs(o-y)&&this.canvasWrapper.drawPoint(n.position.x,n.position.y,d)}if(e){var m="v: "+n.id+" "+h.default.print(i.ringbonds);this.canvasWrapper.drawDebugText(n.position.x,n.position.y,m)}else;}if(this.opts.debug)for(var t=0,b;t<this.rings.length;t++)b=this.rings[t].center,this.canvasWrapper.drawDebugPoint(b.x,b.y,"r: "+this.rings[t].id)}},{key:"position",value:function(){for(var e=null,t=0;t<this.graph.vertices.length;t++)if(null!==this.graph.vertices[t].value.bridgedRing){e=this.graph.vertices[t];break}for(var t=0;t<this.rings.length;t++)this.rings[t].isBridged&&(e=this.graph.vertices[this.rings[t].members[0]]);0<this.rings.length&&null===e&&(e=this.graph.vertices[this.rings[0].members[0]]),null===e&&(e=this.graph.vertices[0]),this.createNextBond(e,null,0)}},{key:"backupRingInformation",value:function(){this.originalRings=[],this.originalRingConnections=[];for(var e=0;e<this.rings.length;e++)this.originalRings.push(this.rings[e]);for(var e=0;e<this.ringConnections.length;e++)this.originalRingConnections.push(this.ringConnections[e]);for(var e=0;e<this.graph.vertices.length;e++)this.graph.vertices[e].value.backupRings()}},{key:"restoreRingInformation",value:function(){var e=this.getBridgedRings();this.rings=[],this.ringConnections=[];for(var t=0,n;t<e.length;t++){n=e[t];for(var i=0,a;i<n.rings.length;i++)a=n.rings[i],this.originalRings[a.id].center=a.center}for(var t=0;t<this.originalRings.length;t++)this.rings.push(this.originalRings[t]);for(var t=0;t<this.originalRingConnections.length;t++)this.ringConnections.push(this.originalRingConnections[t]);for(var t=0;t<this.graph.vertices.length;t++)this.graph.vertices[t].value.restoreRings()}},{key:"createRing",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,o=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;if(!e.positioned){t=t?t:new p.default(0,0);var l=e.getOrderedNeighbours(this.ringConnections),s=n?p.default.subtract(n.position,t).angle():0,d=g.default.polyCircumradius(this.opts.bondLength,e.getSize()),u=g.default.centralAngle(e.getSize());e.centralAngle=u;var h=s,a=this,c=n?n.id:null;if(-1===e.members.indexOf(c)&&(n&&(n.positioned=!1),c=e.members[0]),e.isBridged){this.graph.kkLayout(e.members.slice(),t,n.id,e,this.opts.bondLength),e.positioned=!0,this.setRingCenter(e),t=e.center;for(var f=0;f<e.rings.length;f++)this.setRingCenter(e.rings[f])}else e.eachMember(this.graph.vertices,function(n){var i=a.graph.vertices[n];i.positioned||i.setPosition(t.x+Math.cos(h)*d,t.y+Math.sin(h)*d),h+=u,(!e.isBridged||3>e.rings.length)&&(i.angle=h,i.positioned=!0)},c,o?o.id:null);e.positioned=!0,e.center=t;for(var f=0,i;f<l.length;f++)if(i=this.getRing(l[f].neighbour),!i.positioned){var y=T.default.getVertices(this.ringConnections,e.id,i.id);if(2===y.length){e.isFused=!0,i.isFused=!0;var m=this.graph.vertices[y[0]],b=this.graph.vertices[y[1]],k=p.default.midpoint(m.position,b.position),x=p.default.normals(m.position,b.position);x[0].normalize(),x[1].normalize();var C=g.default.polyCircumradius(this.opts.bondLength,i.getSize()),r=g.default.apothem(C,i.getSize());x[0].multiplyScalar(r).add(k),x[1].multiplyScalar(r).add(k);var S=x[0];p.default.subtract(t,x[1]).lengthSq()>p.default.subtract(t,x[0]).lengthSq()&&(S=x[1]);var R=p.default.subtract(m.position,S),A=p.default.subtract(b.position,S);-1===R.clockwise(A)?!i.positioned&&this.createRing(i,S,m,b):!i.positioned&&this.createRing(i,S,b,m)}else if(1===y.length){e.isSpiro=!0,i.isSpiro=!0;var w=this.graph.vertices[y[0]],P=p.default.subtract(t,w.position);P.invert(),P.normalize();var I=g.default.polyCircumradius(this.opts.bondLength,i.getSize());P.multiplyScalar(I),P.add(w.position),i.positioned||this.createRing(i,P,w)}}for(var f=0;f<e.members.length;f++)for(var B=this.graph.vertices[e.members[f]],L=B.neighbours,E=0,j;E<L.length;E++)(j=this.graph.vertices[L[E]],!j.positioned)&&(j.value.isConnectedToRing=!0,this.createNextBond(j,B,0))}}},{key:"rotateSubtree",value:function(e,t,n,a){var r=this;this.graph.traverseTree(e,t,function(e){e.position.rotateAround(n,a);for(var t=0,i;t<e.value.anchoredRings.length;t++)i=r.rings[e.value.anchoredRings[t]],i&&i.center.rotateAround(n,a)})}},{key:"getSubtreeOverlapScore",value:function(e,t,n){var i=this,a=0,r=new p.default(0,0),o=0;return this.graph.traverseTree(e,t,function(e){if(e.value.isDrawn){var t=n[e.id];t>i.opts.overlapSensitivity&&(a+=t,o++);var l=i.graph.vertices[e.id].position.clone();l.multiplyScalar(t),r.add(l)}}),r.divide(a),{value:a/o,center:r}}},{key:"getCurrentCenterOfMass",value:function(){for(var e=new p.default(0,0),t=0,n=0,i;n<this.graph.vertices.length;n++)i=this.graph.vertices[n],i.positioned&&(e.add(i.position),t++);return e.divide(t)}},{key:"getCurrentCenterOfMassInNeigbourhood",value:function(e){for(var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:2*this.opts.bondLength,n=new p.default(0,0),a=0,r=0,i;r<this.graph.vertices.length;r++)i=this.graph.vertices[r],i.positioned&&e.distanceSq(i.position)<t*t&&(n.add(i.position),a++);return n.divide(a)}},{key:"resolvePrimaryOverlaps",value:function(){for(var e=[],t=Array(this.graph.vertices.length),n=0,i;n<this.rings.length;n++){i=this.rings[n];for(var r=0,l;r<i.members.length;r++)if(l=this.graph.vertices[i.members[r]],!t[l.id]){t[l.id]=!0;var s=this.getNonRingNeighbours(l.id);if(1<s.length){for(var d=[],g=0;g<l.value.rings.length;g++)d.push(l.value.rings[g]);e.push({common:l,rings:d,vertices:s})}else if(1===s.length&&2===l.value.rings.length){for(var u=[],g=0;g<l.value.rings.length;g++)u.push(l.value.rings[g]);e.push({common:l,rings:u,vertices:s})}}}for(var n=0,h;n<e.length;n++)if(h=e[n],2===h.vertices.length){var c=h.vertices[0],a=h.vertices[1];if(!c.value.isDrawn||!a.value.isDrawn)continue;var p=(2*o-this.getRing(h.rings[0]).getAngle())/6;this.rotateSubtree(c.id,h.common.id,p,h.common.position),this.rotateSubtree(a.id,h.common.id,-p,h.common.position);var v=this.getOverlapScore(),f=this.getSubtreeOverlapScore(c.id,h.common.id,v.vertexScores),y=this.getSubtreeOverlapScore(a.id,h.common.id,v.vertexScores),m=f.value+y.value;this.rotateSubtree(c.id,h.common.id,-2*p,h.common.position),this.rotateSubtree(a.id,h.common.id,2*p,h.common.position),v=this.getOverlapScore(),f=this.getSubtreeOverlapScore(c.id,h.common.id,v.vertexScores),y=this.getSubtreeOverlapScore(a.id,h.common.id,v.vertexScores),f.value+y.value>m&&(this.rotateSubtree(c.id,h.common.id,2*p,h.common.position),this.rotateSubtree(a.id,h.common.id,-2*p,h.common.position))}else 1!==h.vertices.length||2!==h.rings.length}},{key:"resolveSecondaryOverlaps",value:function(e){for(var t=0;t<e.length;t++)if(e[t].score>this.opts.overlapSensitivity){var n=this.graph.vertices[e[t].id];if(n.isTerminal()){var i=this.getClosestVertex(n);if(i){var a=null;a=i.isTerminal()?0===i.id?this.graph.vertices[1].position:i.previousPosition:0===i.id?this.graph.vertices[1].position:i.position;var r=0===n.id?this.graph.vertices[1].position:n.previousPosition;n.position.rotateAwayFrom(a,r,g.default.toRad(20))}}}}},{key:"getLastVertexWithAngle",value:function(e){for(var t=0,n=null;!t&&e;)n=this.graph.vertices[e],t=n.angle,e=n.parentVertexId;return n}},{key:"createNextBond",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0,o=3<arguments.length&&void 0!==arguments[3]&&arguments[3],d=4<arguments.length&&void 0!==arguments[4]&&arguments[4];if(!e.positioned||d){var u=!1;if(t){var c=this.graph.getEdge(e.id,t.id);("/"===c.bondType||"\\"===c.bondType)&&1==++this.doubleBondConfigCount%2&&null===this.doubleBondConfig&&(this.doubleBondConfig=c.bondType,u=!0,null===t.parentVertexId&&e.value.branchBond&&("/"===this.doubleBondConfig?this.doubleBondConfig="\\":"\\"===this.doubleBondConfig&&(this.doubleBondConfig="/")))}if(!d)if(!t){var f=new p.default(this.opts.bondLength,0);f.rotate(g.default.toRad(-60)),e.previousPosition=f,e.setPosition(this.opts.bondLength,0),e.angle=g.default.toRad(-60),null===e.value.bridgedRing&&(e.positioned=!0)}else if(0<t.value.rings.length){var m=t.neighbours,b=null,k=new p.default(0,0);if(null===t.value.bridgedRing&&1<t.value.rings.length)for(var C=0,i;C<m.length;C++)if(i=this.graph.vertices[m[C]],h.default.containsAll(i.value.rings,t.value.rings)){b=i;break}if(null===b){for(var C=0,S;C<m.length;C++)S=this.graph.vertices[m[C]],S.positioned&&this.areVerticesInSameRing(S,t)&&k.add(p.default.subtract(S.position,t.position));k.invert().normalize().multiplyScalar(this.opts.bondLength).add(t.position)}else k=b.position.clone().rotateAround(Math.PI,t.position);e.previousPosition=t.position,e.setPositionFromVector(k),e.positioned=!0}else{var v=new p.default(this.opts.bondLength,0);v.rotate(n),v.add(t.position),e.setPositionFromVector(v),e.previousPosition=t.position,e.positioned=!0}if(null!==e.value.bridgedRing){var R=this.getRing(e.value.bridgedRing);if(!R.positioned){var A=p.default.subtract(e.previousPosition,e.position);A.invert(),A.normalize();var j=g.default.polyCircumradius(this.opts.bondLength,R.members.length);A.multiplyScalar(j),A.add(e.position),this.createRing(R,A,e)}}else if(0<e.value.rings.length){var r=this.getRing(e.value.rings[0]);if(!r.positioned){var T=p.default.subtract(e.previousPosition,e.position);T.invert(),T.normalize();var P=g.default.polyCircumradius(this.opts.bondLength,r.getSize());T.multiplyScalar(P),T.add(e.position),this.createRing(r,T,e)}}else{for(var I=e.value.isStereoCenter,B=e.getNeighbours(),L=[],C=0;C<B.length;C++)this.graph.vertices[B[C]].value.isDrawn&&L.push(B[C]);t&&(L=h.default.remove(L,t.id));var E=e.getAngle();if(1===L.length){var D=this.graph.vertices[L[0]];if("#"===e.value.bondType||t&&"#"===t.value.bondType||"="===e.value.bondType&&t&&0===t.value.rings.length&&"="===t.value.bondType&&"-"!==e.value.branchBond){if(e.value.drawExplicit=!1,t){var N=this.graph.getEdge(e.id,t.id);N.center=!0}var _=this.graph.getEdge(e.id,D.id);_.center=!0,("#"===e.value.bondType||t&&"#"===t.value.bondType)&&(D.angle=0),D.drawExplicit=!0,this.createNextBond(D,e,E+D.angle)}else if(t&&0<t.value.rings.length){var O=g.default.toRad(60),V=-O,H=new p.default(this.opts.bondLength,0),F=new p.default(this.opts.bondLength,0);H.rotate(O).add(e.position),F.rotate(V).add(e.position);var W=this.getCurrentCenterOfMass(),M=H.distanceSq(W),q=F.distanceSq(W);D.angle=M<q?V:O,this.createNextBond(D,e,E+D.angle)}else{var U=e.angle;if(t&&3<t.neighbours.length)U=0<U?l(1.0472,U):0>U?Math.max(-1.0472,U):1.0472;else if(!U){var G=this.getLastVertexWithAngle(e.id);U=G.angle,U||(U=1.0472)}if(t&&!u){var X=this.graph.getEdge(e.id,D.id).bondType;"/"===X?("/"===this.doubleBondConfig||"\\"===this.doubleBondConfig&&(U=-U),this.doubleBondConfig=null):"\\"===X&&("/"===this.doubleBondConfig?U=-U:"\\"===this.doubleBondConfig,this.doubleBondConfig=null)}D.angle=o?U:-U,this.createNextBond(D,e,E+D.angle)}}else if(2===L.length){var Y=e.angle;Y||(Y=1.0472);var K=this.graph.getTreeDepth(L[0],e.id),Z=this.graph.getTreeDepth(L[1],e.id),$=this.graph.vertices[L[0]],Q=this.graph.vertices[L[1]];$.value.subtreeDepth=K,Q.value.subtreeDepth=Z;var J=this.graph.getTreeDepth(t?t.id:null,e.id);t&&(t.value.subtreeDepth=J);var ee=0,te=1;"C"===Q.value.element&&"C"!==$.value.element&&1<Z&&5>K?(ee=1,te=0):"C"!==Q.value.element&&"C"===$.value.element&&1<K&&5>Z?(ee=0,te=1):Z>K&&(ee=1,te=0);var ne=this.graph.vertices[L[ee]],ie=this.graph.vertices[L[te]],ae=this.graph.getEdge(e.id,ne.id),re=this.graph.getEdge(e.id,ie.id),oe=!1;J<K&&J<Z&&(oe=!0),ie.angle=Y,ne.angle=-Y,"\\"===this.doubleBondConfig?"\\"===ie.value.branchBond&&(ie.angle=-Y,ne.angle=Y):"/"===this.doubleBondConfig&&"/"===ie.value.branchBond&&(ie.angle=-Y,ne.angle=Y),this.createNextBond(ie,e,E+ie.angle,oe),this.createNextBond(ne,e,E+ne.angle,oe)}else if(3===L.length){var le=this.graph.getTreeDepth(L[0],e.id),se=this.graph.getTreeDepth(L[1],e.id),de=this.graph.getTreeDepth(L[2],e.id),ge=this.graph.vertices[L[0]],s=this.graph.vertices[L[1]],ue=this.graph.vertices[L[2]];ge.value.subtreeDepth=le,s.value.subtreeDepth=se,ue.value.subtreeDepth=de,se>le&&se>de?(ge=this.graph.vertices[L[1]],s=this.graph.vertices[L[0]],ue=this.graph.vertices[L[2]]):de>le&&de>se&&(ge=this.graph.vertices[L[2]],s=this.graph.vertices[L[0]],ue=this.graph.vertices[L[1]]),t&&1>t.value.rings.length&&1>ge.value.rings.length&&1>s.value.rings.length&&1>ue.value.rings.length&&1===this.graph.getTreeDepth(s.id,e.id)&&1===this.graph.getTreeDepth(ue.id,e.id)&&1<this.graph.getTreeDepth(ge.id,e.id)?(ge.angle=-e.angle,0<=e.angle?(s.angle=g.default.toRad(30),ue.angle=g.default.toRad(90)):(s.angle=-g.default.toRad(30),ue.angle=-g.default.toRad(90)),this.createNextBond(ge,e,E+ge.angle),this.createNextBond(s,e,E+s.angle),this.createNextBond(ue,e,E+ue.angle)):(ge.angle=0,s.angle=g.default.toRad(90),ue.angle=-g.default.toRad(90),this.createNextBond(ge,e,E+ge.angle),this.createNextBond(s,e,E+s.angle),this.createNextBond(ue,e,E+ue.angle))}else if(4===L.length){var he=this.graph.getTreeDepth(L[0],e.id),ce=this.graph.getTreeDepth(L[1],e.id),pe=this.graph.getTreeDepth(L[2],e.id),ve=this.graph.getTreeDepth(L[3],e.id),fe=this.graph.vertices[L[0]],w=this.graph.vertices[L[1]],x=this.graph.vertices[L[2]],y=this.graph.vertices[L[3]];fe.value.subtreeDepth=he,w.value.subtreeDepth=ce,x.value.subtreeDepth=pe,y.value.subtreeDepth=ve,ce>he&&ce>pe&&ce>ve?(fe=this.graph.vertices[L[1]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[2]],y=this.graph.vertices[L[3]]):pe>he&&pe>ce&&pe>ve?(fe=this.graph.vertices[L[2]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[1]],y=this.graph.vertices[L[3]]):ve>he&&ve>ce&&ve>pe&&(fe=this.graph.vertices[L[3]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[1]],y=this.graph.vertices[L[2]]),fe.angle=-g.default.toRad(36),w.angle=g.default.toRad(36),x.angle=-g.default.toRad(108),y.angle=g.default.toRad(108),this.createNextBond(fe,e,E+fe.angle),this.createNextBond(w,e,E+w.angle),this.createNextBond(x,e,E+x.angle),this.createNextBond(y,e,E+y.angle)}}}}},{key:"getCommonRingbondNeighbour",value:function(e){for(var t=e.neighbours,n=0,i;n<t.length;n++)if(i=this.graph.vertices[t[n]],h.default.containsAll(i.value.rings,e.value.rings))return i;return null}},{key:"isPointInRing",value:function(e){for(var t=0,n;t<this.rings.length;t++)if(n=this.rings[t],!!n.positioned){var i=g.default.polyCircumradius(this.opts.bondLength,n.getSize());if(e.distanceSq(n.center)<i*i)return!0}return!1}},{key:"isEdgeInRing",value:function(e){var t=this.graph.vertices[e.sourceId],n=this.graph.vertices[e.targetId];return this.areVerticesInSameRing(t,n)}},{key:"isEdgeRotatable",value:function(e){var t=this.graph.vertices[e.sourceId],n=this.graph.vertices[e.targetId];return"-"===e.bondType&&(t.isTerminal()||n.isTerminal()?!1:0<t.value.rings.length&&0<n.value.rings.length&&this.areVerticesInSameRing(t,n)?!1:!0)}},{key:"isRingAromatic",value:function(e){for(var t=0,n;t<e.members.length;t++)if(n=this.graph.vertices[e.members[t]],!n.value.isPartOfAromaticRing)return!1;return!0}},{key:"getEdgeNormals",value:function(e){var t=this.graph.vertices[e.sourceId].position,n=this.graph.vertices[e.targetId].position,i=p.default.units(t,n);return i}},{key:"getBondCount",value:function(e){for(var t=0,n=0;n<e.edges.length;n++)t+=this.graph.edges[e.edges[n]].weight;return t}},{key:"getNonRingNeighbours",value:function(e){for(var t=[],n=this.graph.vertices[e],a=n.neighbours,r=0;r<a.length;r++){var i=this.graph.vertices[a[r]],o=h.default.intersection(n.value.rings,i.value.rings).length;0===o&&!1==i.value.isBridge&&t.push(i)}return t}},{key:"annotateStereochemistry",value:function(){for(var e=0,t;e<this.graph.vertices.length;e++)if(t=this.graph.vertices[e],!!t.value.isStereoCenter){for(var n=t.getNeighbours(),i=n.length,a=Array(i),r=0;r<i;r++){var o=new Uint8Array(this.graph.vertices.length),s=[[]];o[t.id]=1,this.visitStereochemistry(n[r],t.id,o,s,10,0);for(var d=0;d<s.length;d++)s[d].sort(function(e,t){return t-e});a[r]=[r,s]}for(var u=0,h=0,r=0;r<a.length;r++){a[r][1].length>u&&(u=a[r][1].length);for(var d=0;d<a[r][1].length;d++)a[r][1][d].length>h&&(h=a[r][1][d].length)}for(var r=0,c;r<a.length;r++){c=u-a[r][1].length;for(var d=0;d<c;d++)a[r][1].push([]);a[r][1].push([n[r]]);for(var d=0,p;d<a[r][1].length;d++){p=h-a[r][1][d].length;for(var v=0;v<p;v++)a[r][1][d].push(0)}}a.sort(function(e,t){for(var n=0;n<e[1].length;n++)for(var i=0;i<e[1][n].length;i++){if(e[1][n][i]>t[1][n][i])return-1;if(e[1][n][i]<t[1][n][i])return 1}return 0});for(var l=new Uint8Array(i),r=0;r<i;r++)l[r]=a[r][0],t.value.priority=r;var f=this.graph.vertices[n[l[0]]].position,y=this.graph.vertices[n[l[1]]].position,m=this.graph.vertices[n[l[2]]].position,b=f.relativeClockwise(y,t.position),k=f.relativeClockwise(m,t.position),x=-1===b,C="@"===t.value.bracket.chirality?-1:1,S=1==g.default.parityOfPermutation(l)*C?"R":"S",R="down",A="up";(x&&"R"!=S||!x&&"S"!=S)&&(t.value.hydrogenDirection="up",R="up",A="down"),t.value.hasHydrogen&&(this.graph.getEdge(t.id,n[l[l.length-1]]).wedge=R);for(var j=Array(n.length-1),T=1<t.value.rings.length&&t.value.hasHydrogen,w=t.value.hasHydrogen?1:0,r=0;r<l.length-w;r++){j[r]=new Uint32Array(2);var P=this.graph.vertices[n[l[r]]];j[r][0]+=P.value.isStereoCenter?0:1e5,j[r][0]+=this.areVerticesInSameRing(P,t)?0:1e4,j[r][0]+=P.value.isHeteroAtom()?1e3:0,j[r][0]-=0===P.value.subtreeDepth?1e3:0,j[r][0]+=1e3-P.value.subtreeDepth,j[r][1]=n[l[r]]}if(j.sort(function(e,t){return e[0]>t[0]?-1:e[0]<t[0]?1:0}),!T){var I=j[0][1];if(t.value.hasHydrogen)this.graph.getEdge(t.id,I).wedge=A;else{for(var B=A,r=l.length-1;0<=r&&(B=B==R?A:R,n[l[r]]!==I);r--);this.graph.getEdge(t.id,I).wedge=B}}t.value.chirality=S}}},{key:"visitStereochemistry",value:function(e,t,n,a,r,o){var l=6<arguments.length&&void 0!==arguments[6]?arguments[6]:0;n[e]=1;var s=this.graph.vertices[e],d=s.value.getAtomicNumber();a.length<=o&&a.push([]);for(var g=0;g<this.graph.getEdge(e,t).weight;g++)a[o].push(1e3*l+d);for(var i=this.graph.vertices[e].neighbours,g=0;g<i.length;g++)1!==n[i[g]]&&o<r-1&&this.visitStereochemistry(i[g],e,n.slice(),a,r,o+1,d);if(o<r-1){for(var u=0,g=0;g<i.length;g++)u+=this.graph.getEdge(e,i[g]).weight;for(var g=0;g<s.value.getMaxBonds()-u;g++)a.length<=o+1&&a.push([]),a[o+1].push(1e3*d+1)}}},{key:"initPseudoElements",value:function(){for(var e=0;e<this.graph.vertices.length;e++){for(var t=this.graph.vertices[e],n=t.neighbours,i=Array(n.length),a=0;a<n.length;a++)i[a]=this.graph.vertices[n[a]];if(!(3>t.getNeighbourCount()||0<t.value.rings.length)&&"P"!==t.value.element&&("C"!==t.value.element||3!==i.length||"N"!==i[0].value.element||"N"!==i[1].value.element||"N"!==i[2].value.element)){for(var r=0,o=0,a=0;a<i.length;a++){var l=i[a],s=l.value.element,d=l.getNeighbourCount();"C"!==s&&"H"!==s&&1===d&&r++,1<d&&o++}if(!(1<o||2>r)){for(var g=null,a=0,u;a<i.length;a++)u=i[a],1<u.getNeighbourCount()&&(g=u);for(var a=0,h;a<i.length;a++)if(h=i[a],!(1<h.getNeighbourCount())){h.value.isDrawn=!1;var c=C.default.maxBonds[h.value.element]-this.getBondCount(h),p="";h.value.bracket&&(c=h.value.bracket.hcount,p=h.value.bracket.charge||0),t.value.attachPseudoElement(h.value.element,g?g.value.element:null,c,p)}}}}for(var e=0;e<this.graph.vertices.length;e++){var v=this.graph.vertices[e],f=v.value,y=f.element;if("C"!==y&&"H"!==y&&f.isDrawn){for(var m=v.neighbours,b=Array(m.length),a=0;a<m.length;a++)b[a]=this.graph.vertices[m[a]];for(var a=0,k;a<b.length;a++)if(k=b[a].value,k.hasAttachedPseudoElements&&2===k.getAttachedPseudoElementsCount()){var x=k.getAttachedPseudoElements();x.hasOwnProperty("0O")&&x.hasOwnProperty("3C")&&(k.isDrawn=!1,v.value.attachPseudoElement("Ac","",0))}}}}}]),e}();n.default=E},{"./ArrayHelper":2,"./Atom":3,"./CanvasWrapper":4,"./Edge":6,"./Graph":7,"./Line":8,"./MathHelper":9,"./Ring":11,"./RingConnection":12,"./SSSR":13,"./Vector2":14,"./Vertex":15}],6:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var a=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r=function(){function e(t,n){var a=2<arguments.length&&void 0!==arguments[2]?arguments[2]:1;i(this,e),this.id=null,this.sourceId=t,this.targetId=n,this.weight=a,this.bondType="-",this.isPartOfAromaticRing=!1,this.center=!1,this.wedge=""}return a(e,[{key:"setBondType",value:function(t){this.bondType=t,this.weight=e.bonds[t]}}],[{key:"bonds",get:function(){return{"-":1,"/":1,"\\":1,"=":2,"#":3,$:4}}}]),e}();n.default=r},{}],7:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.pow,o=Math.sqrt,l=Math.min;Object.defineProperty(n,"__esModule",{value:!0});var s=function(){function e(e,t){var n=[],i=!0,a=!1,r;try{for(var o=e[Symbol.iterator](),l;!(i=(l=o.next()).done)&&(n.push(l.value),!(t&&n.length===t));i=!0);}catch(e){a=!0,r=e}finally{try{!i&&o["return"]&&o["return"]()}finally{if(a)throw r}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),d=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),g=e("./MathHelper"),u=i(g),h=e("./Vector2"),c=i(h),p=e("./Vertex"),v=i(p),f=e("./Edge"),y=i(f),m=e("./Ring"),b=i(m),k=e("./Atom"),x=i(k),C=function(){function e(t){var n=1<arguments.length&&void 0!==arguments[1]&&arguments[1];a(this,e),this.vertices=[],this.edges=[],this.vertexIdsToEdgeId={},this.elementCount={},this.isomeric=n,this._time=0,this._init(t),this._initInfos()}return d(e,[{key:"_init",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,a=3<arguments.length&&void 0!==arguments[3]&&arguments[3],r=new x.default(e.atom.element?e.atom.element:e.atom,e.bond);r.branchBond=e.branchBond,r.ringbonds=e.ringbonds,r.bracket=e.atom.element?e.atom:null;var o=new v.default(r),l=this.vertices[n];if(this.addVertex(o),null!==n){o.setParentVertexId(n),o.value.addNeighbouringElement(l.value.element),l.addChild(o.id),l.value.addNeighbouringElement(r.element),l.spanningTreeChildren.push(o.id);var s=new y.default(n,o.id,1),d=null;a?(s.setBondType(o.value.branchBond||"-"),d=o.id):(s.setBondType(l.value.bondType||"-"),d=l.id);var g=this.addEdge(s);o.edges.push(g),l.edges.push(g)}var u=e.ringbondCount+1;r.bracket&&(u+=r.bracket.hcount);var h=0;if(r.bracket&&r.bracket.chirality){r.isStereoCenter=!0,h=r.bracket.hcount;for(var c=0;c<h;c++)this._init({atom:"H",isBracket:"false",branches:[],branchCount:0,ringbonds:[],ringbondCount:!1,next:null,hasNext:!1,bond:"-"},c,o.id,!0)}for(var c=0;c<e.branchCount;c++)this._init(e.branches[c],c+u,o.id,!0);e.hasNext&&this._init(e.next,e.branchCount+u,o.id)}},{key:"_initInfos",value:function(){for(var e=0,t;e<this.vertices.length;e++)t=this.vertices[e].value,"undefined"==typeof this.elementCount[t.element]?this.elementCount[t.element]=0:this.elementCount[t.element]+=1}},{key:"clear",value:function(){this.vertices=[],this.edges=[],this.vertexIdsToEdgeId={}}},{key:"addVertex",value:function(e){return e.id=this.vertices.length,this.vertices.push(e),e.id}},{key:"addEdge",value:function(e){return e.id=this.edges.length,this.edges.push(e),this.vertexIdsToEdgeId[e.sourceId+"_"+e.targetId]=e.id,this.vertexIdsToEdgeId[e.targetId+"_"+e.sourceId]=e.id,e.isPartOfAromaticRing=this.vertices[e.sourceId].value.isPartOfAromaticRing&&this.vertices[e.targetId].value.isPartOfAromaticRing,e.id}},{key:"getEdge",value:function(e,t){var n=this.vertexIdsToEdgeId[e+"_"+t];return void 0===n?null:this.edges[n]}},{key:"getEdges",value:function(e){for(var t=[],n=this.vertices[e],a=0;a<n.neighbours.length;a++)t.push(this.vertexIdsToEdgeId[e+"_"+n.neighbours[a]]);return t}},{key:"hasEdge",value:function(e,t){return void 0!==this.vertexIdsToEdgeId[e+"_"+t]}},{key:"getVertexList",value:function(){for(var e=[this.vertices.length],t=0;t<this.vertices.length;t++)e[t]=this.vertices[t].id;return e}},{key:"getEdgeList",value:function(){for(var e=Array(this.edges.length),t=0;t<this.edges.length;t++)e[t]=[this.edges[t].sourceId,this.edges[t].targetId];return e}},{key:"getAdjacencyMatrix",value:function(){for(var e=this.vertices.length,t=Array(e),n=0;n<e;n++)t[n]=Array(e),t[n].fill(0);for(var n=0,i;n<this.edges.length;n++)i=this.edges[n],t[i.sourceId][i.targetId]=1,t[i.targetId][i.sourceId]=1;return t}},{key:"getComponentsAdjacencyMatrix",value:function(){for(var e=this.vertices.length,t=Array(e),n=this.getBridges(),a=0;a<e;a++)t[a]=Array(e),t[a].fill(0);for(var a=0,i;a<this.edges.length;a++)i=this.edges[a],t[i.sourceId][i.targetId]=1,t[i.targetId][i.sourceId]=1;for(var a=0;a<n.length;a++)t[n[a][0]][n[a][1]]=0,t[n[a][1]][n[a][0]]=0;return t}},{key:"getSubgraphAdjacencyMatrix",value:function(e){for(var t=e.length,n=Array(t),a=0;a<t;a++){n[a]=Array(t),n[a].fill(0);for(var i=0;i<t;i++)a!==i&&this.hasEdge(e[a],e[i])&&(n[a][i]=1)}return n}},{key:"getDistanceMatrix",value:function(){for(var e=this.vertices.length,t=this.getAdjacencyMatrix(),n=Array(e),a=0;a<e;a++)n[a]=Array(e),n[a].fill(Infinity);for(var a=0;a<e;a++)for(var i=0;i<e;i++)1===t[a][i]&&(n[a][i]=1);for(var r=0;r<e;r++)for(var a=0;a<e;a++)for(var i=0;i<e;i++)n[a][i]>n[a][r]+n[r][i]&&(n[a][i]=n[a][r]+n[r][i]);return n}},{key:"getSubgraphDistanceMatrix",value:function(e){for(var t=e.length,n=this.getSubgraphAdjacencyMatrix(e),a=Array(t),r=0;r<t;r++)a[r]=Array(t),a[r].fill(Infinity);for(var r=0;r<t;r++)for(var i=0;i<t;i++)1===n[r][i]&&(a[r][i]=1);for(var o=0;o<t;o++)for(var r=0;r<t;r++)for(var i=0;i<t;i++)a[r][i]>a[r][o]+a[o][i]&&(a[r][i]=a[r][o]+a[o][i]);return a}},{key:"getAdjacencyList",value:function(){for(var e=this.vertices.length,t=Array(e),n=0;n<e;n++){t[n]=[];for(var i=0;i<e;i++)n!==i&&this.hasEdge(this.vertices[n].id,this.vertices[i].id)&&t[n].push(i)}return t}},{key:"getSubgraphAdjacencyList",value:function(e){for(var t=e.length,n=Array(t),a=0;a<t;a++){n[a]=[];for(var i=0;i<t;i++)a!==i&&this.hasEdge(e[a],e[i])&&n[a].push(i)}return n}},{key:"getBridges",value:function(){var e=this.vertices.length,t=Array(e),n=Array(e),a=Array(e),r=Array(e),o=this.getAdjacencyList(),l=[];t.fill(!1),r.fill(null),this._time=0;for(var s=0;s<e;s++)t[s]||this._bridgeDfs(s,t,n,a,r,o,l);return l}},{key:"traverseBF",value:function(e,t){var n=this.vertices.length,a=Array(n);a.fill(!1);for(var r=[e];0<r.length;){var o=r.shift(),l=this.vertices[o];t(l);for(var s=0,i;s<l.neighbours.length;s++)i=l.neighbours[s],a[i]||(a[i]=!0,r.push(i))}}},{key:"getTreeDepth",value:function(e,t){if(null===e||null===t)return 0;for(var n=this.vertices[e].getSpanningTreeNeighbours(t),a=0,r=0;r<n.length;r++){var i=n[r],o=this.getTreeDepth(i,e);o>a&&(a=o)}return a+1}},{key:"traverseTree",value:function(e,t,n){var a=3<arguments.length&&void 0!==arguments[3]?arguments[3]:Number.MAX_SAFE_INTEGER,r=4<arguments.length&&void 0!==arguments[4]&&arguments[4],o=5<arguments.length&&void 0!==arguments[5]?arguments[5]:1,l=6<arguments.length&&void 0!==arguments[6]?arguments[6]:null;if(null===l&&(l=new Uint8Array(this.vertices.length)),!(o>a+1||1===l[e])){l[e]=1;var s=this.vertices[e],d=s.getNeighbours(t);(!r||1<o)&&n(s);for(var g=0;g<d.length;g++)this.traverseTree(d[g],e,n,a,r,o+1,l)}}},{key:"kkLayout",value:function(e,t,n,l,d){for(var g=d,h=e.length;h--;)var i=this.vertices[e[h]],c=i.neighbours.length;var p=this.getSubgraphDistanceMatrix(e),v=e.length,f=u.default.polyCircumradius(500,v),y=u.default.centralAngle(v),m=0,a=new Float32Array(v),b=new Float32Array(v),k=Array(v);for(h=v;h--;){var x=this.vertices[e[h]];x.positioned?(a[h]=x.position.x,b[h]=x.position.y):(a[h]=t.x+Math.cos(m)*f,b[h]=t.y+Math.sin(m)*f),k[h]=x.positioned,m+=y}var C=Array(v);for(h=v;h--;){C[h]=Array(v);for(var c=v;c--;)C[h][c]=d*p[h][c]}var S=Array(v);for(h=v;h--;){S[h]=Array(v);for(var c=v;c--;)S[h][c]=g*r(p[h][c],-2)}var R=Array(v),A=new Float32Array(v),j=new Float32Array(v);for(h=v;h--;)R[h]=Array(v);h=v;for(var T,w,P,I,B,L,E;h--;){T=a[h],w=b[h],P=0,I=0;for(var D=v;D--;)h!==D&&(B=a[D],L=b[D],E=1/o((T-B)*(T-B)+(w-L)*(w-L)),R[h][D]=[S[h][D]*(T-B-C[h][D]*(T-B)*E),S[h][D]*(w-L-C[h][D]*(w-L)*E)],R[D][h]=R[h][D],P+=R[h][D][0],I+=R[h][D][1]);A[h]=P,j[h]=I}for(var N=function(e){return[A[e]*A[e]+j[e]*j[e],A[e],j[e]]},_=function(){var e=0,t=0,n=0,i=0;for(h=v;h--;){var a=N(h),r=s(a,3),o=r[0],l=r[1],d=r[2];o>e&&!1===k[h]&&(e=o,t=h,n=l,i=d)}return[t,e,n,i]},O=function(e,t,n){var i=0,s=0,d=0,g=a[e],u=b[e],c=C[e],p=S[e];for(h=v;h--;)if(h!==e){var f=a[h],y=b[h],x=c[h],l=p[h],k=(g-f)*(g-f),m=1/r(k+(u-y)*(u-y),1.5);i+=l*(1-x*(u-y)*(u-y)*m),s+=l*(1-x*k*m),d+=l*(x*(g-f)*(u-y)*m)}0==i&&(i=0.1),0==s&&(s=0.1),0==d&&(d=0.1);var T=t/i+n/d;T/=d/i-s/d;var w=-(d*T+t)/i;a[e]+=w,b[e]+=T;var P=R[e];t=0,n=0,g=a[e],u=b[e];var I,B,L,E,D;for(h=v;h--;)e!==h&&(I=a[h],B=b[h],L=P[h][0],E=P[h][1],D=1/o((g-I)*(g-I)+(u-B)*(u-B)),w=p[h]*(g-I-c[h]*(g-I)*D),T=p[h]*(u-B-c[h]*(u-B)*D),P[h]=[w,T],t+=w,n+=T,A[h]+=w-L,j[h]+=T-E);A[e]=t,j[e]=n},V=1e9,z=0,H=0,F=0,W=0,M=0,q=0;V>0.1&&2e3>M;){M++;var U=_(),G=s(U,4);for(z=G[0],V=G[1],H=G[2],F=G[3],W=V,q=0;W>0.1&&50>q;){q++,O(z,H,F);var X=N(z),Y=s(X,3);W=Y[0],H=Y[1],F=Y[2]}}for(h=v;h--;){var K=e[h],Z=this.vertices[K];Z.position.x=a[h],Z.position.y=b[h],Z.positioned=!0,Z.forcePositioned=!0}}},{key:"_bridgeDfs",value:function(e,t,n,a,r,o,s){t[e]=!0,n[e]=a[e]=++this._time;for(var d=0,i;d<o[e].length;d++)i=o[e][d],t[i]?i!==r[e]&&(a[e]=l(a[e],n[i])):(r[i]=e,this._bridgeDfs(i,t,n,a,r,o,s),a[e]=l(a[e],a[i]),a[i]>n[e]&&s.push([e,i]))}}],[{key:"getConnectedComponents",value:function(t){var n=t.length,i=Array(n),a=[],r=0;i.fill(!1);for(var o=0;o<n;o++)if(!i[o]){var l=[];i[o]=!0,l.push(o),r++,e._ccGetDfs(o,i,t,l),1<l.length&&a.push(l)}return a}},{key:"getConnectedComponentCount",value:function(t){var n=t.length,i=Array(n),a=0;i.fill(!1);for(var r=0;r<n;r++)i[r]||(i[r]=!0,a++,e._ccCountDfs(r,i,t));return a}},{key:"_ccCountDfs",value:function(t,n,i){for(var a=0,r;a<i[t].length;a++)(r=i[t][a],r&&!n[a]&&t!==a)&&(n[a]=!0,e._ccCountDfs(a,n,i))}},{key:"_ccGetDfs",value:function(t,n,i,a){for(var r=0,o;r<i[t].length;r++)(o=i[t][r],o&&!n[r]&&t!==r)&&(n[r]=!0,a.push(r),e._ccGetDfs(r,n,i,a))}}]),e}();n.default=C},{"./Atom":3,"./Edge":6,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],8:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=Math.pow;Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=e("./Vector2"),l=function(e){return e&&e.__esModule?e:{default:e}}(o),s=function(){function e(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:new l.default(0,0),n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:new l.default(0,0),a=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,o=4<arguments.length&&void 0!==arguments[4]&&arguments[4],s=5<arguments.length&&void 0!==arguments[5]&&arguments[5];i(this,e),this.from=t,this.to=n,this.elementFrom=a,this.elementTo=r,this.chiralFrom=o,this.chiralTo=s}return r(e,[{key:"clone",value:function(){return new e(this.from.clone(),this.to.clone(),this.elementFrom,this.elementTo)}},{key:"getLength",value:function(){return Math.sqrt(a(this.to.x-this.from.x,2)+a(this.to.y-this.from.y,2))}},{key:"getAngle",value:function(){var e=l.default.subtract(this.getRightVector(),this.getLeftVector());return e.angle()}},{key:"getRightVector",value:function(){return this.from.x<this.to.x?this.to:this.from}},{key:"getLeftVector",value:function(){return this.from.x<this.to.x?this.from:this.to}},{key:"getRightElement",value:function(){return this.from.x<this.to.x?this.elementTo:this.elementFrom}},{key:"getLeftElement",value:function(){return this.from.x<this.to.x?this.elementFrom:this.elementTo}},{key:"getRightChiral",value:function(){return this.from.x<this.to.x?this.chiralTo:this.chiralFrom}},{key:"getLeftChiral",value:function(){return this.from.x<this.to.x?this.chiralFrom:this.chiralTo}},{key:"setRightVector",value:function(e,t){return this.from.x<this.to.x?(this.to.x=e,this.to.y=t):(this.from.x=e,this.from.y=t),this}},{key:"setLeftVector",value:function(e,t){return this.from.x<this.to.x?(this.from.x=e,this.from.y=t):(this.to.x=e,this.to.y=t),this}},{key:"rotateToXAxis",value:function(){var e=this.getLeftVector();return this.setRightVector(e.x+this.getLength(),e.y),this}},{key:"rotate",value:function(e){var t=this.getLeftVector(),n=this.getRightVector(),i=Math.sin(e),a=Math.cos(e),r=a*(n.x-t.x)-i*(n.y-t.y)+t.x,o=i*(n.x-t.x)-a*(n.y-t.y)+t.y;return this.setRightVector(r,o),this}},{key:"shortenFrom",value:function(e){var t=l.default.subtract(this.to,this.from);return t.normalize(),t.multiplyScalar(e),this.from.add(t),this}},{key:"shortenTo",value:function(e){var t=l.default.subtract(this.from,this.to);return t.normalize(),t.multiplyScalar(e),this.to.add(t),this}},{key:"shortenRight",value:function(e){return this.from.x<this.to.x?this.shortenTo(e):this.shortenFrom(e),this}},{key:"shortenLeft",value:function(e){return this.from.x<this.to.x?this.shortenFrom(e):this.shortenTo(e),this}},{key:"shorten",value:function(e){var t=l.default.subtract(this.from,this.to);return t.normalize(),t.multiplyScalar(e/2),this.to.add(t),this.from.subtract(t),this}}]),e}();n.default=s},{"./Vector2":14}],9:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=Math.sin,o=Math.cos,l=Math.PI;Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),s=function(){function e(){i(this,e)}return r(e,null,[{key:"round",value:function(e,t){return t=t?t:1,+(Math.round(e+"e"+t)+"e-"+t)}},{key:"meanAngle",value:function(e){for(var t=0,n=0,r=0;r<e.length;r++)t+=a(e[r]),n+=o(e[r]);return Math.atan2(t/e.length,n/e.length)}},{key:"innerAngle",value:function(t){return e.toRad(180*(t-2)/t)}},{key:"polyCircumradius",value:function(e,t){return e/(2*a(l/t))}},{key:"apothem",value:function(e,t){return e*o(l/t)}},{key:"apothemFromSideLength",value:function(t,i){var n=e.polyCircumradius(t,i);return e.apothem(n,i)}},{key:"centralAngle",value:function(t){return e.toRad(360/t)}},{key:"toDeg",value:function(t){return t*e.degFactor}},{key:"toRad",value:function(t){return t*e.radFactor}},{key:"parityOfPermutation",value:function(e){for(var t=new Uint8Array(e.length),n=0,a=function n(a){var i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0;return 1===t[a]?i:(i++,t[a]=1,n(e[a],i))},r=0;r<e.length;r++)if(1!==t[r]){var i=a(r);n+=1-i%2}return n%2?-1:1}},{key:"radFactor",get:function(){return l/180}},{key:"degFactor",get:function(){return 180/l}},{key:"twoPI",get:function(){return 2*l}}]),e}();n.default=s},{}],10:[function(e,t,n){"use strict";Object.defineProperty(n,"__esModule",{value:!0}),n.default=function(){function e(t,n,i,a){this.message=t,this.expected=n,this.found=i,this.location=a,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e)}return function(e,t){function n(){this.constructor=e}n.prototype=t.prototype,e.prototype=new n}(e,Error),{SyntaxError:e,parse:function(t){function n(e){var n=tt[e],i,a;if(n)return n;for(i=e-1;!tt[i];)i--;for(n=tt[i],n={line:n.line,column:n.column,seenCR:n.seenCR};i<e;)a=t.charAt(i),"\n"===a?(!n.seenCR&&n.line++,n.column=1,n.seenCR=!1):"\r"===a||"\u2028"===a||"\u2029"===a?(n.line++,n.column=1,n.seenCR=!0):(n.column++,n.seenCR=!1),i++;return tt[e]=n,n}function i(e,t){var i=n(e),a=n(t);return{start:{offset:e,line:i.line,column:i.column},end:{offset:t,line:a.line,column:a.column}}}function a(e){Je<nt||(Je>nt&&(nt=Je,it=[]),it.push(e))}function r(){var e,t,n,i,a,d,g,u,h,c;if(e=Je,t=Je,n=l(),n!==R){for(i=[],a=o();a!==R;)i.push(a),a=o();if(i!==R){for(a=[],d=Je,g=s(),g===R&&(g=null),g===R?(Je=d,d=R):(u=p(),u===R?(Je=d,d=R):(g=[g,u],d=g));d!==R;)a.push(d),d=Je,g=s(),g===R&&(g=null),g===R?(Je=d,d=R):(u=p(),u===R?(Je=d,d=R):(g=[g,u],d=g));if(a!==R){for(d=[],g=o();g!==R;)d.push(g),g=o();if(d===R)Je=t,t=R;else if(g=s(),g===R&&(g=null),g===R)Je=t,t=R;else if(u=r(),u===R&&(u=null),u!==R){for(h=[],c=o();c!==R;)h.push(c),c=o();h===R?(Je=t,t=R):(n=[n,i,a,d,g,u,h],t=n)}else Je=t,t=R}else Je=t,t=R}else Je=t,t=R}else Je=t,t=R;return t!==R&&(et=e,t=T(t)),e=t,e}function o(){var e,n,i,o,l,d;return e=Je,n=Je,40===t.charCodeAt(Je)?(i=w,Je++):(i=R,a(P)),i===R?(Je=n,n=R):(o=s(),o===R&&(o=null),o===R?(Je=n,n=R):(l=r(),l===R?(Je=n,n=R):(41===t.charCodeAt(Je)?(d=I,Je++):(d=R,a(B)),d===R?(Je=n,n=R):(i=[i,o,l,d],n=i)))),n!==R&&(et=e,n=L(n)),e=n,e}function l(){var e,t;return e=Je,t=g(),t===R&&(t=u(),t===R&&(t=d(),t===R&&(t=h()))),t!==R&&(et=e,t=E(t)),e=t,e}function s(){var e,n;return e=Je,D.test(t.charAt(Je))?(n=t.charAt(Je),Je++):(n=R,a(N)),n!==R&&(et=e,n=_(n)),e=n,e}function d(){var e,n,i,r,o,l,s,d,g,p;return e=Je,n=Je,91===t.charCodeAt(Je)?(i=O,Je++):(i=R,a(V)),i===R?(Je=n,n=R):(r=x(),r===R&&(r=null),r===R?(Je=n,n=R):(t.substr(Je,2)===z?(o=z,Je+=2):(o=R,a(H)),o===R&&(t.substr(Je,2)===F?(o=F,Je+=2):(o=R,a(W)),o===R&&(o=u(),o===R&&(o=c(),o===R&&(o=h())))),o===R?(Je=n,n=R):(l=v(),l===R&&(l=null),l===R?(Je=n,n=R):(s=b(),s===R&&(s=null),s===R?(Je=n,n=R):(d=f(),d===R&&(d=null),d===R?(Je=n,n=R):(g=k(),g===R&&(g=null),g===R?(Je=n,n=R):(93===t.charCodeAt(Je)?(p=M,Je++):(p=R,a(q)),p===R?(Je=n,n=R):(i=[i,r,o,l,s,d,g,p],n=i)))))))),n!==R&&(et=e,n=U(n)),e=n,e}function g(){var e,n,i,r;return e=Je,n=Je,66===t.charCodeAt(Je)?(i=G,Je++):(i=R,a(X)),i===R?(Je=n,n=R):(114===t.charCodeAt(Je)?(r=Y,Je++):(r=R,a(K)),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n===R&&(n=Je,67===t.charCodeAt(Je)?(i=Z,Je++):(i=R,a($)),i===R?(Je=n,n=R):(108===t.charCodeAt(Je)?(r=Q,Je++):(r=R,a(J)),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n===R&&(ee.test(t.charAt(Je))?(n=t.charAt(Je),Je++):(n=R,a(te)))),n!==R&&(et=e,n=ne(n)),e=n,e}function u(){var e,n;return e=Je,ie.test(t.charAt(Je))?(n=t.charAt(Je),Je++):(n=R,a(ae)),n!==R&&(et=e,n=E(n)),e=n,e}function h(){var e,n;return e=Je,42===t.charCodeAt(Je)?(n=re,Je++):(n=R,a(oe)),n!==R&&(et=e,n=le(n)),e=n,e}function c(){var e,n,i,r;return e=Je,n=Je,se.test(t.charAt(Je))?(i=t.charAt(Je),Je++):(i=R,a(de)),i===R?(Je=n,n=R):(ge.test(t.charAt(Je))?(r=t.charAt(Je),Je++):(r=R,a(ue)),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n!==R&&(et=e,n=he(n)),e=n,e}function p(){var e,n,i,r,o;return e=Je,n=Je,37===t.charCodeAt(Je)?(i=ce,Je++):(i=R,a(pe)),i===R?(Je=n,n=R):(ve.test(t.charAt(Je))?(r=t.charAt(Je),Je++):(r=R,a(fe)),r===R?(Je=n,n=R):(ye.test(t.charAt(Je))?(o=t.charAt(Je),Je++):(o=R,a(me)),o===R?(Je=n,n=R):(i=[i,r,o],n=i))),n===R&&(ye.test(t.charAt(Je))?(n=t.charAt(Je),Je++):(n=R,a(me))),n!==R&&(et=e,n=be(n)),e=n,e}function v(){var e,n,i,r,o,l,s;return e=Je,n=Je,64===t.charCodeAt(Je)?(i=ke,Je++):(i=R,a(xe)),i===R?(Je=n,n=R):(64===t.charCodeAt(Je)?(r=ke,Je++):(r=R,a(xe)),r===R&&(r=Je,t.substr(Je,2)===Ce?(o=Ce,Je+=2):(o=R,a(Se)),o===R?(Je=r,r=R):(Re.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(Ae)),l===R?(Je=r,r=R):(o=[o,l],r=o)),r===R&&(r=Je,t.substr(Je,2)===je?(o=je,Je+=2):(o=R,a(Te)),o===R?(Je=r,r=R):(Re.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(Ae)),l===R?(Je=r,r=R):(o=[o,l],r=o)),r===R&&(r=Je,t.substr(Je,2)===we?(o=we,Je+=2):(o=R,a(Pe)),o===R?(Je=r,r=R):(Ie.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(Be)),l===R?(Je=r,r=R):(o=[o,l],r=o)),r===R&&(r=Je,t.substr(Je,2)===Le?(o=Le,Je+=2):(o=R,a(Ee)),o===R?(Je=r,r=R):(ve.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(fe)),l===R?(Je=r,r=R):(ye.test(t.charAt(Je))?(s=t.charAt(Je),Je++):(s=R,a(me)),s===R&&(s=null),s===R?(Je=r,r=R):(o=[o,l,s],r=o))),r===R&&(r=Je,t.substr(Je,2)===De?(o=De,Je+=2):(o=R,a(Ne)),o===R?(Je=r,r=R):(ve.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(fe)),l===R?(Je=r,r=R):(ye.test(t.charAt(Je))?(s=t.charAt(Je),Je++):(s=R,a(me)),s===R&&(s=null),s===R?(Je=r,r=R):(o=[o,l,s],r=o)))))))),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n!==R&&(et=e,n=_e(n)),e=n,e}function f(){var e,t;return e=Je,t=y(),t===R&&(t=m()),t!==R&&(et=e,t=Oe(t)),e=t,e}function y(){var e,n,i,r,o,l;return e=Je,n=Je,43===t.charCodeAt(Je)?(i=Ve,Je++):(i=R,a(ze)),i===R?(Je=n,n=R):(43===t.charCodeAt(Je)?(r=Ve,Je++):(r=R,a(ze)),r===R&&(r=Je,ve.test(t.charAt(Je))?(o=t.charAt(Je),Je++):(o=R,a(fe)),o===R?(Je=r,r=R):(ye.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(me)),l===R&&(l=null),l===R?(Je=r,r=R):(o=[o,l],r=o))),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n!==R&&(et=e,n=He(n)),e=n,e}function m(){var e,n,i,r,o,l;return e=Je,n=Je,45===t.charCodeAt(Je)?(i=Fe,Je++):(i=R,a(We)),i===R?(Je=n,n=R):(45===t.charCodeAt(Je)?(r=Fe,Je++):(r=R,a(We)),r===R&&(r=Je,ve.test(t.charAt(Je))?(o=t.charAt(Je),Je++):(o=R,a(fe)),o===R?(Je=r,r=R):(ye.test(t.charAt(Je))?(l=t.charAt(Je),Je++):(l=R,a(me)),l===R&&(l=null),l===R?(Je=r,r=R):(o=[o,l],r=o))),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n!==R&&(et=e,n=Me(n)),e=n,e}function b(){var e,n,i,r;return e=Je,n=Je,72===t.charCodeAt(Je)?(i=qe,Je++):(i=R,a(Ue)),i===R?(Je=n,n=R):(ye.test(t.charAt(Je))?(r=t.charAt(Je),Je++):(r=R,a(me)),r===R&&(r=null),r===R?(Je=n,n=R):(i=[i,r],n=i)),n!==R&&(et=e,n=Ge(n)),e=n,e}function k(){var e,n,i,r,o,l,s;if(e=Je,n=Je,58===t.charCodeAt(Je)?(i=Xe,Je++):(i=R,a(Ye)),i!==R){if(r=Je,ve.test(t.charAt(Je))?(o=t.charAt(Je),Je++):(o=R,a(fe)),o!==R){for(l=[],ye.test(t.charAt(Je))?(s=t.charAt(Je),Je++):(s=R,a(me));s!==R;)l.push(s),ye.test(t.charAt(Je))?(s=t.charAt(Je),Je++):(s=R,a(me));l===R?(Je=r,r=R):(o=[o,l],r=o)}else Je=r,r=R;r===R&&(Ke.test(t.charAt(Je))?(r=t.charAt(Je),Je++):(r=R,a(Ze))),r===R?(Je=n,n=R):(i=[i,r],n=i)}else Je=n,n=R;return n!==R&&(et=e,n=$e(n)),e=n,e}function x(){var e,n,i,r,o;return e=Je,n=Je,ve.test(t.charAt(Je))?(i=t.charAt(Je),Je++):(i=R,a(fe)),i===R?(Je=n,n=R):(ye.test(t.charAt(Je))?(r=t.charAt(Je),Je++):(r=R,a(me)),r===R&&(r=null),r===R?(Je=n,n=R):(ye.test(t.charAt(Je))?(o=t.charAt(Je),Je++):(o=R,a(me)),o===R&&(o=null),o===R?(Je=n,n=R):(i=[i,r,o],n=i))),n!==R&&(et=e,n=Qe(n)),e=n,e}var C=1<arguments.length?arguments[1]:{},S=this,R={},A={chain:r},j=r,T=function(e){for(var t=[],n=[],a=0;a<e[1].length;a++)t.push(e[1][a]);for(var a=0,i;a<e[2].length;a++)i=e[2][a][0]?e[2][a][0]:"-",n.push({bond:i,id:e[2][a][1]});for(var a=0;a<e[3].length;a++)t.push(e[3][a]);for(var a=0;a<e[6].length;a++)t.push(e[6][a]);return{atom:e[0],isBracket:!!e[0].element,branches:t,branchCount:t.length,ringbonds:n,ringbondCount:n.length,bond:e[4]?e[4]:"-",next:e[5],hasNext:!!e[5]}},w="(",P={type:"literal",value:"(",description:"\"(\""},I=")",B={type:"literal",value:")",description:"\")\""},L=function(e){var t=e[1]?e[1]:"-";return e[2].branchBond=t,e[2]},E=function(e){return e},D=/^[\-=#$:\/\\.]/,N={type:"class",value:"[-=#$:/\\\\.]",description:"[-=#$:/\\\\.]"},_=function(e){return e},O="[",V={type:"literal",value:"[",description:"\"[\""},z="se",H={type:"literal",value:"se",description:"\"se\""},F="as",W={type:"literal",value:"as",description:"\"as\""},M="]",q={type:"literal",value:"]",description:"\"]\""},U=function(e){return{isotope:e[1],element:e[2],chirality:e[3],hcount:e[4],charge:e[5],class:e[6]}},G="B",X={type:"literal",value:"B",description:"\"B\""},Y="r",K={type:"literal",value:"r",description:"\"r\""},Z="C",$={type:"literal",value:"C",description:"\"C\""},Q="l",J={type:"literal",value:"l",description:"\"l\""},ee=/^[NOPSFI]/,te={type:"class",value:"[NOPSFI]",description:"[NOPSFI]"},ne=function(e){return 1<e.length?e.join(""):e},ie=/^[bcnops]/,ae={type:"class",value:"[bcnops]",description:"[bcnops]"},re="*",oe={type:"literal",value:"*",description:"\"*\""},le=function(e){return e},se=/^[A-Z]/,de={type:"class",value:"[A-Z]",description:"[A-Z]"},ge=/^[a-z]/,ue={type:"class",value:"[a-z]",description:"[a-z]"},he=function(t){return t.join("")},ce="%",pe={type:"literal",value:"%",description:"\"%\""},ve=/^[1-9]/,fe={type:"class",value:"[1-9]",description:"[1-9]"},ye=/^[0-9]/,me={type:"class",value:"[0-9]",description:"[0-9]"},be=function(e){return 1==e.length?+e:+e.join("").replace("%","")},ke="@",xe={type:"literal",value:"@",description:"\"@\""},Ce="TH",Se={type:"literal",value:"TH",description:"\"TH\""},Re=/^[12]/,Ae={type:"class",value:"[12]",description:"[12]"},je="AL",Te={type:"literal",value:"AL",description:"\"AL\""},we="SP",Pe={type:"literal",value:"SP",description:"\"SP\""},Ie=/^[1-3]/,Be={type:"class",value:"[1-3]",description:"[1-3]"},Le="TB",Ee={type:"literal",value:"TB",description:"\"TB\""},De="OH",Ne={type:"literal",value:"OH",description:"\"OH\""},_e=function(e){return e[1]?"@"==e[1]?"@@":e[1].join("").replace(",",""):"@"},Oe=function(e){return e},Ve="+",ze={type:"literal",value:"+",description:"\"+\""},He=function(e){return e[1]?"+"==e[1]?2:+e[1].join(""):1},Fe="-",We={type:"literal",value:"-",description:"\"-\""},Me=function(e){return e[1]?"-"==e[1]?-2:-+e[1].join(""):-1},qe="H",Ue={type:"literal",value:"H",description:"\"H\""},Ge=function(e){return e[1]?+e[1]:1},Xe=":",Ye={type:"literal",value:":",description:"\":\""},Ke=/^[0]/,Ze={type:"class",value:"[0]",description:"[0]"},$e=function(e){return+(e[1][0]+e[1][1].join(""))},Qe=function(e){return+e.join("")},Je=0,et=0,tt=[{line:1,column:1,seenCR:!1}],nt=0,it=[],at;if("startRule"in C){if(!(C.startRule in A))throw new Error("Can't start parsing from rule \""+C.startRule+"\".");j=A[C.startRule]}if(at=j(),at!==R&&Je===t.length)return at;throw at!==R&&Je<t.length&&a({type:"end",description:"end of input"}),function(t,n,i,a){return null!==n&&function(e){var t=1;for(e.sort(function(e,t){return e.description<t.description?-1:e.description>t.description?1:0});t<e.length;)e[t-1]===e[t]?e.splice(t,1):t++}(n),new e(null===t?function(e,t){var n=Array(e.length),a,r,o;for(o=0;o<e.length;o++)n[o]=e[o].description;return a=1<e.length?n.slice(0,-1).join(", ")+" or "+n[e.length-1]:n[0],r=t?"\""+function(e){function t(e){return e.charCodeAt(0).toString(16).toUpperCase()}return e.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\x08/g,"\\b").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\f/g,"\\f").replace(/\r/g,"\\r").replace(/[\x00-\x07\x0B\x0E\x0F]/g,function(e){return"\\x0"+t(e)}).replace(/[\x10-\x1F\x80-\xFF]/g,function(e){return"\\x"+t(e)}).replace(/[\u0100-\u0FFF]/g,function(e){return"\\u0"+t(e)}).replace(/[\u1000-\uFFFF]/g,function(e){return"\\u"+t(e)})}(t)+"\"":"end of input","Expected "+a+" but "+r+" found."}(n,i):t,n,i,a)}(null,it,nt<t.length?t.charAt(nt):null,nt<t.length?i(nt,nt+1):i(nt,nt))}}}()},{}],11:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=e("./ArrayHelper"),l=i(o),s=e("./Vector2"),d=i(s),g=e("./Vertex"),u=i(g),h=e("./RingConnection"),c=i(h),p=function(){function e(t){a(this,e),this.id=null,this.members=t,this.edges=[],this.insiders=[],this.neighbours=[],this.positioned=!1,this.center=new d.default(0,0),this.rings=[],this.isBridged=!1,this.isPartOfBridged=!1,this.isSpiro=!1,this.isFused=!1,this.centralAngle=0,this.canFlip=!0}return r(e,[{key:"clone",value:function(){var t=new e(this.members);return t.id=this.id,t.insiders=l.default.clone(this.insiders),t.neighbours=l.default.clone(this.neighbours),t.positioned=this.positioned,t.center=this.center.clone(),t.rings=l.default.clone(this.rings),t.isBridged=this.isBridged,t.isPartOfBridged=this.isPartOfBridged,t.isSpiro=this.isSpiro,t.isFused=this.isFused,t.centralAngle=this.centralAngle,t.canFlip=this.canFlip,t}},{key:"getSize",value:function(){return this.members.length}},{key:"getPolygon",value:function(e){for(var t=[],n=0;n<this.members.length;n++)t.push(e[this.members[n]].position);return t}},{key:"getAngle",value:function(){return Math.PI-this.centralAngle}},{key:"eachMember",value:function(e,t,n,i){n=n||0===n?n:this.members[0];for(var a=n,r=0,o;null!=a&&100>r;)o=a,t(o),a=e[a].getNextInRing(e,this.id,i),i=o,a==n&&(a=null),r++}},{key:"getOrderedNeighbours",value:function(e){for(var t=Array(this.neighbours.length),n=0,i;n<this.neighbours.length;n++)i=c.default.getVertices(e,this.id,this.neighbours[n]),t[n]={n:i.length,neighbour:this.neighbours[n]};return t.sort(function(e,t){return t.n-e.n}),t}},{key:"isBenzeneLike",value:function(e){var t=this.getDoubleBondCount(e),n=this.members.length;return 3===t&&6===n||2===t&&5===n}},{key:"getDoubleBondCount",value:function(e){for(var t=0,n=0,i;n<this.members.length;n++)i=e[this.members[n]].value,("="===i.bondType||"="===i.branchBond)&&t++;return t}},{key:"contains",value:function(e){for(var t=0;t<this.members.length;t++)if(this.members[t]==e)return!0;return!1}}]),e}();n.default=p},{"./ArrayHelper":2,"./RingConnection":12,"./Vector2":14,"./Vertex":15}],12:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function r(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=e("./Vertex"),s=i(l),d=e("./Ring"),g=i(d),u=function(){function e(t,i){r(this,e),this.id=null,this.firstRingId=t.id,this.secondRingId=i.id,this.vertices=new Set;for(var a=0,o;a<t.members.length;a++){o=t.members[a];for(var l=0,n;l<i.members.length;l++)n=i.members[l],o===n&&this.addVertex(o)}}return o(e,[{key:"addVertex",value:function(e){this.vertices.add(e)}},{key:"updateOther",value:function(e,t){this.firstRingId===t?this.secondRingId=e:this.firstRingId=e}},{key:"containsRing",value:function(e){return this.firstRingId===e||this.secondRingId===e}},{key:"isBridge",value:function(e){if(2<this.vertices.size)return!0;var t=!0,n=!1,i;try{for(var a=this.vertices[Symbol.iterator](),r,o;!(t=(r=a.next()).done);t=!0)if(o=r.value,2<e[o].value.rings.length)return!0}catch(e){n=!0,i=e}finally{try{!t&&a.return&&a.return()}finally{if(n)throw i}}return!1}}],[{key:"isBridge",value:function(e,t,n,a){for(var r=null,o=0;o<e.length;o++)if(r=e[o],r.firstRingId===n&&r.secondRingId===a||r.firstRingId===a&&r.secondRingId===n)return r.isBridge(t);return!1}},{key:"getNeighbours",value:function(e,t){for(var n=[],a=0,i;a<e.length;a++)i=e[a],i.firstRingId===t?n.push(i.secondRingId):i.secondRingId===t&&n.push(i.firstRingId);return n}},{key:"getVertices",value:function(e,t,n){for(var r=0,i;r<e.length;r++)if(i=e[r],i.firstRingId===t&&i.secondRingId===n||i.firstRingId===n&&i.secondRingId===t)return[].concat(a(i.vertices))}}]),e}();n.default=u},{"./Ring":11,"./Vertex":15}],13:[function(e,t,n){"use strict";function a(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(n,"__esModule",{value:!0});var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),o=e("./Graph"),l=function(e){return e&&e.__esModule?e:{default:e}}(o),s=function(){function e(){i(this,e)}return r(e,null,[{key:"getRings",value:function(t){var n=t.getComponentsAdjacencyMatrix();if(0===n.length)return null;for(var r=l.default.getConnectedComponents(n),o=[],s=0;s<r.length;s++){for(var i=r[s],g=t.getSubgraphAdjacencyMatrix([].concat(a(i))),u=new Uint16Array(g.length),h=new Uint16Array(g.length),p=0;p<g.length;p++){h[p]=0,u[p]=0;for(var v=0;v<g[p].length;v++)u[p]+=g[p][v]}for(var f=0,p=0;p<g.length;p++)for(var v=p+1;v<g.length;v++)f+=g[p][v];for(var y=f-g.length+1,m=!0,p=0;p<u.length;p++)3!==u[p]&&(m=!1);if(m&&(y=2+f-g.length),1==y){o.push([].concat(a(i)));continue}for(var b=e.getPathIncludedDistanceMatrices(g),k=b.d,d=b.pe,x=b.pe_prime,C=e.getRingCandidates(k,d,x),c=e.getSSSR(C,k,g,d,x,u,h,y),p=0;p<c.length;p++){var S=Array(c[p].size),R=0,A=!0,j=!1,T=void 0;try{for(var w=c[p][Symbol.iterator](),P,I;!(A=(P=w.next()).done);A=!0)I=P.value,S[R++]=i[I]}catch(e){j=!0,T=e}finally{try{!A&&w.return&&w.return()}finally{if(j)throw T}}o.push(S)}}return o}},{key:"matrixToString",value:function(e){for(var t="",n=0;n<e.length;n++){for(var i=0;i<e[n].length;i++)t+=e[n][i]+" ";t+="\n"}return t}},{key:"getPathIncludedDistanceMatrices",value:function(e){for(var t=e.length,a=Array(t),r=Array(t),o=Array(t),s=0,l=0,g=0,n=t;n--;){a[n]=Array(t),r[n]=Array(t),o[n]=Array(t);for(var i=t;i--;)a[n][i]=n===i||1===e[n][i]?e[n][i]:Number.POSITIVE_INFINITY,r[n][i]=1===a[n][i]?[[[n,i]]]:[],o[n][i]=[]}for(var u=t,i;u--;)for(n=t;n--;)for(i=t;i--;){var h=a[n][i],c=a[n][u]+a[u][i];if(h>c){var s,l,g;if(h===c+1)for(o[n][i]=[r[n][i].length],s=r[n][i].length;s--;)for(o[n][i][s]=[r[n][i][s].length],l=r[n][i][s].length;l--;)for(o[n][i][s][l]=[r[n][i][s][l].length],g=r[n][i][s][l].length;g--;)o[n][i][s][l][g]=[r[n][i][s][l][0],r[n][i][s][l][1]];else o[n][i]=[];for(a[n][i]=c,r[n][i]=[[]],s=r[n][u][0].length;s--;)r[n][i][0].push(r[n][u][0][s]);for(s=r[u][i][0].length;s--;)r[n][i][0].push(r[u][i][0][s])}else if(h===c){if(r[n][u].length&&r[u][i].length){var s;if(r[n][i].length){var p=[];for(s=r[n][u][0].length;s--;)p.push(r[n][u][0][s]);for(s=r[u][i][0].length;s--;)p.push(r[u][i][0][s]);r[n][i].push(p)}else{var v=[];for(s=r[n][u][0].length;s--;)v.push(r[n][u][0][s]);for(s=r[u][i][0].length;s--;)v.push(r[u][i][0][s]);r[n][i][0]=v}}}else if(h===c-1){var s;if(o[n][i].length){var f=[];for(s=r[n][u][0].length;s--;)f.push(r[n][u][0][s]);for(s=r[u][i][0].length;s--;)f.push(r[u][i][0][s]);o[n][i].push(f)}else{var y=[];for(s=r[n][u][0].length;s--;)y.push(r[n][u][0][s]);for(s=r[u][i][0].length;s--;)y.push(r[u][i][0][s]);o[n][i][0]=y}}}return{d:a,pe:r,pe_prime:o}}},{key:"getRingCandidates",value:function(e,t,n){for(var a=e.length,r=[],o=0,l=0;l<a;l++)for(var i=0;i<a;i++)if(0===e[l][i]||1===t[l][i].length&&0===n[l][i])continue;else o=0===n[l][i].length?2*e[l][i]:2*(e[l][i]+0.5),o!=Infinity&&r.push([o,t[l][i],n[l][i]]);return r.sort(function(e,t){return e[0]-t[0]}),r}},{key:"getSSSR",value:function(t,n,a,r,o,l,s,d){for(var g=[],u=[],h=0;h<t.length;h++)if(0!=t[h][0]%2)for(var i=0,c;i<t[h][2].length;i++){c=t[h][1][0].concat(t[h][2][i]);for(var p=0;p<c.length;p++)c[p][0].constructor===Array&&(c[p]=c[p][0]);var v=e.bondsToAtoms(c);if(e.getBondCount(v,a)!==v.size||e.pathSetsContain(g,v,c,u,l,s)||(g.push(v),u=u.concat(c)),g.length>d)return g}else for(var f=0,y;f<t[h][1].length-1;f++){y=t[h][1][f].concat(t[h][1][f+1]);for(var p=0;p<y.length;p++)y[p][0].constructor===Array&&(y[p]=y[p][0]);var m=e.bondsToAtoms(y);if(e.getBondCount(m,a)!==m.size||e.pathSetsContain(g,m,y,u,l,s)||(g.push(m),u=u.concat(y)),g.length>d)return g}return g}},{key:"getEdgeCount",value:function(e){for(var t=0,n=e.length,a=n-1;a--;)for(var i=n;i--;)1===e[a][i]&&t++;return t}},{key:"getEdgeList",value:function(e){for(var t=e.length,n=[],a=t-1;a--;)for(var i=t;i--;)1===e[a][i]&&n.push([a,i]);return n}},{key:"bondsToAtoms",value:function(e){for(var t=new Set,n=e.length;n--;)t.add(e[n][0]),t.add(e[n][1]);return t}},{key:"getBondCount",value:function(e,t){var n=0,i=!0,a=!1,r;try{for(var o=e[Symbol.iterator](),l;!(i=(l=o.next()).done);i=!0){var s=l.value,d=!0,g=!1,u=void 0;try{for(var h=e[Symbol.iterator](),c,p;!(d=(c=h.next()).done);d=!0)(p=c.value,s!==p)&&(n+=t[s][p])}catch(e){g=!0,u=e}finally{try{!d&&h.return&&h.return()}finally{if(g)throw u}}}}catch(e){a=!0,r=e}finally{try{!i&&o.return&&o.return()}finally{if(a)throw r}}return n/2}},{key:"pathSetsContain",value:function(t,n,a,r,o,l){for(var s=t.length;s--;){if(e.isSupersetOf(n,t[s]))return!0;if(t[s].size===n.size&&e.areSetsEqual(t[s],n))return!0}var i=0,d=!1;for(s=a.length;s--;)for(var g=r.length;g--;)(a[s][0]===r[g][0]&&a[s][1]===r[g][1]||a[s][1]===r[g][0]&&a[s][0]===r[g][1])&&i++,i===a.length&&(d=!0);var u=!1;if(d){var h=!0,c=!1,p;try{for(var v=n[Symbol.iterator](),f,y;!(h=(f=v.next()).done);h=!0)if(y=f.value,l[y]<o[y]){u=!0;break}}catch(e){c=!0,p=e}finally{try{!h&&v.return&&v.return()}finally{if(c)throw p}}}if(d&&!u)return!0;var m=!0,b=!1,k;try{for(var x=n[Symbol.iterator](),C,S;!(m=(C=x.next()).done);m=!0)S=C.value,l[S]++}catch(e){b=!0,k=e}finally{try{!m&&x.return&&x.return()}finally{if(b)throw k}}return!1}},{key:"areSetsEqual",value:function(e,t){if(e.size!==t.size)return!1;var n=!0,i=!1,a;try{for(var r=e[Symbol.iterator](),o,l;!(n=(o=r.next()).done);n=!0)if(l=o.value,!t.has(l))return!1}catch(e){i=!0,a=e}finally{try{!n&&r.return&&r.return()}finally{if(i)throw a}}return!0}},{key:"isSupersetOf",value:function(e,t){var n=!0,i=!1,a;try{for(var r=t[Symbol.iterator](),o,l;!(n=(o=r.next()).done);n=!0)if(l=o.value,!e.has(l))return!1}catch(e){i=!0,a=e}finally{try{!n&&r.return&&r.return()}finally{if(i)throw a}}return!0}}]),e}();n.default=s},{"./Graph":7}],14:[function(e,t,n){"use strict";function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var a=Math.sin,r=Math.cos,o=Math.sqrt;Object.defineProperty(n,"__esModule",{value:!0});var l=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),s=function(){function e(t,n){i(this,e),0==arguments.length?(this.x=0,this.y=0):1==arguments.length?(this.x=t.x,this.y=t.y):(this.x=t,this.y=n)}var t=Math.acos;return l(e,[{key:"clone",value:function(){return new e(this.x,this.y)}},{key:"toString",value:function(){return"("+this.x+","+this.y+")"}},{key:"add",value:function(e){return this.x+=e.x,this.y+=e.y,this}},{key:"subtract",value:function(e){return this.x-=e.x,this.y-=e.y,this}},{key:"divide",value:function(e){return this.x/=e,this.y/=e,this}},{key:"multiply",value:function(e){return this.x*=e.x,this.y*=e.y,this}},{key:"multiplyScalar",value:function(e){return this.x*=e,this.y*=e,this}},{key:"invert",value:function(){return this.x=-this.x,this.y=-this.y,this}},{key:"angle",value:function(){return Math.atan2(this.y,this.x)}},{key:"distance",value:function(e){return o((e.x-this.x)*(e.x-this.x)+(e.y-this.y)*(e.y-this.y))}},{key:"distanceSq",value:function(e){return(e.x-this.x)*(e.x-this.x)+(e.y-this.y)*(e.y-this.y)}},{key:"clockwise",value:function(e){var t=this.y*e.x,n=this.x*e.y;return t>n?-1:t==n?0:1}},{key:"relativeClockwise",value:function(e,t){var n=(this.y-e.y)*(t.x-e.x),i=(this.x-e.x)*(t.y-e.y);return n>i?-1:n==i?0:1}},{key:"rotate",value:function(t){var n=new e(0,0),i=r(t),o=a(t);return n.x=this.x*i-this.y*o,n.y=this.x*o+this.y*i,this.x=n.x,this.y=n.y,this}},{key:"rotateAround",value:function(e,t){var n=a(e),i=r(e);this.x-=t.x,this.y-=t.y;var o=this.x*i-this.y*n,l=this.x*n+this.y*i;return this.x=o+t.x,this.y=l+t.y,this}},{key:"rotateTo",value:function(t,n){var i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0;this.x+=1e-3,this.y-=1e-3;var r=e.subtract(this,n),a=e.subtract(t,n),o=e.angle(a,r);return this.rotateAround(o+i,n),this}},{key:"rotateAwayFrom",value:function(e,t,n){this.rotateAround(n,t);var i=this.distanceSq(e);this.rotateAround(-2*n,t);var a=this.distanceSq(e);a<i&&this.rotateAround(2*n,t)}},{key:"getRotateAwayFromAngle",value:function(e,t,n){var i=this.clone();i.rotateAround(n,t);var a=i.distanceSq(e);i.rotateAround(-2*n,t);var r=i.distanceSq(e);return r<a?n:-n}},{key:"getRotateTowardsAngle",value:function(e,t,n){var i=this.clone();i.rotateAround(n,t);var a=i.distanceSq(e);i.rotateAround(-2*n,t);var r=i.distanceSq(e);return r>a?n:-n}},{key:"getRotateToAngle",value:function(t,n){var i=e.subtract(this,n),a=e.subtract(t,n),r=e.angle(a,i);return Number.isNaN(r)?0:r}},{key:"isInPolygon",value:function(e){for(var t=!1,n=0,i=e.length-1;n<e.length;i=n++)e[n].y>this.y!=e[i].y>this.y&&this.x<(e[i].x-e[n].x)*(this.y-e[n].y)/(e[i].y-e[n].y)+e[n].x&&(t=!t);return t}},{key:"length",value:function(){return o(this.x*this.x+this.y*this.y)}},{key:"lengthSq",value:function(){return this.x*this.x+this.y*this.y}},{key:"normalize",value:function(){return this.divide(this.length()),this}},{key:"normalized",value:function(){return e.divideScalar(this,this.length())}},{key:"whichSide",value:function(e,t){return(this.x-e.x)*(t.y-e.y)-(this.y-e.y)*(t.x-e.x)}},{key:"sameSideAs",value:function(e,t,n){var i=this.whichSide(e,t),a=n.whichSide(e,t);return 0>i&&0>a||0==i&&0==a||0<i&&0<a}}],[{key:"add",value:function(t,n){return new e(t.x+n.x,t.y+n.y)}},{key:"subtract",value:function(t,n){return new e(t.x-n.x,t.y-n.y)}},{key:"multiply",value:function(t,n){return new e(t.x*n.x,t.y*n.y)}},{key:"multiplyScalar",value:function(t,n){return new e(t.x,t.y).multiplyScalar(n)}},{key:"midpoint",value:function(t,n){return new e((t.x+n.x)/2,(t.y+n.y)/2)}},{key:"normals",value:function(t,n){var i=e.subtract(n,t);return[new e(-i.y,i.x),new e(i.y,-i.x)]}},{key:"units",value:function(t,n){var i=e.subtract(n,t);return[new e(-i.y,i.x).normalize(),new e(i.y,-i.x).normalize()]}},{key:"divide",value:function(t,n){return new e(t.x/n.x,t.y/n.y)}},{key:"divideScalar",value:function(t,n){return new e(t.x/n,t.y/n)}},{key:"dot",value:function(e,t){return e.x*t.x+e.y*t.y}},{key:"angle",value:function(n,i){var a=e.dot(n,i);return t(a/(n.length()*i.length()))}},{key:"threePointangle",value:function(n,i,a){var r=e.subtract(i,n),o=e.subtract(a,i),l=n.distance(i),s=i.distance(a);return t(e.dot(r,o)/(l*s))}},{key:"scalarProjection",value:function(t,n){var i=n.normalized();return e.dot(t,i)}},{key:"averageDirection",value:function(t){for(var n=new e(0,0),a=0,i;a<t.length;a++)i=t[a],n.add(i);return n.normalize()}}]),e}();n.default=s},{}],15:[function(e,t,n){"use strict";function i(e){return e&&e.__esModule?e:{default:e}}function a(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.round;Object.defineProperty(n,"__esModule",{value:!0});var o=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=e("./MathHelper"),s=i(l),d=e("./ArrayHelper"),g=i(d),u=e("./Vector2"),h=i(u),c=e("./Atom"),p=i(c),v=function(){function e(t){var n=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0,i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0;a(this,e),this.id=null,this.value=t,this.position=new h.default(n?n:0,i?i:0),this.previousPosition=new h.default(0,0),this.parentVertexId=null,this.children=[],this.spanningTreeChildren=[],this.edges=[],this.positioned=!1,this.angle=null,this.dir=1,this.neighbourCount=0,this.neighbours=[],this.neighbouringElements=[],this.forcePositioned=!1}return o(e,[{key:"setPosition",value:function(e,t){this.position.x=e,this.position.y=t}},{key:"setPositionFromVector",value:function(e){this.position.x=e.x,this.position.y=e.y}},{key:"addChild",value:function(e){this.children.push(e),this.neighbours.push(e),this.neighbourCount++,this.value.bondCount++}},{key:"addRingbondChild",value:function(e,t){if(this.children.push(e),this.value.bracket){var n=1;0===this.id&&0===this.value.bracket.hcount&&(n=0),1===this.value.bracket.hcount&&0===t&&(n=2),1===this.value.bracket.hcount&&1===t&&(3>this.neighbours.length?n=2:n=3),null===this.value.bracket.hcount&&0===t&&(n=1),null===this.value.bracket.hcount&&1===t&&(3>this.neighbours.length?n=1:n=2),this.neighbours.splice(n,0,e)}else this.neighbours.push(e);this.neighbourCount++,this.value.bondCount++}},{key:"setParentVertexId",value:function(e){this.neighbourCount++,this.parentVertexId=e,this.neighbours.push(e),this.value.bondCount++}},{key:"isTerminal",value:function(){return!!this.value.hasAttachedPseudoElements||null===this.parentVertexId&&2>this.children.length||0===this.children.length}},{key:"clone",value:function(){var t=new e(this.value,this.position.x,this.position.y);return t.id=this.id,t.previousPosition=new h.default(this.previousPosition.x,this.previousPosition.y),t.parentVertexId=this.parentVertexId,t.children=g.default.clone(this.children),t.spanningTreeChildren=g.default.clone(this.spanningTreeChildren),t.edges=g.default.clone(this.edges),t.positioned=this.positioned,t.angle=this.angle,t.forcePositioned=this.forcePositioned,t}},{key:"equals",value:function(e){return this.id===e.id}},{key:"getAngle",value:function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=null;return n=e?h.default.subtract(this.position,e):h.default.subtract(this.position,this.previousPosition),t?s.default.toDeg(n.angle()):n.angle()}},{key:"getTextDirection",value:function(e){for(var t=this.getDrawnNeighbours(e),n=[],a=0;a<t.length;a++)n.push(this.getAngle(e[t[a]].position));var i=s.default.meanAngle(n),o=Math.PI/2;return i=r(r(i/o)*o),2===i?"down":-2===i?"up":0===i||-0===i?"right":3===i||-3===i?"left":"down"}},{key:"getNeighbours",value:function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null;if(null===e)return this.neighbours.slice();for(var t=[],n=0;n<this.neighbours.length;n++)this.neighbours[n]!==e&&t.push(this.neighbours[n]);return t}},{key:"getDrawnNeighbours",value:function(e){for(var t=[],n=0;n<this.neighbours.length;n++)e[this.neighbours[n]].value.isDrawn&&t.push(this.neighbours[n]);return t}},{key:"getNeighbourCount",value:function(){return this.neighbourCount}},{key:"getSpanningTreeNeighbours",value:function(){for(var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,t=[],n=0;n<this.spanningTreeChildren.length;n++)(void 0===e||e!=this.spanningTreeChildren[n])&&t.push(this.spanningTreeChildren[n]);return null!=this.parentVertexId&&(void 0===e||e!=this.parentVertexId)&&t.push(this.parentVertexId),t}},{key:"getNextInRing",value:function(e,t,n){for(var a=this.getNeighbours(),r=0;r<a.length;r++)if(g.default.contains(e[a[r]].value.rings,{value:t})&&a[r]!=n)return a[r];return null}}]),e}();n.default=v},{"./ArrayHelper":2,"./Atom":3,"./MathHelper":9,"./Vector2":14}]},{},[1]);
-//# sourceMappingURL=smiles-drawer.min.js.map
+(function g(d,e,t){function r(i,o){if(!e[i]){if(!d[i]){var s="function"==typeof require&&require;if(!o&&s)return s(i,!0);if(n)return n(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var h=e[i]={exports:{}};d[i][0].call(h.exports,function(t){var e=d[i][1][t];return r(e?e:t)},h,h.exports,g,d,e,t)}return e[i].exports}for(var n="function"==typeof require&&require,i=0;i<t.length;i++)r(t[i]);return r})({1:[function(e,t){"use strict";var n=Math.min,i=Math.max,r=e("./src/Drawer"),a=e("./src/Parser"),o=!!("undefined"!=typeof window&&window.document&&window.document.createElement),l={Version:"1.0.0"};l.Drawer=r,l.Parser=a,l.clean=function(e){return e.replace(/[^A-Za-z0-9@\.\+\-\?!\(\)\[\]\{\}/\\=#\$:\*]/g,"")},l.apply=function(e){for(var t=1<arguments.length&&arguments[1]!==void 0?arguments[1]:"canvas[data-smiles]",n=2<arguments.length&&arguments[2]!==void 0?arguments[2]:"light",a=3<arguments.length&&arguments[3]!==void 0?arguments[3]:null,o=new r(e),s=document.querySelectorAll(t),g=function(){var e=s[d];l.parse(e.getAttribute("data-smiles"),function(t){o.draw(t,e,n,!1)},function(e){a&&a(e)})},d=0;d<s.length;d++)g()},l.parse=function(e,t,n){try{t&&t(a.parse(e))}catch(e){n&&n(e)}},o&&(window.SmilesDrawer=l),Array.prototype.fill||Object.defineProperty(Array.prototype,"fill",{value:function(e){if(null==this)throw new TypeError("this is null or not defined");for(var t=Object(this),r=t.length>>>0,a=arguments[1],o=a>>0,l=0>o?i(r+o,0):n(o,r),s=arguments[2],g=void 0===s?r:s>>0,d=0>g?i(r+g,0):n(g,r);l<d;)t[l]=e,l++;return t}}),t.exports=l},{"./src/Drawer":5,"./src/Parser":10}],2:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=function(){function e(){n(this,e)}return r(e,null,[{key:"clone",value:function(t){var n=Array.isArray(t)?[]:{};for(var r in t){var a=t[r];n[r]="function"==typeof a.clone?a.clone():"object"===("undefined"==typeof a?"undefined":i(a))?e.clone(a):a}return n}},{key:"equals",value:function(e,t){if(e.length!==t.length)return!1;for(var n=e.slice().sort(),r=t.slice().sort(),a=0;a<n.length;a++)if(n[a]!==r[a])return!1;return!0}},{key:"print",value:function(e){if(0==e.length)return"";for(var t="(",n=0;n<e.length;n++)t+=e[n].id?e[n].id+", ":e[n]+", ";return t=t.substring(0,t.length-2),t+")"}},{key:"each",value:function(e,t){for(var n=0;n<e.length;n++)t(e[n])}},{key:"get",value:function(e,t,n){for(var r=0;r<e.length;r++)if(e[r][t]==n)return e[r]}},{key:"contains",value:function(e,t){if(!t.property&&!t.func){for(var n=0;n<e.length;n++)if(e[n]==t.value)return!0;}else if(t.func){for(var i=0;i<e.length;i++)if(t.func(e[i]))return!0;}else for(var r=0;r<e.length;r++)if(e[r][t.property]==t.value)return!0;return!1}},{key:"intersection",value:function(e,t){for(var n=[],r=0;r<e.length;r++)for(var i=0;i<t.length;i++)e[r]===t[i]&&n.push(e[r]);return n}},{key:"unique",value:function(e){var t={};return e.filter(function(e){return void 0===t[e]&&(t[e]=!0)})}},{key:"count",value:function(e,t){for(var n=0,r=0;r<e.length;r++)e[r]===t&&n++;return n}},{key:"toggle",value:function(e,t){for(var n=[],r=!1,a=0;a<e.length;a++)e[a]===t?r=!0:n.push(e[a]);return r||n.push(t),n}},{key:"remove",value:function(e,t){for(var n=[],r=0;r<e.length;r++)e[r]!==t&&n.push(e[r]);return n}},{key:"removeUnique",value:function(e,t){var n=e.indexOf(t);return-1<n&&e.splice(n,1),e}},{key:"removeAll",value:function(e,t){return e.filter(function(e){return-1===t.indexOf(e)})}},{key:"merge",value:function(e,t){for(var n=Array(e.length+t.length),r=0;r<e.length;r++)n[r]=e[r];for(var i=0;i<t.length;i++)n[e.length+i]=t[i];return n}},{key:"containsAll",value:function(e,t){for(var n=0,r=0;r<e.length;r++)for(var i=0;i<t.length;i++)e[r]===t[i]&&n++;return n===t.length}},{key:"sortByAtomicNumberDesc",value:function(t){var e=t.map(function(t,e){return{index:e,value:t.atomicNumber.split(".").map(Number)}});return e.sort(function(e,t){for(var n=Math.min(t.value.length,e.value.length),r=0;r<n&&t.value[r]===e.value[r];)r++;return r===n?t.value.length-e.value.length:t.value[r]-e.value[r]}),e.map(function(n){return t[n.index]})}},{key:"deepCopy",value:function(t){for(var n=[],r=0,i;r<t.length;r++)i=t[r],n[r]=i instanceof Array?e.deepCopy(i):i;return n}}]),e}();t.exports=a},{}],3:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r=e("./ArrayHelper"),a=e("./Vertex"),o=e("./Ring"),l=function(){function e(t){var i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:"-";n(this,e),this.element=1===t.length?t.toUpperCase():t,this.drawExplicit=!1,this.ringbonds=[],this.rings=[],this.bondType=i,this.branchBond=null,this.isBridge=!1,this.isBridgeNode=!1,this.originalRings=[],this.bridgedRing=null,this.anchoredRings=[],this.bracket=null,this.plane=0,this.attachedPseudoElements={},this.hasAttachedPseudoElements=!1,this.isDrawn=!0,this.isConnectedToRing=!1,this.neighbouringElements=[],this.isPartOfAromaticRing=t!==this.element,this.bondCount=0,this.chirality="",this.isStereoCenter=!1,this.priority=0,this.mainChain=!1,this.hydrogenDirection="down",this.subtreeDepth=1,this.hasHydrogen=!1}return i(e,[{key:"addNeighbouringElement",value:function(e){this.neighbouringElements.push(e)}},{key:"attachPseudoElement",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0,i=3<arguments.length&&void 0!==arguments[3]?arguments[3]:0;null===n&&(n=0),null===i&&(i=0);var r=n+e+i;this.attachedPseudoElements[r]?this.attachedPseudoElements[r].count+=1:this.attachedPseudoElements[r]={element:e,count:1,hydrogenCount:n,previousElement:t,charge:i},this.hasAttachedPseudoElements=!0}},{key:"getAttachedPseudoElements",value:function(){var e={},t=this;return Object.keys(this.attachedPseudoElements).sort().forEach(function(n){e[n]=t.attachedPseudoElements[n]}),e}},{key:"getAttachedPseudoElementsCount",value:function(){return Object.keys(this.attachedPseudoElements).length}},{key:"isHeteroAtom",value:function(){return"C"!==this.element&&"H"!==this.element}},{key:"addAnchoredRing",value:function(e){r.contains(this.anchoredRings,{value:e})||this.anchoredRings.push(e)}},{key:"getRingbondCount",value:function(){return this.ringbonds.length}},{key:"backupRings",value:function(){this.originalRings=Array(this.rings.length);for(var e=0;e<this.rings.length;e++)this.originalRings[e]=this.rings[e]}},{key:"restoreRings",value:function(){this.rings=Array(this.originalRings.length);for(var e=0;e<this.originalRings.length;e++)this.rings[e]=this.originalRings[e]}},{key:"haveCommonRingbond",value:function(e,t){for(var n=0;n<e.ringbonds.length;n++)for(var i=0;i<t.ringbonds.length;i++)if(e.ringbonds[n].id==t.ringbonds[i].id)return!0;return!1}},{key:"neighbouringElementsEqual",value:function(e){if(e.length!==this.neighbouringElements.length)return!1;e.sort(),this.neighbouringElements.sort();for(var t=0;t<this.neighbouringElements.length;t++)if(e[t]!==this.neighbouringElements[t])return!1;return!0}},{key:"getAtomicNumber",value:function(){return e.atomicNumbers[this.element]}},{key:"getMaxBonds",value:function(){return e.maxBonds[this.element]}}],[{key:"maxBonds",get:function(){return{H:1,C:4,N:3,O:2,P:3,S:2,B:3,F:1,I:1,Cl:1,Br:1}}},{key:"atomicNumbers",get:function(){return{H:1,He:2,Li:3,Be:4,B:5,b:5,C:6,c:6,N:7,n:7,O:8,o:8,F:9,Ne:10,Na:11,Mg:12,Al:13,Si:14,P:15,p:15,S:16,s:16,Cl:17,Ar:18,K:19,Ca:20,Sc:21,Ti:22,V:23,Cr:24,Mn:25,Fe:26,Co:27,Ni:28,Cu:29,Zn:30,Ga:31,Ge:32,As:33,Se:34,Br:35,Kr:36,Rb:37,Sr:38,Y:39,Zr:40,Nb:41,Mo:42,Tc:43,Ru:44,Rh:45,Pd:46,Ag:47,Cd:48,In:49,Sn:50,Sb:51,Te:52,I:53,Xe:54,Cs:55,Ba:56,La:57,Ce:58,Pr:59,Nd:60,Pm:61,Sm:62,Eu:63,Gd:64,Tb:65,Dy:66,Ho:67,Er:68,Tm:69,Yb:70,Lu:71,Hf:72,Ta:73,W:74,Re:75,Os:76,Ir:77,Pt:78,Au:79,Hg:80,Tl:81,Pb:82,Bi:83,Po:84,At:85,Rn:86,Fr:87,Ra:88,Ac:89,Th:90,Pa:91,U:92,Np:93,Pu:94,Am:95,Cm:96,Bk:97,Cf:98,Es:99,Fm:100,Md:101,No:102,Lr:103,Rf:104,Db:105,Sg:106,Bh:107,Hs:108,Mt:109,Ds:110,Rg:111,Cn:112,Uut:113,Uuq:114,Uup:115,Uuh:116,Uus:117,Uuo:118}}},{key:"mass",get:function(){return{H:1,He:2,Li:3,Be:4,B:5,b:5,C:6,c:6,N:7,n:7,O:8,o:8,F:9,Ne:10,Na:11,Mg:12,Al:13,Si:14,P:15,p:15,S:16,s:16,Cl:17,Ar:18,K:19,Ca:20,Sc:21,Ti:22,V:23,Cr:24,Mn:25,Fe:26,Co:27,Ni:28,Cu:29,Zn:30,Ga:31,Ge:32,As:33,Se:34,Br:35,Kr:36,Rb:37,Sr:38,Y:39,Zr:40,Nb:41,Mo:42,Tc:43,Ru:44,Rh:45,Pd:46,Ag:47,Cd:48,In:49,Sn:50,Sb:51,Te:52,I:53,Xe:54,Cs:55,Ba:56,La:57,Ce:58,Pr:59,Nd:60,Pm:61,Sm:62,Eu:63,Gd:64,Tb:65,Dy:66,Ho:67,Er:68,Tm:69,Yb:70,Lu:71,Hf:72,Ta:73,W:74,Re:75,Os:76,Ir:77,Pt:78,Au:79,Hg:80,Tl:81,Pb:82,Bi:83,Po:84,At:85,Rn:86,Fr:87,Ra:88,Ac:89,Th:90,Pa:91,U:92,Np:93,Pu:94,Am:95,Cm:96,Bk:97,Cf:98,Es:99,Fm:100,Md:101,No:102,Lr:103,Rf:104,Db:105,Sg:106,Bh:107,Hs:108,Mt:109,Ds:110,Rg:111,Cn:112,Uut:113,Uuq:114,Uup:115,Uuh:116,Uus:117,Uuo:118}}}]),e}();t.exports=l},{"./ArrayHelper":2,"./Ring":11,"./Vertex":15}],4:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Number.MAX_VALUE,i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=e("./MathHelper"),o=e("./Vector2"),l=e("./Line"),s=e("./Vertex"),g=e("./Ring"),d=function(){function e(t,i,r){n(this,e),this.canvas="string"==typeof t||t instanceof String?document.getElementById(t):t,this.ctx=this.canvas.getContext("2d"),this.colors=i,this.opts=r,this.drawingWidth=0,this.drawingHeight=0,this.offsetX=0,this.offsetY=0,this.fontLarge=this.opts.fontSizeLarge+"pt Helvetica, Arial, sans-serif",this.fontSmall=this.opts.fontSizeSmall+"pt Helvetica, Arial, sans-serif",this.updateSize(this.opts.width,this.opts.height),this.ctx.font=this.fontLarge,this.hydrogenWidth=this.ctx.measureText("H").width,this.halfHydrogenWidth=this.hydrogenWidth/2,this.halfBondThickness=this.opts.bondThickness/2}return i(e,[{key:"updateSize",value:function(e,t){this.devicePixelRatio=window.devicePixelRatio||1,this.backingStoreRatio=this.ctx.webkitBackingStorePixelRatio||this.ctx.mozBackingStorePixelRatio||this.ctx.msBackingStorePixelRatio||this.ctx.oBackingStorePixelRatio||this.ctx.backingStorePixelRatio||1,this.ratio=this.devicePixelRatio/this.backingStoreRatio,1===this.ratio?(this.canvas.width=e*this.ratio,this.canvas.height=t*this.ratio):(this.canvas.width=e*this.ratio,this.canvas.height=t*this.ratio,this.canvas.style.width=e+"px",this.canvas.style.height=t+"px",this.ctx.setTransform(this.ratio,0,0,this.ratio,0,0))}},{key:"setTheme",value:function(e){this.colors=e}},{key:"scale",value:function(e){for(var t=-r,n=-r,a=r,o=r,l=0;l<e.length;l++)if(e[l].value.isDrawn){var i=e[l].position;t<i.x&&(t=i.x),n<i.y&&(n=i.y),a>i.x&&(a=i.x),o>i.y&&(o=i.y)}var s=this.opts.padding;t+=s,n+=s,a-=s,o-=s,this.drawingWidth=t-a,this.drawingHeight=n-o;var g=this.canvas.offsetWidth/this.drawingWidth,d=this.canvas.offsetHeight/this.drawingHeight,u=g<d?g:d;this.ctx.scale(u,u),this.offsetX=-a,this.offsetY=-o,g<d?this.offsetY+=this.canvas.offsetHeight/(2*u)-this.drawingHeight/2:this.offsetX+=this.canvas.offsetWidth/(2*u)-this.drawingWidth/2}},{key:"reset",value:function(){this.ctx.setTransform(1,0,0,1,0,0)}},{key:"getColor",value:function(e){return e=e.toUpperCase(),e in this.colors?this.colors[e]:this.colors.C}},{key:"drawCircle",value:function(e,t,n,i){var r=4<arguments.length&&void 0!==arguments[4]?arguments[4]:!0,o=5<arguments.length&&void 0!==arguments[5]&&arguments[5],l=6<arguments.length&&void 0!==arguments[6]?arguments[6]:"",s=this.ctx,g=this.offsetX,d=this.offsetY;s.save(),s.lineWidth=1.5,s.beginPath(),s.arc(e+g,t+d,n,0,a.twoPI,!0),s.closePath(),o?(r?(s.fillStyle="#f00",s.fill()):(s.strokeStyle="#f00",s.stroke()),this.drawDebugText(e,t,l)):r?(s.fillStyle=i,s.fill()):(s.strokeStyle=i,s.stroke()),s.restore()}},{key:"drawLine",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:1,i=this.ctx,a=this.offsetX,o=this.offsetY,s=e.clone().shorten(4),g=s.getLeftVector().clone(),l=s.getRightVector().clone();g.x+=a,g.y+=o,l.x+=a,l.y+=o,t||(i.save(),i.globalCompositeOperation="destination-out",i.beginPath(),i.moveTo(g.x,g.y),i.lineTo(l.x,l.y),i.lineCap="round",i.lineWidth=this.opts.bondThickness+1.2,i.strokeStyle=this.getColor("BACKGROUND"),i.stroke(),i.globalCompositeOperation="source-over",i.restore()),g=e.getLeftVector().clone(),l=e.getRightVector().clone(),g.x+=a,g.y+=o,l.x+=a,l.y+=o,i.save(),i.beginPath(),i.moveTo(g.x,g.y),i.lineTo(l.x,l.y),i.lineCap="round",i.lineWidth=this.opts.bondThickness;var r=this.ctx.createLinearGradient(g.x,g.y,l.x,l.y);r.addColorStop(0.4,this.getColor(e.getLeftElement())||this.getColor("C")),r.addColorStop(0.6,this.getColor(e.getRightElement())||this.getColor("C")),t&&(i.setLineDash([1,1.5]),i.lineWidth=this.opts.bondThickness/1.5),1>n&&(i.globalAlpha=n),i.strokeStyle=r,i.stroke(),i.restore()}},{key:"drawWedge",value:function(e){if(1<arguments.length&&void 0!==arguments[1]?arguments[1]:1,!(isNaN(e.from.x)||isNaN(e.from.y)||isNaN(e.to.x)||isNaN(e.to.y))){var n=this.ctx,i=this.offsetX,a=this.offsetY,s=e.clone().shorten(5),g=s.getLeftVector().clone(),l=s.getRightVector().clone();g.x+=i,g.y+=a,l.x+=i,l.y+=a,g=e.getLeftVector().clone(),l=e.getRightVector().clone(),g.x+=i,g.y+=a,l.x+=i,l.y+=a,n.save();var r=o.normals(g,l);r[0].normalize(),r[1].normalize();var d=e.getRightChiral(),h=g,c=l;d&&(h=l,c=g);var p=o.add(h,o.multiplyScalar(r[0],this.halfBondThickness)),t=o.add(c,o.multiplyScalar(r[0],1.5+this.halfBondThickness)),u=o.add(c,o.multiplyScalar(r[1],1.5+this.halfBondThickness)),v=o.add(h,o.multiplyScalar(r[1],this.halfBondThickness));n.beginPath(),n.moveTo(p.x,p.y),n.lineTo(t.x,t.y),n.lineTo(u.x,u.y),n.lineTo(v.x,v.y);var y=this.ctx.createRadialGradient(l.x,l.y,this.opts.bondLength,l.x,l.y,0);y.addColorStop(0.4,this.getColor(e.getLeftElement())||this.getColor("C")),y.addColorStop(0.6,this.getColor(e.getRightElement())||this.getColor("C")),n.fillStyle=y,n.fill(),n.restore()}}},{key:"drawDashedWedge",value:function(e){if(!(isNaN(e.from.x)||isNaN(e.from.y)||isNaN(e.to.x)||isNaN(e.to.y))){var n=this.ctx,i=this.offsetX,a=this.offsetY,s=e.getLeftVector().clone(),l=e.getRightVector().clone();s.x+=i,s.y+=a,l.x+=i,l.y+=a,n.save();var r=o.normals(s,l);r[0].normalize(),r[1].normalize();var g=e.getRightChiral(),d=e.clone(),u,h,c,p;g?(u=l,h=s,d.shortenRight(1),c=d.getRightVector().clone(),p=d.getLeftVector().clone()):(u=s,h=l,d.shortenLeft(1),c=d.getLeftVector().clone(),p=d.getRightVector().clone()),c.x+=i,c.y+=a,p.x+=i,p.y+=a;var v=o.subtract(h,u).normalize();n.strokeStyle=this.getColor("C"),n.lineCap="round",n.lineWidth=this.opts.bondThickness,n.beginPath();for(var y=e.getLength(),m=1.25/(y/(3*this.opts.bondThickness)),f=!1,b=0;1>b;b+=m){var t=o.multiplyScalar(v,b*y),x=o.add(u,t),k=1.5*b,C=o.multiplyScalar(r[0],k);!f&&0.5<b&&(n.stroke(),n.beginPath(),n.strokeStyle=this.getColor(e.getRightElement())||this.getColor("C"),f=!0),x.subtract(C),n.moveTo(x.x,x.y),x.add(o.multiplyScalar(C,2)),n.lineTo(x.x,x.y)}n.stroke(),n.restore()}}},{key:"drawDebugText",value:function(e,t,n){var i=this.ctx;i.save(),i.font="5px Droid Sans, sans-serif",i.textAlign="start",i.textBaseline="top",i.fillStyle="#ff0000",i.fillText(n,e+this.offsetX,t+this.offsetY),i.restore()}},{key:"drawBall",value:function(e,t,n){var i=this.ctx;i.save(),i.beginPath(),i.arc(e+this.offsetX,t+this.offsetY,this.opts.bondLength/4.5,0,a.twoPI,!1),i.fillStyle=this.getColor(n),i.fill(),i.restore()}},{key:"drawPoint",value:function(e,t,n){var i=this.ctx,r=this.offsetX,o=this.offsetY;i.save(),i.globalCompositeOperation="destination-out",i.beginPath(),i.arc(e+r,t+o,1.5,0,a.twoPI,!0),i.closePath(),i.fill(),i.globalCompositeOperation="source-over",i.beginPath(),i.arc(e+this.offsetX,t+this.offsetY,0.75,0,a.twoPI,!1),i.fillStyle=this.getColor(n),i.fill(),i.restore()}},{key:"drawText",value:function(e,t,n,i,o,l,s,g){var d=8<arguments.length&&void 0!==arguments[8]?arguments[8]:{},u=this.ctx,h=this.offsetX,c=this.offsetY;u.save(),u.textAlign="start",u.textBaseline="alphabetic";var p="",v=0;s&&(p=this.getChargeText(s),u.font=this.fontSmall,v=u.measureText(p).width);var y="0",m=0;0<g&&(y=g.toString(),u.font=this.fontSmall,m=u.measureText(y).width),1===s&&"N"===n&&d.hasOwnProperty("0O")&&d.hasOwnProperty("0O-1")&&(d={"0O":{element:"O",count:2,hydrogenCount:0,previousElement:"C",charge:""}},s=0),u.font=this.fontLarge,u.fillStyle=this.getColor("BACKGROUND");var f=u.measureText(n);f.totalWidth=f.width+v,f.height=parseInt(this.fontLarge,10);var b=f.width>this.opts.fontSizeLarge?f.width:this.opts.fontSizeLarge;b/=1.5,u.globalCompositeOperation="destination-out",u.beginPath(),u.arc(e+h,t+c,b,0,a.twoPI,!0),u.closePath(),u.fill(),u.globalCompositeOperation="source-over";var r=-f.width/2,x=-f.width/2;u.fillStyle=this.getColor(n),u.fillText(n,e+h+r,t+this.opts.halfFontSizeLarge+c),r+=f.width,s&&(u.font=this.fontSmall,u.fillText(p,e+h+r,t-this.opts.fifthFontSizeSmall+c),r+=v),0<g&&(u.font=this.fontSmall,u.fillText(y,e+h+x-m,t-this.opts.fifthFontSizeSmall+c),x-=m),u.font=this.fontLarge;var k=0,C=0;if(1===i){var S=e+h,R=t+c+this.opts.halfFontSizeLarge;k=this.hydrogenWidth,x-=k,"left"===o?S+=x:"right"===o?S+=r:"up"===o&&l?S+=r:"down"===o&&l?S+=r:"up"!==o||l?"down"==o&&!l&&(R+=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,S-=this.halfHydrogenWidth):(R-=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,S-=this.halfHydrogenWidth),u.fillText("H",S,R),r+=k}else if(1<i){var A=e+h,j=t+c+this.opts.halfFontSizeLarge;k=this.hydrogenWidth,u.font=this.fontSmall,C=u.measureText(i).width,x-=k+C,"left"===o?A+=x:"right"===o?A+=r:"up"===o&&l?A+=r:"down"===o&&l?A+=r:"up"!==o||l?"down"==o&&!l&&(j+=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,A-=this.halfHydrogenWidth):(j-=this.opts.fontSizeLarge+this.opts.quarterFontSizeLarge,A-=this.halfHydrogenWidth),u.font=this.fontLarge,u.fillText("H",A,j),u.font=this.fontSmall,u.fillText(i,A+this.halfHydrogenWidth+C,j+this.opts.fifthFontSizeSmall),r+=k+this.halfHydrogenWidth+C}for(var T in d)if(d.hasOwnProperty(T)){var w=0,I=0,P=d[T].element,B=d[T].count,L=d[T].hydrogenCount,E=d[T].charge;u.font=this.fontLarge,1<B&&0<L&&(w=u.measureText("(").width,I=u.measureText(")").width);var D=u.measureText(P).width,N=0,O="",V=0;k=0,0<L&&(k=this.hydrogenWidth),u.font=this.fontSmall,1<B&&(N=u.measureText(B).width),0!==E&&(O=this.getChargeText(E),V=u.measureText(O).width),C=0,1<L&&(C=u.measureText(L).width),u.font=this.fontLarge;var H=e+h,z=t+c+this.opts.halfFontSizeLarge;u.fillStyle=this.getColor(P),0<B&&(x-=N),1<B&&0<L&&("left"===o?(x-=I,u.fillText(")",H+x,z)):(u.fillText("(",H+r,z),r+=w)),"left"===o?(x-=D,u.fillText(P,H+x,z)):(u.fillText(P,H+r,z),r+=D),0<L&&("left"===o?(x-=k+C,u.fillText("H",H+x,z),1<L&&(u.font=this.fontSmall,u.fillText(L,H+x+k,z+this.opts.fifthFontSizeSmall))):(u.fillText("H",H+r,z),r+=k,1<L&&(u.font=this.fontSmall,u.fillText(L,H+r,z+this.opts.fifthFontSizeSmall),r+=C))),u.font=this.fontLarge,1<B&&0<L&&("left"===o?(x-=w,u.fillText("(",H+x,z)):(u.fillText(")",H+r,z),r+=I)),u.font=this.fontSmall,1<B&&("left"===o?u.fillText(B,H+x+w+I+k+C+D,z+this.opts.fifthFontSizeSmall):(u.fillText(B,H+r,z+this.opts.fifthFontSizeSmall),r+=N)),0!==E&&("left"===o?u.fillText(O,H+x+w+I+k+C+D,t-this.opts.fifthFontSizeSmall+c):(u.fillText(O,H+r,t-this.opts.fifthFontSizeSmall+c),r+=V))}u.restore()}},{key:"getChargeText",value:function(e){return 1===e?"+":2===e?"2+":-1===e?"-":-2===e?"2-":""}},{key:"drawDebugPoint",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:"",i=3<arguments.length&&void 0!==arguments[3]?arguments[3]:"#f00";this.drawCircle(e,t,2,i,!0,!0,n)}},{key:"drawAromaticityRing",value:function(e){var t=this.ctx,n=a.apothemFromSideLength(this.opts.bondLength,e.getSize());t.save(),t.strokeStyle=this.getColor("C"),t.lineWidth=this.opts.bondThickness,t.beginPath(),t.arc(e.center.x+this.offsetX,e.center.y+this.offsetY,n-this.opts.bondSpacing,0,2*Math.PI,!0),t.closePath(),t.stroke(),t.restore()}},{key:"clear",value:function(){this.ctx.clearRect(0,0,this.canvas.offsetWidth,this.canvas.offsetHeight)}}]),e}();t.exports=d},{"./Line":8,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],5:[function(e,t){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.PI,o=Math.min,a=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),g=e("./MathHelper"),d=e("./ArrayHelper"),l=e("./Vector2"),s=e("./Line"),u=e("./Vertex"),h=e("./Edge"),c=e("./Atom"),p=e("./Ring"),v=e("./RingConnection"),y=e("./CanvasWrapper"),m=e("./Graph"),f=e("./SSSR"),b=function(){function e(t){i(this,e),this.graph=null,this.doubleBondConfigCount=0,this.doubleBondConfig=null,this.ringIdCounter=0,this.ringConnectionIdCounter=0,this.canvasWrapper=null,this.totalOverlapScore=0,this.defaultOptions={width:500,height:500,bondThickness:0.6,bondLength:15,shortBondLength:0.85,bondSpacing:15*0.18,atomVisualization:"default",isomeric:!0,debug:!1,terminalCarbons:!1,explicitHydrogens:!1,overlapSensitivity:0.42,overlapResolutionIterations:1,compactDrawing:!0,fontSizeLarge:5,fontSizeSmall:3,padding:20,themes:{dark:{C:"#fff",O:"#e74c3c",N:"#3498db",F:"#27ae60",CL:"#16a085",BR:"#d35400",I:"#8e44ad",P:"#d35400",S:"#f1c40f",B:"#e67e22",SI:"#e67e22",H:"#fff",BACKGROUND:"#141414"},light:{C:"#222",O:"#e74c3c",N:"#3498db",F:"#27ae60",CL:"#16a085",BR:"#d35400",I:"#8e44ad",P:"#d35400",S:"#f1c40f",B:"#e67e22",SI:"#e67e22",H:"#222",BACKGROUND:"#fff"}}},this.opts=this.extend(!0,this.defaultOptions,t),this.opts.halfBondSpacing=this.opts.bondSpacing/2,this.opts.bondLengthSq=this.opts.bondLength*this.opts.bondLength,this.opts.halfFontSizeLarge=this.opts.fontSizeLarge/2,this.opts.quarterFontSizeLarge=this.opts.fontSizeLarge/4,this.opts.fifthFontSizeSmall=this.opts.fontSizeSmall/5,this.theme=this.opts.themes.dark}return a(e,[{key:"extend",value:function(){var e=this,t={},n=!1,r=0,i=arguments.length;"[object Boolean]"===Object.prototype.toString.call(arguments[0])&&(n=arguments[0],r++);for(var a=function(i){for(var r in i)Object.prototype.hasOwnProperty.call(i,r)&&(t[r]=n&&"[object Object]"===Object.prototype.toString.call(i[r])?e.extend(!0,t[r],i[r]):i[r])},o;r<i;r++)o=arguments[r],a(o);return t}},{key:"draw",value:function(e,t){var n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:"light",r=3<arguments.length&&void 0!==arguments[3]&&arguments[3];if(this.data=e,this.infoOnly=r,this.infoOnly||(this.canvasWrapper=new y(t,this.opts.themes[n],this.opts)),this.ringIdCounter=0,this.ringConnectionIdCounter=0,this.graph=new m(e,this.opts.isomeric),this.rings=[],this.ringConnections=[],this.originalRings=[],this.originalRingConnections=[],this.bridgedRing=!1,this.doubleBondConfigCount=null,this.doubleBondConfig=null,this.initRings(),this.initHydrogens(),!this.infoOnly){this.position(),this.restoreRingInformation(),this.resolvePrimaryOverlaps();var l=this.getOverlapScore();this.totalOverlapScore=this.getOverlapScore().total;for(var s=0;s<this.opts.overlapResolutionIterations;s++)for(var o=0,i;o<this.graph.edges.length;o++)if(i=this.graph.edges[o],this.isEdgeRotatable(i)){var d=this.graph.getTreeDepth(i.sourceId,i.targetId),u=this.graph.getTreeDepth(i.targetId,i.sourceId),h=i.targetId,a=i.sourceId;d>u&&(h=i.sourceId,a=i.targetId);var c=this.getSubtreeOverlapScore(a,h,l.vertexScores);if(c.value>this.opts.overlapSensitivity){var p=this.graph.vertices[h],v=this.graph.vertices[a],f=v.getNeighbours(h);if(1===f.length){var b=this.graph.vertices[f[0]],x=b.position.getRotateAwayFromAngle(p.position,v.position,g.toRad(120));this.rotateSubtree(b.id,v.id,x,v.position);var k=this.getOverlapScore().total;k>this.totalOverlapScore?this.rotateSubtree(b.id,v.id,-x,v.position):this.totalOverlapScore=k}else if(2===f.length){if(0!==v.value.rings.length&&0!==p.value.rings.length)continue;var C=this.graph.vertices[f[0]],S=this.graph.vertices[f[1]];if(1===C.value.rings.length&&1===S.value.rings.length){if(C.value.rings[0]!==S.value.rings[0])continue;}else if(0!==C.value.rings.length||0!==S.value.rings.length)continue;else{var R=C.position.getRotateAwayFromAngle(p.position,v.position,g.toRad(120)),A=S.position.getRotateAwayFromAngle(p.position,v.position,g.toRad(120));this.rotateSubtree(C.id,v.id,R,v.position),this.rotateSubtree(S.id,v.id,A,v.position);var j=this.getOverlapScore().total;j>this.totalOverlapScore?(this.rotateSubtree(C.id,v.id,-R,v.position),this.rotateSubtree(S.id,v.id,-A,v.position)):this.totalOverlapScore=j}}l=this.getOverlapScore()}}this.resolveSecondaryOverlaps(l.scores),this.opts.isomeric&&this.annotateStereochemistry(),this.opts.compactDrawing&&"default"===this.opts.atomVisualization&&this.initPseudoElements(),this.rotateDrawing(),this.canvasWrapper.scale(this.graph.vertices),this.drawEdges(this.opts.debug),this.drawVertices(this.opts.debug),this.canvasWrapper.reset()}}},{key:"edgeRingCount",value:function(e){var t=this.graph.edges[e],n=this.graph.vertices[t.sourceId],i=this.graph.vertices[t.targetId];return o(n.value.rings.length,i.value.rings.length)}},{key:"getBridgedRings",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isBridged&&e.push(this.rings[t]);return e}},{key:"getFusedRings",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isFused&&e.push(this.rings[t]);return e}},{key:"getSpiros",value:function(){for(var e=[],t=0;t<this.rings.length;t++)this.rings[t].isSpiro&&e.push(this.rings[t]);return e}},{key:"printRingInfo",value:function(){for(var e="",t=0,n;t<this.rings.length;t++)n=this.rings[t],e+=n.id+";",e+=n.members.length+";",e+=n.neighbours.length+";",e+=n.isSpiro?"true;":"false;",e+=n.isFused?"true;":"false;",e+=n.isBridged?"true;":"false;",e+=n.rings.length+";",e+="\n";return e}},{key:"rotateDrawing",value:function(){for(var e=0,t=0,n=0,r=0,i;r<this.graph.vertices.length;r++)if(i=this.graph.vertices[r],!!i.value.isDrawn)for(var a=r+1,o;a<this.graph.vertices.length;a++)if(o=this.graph.vertices[a],!!o.value.isDrawn){var s=i.position.distanceSq(o.position);s>n&&(n=s,e=r,t=a)}var g=-l.subtract(this.graph.vertices[e].position,this.graph.vertices[t].position).angle();if(!isNaN(g)){var d=g%0.523599;0.2617995>d?g-=d:g+=0.523599-d;for(var r=0;r<this.graph.vertices.length;r++)r!==t&&this.graph.vertices[r].position.rotateAround(g,this.graph.vertices[t].position);for(var r=0;r<this.rings.length;r++)this.rings[r].center.rotateAround(g,this.graph.vertices[t].position)}}},{key:"getTotalOverlapScore",value:function(){return this.totalOverlapScore}},{key:"getRingCount",value:function(){return this.rings.length}},{key:"hasBridgedRing",value:function(){return this.bridgedRing}},{key:"getHeavyAtomCount",value:function(){for(var e=0,t=0;t<this.graph.vertices.length;t++)"H"!==this.graph.vertices[t].value.element&&e++;return e}},{key:"getMolecularFormula",value:function(){for(var t="",n=new Map,e=0,i;e<this.graph.vertices.length;e++)if(i=this.graph.vertices[e].value,n.has(i.element)?n.set(i.element,n.get(i.element)+1):n.set(i.element,1),i.bracket&&!i.bracket.chirality&&(n.has("H")?n.set("H",n.get("H")+i.bracket.hcount):n.set("H",i.bracket.hcount)),!i.bracket){var r=c.maxBonds[i.element]-i.bondCount;i.isPartOfAromaticRing&&r--,n.has("H")?n.set("H",n.get("H")+r):n.set("H",r)}if(n.has("C")){var a=n.get("C");t+="C"+(1<a?a:""),n.delete("C")}if(n.has("H")){var o=n.get("H");t+="H"+(1<o?o:""),n.delete("H")}var l=Object.keys(c.atomicNumbers).sort();return l.map(function(i){if(n.has(i)){var e=n.get(i);t+=i+(1<e?e:"")}}),t}},{key:"getRingbondType",value:function(e,t){if(1>e.value.getRingbondCount()||1>t.value.getRingbondCount())return null;for(var n=0;n<e.value.ringbonds.length;n++)for(var i=0;i<t.value.ringbonds.length;i++)if(e.value.ringbonds[n].id===t.value.ringbonds[i].id)return"-"===e.value.ringbonds[n].bondType?t.value.ringbonds[i].bond:e.value.ringbonds[n].bond;return null}},{key:"initRings",value:function(){for(var e=new Map,t=this.graph.vertices.length-1,i;0<=t;t--)if(i=this.graph.vertices[t],0!==i.value.ringbonds.length)for(var r=0;r<i.value.ringbonds.length;r++){var o=i.value.ringbonds[r].id,l=i.value.ringbonds[r].bond;if(!e.has(o))e.set(o,[i.id,l]);else{var s=i.id,g=e.get(o)[0],d=e.get(o)[1],u=new h(s,g,1);u.setBondType(d||l||"-");var c=this.graph.addEdge(u),y=this.graph.vertices[g];i.addRingbondChild(g,r),i.value.addNeighbouringElement(y.value.element),y.addRingbondChild(s,r),y.value.addNeighbouringElement(i.value.element),i.edges.push(c),y.edges.push(c),e.delete(o)}}var m=f.getRings(this.graph);if(null!==m){for(var t=0;t<m.length;t++)for(var x=[].concat(n(m[t])),k=this.addRing(new p(x)),r=0;r<x.length;r++)this.graph.vertices[x[r]].value.rings.push(k);for(var t=0;t<this.rings.length-1;t++)for(var r=t+1;r<this.rings.length;r++){var C=this.rings[t],a=this.rings[r],b=new v(C,a);0<b.vertices.size&&this.addRingConnection(b)}for(var t=0,S;t<this.rings.length;t++)S=this.rings[t],S.neighbours=v.getNeighbours(this.ringConnections,S.id);for(var t=0,R;t<this.rings.length;t++)R=this.rings[t],this.graph.vertices[R.members[0]].value.addAnchoredRing(R.id);for(this.backupRingInformation();0<this.rings.length;){for(var A=-1,t=0,j;t<this.rings.length;t++)j=this.rings[t],this.isPartOfBridgedRing(j.id)&&!j.isBridged&&(A=j.id);if(-1===A)break;var T=this.getRing(A),w=this.getBridgedRingRings(T.id);this.bridgedRing=!0,this.createBridgedRing(w,T.members[0]);for(var t=0;t<w.length;t++)this.removeRing(w[t])}}}},{key:"initHydrogens",value:function(){if(!this.opts.explicitHydrogens)for(var e=0,t;e<this.graph.vertices.length;e++)if(t=this.graph.vertices[e],"H"===t.value.element){var n=this.graph.vertices[t.neighbours[0]];n.value.hasHydrogen=!0,(!n.value.isStereoCenter||2>n.value.rings.length&&!n.value.bridgedRing||n.value.bridgedRing&&2>n.value.originalRings.length)&&(t.value.isDrawn=!1)}}},{key:"getBridgedRingRings",value:function(e){var t=[],a=this;return function e(o){var r=a.getRing(o);t.push(o);for(var l=0,i;l<r.neighbours.length;l++)i=r.neighbours[l],-1===t.indexOf(i)&&i!==o&&v.isBridge(a.ringConnections,a.graph.vertices,o,i)&&e(i)}(e),d.unique(t)}},{key:"isPartOfBridgedRing",value:function(e){for(var t=0;t<this.ringConnections.length;t++)if(this.ringConnections[t].containsRing(e)&&this.ringConnections[t].isBridge(this.graph.vertices))return!0;return!1}},{key:"createBridgedRing",value:function(e){for(var t=new Set,r=new Set,a=new Set,o=0,i;o<e.length;o++){i=this.getRing(e[o]),i.isPartOfBridged=!0;for(var l=0;l<i.members.length;l++)r.add(i.members[l]);for(var l=0,s;l<i.neighbours.length;l++)s=i.neighbours[l],-1===e.indexOf(s)&&a.add(i.neighbours[l])}var g=new Set,u=!0,h=!1,c;try{for(var v=r[Symbol.iterator](),y;!(u=(y=v.next()).done);u=!0){var m=y.value,f=this.graph.vertices[m],b=d.intersection(e,f.value.rings);1===f.value.rings.length||1===b.length?t.add(f.id):g.add(f.id)}}catch(e){h=!0,c=e}finally{try{!u&&v.return&&v.return()}finally{if(h)throw c}}var x=[],k=!0,C=!1,S;try{for(var R=g[Symbol.iterator](),A;!(k=(A=R.next()).done);k=!0){for(var j=A.value,T=this.graph.vertices[j],w=!1,I=0;I<T.edges.length;I++)1===this.edgeRingCount(T.edges[I])&&(w=!0);w?(T.value.isBridgeNode=!0,t.add(T.id)):(T.value.isBridge=!0,t.add(T.id))}}catch(e){C=!0,S=e}finally{try{!k&&R.return&&R.return()}finally{if(C)throw S}}var P=new p([].concat(n(t)));P.isBridged=!0,P.neighbours=[].concat(n(a));for(var o=0;o<e.length;o++)P.rings.push(this.getRing(e[o]).clone());this.addRing(P);for(var o=0;o<P.members.length;o++)this.graph.vertices[P.members[o]].value.bridgedRing=P.id;for(var o=0,B;o<x.length;o++)B=this.graph.vertices[x[o]],B.value.rings=[];var L=!0,E=!1,D;try{for(var N=t[Symbol.iterator](),O;!(L=(O=N.next()).done);L=!0){var V=O.value,H=this.graph.vertices[V];H.value.rings=d.removeAll(H.value.rings,e),H.value.rings.push(P.id)}}catch(e){E=!0,D=e}finally{try{!L&&N.return&&N.return()}finally{if(E)throw D}}for(var o=0;o<e.length;o++)for(var l=o+1;l<e.length;l++)this.removeRingConnectionsBetween(e[o],e[l]);var z=!0,F=!1,W;try{for(var _=a[Symbol.iterator](),M;!(z=(M=_.next()).done);z=!0){for(var q=M.value,U=this.getRingConnections(q,e),l=0;l<U.length;l++)this.getRingConnection(U[l]).updateOther(P.id,q);this.getRing(q).neighbours.push(P.id)}}catch(e){F=!0,W=e}finally{try{!z&&_.return&&_.return()}finally{if(F)throw W}}return P}},{key:"areVerticesInSameRing",value:function(e,t){for(var n=0;n<e.value.rings.length;n++)for(var i=0;i<t.value.rings.length;i++)if(e.value.rings[n]===t.value.rings[i])return!0;return!1}},{key:"getCommonRings",value:function(e,t){for(var n=[],r=0;r<e.value.rings.length;r++)for(var i=0;i<t.value.rings.length;i++)e.value.rings[r]==t.value.rings[i]&&n.push(e.value.rings[r]);return n}},{key:"getLargestOrAromaticCommonRing",value:function(e,t){for(var n=this.getCommonRings(e,t),r=0,a=null,o=0;o<n.length;o++){var i=this.getRing(n[o]),l=i.getSize();if(i.isBenzeneLike(this.graph.vertices))return i;l>r&&(r=l,a=i)}return a}},{key:"getVerticesAt",value:function(e,t,n){for(var r=[],a=0,i;a<this.graph.vertices.length;a++)if(i=this.graph.vertices[a],i.id!==n&&i.positioned){var o=e.distanceSq(i.position);o<=t*t&&r.push(i.id)}return r}},{key:"getClosestVertex",value:function(e){for(var t=99999,n=null,r=0,i;r<this.graph.vertices.length;r++)if(i=this.graph.vertices[r],i.id!==e.id){var a=e.position.distanceSq(i.position);a<t&&(t=a,n=i)}return n}},{key:"addRing",value:function(e){return e.id=this.ringIdCounter++,this.rings.push(e),e.id}},{key:"removeRing",value:function(e){this.rings=this.rings.filter(function(t){return t.id!==e}),this.ringConnections=this.ringConnections.filter(function(t){return t.firstRingId!==e&&t.secondRingId!==e});for(var t=0,n;t<this.rings.length;t++)n=this.rings[t],n.neighbours=n.neighbours.filter(function(t){return t!==e})}},{key:"getRing",value:function(e){for(var t=0;t<this.rings.length;t++)if(this.rings[t].id==e)return this.rings[t]}},{key:"addRingConnection",value:function(e){return e.id=this.ringConnectionIdCounter++,this.ringConnections.push(e),e.id}},{key:"removeRingConnection",value:function(e){this.ringConnections=this.ringConnections.filter(function(t){return t.id!==e})}},{key:"removeRingConnectionsBetween",value:function(e,t){for(var n=[],r=0,i;r<this.ringConnections.length;r++)i=this.ringConnections[r],(i.firstRingId===e&&i.secondRingId===t||i.firstRingId===t&&i.secondRingId===e)&&n.push(i.id);for(var r=0;r<n.length;r++)this.removeRingConnection(n[r])}},{key:"getRingConnection",value:function(e){for(var t=0;t<this.ringConnections.length;t++)if(this.ringConnections[t].id==e)return this.ringConnections[t]}},{key:"getRingConnections",value:function(e,t){for(var n=[],r=0,i;r<this.ringConnections.length;r++){i=this.ringConnections[r];for(var a=0,o;a<t.length;a++)o=t[a],(i.firstRingId===e&&i.secondRingId===o||i.firstRingId===o&&i.secondRingId===e)&&n.push(i.id)}return n}},{key:"getOverlapScore",value:function(){for(var e=0,t=new Float32Array(this.graph.vertices.length),n=0;n<this.graph.vertices.length;n++)t[n]=0;for(var n=0,r;n<this.graph.vertices.length;n++)for(r=this.graph.vertices.length;--r>n;){var o=this.graph.vertices[n],a=this.graph.vertices[r];if(o.value.isDrawn&&a.value.isDrawn){var s=l.subtract(o.position,a.position).lengthSq();if(s<this.opts.bondLengthSq){var g=(this.opts.bondLength-Math.sqrt(s))/this.opts.bondLength;e+=g,t[n]+=g,t[r]+=g}}}for(var d=[],n=0;n<this.graph.vertices.length;n++)d.push({id:n,score:t[n]});return d.sort(function(e,t){return t.score-e.score}),{total:e,scores:d,vertexScores:t}}},{key:"chooseSide",value:function(e,t,n){for(var r=e.getNeighbours(t.id),a=t.getNeighbours(e.id),o=r.length,l=a.length,s=d.merge(r,a),g=[0,0],u=0,i;u<s.length;u++)i=this.graph.vertices[s[u]].position,i.sameSideAs(e.position,t.position,n[0])?g[0]++:g[1]++;for(var h=[0,0],u=0,c;u<this.graph.vertices.length;u++)c=this.graph.vertices[u].position,c.sameSideAs(e.position,t.position,n[0])?h[0]++:h[1]++;return{totalSideCount:h,totalPosition:h[0]>h[1]?0:1,sideCount:g,position:g[0]>g[1]?0:1,anCount:o,bnCount:l}}},{key:"setRingCenter",value:function(e){for(var t=e.getSize(),n=new l(0,0),r=0;r<t;r++)n.add(this.graph.vertices[e.members[r]].position);e.center=n.divide(t)}},{key:"getSubringCenter",value:function(e,t){for(var n=t.value.originalRings,r=e.center,a=Number.MAX_VALUE,o=0;o<n.length;o++)for(var i=0;i<e.rings.length;i++)n[o]===e.rings[i].id&&e.rings[i].getSize()<a&&(r=e.rings[i].center,a=e.rings[i].getSize());return r}},{key:"drawEdges",value:function(e){var t=this,n=Array(this.graph.edges.length);if(n.fill(!1),this.graph.traverseBF(0,function(r){for(var a=t.graph.getEdges(r.id),o=0,i;o<a.length;o++)i=a[o],n[i]||(n[i]=!0,t.drawEdge(i,e))}),!this.bridgedRing)for(var r=0,i;r<this.rings.length;r++)i=this.rings[r],this.isRingAromatic(i)&&this.canvasWrapper.drawAromaticityRing(i)}},{key:"drawEdge",value:function(e,t){var n=this,i=this.graph.edges[e],r=this.graph.vertices[i.sourceId],o=this.graph.vertices[i.targetId],g=r.value.element,u=o.value.element;if(r.value.isDrawn&&o.value.isDrawn||"default"!==this.opts.atomVisualization){var h=r.position,a=o.position,c=this.getEdgeNormals(i),p=d.clone(c);if(p[0].multiplyScalar(10).add(h),p[1].multiplyScalar(10).add(h),"="===i.bondType||"="===this.getRingbondType(r,o)||i.isPartOfAromaticRing&&this.bridgedRing){var v=this.areVerticesInSameRing(r,o),y=this.chooseSide(r,o,p);if(v){var m=this.getLargestOrAromaticCommonRing(r,o),f=m.center;c[0].multiplyScalar(n.opts.bondSpacing),c[1].multiplyScalar(n.opts.bondSpacing);var b=null;b=f.sameSideAs(r.position,o.position,l.add(h,c[0]))?new s(l.add(h,c[0]),l.add(a,c[0]),g,u):new s(l.add(h,c[1]),l.add(a,c[1]),g,u),b.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),i.isPartOfAromaticRing?this.canvasWrapper.drawLine(b,!0):this.canvasWrapper.drawLine(b),this.canvasWrapper.drawLine(new s(h,a,g,u))}else if(i.center||r.isTerminal()&&o.isTerminal()){c[0].multiplyScalar(n.opts.halfBondSpacing),c[1].multiplyScalar(n.opts.halfBondSpacing);var x=new s(l.add(h,c[0]),l.add(a,c[0]),g,u),k=new s(l.add(h,c[1]),l.add(a,c[1]),g,u);this.canvasWrapper.drawLine(x),this.canvasWrapper.drawLine(k)}else if(0==y.anCount&&1<y.bnCount||0==y.bnCount&&1<y.anCount){c[0].multiplyScalar(n.opts.halfBondSpacing),c[1].multiplyScalar(n.opts.halfBondSpacing);var C=new s(l.add(h,c[0]),l.add(a,c[0]),g,u),S=new s(l.add(h,c[1]),l.add(a,c[1]),g,u);this.canvasWrapper.drawLine(C),this.canvasWrapper.drawLine(S)}else if(y.sideCount[0]>y.sideCount[1]){c[0].multiplyScalar(n.opts.bondSpacing),c[1].multiplyScalar(n.opts.bondSpacing);var R=new s(l.add(h,c[0]),l.add(a,c[0]),g,u);R.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(R),this.canvasWrapper.drawLine(new s(h,a,g,u))}else if(y.sideCount[0]<y.sideCount[1]){c[0].multiplyScalar(n.opts.bondSpacing),c[1].multiplyScalar(n.opts.bondSpacing);var A=new s(l.add(h,c[1]),l.add(a,c[1]),g,u);A.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(A),this.canvasWrapper.drawLine(new s(h,a,g,u))}else if(y.totalSideCount[0]>y.totalSideCount[1]){c[0].multiplyScalar(n.opts.bondSpacing),c[1].multiplyScalar(n.opts.bondSpacing);var j=new s(l.add(h,c[0]),l.add(a,c[0]),g,u);j.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(j),this.canvasWrapper.drawLine(new s(h,a,g,u))}else if(y.totalSideCount[0]<=y.totalSideCount[1]){c[0].multiplyScalar(n.opts.bondSpacing),c[1].multiplyScalar(n.opts.bondSpacing);var T=new s(l.add(h,c[1]),l.add(a,c[1]),g,u);T.shorten(this.opts.bondLength-this.opts.shortBondLength*this.opts.bondLength),this.canvasWrapper.drawLine(T),this.canvasWrapper.drawLine(new s(h,a,g,u))}else;}else if("#"===i.bondType){c[0].multiplyScalar(n.opts.bondSpacing/1.5),c[1].multiplyScalar(n.opts.bondSpacing/1.5);var w=new s(l.add(h,c[0]),l.add(a,c[0]),g,u),I=new s(l.add(h,c[1]),l.add(a,c[1]),g,u);this.canvasWrapper.drawLine(w),this.canvasWrapper.drawLine(I),this.canvasWrapper.drawLine(new s(h,a,g,u))}else if("."===i.bondType);else{var P=r.value.isStereoCenter,B=o.value.isStereoCenter;"up"===i.wedge?this.canvasWrapper.drawWedge(new s(h,a,g,u,P,B)):"down"===i.wedge?this.canvasWrapper.drawDashedWedge(new s(h,a,g,u,P,B)):this.canvasWrapper.drawLine(new s(h,a,g,u,P,B))}if(t){var L=l.midpoint(h,a);this.canvasWrapper.drawDebugText(L.x,L.y,"e: "+e)}}}},{key:"drawVertices",value:function(e){for(var t=this.graph.vertices.length,t=0;t<this.graph.vertices.length;t++){var n=this.graph.vertices[t],i=n.value,o=0,s=0,g=n.value.bondCount,u=i.element,h=c.maxBonds[u]-g,p=n.getTextDirection(this.graph.vertices),v=(this.opts.terminalCarbons||"C"!==u||i.hasAttachedPseudoElements)&&n.isTerminal(),y="C"===i.element;if("N"===i.element&&i.isPartOfAromaticRing&&(h=0),i.bracket&&(h=i.bracket.hcount,o=i.bracket.charge,s=i.bracket.isotope),"allballs"===this.opts.atomVisualization)this.canvasWrapper.drawBall(n.position.x,n.position.y,u);else if(i.isDrawn&&(!y||i.drawExplicit||v||i.hasAttachedPseudoElements)||1===this.graph.vertices.length)"default"===this.opts.atomVisualization?this.canvasWrapper.drawText(n.position.x,n.position.y,u,h,p,v,o,s,i.getAttachedPseudoElements()):"balls"===this.opts.atomVisualization&&this.canvasWrapper.drawBall(n.position.x,n.position.y,u);else if(2===n.getNeighbourCount()&&!0==n.forcePositioned){var m=this.graph.vertices[n.neighbours[0]].position,a=this.graph.vertices[n.neighbours[1]].position,f=l.threePointangle(n.position,m,a);0.1>Math.abs(r-f)&&this.canvasWrapper.drawPoint(n.position.x,n.position.y,u)}if(e){var b="v: "+n.id+" "+d.print(i.ringbonds);this.canvasWrapper.drawDebugText(n.position.x,n.position.y,b)}else;}if(this.opts.debug)for(var t=0,x;t<this.rings.length;t++)x=this.rings[t].center,this.canvasWrapper.drawDebugPoint(x.x,x.y,"r: "+this.rings[t].id)}},{key:"position",value:function(){for(var e=null,t=0;t<this.graph.vertices.length;t++)if(null!==this.graph.vertices[t].value.bridgedRing){e=this.graph.vertices[t];break}for(var t=0;t<this.rings.length;t++)this.rings[t].isBridged&&(e=this.graph.vertices[this.rings[t].members[0]]);0<this.rings.length&&null===e&&(e=this.graph.vertices[this.rings[0].members[0]]),null===e&&(e=this.graph.vertices[0]),this.createNextBond(e,null,0)}},{key:"backupRingInformation",value:function(){this.originalRings=[],this.originalRingConnections=[];for(var e=0;e<this.rings.length;e++)this.originalRings.push(this.rings[e]);for(var e=0;e<this.ringConnections.length;e++)this.originalRingConnections.push(this.ringConnections[e]);for(var e=0;e<this.graph.vertices.length;e++)this.graph.vertices[e].value.backupRings()}},{key:"restoreRingInformation",value:function(){var e=this.getBridgedRings();this.rings=[],this.ringConnections=[];for(var t=0,n;t<e.length;t++){n=e[t];for(var i=0,r;i<n.rings.length;i++)r=n.rings[i],this.originalRings[r.id].center=r.center}for(var t=0;t<this.originalRings.length;t++)this.rings.push(this.originalRings[t]);for(var t=0;t<this.originalRingConnections.length;t++)this.ringConnections.push(this.originalRingConnections[t]);for(var t=0;t<this.graph.vertices.length;t++)this.graph.vertices[t].value.restoreRings()}},{key:"createRing",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,o=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null;if(!e.positioned){t=t?t:new l(0,0);var s=e.getOrderedNeighbours(this.ringConnections),d=n?l.subtract(n.position,t).angle():0,u=g.polyCircumradius(this.opts.bondLength,e.getSize()),h=g.centralAngle(e.getSize());e.centralAngle=h;var c=d,a=this,p=n?n.id:null;if(-1===e.members.indexOf(p)&&(n&&(n.positioned=!1),p=e.members[0]),e.isBridged){this.graph.kkLayout(e.members.slice(),t,n.id,e,this.opts.bondLength),e.positioned=!0,this.setRingCenter(e),t=e.center;for(var y=0;y<e.rings.length;y++)this.setRingCenter(e.rings[y])}else e.eachMember(this.graph.vertices,function(n){var i=a.graph.vertices[n];i.positioned||i.setPosition(t.x+Math.cos(c)*u,t.y+Math.sin(c)*u),c+=h,(!e.isBridged||3>e.rings.length)&&(i.angle=c,i.positioned=!0)},p,o?o.id:null);e.positioned=!0,e.center=t;for(var y=0,i;y<s.length;y++)if(i=this.getRing(s[y].neighbour),!i.positioned){var m=v.getVertices(this.ringConnections,e.id,i.id);if(2===m.length){e.isFused=!0,i.isFused=!0;var f=this.graph.vertices[m[0]],b=this.graph.vertices[m[1]],x=l.midpoint(f.position,b.position),k=l.normals(f.position,b.position);k[0].normalize(),k[1].normalize();var C=g.polyCircumradius(this.opts.bondLength,i.getSize()),r=g.apothem(C,i.getSize());k[0].multiplyScalar(r).add(x),k[1].multiplyScalar(r).add(x);var S=k[0];l.subtract(t,k[1]).lengthSq()>l.subtract(t,k[0]).lengthSq()&&(S=k[1]);var R=l.subtract(f.position,S),A=l.subtract(b.position,S);-1===R.clockwise(A)?!i.positioned&&this.createRing(i,S,f,b):!i.positioned&&this.createRing(i,S,b,f)}else if(1===m.length){e.isSpiro=!0,i.isSpiro=!0;var T=this.graph.vertices[m[0]],w=l.subtract(t,T.position);w.invert(),w.normalize();var I=g.polyCircumradius(this.opts.bondLength,i.getSize());w.multiplyScalar(I),w.add(T.position),i.positioned||this.createRing(i,w,T)}}for(var y=0;y<e.members.length;y++)for(var P=this.graph.vertices[e.members[y]],B=P.neighbours,L=0,j;L<B.length;L++)(j=this.graph.vertices[B[L]],!j.positioned)&&(j.value.isConnectedToRing=!0,this.createNextBond(j,P,0))}}},{key:"rotateSubtree",value:function(e,t,n,r){var a=this;this.graph.traverseTree(e,t,function(e){e.position.rotateAround(n,r);for(var t=0,i;t<e.value.anchoredRings.length;t++)i=a.rings[e.value.anchoredRings[t]],i&&i.center.rotateAround(n,r)})}},{key:"getSubtreeOverlapScore",value:function(e,t,n){var i=this,r=0,a=new l(0,0),o=0;return this.graph.traverseTree(e,t,function(e){if(e.value.isDrawn){var t=n[e.id];t>i.opts.overlapSensitivity&&(r+=t,o++);var l=i.graph.vertices[e.id].position.clone();l.multiplyScalar(t),a.add(l)}}),a.divide(r),{value:r/o,center:a}}},{key:"getCurrentCenterOfMass",value:function(){for(var e=new l(0,0),t=0,n=0,i;n<this.graph.vertices.length;n++)i=this.graph.vertices[n],i.positioned&&(e.add(i.position),t++);return e.divide(t)}},{key:"getCurrentCenterOfMassInNeigbourhood",value:function(e){for(var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:2*this.opts.bondLength,n=new l(0,0),r=0,a=0,i;a<this.graph.vertices.length;a++)i=this.graph.vertices[a],i.positioned&&e.distanceSq(i.position)<t*t&&(n.add(i.position),r++);return n.divide(r)}},{key:"resolvePrimaryOverlaps",value:function(){for(var e=[],t=Array(this.graph.vertices.length),n=0,i;n<this.rings.length;n++){i=this.rings[n];for(var o=0,l;o<i.members.length;o++)if(l=this.graph.vertices[i.members[o]],!t[l.id]){t[l.id]=!0;var s=this.getNonRingNeighbours(l.id);if(1<s.length){for(var g=[],d=0;d<l.value.rings.length;d++)g.push(l.value.rings[d]);e.push({common:l,rings:g,vertices:s})}else if(1===s.length&&2===l.value.rings.length){for(var u=[],d=0;d<l.value.rings.length;d++)u.push(l.value.rings[d]);e.push({common:l,rings:u,vertices:s})}}}for(var n=0,h;n<e.length;n++)if(h=e[n],2===h.vertices.length){var c=h.vertices[0],a=h.vertices[1];if(!c.value.isDrawn||!a.value.isDrawn)continue;var p=(2*r-this.getRing(h.rings[0]).getAngle())/6;this.rotateSubtree(c.id,h.common.id,p,h.common.position),this.rotateSubtree(a.id,h.common.id,-p,h.common.position);var v=this.getOverlapScore(),y=this.getSubtreeOverlapScore(c.id,h.common.id,v.vertexScores),m=this.getSubtreeOverlapScore(a.id,h.common.id,v.vertexScores),f=y.value+m.value;this.rotateSubtree(c.id,h.common.id,-2*p,h.common.position),this.rotateSubtree(a.id,h.common.id,2*p,h.common.position),v=this.getOverlapScore(),y=this.getSubtreeOverlapScore(c.id,h.common.id,v.vertexScores),m=this.getSubtreeOverlapScore(a.id,h.common.id,v.vertexScores),y.value+m.value>f&&(this.rotateSubtree(c.id,h.common.id,2*p,h.common.position),this.rotateSubtree(a.id,h.common.id,-2*p,h.common.position))}else 1!==h.vertices.length||2!==h.rings.length}},{key:"resolveSecondaryOverlaps",value:function(e){for(var t=0;t<e.length;t++)if(e[t].score>this.opts.overlapSensitivity){var n=this.graph.vertices[e[t].id];if(n.isTerminal()){var i=this.getClosestVertex(n);if(i){var r=null;r=i.isTerminal()?0===i.id?this.graph.vertices[1].position:i.previousPosition:0===i.id?this.graph.vertices[1].position:i.position;var a=0===n.id?this.graph.vertices[1].position:n.previousPosition;n.position.rotateAwayFrom(r,a,g.toRad(20))}}}}},{key:"getLastVertexWithAngle",value:function(e){for(var t=0,n=null;!t&&e;)n=this.graph.vertices[e],t=n.angle,e=n.parentVertexId;return n}},{key:"createNextBond",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:null,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0,u=3<arguments.length&&void 0!==arguments[3]&&arguments[3],h=4<arguments.length&&void 0!==arguments[4]&&arguments[4];if(!e.positioned||h){var c=!1;if(t){var p=this.graph.getEdge(e.id,t.id);("/"===p.bondType||"\\"===p.bondType)&&1==++this.doubleBondConfigCount%2&&null===this.doubleBondConfig&&(this.doubleBondConfig=p.bondType,c=!0,null===t.parentVertexId&&e.value.branchBond&&("/"===this.doubleBondConfig?this.doubleBondConfig="\\":"\\"===this.doubleBondConfig&&(this.doubleBondConfig="/")))}if(!h)if(!t){var m=new l(this.opts.bondLength,0);m.rotate(g.toRad(-60)),e.previousPosition=m,e.setPosition(this.opts.bondLength,0),e.angle=g.toRad(-60),null===e.value.bridgedRing&&(e.positioned=!0)}else if(0<t.value.rings.length){var f=t.neighbours,b=null,k=new l(0,0);if(null===t.value.bridgedRing&&1<t.value.rings.length)for(var C=0,i;C<f.length;C++)if(i=this.graph.vertices[f[C]],d.containsAll(i.value.rings,t.value.rings)){b=i;break}if(null===b){for(var C=0,S;C<f.length;C++)S=this.graph.vertices[f[C]],S.positioned&&this.areVerticesInSameRing(S,t)&&k.add(l.subtract(S.position,t.position));k.invert().normalize().multiplyScalar(this.opts.bondLength).add(t.position)}else k=b.position.clone().rotateAround(Math.PI,t.position);e.previousPosition=t.position,e.setPositionFromVector(k),e.positioned=!0}else{var v=new l(this.opts.bondLength,0);v.rotate(n),v.add(t.position),e.setPositionFromVector(v),e.previousPosition=t.position,e.positioned=!0}if(null!==e.value.bridgedRing){var R=this.getRing(e.value.bridgedRing);if(!R.positioned){var A=l.subtract(e.previousPosition,e.position);A.invert(),A.normalize();var j=g.polyCircumradius(this.opts.bondLength,R.members.length);A.multiplyScalar(j),A.add(e.position),this.createRing(R,A,e)}}else if(0<e.value.rings.length){var r=this.getRing(e.value.rings[0]);if(!r.positioned){var T=l.subtract(e.previousPosition,e.position);T.invert(),T.normalize();var I=g.polyCircumradius(this.opts.bondLength,r.getSize());T.multiplyScalar(I),T.add(e.position),this.createRing(r,T,e)}}else{for(var P=e.value.isStereoCenter,B=e.getNeighbours(),L=[],C=0;C<B.length;C++)this.graph.vertices[B[C]].value.isDrawn&&L.push(B[C]);t&&(L=d.remove(L,t.id));var E=e.getAngle();if(1===L.length){var D=this.graph.vertices[L[0]];if("#"===e.value.bondType||t&&"#"===t.value.bondType||"="===e.value.bondType&&t&&0===t.value.rings.length&&"="===t.value.bondType&&"-"!==e.value.branchBond){if(e.value.drawExplicit=!1,t){var N=this.graph.getEdge(e.id,t.id);N.center=!0}var O=this.graph.getEdge(e.id,D.id);O.center=!0,("#"===e.value.bondType||t&&"#"===t.value.bondType)&&(D.angle=0),D.drawExplicit=!0,this.createNextBond(D,e,E+D.angle)}else if(t&&0<t.value.rings.length){var V=g.toRad(60),H=-V,F=new l(this.opts.bondLength,0),W=new l(this.opts.bondLength,0);F.rotate(V).add(e.position),W.rotate(H).add(e.position);var _=this.getCurrentCenterOfMass(),M=F.distanceSq(_),q=W.distanceSq(_);D.angle=M<q?H:V,this.createNextBond(D,e,E+D.angle)}else{var U=e.angle;if(t&&3<t.neighbours.length)U=0<U?o(1.0472,U):0>U?Math.max(-1.0472,U):1.0472;else if(!U){var G=this.getLastVertexWithAngle(e.id);U=G.angle,U||(U=1.0472)}if(t&&!c){var X=this.graph.getEdge(e.id,D.id).bondType;"/"===X?("/"===this.doubleBondConfig||"\\"===this.doubleBondConfig&&(U=-U),this.doubleBondConfig=null):"\\"===X&&("/"===this.doubleBondConfig?U=-U:"\\"===this.doubleBondConfig,this.doubleBondConfig=null)}D.angle=u?U:-U,this.createNextBond(D,e,E+D.angle)}}else if(2===L.length){var Y=e.angle;Y||(Y=1.0472);var K=this.graph.getTreeDepth(L[0],e.id),Z=this.graph.getTreeDepth(L[1],e.id),$=this.graph.vertices[L[0]],Q=this.graph.vertices[L[1]];$.value.subtreeDepth=K,Q.value.subtreeDepth=Z;var J=this.graph.getTreeDepth(t?t.id:null,e.id);t&&(t.value.subtreeDepth=J);var ee=0,te=1;"C"===Q.value.element&&"C"!==$.value.element&&1<Z&&5>K?(ee=1,te=0):"C"!==Q.value.element&&"C"===$.value.element&&1<K&&5>Z?(ee=0,te=1):Z>K&&(ee=1,te=0);var ne=this.graph.vertices[L[ee]],ie=this.graph.vertices[L[te]],re=this.graph.getEdge(e.id,ne.id),ae=this.graph.getEdge(e.id,ie.id),oe=!1;J<K&&J<Z&&(oe=!0),ie.angle=Y,ne.angle=-Y,"\\"===this.doubleBondConfig?"\\"===ie.value.branchBond&&(ie.angle=-Y,ne.angle=Y):"/"===this.doubleBondConfig&&"/"===ie.value.branchBond&&(ie.angle=-Y,ne.angle=Y),this.createNextBond(ie,e,E+ie.angle,oe),this.createNextBond(ne,e,E+ne.angle,oe)}else if(3===L.length){var le=this.graph.getTreeDepth(L[0],e.id),se=this.graph.getTreeDepth(L[1],e.id),ge=this.graph.getTreeDepth(L[2],e.id),de=this.graph.vertices[L[0]],s=this.graph.vertices[L[1]],ue=this.graph.vertices[L[2]];de.value.subtreeDepth=le,s.value.subtreeDepth=se,ue.value.subtreeDepth=ge,se>le&&se>ge?(de=this.graph.vertices[L[1]],s=this.graph.vertices[L[0]],ue=this.graph.vertices[L[2]]):ge>le&&ge>se&&(de=this.graph.vertices[L[2]],s=this.graph.vertices[L[0]],ue=this.graph.vertices[L[1]]),t&&1>t.value.rings.length&&1>de.value.rings.length&&1>s.value.rings.length&&1>ue.value.rings.length&&1===this.graph.getTreeDepth(s.id,e.id)&&1===this.graph.getTreeDepth(ue.id,e.id)&&1<this.graph.getTreeDepth(de.id,e.id)?(de.angle=-e.angle,0<=e.angle?(s.angle=g.toRad(30),ue.angle=g.toRad(90)):(s.angle=-g.toRad(30),ue.angle=-g.toRad(90)),this.createNextBond(de,e,E+de.angle),this.createNextBond(s,e,E+s.angle),this.createNextBond(ue,e,E+ue.angle)):(de.angle=0,s.angle=g.toRad(90),ue.angle=-g.toRad(90),this.createNextBond(de,e,E+de.angle),this.createNextBond(s,e,E+s.angle),this.createNextBond(ue,e,E+ue.angle))}else if(4===L.length){var he=this.graph.getTreeDepth(L[0],e.id),ce=this.graph.getTreeDepth(L[1],e.id),pe=this.graph.getTreeDepth(L[2],e.id),ve=this.graph.getTreeDepth(L[3],e.id),ye=this.graph.vertices[L[0]],w=this.graph.vertices[L[1]],x=this.graph.vertices[L[2]],y=this.graph.vertices[L[3]];ye.value.subtreeDepth=he,w.value.subtreeDepth=ce,x.value.subtreeDepth=pe,y.value.subtreeDepth=ve,ce>he&&ce>pe&&ce>ve?(ye=this.graph.vertices[L[1]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[2]],y=this.graph.vertices[L[3]]):pe>he&&pe>ce&&pe>ve?(ye=this.graph.vertices[L[2]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[1]],y=this.graph.vertices[L[3]]):ve>he&&ve>ce&&ve>pe&&(ye=this.graph.vertices[L[3]],w=this.graph.vertices[L[0]],x=this.graph.vertices[L[1]],y=this.graph.vertices[L[2]]),ye.angle=-g.toRad(36),w.angle=g.toRad(36),x.angle=-g.toRad(108),y.angle=g.toRad(108),this.createNextBond(ye,e,E+ye.angle),this.createNextBond(w,e,E+w.angle),this.createNextBond(x,e,E+x.angle),this.createNextBond(y,e,E+y.angle)}}}}},{key:"getCommonRingbondNeighbour",value:function(e){for(var t=e.neighbours,n=0,i;n<t.length;n++)if(i=this.graph.vertices[t[n]],d.containsAll(i.value.rings,e.value.rings))return i;return null}},{key:"isPointInRing",value:function(e){for(var t=0,n;t<this.rings.length;t++)if(n=this.rings[t],!!n.positioned){var i=g.polyCircumradius(this.opts.bondLength,n.getSize());if(e.distanceSq(n.center)<i*i)return!0}return!1}},{key:"isEdgeInRing",value:function(e){var t=this.graph.vertices[e.sourceId],n=this.graph.vertices[e.targetId];return this.areVerticesInSameRing(t,n)}},{key:"isEdgeRotatable",value:function(e){var t=this.graph.vertices[e.sourceId],n=this.graph.vertices[e.targetId];return"-"===e.bondType&&(t.isTerminal()||n.isTerminal()?!1:0<t.value.rings.length&&0<n.value.rings.length&&this.areVerticesInSameRing(t,n)?!1:!0)}},{key:"isRingAromatic",value:function(e){for(var t=0,n;t<e.members.length;t++)if(n=this.graph.vertices[e.members[t]],!n.value.isPartOfAromaticRing)return!1;return!0}},{key:"getEdgeNormals",value:function(e){var t=this.graph.vertices[e.sourceId].position,n=this.graph.vertices[e.targetId].position,i=l.units(t,n);return i}},{key:"getNonRingNeighbours",value:function(e){for(var t=[],n=this.graph.vertices[e],r=n.neighbours,a=0;a<r.length;a++){var i=this.graph.vertices[r[a]],o=d.intersection(n.value.rings,i.value.rings).length;0===o&&!1==i.value.isBridge&&t.push(i)}return t}},{key:"annotateStereochemistry",value:function(){for(var e=0,t;e<this.graph.vertices.length;e++)if(t=this.graph.vertices[e],!!t.value.isStereoCenter){for(var n=t.getNeighbours(),i=n.length,r=Array(i),a=0;a<i;a++){var o=new Uint8Array(this.graph.vertices.length),s=[[]];o[t.id]=1,this.visitStereochemistry(n[a],t.id,o,s,10,0);for(var d=0;d<s.length;d++)s[d].sort(function(e,t){return t-e});r[a]=[a,s]}for(var u=0,h=0,a=0;a<r.length;a++){r[a][1].length>u&&(u=r[a][1].length);for(var d=0;d<r[a][1].length;d++)r[a][1][d].length>h&&(h=r[a][1][d].length)}for(var a=0,c;a<r.length;a++){c=u-r[a][1].length;for(var d=0;d<c;d++)r[a][1].push([]);r[a][1].push([n[a]]);for(var d=0,p;d<r[a][1].length;d++){p=h-r[a][1][d].length;for(var v=0;v<p;v++)r[a][1][d].push(0)}}r.sort(function(e,t){for(var n=0;n<e[1].length;n++)for(var i=0;i<e[1][n].length;i++){if(e[1][n][i]>t[1][n][i])return-1;if(e[1][n][i]<t[1][n][i])return 1}return 0});for(var l=new Uint8Array(i),a=0;a<i;a++)l[a]=r[a][0],t.value.priority=a;var y=this.graph.vertices[n[l[0]]].position,m=this.graph.vertices[n[l[1]]].position,f=this.graph.vertices[n[l[2]]].position,b=y.relativeClockwise(m,t.position),x=y.relativeClockwise(f,t.position),k=-1===b,C="@"===t.value.bracket.chirality?-1:1,S=1==g.parityOfPermutation(l)*C?"R":"S",R="down",A="up";(k&&"R"!=S||!k&&"S"!=S)&&(t.value.hydrogenDirection="up",R="up",A="down"),t.value.hasHydrogen&&(this.graph.getEdge(t.id,n[l[l.length-1]]).wedge=R);for(var j=Array(n.length-1),T=1<t.value.rings.length&&t.value.hasHydrogen,w=t.value.hasHydrogen?1:0,a=0;a<l.length-w;a++){j[a]=new Uint32Array(2);var I=this.graph.vertices[n[l[a]]];j[a][0]+=I.value.isStereoCenter?0:1e5,j[a][0]+=this.areVerticesInSameRing(I,t)?0:1e4,j[a][0]+=I.value.isHeteroAtom()?1e3:0,j[a][0]-=0===I.value.subtreeDepth?1e3:0,j[a][0]+=1e3-I.value.subtreeDepth,j[a][1]=n[l[a]]}if(j.sort(function(e,t){return e[0]>t[0]?-1:e[0]<t[0]?1:0}),!T){var P=j[0][1];if(t.value.hasHydrogen)this.graph.getEdge(t.id,P).wedge=A;else{for(var B=A,a=l.length-1;0<=a&&(B=B==R?A:R,n[l[a]]!==P);a--);this.graph.getEdge(t.id,P).wedge=B}}t.value.chirality=S}}},{key:"visitStereochemistry",value:function(e,t,n,r,a,o){var l=6<arguments.length&&void 0!==arguments[6]?arguments[6]:0;n[e]=1;var s=this.graph.vertices[e],g=s.value.getAtomicNumber();r.length<=o&&r.push([]);for(var d=0;d<this.graph.getEdge(e,t).weight;d++)r[o].push(1e3*l+g);for(var i=this.graph.vertices[e].neighbours,d=0;d<i.length;d++)1!==n[i[d]]&&o<a-1&&this.visitStereochemistry(i[d],e,n.slice(),r,a,o+1,g);if(o<a-1){for(var u=0,d=0;d<i.length;d++)u+=this.graph.getEdge(e,i[d]).weight;for(var d=0;d<s.value.getMaxBonds()-u;d++)r.length<=o+1&&r.push([]),r[o+1].push(1e3*g+1)}}},{key:"initPseudoElements",value:function(){for(var e=0;e<this.graph.vertices.length;e++){for(var t=this.graph.vertices[e],n=t.neighbours,i=Array(n.length),r=0;r<n.length;r++)i[r]=this.graph.vertices[n[r]];if(!(3>t.getNeighbourCount()||0<t.value.rings.length)&&"P"!==t.value.element&&("C"!==t.value.element||3!==i.length||"N"!==i[0].value.element||"N"!==i[1].value.element||"N"!==i[2].value.element)){for(var a=0,o=0,r=0;r<i.length;r++){var l=i[r],s=l.value.element,g=l.getNeighbourCount();"C"!==s&&"H"!==s&&1===g&&a++,1<g&&o++}if(!(1<o||2>a)){for(var d=null,r=0,u;r<i.length;r++)u=i[r],1<u.getNeighbourCount()&&(d=u);for(var r=0,h;r<i.length;r++)if(h=i[r],!(1<h.getNeighbourCount())){h.value.isDrawn=!1;var p=c.maxBonds[h.value.element]-h.value.bondCount,v="";h.value.bracket&&(p=h.value.bracket.hcount,v=h.value.bracket.charge||0),t.value.attachPseudoElement(h.value.element,d?d.value.element:null,p,v)}}}}for(var e=0;e<this.graph.vertices.length;e++){var y=this.graph.vertices[e],m=y.value,f=m.element;if("C"!==f&&"H"!==f&&m.isDrawn){for(var b=y.neighbours,x=Array(b.length),r=0;r<b.length;r++)x[r]=this.graph.vertices[b[r]];for(var r=0,k;r<x.length;r++)if(k=x[r].value,k.hasAttachedPseudoElements&&2===k.getAttachedPseudoElementsCount()){var C=k.getAttachedPseudoElements();C.hasOwnProperty("0O")&&C.hasOwnProperty("3C")&&(k.isDrawn=!1,y.value.attachPseudoElement("Ac","",0))}}}}}]),e}();t.exports=b},{"./ArrayHelper":2,"./Atom":3,"./CanvasWrapper":4,"./Edge":6,"./Graph":7,"./Line":8,"./MathHelper":9,"./Ring":11,"./RingConnection":12,"./SSSR":13,"./Vector2":14,"./Vertex":15}],6:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r=function(){function e(t,i){var r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:1;n(this,e),this.id=null,this.sourceId=t,this.targetId=i,this.weight=r,this.bondType="-",this.isPartOfAromaticRing=!1,this.center=!1,this.wedge=""}return i(e,[{key:"setBondType",value:function(t){this.bondType=t,this.weight=e.bonds[t]}}],[{key:"bonds",get:function(){return{"-":1,"/":1,"\\":1,"=":2,"#":3,$:4}}}]),e}();t.exports=r},{}],7:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.pow,a=Math.sqrt,o=Math.min,l=function(){function e(e,t){var n=[],i=!0,r=!1,a;try{for(var o=e[Symbol.iterator](),l;!(i=(l=o.next()).done)&&(n.push(l.value),!(t&&n.length===t));i=!0);}catch(e){r=!0,a=e}finally{try{!i&&o["return"]&&o["return"]()}finally{if(r)throw a}}return n}return function(t,n){if(Array.isArray(t))return t;if(Symbol.iterator in Object(t))return e(t,n);throw new TypeError("Invalid attempt to destructure non-iterable instance")}}(),i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),s=e("./MathHelper"),g=e("./Vector2"),d=e("./Vertex"),u=e("./Edge"),h=e("./Ring"),c=e("./Atom"),p=function(){function e(t){var i=1<arguments.length&&void 0!==arguments[1]&&arguments[1];n(this,e),this.vertices=[],this.edges=[],this.vertexIdsToEdgeId={},this.isomeric=i,this._time=0,this._init(t)}return i(e,[{key:"_init",value:function(e){var t=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0,n=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,r=3<arguments.length&&void 0!==arguments[3]&&arguments[3],a=new c(e.atom.element?e.atom.element:e.atom,e.bond);a.branchBond=e.branchBond,a.ringbonds=e.ringbonds,a.bracket=e.atom.element?e.atom:null;var o=new d(a),l=this.vertices[n];if(this.addVertex(o),null!==n){o.setParentVertexId(n),o.value.addNeighbouringElement(l.value.element),l.addChild(o.id),l.value.addNeighbouringElement(a.element),l.spanningTreeChildren.push(o.id);var s=new u(n,o.id,1),g=null;r?(s.setBondType(o.value.branchBond||"-"),g=o.id,s.setBondType(o.value.branchBond||"-"),g=o.id):(s.setBondType(l.value.bondType||"-"),g=l.id),this.addEdge(s)}var h=e.ringbondCount+1;a.bracket&&(h+=a.bracket.hcount);var p=0;if(a.bracket&&a.bracket.chirality){a.isStereoCenter=!0,p=a.bracket.hcount;for(var v=0;v<p;v++)this._init({atom:"H",isBracket:"false",branches:[],branchCount:0,ringbonds:[],ringbondCount:!1,next:null,hasNext:!1,bond:"-"},v,o.id,!0)}for(var v=0;v<e.branchCount;v++)this._init(e.branches[v],v+h,o.id,!0);e.hasNext&&this._init(e.next,e.branchCount+h,o.id)}},{key:"clear",value:function(){this.vertices=[],this.edges=[],this.vertexIdsToEdgeId={}}},{key:"addVertex",value:function(e){return e.id=this.vertices.length,this.vertices.push(e),e.id}},{key:"addEdge",value:function(e){var t=this.vertices[e.sourceId],n=this.vertices[e.targetId];return e.id=this.edges.length,this.edges.push(e),this.vertexIdsToEdgeId[e.sourceId+"_"+e.targetId]=e.id,this.vertexIdsToEdgeId[e.targetId+"_"+e.sourceId]=e.id,e.isPartOfAromaticRing=t.value.isPartOfAromaticRing&&n.value.isPartOfAromaticRing,t.value.bondCount+=e.weight,n.value.bondCount+=e.weight,t.edges.push(e.id),n.edges.push(e.id),e.id}},{key:"getEdge",value:function(e,t){var n=this.vertexIdsToEdgeId[e+"_"+t];return void 0===n?null:this.edges[n]}},{key:"getEdges",value:function(e){for(var t=[],n=this.vertices[e],r=0;r<n.neighbours.length;r++)t.push(this.vertexIdsToEdgeId[e+"_"+n.neighbours[r]]);return t}},{key:"hasEdge",value:function(e,t){return void 0!==this.vertexIdsToEdgeId[e+"_"+t]}},{key:"getVertexList",value:function(){for(var e=[this.vertices.length],t=0;t<this.vertices.length;t++)e[t]=this.vertices[t].id;return e}},{key:"getEdgeList",value:function(){for(var e=Array(this.edges.length),t=0;t<this.edges.length;t++)e[t]=[this.edges[t].sourceId,this.edges[t].targetId];return e}},{key:"getAdjacencyMatrix",value:function(){for(var e=this.vertices.length,t=Array(e),n=0;n<e;n++)t[n]=Array(e),t[n].fill(0);for(var n=0,i;n<this.edges.length;n++)i=this.edges[n],t[i.sourceId][i.targetId]=1,t[i.targetId][i.sourceId]=1;return t}},{key:"getComponentsAdjacencyMatrix",value:function(){for(var e=this.vertices.length,t=Array(e),n=this.getBridges(),r=0;r<e;r++)t[r]=Array(e),t[r].fill(0);for(var r=0,i;r<this.edges.length;r++)i=this.edges[r],t[i.sourceId][i.targetId]=1,t[i.targetId][i.sourceId]=1;for(var r=0;r<n.length;r++)t[n[r][0]][n[r][1]]=0,t[n[r][1]][n[r][0]]=0;return t}},{key:"getSubgraphAdjacencyMatrix",value:function(e){for(var t=e.length,n=Array(t),r=0;r<t;r++){n[r]=Array(t),n[r].fill(0);for(var i=0;i<t;i++)r!==i&&this.hasEdge(e[r],e[i])&&(n[r][i]=1)}return n}},{key:"getDistanceMatrix",value:function(){for(var e=this.vertices.length,t=this.getAdjacencyMatrix(),n=Array(e),r=0;r<e;r++)n[r]=Array(e),n[r].fill(Infinity);for(var r=0;r<e;r++)for(var i=0;i<e;i++)1===t[r][i]&&(n[r][i]=1);for(var a=0;a<e;a++)for(var r=0;r<e;r++)for(var i=0;i<e;i++)n[r][i]>n[r][a]+n[a][i]&&(n[r][i]=n[r][a]+n[a][i]);return n}},{key:"getSubgraphDistanceMatrix",value:function(e){for(var t=e.length,n=this.getSubgraphAdjacencyMatrix(e),r=Array(t),a=0;a<t;a++)r[a]=Array(t),r[a].fill(Infinity);for(var a=0;a<t;a++)for(var i=0;i<t;i++)1===n[a][i]&&(r[a][i]=1);for(var o=0;o<t;o++)for(var a=0;a<t;a++)for(var i=0;i<t;i++)r[a][i]>r[a][o]+r[o][i]&&(r[a][i]=r[a][o]+r[o][i]);return r}},{key:"getAdjacencyList",value:function(){for(var e=this.vertices.length,t=Array(e),n=0;n<e;n++){t[n]=[];for(var i=0;i<e;i++)n!==i&&this.hasEdge(this.vertices[n].id,this.vertices[i].id)&&t[n].push(i)}return t}},{key:"getSubgraphAdjacencyList",value:function(e){for(var t=e.length,n=Array(t),r=0;r<t;r++){n[r]=[];for(var i=0;i<t;i++)r!==i&&this.hasEdge(e[r],e[i])&&n[r].push(i)}return n}},{key:"getBridges",value:function(){var e=this.vertices.length,t=Array(e),n=Array(e),r=Array(e),a=Array(e),o=this.getAdjacencyList(),l=[];t.fill(!1),a.fill(null),this._time=0;for(var s=0;s<e;s++)t[s]||this._bridgeDfs(s,t,n,r,a,o,l);return l}},{key:"traverseBF",value:function(e,t){var n=this.vertices.length,r=Array(n);r.fill(!1);for(var a=[e];0<a.length;){var o=a.shift(),l=this.vertices[o];t(l);for(var s=0,i;s<l.neighbours.length;s++)i=l.neighbours[s],r[i]||(r[i]=!0,a.push(i))}}},{key:"getTreeDepth",value:function(e,t){if(null===e||null===t)return 0;for(var n=this.vertices[e].getSpanningTreeNeighbours(t),r=0,a=0;a<n.length;a++){var i=n[a],o=this.getTreeDepth(i,e);o>r&&(r=o)}return r+1}},{key:"traverseTree",value:function(e,t,n){var r=3<arguments.length&&void 0!==arguments[3]?arguments[3]:Number.MAX_SAFE_INTEGER,a=4<arguments.length&&void 0!==arguments[4]&&arguments[4],o=5<arguments.length&&void 0!==arguments[5]?arguments[5]:1,l=6<arguments.length&&void 0!==arguments[6]?arguments[6]:null;if(null===l&&(l=new Uint8Array(this.vertices.length)),!(o>r+1||1===l[e])){l[e]=1;var s=this.vertices[e],g=s.getNeighbours(t);(!a||1<o)&&n(s);for(var d=0;d<g.length;d++)this.traverseTree(g[d],e,n,r,a,o+1,l)}}},{key:"kkLayout",value:function(e,t,n,o,g){for(var d=g,u=e.length;u--;)var i=this.vertices[e[u]],h=i.neighbours.length;var c=this.getSubgraphDistanceMatrix(e),p=e.length,v=s.polyCircumradius(500,p),y=s.centralAngle(p),m=0,f=new Float32Array(p),b=new Float32Array(p),x=Array(p);for(u=p;u--;){var k=this.vertices[e[u]];k.positioned?(f[u]=k.position.x,b[u]=k.position.y):(f[u]=t.x+Math.cos(m)*v,b[u]=t.y+Math.sin(m)*v),x[u]=k.positioned,m+=y}var C=Array(p);for(u=p;u--;){C[u]=Array(p);for(var h=p;h--;)C[u][h]=g*c[u][h]}var S=Array(p);for(u=p;u--;){S[u]=Array(p);for(var h=p;h--;)S[u][h]=d*r(c[u][h],-2)}var R=Array(p),A=new Float32Array(p),j=new Float32Array(p);for(u=p;u--;)R[u]=Array(p);u=p;for(var T,w,I,P,B,L,E;u--;){T=f[u],w=b[u],I=0,P=0;for(var D=p;D--;)u!==D&&(B=f[D],L=b[D],E=1/a((T-B)*(T-B)+(w-L)*(w-L)),R[u][D]=[S[u][D]*(T-B-C[u][D]*(T-B)*E),S[u][D]*(w-L-C[u][D]*(w-L)*E)],R[D][u]=R[u][D],I+=R[u][D][0],P+=R[u][D][1]);A[u]=I,j[u]=P}for(var N=function(e){return[A[e]*A[e]+j[e]*j[e],A[e],j[e]]},O=function(){var e=0,t=0,n=0,i=0;for(u=p;u--;){var r=N(u),a=l(r,3),o=a[0],s=a[1],g=a[2];o>e&&!1===x[u]&&(e=o,t=u,n=s,i=g)}return[t,e,n,i]},V=function(e,t,n){var i=0,o=0,s=0,g=f[e],d=b[e],h=C[e],c=S[e];for(u=p;u--;)if(u!==e){var v=f[u],y=b[u],x=h[u],l=c[u],k=(g-v)*(g-v),m=1/r(k+(d-y)*(d-y),1.5);i+=l*(1-x*(d-y)*(d-y)*m),o+=l*(1-x*k*m),s+=l*(x*(g-v)*(d-y)*m)}0==i&&(i=0.1),0==o&&(o=0.1),0==s&&(s=0.1);var T=t/i+n/s;T/=s/i-o/s;var w=-(s*T+t)/i;f[e]+=w,b[e]+=T;var I=R[e];t=0,n=0,g=f[e],d=b[e];var P,B,L,E,D;for(u=p;u--;)e!==u&&(P=f[u],B=b[u],L=I[u][0],E=I[u][1],D=1/a((g-P)*(g-P)+(d-B)*(d-B)),w=c[u]*(g-P-h[u]*(g-P)*D),T=c[u]*(d-B-h[u]*(d-B)*D),I[u]=[w,T],t+=w,n+=T,A[u]+=w-L,j[u]+=T-E);A[e]=t,j[e]=n},H=1e9,z=0,F=0,W=0,_=0,M=0,q=0;0.1<H&&2e3>M;){M++;var U=O(),G=l(U,4);for(z=G[0],H=G[1],F=G[2],W=G[3],_=H,q=0;0.1<_&&50>q;){q++,V(z,F,W);var X=N(z),Y=l(X,3);_=Y[0],F=Y[1],W=Y[2]}}for(u=p;u--;){var K=e[u],Z=this.vertices[K];Z.position.x=f[u],Z.position.y=b[u],Z.positioned=!0,Z.forcePositioned=!0}}},{key:"_bridgeDfs",value:function(e,t,n,r,a,l,s){t[e]=!0,n[e]=r[e]=++this._time;for(var g=0,i;g<l[e].length;g++)i=l[e][g],t[i]?i!==a[e]&&(r[e]=o(r[e],n[i])):(a[i]=e,this._bridgeDfs(i,t,n,r,a,l,s),r[e]=o(r[e],r[i]),r[i]>n[e]&&s.push([e,i]))}}],[{key:"getConnectedComponents",value:function(t){var n=t.length,i=Array(n),r=[],a=0;i.fill(!1);for(var o=0;o<n;o++)if(!i[o]){var l=[];i[o]=!0,l.push(o),a++,e._ccGetDfs(o,i,t,l),1<l.length&&r.push(l)}return r}},{key:"getConnectedComponentCount",value:function(t){var n=t.length,i=Array(n),r=0;i.fill(!1);for(var a=0;a<n;a++)i[a]||(i[a]=!0,r++,e._ccCountDfs(a,i,t));return r}},{key:"_ccCountDfs",value:function(t,n,i){for(var r=0,a;r<i[t].length;r++)(a=i[t][r],a&&!n[r]&&t!==r)&&(n[r]=!0,e._ccCountDfs(r,n,i))}},{key:"_ccGetDfs",value:function(t,n,i,r){for(var a=0,o;a<i[t].length;a++)(o=i[t][a],o&&!n[a]&&t!==a)&&(n[a]=!0,r.push(a),e._ccGetDfs(a,n,i,r))}}]),e}();t.exports=p},{"./Atom":3,"./Edge":6,"./MathHelper":9,"./Ring":11,"./Vector2":14,"./Vertex":15}],8:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=Math.pow,r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=e("./Vector2"),o=function(){function e(){var t=0<arguments.length&&void 0!==arguments[0]?arguments[0]:new a(0,0),i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:new a(0,0),r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:null,o=3<arguments.length&&void 0!==arguments[3]?arguments[3]:null,l=4<arguments.length&&void 0!==arguments[4]&&arguments[4],s=5<arguments.length&&void 0!==arguments[5]&&arguments[5];n(this,e),this.from=t,this.to=i,this.elementFrom=r,this.elementTo=o,this.chiralFrom=l,this.chiralTo=s}return r(e,[{key:"clone",value:function(){return new e(this.from.clone(),this.to.clone(),this.elementFrom,this.elementTo)}},{key:"getLength",value:function(){return Math.sqrt(i(this.to.x-this.from.x,2)+i(this.to.y-this.from.y,2))}},{key:"getAngle",value:function(){var e=a.subtract(this.getRightVector(),this.getLeftVector());return e.angle()}},{key:"getRightVector",value:function(){return this.from.x<this.to.x?this.to:this.from}},{key:"getLeftVector",value:function(){return this.from.x<this.to.x?this.from:this.to}},{key:"getRightElement",value:function(){return this.from.x<this.to.x?this.elementTo:this.elementFrom}},{key:"getLeftElement",value:function(){return this.from.x<this.to.x?this.elementFrom:this.elementTo}},{key:"getRightChiral",value:function(){return this.from.x<this.to.x?this.chiralTo:this.chiralFrom}},{key:"getLeftChiral",value:function(){return this.from.x<this.to.x?this.chiralFrom:this.chiralTo}},{key:"setRightVector",value:function(e,t){return this.from.x<this.to.x?(this.to.x=e,this.to.y=t):(this.from.x=e,this.from.y=t),this}},{key:"setLeftVector",value:function(e,t){return this.from.x<this.to.x?(this.from.x=e,this.from.y=t):(this.to.x=e,this.to.y=t),this}},{key:"rotateToXAxis",value:function(){var e=this.getLeftVector();return this.setRightVector(e.x+this.getLength(),e.y),this}},{key:"rotate",value:function(e){var t=this.getLeftVector(),n=this.getRightVector(),i=Math.sin(e),r=Math.cos(e),a=r*(n.x-t.x)-i*(n.y-t.y)+t.x,o=i*(n.x-t.x)-r*(n.y-t.y)+t.y;return this.setRightVector(a,o),this}},{key:"shortenFrom",value:function(e){var t=a.subtract(this.to,this.from);return t.normalize(),t.multiplyScalar(e),this.from.add(t),this}},{key:"shortenTo",value:function(e){var t=a.subtract(this.from,this.to);return t.normalize(),t.multiplyScalar(e),this.to.add(t),this}},{key:"shortenRight",value:function(e){return this.from.x<this.to.x?this.shortenTo(e):this.shortenFrom(e),this}},{key:"shortenLeft",value:function(e){return this.from.x<this.to.x?this.shortenFrom(e):this.shortenTo(e),this}},{key:"shorten",value:function(e){var t=a.subtract(this.from,this.to);return t.normalize(),t.multiplyScalar(e/2),this.to.add(t),this.from.subtract(t),this}}]),e}();t.exports=o},{"./Vector2":14}],9:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.sin,a=Math.cos,i=Math.PI,o=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=function(){function e(){n(this,e)}return o(e,null,[{key:"round",value:function(e,t){return t=t?t:1,+(Math.round(e+"e"+t)+"e-"+t)}},{key:"meanAngle",value:function(e){for(var t=0,n=0,o=0;o<e.length;o++)t+=r(e[o]),n+=a(e[o]);return Math.atan2(t/e.length,n/e.length)}},{key:"innerAngle",value:function(t){return e.toRad(180*(t-2)/t)}},{key:"polyCircumradius",value:function(e,t){return e/(2*r(i/t))}},{key:"apothem",value:function(e,t){return e*a(i/t)}},{key:"apothemFromSideLength",value:function(t,i){var n=e.polyCircumradius(t,i);return e.apothem(n,i)}},{key:"centralAngle",value:function(t){return e.toRad(360/t)}},{key:"toDeg",value:function(t){return t*e.degFactor}},{key:"toRad",value:function(t){return t*e.radFactor}},{key:"parityOfPermutation",value:function(e){for(var t=new Uint8Array(e.length),n=0,r=function n(r){var i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0;return 1===t[r]?i:(i++,t[r]=1,n(e[r],i))},a=0;a<e.length;a++)if(1!==t[a]){var i=r(a);n+=1-i%2}return n%2?-1:1}},{key:"radFactor",get:function(){return i/180}},{key:"degFactor",get:function(){return 180/i}},{key:"twoPI",get:function(){return 2*i}}]),e}();t.exports=l},{}],10:[function(e,t){"use strict";t.exports=function(){function e(t,n,i,r){this.message=t,this.expected=n,this.found=i,this.location=r,this.name="SyntaxError","function"==typeof Error.captureStackTrace&&Error.captureStackTrace(this,e)}return function(e,t){function n(){this.constructor=e}n.prototype=t.prototype,e.prototype=new n}(e,Error),e.buildMessage=function(e,t){function n(e){return e.charCodeAt(0).toString(16).toUpperCase()}function i(e){return e.replace(/\\/g,"\\\\").replace(/"/g,"\\\"").replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+n(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+n(e)})}function r(e){return e.replace(/\\/g,"\\\\").replace(/\]/g,"\\]").replace(/\^/g,"\\^").replace(/-/g,"\\-").replace(/\0/g,"\\0").replace(/\t/g,"\\t").replace(/\n/g,"\\n").replace(/\r/g,"\\r").replace(/[\x00-\x0F]/g,function(e){return"\\x0"+n(e)}).replace(/[\x10-\x1F\x7F-\x9F]/g,function(e){return"\\x"+n(e)})}function a(e){return o[e.type](e)}var o={literal:function(e){return"\""+i(e.text)+"\""},class:function(e){var t="",n;for(n=0;n<e.parts.length;n++)t+=e.parts[n]instanceof Array?r(e.parts[n][0])+"-"+r(e.parts[n][1]):r(e.parts[n]);return"["+(e.inverted?"^":"")+t+"]"},any:function(){return"any character"},end:function(){return"end of input"},other:function(e){return e.description}};return"Expected "+function(e){var t=Array(e.length),n,i;for(n=0;n<e.length;n++)t[n]=a(e[n]);if(t.sort(),0<t.length){for(n=1,i=1;n<t.length;n++)t[n-1]!==t[n]&&(t[i]=t[n],i++);t.length=i}switch(t.length){case 1:return t[0];case 2:return t[0]+" or "+t[1];default:return t.slice(0,-1).join(", ")+", or "+t[t.length-1];}}(e)+" but "+function(e){return e?"\""+i(e)+"\"":"end of input"}(t)+" found."},{SyntaxError:e,parse:function(t,n){function i(e,t){return{type:"literal",text:e,ignoreCase:t}}function r(e,t,n){return{type:"class",parts:e,inverted:t,ignoreCase:n}}function a(e){var n=rt[e],i;if(n)return n;for(i=e-1;!rt[i];)i--;for(n=rt[i],n={line:n.line,column:n.column};i<e;)10===t.charCodeAt(i)?(n.line++,n.column=1):n.column++,i++;return rt[e]=n,n}function o(e,t){var n=a(e),i=a(t);return{start:{offset:e,line:n.line,column:n.column},end:{offset:t,line:i.line,column:i.column}}}function l(e){nt<at||(nt>at&&(at=nt,ot=[]),ot.push(e))}function s(){var e,t,n,i,r,a,o,l,h,c;if(e=nt,t=nt,n=d(),n!==T){for(i=[],r=g();r!==T;)i.push(r),r=g();if(i!==T){for(r=[],a=nt,o=u(),o===T&&(o=null),o===T?(nt=a,a=T):(l=m(),l===T?(nt=a,a=T):(o=[o,l],a=o));a!==T;)r.push(a),a=nt,o=u(),o===T&&(o=null),o===T?(nt=a,a=T):(l=m(),l===T?(nt=a,a=T):(o=[o,l],a=o));if(r!==T){for(a=[],o=g();o!==T;)a.push(o),o=g();if(a===T)nt=t,t=T;else if(o=u(),o===T&&(o=null),o===T)nt=t,t=T;else if(l=s(),l===T&&(l=null),l!==T){for(h=[],c=g();c!==T;)h.push(c),c=g();h===T?(nt=t,t=T):(n=[n,i,r,a,o,l,h],t=n)}else nt=t,t=T}else nt=t,t=T}else nt=t,t=T}else nt=t,t=T;return t!==T&&(it=e,t=P(t)),e=t,e}function g(){var e,n,i,r,a,o;return e=nt,n=nt,40===t.charCodeAt(nt)?(i=B,nt++):(i=T,l(L)),i===T?(nt=n,n=T):(r=u(),r===T&&(r=null),r===T?(nt=n,n=T):(a=s(),a===T?(nt=n,n=T):(41===t.charCodeAt(nt)?(o=E,nt++):(o=T,l(D)),o===T?(nt=n,n=T):(i=[i,r,a,o],n=i)))),n!==T&&(it=e,n=N(n)),e=n,e}function d(){var e,t;return e=nt,t=c(),t===T&&(t=p(),t===T&&(t=h(),t===T&&(t=v()))),t!==T&&(it=e,t=O(t)),e=t,e}function u(){var e,n;return e=nt,V.test(t.charAt(nt))?(n=t.charAt(nt),nt++):(n=T,l(H)),n!==T&&(it=e,n=z(n)),e=n,e}function h(){var e,n,i,r,a,o,s,g,d,u;return e=nt,n=nt,91===t.charCodeAt(nt)?(i=F,nt++):(i=T,l(W)),i===T?(nt=n,n=T):(r=R(),r===T&&(r=null),r===T?(nt=n,n=T):(t.substr(nt,2)===_?(a=_,nt+=2):(a=T,l(M)),a===T&&(t.substr(nt,2)===q?(a=q,nt+=2):(a=T,l(U)),a===T&&(a=p(),a===T&&(a=y(),a===T&&(a=v())))),a===T?(nt=n,n=T):(o=f(),o===T&&(o=null),o===T?(nt=n,n=T):(s=C(),s===T&&(s=null),s===T?(nt=n,n=T):(g=b(),g===T&&(g=null),g===T?(nt=n,n=T):(d=S(),d===T&&(d=null),d===T?(nt=n,n=T):(93===t.charCodeAt(nt)?(u=G,nt++):(u=T,l(X)),u===T?(nt=n,n=T):(i=[i,r,a,o,s,g,d,u],n=i)))))))),n!==T&&(it=e,n=Y(n)),e=n,e}function c(){var e,n,i,r;return e=nt,n=nt,66===t.charCodeAt(nt)?(i=K,nt++):(i=T,l(Z)),i===T?(nt=n,n=T):(114===t.charCodeAt(nt)?(r=$,nt++):(r=T,l(Q)),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n===T&&(n=nt,67===t.charCodeAt(nt)?(i=J,nt++):(i=T,l(ee)),i===T?(nt=n,n=T):(108===t.charCodeAt(nt)?(r=te,nt++):(r=T,l(ne)),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n===T&&(ie.test(t.charAt(nt))?(n=t.charAt(nt),nt++):(n=T,l(re)))),n!==T&&(it=e,n=ae(n)),e=n,e}function p(){var e,n;return e=nt,oe.test(t.charAt(nt))?(n=t.charAt(nt),nt++):(n=T,l(le)),n!==T&&(it=e,n=O(n)),e=n,e}function v(){var e,n;return e=nt,42===t.charCodeAt(nt)?(n=se,nt++):(n=T,l(ge)),n!==T&&(it=e,n=de(n)),e=n,e}function y(){var e,n,i,r;return e=nt,n=nt,ue.test(t.charAt(nt))?(i=t.charAt(nt),nt++):(i=T,l(he)),i===T?(nt=n,n=T):(ce.test(t.charAt(nt))?(r=t.charAt(nt),nt++):(r=T,l(pe)),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n!==T&&(it=e,n=ve(n)),e=n,e}function m(){var e,n,i,r,a;return e=nt,n=nt,37===t.charCodeAt(nt)?(i=ye,nt++):(i=T,l(me)),i===T?(nt=n,n=T):(fe.test(t.charAt(nt))?(r=t.charAt(nt),nt++):(r=T,l(be)),r===T?(nt=n,n=T):(xe.test(t.charAt(nt))?(a=t.charAt(nt),nt++):(a=T,l(ke)),a===T?(nt=n,n=T):(i=[i,r,a],n=i))),n===T&&(xe.test(t.charAt(nt))?(n=t.charAt(nt),nt++):(n=T,l(ke))),n!==T&&(it=e,n=Ce(n)),e=n,e}function f(){var e,n,i,r,a,o,s;return e=nt,n=nt,64===t.charCodeAt(nt)?(i=Se,nt++):(i=T,l(Re)),i===T?(nt=n,n=T):(64===t.charCodeAt(nt)?(r=Se,nt++):(r=T,l(Re)),r===T&&(r=nt,t.substr(nt,2)===Ae?(a=Ae,nt+=2):(a=T,l(je)),a===T?(nt=r,r=T):(Te.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(we)),o===T?(nt=r,r=T):(a=[a,o],r=a)),r===T&&(r=nt,t.substr(nt,2)===Ie?(a=Ie,nt+=2):(a=T,l(Pe)),a===T?(nt=r,r=T):(Te.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(we)),o===T?(nt=r,r=T):(a=[a,o],r=a)),r===T&&(r=nt,t.substr(nt,2)===Be?(a=Be,nt+=2):(a=T,l(Le)),a===T?(nt=r,r=T):(Ee.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(De)),o===T?(nt=r,r=T):(a=[a,o],r=a)),r===T&&(r=nt,t.substr(nt,2)===Ne?(a=Ne,nt+=2):(a=T,l(Oe)),a===T?(nt=r,r=T):(fe.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(be)),o===T?(nt=r,r=T):(xe.test(t.charAt(nt))?(s=t.charAt(nt),nt++):(s=T,l(ke)),s===T&&(s=null),s===T?(nt=r,r=T):(a=[a,o,s],r=a))),r===T&&(r=nt,t.substr(nt,2)===Ve?(a=Ve,nt+=2):(a=T,l(He)),a===T?(nt=r,r=T):(fe.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(be)),o===T?(nt=r,r=T):(xe.test(t.charAt(nt))?(s=t.charAt(nt),nt++):(s=T,l(ke)),s===T&&(s=null),s===T?(nt=r,r=T):(a=[a,o,s],r=a)))))))),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n!==T&&(it=e,n=ze(n)),e=n,e}function b(){var e,t;return e=nt,t=x(),t===T&&(t=k()),t!==T&&(it=e,t=Fe(t)),e=t,e}function x(){var e,n,i,r,a,o;return e=nt,n=nt,43===t.charCodeAt(nt)?(i=We,nt++):(i=T,l(_e)),i===T?(nt=n,n=T):(43===t.charCodeAt(nt)?(r=We,nt++):(r=T,l(_e)),r===T&&(r=nt,fe.test(t.charAt(nt))?(a=t.charAt(nt),nt++):(a=T,l(be)),a===T?(nt=r,r=T):(xe.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(ke)),o===T&&(o=null),o===T?(nt=r,r=T):(a=[a,o],r=a))),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n!==T&&(it=e,n=Me(n)),e=n,e}function k(){var e,n,i,r,a,o;return e=nt,n=nt,45===t.charCodeAt(nt)?(i=qe,nt++):(i=T,l(Ue)),i===T?(nt=n,n=T):(45===t.charCodeAt(nt)?(r=qe,nt++):(r=T,l(Ue)),r===T&&(r=nt,fe.test(t.charAt(nt))?(a=t.charAt(nt),nt++):(a=T,l(be)),a===T?(nt=r,r=T):(xe.test(t.charAt(nt))?(o=t.charAt(nt),nt++):(o=T,l(ke)),o===T&&(o=null),o===T?(nt=r,r=T):(a=[a,o],r=a))),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n!==T&&(it=e,n=Ge(n)),e=n,e}function C(){var e,n,i,r;return e=nt,n=nt,72===t.charCodeAt(nt)?(i=Xe,nt++):(i=T,l(Ye)),i===T?(nt=n,n=T):(xe.test(t.charAt(nt))?(r=t.charAt(nt),nt++):(r=T,l(ke)),r===T&&(r=null),r===T?(nt=n,n=T):(i=[i,r],n=i)),n!==T&&(it=e,n=Ke(n)),e=n,e}function S(){var e,n,i,r,a,o,s;if(e=nt,n=nt,58===t.charCodeAt(nt)?(i=Ze,nt++):(i=T,l($e)),i!==T){if(r=nt,fe.test(t.charAt(nt))?(a=t.charAt(nt),nt++):(a=T,l(be)),a!==T){for(o=[],xe.test(t.charAt(nt))?(s=t.charAt(nt),nt++):(s=T,l(ke));s!==T;)o.push(s),xe.test(t.charAt(nt))?(s=t.charAt(nt),nt++):(s=T,l(ke));o===T?(nt=r,r=T):(a=[a,o],r=a)}else nt=r,r=T;r===T&&(Qe.test(t.charAt(nt))?(r=t.charAt(nt),nt++):(r=T,l(Je))),r===T?(nt=n,n=T):(i=[i,r],n=i)}else nt=n,n=T;return n!==T&&(it=e,n=et(n)),e=n,e}function R(){var e,n,i,r,a;return e=nt,n=nt,fe.test(t.charAt(nt))?(i=t.charAt(nt),nt++):(i=T,l(be)),i===T?(nt=n,n=T):(xe.test(t.charAt(nt))?(r=t.charAt(nt),nt++):(r=T,l(ke)),r===T&&(r=null),r===T?(nt=n,n=T):(xe.test(t.charAt(nt))?(a=t.charAt(nt),nt++):(a=T,l(ke)),a===T&&(a=null),a===T?(nt=n,n=T):(i=[i,r,a],n=i))),n!==T&&(it=e,n=tt(n)),e=n,e}n=void 0===n?{}:n;var A=t.split("(").length-1,j=t.split(")").length-1;if(A!=j)throw function(t,n){return new e(t,null,null,n)}("The number of opening parentheses does not match the number of closing parentheses.",0);var T={},w={chain:s},I=s,P=function(e){for(var t=[],n=[],r=0;r<e[1].length;r++)t.push(e[1][r]);for(var r=0,i;r<e[2].length;r++)i=e[2][r][0]?e[2][r][0]:"-",n.push({bond:i,id:e[2][r][1]});for(var r=0;r<e[3].length;r++)t.push(e[3][r]);for(var r=0;r<e[6].length;r++)t.push(e[6][r]);return{atom:e[0],isBracket:!!e[0].element,branches:t,branchCount:t.length,ringbonds:n,ringbondCount:n.length,bond:e[4]?e[4]:"-",next:e[5],hasNext:!!e[5]}},B="(",L=i("(",!1),E=")",D=i(")",!1),N=function(e){var t=e[1]?e[1]:"-";return e[2].branchBond=t,e[2]},O=function(e){return e},V=/^[\-=#$:\/\\.]/,H=r(["-","=","#","$",":","/","\\","."],!1,!1),z=function(e){return e},F="[",W=i("[",!1),_="se",M=i("se",!1),q="as",U=i("as",!1),G="]",X=i("]",!1),Y=function(e){return{isotope:e[1],element:e[2],chirality:e[3],hcount:e[4],charge:e[5],class:e[6]}},K="B",Z=i("B",!1),$="r",Q=i("r",!1),J="C",ee=i("C",!1),te="l",ne=i("l",!1),ie=/^[NOPSFI]/,re=r(["N","O","P","S","F","I"],!1,!1),ae=function(e){return 1<e.length?e.join(""):e},oe=/^[bcnops]/,le=r(["b","c","n","o","p","s"],!1,!1),se="*",ge=i("*",!1),de=function(e){return e},ue=/^[A-Z]/,he=r([["A","Z"]],!1,!1),ce=/^[a-z]/,pe=r([["a","z"]],!1,!1),ve=function(t){return t.join("")},ye="%",me=i("%",!1),fe=/^[1-9]/,be=r([["1","9"]],!1,!1),xe=/^[0-9]/,ke=r([["0","9"]],!1,!1),Ce=function(e){return 1==e.length?+e:+e.join("").replace("%","")},Se="@",Re=i("@",!1),Ae="TH",je=i("TH",!1),Te=/^[12]/,we=r(["1","2"],!1,!1),Ie="AL",Pe=i("AL",!1),Be="SP",Le=i("SP",!1),Ee=/^[1-3]/,De=r([["1","3"]],!1,!1),Ne="TB",Oe=i("TB",!1),Ve="OH",He=i("OH",!1),ze=function(e){return e[1]?"@"==e[1]?"@@":e[1].join("").replace(",",""):"@"},Fe=function(e){return e},We="+",_e=i("+",!1),Me=function(e){return e[1]?"+"==e[1]?2:+e[1].join(""):1},qe="-",Ue=i("-",!1),Ge=function(e){return e[1]?"-"==e[1]?-2:-+e[1].join(""):-1},Xe="H",Ye=i("H",!1),Ke=function(e){return e[1]?+e[1]:1},Ze=":",$e=i(":",!1),Qe=/^[0]/,Je=r(["0"],!1,!1),et=function(e){return+(e[1][0]+e[1][1].join(""))},tt=function(e){return+e.join("")},nt=0,it=0,rt=[{line:1,column:1}],at=0,ot=[],lt;if("startRule"in n){if(!(n.startRule in w))throw new Error("Can't start parsing from rule \""+n.startRule+"\".");I=w[n.startRule]}if(lt=I(),lt!==T&&nt===t.length)return lt;throw lt!==T&&nt<t.length&&l(function(){return{type:"end"}}()),function(t,n,i){return new e(e.buildMessage(t,n),t,n,i)}(ot,at<t.length?t.charAt(at):null,at<t.length?o(at,at+1):o(at,at))}}}()},{}],11:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),r=e("./ArrayHelper"),a=e("./Vector2"),o=e("./Vertex"),l=e("./RingConnection"),s=function(){function e(t){n(this,e),this.id=null,this.members=t,this.edges=[],this.insiders=[],this.neighbours=[],this.positioned=!1,this.center=new a(0,0),this.rings=[],this.isBridged=!1,this.isPartOfBridged=!1,this.isSpiro=!1,this.isFused=!1,this.centralAngle=0,this.canFlip=!0}return i(e,[{key:"clone",value:function(){var t=new e(this.members);return t.id=this.id,t.insiders=r.clone(this.insiders),t.neighbours=r.clone(this.neighbours),t.positioned=this.positioned,t.center=this.center.clone(),t.rings=r.clone(this.rings),t.isBridged=this.isBridged,t.isPartOfBridged=this.isPartOfBridged,t.isSpiro=this.isSpiro,t.isFused=this.isFused,t.centralAngle=this.centralAngle,t.canFlip=this.canFlip,t}},{key:"getSize",value:function(){return this.members.length}},{key:"getPolygon",value:function(e){for(var t=[],n=0;n<this.members.length;n++)t.push(e[this.members[n]].position);return t}},{key:"getAngle",value:function(){return Math.PI-this.centralAngle}},{key:"eachMember",value:function(e,t,n,i){n=n||0===n?n:this.members[0];for(var r=n,a=0,o;null!=r&&100>a;)o=r,t(o),r=e[r].getNextInRing(e,this.id,i),i=o,r==n&&(r=null),a++}},{key:"getOrderedNeighbours",value:function(e){for(var t=Array(this.neighbours.length),n=0,i;n<this.neighbours.length;n++)i=l.getVertices(e,this.id,this.neighbours[n]),t[n]={n:i.length,neighbour:this.neighbours[n]};return t.sort(function(e,t){return t.n-e.n}),t}},{key:"isBenzeneLike",value:function(e){var t=this.getDoubleBondCount(e),n=this.members.length;return 3===t&&6===n||2===t&&5===n}},{key:"getDoubleBondCount",value:function(e){for(var t=0,n=0,i;n<this.members.length;n++)i=e[this.members[n]].value,("="===i.bondType||"="===i.branchBond)&&t++;return t}},{key:"contains",value:function(e){for(var t=0;t<this.members.length;t++)if(this.members[t]==e)return!0;return!1}}]),e}();t.exports=s},{"./ArrayHelper":2,"./RingConnection":12,"./Vector2":14,"./Vertex":15}],12:[function(e,t){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=e("./Vertex"),o=e("./Ring"),l=function(){function e(t,r){i(this,e),this.id=null,this.firstRingId=t.id,this.secondRingId=r.id,this.vertices=new Set;for(var a=0,o;a<t.members.length;a++){o=t.members[a];for(var l=0,n;l<r.members.length;l++)n=r.members[l],o===n&&this.addVertex(o)}}return r(e,[{key:"addVertex",value:function(e){this.vertices.add(e)}},{key:"updateOther",value:function(e,t){this.firstRingId===t?this.secondRingId=e:this.firstRingId=e}},{key:"containsRing",value:function(e){return this.firstRingId===e||this.secondRingId===e}},{key:"isBridge",value:function(e){if(2<this.vertices.size)return!0;var t=!0,n=!1,i;try{for(var r=this.vertices[Symbol.iterator](),a,o;!(t=(a=r.next()).done);t=!0)if(o=a.value,2<e[o].value.rings.length)return!0}catch(e){n=!0,i=e}finally{try{!t&&r.return&&r.return()}finally{if(n)throw i}}return!1}}],[{key:"isBridge",value:function(e,t,n,r){for(var a=null,o=0;o<e.length;o++)if(a=e[o],a.firstRingId===n&&a.secondRingId===r||a.firstRingId===r&&a.secondRingId===n)return a.isBridge(t);return!1}},{key:"getNeighbours",value:function(e,t){for(var n=[],r=0,i;r<e.length;r++)i=e[r],i.firstRingId===t?n.push(i.secondRingId):i.secondRingId===t&&n.push(i.firstRingId);return n}},{key:"getVertices",value:function(e,t,r){for(var a=0,i;a<e.length;a++)if(i=e[a],i.firstRingId===t&&i.secondRingId===r||i.firstRingId===r&&i.secondRingId===t)return[].concat(n(i.vertices))}}]),e}();t.exports=l},{"./Ring":11,"./Vertex":15}],13:[function(e,t){"use strict";function n(e){if(Array.isArray(e)){for(var t=0,n=Array(e.length);t<e.length;t++)n[t]=e[t];return n}return Array.from(e)}function i(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=e("./Graph"),o=function(){function e(){i(this,e)}return r(e,null,[{key:"getRings",value:function(t){var r=t.getComponentsAdjacencyMatrix();if(0===r.length)return null;for(var o=a.getConnectedComponents(r),l=[],s=0;s<o.length;s++){for(var i=o[s],g=t.getSubgraphAdjacencyMatrix([].concat(n(i))),u=new Uint16Array(g.length),h=new Uint16Array(g.length),p=0;p<g.length;p++){h[p]=0,u[p]=0;for(var v=0;v<g[p].length;v++)u[p]+=g[p][v]}for(var y=0,p=0;p<g.length;p++)for(var v=p+1;v<g.length;v++)y+=g[p][v];for(var m=y-g.length+1,f=!0,p=0;p<u.length;p++)3!==u[p]&&(f=!1);if(f&&(m=2+y-g.length),1==m){l.push([].concat(n(i)));continue}for(var b=e.getPathIncludedDistanceMatrices(g),x=b.d,d=b.pe,k=b.pe_prime,C=e.getRingCandidates(x,d,k),c=e.getSSSR(C,x,g,d,k,u,h,m),p=0;p<c.length;p++){var S=Array(c[p].size),R=0,A=!0,j=!1,T=void 0;try{for(var w=c[p][Symbol.iterator](),I,P;!(A=(I=w.next()).done);A=!0)P=I.value,S[R++]=i[P]}catch(e){j=!0,T=e}finally{try{!A&&w.return&&w.return()}finally{if(j)throw T}}l.push(S)}}return l}},{key:"matrixToString",value:function(e){for(var t="",n=0;n<e.length;n++){for(var i=0;i<e[n].length;i++)t+=e[n][i]+" ";t+="\n"}return t}},{key:"getPathIncludedDistanceMatrices",value:function(e){for(var t=e.length,r=Array(t),a=Array(t),o=Array(t),s=0,l=0,g=0,n=t;n--;){r[n]=Array(t),a[n]=Array(t),o[n]=Array(t);for(var i=t;i--;)r[n][i]=n===i||1===e[n][i]?e[n][i]:Number.POSITIVE_INFINITY,a[n][i]=1===r[n][i]?[[[n,i]]]:[],o[n][i]=[]}for(var u=t,i;u--;)for(n=t;n--;)for(i=t;i--;){var h=r[n][i],c=r[n][u]+r[u][i];if(h>c){var s,l,g;if(h===c+1)for(o[n][i]=[a[n][i].length],s=a[n][i].length;s--;)for(o[n][i][s]=[a[n][i][s].length],l=a[n][i][s].length;l--;)for(o[n][i][s][l]=[a[n][i][s][l].length],g=a[n][i][s][l].length;g--;)o[n][i][s][l][g]=[a[n][i][s][l][0],a[n][i][s][l][1]];else o[n][i]=[];for(r[n][i]=c,a[n][i]=[[]],s=a[n][u][0].length;s--;)a[n][i][0].push(a[n][u][0][s]);for(s=a[u][i][0].length;s--;)a[n][i][0].push(a[u][i][0][s])}else if(h===c){if(a[n][u].length&&a[u][i].length){var s;if(a[n][i].length){var p=[];for(s=a[n][u][0].length;s--;)p.push(a[n][u][0][s]);for(s=a[u][i][0].length;s--;)p.push(a[u][i][0][s]);a[n][i].push(p)}else{var v=[];for(s=a[n][u][0].length;s--;)v.push(a[n][u][0][s]);for(s=a[u][i][0].length;s--;)v.push(a[u][i][0][s]);a[n][i][0]=v}}}else if(h===c-1){var s;if(o[n][i].length){var y=[];for(s=a[n][u][0].length;s--;)y.push(a[n][u][0][s]);for(s=a[u][i][0].length;s--;)y.push(a[u][i][0][s]);o[n][i].push(y)}else{var m=[];for(s=a[n][u][0].length;s--;)m.push(a[n][u][0][s]);for(s=a[u][i][0].length;s--;)m.push(a[u][i][0][s]);o[n][i][0]=m}}}return{d:r,pe:a,pe_prime:o}}},{key:"getRingCandidates",value:function(e,t,n){for(var r=e.length,a=[],o=0,l=0;l<r;l++)for(var i=0;i<r;i++)if(0===e[l][i]||1===t[l][i].length&&0===n[l][i])continue;else o=0===n[l][i].length?2*e[l][i]:2*(e[l][i]+0.5),o!=Infinity&&a.push([o,t[l][i],n[l][i]]);return a.sort(function(e,t){return e[0]-t[0]}),a}},{key:"getSSSR",value:function(t,n,r,a,o,l,s,g){for(var d=[],u=[],h=0;h<t.length;h++)if(0!=t[h][0]%2)for(var i=0,c;i<t[h][2].length;i++){c=t[h][1][0].concat(t[h][2][i]);for(var p=0;p<c.length;p++)c[p][0].constructor===Array&&(c[p]=c[p][0]);var v=e.bondsToAtoms(c);if(e.getBondCount(v,r)!==v.size||e.pathSetsContain(d,v,c,u,l,s)||(d.push(v),u=u.concat(c)),d.length>g)return d}else for(var y=0,m;y<t[h][1].length-1;y++){m=t[h][1][y].concat(t[h][1][y+1]);for(var p=0;p<m.length;p++)m[p][0].constructor===Array&&(m[p]=m[p][0]);var f=e.bondsToAtoms(m);if(e.getBondCount(f,r)!==f.size||e.pathSetsContain(d,f,m,u,l,s)||(d.push(f),u=u.concat(m)),d.length>g)return d}return d}},{key:"getEdgeCount",value:function(e){for(var t=0,n=e.length,r=n-1;r--;)for(var i=n;i--;)1===e[r][i]&&t++;return t}},{key:"getEdgeList",value:function(e){for(var t=e.length,n=[],r=t-1;r--;)for(var i=t;i--;)1===e[r][i]&&n.push([r,i]);return n}},{key:"bondsToAtoms",value:function(e){for(var t=new Set,n=e.length;n--;)t.add(e[n][0]),t.add(e[n][1]);return t}},{key:"getBondCount",value:function(e,t){var n=0,i=!0,r=!1,a;try{for(var o=e[Symbol.iterator](),l;!(i=(l=o.next()).done);i=!0){var s=l.value,g=!0,d=!1,u=void 0;try{for(var h=e[Symbol.iterator](),c,p;!(g=(c=h.next()).done);g=!0)(p=c.value,s!==p)&&(n+=t[s][p])}catch(e){d=!0,u=e}finally{try{!g&&h.return&&h.return()}finally{if(d)throw u}}}}catch(e){r=!0,a=e}finally{try{!i&&o.return&&o.return()}finally{if(r)throw a}}return n/2}},{key:"pathSetsContain",value:function(t,n,r,a,o,l){for(var s=t.length;s--;){if(e.isSupersetOf(n,t[s]))return!0;if(t[s].size===n.size&&e.areSetsEqual(t[s],n))return!0}var i=0,g=!1;for(s=r.length;s--;)for(var d=a.length;d--;)(r[s][0]===a[d][0]&&r[s][1]===a[d][1]||r[s][1]===a[d][0]&&r[s][0]===a[d][1])&&i++,i===r.length&&(g=!0);var u=!1;if(g){var h=!0,c=!1,p;try{for(var v=n[Symbol.iterator](),y,m;!(h=(y=v.next()).done);h=!0)if(m=y.value,l[m]<o[m]){u=!0;break}}catch(e){c=!0,p=e}finally{try{!h&&v.return&&v.return()}finally{if(c)throw p}}}if(g&&!u)return!0;var f=!0,b=!1,x;try{for(var k=n[Symbol.iterator](),C,S;!(f=(C=k.next()).done);f=!0)S=C.value,l[S]++}catch(e){b=!0,x=e}finally{try{!f&&k.return&&k.return()}finally{if(b)throw x}}return!1}},{key:"areSetsEqual",value:function(e,t){if(e.size!==t.size)return!1;var n=!0,i=!1,r;try{for(var a=e[Symbol.iterator](),o,l;!(n=(o=a.next()).done);n=!0)if(l=o.value,!t.has(l))return!1}catch(e){i=!0,r=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw r}}return!0}},{key:"isSupersetOf",value:function(e,t){var n=!0,i=!1,r;try{for(var a=t[Symbol.iterator](),o,l;!(n=(o=a.next()).done);n=!0)if(l=o.value,!e.has(l))return!1}catch(e){i=!0,r=e}finally{try{!n&&a.return&&a.return()}finally{if(i)throw r}}return!0}}]),e}();t.exports=o},{"./Graph":7}],14:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var i=Math.sin,r=Math.cos,a=Math.sqrt,o=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),l=function(){function e(t,i){n(this,e),0==arguments.length?(this.x=0,this.y=0):1==arguments.length?(this.x=t.x,this.y=t.y):(this.x=t,this.y=i)}var t=Math.acos;return o(e,[{key:"clone",value:function(){return new e(this.x,this.y)}},{key:"toString",value:function(){return"("+this.x+","+this.y+")"}},{key:"add",value:function(e){return this.x+=e.x,this.y+=e.y,this}},{key:"subtract",value:function(e){return this.x-=e.x,this.y-=e.y,this}},{key:"divide",value:function(e){return this.x/=e,this.y/=e,this}},{key:"multiply",value:function(e){return this.x*=e.x,this.y*=e.y,this}},{key:"multiplyScalar",value:function(e){return this.x*=e,this.y*=e,this}},{key:"invert",value:function(){return this.x=-this.x,this.y=-this.y,this}},{key:"angle",value:function(){return Math.atan2(this.y,this.x)}},{key:"distance",value:function(e){return a((e.x-this.x)*(e.x-this.x)+(e.y-this.y)*(e.y-this.y))}},{key:"distanceSq",value:function(e){return(e.x-this.x)*(e.x-this.x)+(e.y-this.y)*(e.y-this.y)}},{key:"clockwise",value:function(e){var t=this.y*e.x,n=this.x*e.y;return t>n?-1:t==n?0:1}},{key:"relativeClockwise",value:function(e,t){var n=(this.y-e.y)*(t.x-e.x),i=(this.x-e.x)*(t.y-e.y);return n>i?-1:n==i?0:1}},{key:"rotate",value:function(t){var n=new e(0,0),a=r(t),o=i(t);return n.x=this.x*a-this.y*o,n.y=this.x*o+this.y*a,this.x=n.x,this.y=n.y,this}},{key:"rotateAround",value:function(e,t){var n=i(e),a=r(e);this.x-=t.x,this.y-=t.y;var o=this.x*a-this.y*n,l=this.x*n+this.y*a;return this.x=o+t.x,this.y=l+t.y,this}},{key:"rotateTo",value:function(t,n){var i=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0;this.x+=1e-3,this.y-=1e-3;var r=e.subtract(this,n),a=e.subtract(t,n),o=e.angle(a,r);return this.rotateAround(o+i,n),this}},{key:"rotateAwayFrom",value:function(e,t,n){this.rotateAround(n,t);var i=this.distanceSq(e);this.rotateAround(-2*n,t);var r=this.distanceSq(e);r<i&&this.rotateAround(2*n,t)}},{key:"getRotateAwayFromAngle",value:function(e,t,n){var i=this.clone();i.rotateAround(n,t);var r=i.distanceSq(e);i.rotateAround(-2*n,t);var a=i.distanceSq(e);return a<r?n:-n}},{key:"getRotateTowardsAngle",value:function(e,t,n){var i=this.clone();i.rotateAround(n,t);var r=i.distanceSq(e);i.rotateAround(-2*n,t);var a=i.distanceSq(e);return a>r?n:-n}},{key:"getRotateToAngle",value:function(t,n){var i=e.subtract(this,n),r=e.subtract(t,n),a=e.angle(r,i);return Number.isNaN(a)?0:a}},{key:"isInPolygon",value:function(e){for(var t=!1,n=0,i=e.length-1;n<e.length;i=n++)e[n].y>this.y!=e[i].y>this.y&&this.x<(e[i].x-e[n].x)*(this.y-e[n].y)/(e[i].y-e[n].y)+e[n].x&&(t=!t);return t}},{key:"length",value:function(){return a(this.x*this.x+this.y*this.y)}},{key:"lengthSq",value:function(){return this.x*this.x+this.y*this.y}},{key:"normalize",value:function(){return this.divide(this.length()),this}},{key:"normalized",value:function(){return e.divideScalar(this,this.length())}},{key:"whichSide",value:function(e,t){return(this.x-e.x)*(t.y-e.y)-(this.y-e.y)*(t.x-e.x)}},{key:"sameSideAs",value:function(e,t,n){var i=this.whichSide(e,t),r=n.whichSide(e,t);return 0>i&&0>r||0==i&&0==r||0<i&&0<r}}],[{key:"add",value:function(t,n){return new e(t.x+n.x,t.y+n.y)}},{key:"subtract",value:function(t,n){return new e(t.x-n.x,t.y-n.y)}},{key:"multiply",value:function(t,n){return new e(t.x*n.x,t.y*n.y)}},{key:"multiplyScalar",value:function(t,n){return new e(t.x,t.y).multiplyScalar(n)}},{key:"midpoint",value:function(t,n){return new e((t.x+n.x)/2,(t.y+n.y)/2)}},{key:"normals",value:function(t,n){var i=e.subtract(n,t);return[new e(-i.y,i.x),new e(i.y,-i.x)]}},{key:"units",value:function(t,n){var i=e.subtract(n,t);return[new e(-i.y,i.x).normalize(),new e(i.y,-i.x).normalize()]}},{key:"divide",value:function(t,n){return new e(t.x/n.x,t.y/n.y)}},{key:"divideScalar",value:function(t,n){return new e(t.x/n,t.y/n)}},{key:"dot",value:function(e,t){return e.x*t.x+e.y*t.y}},{key:"angle",value:function(n,i){var r=e.dot(n,i);return t(r/(n.length()*i.length()))}},{key:"threePointangle",value:function(n,i,r){var a=e.subtract(i,n),o=e.subtract(r,i),l=n.distance(i),s=i.distance(r);return t(e.dot(a,o)/(l*s))}},{key:"scalarProjection",value:function(t,n){var i=n.normalized();return e.dot(t,i)}},{key:"averageDirection",value:function(t){for(var n=new e(0,0),r=0,i;r<t.length;r++)i=t[r],n.add(i);return n.normalize()}}]),e}();t.exports=l},{}],15:[function(e,t){"use strict";function n(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}var r=Math.round,i=function(){function e(e,t){for(var n=0,i;n<t.length;n++)i=t[n],i.enumerable=i.enumerable||!1,i.configurable=!0,"value"in i&&(i.writable=!0),Object.defineProperty(e,i.key,i)}return function(t,n,i){return n&&e(t.prototype,n),i&&e(t,i),t}}(),a=e("./MathHelper"),o=e("./ArrayHelper"),l=e("./Vector2"),s=e("./Atom"),g=function(){function e(t){var i=1<arguments.length&&void 0!==arguments[1]?arguments[1]:0,r=2<arguments.length&&void 0!==arguments[2]?arguments[2]:0;n(this,e),this.id=null,this.value=t,this.position=new l(i?i:0,r?r:0),this.previousPosition=new l(0,0),this.parentVertexId=null,this.children=[],this.spanningTreeChildren=[],this.edges=[],this.positioned=!1,this.angle=null,this.dir=1,this.neighbourCount=0,this.neighbours=[],this.neighbouringElements=[],this.forcePositioned=!1}return i(e,[{key:"setPosition",value:function(e,t){this.position.x=e,this.position.y=t}},{key:"setPositionFromVector",value:function(e){this.position.x=e.x,this.position.y=e.y}},{key:"addChild",value:function(e){this.children.push(e),this.neighbours.push(e),this.neighbourCount++}},{key:"addRingbondChild",value:function(e,t){if(this.children.push(e),this.value.bracket){var n=1;0===this.id&&0===this.value.bracket.hcount&&(n=0),1===this.value.bracket.hcount&&0===t&&(n=2),1===this.value.bracket.hcount&&1===t&&(3>this.neighbours.length?n=2:n=3),null===this.value.bracket.hcount&&0===t&&(n=1),null===this.value.bracket.hcount&&1===t&&(3>this.neighbours.length?n=1:n=2),this.neighbours.splice(n,0,e)}else this.neighbours.push(e);this.neighbourCount++}},{key:"setParentVertexId",value:function(e){this.neighbourCount++,this.parentVertexId=e,this.neighbours.push(e)}},{key:"isTerminal",value:function(){return!!this.value.hasAttachedPseudoElements||null===this.parentVertexId&&2>this.children.length||0===this.children.length}},{key:"clone",value:function(){var t=new e(this.value,this.position.x,this.position.y);return t.id=this.id,t.previousPosition=new l(this.previousPosition.x,this.previousPosition.y),t.parentVertexId=this.parentVertexId,t.children=o.clone(this.children),t.spanningTreeChildren=o.clone(this.spanningTreeChildren),t.edges=o.clone(this.edges),t.positioned=this.positioned,t.angle=this.angle,t.forcePositioned=this.forcePositioned,t}},{key:"equals",value:function(e){return this.id===e.id}},{key:"getAngle",value:function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,t=1<arguments.length&&void 0!==arguments[1]&&arguments[1],n=null;return n=e?l.subtract(this.position,e):l.subtract(this.position,this.previousPosition),t?a.toDeg(n.angle()):n.angle()}},{key:"getTextDirection",value:function(e){for(var t=this.getDrawnNeighbours(e),n=[],o=0;o<t.length;o++)n.push(this.getAngle(e[t[o]].position));var i=a.meanAngle(n),l=Math.PI/2;return i=r(r(i/l)*l),2===i?"down":-2===i?"up":0===i||-0===i?"right":3===i||-3===i?"left":"down"}},{key:"getNeighbours",value:function(){var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null;if(null===e)return this.neighbours.slice();for(var t=[],n=0;n<this.neighbours.length;n++)this.neighbours[n]!==e&&t.push(this.neighbours[n]);return t}},{key:"getDrawnNeighbours",value:function(e){for(var t=[],n=0;n<this.neighbours.length;n++)e[this.neighbours[n]].value.isDrawn&&t.push(this.neighbours[n]);return t}},{key:"getNeighbourCount",value:function(){return this.neighbourCount}},{key:"getSpanningTreeNeighbours",value:function(){for(var e=0<arguments.length&&void 0!==arguments[0]?arguments[0]:null,t=[],n=0;n<this.spanningTreeChildren.length;n++)(void 0===e||e!=this.spanningTreeChildren[n])&&t.push(this.spanningTreeChildren[n]);return null!=this.parentVertexId&&(void 0===e||e!=this.parentVertexId)&&t.push(this.parentVertexId),t}},{key:"getNextInRing",value:function(e,t,n){for(var r=this.getNeighbours(),a=0;a<r.length;a++)if(o.contains(e[r[a]].value.rings,{value:t})&&r[a]!=n)return r[a];return null}}]),e}();t.exports=g},{"./ArrayHelper":2,"./Atom":3,"./MathHelper":9,"./Vector2":14}]},{},[1]);
+//# sourceMappingURL=smiles-drawer.min.js.map