mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-03 21:44:30 +08:00
* Configurable legend position (Top/Left/Right/Bottom) and vertical text (GitHub #9023) - Add LegendPosition enum and legendPosition, legendVerticalText to MolDrawOptions - Support legend at Top, Left, Right, Bottom; vertical text for Left/Right - Python: MolDrawOptions.legendPosition, .legendVerticalText; LegendPosition enum - Python: MolToSVG() wrapper with legend/drawOptions; doc updates for MolToImage - JSON: legendPosition (string), legendVerticalText (bool) in draw options - C++ and Python tests; release note and Cartridge.md docs * MolDraw2D: legend gutter for horizontal side legends; vertical side height fit - Reserve horizontal gap between molecule and left/right horizontal legends (scale mol to molWidth-gutter, align toward legend strip). - Position horizontal side legend by measured text width from partition edge. - Vertical side legends: iterative scale so n*max_h+(n-1)*gap fits panel. - Catch: long vertical side legend section. * Update legend-position tests and review-driven cleanup Use enum/default wording for legendPosition docs, move the lightweight Python test to Wrap, add regex-based placement checks (including horizontal side and vertical stacking), and refactor extractLegend helpers per style guidance. * Fix MolDraw2D legend edge cases * MolDraw2D: review follow-up (legend tests, bounds, DRY Top/Bottom) * Update no-FT legend test coords * Address PR review: document constants, remove release-note text, and simplify extra-padding logic
372 lines
18 KiB
C++
Executable File
372 lines
18 KiB
C++
Executable File
//
|
|
// Copyright (C) 2021-2022 David Cosgrove and other RDKit contributors
|
|
//
|
|
// @@ All Rights Reserved @@
|
|
// This file is part of the RDKit.
|
|
// The contents are covered by the terms of the BSD license
|
|
// which is included in the file license.txt, found at the root
|
|
// of the RDKit source tree.
|
|
//
|
|
// Original author: David Cosgrove (CozChemIx Limited)
|
|
//
|
|
// This class is a helper class used by MolDraw2D to draw an ROMol.
|
|
// It is not part of the public API and is not intended to be used except
|
|
// by MolDraw2D.
|
|
|
|
#ifndef RDKIT_DRAWMOL_H
|
|
#define RDKIT_DRAWMOL_H
|
|
|
|
#include <map>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <Geometry/point.h>
|
|
#include <GraphMol/MolDraw2D/AtomSymbol.h>
|
|
#include <GraphMol/MolDraw2D/DrawAnnotation.h>
|
|
#include <GraphMol/MolDraw2D/DrawShape.h>
|
|
#include <GraphMol/MolDraw2D/MolDraw2DHelpers.h>
|
|
|
|
namespace RDKit {
|
|
|
|
class Atom;
|
|
class Bond;
|
|
class ROMol;
|
|
class RWMol;
|
|
class DrawText;
|
|
|
|
namespace MolDraw2D_detail {
|
|
|
|
class DrawMol {
|
|
public:
|
|
virtual ~DrawMol() = default;
|
|
|
|
// Make the object, scaled to a given pixel size.
|
|
/*!
|
|
\param mol : the molecule to draw
|
|
\param legend : the legend (to be drawn under the molecule)
|
|
\param width : width (in pixels) of the rendering
|
|
set this to -1 to have the canvas size set automatically
|
|
\param height : height (in pixels) of the rendering
|
|
set this to -1 to have the canvas size set automatically
|
|
\param drawOptions : a MolDrawOptions object from the owning MolDraw2D
|
|
\param textDrawer : a DrawText object from the owning MolDraw2D
|
|
\param highlightAtoms : (optional) vector of atom ids to highlight
|
|
\param highlightBonds : (optional) vector of bond ids to highlight
|
|
\param highlightAtomMap : (optional) map from atomId -> DrawColour
|
|
providing the highlight colors. If not provided the default highlight colour
|
|
from \c drawOptions() will be used.
|
|
\param highlightBondMap : (optional) map from bondId -> DrawColour
|
|
providing the highlight colors. If not provided the default highlight colour
|
|
from \c drawOptions() will be used.
|
|
\param highlightRadii : (optional) map from atomId -> radius (in molecule
|
|
coordinates) for the radii of atomic highlights. If not provided the default
|
|
value from \c drawOptions() will be used.
|
|
\param confId : (optional) conformer ID to be used for atomic
|
|
coordinates
|
|
*/
|
|
DrawMol(const ROMol &mol, const std::string &legend, int width, int height,
|
|
const MolDrawOptions &drawOptions, DrawText &textDrawer,
|
|
const std::vector<int> *highlightAtoms = nullptr,
|
|
const std::vector<int> *highlightBonds = nullptr,
|
|
const std::map<int, DrawColour> *highlightAtomMap = nullptr,
|
|
const std::map<int, DrawColour> *highlightBondMap = nullptr,
|
|
const std::vector<std::pair<DrawColour, DrawColour>> *bondColours =
|
|
nullptr,
|
|
const std::map<int, double> *highlight_radii = nullptr,
|
|
bool includeAnnotations = true, int confId = -1,
|
|
bool isReactionMol = false);
|
|
/*!
|
|
Make a DrawMol when there's no molecule to draw, but we still want
|
|
a DrawMol in the MolDraw2D for scale, conversion of atom coords to
|
|
draw coords etc. And so DrawMol starts sounding like a poor name for
|
|
the class.
|
|
\param width : width (in pixels) of the rendering
|
|
\param height : height (in pixels) of the rendering
|
|
\param drawOptions : a MolDrawOptions object from the owning MolDraw2D
|
|
\param textDrawer : a DrawText object from the owning MolDraw2D
|
|
\param xmin : minimum value expected in X
|
|
\param xmax : miaximum value expected in X
|
|
\param ymin : minimum value expected in Y
|
|
\param ymax : maximum value expected in Y
|
|
\param scale : scale to use
|
|
\param fontscale : font scale to use
|
|
*/
|
|
DrawMol(int width, int height, const MolDrawOptions &drawOptions,
|
|
DrawText &textDrawer, double xmin, double xmax, double ymin,
|
|
double ymax, double scale, double fontscale);
|
|
DrawMol(const DrawMol &) = delete;
|
|
DrawMol(DrawMol &&) = delete;
|
|
DrawMol &operator=(const DrawMol &) = delete;
|
|
DrawMol &operator=(DrawMol &&) = delete;
|
|
|
|
// this must be called before a drawing can be done.
|
|
void createDrawObjects();
|
|
// common bits used by createDrawObjects and setScale.
|
|
void finishCreateDrawObjects();
|
|
void initDrawMolecule(const ROMol &mol);
|
|
void extractAll(double scale);
|
|
void extractAtomCoords();
|
|
void extractAtomSymbols();
|
|
void extractBonds();
|
|
virtual void extractHighlights(double scale);
|
|
void extractRegions();
|
|
void extractAttachments();
|
|
void extractMolNotes();
|
|
void extractCIPCodes(bool showAllCIPCodes);
|
|
void extractStereoGroups();
|
|
void extractAtomNotes();
|
|
void extractBondNotes();
|
|
void extractRadicals();
|
|
void extractSGroupData();
|
|
void extractVariableBonds();
|
|
void extractBrackets();
|
|
void extractLinkNodes();
|
|
// extractCloseContacts is to show where 2 atoms are drawn too close
|
|
// together and so needs the final drawing coords. It is therefore called
|
|
// at the end of changeToDrawCoords() and any necessary DrawShapePolyLines
|
|
// added to postShapes_ in drawCoords.
|
|
void extractCloseContacts();
|
|
void resolveAtomSymbolClashes();
|
|
void calculateScale();
|
|
void findExtremes();
|
|
void changeToDrawCoords();
|
|
void draw(MolDraw2D &drawer) const;
|
|
void drawRadicals(MolDraw2D &drawer) const;
|
|
void resetEverything();
|
|
// reduce width_ and height_ to just accomodate the Xrange_ and YRange_
|
|
// at the current scale. Recentres everything. So the DrawMol takes up
|
|
// no more screen real estate than it needs.
|
|
void shrinkToFit(bool withPadding = true);
|
|
|
|
// adds LaTeX-like annotation for super- and sub-script.
|
|
std::pair<std::string, OrientType> getAtomSymbolAndOrientation(
|
|
const Atom &atom) const;
|
|
std::string getAtomSymbol(const Atom &atom, OrientType orientation) const;
|
|
OrientType getAtomOrientation(const Atom &atom) const;
|
|
|
|
// if there's a legend, partition the height_ to accommodate it
|
|
void partitionForLegend();
|
|
// extractLegend is different from all the other extract... functions
|
|
// in that it needs to be called once the final scale has been found
|
|
// by calculateScale.
|
|
void extractLegend();
|
|
|
|
void calcMeanBondLength();
|
|
void makeStandardBond(Bond *bond, double doubleBondOffset);
|
|
void makeQueryBond(Bond *bond, double doubleBondOffset);
|
|
void makeDoubleBondLines(Bond *bond, double doubleBondOffset,
|
|
const std::pair<DrawColour, DrawColour> &cols);
|
|
void makeTripleBondLines(Bond *bond, double doubleBondOffset,
|
|
const std::pair<DrawColour, DrawColour> &cols);
|
|
void makeWedgedBond(Bond *bond,
|
|
const std::pair<DrawColour, DrawColour> &cols);
|
|
void makeWavyBond(Bond *bond, double offset,
|
|
const std::pair<DrawColour, DrawColour> &cols);
|
|
void makeDativeBond(Bond *bond, double offset,
|
|
const std::pair<DrawColour, DrawColour> &cols);
|
|
void makeZeroBond(Bond *bond, const std::pair<DrawColour, DrawColour> &cols,
|
|
const DashPattern &dashPattern);
|
|
void adjustBondEndsForLabels(int begAtIdx, int endAtIdx, Point2D &begCds,
|
|
Point2D &endCds) const;
|
|
void newBondLine(const Point2D &pt1, const Point2D &pt2,
|
|
const DrawColour &col1, const DrawColour &col2, int atom1Idx,
|
|
int atom2Idx, int bondIdx, const DashPattern &dashPattern);
|
|
std::pair<DrawColour, DrawColour> getBondColours(Bond *bond);
|
|
void makeContinuousHighlights(double scale);
|
|
void makeAtomCircleHighlights();
|
|
void makeAtomEllipseHighlights(double lineWidth);
|
|
// These are the lines for continuous highlights, that are
|
|
// now filled trapezoids rather than fat simple lines.
|
|
void makeBondHighlightLines(double lineWidth, double scale);
|
|
void calcAnnotationPosition(const Atom *atom, DrawAnnotation &annot) const;
|
|
void calcAnnotationPosition(const Bond *bond, DrawAnnotation &annot) const;
|
|
double getNoteStartAngle(const Atom *atom) const;
|
|
// see if the note will clash with anything else drawn on the molecule.
|
|
// Returns 0 if no clash, 1-4 if there is a clash, denoting what clashed.
|
|
int doesNoteClash(const DrawAnnotation &annot) const;
|
|
int doesRectClash(const StringRect &rect, double padding) const;
|
|
OrientType calcRadicalRect(const Atom *atom, StringRect &rad_rect) const;
|
|
void getDrawTransformers(Point2D &trans, Point2D &scale,
|
|
Point2D &toCentre) const;
|
|
// Given some coords in molecule space (angstrom, probably) return the
|
|
// screen coords.
|
|
Point2D getDrawCoords(const Point2D &atCds, const Point2D &trans,
|
|
const Point2D &scaleFactor,
|
|
const Point2D &toCentre) const;
|
|
Point2D getDrawCoords(const Point2D &atCds) const;
|
|
Point2D getDrawCoords(int atnum) const;
|
|
// and the other way.
|
|
Point2D getAtomCoords(const Point2D &screenCds) const;
|
|
Point2D getAtomCoords(int atnum) const;
|
|
double getScale() const { return scale_; }
|
|
double getFontScale() const { return fontScale_; }
|
|
// More often than not, newScale and newFontScale will be the same,
|
|
// but not if minFontScale or maxFontScale have become involved.
|
|
// The newFontScale will be used without checking the min and max.
|
|
void setScale(double newScale, double newFontScale,
|
|
bool ignoreFontLimits = true);
|
|
// Set all the transformation details from the incoming DrawMol to this
|
|
// one, so they can be overlaid properly. Doesn't change the offsets.
|
|
void setTransformation(const DrawMol &sourceMol);
|
|
|
|
// For drawing into a grid, for example. Must be set before
|
|
// changeToDrawCoords is called for it to have effect.
|
|
void setOffsets(double xOffset, double yOffset);
|
|
// So we can add metadata later. Most likely used after changeToDrawCoords
|
|
// has been called.
|
|
void tagAtomsWithCoords();
|
|
// Apply the transformations to everything. trans and toCentre are added,
|
|
// scale is multiplied.
|
|
void transformAll(const Point2D *trans = nullptr, Point2D *scale = nullptr,
|
|
const Point2D *toCentre = nullptr);
|
|
// Apply the transformations to the given point and return a new one.
|
|
Point2D transformPoint(const Point2D &pt, const Point2D *trans = nullptr,
|
|
Point2D *scale = nullptr,
|
|
const Point2D *toCentre = nullptr) const;
|
|
void calcDoubleBondLines(double offset, const Bond &bond, Point2D &l1s,
|
|
Point2D &l1f, Point2D &l2s, Point2D &l2f) const;
|
|
void bondInsideRing(const Bond &bond, double offset, Point2D &l2s,
|
|
Point2D &l2f) const;
|
|
void bondNonRing(const Bond &bond, double offset, Point2D &l2s,
|
|
Point2D &l2f) const;
|
|
// deal with terminal double bond between at1 and at2, either to atoms of
|
|
// degree 2 or 3.
|
|
void doubleBondTerminal(Atom *at1, Atom *at2, double offset, Point2D &l1s,
|
|
Point2D &l1f, Point2D &l2s, Point2D &l2f) const;
|
|
// assuming at[1-3] are atoms where at1 is bonded to at2 and at2 is bonded
|
|
// to at3, find the position of the at2 end of a double bond between at2
|
|
// and at3. If trunc, it'll be along the vector that bisects the two bonds
|
|
// on the inside, otherwise it's perpendicular to the bond from at1 to at2.
|
|
Point2D doubleBondEnd(unsigned int at1, unsigned int at2, unsigned int at3,
|
|
double offset, bool trunc) const;
|
|
void calcTripleBondLines(double offset, const Bond &bond, Point2D &l1s,
|
|
Point2D &l1f, Point2D &l2s, Point2D &l2f);
|
|
// find the vectors of any atoms singly bonded to atom that aren't
|
|
// otherAtom.
|
|
void findOtherBondVecs(const Atom *atom, const Atom *otherAtom,
|
|
std::vector<Point2D> &otherBondVecs) const;
|
|
void adjustBondsOnSolidWedgeEnds();
|
|
void smoothBondJoins();
|
|
// If doing a continuous highlight, add to points the 2 or 3 points that
|
|
// will be for the end1 end of the highlight. The final highlight will
|
|
// be a 4-6 sided polygon formed by calling this twice, with the ends in
|
|
// opposite order the second time.
|
|
void makeHighlightEnd(const Atom *end1, const Atom *end2, double lineWidth,
|
|
const std::vector<Atom *> &end1HighNbrs,
|
|
std::vector<Point2D> &points);
|
|
DrawColour getColour(int atom_idx) const;
|
|
|
|
const MolDrawOptions &drawOptions_;
|
|
DrawText &textDrawer_;
|
|
// For drawing reactions, padding needs to be 0 irrespective
|
|
// of what drawOptions_ does elsewhere, so it is copied from drawOptions_
|
|
// on construction, and is then immune to changes in the outer program.
|
|
double marginPadding_;
|
|
std::vector<int> highlightAtoms_;
|
|
std::vector<int> highlightBonds_;
|
|
std::map<int, DrawColour> highlightAtomMap_;
|
|
std::map<int, DrawColour> highlightBondMap_;
|
|
std::vector<std::pair<DrawColour, DrawColour>> bondColours_;
|
|
std::map<int, double> highlightRadii_;
|
|
bool includeAnnotations_;
|
|
bool isReactionMol_;
|
|
std::string legend_;
|
|
|
|
std::unique_ptr<RWMol> drawMol_;
|
|
int confId_;
|
|
// atCds_ are as extracted from the molecule, except that the y is
|
|
// inverted and drawOptions_.rotate is applied.
|
|
std::vector<Point2D> atCds_;
|
|
std::vector<std::unique_ptr<DrawShape>> bonds_;
|
|
std::vector<std::unique_ptr<DrawShape>> preShapes_;
|
|
std::vector<std::unique_ptr<DrawShape>> postShapes_;
|
|
std::vector<int> atomicNums_;
|
|
std::vector<std::pair<std::string, OrientType>> atomSyms_;
|
|
std::vector<std::unique_ptr<AtomSymbol>> atomLabels_;
|
|
std::vector<std::unique_ptr<DrawShape>> highlights_;
|
|
std::vector<std::unique_ptr<DrawAnnotation>> annotations_;
|
|
std::vector<std::unique_ptr<DrawAnnotation>> legends_;
|
|
std::vector<std::tuple<StringRect, OrientType, int>> radicals_;
|
|
std::vector<int> singleBondLines_;
|
|
|
|
// The total width and height of the canvas. Either or both may be
|
|
// -1 initially, in which case they will be calculated so the molecule
|
|
// can be drawn within it at a fixed scale (the so-called flexiCanvas).
|
|
int width_, height_;
|
|
// The width and height of the drawing area within the canvas. This is
|
|
// width_ and height_ less the drawOptions._padding round the outside.
|
|
// Will always be <= width_, height_.
|
|
int drawWidth_, drawHeight_;
|
|
// to allow for min and max font sizes, the font scale needs to be
|
|
// independent of the main scale.
|
|
double scale_, fontScale_;
|
|
double xMin_, yMin_, xMax_, yMax_, xRange_, yRange_;
|
|
// offsets are for drawing molecules in grids, for example.
|
|
double xOffset_ = 0.0, yOffset_ = 0.0;
|
|
double meanBondLength_ = 0.0;
|
|
// if there's a legend, we reserve a bit for it. molHeight_ is the
|
|
// bit for drawing the molecule, legendHeight_ the bit under that
|
|
// for the legend. For Left/Right, legendWidth_ is the side strip.
|
|
// molWidth_ is the width available for the molecule (drawWidth_ for
|
|
// Bottom/Top, drawWidth_ - legendWidth_ for Left/Right). In pixels.
|
|
int molHeight_, legendHeight_ = 0;
|
|
int molWidth_ = 0, legendWidth_ = 0;
|
|
bool drawingInitialised_ = false;
|
|
// when drawing the atoms and bonds in an SVG, they are given a class
|
|
// via MolDraw2D's activeAtmIdx[12]_ and activeBndIdx. We don't always want
|
|
// them to start from 0 for atom/bond 0.
|
|
int activeAtmIdxOffset_ = 0, activeBndIdxOffset_ = 0;
|
|
bool flexiCanvasX_ = false;
|
|
bool flexiCanvasY_ = false;
|
|
};
|
|
|
|
void centerMolForDrawing(RWMol &mol, int confId = 1);
|
|
bool isLinearAtom(const Atom &atom, const std::vector<Point2D> &atCds);
|
|
std::string getAtomListText(const Atom &atom);
|
|
DrawColour getColourByAtomicNum(int atomicNum,
|
|
const MolDrawOptions &drawOptions);
|
|
DrawColour getHighlightBondColour(
|
|
const Bond *bond, const MolDrawOptions &drawOptions,
|
|
const std::vector<int> &highlightBonds,
|
|
const std::map<int, DrawColour> &highlightBondMap,
|
|
const std::vector<int> &highlightAtoms,
|
|
const std::map<int, DrawColour> &highlightAtomMap);
|
|
double getHighlightBondWidth(
|
|
const MolDrawOptions &drawOptions, int bond_idx,
|
|
const std::map<int, int> *highlight_linewidth_multipliers);
|
|
|
|
Point2D calcPerpendicular(const Point2D &cds1, const Point2D &cds2);
|
|
Point2D calcInnerPerpendicular(const Point2D &cds1, const Point2D &cds2,
|
|
const Point2D &cds3);
|
|
// return a point that is moveEnd moved so as not to clash with any of the
|
|
// rects of a label. moveEnd to end2 are the coords of 2 ends of a bond.
|
|
void adjustBondEndForString(
|
|
const Point2D &end2, double padding,
|
|
const std::vector<std::shared_ptr<StringRect>> &rects, Point2D &moveEnd);
|
|
void findRadicalExtremes(
|
|
const std::vector<std::tuple<StringRect, OrientType, int>> &radicals,
|
|
double &xmin, double &xmax, double &ymin, double &ymax);
|
|
void findRectExtremes(const StringRect &rect, const TextAlignType &align,
|
|
double &xmin, double &xmax, double &ymin, double &ymax);
|
|
void getBondHighlightsForAtoms(const ROMol &mol,
|
|
const std::vector<int> &highlight_atoms,
|
|
std::vector<int> &highlight_bonds);
|
|
// returns true if the vector at2->at1 points in roughly the opposite
|
|
// direction to at3->at4. Basically, if the dot product is negative.
|
|
bool areBondsTrans(const Point2D &at1, const Point2D &at2, const Point2D &at3,
|
|
const Point2D &at4);
|
|
// returns true if the vector at2->at1 points is roughly linear with
|
|
// direction of at3->at4. Basically, if the dot product is 1.0 within the
|
|
// given tolerance.
|
|
bool areBondsParallel(const Point2D &at1, const Point2D &at2,
|
|
const Point2D &at3, const Point2D &at4,
|
|
double tol = 1.0e-4);
|
|
|
|
// find the nborNum'th neighbour of firstAtom that isn't secondAtom
|
|
const Atom *otherNeighbor(const Atom *firstAtom, const Atom *secondAtom,
|
|
int nborNum, const ROMol &mol);
|
|
} // namespace MolDraw2D_detail
|
|
} // namespace RDKit
|
|
|
|
#endif // RDKIT_DRAWMOL_H
|