1 //
2 // Copyright (C) 2014-2020 David Cosgrove and Greg Landrum
3 //
4 // @@ All Rights Reserved @@
5 // This file is part of the RDKit.
6 // The contents are covered by the terms of the BSD license
7 // which is included in the file license.txt, found at the root
8 // of the RDKit source tree.
9 //
10 // Original author: David Cosgrove (AstraZeneca)
11 // 27th May 2014
12 //
13 // This class makes a 2D drawing of an RDKit molecule.
14 // It draws heavily on $RDBASE/GraphMol/MolDrawing/MolDrawing.h.
15 // One purpose of this is to make it easier to overlay annotations on top of
16 // the molecule drawing, which is difficult to do from the output of
17 // MolDrawing.h
18 // The class design philosophy echoes a standard one:
19 // a virtual base class defines the interface and does all
20 // the heavy lifting and concrete derived classes implement
21 // library-specific drawing code such as drawing lines, writing strings
22 // etc.
23
24 #include <RDGeneral/export.h>
25 #ifndef RDKITMOLDRAW2D_H
26 #define RDKITMOLDRAW2D_H
27
28 #include <vector>
29
30 #include <Geometry/point.h>
31 #include <Geometry/Transform2D.h>
32 #include <GraphMol/RDKitBase.h>
33 #include <GraphMol/ChemReactions/Reaction.h>
34
35 // ****************************************************************************
36 using RDGeom::Point2D;
37
38 namespace RDKit {
39
40 class DrawText;
41 // for aligning the drawing of text to the passed in coords.
42 enum class OrientType : unsigned char { C = 0, N, E, S, W };
43 enum class TextAlignType : unsigned char { MIDDLE = 0, START, END };
44
45 struct DrawColour {
46 double r = 0.0, g = 0.0, b = 0.0, a = 1.0;
47 DrawColour() = default;
48 DrawColour(double r, double g, double b, double a = 1.0)
rDrawColour49 : r(r), g(g), b(b), a(a){};
50 bool operator==(const DrawColour &other) const {
51 return r == other.r && g == other.g && b == other.b && a == other.a;
52 }
53 bool feq(const DrawColour &other, double tol = 0.001,
54 bool ignoreAlpha = true) const {
55 return fabs(r - other.r) <= tol && fabs(g - other.g) <= tol &&
56 fabs(b - other.b) <= tol &&
57 (ignoreAlpha || fabs(a - other.a) <= tol);
58 };
59 DrawColour operator+(const DrawColour &other) const {
60 return {r + other.r, g + other.g, b + other.b, a + other.a};
61 }
62 DrawColour operator-(const DrawColour &other) const {
63 return {r - other.r, g - other.g, b - other.b, a - other.a};
64 }
65 DrawColour operator/(double v) const {
66 PRECONDITION(v != 0.0, "divide by zero");
67 return {r / v, g / v, b / v, a / v};
68 }
69 DrawColour operator*(double v) const { return {r * v, g * v, b * v, a * v}; }
70 };
71
72 //! for annotating the type of the extra shapes
73 enum class MolDrawShapeType {
74 Arrow, // ordering of points is: start, end, p1, p2
75 Polyline,
76 Ellipse,
77 };
78
79 //! extra shape to add to canvas
80 struct MolDrawShape {
81 MolDrawShapeType shapeType = MolDrawShapeType::Polyline;
82 std::vector<Point2D> points;
83 DrawColour lineColour{0, 0, 0};
84 int lineWidth = 2;
85 bool fill = false;
86 bool scaleLineWidth = false;
87 };
88
89 // for holding dimensions of the rectangle round a string.
90 struct StringRect {
91 Point2D trans_; // Where to draw char relative to other chars in string
92 Point2D offset_; // offset for draw coords so char is centred correctly
93 Point2D g_centre_; // glyph centre relative to the origin of the char.
94 double y_shift_; // shift the whole thing in y by this. For multi-line text.
95 double width_, height_; // of the glyph itself, not the character cell
96 double rect_corr_; // because if we move a char one way, we need to move the
97 // rectangle the other.
98 int clash_score_; // rough measure of how badly it clashed with other things
99 // lower is better, 0 is no clash.
100
StringRectStringRect101 StringRect()
102 : trans_(0.0, 0.0),
103 offset_(0.0, 0.0),
104 g_centre_(offset_),
105 y_shift_(0.0),
106 width_(0.0),
107 height_(0.0),
108 rect_corr_(0.0),
109 clash_score_(0) {}
StringRectStringRect110 StringRect(const Point2D &offset, const Point2D &g_centre, double w, double h)
111 : trans_(0.0, 0.0),
112 offset_(offset),
113 g_centre_(g_centre),
114 y_shift_(0.0),
115 width_(w),
116 height_(h),
117 rect_corr_(0.0),
118 clash_score_(0) {}
119 // tl is top, left; br is bottom, right of the glyph, relative to the
120 // centre. Padding in draw coords.
calcCornersStringRect121 void calcCorners(Point2D &tl, Point2D &tr, Point2D &br, Point2D &bl,
122 double padding) const {
123 double wb2 = padding + width_ / 2.0;
124 double hb2 = padding + height_ / 2.0;
125 Point2D c = trans_ + g_centre_ - offset_;
126 c.y -= y_shift_;
127 tl = Point2D(c.x - wb2, c.y - hb2);
128 tr = Point2D(c.x + wb2, c.y - hb2);
129 br = Point2D(c.x + wb2, c.y + hb2);
130 bl = Point2D(c.x - wb2, c.y + hb2);
131 }
doesItIntersectStringRect132 bool doesItIntersect(const StringRect &other) const {
133 Point2D ttl, ttr, tbr, tbl;
134 calcCorners(ttl, ttr, tbr, tbl, 0.0);
135 // is +ve y up or down?
136 if (ttl.y < tbl.y) {
137 std::swap(ttl, tbl);
138 std::swap(ttr, tbr);
139 }
140 Point2D otl, otr, obr, obl;
141 other.calcCorners(otl, otr, obr, obl, 0.0);
142 if (otl.y < obl.y) {
143 std::swap(otl, obl);
144 std::swap(otr, obr);
145 }
146 if ((otl.x >= ttl.x && otl.x <= ttr.x && otl.y >= tbl.y &&
147 otl.y <= ttl.y) ||
148 (otr.x >= ttl.x && otr.x <= ttr.x && otr.y >= tbl.y &&
149 otr.y <= ttl.y) ||
150 (obr.x >= ttl.x && obr.x <= ttr.x && obr.y >= tbl.y &&
151 obr.y <= ttl.y) ||
152 (obl.x >= ttl.x && obl.x <= ttr.x && obl.y >= tbl.y &&
153 obl.y <= ttl.y)) {
154 return true;
155 }
156 if ((ttl.x >= otl.x && ttl.x <= otr.x && ttl.y >= obl.y &&
157 ttl.y <= otl.y) ||
158 (ttr.x >= otl.x && ttr.x <= otr.x && ttr.y >= obl.y &&
159 ttr.y <= otl.y) ||
160 (tbr.x >= otl.x && tbr.x <= otr.x && tbr.y >= obl.y &&
161 tbr.y <= otl.y) ||
162 (tbl.x >= otl.x && tbl.x <= otr.x && tbl.y >= obl.y &&
163 tbl.y <= otl.y)) {
164 return true;
165 }
166 return false;
167 }
168 };
169 struct AnnotationType {
170 std::string text_;
171 StringRect rect_;
172 OrientType orient_ = OrientType::C;
173 TextAlignType align_ = TextAlignType::MIDDLE;
174 bool scaleText_ = true;
175 };
176
177 typedef std::map<int, DrawColour> ColourPalette;
178 typedef std::vector<double> DashPattern;
179
assignDefaultPalette(ColourPalette & palette)180 inline void assignDefaultPalette(ColourPalette &palette) {
181 palette.clear();
182 palette[-1] = DrawColour(0, 0, 0);
183 palette[0] = DrawColour(0.1, 0.1, 0.1);
184 palette[1] = palette[6] = DrawColour(0.0, 0.0, 0.0);
185 palette[7] = DrawColour(0.0, 0.0, 1.0);
186 palette[8] = DrawColour(1.0, 0.0, 0.0);
187 palette[9] = DrawColour(0.2, 0.8, 0.8);
188 palette[15] = DrawColour(1.0, 0.5, 0.0);
189 palette[16] = DrawColour(0.8, 0.8, 0.0);
190 palette[17] = DrawColour(0.0, 0.802, 0.0);
191 palette[35] = DrawColour(0.5, 0.3, 0.1);
192 palette[53] = DrawColour(0.63, 0.12, 0.94);
193 };
194
assignBWPalette(ColourPalette & palette)195 inline void assignBWPalette(ColourPalette &palette) {
196 palette.clear();
197 palette[-1] = DrawColour(0, 0, 0);
198 };
199
200 struct RDKIT_MOLDRAW2D_EXPORT MolDrawOptions {
201 bool atomLabelDeuteriumTritium =
202 false; // toggles replacing 2H with D and 3H with T
203 bool dummiesAreAttachments = false; // draws "breaks" at dummy atoms
204 bool circleAtoms = true; // draws circles under highlighted atoms
205 bool splitBonds = false; // split bonds into per atom segments
206 // most useful for dynamic manipulation of drawing
207 // especially for svg
208 DrawColour highlightColour{1, 0.5, 0.5, 1.0}; // default highlight color
209 bool continuousHighlight = true; // highlight by drawing an outline
210 // *underneath* the molecule
211 bool fillHighlights = true; // fill the areas used to highlight atoms and
212 // atom regions
213 double highlightRadius = 0.3; // default if nothing given for a particular
214 // atom. units are "Angstrom"
215 int flagCloseContactsDist = 3; // if positive, this will be used as a cutoff
216 // (in pixels) for highlighting close contacts
217 bool includeAtomTags =
218 false; // toggles inclusion of atom tags in the output. does
219 // not make sense for all renderers.
220 bool clearBackground = true; // toggles clearing the background before
221 // drawing a molecule
222 DrawColour backgroundColour{
223 1, 1, 1, 1}; // color to be used while clearing the background
224 int legendFontSize = 16; // font size (in pixels) to be used for the legend
225 // (if present)
226 int maxFontSize = 40; // maximum size in pixels for font in drawn molecule.
227 // -1 means no max.
228 int minFontSize = 6; // likewise for -1.
229 double annotationFontScale = 0.5; // scales font relative to atom labels for
230 // atom and bond annotation.
231 std::string fontFile = ""; // name of font for freetype rendering. If given,
232 // over-rides default
233 DrawColour legendColour{0, 0,
234 0}; // color to be used for the legend (if present)
235 double multipleBondOffset = 0.15; // offset (in Angstrom) for the extra lines
236 // in a multiple bond
237 double padding =
238 0.05; // fraction of empty space to leave around the molecule
239 double additionalAtomLabelPadding = 0.0; // additional padding to leave
240 // around atom labels. Expressed as
241 // a fraction of the font size.
242 std::map<int, std::string> atomLabels; // replacement labels for atoms
243 bool noAtomLabels =
244 false; // disables inclusion of atom labels in the rendering
245 std::vector<std::vector<int>> atomRegions; // regions
246 DrawColour symbolColour{
247 0, 0, 0, 1}; // color to be used for the symbols and arrows in reactions
248 DrawColour annotationColour{0, 0, 0, 1}; // color to be used for annotations
249 int bondLineWidth = 2; // default line width when drawing bonds
250 bool scaleBondWidth = false; // whether to apply scale() to the bond width
251 bool scaleHighlightBondWidth = true; // likewise with bond highlights.
252 int highlightBondWidthMultiplier = 8; // what to multiply standard bond width
253 // by for highlighting.
254 bool prepareMolsBeforeDrawing = true; // call prepareMolForDrawing() on each
255 // molecule passed to drawMolecules()
256 std::vector<DrawColour> highlightColourPalette; // defining 10 default colors
257 // for highlighting atoms and bonds
258 // or reactants in a reactions
259 ColourPalette atomColourPalette; // the palette used to assign
260 // colors to atoms based on
261 // atomic number.
262 double fixedScale =
263 -1.0; // fixes scale to this fraction of draw window width, so
264 // an average bond is this fraction of the width. If
265 // scale comes out smaller than this, reduces scale, but
266 // won't make it larger. The default of -1.0 means no fix.
267 double fixedBondLength =
268 -1.0; // fixes the bond length (and hence the scale) to
269 // always be this number of pixels. Assuming a bond
270 // length in coordinates is 1, as is normal. If
271 // scale comes out smaller than this, reduces scale,
272 // but won't make it larger. The default -1.0 means no
273 // fix. If both fixedScale and fixedBondLength are >
274 // 0.0, fixedScale wins.
275 double rotate = 0.0; // angle in degrees to rotate coords by about centre
276 // before drawing.
277 bool addAtomIndices = false; // adds atom indices to drawings.
278 bool addBondIndices = false; // adds bond indices to drawings.
279 bool isotopeLabels = true; // adds isotope to non-dummy atoms.
280 bool dummyIsotopeLabels = true; // adds isotope labels to dummy atoms.
281
282 bool addStereoAnnotation = false; // adds E/Z and R/S to drawings.
283 bool atomHighlightsAreCircles = false; // forces atom highlights always to be
284 // circles. Default (false) is to put
285 // ellipses round longer labels.
286 bool centreMoleculesBeforeDrawing = false; // moves the centre of the drawn
287 // molecule to (0,0)
288 bool explicitMethyl = false; // draw terminal methyl and related as CH3
289 bool includeRadicals =
290 true; // include radicals in the drawing (it can be useful to turn this
291 // off for reactions and queries)
292 bool includeMetadata =
293 true; // when possible include metadata about molecules and reactions in
294 // the output to allow them to be reconstructed
295 bool comicMode = false; // simulate hand-drawn lines for bonds. When combined
296 // with a font like Comic-Sans or Comic-Neue, this
297 // gives xkcd-like drawings.
298 int variableBondWidthMultiplier = 16; // what to multiply standard bond width
299 // by for variable attachment points.
300 double variableAtomRadius = 0.4; // radius value to use for atoms involved in
301 // variable attachment points.
302 DrawColour variableAttachmentColour = {
303 0.8, 0.8, 0.8, 1.0}; // colour to use for variable attachment points
304 bool includeChiralFlagLabel =
305 false; // add a molecule annotation with "ABS" if the chiral flag is set
306 bool simplifiedStereoGroupLabel =
307 false; // if all specified stereocenters are in a single StereoGroup,
308 // show a molecule-level annotation instead of the individual
309 // labels
310 bool singleColourWedgeBonds = false; // if true wedged and dashed bonds are drawn
311 // using symbolColour rather than inheriting
312 // their colour from the atoms
313
MolDrawOptionsMolDrawOptions314 MolDrawOptions() {
315 highlightColourPalette.emplace_back(
316 DrawColour(1., 1., .67)); // popcorn yellow
317 highlightColourPalette.emplace_back(DrawColour(1., .8, .6)); // sand
318 highlightColourPalette.emplace_back(
319 DrawColour(1., .71, .76)); // light pink
320 highlightColourPalette.emplace_back(
321 DrawColour(.8, 1., .8)); // offwhitegreen
322 highlightColourPalette.emplace_back(DrawColour(.87, .63, .87)); // plum
323 highlightColourPalette.emplace_back(
324 DrawColour(.76, .94, .96)); // pastel blue
325 highlightColourPalette.emplace_back(
326 DrawColour(.67, .67, 1.)); // periwinkle
327 highlightColourPalette.emplace_back(DrawColour(.64, .76, .34)); // avocado
328 highlightColourPalette.emplace_back(
329 DrawColour(.56, .93, .56)); // light green
330 highlightColourPalette.emplace_back(DrawColour(.20, .63, .79)); // peacock
331 assignDefaultPalette(atomColourPalette);
332 };
333 };
334
335 //! MolDraw2D is the base class for doing 2D renderings of molecules
336 class RDKIT_MOLDRAW2D_EXPORT MolDraw2D {
337 public:
338 //! constructor for a particular size
339 /*!
340 \param width : width (in pixels) of the rendering
341 \param height : height (in pixels) of the rendering
342 \param panelWidth : (optional) width (in pixels) of a single panel
343 \param panelHeight : (optional) height (in pixels) of a single panel
344
345 The \c panelWidth and \c panelHeight arguments are used to provide the
346 sizes of the panels individual molecules are drawn in when
347 \c drawMolecules() is called.
348 */
349 MolDraw2D(int width, int height, int panelWidth, int panelHeight);
350 virtual ~MolDraw2D();
351
352 //! \name Methods that must be provided by child classes
353 //@{
354 private:
355 virtual void initDrawing() = 0;
356 virtual void initTextDrawer(bool noFreetype) = 0;
357
358 public:
359 //! clears the contents of the drawing
360 virtual void clearDrawing() = 0;
361 //! draws a line from \c cds1 to \c cds2 using the current drawing style
362 // in atom coords.
363 virtual void drawLine(const Point2D &cds1, const Point2D &cds2) = 0;
364 //! draw a polygon. Note that if fillPolys() returns false, it
365 //! doesn't close the path. If you want it to in that case, you
366 //! do it explicitly yourself.
367 virtual void drawPolygon(const std::vector<Point2D> &cds) = 0;
368 //@}
369
370 //! draw a single molecule
371 /*!
372 \param mol : the molecule to draw
373 \param legend : the legend (to be drawn under the molecule)
374 \param highlight_atoms : (optional) vector of atom ids to highlight
375 \param highlight_atoms : (optional) vector of bond ids to highlight
376 \param highlight_atom_map : (optional) map from atomId -> DrawColour
377 providing the highlight colors. If not provided the default highlight colour
378 from \c drawOptions() will be used.
379 \param highlight_bond_map : (optional) map from bondId -> DrawColour
380 providing the highlight colors. If not provided the default highlight colour
381 from \c drawOptions() will be used.
382 \param highlight_radii : (optional) map from atomId -> radius (in molecule
383 coordinates) for the radii of atomic highlights. If not provided the default
384 value from \c drawOptions() will be used.
385 \param confId : (optional) conformer ID to be used for atomic
386 coordinates
387
388 */
389 virtual void drawMolecule(
390 const ROMol &mol, const std::string &legend,
391 const std::vector<int> *highlight_atoms,
392 const std::vector<int> *highlight_bonds,
393 const std::map<int, DrawColour> *highlight_atom_map = nullptr,
394 const std::map<int, DrawColour> *highlight_bond_map = nullptr,
395 const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
396
397 //! \overload
398 virtual void drawMolecule(
399 const ROMol &mol, const std::vector<int> *highlight_atoms = nullptr,
400 const std::map<int, DrawColour> *highlight_map = nullptr,
401 const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
402
403 //! \overload
404 virtual void drawMolecule(
405 const ROMol &mol, const std::string &legend,
406 const std::vector<int> *highlight_atoms = nullptr,
407 const std::map<int, DrawColour> *highlight_map = nullptr,
408 const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
409
410 //! \overload
411 virtual void drawMolecule(
412 const ROMol &mol, const std::vector<int> *highlight_atoms,
413 const std::vector<int> *highlight_bonds,
414 const std::map<int, DrawColour> *highlight_atom_map = nullptr,
415 const std::map<int, DrawColour> *highlight_bond_map = nullptr,
416 const std::map<int, double> *highlight_radii = nullptr, int confId = -1);
417
418 //! draw molecule with multiple colours allowed per atom.
419 /*!
420 \param mol : the molecule to draw
421 \param legend : the legend (to be drawn under the molecule)
422 \param highlight_atom_map : map from atomId -> DrawColours
423 providing the highlight colours.
424 \param highlight_bond_map : map from bondId -> DrawColours
425 providing the highlight colours.
426 \param highlight_radii : map from atomId -> radius (in molecule
427 coordinates) for the radii of atomic highlights. If not provided for an
428 index, the default value from \c drawOptions() will be used.
429 \param confId : (optional) conformer ID to be used for atomic
430 coordinates
431 */
432 virtual void drawMoleculeWithHighlights(
433 const ROMol &mol, const std::string &legend,
434 const std::map<int, std::vector<DrawColour>> &highlight_atom_map,
435 const std::map<int, std::vector<DrawColour>> &highlight_bond_map,
436 const std::map<int, double> &highlight_radii,
437 const std::map<int, int> &highlight_linewidth_multipliers,
438 int confId = -1);
439
440 //! draw multiple molecules in a grid
441 /*!
442 \param mols : the molecules to draw
443 \param legends : (optional) the legends (to be drawn under the
444 molecules)
445 \param highlight_atoms : (optional) vectors of atom ids to highlight
446 \param highlight_atoms : (optional) vectors of bond ids to highlight
447 \param highlight_atom_map : (optional) maps from atomId -> DrawColour
448 providing the highlight colors. If not provided the default highlight colour
449 from \c drawOptions() will be used.
450 \param highlight_bond_map : (optional) maps from bondId -> DrawColour
451 providing the highlight colors. If not provided the default highlight colour
452 from \c drawOptions() will be used.
453 \param highlight_radii : (optional) maps from atomId -> radius (in molecule
454 coordinates) for the radii of atomic highlights. If not provided the default
455 value from \c drawOptions() will be used.
456 \param confId : (optional) conformer IDs to be used for atomic
457 coordinates
458
459 The \c panelWidth and \c panelHeight values will be used to determine the
460 number of rows and columns to be drawn. Theres not a lot of error checking
461 here, so if you provide too many molecules for the number of panes things
462 are likely to get screwed up.
463 If the number of rows or columns ends up being <= 1, molecules will be
464 being drawn in a single row/column.
465 */
466 virtual void drawMolecules(
467 const std::vector<ROMol *> &mols,
468 const std::vector<std::string> *legends = nullptr,
469 const std::vector<std::vector<int>> *highlight_atoms = nullptr,
470 const std::vector<std::vector<int>> *highlight_bonds = nullptr,
471 const std::vector<std::map<int, DrawColour>> *highlight_atom_maps =
472 nullptr,
473 const std::vector<std::map<int, DrawColour>> *highlight_bond_maps =
474 nullptr,
475 const std::vector<std::map<int, double>> *highlight_radii = nullptr,
476 const std::vector<int> *confIds = nullptr);
477
478 //! draw a ChemicalReaction
479 /*!
480 \param rxn : the reaction to draw
481 \param highlightByReactant : (optional) if this is set, atoms and bonds will
482 be highlighted based on which reactant they come from. Atom map numbers
483 will not be shown.
484 \param highlightColorsReactants : (optional) provide a vector of colors for
485 the
486 reactant highlighting.
487 \param confIds : (optional) vector of confIds to use for rendering. These
488 are numbered by reactants, then agents, then products.
489 */
490 virtual void drawReaction(
491 const ChemicalReaction &rxn, bool highlightByReactant = false,
492 const std::vector<DrawColour> *highlightColorsReactants = nullptr,
493 const std::vector<int> *confIds = nullptr);
494
495 //! \name Transformations
496 //@{
497 // transform a set of coords in the molecule's coordinate system
498 // to drawing system coordinates and vice versa. Note that the coordinates
499 // have
500 // the origin in the top left corner, which is how Qt and Cairo have it, no
501 // doubt a holdover from X Windows. This means that a higher y value will be
502 // nearer the bottom of the screen. This doesn't really matter except when
503 // doing text superscripts and subscripts.
504
505 //! transform a point from the molecule coordinate system into the drawing
506 //! coordinate system
507 virtual Point2D getDrawCoords(const Point2D &mol_cds) const;
508 //! returns the drawing coordinates of a particular atom
509 virtual Point2D getDrawCoords(int at_num) const;
510 virtual Point2D getAtomCoords(const std::pair<int, int> &screen_cds) const;
511 //! transform a point from drawing coordinates to the molecule coordinate
512 //! system
513 virtual Point2D getAtomCoords(
514 const std::pair<double, double> &screen_cds) const;
515 //! returns the molecular coordinates of a particular atom
516 virtual Point2D getAtomCoords(int at_num) const;
517 //@}
518 //! return the width of the drawing area.
width()519 virtual int width() const { return width_; }
520 //! return the height of the drawing area.
height()521 virtual int height() const { return height_; }
522 //! return the width of the drawing panels.
panelWidth()523 virtual int panelWidth() const { return panel_width_; }
524 //! return the height of the drawing panels.
panelHeight()525 virtual int panelHeight() const { return panel_height_; }
drawHeight()526 virtual int drawHeight() const { return panel_height_ - legend_height_; }
527
528 //! returns the drawing scale (conversion from molecular coords -> drawing
529 // coords)
scale()530 double scale() const { return scale_; }
531 //! calculates the drawing scale (conversion from molecular coords -> drawing
532 // coords)
533 void calculateScale(int width, int height, const ROMol &mol,
534 const std::vector<int> *highlight_atoms = nullptr,
535 const std::map<int, double> *highlight_radii = nullptr,
536 int confId = -1);
537 //! overload
538 // calculate a single scale that will suit all molecules. For use by
539 // drawMolecules primarily.
540 void calculateScale(int width, int height, const std::vector<ROMol *> &mols,
541 const std::vector<std::vector<int>> *highlight_atoms,
542 const std::vector<std::map<int, double>> *highlight_radii,
543 const std::vector<int> *confIds,
544 std::vector<std::unique_ptr<RWMol>> &tmols);
545 // set [xy]_trans_ to the middle of the draw area in molecule coords
546 void centrePicture(int width, int height);
547
548 //! explicitly sets the scaling factors for the drawing
549 void setScale(int width, int height, const Point2D &minv, const Point2D &maxv,
550 const ROMol *mol = nullptr);
551 //! sets the drawing offset (in drawing coords)
setOffset(int x,int y)552 void setOffset(int x, int y) {
553 x_offset_ = x;
554 y_offset_ = y;
555 }
556 //! returns the drawing offset (in drawing coords)
offset()557 Point2D offset() const { return Point2D(x_offset_, y_offset_); }
558
559 //! returns the minimum point of the drawing (in molecular coords)
minPt()560 Point2D minPt() const { return Point2D(x_min_, y_min_); }
561 //! returns the width and height of the grid (in molecular coords)
range()562 Point2D range() const { return Point2D(x_range_, y_range_); }
563
564 //! font size in drawing coordinate units. That's probably pixels.
565 virtual double fontSize() const;
566 virtual void setFontSize(double new_size);
567
568 //! sets the current draw color
setColour(const DrawColour & col)569 virtual void setColour(const DrawColour &col) { curr_colour_ = col; }
570 //! returns the current draw color
colour()571 virtual DrawColour colour() const { return curr_colour_; }
572 //! sets the current dash pattern
setDash(const DashPattern & patt)573 virtual void setDash(const DashPattern &patt) { curr_dash_ = patt; }
574 //! returns the current dash pattern
dash()575 virtual const DashPattern &dash() const { return curr_dash_; }
576
577 //! sets the current line width
setLineWidth(int width)578 virtual void setLineWidth(int width) { drawOptions().bondLineWidth = width; }
579 //! returns the current line width
lineWidth()580 virtual int lineWidth() const { return drawOptions().bondLineWidth; }
581
582 //! using the current scale, work out the size of the label in molecule
583 //! coordinates.
584 /*!
585 Bear in mind when implementing this, that, for example, NH2 will appear as
586 NH<sub>2</sub> to convey that the 2 is a subscript, and this needs to
587 accounted for in the width and height.
588 */
589 virtual void getStringSize(const std::string &label, double &label_width,
590 double &label_height) const;
591 // get the overall size of the label, allowing for it being split
592 // into pieces according to orientation.
593 void getLabelSize(const std::string &label, OrientType orient,
594 double &label_width, double &label_height) const;
595 // return extremes for string in molecule coords.
596 void getStringExtremes(const std::string &label, OrientType orient,
597 const Point2D &cds, double &x_min, double &y_min,
598 double &x_max, double &y_max) const;
599
600 //! drawString centres the string on cds.
601 virtual void drawString(const std::string &str, const Point2D &cds);
602 // unless the specific drawer over-rides this overload, it will just call
603 // the first one. SVG for one needs the alignment flag.
604 virtual void drawString(const std::string &str, const Point2D &cds,
605 TextAlignType align);
606 //! draw a triangle
607 virtual void drawTriangle(const Point2D &cds1, const Point2D &cds2,
608 const Point2D &cds3);
609 //! draw an ellipse
610 virtual void drawEllipse(const Point2D &cds1, const Point2D &cds2);
611 // draw the arc of a circle between ang1 and ang2. Note that 0 is
612 // at 3 o-clock and 90 at 12 o'clock as you'd expect from your maths.
613 // ang2 must be > ang1 - it won't draw backwards. This is not enforced.
614 // Angles in degrees.
615 virtual void drawArc(const Point2D ¢re, double radius, double ang1,
616 double ang2);
617 // and a general ellipse form
618 virtual void drawArc(const Point2D ¢re, double xradius, double yradius,
619 double ang1, double ang2);
620 //! draw a rectangle
621 virtual void drawRect(const Point2D &cds1, const Point2D &cds2);
622 //! draw a line indicating the presence of an attachment point (normally a
623 //! squiggle line perpendicular to a bond)
624 virtual void drawAttachmentLine(const Point2D &cds1, const Point2D &cds2,
625 const DrawColour &col, double len = 1.0,
626 unsigned int nSegments = 16);
627 //! draw a wavy line like that used to indicate unknown stereochemistry
628 virtual void drawWavyLine(const Point2D &cds1, const Point2D &cds2,
629 const DrawColour &col1, const DrawColour &col2,
630 unsigned int nSegments = 16,
631 double vertOffset = 0.05);
632 //! draw a line where the ends are different colours
633 virtual void drawLine(const Point2D &cds1, const Point2D &cds2,
634 const DrawColour &col1, const DrawColour &col2);
635 //! adds additional information about the atoms to the output. Does not make
636 //! sense for all renderers.
tagAtoms(const ROMol & mol)637 virtual void tagAtoms(const ROMol &mol) { RDUNUSED_PARAM(mol); };
638 //! set whether or not polygons are being filled
fillPolys()639 virtual bool fillPolys() const { return fill_polys_; }
640 //! returns either or not polygons should be filled
setFillPolys(bool val)641 virtual void setFillPolys(bool val) { fill_polys_ = val; }
642
643 //! returns our current drawing options
drawOptions()644 MolDrawOptions &drawOptions() { return options_; }
645 //! \overload
drawOptions()646 const MolDrawOptions &drawOptions() const { return options_; }
647
648 //! returns the coordinates of the atoms of the current molecule in molecular
649 //! coordinates
atomCoords()650 const std::vector<Point2D> &atomCoords() const {
651 PRECONDITION(activeMolIdx_ >= 0, "no index");
652 return at_cds_[activeMolIdx_];
653 };
654 //! returns the atomic symbols of the current molecule
atomSyms()655 const std::vector<std::pair<std::string, OrientType>> &atomSyms() const {
656 PRECONDITION(activeMolIdx_ >= 0, "no index");
657 return atom_syms_[activeMolIdx_];
658 };
659 //! Draw an arrow with either lines or a filled head (when asPolygon is true)
660 virtual void drawArrow(const Point2D &cds1, const Point2D &cds2,
661 bool asPolygon = false, double frac = 0.05,
662 double angle = M_PI / 6);
663
664 // reset to default values all the things the c'tor sets
665 void tabulaRasa();
666
supportsAnnotations()667 virtual bool supportsAnnotations() { return true; }
668 virtual void drawAnnotation(const AnnotationType &annotation);
669
hasActiveAtmIdx()670 bool hasActiveAtmIdx() { return activeAtmIdx1_ >= 0; }
getActiveAtmIdx1()671 int getActiveAtmIdx1() { return activeAtmIdx1_; }
getActiveAtmIdx2()672 int getActiveAtmIdx2() { return activeAtmIdx2_; }
673 void setActiveAtmIdx(int at_idx1 = -1, int at_idx2 = -1) {
674 at_idx1 = (at_idx1 < 0 ? -1 : at_idx1);
675 at_idx2 = (at_idx2 < 0 ? -1 : at_idx2);
676 if (at_idx2 >= 0 && at_idx1 < 0) {
677 std::swap(at_idx1, at_idx2);
678 }
679 activeAtmIdx1_ = at_idx1;
680 activeAtmIdx2_ = at_idx2;
681 }
682
683 protected:
684 std::unique_ptr<DrawText> text_drawer_;
685
686 private:
687 bool needs_scale_;
688 int width_, height_, panel_width_, panel_height_, legend_height_;
689 double scale_;
690 double x_min_, y_min_, x_range_, y_range_;
691 double x_trans_, y_trans_;
692 int x_offset_, y_offset_; // translation in screen coordinates
693 bool fill_polys_;
694 int activeMolIdx_;
695 int activeAtmIdx1_;
696 int activeAtmIdx2_;
697
698 DrawColour curr_colour_;
699 DashPattern curr_dash_;
700 MolDrawOptions options_;
701
702 std::vector<std::vector<Point2D>> at_cds_; // from mol
703 std::vector<std::vector<int>> atomic_nums_;
704 std::vector<std::vector<std::pair<std::string, OrientType>>> atom_syms_;
705 // by the time annotations_ are drawn, we're only ever using the trans_ member
706 // of the StringRect, but it is convenient to keep the whole thing rather than
707 // just a StringPos for the position for calculating the scale of the drawing.
708 // Went a long way down the rabbit hole before realising this, hence this
709 // note.
710 std::vector<std::vector<AnnotationType>> annotations_;
711 std::vector<std::vector<std::pair<std::shared_ptr<StringRect>, OrientType>>>
712 radicals_;
713 Point2D bbox_[2];
714 std::vector<std::vector<MolDrawShape>> pre_shapes_;
715 std::vector<std::vector<MolDrawShape>> post_shapes_;
716
717 // return a DrawColour based on the contents of highlight_atoms or
718 // highlight_map, falling back to atomic number by default
719 DrawColour getColour(
720 int atom_idx, const std::vector<int> *highlight_atoms = nullptr,
721 const std::map<int, DrawColour> *highlight_map = nullptr);
722 DrawColour getColourByAtomicNum(int atomic_num);
723
724 // set the system up to draw the molecule including calculating the scale.
725 std::unique_ptr<RWMol> setupDrawMolecule(
726 const ROMol &mol, const std::vector<int> *highlight_atoms,
727 const std::map<int, double> *highlight_radii, int confId, int width,
728 int height);
729 // copies of atom coords, atomic symbols etc. are stashed for convenience.
730 // these put empty collections onto the stack and pop the off when done.
731 void pushDrawDetails();
732 void popDrawDetails();
733
734 // do the initial setup bits for drawing a molecule.
735 std::unique_ptr<RWMol> setupMoleculeDraw(
736 const ROMol &mol, const std::vector<int> *highlight_atoms,
737 const std::map<int, double> *highlight_radii, int confId = -1);
738 void setupTextDrawer();
739
740 // if bond_colours is given, it must have an entry for every bond, and it
741 // trumps everything else. First in pair is bonds begin atom, second is
742 // end atom.
743 void drawBonds(const ROMol &draw_mol,
744 const std::vector<int> *highlight_atoms = nullptr,
745 const std::map<int, DrawColour> *highlight_atom_map = nullptr,
746 const std::vector<int> *highlight_bonds = nullptr,
747 const std::map<int, DrawColour> *highlight_bond_map = nullptr,
748 const std::vector<std::pair<DrawColour, DrawColour>>
749 *bond_colours = nullptr);
750 // do the finishing touches to the drawing
751 void finishMoleculeDraw(const ROMol &draw_mol,
752 const std::vector<DrawColour> &atom_colours);
753 void drawLegend(const std::string &legend);
754 // draw a circle in the requested colour(s) around the atom.
755 void drawHighlightedAtom(int atom_idx, const std::vector<DrawColour> &colours,
756 const std::map<int, double> *highlight_radii);
757 // calculate the rectangle that goes round the string, taking its
758 // orientation into account. Centre of StringRect
759 // won't be the same as label_coords, necessarily, as the string might
760 // be offset according to orient.
761 StringRect calcLabelRect(const std::string &label, OrientType orient,
762 const Point2D &label_coords) const;
763 // calculate parameters for an ellipse that roughly goes round the label
764 // of the given atom.
765 void calcLabelEllipse(int atom_idx,
766 const std::map<int, double> *highlight_radii,
767 Point2D ¢re, double &xradius,
768 double &yradius) const;
769 // StringRect will have a width of -1.0 if there's a problem.
770 StringRect calcAnnotationPosition(const ROMol &mol, const Atom *atom,
771 const std::string ¬e);
772 StringRect calcAnnotationPosition(const ROMol &mol, const Bond *bond,
773 const std::string ¬e);
774 StringRect calcAnnotationPosition(const ROMol &mol, const std::string ¬e);
775 // find where to put the given annotation around an atom. Starting
776 // search at angle start_ang, in degrees.
777 void calcAtomAnnotationPosition(const ROMol &mol, const Atom *atom,
778 double start_ang, StringRect &rect,
779 const std::string ¬e);
780
781 // draw 1 or more coloured line along bonds
782 void drawHighlightedBonds(
783 const ROMol &mol,
784 const std::map<int, std::vector<DrawColour>> &highlight_bond_map,
785 const std::map<int, int> &highlight_linewidth_multipliers,
786 const std::map<int, double> *highlight_radii);
787 int getHighlightBondWidth(
788 int bond_idx,
789 const std::map<int, int> *highlight_linewidth_multipliers) const;
790 // move p2 so that the line defined by p1 to p2 touches the ellipse for the
791 // atom highlighted.
792 void adjustLineEndForHighlight(int at_idx,
793 const std::map<int, double> *highlight_radii,
794 Point2D p1, Point2D &p2) const;
795
796 void extractAtomCoords(const ROMol &mol, int confId, bool updateBBox);
797 void extractAtomSymbols(const ROMol &mol);
798 void extractMolNotes(const ROMol &mol);
799 void extractAtomNotes(const ROMol &mol);
800 void extractBondNotes(const ROMol &mol);
801 void extractRadicals(const ROMol &mol);
802 void extractSGroupData(const ROMol &mol);
803 void extractVariableBonds(const ROMol &mol);
804 void extractBrackets(const ROMol &mol);
805 void extractLinkNodes(const ROMol &mol);
806
807 void drawAtomLabel(int atom_num,
808 const std::vector<int> *highlight_atoms = nullptr,
809 const std::map<int, DrawColour> *highlight_map = nullptr);
810 OrientType calcRadicalRect(const ROMol &mol, const Atom *atom,
811 StringRect &rad_rect);
812 void drawRadicals(const ROMol &mol);
813 // find a good starting point for scanning round the annotation
814 // atom. If we choose well, the first angle should be the one.
815 // Returns angle in radians.
816 double getNoteStartAngle(const ROMol &mol, const Atom *atom) const;
817 // see if the note will clash with anything else drawn on the molecule.
818 // note_vec should have unit length. note_rad is the radius along
819 // note_vec that the note will be drawn.
820 bool doesAtomNoteClash(StringRect ¬e_rect,
821 const std::vector<std::shared_ptr<StringRect>> &rects,
822 const ROMol &mol, unsigned int atom_idx);
823 bool doesBondNoteClash(StringRect ¬e_rect,
824 const std::vector<std::shared_ptr<StringRect>> &rects,
825 const ROMol &mol, const Bond *bond);
826 // does the note_vec form an unacceptably acute angle with one of the
827 // bonds from atom to its neighbours.
828 bool doesNoteClashNbourBonds(
829 const StringRect ¬e_rect,
830 const std::vector<std::shared_ptr<StringRect>> &rects, const ROMol &mol,
831 const Atom *atom) const;
832 // does the note intersect with atsym, and if not, any other atom symbol.
833 bool doesNoteClashAtomLabels(
834 const StringRect ¬e_rect,
835 const std::vector<std::shared_ptr<StringRect>> &rects, const ROMol &mol,
836 unsigned int atom_idx) const;
837 bool doesNoteClashOtherNotes(
838 const StringRect ¬e_rect,
839 const std::vector<std::shared_ptr<StringRect>> &rects) const;
840
841 // take the coords for atnum, with neighbour nbr_cds, and move cds out to
842 // accommodate
843 // the label associated with it.
844 void adjustBondEndForLabel(const std::pair<std::string, OrientType> &lbl,
845 const Point2D &nbr_cds, Point2D &cds) const;
846
847 // adds LaTeX-like annotation for super- and sub-script.
848 std::pair<std::string, OrientType> getAtomSymbolAndOrientation(
849 const Atom &atom) const;
850 std::string getAtomSymbol(const Atom &atom, OrientType orientation) const;
851 OrientType getAtomOrientation(const Atom &atom) const;
852
853 // things used by calculateScale.
854 void adjustScaleForAtomLabels(const std::vector<int> *highlight_atoms,
855 const std::map<int, double> *highlight_radii);
856 void adjustScaleForRadicals(const ROMol &mol);
857 void adjustScaleForAnnotation(const std::vector<AnnotationType> ¬es);
858
859 private:
updateMetadata(const ROMol & mol,int confId)860 virtual void updateMetadata(const ROMol &mol, int confId) {
861 RDUNUSED_PARAM(mol);
862 RDUNUSED_PARAM(confId);
863 };
updateMetadata(const ChemicalReaction & rxn)864 virtual void updateMetadata(const ChemicalReaction &rxn) {
865 RDUNUSED_PARAM(rxn);
866 };
867
868 protected:
869 std::vector<std::pair<std::string, std::string>> d_metadata;
870 unsigned int d_numMetadataEntries = 0;
871
872 virtual void doContinuousHighlighting(
873 const ROMol &mol, const std::vector<int> *highlight_atoms,
874 const std::vector<int> *highlight_bonds,
875 const std::map<int, DrawColour> *highlight_atom_map,
876 const std::map<int, DrawColour> *highlight_bond_map,
877 const std::map<int, double> *highlight_radii);
878
879 virtual void highlightCloseContacts();
880 // if bond_colours is given, it must have an entry for every bond, and it
881 // trumps everything else. First in pair is bonds begin atom, second is
882 // end atom.
883 virtual void drawBond(
884 const ROMol &mol, const Bond *bond, int at1_idx, int at2_idx,
885 const std::vector<int> *highlight_atoms = nullptr,
886 const std::map<int, DrawColour> *highlight_atom_map = nullptr,
887 const std::vector<int> *highlight_bonds = nullptr,
888 const std::map<int, DrawColour> *highlight_bond_map = nullptr,
889 const std::vector<std::pair<DrawColour, DrawColour>> *bond_colours =
890 nullptr);
891 virtual void drawAtomLabel(int atom_num, const DrawColour &draw_colour);
892 //! DEPRECATED
drawAnnotation(const std::string & note,const StringRect & note_rect)893 virtual void drawAnnotation(const std::string ¬e,
894 const StringRect ¬e_rect) {
895 AnnotationType annot;
896 annot.text_ = note;
897 annot.rect_ = note_rect;
898 drawAnnotation(annot);
899 }
900
901 // calculate the width to draw a line in draw coords.
902 virtual double getDrawLineWidth() const;
903
904 // sort out coords and scale for drawing reactions.
905 void get2DCoordsForReaction(ChemicalReaction &rxn, Point2D &arrowBegin,
906 Point2D &arrowEnd, std::vector<double> &plusLocs,
907 double spacing, const std::vector<int> *confIds);
908 // despite the name, this is only ever used for molecules in a reaction.
909 void get2DCoordsMol(RWMol &mol, double &offset, double spacing, double &maxY,
910 double &minY, int confId, bool shiftAgents,
911 double coordScale);
912 };
913
914 // return true if the line l1s->l1f intersects line l2s->l2f. If ip is not
915 // nullptr, the intersection point is stored in it.
916 RDKIT_MOLDRAW2D_EXPORT bool doLinesIntersect(const Point2D &l1s,
917 const Point2D &l1f,
918 const Point2D &l2s,
919 const Point2D &l2f,
920 Point2D *ip = nullptr);
921 // return true if line ls->lf intersects (or is fully inside) the
922 // rectangle of the string.
923 RDKIT_MOLDRAW2D_EXPORT bool doesLineIntersectLabel(const Point2D &ls,
924 const Point2D &lf,
925 const StringRect &lab_rect,
926 double padding = 0.0);
927
928 } // namespace RDKit
929
930 #endif // RDKITMOLDRAW2D_H
931