smiles-drawer
Version:
A SMILES drawer and parser. Generate molecular structure depictions in pure JavaScript.
904 lines (748 loc) • 128 kB
HTML
<!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 && 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++) {
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 < this.opts.overlapResolutionIterations; o++) {
for (var i = 0; i < 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 && 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 && 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 && 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 < 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 < 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 < 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 < 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 < this.graph.vertices.length; i++) {
let vertexA = this.graph.vertices[i];
if (!vertexA.value.isDrawn) {
continue;
}
for (var j = i + 1; j < 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 < 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.
*/
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 < 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 < 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 && !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() < 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.
*/
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 < 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 < 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 < 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++) {
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 < 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 < 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 < this.rings.length; i++) {
let ring = this.rings[i];
if (this.isPartOfBridgedRing(ring.id) && !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 < 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 < 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 < 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.
*/
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 < ring.neighbours.length; i++) {
let 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.
*/
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.
*/
createBridgedRing(ringIds, sourceVertexId) {
let ringMembers = new Set();
let vertices = new Set();
let neighbours = new Set();
for (var i = 0; i < ringIds.length; i++) {
let ring = this.getRing(ringIds[i]);
ring.isPartOfBridged = true;
for (var j = 0; j < ring.members.length; j++) {
vertices.add(ring.members[j]);
}
for (var j = 0; j < 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 < 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 < ringIds.length; i++) {
ring.rings.push(this.getRing(ringIds[i]).clone());
}
this.addRing(ring);
for (va