File indexing completed on 2024-04-28 15:29:29

0001 /*  -*- C++ -*-
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kplotobject.h"
0009 
0010 #include <QDebug>
0011 #include <QPainter>
0012 #include <QtAlgorithms>
0013 
0014 #include "kplotpoint.h"
0015 #include "kplotwidget.h"
0016 
0017 class KPlotObject::Private
0018 {
0019 public:
0020     Private(KPlotObject *qq)
0021         : q(qq)
0022     {
0023     }
0024 
0025     ~Private()
0026     {
0027         qDeleteAll(pList);
0028     }
0029 
0030     KPlotObject *q;
0031 
0032     QList<KPlotPoint *> pList;
0033     PlotTypes type;
0034     PointStyle pointStyle;
0035     double size;
0036     QPen pen, linePen, barPen, labelPen;
0037     QBrush brush, barBrush;
0038 };
0039 
0040 KPlotObject::KPlotObject(const QColor &c, PlotType t, double size, PointStyle ps)
0041     : d(new Private(this))
0042 {
0043     // By default, all pens and brushes are set to the given color
0044     setBrush(c);
0045     setBarBrush(c);
0046     setPen(QPen(brush(), 1));
0047     setLinePen(pen());
0048     setBarPen(pen());
0049     setLabelPen(pen());
0050 
0051     d->type |= t;
0052     setSize(size);
0053     setPointStyle(ps);
0054 }
0055 
0056 KPlotObject::~KPlotObject()
0057 {
0058     delete d;
0059 }
0060 
0061 KPlotObject::PlotTypes KPlotObject::plotTypes() const
0062 {
0063     return d->type;
0064 }
0065 
0066 void KPlotObject::setShowPoints(bool b)
0067 {
0068     if (b) {
0069         d->type |= KPlotObject::Points;
0070     } else {
0071         d->type &= ~KPlotObject::Points;
0072     }
0073 }
0074 
0075 void KPlotObject::setShowLines(bool b)
0076 {
0077     if (b) {
0078         d->type |= KPlotObject::Lines;
0079     } else {
0080         d->type &= ~KPlotObject::Lines;
0081     }
0082 }
0083 
0084 void KPlotObject::setShowBars(bool b)
0085 {
0086     if (b) {
0087         d->type |= KPlotObject::Bars;
0088     } else {
0089         d->type &= ~KPlotObject::Bars;
0090     }
0091 }
0092 
0093 double KPlotObject::size() const
0094 {
0095     return d->size;
0096 }
0097 
0098 void KPlotObject::setSize(double s)
0099 {
0100     d->size = s;
0101 }
0102 
0103 KPlotObject::PointStyle KPlotObject::pointStyle() const
0104 {
0105     return d->pointStyle;
0106 }
0107 
0108 void KPlotObject::setPointStyle(PointStyle p)
0109 {
0110     d->pointStyle = p;
0111 }
0112 
0113 const QPen &KPlotObject::pen() const
0114 {
0115     return d->pen;
0116 }
0117 
0118 void KPlotObject::setPen(const QPen &p)
0119 {
0120     d->pen = p;
0121 }
0122 
0123 const QPen &KPlotObject::linePen() const
0124 {
0125     return d->linePen;
0126 }
0127 
0128 void KPlotObject::setLinePen(const QPen &p)
0129 {
0130     d->linePen = p;
0131 }
0132 
0133 const QPen &KPlotObject::barPen() const
0134 {
0135     return d->barPen;
0136 }
0137 
0138 void KPlotObject::setBarPen(const QPen &p)
0139 {
0140     d->barPen = p;
0141 }
0142 
0143 const QPen &KPlotObject::labelPen() const
0144 {
0145     return d->labelPen;
0146 }
0147 
0148 void KPlotObject::setLabelPen(const QPen &p)
0149 {
0150     d->labelPen = p;
0151 }
0152 
0153 const QBrush KPlotObject::brush() const
0154 {
0155     return d->brush;
0156 }
0157 
0158 void KPlotObject::setBrush(const QBrush &b)
0159 {
0160     d->brush = b;
0161 }
0162 
0163 const QBrush KPlotObject::barBrush() const
0164 {
0165     return d->barBrush;
0166 }
0167 
0168 void KPlotObject::setBarBrush(const QBrush &b)
0169 {
0170     d->barBrush = b;
0171 }
0172 
0173 QList<KPlotPoint *> KPlotObject::points() const
0174 {
0175     return d->pList;
0176 }
0177 
0178 void KPlotObject::addPoint(const QPointF &p, const QString &label, double barWidth)
0179 {
0180     addPoint(new KPlotPoint(p.x(), p.y(), label, barWidth));
0181 }
0182 
0183 void KPlotObject::addPoint(KPlotPoint *p)
0184 {
0185     if (!p) {
0186         return;
0187     }
0188     d->pList.append(p);
0189 }
0190 
0191 void KPlotObject::addPoint(double x, double y, const QString &label, double barWidth)
0192 {
0193     addPoint(new KPlotPoint(x, y, label, barWidth));
0194 }
0195 
0196 void KPlotObject::removePoint(int index)
0197 {
0198     if ((index < 0) || (index >= d->pList.count())) {
0199         // qWarning() << "KPlotObject::removePoint(): index " << index << " out of range!";
0200         return;
0201     }
0202 
0203     d->pList.removeAt(index);
0204 }
0205 
0206 void KPlotObject::clearPoints()
0207 {
0208     qDeleteAll(d->pList);
0209     d->pList.clear();
0210 }
0211 
0212 void KPlotObject::draw(QPainter *painter, KPlotWidget *pw)
0213 {
0214     // Order of drawing determines z-distance: Bars in the back, then lines,
0215     // then points, then labels.
0216 
0217     if (d->type & Bars) {
0218         painter->setPen(barPen());
0219         painter->setBrush(barBrush());
0220 
0221         double w = 0;
0222         for (int i = 0; i < d->pList.size(); ++i) {
0223             if (d->pList[i]->barWidth() == 0.0) {
0224                 if (i < d->pList.size() - 1) {
0225                     w = d->pList[i + 1]->x() - d->pList[i]->x();
0226                 }
0227                 // For the last bin, we'll just keep the previous width
0228 
0229             } else {
0230                 w = d->pList[i]->barWidth();
0231             }
0232 
0233             QPointF pp = d->pList[i]->position();
0234             QPointF p1(pp.x() - 0.5 * w, 0.0);
0235             QPointF p2(pp.x() + 0.5 * w, pp.y());
0236             QPointF sp1 = pw->mapToWidget(p1);
0237             QPointF sp2 = pw->mapToWidget(p2);
0238 
0239             QRectF barRect = QRectF(sp1.x(), sp1.y(), sp2.x() - sp1.x(), sp2.y() - sp1.y()).normalized();
0240             painter->drawRect(barRect);
0241             pw->maskRect(barRect, 0.25);
0242         }
0243     }
0244 
0245     // Draw lines:
0246     if (d->type & Lines) {
0247         painter->setPen(linePen());
0248 
0249         QPointF Previous = QPointF(); // Initialize to null
0250 
0251         for (const KPlotPoint *pp : std::as_const(d->pList)) {
0252             // q is the position of the point in screen pixel coordinates
0253             QPointF q = pw->mapToWidget(pp->position());
0254 
0255             if (!Previous.isNull()) {
0256                 painter->drawLine(Previous, q);
0257                 pw->maskAlongLine(Previous, q);
0258             }
0259 
0260             Previous = q;
0261         }
0262     }
0263 
0264     // Draw points:
0265     if (d->type & Points) {
0266         for (const KPlotPoint *pp : std::as_const(d->pList)) {
0267             // q is the position of the point in screen pixel coordinates
0268             QPointF q = pw->mapToWidget(pp->position());
0269             if (pw->pixRect().contains(q.toPoint(), false)) {
0270                 double x1 = q.x() - size();
0271                 double y1 = q.y() - size();
0272                 QRectF qr = QRectF(x1, y1, 2 * size(), 2 * size());
0273 
0274                 // Mask out this rect in the plot for label avoidance
0275                 pw->maskRect(qr, 2.0);
0276 
0277                 painter->setPen(pen());
0278                 painter->setBrush(brush());
0279 
0280                 switch (pointStyle()) {
0281                 case Circle:
0282                     painter->drawEllipse(qr);
0283                     break;
0284 
0285                 case Letter:
0286                     painter->drawText(qr, Qt::AlignCenter, pp->label().left(1));
0287                     break;
0288 
0289                 case Triangle: {
0290                     QPolygonF tri;
0291                     /* clang-format off */
0292                     tri << QPointF(q.x() - size(), q.y() + size())
0293                         << QPointF(q.x(), q.y() - size())
0294                         << QPointF(q.x() + size(), q.y() + size());
0295                     /* clang-format on */
0296                     painter->drawPolygon(tri);
0297                     break;
0298                 }
0299 
0300                 case Square:
0301                     painter->drawRect(qr);
0302                     break;
0303 
0304                 case Pentagon: {
0305                     QPolygonF pent;
0306                     /* clang-format off */
0307                     pent << QPointF(q.x(), q.y() - size())
0308                          << QPointF(q.x() + size(), q.y() - 0.309 * size())
0309                          << QPointF(q.x() + 0.588 * size(), q.y() + size())
0310                          << QPointF(q.x() - 0.588 * size(), q.y() + size())
0311                          << QPointF(q.x() - size(), q.y() - 0.309 * size());
0312                     /* clang-format on */
0313                     painter->drawPolygon(pent);
0314                     break;
0315                 }
0316 
0317                 case Hexagon: {
0318                     QPolygonF hex;
0319                     /* clang-format off */
0320                     hex << QPointF(q.x(), q.y() + size())
0321                         << QPointF(q.x() + size(), q.y() + 0.5 * size())
0322                         << QPointF(q.x() + size(), q.y() - 0.5 * size())
0323                         << QPointF(q.x(), q.y() - size())
0324                         << QPointF(q.x() - size(), q.y() + 0.5 * size())
0325                         << QPointF(q.x() - size(), q.y() - 0.5 * size());
0326                     /* clang-format on */
0327                     painter->drawPolygon(hex);
0328                     break;
0329                 }
0330 
0331                 case Asterisk:
0332                     painter->drawLine(q, QPointF(q.x(), q.y() + size()));
0333                     painter->drawLine(q, QPointF(q.x() + size(), q.y() + 0.5 * size()));
0334                     painter->drawLine(q, QPointF(q.x() + size(), q.y() - 0.5 * size()));
0335                     painter->drawLine(q, QPointF(q.x(), q.y() - size()));
0336                     painter->drawLine(q, QPointF(q.x() - size(), q.y() + 0.5 * size()));
0337                     painter->drawLine(q, QPointF(q.x() - size(), q.y() - 0.5 * size()));
0338                     break;
0339 
0340                 case Star: {
0341                     QPolygonF star;
0342                     /* clang-format off */
0343                     star << QPointF(q.x(), q.y() - size())
0344                          << QPointF(q.x() + 0.2245 * size(), q.y() - 0.309 * size())
0345                          << QPointF(q.x() + size(), q.y() - 0.309 * size()) << QPointF(q.x() + 0.363 * size(), q.y() + 0.118 * size())
0346                          << QPointF(q.x() + 0.588 * size(), q.y() + size()) << QPointF(q.x(), q.y() + 0.382 * size())
0347                          << QPointF(q.x() - 0.588 * size(), q.y() + size()) << QPointF(q.x() - 0.363 * size(), q.y() + 0.118 * size())
0348                          << QPointF(q.x() - size(), q.y() - 0.309 * size()) << QPointF(q.x() - 0.2245 * size(), q.y() - 0.309 * size());
0349                     /* clang-format on */
0350                     painter->drawPolygon(star);
0351                     break;
0352                 }
0353 
0354                 default:
0355                     break;
0356                 }
0357             }
0358         }
0359     }
0360 
0361     // Draw labels
0362     painter->setPen(labelPen());
0363 
0364     for (KPlotPoint *pp : std::as_const(d->pList)) {
0365         QPoint q = pw->mapToWidget(pp->position()).toPoint();
0366         if (pw->pixRect().contains(q, false) && !pp->label().isEmpty()) {
0367             pw->placeLabel(painter, pp);
0368         }
0369     }
0370 }