UNPKG

smiles-drawer

Version:

A SMILES drawer and parser. Generate molecular structure depictions in pure JavaScript.

904 lines (748 loc) 128 kB
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Drawer.js - Documentation</title> <script src="scripts/prettify/prettify.js"></script> <script src="scripts/prettify/lang-css.js"></script> <!--[if lt IE 9]> <script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <link type="text/css" rel="stylesheet" href="styles/prettify.css"> <link type="text/css" rel="stylesheet" href="styles/jsdoc.css"> </head> <body> <input type="checkbox" id="nav-trigger" class="nav-trigger" /> <label for="nav-trigger" class="navicon-button x"> <div class="navicon"></div> </label> <label for="nav-trigger" class="overlay"></label> <nav> <h2><a href="index.html">Home</a></h2><h3>Classes</h3><ul><li><a href="ArrayHelper.html">ArrayHelper</a><ul class='methods'><li data-type='method'><a href="ArrayHelper.html#.clone">clone</a></li><li data-type='method'><a href="ArrayHelper.html#.contains">contains</a></li><li data-type='method'><a href="ArrayHelper.html#.containsAll">containsAll</a></li><li data-type='method'><a href="ArrayHelper.html#.count">count</a></li><li data-type='method'><a href="ArrayHelper.html#.deepCopy">deepCopy</a></li><li data-type='method'><a href="ArrayHelper.html#.each">each</a></li><li data-type='method'><a href="ArrayHelper.html#.equals">equals</a></li><li data-type='method'><a href="ArrayHelper.html#.get">get</a></li><li data-type='method'><a href="ArrayHelper.html#.intersection">intersection</a></li><li data-type='method'><a href="ArrayHelper.html#.merge">merge</a></li><li data-type='method'><a href="ArrayHelper.html#.print">print</a></li><li data-type='method'><a href="ArrayHelper.html#.remove">remove</a></li><li data-type='method'><a href="ArrayHelper.html#.removeAll">removeAll</a></li><li data-type='method'><a href="ArrayHelper.html#.removeUnique">removeUnique</a></li><li data-type='method'><a href="ArrayHelper.html#.sortByAtomicNumberDesc">sortByAtomicNumberDesc</a></li><li data-type='method'><a href="ArrayHelper.html#.toggle">toggle</a></li><li data-type='method'><a href="ArrayHelper.html#.unique">unique</a></li></ul></li><li><a href="Atom.html">Atom</a><ul class='methods'><li data-type='method'><a href="Atom.html#addAnchoredRing">addAnchoredRing</a></li><li data-type='method'><a href="Atom.html#addNeighbouringElement">addNeighbouringElement</a></li><li data-type='method'><a href="Atom.html#attachPseudoElement">attachPseudoElement</a></li><li data-type='method'><a href="Atom.html#backupRings">backupRings</a></li><li data-type='method'><a href="Atom.html#getAtomicNumber">getAtomicNumber</a></li><li data-type='method'><a href="Atom.html#getAttachedPseudoElements">getAttachedPseudoElements</a></li><li data-type='method'><a href="Atom.html#getAttachedPseudoElementsCount">getAttachedPseudoElementsCount</a></li><li data-type='method'><a href="Atom.html#getMaxBonds">getMaxBonds</a></li><li data-type='method'><a href="Atom.html#getRingbondCount">getRingbondCount</a></li><li data-type='method'><a href="Atom.html#haveCommonRingbond">haveCommonRingbond</a></li><li data-type='method'><a href="Atom.html#isHeteroAtom">isHeteroAtom</a></li><li data-type='method'><a href="Atom.html#neighbouringElementsEqual">neighbouringElementsEqual</a></li><li data-type='method'><a href="Atom.html#restoreRings">restoreRings</a></li></ul></li><li><a href="CanvasWrapper.html">CanvasWrapper</a><ul class='methods'><li data-type='method'><a href="CanvasWrapper.html#clear">clear</a></li><li data-type='method'><a href="CanvasWrapper.html#drawAromaticityRing">drawAromaticityRing</a></li><li data-type='method'><a href="CanvasWrapper.html#drawBall">drawBall</a></li><li data-type='method'><a href="CanvasWrapper.html#drawCircle">drawCircle</a></li><li data-type='method'><a href="CanvasWrapper.html#drawDashedWedge">drawDashedWedge</a></li><li data-type='method'><a href="CanvasWrapper.html#drawDebugPoint">drawDebugPoint</a></li><li data-type='method'><a href="CanvasWrapper.html#drawDebugText">drawDebugText</a></li><li data-type='method'><a href="CanvasWrapper.html#drawLine">drawLine</a></li><li data-type='method'><a href="CanvasWrapper.html#drawPoint">drawPoint</a></li><li data-type='method'><a href="CanvasWrapper.html#drawText">drawText</a></li><li data-type='method'><a href="CanvasWrapper.html#drawWedge">drawWedge</a></li><li data-type='method'><a href="CanvasWrapper.html#getChargeText">getChargeText</a></li><li data-type='method'><a href="CanvasWrapper.html#getColor">getColor</a></li><li data-type='method'><a href="CanvasWrapper.html#reset">reset</a></li><li data-type='method'><a href="CanvasWrapper.html#scale">scale</a></li><li data-type='method'><a href="CanvasWrapper.html#setTheme">setTheme</a></li><li data-type='method'><a href="CanvasWrapper.html#updateSize">updateSize</a></li></ul></li><li><a href="Drawer.html">Drawer</a><ul class='methods'><li data-type='method'><a href="Drawer.html#addRing">addRing</a></li><li data-type='method'><a href="Drawer.html#addRingConnection">addRingConnection</a></li><li data-type='method'><a href="Drawer.html#annotateStereochemistry">annotateStereochemistry</a></li><li data-type='method'><a href="Drawer.html#areVerticesInSameRing">areVerticesInSameRing</a></li><li data-type='method'><a href="Drawer.html#backupRingInformation">backupRingInformation</a></li><li data-type='method'><a href="Drawer.html#chooseSide">chooseSide</a></li><li data-type='method'><a href="Drawer.html#createBridgedRing">createBridgedRing</a></li><li data-type='method'><a href="Drawer.html#createNextBond">createNextBond</a></li><li data-type='method'><a href="Drawer.html#createRing">createRing</a></li><li data-type='method'><a href="Drawer.html#draw">draw</a></li><li data-type='method'><a href="Drawer.html#drawEdge">drawEdge</a></li><li data-type='method'><a href="Drawer.html#drawEdges">drawEdges</a></li><li data-type='method'><a href="Drawer.html#drawVertices">drawVertices</a></li><li data-type='method'><a href="Drawer.html#edgeRingCount">edgeRingCount</a></li><li data-type='method'><a href="Drawer.html#extend">extend</a></li><li data-type='method'><a href="Drawer.html#getBridgedRingRings">getBridgedRingRings</a></li><li data-type='method'><a href="Drawer.html#getBridgedRings">getBridgedRings</a></li><li data-type='method'><a href="Drawer.html#getClosestVertex">getClosestVertex</a></li><li data-type='method'><a href="Drawer.html#getCommonRingbondNeighbour">getCommonRingbondNeighbour</a></li><li data-type='method'><a href="Drawer.html#getCommonRings">getCommonRings</a></li><li data-type='method'><a href="Drawer.html#getCurrentCenterOfMass">getCurrentCenterOfMass</a></li><li data-type='method'><a href="Drawer.html#getCurrentCenterOfMassInNeigbourhood">getCurrentCenterOfMassInNeigbourhood</a></li><li data-type='method'><a href="Drawer.html#getEdgeNormals">getEdgeNormals</a></li><li data-type='method'><a href="Drawer.html#getFusedRings">getFusedRings</a></li><li data-type='method'><a href="Drawer.html#getHeavyAtomCount">getHeavyAtomCount</a></li><li data-type='method'><a href="Drawer.html#getLargestOrAromaticCommonRing">getLargestOrAromaticCommonRing</a></li><li data-type='method'><a href="Drawer.html#getLastVertexWithAngle">getLastVertexWithAngle</a></li><li data-type='method'><a href="Drawer.html#getMolecularFormula">getMolecularFormula</a></li><li data-type='method'><a href="Drawer.html#getNonRingNeighbours">getNonRingNeighbours</a></li><li data-type='method'><a href="Drawer.html#getOverlapScore">getOverlapScore</a></li><li data-type='method'><a href="Drawer.html#getRing">getRing</a></li><li data-type='method'><a href="Drawer.html#getRingbondType">getRingbondType</a></li><li data-type='method'><a href="Drawer.html#getRingConnection">getRingConnection</a></li><li data-type='method'><a href="Drawer.html#getRingConnections">getRingConnections</a></li><li data-type='method'><a href="Drawer.html#getRingCount">getRingCount</a></li><li data-type='method'><a href="Drawer.html#getSpiros">getSpiros</a></li><li data-type='method'><a href="Drawer.html#getSubringCenter">getSubringCenter</a></li><li data-type='method'><a href="Drawer.html#getSubtreeOverlapScore">getSubtreeOverlapScore</a></li><li data-type='method'><a href="Drawer.html#getTotalOverlapScore">getTotalOverlapScore</a></li><li data-type='method'><a href="Drawer.html#getVerticesAt">getVerticesAt</a></li><li data-type='method'><a href="Drawer.html#hasBridgedRing">hasBridgedRing</a></li><li data-type='method'><a href="Drawer.html#initPseudoElements">initPseudoElements</a></li><li data-type='method'><a href="Drawer.html#initRings">initRings</a></li><li data-type='method'><a href="Drawer.html#isEdgeInRing">isEdgeInRing</a></li><li data-type='method'><a href="Drawer.html#isEdgeRotatable">isEdgeRotatable</a></li><li data-type='method'><a href="Drawer.html#isPartOfBridgedRing">isPartOfBridgedRing</a></li><li data-type='method'><a href="Drawer.html#isPointInRing">isPointInRing</a></li><li data-type='method'><a href="Drawer.html#isRingAromatic">isRingAromatic</a></li><li data-type='method'><a href="Drawer.html#position">position</a></li><li data-type='method'><a href="Drawer.html#printRingInfo">printRingInfo</a></li><li data-type='method'><a href="Drawer.html#removeRing">removeRing</a></li><li data-type='method'><a href="Drawer.html#removeRingConnection">removeRingConnection</a></li><li data-type='method'><a href="Drawer.html#removeRingConnectionsBetween">removeRingConnectionsBetween</a></li><li data-type='method'><a href="Drawer.html#resolvePrimaryOverlaps">resolvePrimaryOverlaps</a></li><li data-type='method'><a href="Drawer.html#resolveSecondaryOverlaps">resolveSecondaryOverlaps</a></li><li data-type='method'><a href="Drawer.html#restoreRingInformation">restoreRingInformation</a></li><li data-type='method'><a href="Drawer.html#rotateDrawing">rotateDrawing</a></li><li data-type='method'><a href="Drawer.html#rotateSubtree">rotateSubtree</a></li><li data-type='method'><a href="Drawer.html#setRingCenter">setRingCenter</a></li><li data-type='method'><a href="Drawer.html#visitStereochemistry">visitStereochemistry</a></li></ul></li><li><a href="Edge.html">Edge</a><ul class='methods'><li data-type='method'><a href="Edge.html#setBondType">setBondType</a></li></ul></li><li><a href="Graph.html">Graph</a><ul class='methods'><li data-type='method'><a href="Graph.html#._ccCountDfs">_ccCountDfs</a></li><li data-type='method'><a href="Graph.html#._ccGetDfs">_ccGetDfs</a></li><li data-type='method'><a href="Graph.html#.getConnectedComponentCount">getConnectedComponentCount</a></li><li data-type='method'><a href="Graph.html#.getConnectedComponents">getConnectedComponents</a></li><li data-type='method'><a href="Graph.html#_bridgeDfs">_bridgeDfs</a></li><li data-type='method'><a href="Graph.html#_init">_init</a></li><li data-type='method'><a href="Graph.html#addEdge">addEdge</a></li><li data-type='method'><a href="Graph.html#addVertex">addVertex</a></li><li data-type='method'><a href="Graph.html#clear">clear</a></li><li data-type='method'><a href="Graph.html#getAdjacencyList">getAdjacencyList</a></li><li data-type='method'><a href="Graph.html#getAdjacencyMatrix">getAdjacencyMatrix</a></li><li data-type='method'><a href="Graph.html#getBridges">getBridges</a></li><li data-type='method'><a href="Graph.html#getComponentsAdjacencyMatrix">getComponentsAdjacencyMatrix</a></li><li data-type='method'><a href="Graph.html#getDistanceMatrix">getDistanceMatrix</a></li><li data-type='method'><a href="Graph.html#getEdge">getEdge</a></li><li data-type='method'><a href="Graph.html#getEdgeList">getEdgeList</a></li><li data-type='method'><a href="Graph.html#getEdges">getEdges</a></li><li data-type='method'><a href="Graph.html#getSubgraphAdjacencyList">getSubgraphAdjacencyList</a></li><li data-type='method'><a href="Graph.html#getSubgraphAdjacencyMatrix">getSubgraphAdjacencyMatrix</a></li><li data-type='method'><a href="Graph.html#getSubgraphDistanceMatrix">getSubgraphDistanceMatrix</a></li><li data-type='method'><a href="Graph.html#getTreeDepth">getTreeDepth</a></li><li data-type='method'><a href="Graph.html#getVertexList">getVertexList</a></li><li data-type='method'><a href="Graph.html#hasEdge">hasEdge</a></li><li data-type='method'><a href="Graph.html#kkLayout">kkLayout</a></li><li data-type='method'><a href="Graph.html#traverseBF">traverseBF</a></li><li data-type='method'><a href="Graph.html#traverseTree">traverseTree</a></li></ul></li><li><a href="Line.html">Line</a><ul class='methods'><li data-type='method'><a href="Line.html#clone">clone</a></li><li data-type='method'><a href="Line.html#getAngle">getAngle</a></li><li data-type='method'><a href="Line.html#getLeftChiral">getLeftChiral</a></li><li data-type='method'><a href="Line.html#getLeftElement">getLeftElement</a></li><li data-type='method'><a href="Line.html#getLeftVector">getLeftVector</a></li><li data-type='method'><a href="Line.html#getLength">getLength</a></li><li data-type='method'><a href="Line.html#getRightChiral">getRightChiral</a></li><li data-type='method'><a href="Line.html#getRightElement">getRightElement</a></li><li data-type='method'><a href="Line.html#getRightVector">getRightVector</a></li><li data-type='method'><a href="Line.html#rotate">rotate</a></li><li data-type='method'><a href="Line.html#rotateToXAxis">rotateToXAxis</a></li><li data-type='method'><a href="Line.html#setLeftVector">setLeftVector</a></li><li data-type='method'><a href="Line.html#setRightVector">setRightVector</a></li><li data-type='method'><a href="Line.html#shorten">shorten</a></li><li data-type='method'><a href="Line.html#shortenFrom">shortenFrom</a></li><li data-type='method'><a href="Line.html#shortenLeft">shortenLeft</a></li><li data-type='method'><a href="Line.html#shortenRight">shortenRight</a></li><li data-type='method'><a href="Line.html#shortenTo">shortenTo</a></li></ul></li><li><a href="MathHelper.html">MathHelper</a><ul class='methods'><li data-type='method'><a href="MathHelper.html#.apothem">apothem</a></li><li data-type='method'><a href="MathHelper.html#.centralAngle">centralAngle</a></li><li data-type='method'><a href="MathHelper.html#.innerAngle">innerAngle</a></li><li data-type='method'><a href="MathHelper.html#.meanAngle">meanAngle</a></li><li data-type='method'><a href="MathHelper.html#.parityOfPermutation">parityOfPermutation</a></li><li data-type='method'><a href="MathHelper.html#.polyCircumradius">polyCircumradius</a></li><li data-type='method'><a href="MathHelper.html#.round">round</a></li><li data-type='method'><a href="MathHelper.html#.toDeg">toDeg</a></li><li data-type='method'><a href="MathHelper.html#.toRad">toRad</a></li></ul></li><li><a href="Ring.html">Ring</a><ul class='methods'><li data-type='method'><a href="Ring.html#clone">clone</a></li><li data-type='method'><a href="Ring.html#contains">contains</a></li><li data-type='method'><a href="Ring.html#eachMember">eachMember</a></li><li data-type='method'><a href="Ring.html#getAngle">getAngle</a></li><li data-type='method'><a href="Ring.html#getDoubleBondCount">getDoubleBondCount</a></li><li data-type='method'><a href="Ring.html#getOrderedNeighbours">getOrderedNeighbours</a></li><li data-type='method'><a href="Ring.html#getPolygon">getPolygon</a></li><li data-type='method'><a href="Ring.html#getSize">getSize</a></li><li data-type='method'><a href="Ring.html#isBenzeneLike">isBenzeneLike</a></li></ul></li><li><a href="RingConnection.html">RingConnection</a><ul class='methods'><li data-type='method'><a href="RingConnection.html#.getNeighbours">getNeighbours</a></li><li data-type='method'><a href="RingConnection.html#.getVertices">getVertices</a></li><li data-type='method'><a href="RingConnection.html#.isBridge">isBridge</a></li><li data-type='method'><a href="RingConnection.html#addVertex">addVertex</a></li><li data-type='method'><a href="RingConnection.html#containsRing">containsRing</a></li><li data-type='method'><a href="RingConnection.html#isBridge">isBridge</a></li><li data-type='method'><a href="RingConnection.html#updateOther">updateOther</a></li></ul></li><li><a href="SSSR.html">SSSR</a><ul class='methods'><li data-type='method'><a href="SSSR.html#.areSetsEqual">areSetsEqual</a></li><li data-type='method'><a href="SSSR.html#.bondsToAtoms">bondsToAtoms</a></li><li data-type='method'><a href="SSSR.html#.getBondCount">getBondCount</a></li><li data-type='method'><a href="SSSR.html#.getEdgeCount">getEdgeCount</a></li><li data-type='method'><a href="SSSR.html#.getEdgeList">getEdgeList</a></li><li data-type='method'><a href="SSSR.html#.getPathIncludedDistanceMatrices">getPathIncludedDistanceMatrices</a></li><li data-type='method'><a href="SSSR.html#.getRingCandidates">getRingCandidates</a></li><li data-type='method'><a href="SSSR.html#.getRings">getRings</a></li><li data-type='method'><a href="SSSR.html#.getSSSR">getSSSR</a></li><li data-type='method'><a href="SSSR.html#.isSupersetOf">isSupersetOf</a></li><li data-type='method'><a href="SSSR.html#.matrixToString">matrixToString</a></li><li data-type='method'><a href="SSSR.html#.pathSetsContain">pathSetsContain</a></li></ul></li><li><a href="Vector2.html">Vector2</a><ul class='methods'><li data-type='method'><a href="Vector2.html#add">add</a></li><li data-type='method'><a href="Vector2.html#angle">angle</a></li><li data-type='method'><a href="Vector2.html#clockwise">clockwise</a></li><li data-type='method'><a href="Vector2.html#clone">clone</a></li><li data-type='method'><a href="Vector2.html#distance">distance</a></li><li data-type='method'><a href="Vector2.html#distanceSq">distanceSq</a></li><li data-type='method'><a href="Vector2.html#divide">divide</a></li><li data-type='method'><a href="Vector2.html#getRotateAwayFromAngle">getRotateAwayFromAngle</a></li><li data-type='method'><a href="Vector2.html#getRotateToAngle">getRotateToAngle</a></li><li data-type='method'><a href="Vector2.html#getRotateTowardsAngle">getRotateTowardsAngle</a></li><li data-type='method'><a href="Vector2.html#invert">invert</a></li><li data-type='method'><a href="Vector2.html#isInPolygon">isInPolygon</a></li><li data-type='method'><a href="Vector2.html#length">length</a></li><li data-type='method'><a href="Vector2.html#lengthSq">lengthSq</a></li><li data-type='method'><a href="Vector2.html#multiply">multiply</a></li><li data-type='method'><a href="Vector2.html#multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="Vector2.html#normalize">normalize</a></li><li data-type='method'><a href="Vector2.html#normalized">normalized</a></li><li data-type='method'><a href="Vector2.html#relativeClockwise">relativeClockwise</a></li><li data-type='method'><a href="Vector2.html#rotate">rotate</a></li><li data-type='method'><a href="Vector2.html#rotateAround">rotateAround</a></li><li data-type='method'><a href="Vector2.html#rotateAwayFrom">rotateAwayFrom</a></li><li data-type='method'><a href="Vector2.html#rotateTo">rotateTo</a></li><li data-type='method'><a href="Vector2.html#sameSideAs">sameSideAs</a></li><li data-type='method'><a href="Vector2.html#subtract">subtract</a></li><li data-type='method'><a href="Vector2.html#toString">toString</a></li><li data-type='method'><a href="Vector2.html#whichSide">whichSide</a></li><li data-type='method'><a href="Vector2.html#.add">add</a></li><li data-type='method'><a href="Vector2.html#.angle">angle</a></li><li data-type='method'><a href="Vector2.html#.averageDirection">averageDirection</a></li><li data-type='method'><a href="Vector2.html#.divide">divide</a></li><li data-type='method'><a href="Vector2.html#.divideScalar">divideScalar</a></li><li data-type='method'><a href="Vector2.html#.dot">dot</a></li><li data-type='method'><a href="Vector2.html#.midpoint">midpoint</a></li><li data-type='method'><a href="Vector2.html#.multiply">multiply</a></li><li data-type='method'><a href="Vector2.html#.multiplyScalar">multiplyScalar</a></li><li data-type='method'><a href="Vector2.html#.normals">normals</a></li><li data-type='method'><a href="Vector2.html#.scalarProjection">scalarProjection</a></li><li data-type='method'><a href="Vector2.html#.subtract">subtract</a></li><li data-type='method'><a href="Vector2.html#.threePointangle">threePointangle</a></li><li data-type='method'><a href="Vector2.html#.units">units</a></li></ul></li><li><a href="Vertex.html">Vertex</a><ul class='methods'><li data-type='method'><a href="Vertex.html#addChild">addChild</a></li><li data-type='method'><a href="Vertex.html#addRingbondChild">addRingbondChild</a></li><li data-type='method'><a href="Vertex.html#clone">clone</a></li><li data-type='method'><a href="Vertex.html#equals">equals</a></li><li data-type='method'><a href="Vertex.html#getAngle">getAngle</a></li><li data-type='method'><a href="Vertex.html#getDrawnNeighbours">getDrawnNeighbours</a></li><li data-type='method'><a href="Vertex.html#getNeighbourCount">getNeighbourCount</a></li><li data-type='method'><a href="Vertex.html#getNeighbours">getNeighbours</a></li><li data-type='method'><a href="Vertex.html#getNextInRing">getNextInRing</a></li><li data-type='method'><a href="Vertex.html#getSpanningTreeNeighbours">getSpanningTreeNeighbours</a></li><li data-type='method'><a href="Vertex.html#getTextDirection">getTextDirection</a></li><li data-type='method'><a href="Vertex.html#isTerminal">isTerminal</a></li><li data-type='method'><a href="Vertex.html#setParentVertexId">setParentVertexId</a></li><li data-type='method'><a href="Vertex.html#setPosition">setPosition</a></li><li data-type='method'><a href="Vertex.html#setPositionFromVector">setPositionFromVector</a></li></ul></li></ul> </nav> <div id="main"> <h1 class="page-title">Drawer.js</h1> <section> <article> <pre class="prettyprint source linenums"><code>//@ts-check const MathHelper = require('./MathHelper') const ArrayHelper = require('./ArrayHelper') const Vector2 = require('./Vector2') const Line = require('./Line') const Vertex = require('./Vertex') const Edge = require('./Edge') const Atom = require('./Atom') const Ring = require('./Ring') const RingConnection = require('./RingConnection') const CanvasWrapper = require('./CanvasWrapper') const Graph = require('./Graph') const 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. */ class Drawer { /** * The constructor for the class SmilesDrawer. * * @param {Object} options An object containing custom values for different options. It is merged with the default options. */ constructor(options) { 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. */ extend() { let that = this; let extended = {}; let deep = false; let i = 0; let length = arguments.length; if (Object.prototype.toString.call(arguments[0]) === '[object Boolean]') { deep = arguments[0]; i++; } let merge = function (obj) { for (var prop in obj) { if (Object.prototype.hasOwnProperty.call(obj, prop)) { if (deep &amp;&amp; Object.prototype.toString.call(obj[prop]) === '[object Object]') { extended[prop] = that.extend(true, extended[prop], obj[prop]); } else { extended[prop] = obj[prop]; } } } }; for (; i &lt; length; i++) { let obj = arguments[i]; merge(obj); } return extended; }; /** * 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. */ draw(data, target, themeName = 'light', infoOnly = 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(); let overlapScore = this.getOverlapScore(); this.totalOverlapScore = this.getOverlapScore().total; for (var o = 0; o &lt; this.opts.overlapResolutionIterations; o++) { for (var i = 0; i &lt; this.graph.edges.length; i++) { let edge = this.graph.edges[i]; if (this.isEdgeRotatable(edge)) { let subTreeDepthA = this.graph.getTreeDepth(edge.sourceId, edge.targetId); let subTreeDepthB = this.graph.getTreeDepth(edge.targetId, edge.sourceId); // Only rotate the shorter subtree let a = edge.targetId; let b = edge.sourceId; if (subTreeDepthA > subTreeDepthB) { a = edge.sourceId; b = edge.targetId; } let subTreeOverlap = this.getSubtreeOverlapScore(b, a, overlapScore.vertexScores); if (subTreeOverlap.value > this.opts.overlapSensitivity) { let vertexA = this.graph.vertices[a]; let vertexB = this.graph.vertices[b]; let neighboursB = vertexB.getNeighbours(a); if (neighboursB.length === 1) { let neighbour = this.graph.vertices[neighboursB[0]]; let 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 let 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 &amp;&amp; vertexA.value.rings.length !== 0) { continue; } let neighbourA = this.graph.vertices[neighboursB[0]]; let neighbourB = this.graph.vertices[neighboursB[1]]; if (neighbourA.value.rings.length === 1 &amp;&amp; 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 { let angleA = neighbourA.position.getRotateAwayFromAngle(vertexA.position, vertexB.position, MathHelper.toRad(120)); let 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); let 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 &amp;&amp; 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. */ edgeRingCount(edgeId) { let edge = this.graph.edges[edgeId]; let a = this.graph.vertices[edge.sourceId]; let 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. */ getBridgedRings() { let bridgedRings = Array(); for (var i = 0; i &lt; 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. */ getFusedRings() { let fusedRings = Array(); for (var i = 0; i &lt; 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. */ getSpiros() { let spiros = Array(); for (var i = 0; i &lt; 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. */ printRingInfo() { let result = ''; for (var i = 0; i &lt; this.rings.length; i++) { const 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. */ rotateDrawing() { // Rotate the vertices to make the molecule align horizontally // Find the longest distance let a = 0; let b = 0; let maxDist = 0; for (var i = 0; i &lt; this.graph.vertices.length; i++) { let vertexA = this.graph.vertices[i]; if (!vertexA.value.isDrawn) { continue; } for (var j = i + 1; j &lt; this.graph.vertices.length; j++) { let vertexB = this.graph.vertices[j]; if (!vertexB.value.isDrawn) { continue; } let dist = vertexA.position.distanceSq(vertexB.position); if (dist > maxDist) { maxDist = dist; a = i; b = j; } } } let angle = -Vector2.subtract(this.graph.vertices[a].position, this.graph.vertices[b].position).angle(); if (!isNaN(angle)) { // Round to 30 degrees let remainder = angle % 0.523599; // Round either up or down in 30 degree steps if (remainder &lt; 0.2617995) { angle = angle - remainder; } else { angle += 0.523599 - remainder; } // Finally, rotate everything for (var i = 0; i &lt; 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 &lt; 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. */ getTotalOverlapScore() { return this.totalOverlapScore; } /** * Returns the ring count of the current molecule. * * @returns {Number} The ring count. */ 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. */ hasBridgedRing() { return this.bridgedRing; } /** * Returns the number of heavy atoms (non-hydrogen) in the current molecule. * * @returns {Number} The heavy atom count. */ getHeavyAtomCount() { let hac = 0; for (var i = 0; i &lt; 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. */ getMolecularFormula() { let molecularFormula = ''; let counts = new Map(); // Initialize element count for (var i = 0; i &lt; this.graph.vertices.length; i++) { let 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 &amp;&amp; !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) { let 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')) { let count = counts.get('C'); molecularFormula += 'C' + (count > 1 ? count : ''); counts.delete('C'); } if (counts.has('H')) { let count = counts.get('H'); molecularFormula += 'H' + (count > 1 ? count : ''); counts.delete('H'); } let elements = Object.keys(Atom.atomicNumbers).sort(); elements.map(e => { if (counts.has(e)) { let count = counts.get(e); molecularFormula += e + (count > 1 ? count : ''); } }); 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. */ 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() &lt; 1 || vertexB.value.getRingbondCount() &lt; 1) { return null; } for (var i = 0; i &lt; vertexA.value.ringbonds.length; i++) { for (var j = 0; j &lt; 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. */ initRings() { let openBonds = new Map(); // Close the open ring bonds (spanning tree -> graph) for (var i = this.graph.vertices.length - 1; i >= 0; i--) { let vertex = this.graph.vertices[i]; if (vertex.value.ringbonds.length === 0) { continue; } for (var j = 0; j &lt; vertex.value.ringbonds.length; j++) { let ringbondId = vertex.value.ringbonds[j].id; let 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 { let sourceVertexId = vertex.id; let targetVertexId = openBonds.get(ringbondId)[0]; let targetRingbondBond = openBonds.get(ringbondId)[1]; let edge = new Edge(sourceVertexId, targetVertexId, 1); edge.setBondType(targetRingbondBond || ringbondBond || '-'); let edgeId = this.graph.addEdge(edge); let 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) let rings = SSSR.getRings(this.graph); if (rings === null) { return; } for (var i = 0; i &lt; rings.length; i++) { let ringVertices = [...rings[i]]; let ringId = this.addRing(new Ring(ringVertices)); // Add the ring to the atoms for (var j = 0; j &lt; 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 (&lt; 100) for (var i = 0; i &lt; this.rings.length - 1; i++) { for (var j = i + 1; j &lt; this.rings.length; j++) { let a = this.rings[i]; let b = this.rings[j]; let 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 &lt; this.rings.length; i++) { let 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 &lt; this.rings.length; i++) { let 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) { let id = -1; for (var i = 0; i &lt; this.rings.length; i++) { let ring = this.rings[i]; if (this.isPartOfBridgedRing(ring.id) &amp;&amp; !ring.isBridged) { id = ring.id; } } if (id === -1) { break; } let ring = this.getRing(id); let involvedRings = this.getBridgedRingRings(ring.id); this.bridgedRing = true; this.createBridgedRing(involvedRings, ring.members[0]); // Remove the rings for (var i = 0; i &lt; involvedRings.length; i++) { this.removeRing(involvedRings[i]); } } } 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 &lt; this.graph.vertices.length; i++) { let 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 let neighbour = this.graph.vertices[vertex.neighbours[0]]; neighbour.value.hasHydrogen = true; if (!neighbour.value.isStereoCenter || neighbour.value.rings.length &lt; 2 &amp;&amp; !neighbour.value.bridgedRing || neighbour.value.bridgedRing &amp;&amp; neighbour.value.originalRings.length &lt; 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. */ getBridgedRingRings(ringId) { let involvedRings = Array(); let that = this; let recurse = function (r) { let ring = that.getRing(r); involvedRings.push(r); for (var i = 0; i &lt; ring.neighbours.length; i++) { let n = ring.neighbours[i]; if (involvedRings.indexOf(n) === -1 &amp;&amp; n !== r &amp;&amp; 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. */ isPartOfBridgedRing(ringId) { for (var i = 0; i &lt; this.ringConnections.length; i++) { if (this.ringConnections[i].containsRing(ringId) &amp;&amp; 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. */ createBridgedRing(ringIds, sourceVertexId) { let ringMembers = new Set(); let vertices = new Set(); let neighbours = new Set(); for (var i = 0; i &lt; ringIds.length; i++) { let ring = this.getRing(ringIds[i]); ring.isPartOfBridged = true; for (var j = 0; j &lt; ring.members.length; j++) { vertices.add(ring.members[j]); } for (var j = 0; j &lt; ring.neighbours.length; j++) { let id = ring.neighbours[j]; if (ringIds.indexOf(id) === -1) { neighbours.add(ring.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). let leftovers = new Set(); for (let id of vertices) { let vertex = this.graph.vertices[id]; let 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 let tmp = Array(); let insideRing = Array(); for (let id of leftovers) { let vertex = this.graph.vertices[id]; let onRing = false; for (let j = 0; j &lt; vertex.edges.length; j++) { if (this.edgeRingCount(vertex.edges[j]) === 1) { onRing = true; } } if (onRing) { vertex.value.isBridgeNode = true; ringMembers.add(vertex.id); } else { vertex.value.isBridge = true; ringMembers.add(vertex.id); } } // Create the ring let ring = new Ring([...ringMembers]); ring.isBridged = true; ring.neighbours = [...neighbours]; for (var i = 0; i &lt; ringIds.length; i++) { ring.rings.push(this.getRing(ringIds[i]).clone()); } this.addRing(ring); for (va