mirror of
https://github.com/rdkit/rdkit.git
synced 2026-06-04 21:54:27 +08:00
771 lines
29 KiB
C++
771 lines
29 KiB
C++
//
|
|
// 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)
|
|
//
|
|
|
|
#include <cmath>
|
|
|
|
#include <GraphMol/MolDraw2D/MolDraw2DDetails.h>
|
|
#include <GraphMol/MolDraw2D/DrawShape.h>
|
|
#include <GraphMol/MolDraw2D/DrawText.h>
|
|
#include <GraphMol/MolDraw2D/MolDraw2D.h>
|
|
#include <GraphMol/MolDraw2D/StringRect.h>
|
|
|
|
namespace RDKit {
|
|
namespace MolDraw2D_detail {
|
|
|
|
// ****************************************************************************
|
|
DrawShape::DrawShape(const std::vector<Point2D> &points, double lineWidth,
|
|
bool scaleLineWidth, DrawColour lineColour, bool fill,
|
|
int atom1, int atom2, int bond)
|
|
: points_(points),
|
|
lineWidth_(lineWidth),
|
|
scaleLineWidth_(scaleLineWidth),
|
|
lineColour_(lineColour),
|
|
fill_(fill),
|
|
atom1_(atom1),
|
|
atom2_(atom2),
|
|
bond_(bond) {}
|
|
|
|
// ****************************************************************************
|
|
void DrawShape::draw(MolDraw2D &drawer) {
|
|
// the various myDraw functions may over-ride these.
|
|
const auto ocolour = drawer.colour();
|
|
drawer.setColour(lineColour_);
|
|
const auto olw = drawer.lineWidth();
|
|
drawer.setLineWidth(lineWidth_);
|
|
const auto ofill = drawer.fillPolys();
|
|
drawer.setFillPolys(fill_);
|
|
const auto lineScale = drawer.drawOptions().scaleBondWidth;
|
|
drawer.drawOptions().scaleBondWidth = scaleLineWidth_;
|
|
drawer.setActiveAtmIdx(atom1_, atom2_);
|
|
drawer.setActiveBndIdx(bond_);
|
|
|
|
myDraw(drawer);
|
|
|
|
drawer.setActiveAtmIdx();
|
|
drawer.setActiveBndIdx();
|
|
drawer.setColour(ocolour);
|
|
drawer.setLineWidth(olw);
|
|
drawer.setFillPolys(ofill);
|
|
drawer.drawOptions().scaleBondWidth = lineScale;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShape::findExtremes(double &xmin, double &xmax, double &ymin,
|
|
double &ymax) const {
|
|
for (const auto &p : points_) {
|
|
xmin = std::min(xmin, p.x);
|
|
xmax = std::max(xmax, p.x);
|
|
ymin = std::min(ymin, p.y);
|
|
ymax = std::max(ymax, p.y);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShape::scale(const Point2D &scale_factor) {
|
|
for (auto &p : points_) {
|
|
p.x *= scale_factor.x;
|
|
p.y *= scale_factor.y;
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShape::move(const Point2D &trans) {
|
|
for (auto &p : points_) {
|
|
p += trans;
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShape::doesRectClash(const StringRect &, double) const {
|
|
return false;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeArrow::DrawShapeArrow(const std::vector<Point2D> &points,
|
|
double lineWidth, bool scaleLineWidth,
|
|
DrawColour lineColour, bool fill, int atom1,
|
|
int atom2, int bond, double frac, double angle)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1,
|
|
atom2, bond),
|
|
frac_(frac),
|
|
angle_(angle) {
|
|
PRECONDITION(points_.size() == 2, "arrow bad points size");
|
|
// The two ends of the arrowhead are used for collision detection so we
|
|
// may as well store them. They won't be used for drawing. lineWidth_ is
|
|
// in pixels rather than drawing coords, so keep another set of coords that
|
|
// are the original ends and arrowhead ends, with a lineWidth_ of 0, used
|
|
// for findExtremes and doesRectClash.
|
|
origPts_[0] = points_[0];
|
|
origPts_[1] = points_[1];
|
|
Point2D ab(points_[1]), p1, p2;
|
|
MolDraw2D_detail::calcArrowHead(ab, p1, p2, points_[1], frac_, 0.0, angle_);
|
|
origPts_[2] = p1;
|
|
origPts_[3] = p2;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArrow::myDraw(MolDraw2D &drawer) const {
|
|
if (drawer.drawOptions().splitBonds) {
|
|
drawer.setActiveAtmIdx(atom2_);
|
|
} else {
|
|
drawer.setActiveAtmIdx(atom1_, atom2_);
|
|
}
|
|
drawer.setActiveBndIdx(bond_);
|
|
drawer.drawArrow(points_[0], points_[1], fill_, frac_, angle_, lineColour_,
|
|
true);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArrow::findExtremes(double &xmin, double &xmax, double &ymin,
|
|
double &ymax) const {
|
|
// do it for origPts_ rather than points_. The difference is that the
|
|
// arrow ends haven't been adjusted for the lineWidth_. It's pretty
|
|
// inconceivable that this will matter for these purposes as the chances
|
|
// of a dative bond going to an atom without a symbol on the edge of the
|
|
// drawing seem slim. findExtremes is only used for finding the extremes
|
|
// of the whole drawing so as to set the scale.
|
|
for (int i = 0; i < 4; i++) {
|
|
const Point2D &p = origPts_[i];
|
|
xmin = std::min(xmin, p.x);
|
|
xmax = std::max(xmax, p.x);
|
|
ymin = std::min(ymin, p.y);
|
|
ymax = std::max(ymax, p.y);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArrow::scale(const Point2D &scale_factor) {
|
|
DrawShape::scale(scale_factor);
|
|
for (int i = 0; i < 4; i++) {
|
|
Point2D &p = origPts_[i];
|
|
p.x *= scale_factor.x;
|
|
p.y *= scale_factor.y;
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArrow::move(const Point2D &trans) {
|
|
DrawShape::move(trans);
|
|
for (int i = 0; i < 4; i++) {
|
|
origPts_[i] += trans;
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeArrow::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
if (doesLineIntersect(rect, origPts_[0], origPts_[1], padding)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersect(rect, origPts_[1], origPts_[2], padding)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersect(rect, origPts_[1], origPts_[3], padding)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersect(rect, origPts_[2], origPts_[3], padding)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeEllipse::DrawShapeEllipse(const std::vector<Point2D> &points,
|
|
double lineWidth, bool scaleLineWidth,
|
|
DrawColour lineColour, bool fill, int atom1)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1) {
|
|
PRECONDITION(points_.size() == 2, "ellipse wrong points");
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeEllipse::myDraw(MolDraw2D &drawer) const {
|
|
if (fill_) {
|
|
drawer.setLineWidth(1);
|
|
drawer.drawOptions().scaleBondWidth = false;
|
|
}
|
|
auto p1 = points_[0] - points_[1];
|
|
auto p2 = points_[0] + points_[1];
|
|
drawer.drawEllipse(p1, p2, true);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeEllipse::findExtremes(double &xmin, double &xmax, double &ymin,
|
|
double &ymax) const {
|
|
// points_[0] is the centre, points_[1] the radii
|
|
xmin = std::min(points_[0].x - points_[1].x, xmin);
|
|
xmax = std::max(points_[0].x + points_[1].x, xmax);
|
|
ymin = std::min(points_[0].y - points_[1].y, ymin);
|
|
ymax = std::max(points_[0].y + points_[1].y, ymax);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeEllipse::move(const Point2D &trans) { points_[0] += trans; }
|
|
// ****************************************************************************
|
|
bool DrawShapeEllipse::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
Point2D tl, tr, br, bl;
|
|
rect.calcCorners(tl, tr, br, bl, padding);
|
|
auto w = points_[1].x;
|
|
auto h = points_[1].y;
|
|
auto cx = points_[0].x;
|
|
auto cy = points_[0].y;
|
|
w = w > 0 ? w : -1 * w;
|
|
h = h > 0 ? h : -1 * h;
|
|
Point2D centre{cx, cy};
|
|
if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, tl, tr)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, tr, br)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, br, bl)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectEllipse(centre, w / 2.0, h / 2.0, padding, bl, tl)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeSimpleLine::DrawShapeSimpleLine(const std::vector<Point2D> &points,
|
|
double lineWidth, bool scaleLineWidth,
|
|
DrawColour lineColour, int atom1,
|
|
int atom2, int bond,
|
|
DashPattern dashPattern)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, lineColour, false, atom1,
|
|
atom2, bond),
|
|
dashPattern_(dashPattern) {
|
|
PRECONDITION(points_.size() == 2, "simple line wrong number of points");
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSimpleLine::myDraw(MolDraw2D &drawer) const {
|
|
auto od = drawer.dash();
|
|
auto dp = dashPattern_;
|
|
if (dp == shortDashes) {
|
|
// these are roughly equivalent to the original checks on scale,
|
|
// which we don't have any more.
|
|
double sq_len = (points_[0] - points_[1]).lengthSq();
|
|
if (sq_len < 55.0) {
|
|
dp[0] /= 4;
|
|
dp[1] /= 3;
|
|
} else if (sq_len < 900.0) {
|
|
dp[0] /= 2;
|
|
dp[1] /= 1.5;
|
|
}
|
|
}
|
|
drawer.setDash(dp);
|
|
drawer.setActiveAtmIdx(atom1_, atom2_);
|
|
drawer.setActiveBndIdx(bond_);
|
|
drawer.drawLine(points_[0], points_[1], lineColour_, lineColour_, true);
|
|
drawer.setActiveAtmIdx();
|
|
drawer.setActiveBndIdx();
|
|
drawer.setDash(od);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeSimpleLine::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
return doesLineIntersect(rect, points_[0], points_[1], padding);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapePolyLine::DrawShapePolyLine(const std::vector<Point2D> &points,
|
|
double lineWidth, bool scaleLineWidth,
|
|
DrawColour lineColour, bool fill,
|
|
int atom1, int atom2, int bond,
|
|
DashPattern dashPattern)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, lineColour, fill, atom1,
|
|
atom2, bond),
|
|
dashPattern_(dashPattern) {
|
|
PRECONDITION(points_.size() > 2, "polyline not enough points");
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapePolyLine::myDraw(MolDraw2D &drawer) const {
|
|
drawer.drawPolygon(points_, true);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapePolyLine::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
Point2D tl, tr, br, bl;
|
|
rect.calcCorners(tl, tr, br, bl, padding);
|
|
for (size_t i = 1; i < points_.size(); ++i) {
|
|
if (doesLineIntersect(rect, points_[i - 1], points_[i], padding)) {
|
|
return true;
|
|
}
|
|
}
|
|
return doesLineIntersect(rect, points_.front(), points_.back(), padding);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeSolidWedge::DrawShapeSolidWedge(const std::vector<Point2D> points,
|
|
const DrawColour &col1,
|
|
const DrawColour &col2,
|
|
bool splitBonds,
|
|
std::vector<Point2D> &otherBondVecs,
|
|
double lineWidth, int atom1, int atom2,
|
|
int bond)
|
|
: DrawShape(points, lineWidth / 2.0, false, col1, false, atom1, atom2,
|
|
bond),
|
|
col2_(col2),
|
|
splitBonds_(splitBonds),
|
|
otherBondVecs_(otherBondVecs) {
|
|
PRECONDITION(points_.size() == 3, "solid wedge wrong points");
|
|
if (otherBondVecs_.size() > 2) {
|
|
trimOtherBondVecs();
|
|
}
|
|
if (otherBondVecs_.size() == 2) {
|
|
orderOtherBondVecs();
|
|
}
|
|
buildTriangles();
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::buildTriangles() {
|
|
if (!(lineColour_ == col2_) || splitBonds_) {
|
|
buildTwoColorTriangles();
|
|
} else {
|
|
buildSingleColorTriangles();
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::buildSingleColorTriangles() {
|
|
auto point = points_[0];
|
|
auto end1 = points_[1];
|
|
auto end2 = points_[2];
|
|
auto midEnd = (end1 + end2) / 2.0;
|
|
auto adjend1 = end1;
|
|
auto adjend2 = end2;
|
|
points_.clear();
|
|
// adjust adjend1 and adjend2 to line up with otherBondVecs_.
|
|
if (otherBondVecs_.empty()) {
|
|
points_.push_back(point);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(adjend2);
|
|
} else if (otherBondVecs_.size() == 1) {
|
|
auto side1 = (end1 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend1)) {
|
|
adjend1 = end1;
|
|
}
|
|
auto side2 = (end2 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend2)) {
|
|
adjend2 = end2;
|
|
}
|
|
points_.push_back(point);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(adjend2);
|
|
} else if (otherBondVecs_.size() == 2) {
|
|
auto side1 = (end1 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend1)) {
|
|
adjend1 = end1;
|
|
}
|
|
points_.push_back(point);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(midEnd);
|
|
auto side2 = (end2 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[1],
|
|
midEnd + otherBondVecs_[1], &adjend2)) {
|
|
adjend2 = end2;
|
|
}
|
|
points_.push_back(point);
|
|
points_.push_back(midEnd);
|
|
points_.push_back(adjend2);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::buildTwoColorTriangles() {
|
|
auto point = points_[0];
|
|
auto end1 = points_[1];
|
|
auto end2 = points_[2];
|
|
auto midEnd = (end1 + end2) / 2.0;
|
|
auto adjend1 = end1;
|
|
auto adjend2 = end2;
|
|
points_.clear();
|
|
auto e1 = end1 - point;
|
|
auto e2 = end2 - point;
|
|
auto mid1 = point + e1 * 0.5;
|
|
auto mid2 = point + e2 * 0.5;
|
|
points_.push_back(point);
|
|
points_.push_back(mid1);
|
|
points_.push_back(mid2);
|
|
if (otherBondVecs_.empty()) {
|
|
points_.push_back(mid1);
|
|
points_.push_back(adjend2);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(mid1);
|
|
points_.push_back(mid2);
|
|
points_.push_back(adjend2);
|
|
} else if (otherBondVecs_.size() == 1) {
|
|
auto side1 = (end1 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend1)) {
|
|
adjend1 = end1;
|
|
}
|
|
auto side2 = (end2 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend2)) {
|
|
adjend2 = end2;
|
|
}
|
|
points_.push_back(mid1);
|
|
points_.push_back(adjend2);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(mid1);
|
|
points_.push_back(mid2);
|
|
points_.push_back(adjend2);
|
|
} else if (otherBondVecs_.size() == 2) {
|
|
auto side1 = (end1 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side1, midEnd - otherBondVecs_[0],
|
|
midEnd + otherBondVecs_[0], &adjend1)) {
|
|
adjend1 = end1;
|
|
}
|
|
auto side2 = (end2 - point) * 2.0;
|
|
if (!doLinesIntersect(point, point + side2, midEnd - otherBondVecs_[1],
|
|
midEnd + otherBondVecs_[1], &adjend2)) {
|
|
adjend2 = end2;
|
|
}
|
|
points_.push_back(mid1);
|
|
points_.push_back(adjend1);
|
|
points_.push_back(midEnd);
|
|
points_.push_back(midEnd);
|
|
points_.push_back(mid2);
|
|
points_.push_back(mid1);
|
|
points_.push_back(midEnd);
|
|
points_.push_back(adjend2);
|
|
points_.push_back(mid2);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::myDraw(MolDraw2D &drawer) const {
|
|
drawer.setFillPolys(true);
|
|
if (drawer.drawOptions().splitBonds) {
|
|
drawer.setActiveAtmIdx(atom1_);
|
|
} else {
|
|
drawer.setActiveAtmIdx(atom1_, atom2_);
|
|
}
|
|
drawer.setActiveBndIdx(bond_);
|
|
drawer.drawTriangle(points_[0], points_[1], points_[2], true);
|
|
if (points_.size() > 3) {
|
|
if (drawer.drawOptions().splitBonds) {
|
|
drawer.setActiveAtmIdx(atom2_);
|
|
}
|
|
drawer.setColour(col2_);
|
|
}
|
|
for (unsigned int i = 3; i < points_.size(); i += 3) {
|
|
drawer.drawTriangle(points_[i], points_[i + 1], points_[i + 2], true);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeSolidWedge::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
if (doesTriangleIntersect(rect, points_[0], points_[1], points_[2],
|
|
padding)) {
|
|
return true;
|
|
}
|
|
if (points_.size() >= 6) {
|
|
if (doesTriangleIntersect(rect, points_[3], points_[4], points_[5],
|
|
padding)) {
|
|
return true;
|
|
}
|
|
if (points_.size() >= 9) {
|
|
if (doesTriangleIntersect(rect, points_[6], points_[7], points_[8],
|
|
padding)) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::trimOtherBondVecs() {
|
|
if (otherBondVecs_.size() < 3) {
|
|
return;
|
|
}
|
|
int firstVec = 0, secondVec = 1;
|
|
double largestAng = -361.0;
|
|
for (unsigned int i = 0; i < otherBondVecs_.size() - 1; ++i) {
|
|
for (unsigned int j = i + 1; j < otherBondVecs_.size(); ++j) {
|
|
auto ang = otherBondVecs_[i].angleTo(otherBondVecs_[j]);
|
|
if (ang > largestAng) {
|
|
firstVec = i;
|
|
secondVec = j;
|
|
largestAng = ang;
|
|
}
|
|
}
|
|
}
|
|
std::vector<Point2D> newVecs{otherBondVecs_[firstVec],
|
|
otherBondVecs_[secondVec]};
|
|
otherBondVecs_ = newVecs;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeSolidWedge::orderOtherBondVecs() {
|
|
if (otherBondVecs_.size() < 2) {
|
|
return;
|
|
}
|
|
// otherBondVecs_[0] needs to be on the same side as points_[1], which
|
|
// implies the larger dot product between it and the vector along the
|
|
// triangle edge.
|
|
auto side1 = (points_[0] - points_[1]);
|
|
auto mid = (points_[1] + points_[2]) / 2.0;
|
|
auto midp1 = mid.directionVector(points_[1]);
|
|
auto dot1 = midp1.dotProduct(otherBondVecs_[0]);
|
|
auto dot2 = midp1.dotProduct(otherBondVecs_[1]);
|
|
if (dot1 < dot2) {
|
|
std::swap(otherBondVecs_[0], otherBondVecs_[1]);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeDashedWedge::DrawShapeDashedWedge(const std::vector<Point2D> points,
|
|
const DrawColour &col1,
|
|
const DrawColour &col2,
|
|
bool oneLessDash, double lineWidth,
|
|
int atom1, int atom2, int bond)
|
|
: DrawShape(points, lineWidth, false, col1, false, atom1, atom2, bond),
|
|
col2_(col2),
|
|
oneLessDash_(oneLessDash) {
|
|
PRECONDITION(points_.size() == 3, "dashed wedge wrong points");
|
|
at1Cds_ = points[0];
|
|
end1Cds_ = points[1];
|
|
end2Cds_ = points[2];
|
|
buildLines();
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeDashedWedge::buildLines() {
|
|
auto midend = (end1Cds_ + end2Cds_) * 0.5;
|
|
points_.clear();
|
|
lineColours_.clear();
|
|
auto e1 = at1Cds_.directionVector(end1Cds_);
|
|
auto e2 = at1Cds_.directionVector(end2Cds_);
|
|
// the ACS1996 rules say the dash separation should be 2.5px. It seems
|
|
// like a good result for all of them.
|
|
// It appears that this means a 2.5px gap between each line, so we need
|
|
// to take the line width into account. Each line that the gap is
|
|
// between will contribute half a width.
|
|
double dashSep = 2.5 + lineWidth_;
|
|
double centralLen = (at1Cds_ - midend).length();
|
|
unsigned int nDashes = rdcast<unsigned int>(std::round(centralLen / dashSep));
|
|
// There should be at least 3 dashes so we can see which way the wedge
|
|
// is going (Github6041b).
|
|
unsigned int numDashesNeeded = oneLessDash_ ? 4 : 3;
|
|
if (nDashes < numDashesNeeded) {
|
|
nDashes = numDashesNeeded;
|
|
}
|
|
if (!nDashes) {
|
|
points_.push_back(end1Cds_);
|
|
points_.push_back(end2Cds_);
|
|
lineColours_.push_back(lineColour_);
|
|
} else {
|
|
// re-adjust so the last dash is on the end of the wedge.
|
|
dashSep = centralLen / rdcast<double>(nDashes);
|
|
// if doing one less dash, we want a shorter wedge that is just as wide
|
|
// at the end as it would have been.
|
|
if (oneLessDash_) {
|
|
double endlenb2 = (end1Cds_ - end2Cds_).length() / 2.0;
|
|
auto centralLine = at1Cds_.directionVector(midend);
|
|
Point2D centralPerp{-centralLine.y, centralLine.x};
|
|
Point2D newEnd1 = at1Cds_ + centralLine * (centralLen - dashSep) +
|
|
centralPerp * endlenb2;
|
|
Point2D newEnd2 = at1Cds_ + centralLine * (centralLen - dashSep) -
|
|
centralPerp * endlenb2;
|
|
e1 = at1Cds_.directionVector(newEnd1);
|
|
e2 = at1Cds_.directionVector(newEnd2);
|
|
}
|
|
// we want the separation down the sides of the triangle, so use
|
|
// similar triangles to scale.
|
|
dashSep *= (end1Cds_ - at1Cds_).length() / centralLen;
|
|
int extra = oneLessDash_ ? 0 : 1;
|
|
for (unsigned int i = 1; i < nDashes + extra; ++i) {
|
|
auto e11 = at1Cds_ + e1 * rdcast<double>(i) * dashSep;
|
|
auto e22 = at1Cds_ + e2 * rdcast<double>(i) * dashSep;
|
|
points_.push_back(e11);
|
|
points_.push_back(e22);
|
|
if (i > nDashes / 2) {
|
|
lineColours_.push_back(col2_);
|
|
} else {
|
|
lineColours_.push_back(lineColour_);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeDashedWedge::myDraw(MolDraw2D &drawer) const {
|
|
drawer.setFillPolys(false);
|
|
drawer.setActiveAtmIdx(atom1_, atom2_);
|
|
drawer.setActiveBndIdx(bond_);
|
|
for (size_t i = 0, j = 0; i < points_.size(); i += 2, ++j) {
|
|
if (drawer.drawOptions().splitBonds) {
|
|
if (i < points_.size() / 2) {
|
|
drawer.setActiveAtmIdx(atom1_);
|
|
} else {
|
|
drawer.setActiveAtmIdx(atom2_);
|
|
}
|
|
}
|
|
drawer.drawLine(points_[i], points_[i + 1], lineColours_[j],
|
|
lineColours_[j], true);
|
|
}
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeDashedWedge::scale(const Point2D &scale_factor) {
|
|
DrawShape::scale(scale_factor);
|
|
at1Cds_.x *= scale_factor.x;
|
|
at1Cds_.y *= scale_factor.y;
|
|
end1Cds_.x *= scale_factor.x;
|
|
end1Cds_.y *= scale_factor.y;
|
|
end2Cds_.x *= scale_factor.x;
|
|
end2Cds_.y *= scale_factor.y;
|
|
buildLines();
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeDashedWedge::move(const Point2D &trans) {
|
|
DrawShape::move(trans);
|
|
at1Cds_ += trans;
|
|
end1Cds_ += trans;
|
|
end2Cds_ += trans;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeDashedWedge::findExtremes(double &xmin, double &xmax,
|
|
double &ymin, double &ymax) const {
|
|
xmin = std::min({at1Cds_.x, end1Cds_.x, end2Cds_.x, xmin});
|
|
xmax = std::max({at1Cds_.x, end1Cds_.x, end2Cds_.x, xmax});
|
|
ymin = std::min({at1Cds_.y, end1Cds_.y, end2Cds_.y, ymin});
|
|
ymax = std::max({at1Cds_.y, end1Cds_.y, end2Cds_.y, ymax});
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeDashedWedge::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
return doesTriangleIntersect(rect, at1Cds_, end1Cds_, end2Cds_, padding);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeWavyLine::DrawShapeWavyLine(const std::vector<Point2D> points,
|
|
double lineWidth, bool scaleLineWidth,
|
|
const DrawColour &col1,
|
|
const DrawColour &col2, double offset,
|
|
int atom1, int atom2, int bond)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, col1, false, atom1, atom2,
|
|
bond),
|
|
col2_(col2),
|
|
offset_(offset) {
|
|
PRECONDITION(points_.size() == 2, "wavy line wrong points");
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeWavyLine::myDraw(MolDraw2D &drawer) const {
|
|
// nSegments is 16 by default in MolDraw2D.
|
|
// use a negative offset because of inverted y coords to make it look the
|
|
// same as it used to.
|
|
int nsegs = int(
|
|
std::round((points_[0] - points_[1]).length() / (offset_ * 2.0 / 3.0)));
|
|
drawer.drawWavyLine(points_[0], points_[1], lineColour_, col2_, nsegs,
|
|
-offset_ / 2.0, true);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeWavyLine::scale(const Point2D &scaleFactor) {
|
|
DrawShape::scale(scaleFactor);
|
|
offset_ *= scaleFactor.x;
|
|
}
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeWavyLine::doesRectClash(const StringRect &rect,
|
|
double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
padding += offset_;
|
|
return doesLineIntersect(rect, points_[0], points_[1], padding);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
DrawShapeArc::DrawShapeArc(const std::vector<Point2D> points, double ang1,
|
|
double ang2, double lineWidth, bool scaleLineWidth,
|
|
const DrawColour &col1, bool fill, int atom1)
|
|
: DrawShape(points, lineWidth, scaleLineWidth, col1, fill, atom1),
|
|
ang1_(ang1),
|
|
ang2_(ang2) {
|
|
PRECONDITION(points_.size() == 2, "arc wrong points");
|
|
RANGE_CHECK(0., ang1_, 360.);
|
|
RANGE_CHECK(0., ang2_, 360.);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArc::myDraw(MolDraw2D &drawer) const {
|
|
if (fill_) {
|
|
drawer.setLineWidth(1);
|
|
drawer.drawOptions().scaleBondWidth = false;
|
|
}
|
|
double start_ang = ang1_ > ang2_ ? ang1_ - 360.0 : ang1_;
|
|
drawer.drawArc(points_[0], points_[1].x, points_[1].y, start_ang, ang2_,
|
|
true);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArc::findExtremes(double &xmin, double &xmax, double &ymin,
|
|
double &ymax) const {
|
|
xmin = std::min(xmin, points_[0].x - points_[1].x);
|
|
xmax = std::max(xmax, points_[0].x + points_[1].x);
|
|
ymin = std::min(ymin, points_[0].y - points_[1].y);
|
|
ymax = std::max(ymax, points_[0].y + points_[1].y);
|
|
}
|
|
|
|
// ****************************************************************************
|
|
void DrawShapeArc::move(const Point2D &trans) { points_[0] += trans; }
|
|
|
|
// ****************************************************************************
|
|
bool DrawShapeArc::doesRectClash(const StringRect &rect, double padding) const {
|
|
padding = scaleLineWidth_ ? padding * lineWidth_ : padding;
|
|
Point2D tl, tr, br, bl;
|
|
rect.calcCorners(tl, tr, br, bl, padding);
|
|
if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
|
|
padding, tl, tr)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
|
|
padding, tr, br)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
|
|
padding, br, bl)) {
|
|
return true;
|
|
}
|
|
if (doesLineIntersectArc(points_[0], points_[1].x, points_[1].y, ang1_, ang2_,
|
|
padding, bl, tl)) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
} // namespace MolDraw2D_detail
|
|
} // namespace RDKit
|