File indexing completed on 2024-04-28 03:56:43

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 "kplotwidget.h"
0009 
0010 #include <math.h>
0011 
0012 #include <QHash>
0013 #include <QHelpEvent>
0014 #include <QPainter>
0015 #include <QToolTip>
0016 #include <QtAlgorithms>
0017 
0018 #include "kplotaxis.h"
0019 #include "kplotobject.h"
0020 #include "kplotpoint.h"
0021 
0022 #define XPADDING 20
0023 #define YPADDING 20
0024 #define BIGTICKSIZE 10
0025 #define SMALLTICKSIZE 4
0026 #define TICKOFFSET 0
0027 
0028 class Q_DECL_HIDDEN KPlotWidget::Private
0029 {
0030 public:
0031     Private(KPlotWidget *qq)
0032         : q(qq)
0033         , cBackground(Qt::black)
0034         , cForeground(Qt::white)
0035         , cGrid(Qt::gray)
0036         , showGrid(false)
0037         , showObjectToolTip(true)
0038         , useAntialias(false)
0039         , autoDelete(true)
0040     {
0041         // create the axes and setting their default properties
0042         KPlotAxis *leftAxis = new KPlotAxis();
0043         leftAxis->setTickLabelsShown(true);
0044         axes.insert(LeftAxis, leftAxis);
0045         KPlotAxis *bottomAxis = new KPlotAxis();
0046         bottomAxis->setTickLabelsShown(true);
0047         axes.insert(BottomAxis, bottomAxis);
0048         KPlotAxis *rightAxis = new KPlotAxis();
0049         axes.insert(RightAxis, rightAxis);
0050         KPlotAxis *topAxis = new KPlotAxis();
0051         axes.insert(TopAxis, topAxis);
0052     }
0053 
0054     ~Private()
0055     {
0056         if (autoDelete) {
0057             qDeleteAll(objectList);
0058         }
0059         qDeleteAll(axes);
0060     }
0061 
0062     KPlotWidget *q;
0063 
0064     void calcDataRectLimits(double x1, double x2, double y1, double y2);
0065     /**
0066      * @return a value indicating how well the given rectangle is
0067      * avoiding masked regions in the plot.  A higher returned value
0068      * indicates that the rectangle is intersecting a larger portion
0069      * of the masked region, or a portion of the masked region which
0070      * is weighted higher.
0071      * @param r The rectangle to be tested
0072      */
0073     float rectCost(const QRectF &r) const;
0074 
0075     // Colors
0076     QColor cBackground, cForeground, cGrid;
0077     // draw options
0078     bool showGrid;
0079     bool showObjectToolTip;
0080     bool useAntialias;
0081     bool autoDelete;
0082     // padding
0083     int leftPadding, rightPadding, topPadding, bottomPadding;
0084     // hashmap with the axes we have
0085     QHash<Axis, KPlotAxis *> axes;
0086     // List of KPlotObjects
0087     QList<KPlotObject *> objectList;
0088     // Limits of the plot area in data units
0089     QRectF dataRect, secondDataRect;
0090     // Limits of the plot area in pixel units
0091     QRect pixRect;
0092     // Array holding the mask of "used" regions of the plot
0093     QImage plotMask;
0094 };
0095 
0096 KPlotWidget::KPlotWidget(QWidget *parent)
0097     : QFrame(parent)
0098     , d(new Private(this))
0099 {
0100     setAttribute(Qt::WA_OpaquePaintEvent);
0101     setAttribute(Qt::WA_NoSystemBackground);
0102 
0103     d->secondDataRect = QRectF(); // default: no secondary data rect
0104     // sets the default limits
0105     d->calcDataRectLimits(0.0, 1.0, 0.0, 1.0);
0106 
0107     setDefaultPaddings();
0108 }
0109 
0110 KPlotWidget::~KPlotWidget() = default;
0111 
0112 QSize KPlotWidget::minimumSizeHint() const
0113 {
0114     return QSize(150, 150);
0115 }
0116 
0117 QSize KPlotWidget::sizeHint() const
0118 {
0119     return size();
0120 }
0121 
0122 void KPlotWidget::setLimits(double x1, double x2, double y1, double y2)
0123 {
0124     d->calcDataRectLimits(x1, x2, y1, y2);
0125     update();
0126 }
0127 
0128 void KPlotWidget::Private::calcDataRectLimits(double x1, double x2, double y1, double y2)
0129 {
0130     double XA1;
0131     double XA2;
0132     double YA1;
0133     double YA2;
0134     if (x2 < x1) {
0135         XA1 = x2;
0136         XA2 = x1;
0137     } else {
0138         XA1 = x1;
0139         XA2 = x2;
0140     }
0141     if (y2 < y1) {
0142         YA1 = y2;
0143         YA2 = y1;
0144     } else {
0145         YA1 = y1;
0146         YA2 = y2;
0147     }
0148 
0149     if (XA2 == XA1) {
0150         // qWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0";
0151         XA2 = XA1 + 1.0;
0152     }
0153     if (YA2 == YA1) {
0154         // qWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0";
0155         YA2 = YA1 + 1.0;
0156     }
0157     dataRect = QRectF(XA1, YA1, XA2 - XA1, YA2 - YA1);
0158 
0159     q->axis(LeftAxis)->setTickMarks(dataRect.y(), dataRect.height());
0160     q->axis(BottomAxis)->setTickMarks(dataRect.x(), dataRect.width());
0161 
0162     if (secondDataRect.isNull()) {
0163         q->axis(RightAxis)->setTickMarks(dataRect.y(), dataRect.height());
0164         q->axis(TopAxis)->setTickMarks(dataRect.x(), dataRect.width());
0165     }
0166 }
0167 
0168 void KPlotWidget::setSecondaryLimits(double x1, double x2, double y1, double y2)
0169 {
0170     double XA1;
0171     double XA2;
0172     double YA1;
0173     double YA2;
0174     if (x2 < x1) {
0175         XA1 = x2;
0176         XA2 = x1;
0177     } else {
0178         XA1 = x1;
0179         XA2 = x2;
0180     }
0181     if (y2 < y1) {
0182         YA1 = y2;
0183         YA2 = y1;
0184     } else {
0185         YA1 = y1;
0186         YA2 = y2;
0187     }
0188 
0189     if (XA2 == XA1) {
0190         // qWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0";
0191         XA2 = XA1 + 1.0;
0192     }
0193     if (YA2 == YA1) {
0194         // qWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0";
0195         YA2 = YA1 + 1.0;
0196     }
0197     d->secondDataRect = QRectF(XA1, YA1, XA2 - XA1, YA2 - YA1);
0198 
0199     axis(RightAxis)->setTickMarks(d->secondDataRect.y(), d->secondDataRect.height());
0200     axis(TopAxis)->setTickMarks(d->secondDataRect.x(), d->secondDataRect.width());
0201 
0202     update();
0203 }
0204 
0205 void KPlotWidget::clearSecondaryLimits()
0206 {
0207     d->secondDataRect = QRectF();
0208     axis(RightAxis)->setTickMarks(d->dataRect.y(), d->dataRect.height());
0209     axis(TopAxis)->setTickMarks(d->dataRect.x(), d->dataRect.width());
0210 
0211     update();
0212 }
0213 
0214 QRectF KPlotWidget::dataRect() const
0215 {
0216     return d->dataRect;
0217 }
0218 
0219 QRectF KPlotWidget::secondaryDataRect() const
0220 {
0221     return d->secondDataRect;
0222 }
0223 
0224 void KPlotWidget::addPlotObject(KPlotObject *object)
0225 {
0226     // skip null pointers
0227     if (!object) {
0228         return;
0229     }
0230     d->objectList.append(object);
0231     update();
0232 }
0233 
0234 void KPlotWidget::addPlotObjects(const QList<KPlotObject *> &objects)
0235 {
0236     bool addedsome = false;
0237     for (KPlotObject *o : objects) {
0238         if (!o) {
0239             continue;
0240         }
0241 
0242         d->objectList.append(o);
0243         addedsome = true;
0244     }
0245     if (addedsome) {
0246         update();
0247     }
0248 }
0249 
0250 QList<KPlotObject *> KPlotWidget::plotObjects() const
0251 {
0252     return d->objectList;
0253 }
0254 
0255 void KPlotWidget::setAutoDeletePlotObjects(bool autoDelete)
0256 {
0257     d->autoDelete = autoDelete;
0258 }
0259 
0260 void KPlotWidget::removeAllPlotObjects()
0261 {
0262     if (d->objectList.isEmpty()) {
0263         return;
0264     }
0265 
0266     if (d->autoDelete) {
0267         qDeleteAll(d->objectList);
0268     }
0269     d->objectList.clear();
0270     update();
0271 }
0272 
0273 void KPlotWidget::resetPlotMask()
0274 {
0275     d->plotMask = QImage(pixRect().size(), QImage::Format_ARGB32);
0276     QColor fillColor = Qt::black;
0277     fillColor.setAlpha(128);
0278     d->plotMask.fill(fillColor.rgb());
0279 }
0280 
0281 void KPlotWidget::resetPlot()
0282 {
0283     if (d->autoDelete) {
0284         qDeleteAll(d->objectList);
0285     }
0286     d->objectList.clear();
0287     clearSecondaryLimits();
0288     d->calcDataRectLimits(0.0, 1.0, 0.0, 1.0);
0289     KPlotAxis *a = axis(RightAxis);
0290     a->setLabel(QString());
0291     a->setTickLabelsShown(false);
0292     a = axis(TopAxis);
0293     a->setLabel(QString());
0294     a->setTickLabelsShown(false);
0295     axis(KPlotWidget::LeftAxis)->setLabel(QString());
0296     axis(KPlotWidget::BottomAxis)->setLabel(QString());
0297     resetPlotMask();
0298 }
0299 
0300 void KPlotWidget::replacePlotObject(int i, KPlotObject *o)
0301 {
0302     // skip null pointers and invalid indexes
0303     if (!o || i < 0 || i >= d->objectList.count()) {
0304         return;
0305     }
0306     if (d->objectList.at(i) == o) {
0307         return;
0308     }
0309     if (d->autoDelete) {
0310         delete d->objectList.at(i);
0311     }
0312     d->objectList.replace(i, o);
0313     update();
0314 }
0315 
0316 QColor KPlotWidget::backgroundColor() const
0317 {
0318     return d->cBackground;
0319 }
0320 
0321 QColor KPlotWidget::foregroundColor() const
0322 {
0323     return d->cForeground;
0324 }
0325 
0326 QColor KPlotWidget::gridColor() const
0327 {
0328     return d->cGrid;
0329 }
0330 
0331 void KPlotWidget::setBackgroundColor(const QColor &bg)
0332 {
0333     d->cBackground = bg;
0334     update();
0335 }
0336 
0337 void KPlotWidget::setForegroundColor(const QColor &fg)
0338 {
0339     d->cForeground = fg;
0340     update();
0341 }
0342 
0343 void KPlotWidget::setGridColor(const QColor &gc)
0344 {
0345     d->cGrid = gc;
0346     update();
0347 }
0348 
0349 bool KPlotWidget::isGridShown() const
0350 {
0351     return d->showGrid;
0352 }
0353 
0354 bool KPlotWidget::isObjectToolTipShown() const
0355 {
0356     return d->showObjectToolTip;
0357 }
0358 
0359 bool KPlotWidget::antialiasing() const
0360 {
0361     return d->useAntialias;
0362 }
0363 
0364 void KPlotWidget::setAntialiasing(bool b)
0365 {
0366     d->useAntialias = b;
0367     update();
0368 }
0369 
0370 void KPlotWidget::setShowGrid(bool show)
0371 {
0372     d->showGrid = show;
0373     update();
0374 }
0375 
0376 void KPlotWidget::setObjectToolTipShown(bool show)
0377 {
0378     d->showObjectToolTip = show;
0379 }
0380 
0381 KPlotAxis *KPlotWidget::axis(Axis type)
0382 {
0383     QHash<Axis, KPlotAxis *>::Iterator it = d->axes.find(type);
0384     return it != d->axes.end() ? it.value() : nullptr;
0385 }
0386 
0387 const KPlotAxis *KPlotWidget::axis(Axis type) const
0388 {
0389     QHash<Axis, KPlotAxis *>::ConstIterator it = d->axes.constFind(type);
0390     return it != d->axes.constEnd() ? it.value() : nullptr;
0391 }
0392 
0393 QRect KPlotWidget::pixRect() const
0394 {
0395     return d->pixRect;
0396 }
0397 
0398 QList<KPlotPoint *> KPlotWidget::pointsUnderPoint(const QPoint &p) const
0399 {
0400     QList<KPlotPoint *> pts;
0401     for (const KPlotObject *po : std::as_const(d->objectList)) {
0402         const auto pointsList = po->points();
0403         for (KPlotPoint *pp : pointsList) {
0404             if ((p - mapToWidget(pp->position()).toPoint()).manhattanLength() <= 4) {
0405                 pts << pp;
0406             }
0407         }
0408     }
0409 
0410     return pts;
0411 }
0412 
0413 bool KPlotWidget::event(QEvent *e)
0414 {
0415     if (e->type() == QEvent::ToolTip) {
0416         if (d->showObjectToolTip) {
0417             QHelpEvent *he = static_cast<QHelpEvent *>(e);
0418             QList<KPlotPoint *> pts = pointsUnderPoint(he->pos() - QPoint(leftPadding(), topPadding()) - contentsRect().topLeft());
0419             if (!pts.isEmpty()) {
0420                 QToolTip::showText(he->globalPos(), pts.front()->label(), this);
0421             }
0422         }
0423         e->accept();
0424         return true;
0425     } else {
0426         return QFrame::event(e);
0427     }
0428 }
0429 
0430 void KPlotWidget::resizeEvent(QResizeEvent *e)
0431 {
0432     QFrame::resizeEvent(e);
0433     setPixRect();
0434     resetPlotMask();
0435 }
0436 
0437 void KPlotWidget::setPixRect()
0438 {
0439     int newWidth = contentsRect().width() - leftPadding() - rightPadding();
0440     int newHeight = contentsRect().height() - topPadding() - bottomPadding();
0441     // PixRect starts at (0,0) because we will translate by leftPadding(), topPadding()
0442     d->pixRect = QRect(0, 0, newWidth, newHeight);
0443 }
0444 
0445 QPointF KPlotWidget::mapToWidget(const QPointF &p) const
0446 {
0447     float px = d->pixRect.left() + d->pixRect.width() * (p.x() - d->dataRect.x()) / d->dataRect.width();
0448     float py = d->pixRect.top() + d->pixRect.height() * (d->dataRect.y() + d->dataRect.height() - p.y()) / d->dataRect.height();
0449     return QPointF(px, py);
0450 }
0451 
0452 void KPlotWidget::maskRect(const QRectF &rf, float fvalue)
0453 {
0454     QRect r = rf.toRect().intersected(d->pixRect);
0455     int value = int(fvalue);
0456     QColor newColor;
0457     for (int ix = r.left(); ix < r.right(); ++ix) {
0458         for (int iy = r.top(); iy < r.bottom(); ++iy) {
0459             newColor = QColor(d->plotMask.pixel(ix, iy));
0460             newColor.setAlpha(200);
0461             newColor.setRed(qMin(newColor.red() + value, 255));
0462             d->plotMask.setPixel(ix, iy, newColor.rgba());
0463         }
0464     }
0465 }
0466 
0467 void KPlotWidget::maskAlongLine(const QPointF &p1, const QPointF &p2, float fvalue)
0468 {
0469     if (!d->pixRect.contains(p1.toPoint()) && !d->pixRect.contains(p2.toPoint())) {
0470         return;
0471     }
0472 
0473     int value = int(fvalue);
0474 
0475     // Determine slope and zeropoint of line
0476     double m = (p2.y() - p1.y()) / (p2.x() - p1.x());
0477     double y0 = p1.y() - m * p1.x();
0478     QColor newColor;
0479 
0480     // Mask each pixel along the line joining p1 and p2
0481     if (m > 1.0 || m < -1.0) { // step in y-direction
0482         int y1 = int(p1.y());
0483         int y2 = int(p2.y());
0484         if (y1 > y2) {
0485             y1 = int(p2.y());
0486             y2 = int(p1.y());
0487         }
0488 
0489         for (int y = y1; y <= y2; ++y) {
0490             int x = int((y - y0) / m);
0491             if (d->pixRect.contains(x, y)) {
0492                 newColor = QColor(d->plotMask.pixel(x, y));
0493                 newColor.setAlpha(100);
0494                 newColor.setRed(qMin(newColor.red() + value, 255));
0495                 d->plotMask.setPixel(x, y, newColor.rgba());
0496             }
0497         }
0498 
0499     } else { // step in x-direction
0500         int x1 = int(p1.x());
0501         int x2 = int(p2.x());
0502         if (x1 > x2) {
0503             x1 = int(p2.x());
0504             x2 = int(p1.x());
0505         }
0506 
0507         for (int x = x1; x <= x2; ++x) {
0508             int y = int(y0 + m * x);
0509             if (d->pixRect.contains(x, y)) {
0510                 newColor = QColor(d->plotMask.pixel(x, y));
0511                 newColor.setAlpha(100);
0512                 newColor.setRed(qMin(newColor.red() + value, 255));
0513                 d->plotMask.setPixel(x, y, newColor.rgba());
0514             }
0515         }
0516     }
0517 }
0518 
0519 // Determine optimal placement for a text label for point pp.  We want
0520 // the label to be near point pp, but we don't want it to overlap with
0521 // other labels or plot elements.  We will use a "downhill simplex"
0522 // algorithm to find a label position that minimizes the pixel values
0523 // in the plotMask image over the label's rect().  The sum of pixel
0524 // values in the label's rect is the "cost" of placing the label there.
0525 //
0526 // Because a downhill simplex follows the local gradient to find low
0527 // values, it can get stuck in local minima.  To mitigate this, we will
0528 // iteratively attempt each of the initial path offset directions (up,
0529 // down, right, left) in the order of increasing cost at each location.
0530 void KPlotWidget::placeLabel(QPainter *painter, KPlotPoint *pp)
0531 {
0532     int textFlags = Qt::TextSingleLine | Qt::AlignCenter;
0533 
0534     QPointF pos = mapToWidget(pp->position());
0535     if (!d->pixRect.contains(pos.toPoint())) {
0536         return;
0537     }
0538 
0539     QFontMetricsF fm(painter->font(), painter->device());
0540     QRectF bestRect = fm.boundingRect(QRectF(pos.x(), pos.y(), 1, 1), textFlags, pp->label());
0541     float xStep = 0.5 * bestRect.width();
0542     float yStep = 0.5 * bestRect.height();
0543     float maxCost = 0.05 * bestRect.width() * bestRect.height();
0544     float bestCost = d->rectCost(bestRect);
0545 
0546     // We will travel along a path defined by the maximum decrease in
0547     // the cost at each step.  If this path takes us to a local minimum
0548     // whose cost exceeds maxCost, then we will restart at the
0549     // beginning and select the next-best path.  The indices of
0550     // already-tried paths are stored in the TriedPathIndex list.
0551     //
0552     // If we try all four first-step paths and still don't get below
0553     // maxCost, then we'll adopt the local minimum position with the
0554     // best cost (designated as bestBadCost).
0555     int iter = 0;
0556     QList<int> TriedPathIndex;
0557     float bestBadCost = 10000;
0558     QRectF bestBadRect;
0559 
0560     // needed to halt iteration from inside the switch
0561     bool flagStop = false;
0562 
0563     while (bestCost > maxCost) {
0564         // Displace the label up, down, left, right; determine which
0565         // step provides the lowest cost
0566         QRectF upRect = bestRect;
0567         upRect.moveTop(upRect.top() + yStep);
0568         float upCost = d->rectCost(upRect);
0569         QRectF downRect = bestRect;
0570         downRect.moveTop(downRect.top() - yStep);
0571         float downCost = d->rectCost(downRect);
0572         QRectF leftRect = bestRect;
0573         leftRect.moveLeft(leftRect.left() - xStep);
0574         float leftCost = d->rectCost(leftRect);
0575         QRectF rightRect = bestRect;
0576         rightRect.moveLeft(rightRect.left() + xStep);
0577         float rightCost = d->rectCost(rightRect);
0578 
0579         // which direction leads to the lowest cost?
0580         QList<float> costList;
0581         costList << upCost << downCost << leftCost << rightCost;
0582         int imin = -1;
0583         for (int i = 0; i < costList.size(); ++i) {
0584             if (iter == 0 && TriedPathIndex.contains(i)) {
0585                 continue; // Skip this first-step path, we already tried it!
0586             }
0587 
0588             // If this first-step path doesn't improve the cost,
0589             // skip this direction from now on
0590             if (iter == 0 && costList[i] >= bestCost) {
0591                 TriedPathIndex.append(i);
0592                 continue;
0593             }
0594 
0595             if (costList[i] < bestCost && (imin < 0 || costList[i] < costList[imin])) {
0596                 imin = i;
0597             }
0598         }
0599 
0600         // Make a note that we've tried the current first-step path
0601         if (iter == 0 && imin >= 0) {
0602             TriedPathIndex.append(imin);
0603         }
0604 
0605         // Adopt the step that produced the best cost
0606         switch (imin) {
0607         case 0: // up
0608             bestRect.moveTop(upRect.top());
0609             bestCost = upCost;
0610             break;
0611         case 1: // down
0612             bestRect.moveTop(downRect.top());
0613             bestCost = downCost;
0614             break;
0615         case 2: // left
0616             bestRect.moveLeft(leftRect.left());
0617             bestCost = leftCost;
0618             break;
0619         case 3: // right
0620             bestRect.moveLeft(rightRect.left());
0621             bestCost = rightCost;
0622             break;
0623         case -1: // no lower cost found!
0624             // We hit a local minimum.  Keep the best of these as bestBadRect
0625             if (bestCost < bestBadCost) {
0626                 bestBadCost = bestCost;
0627                 bestBadRect = bestRect;
0628             }
0629 
0630             // If all of the first-step paths have now been searched, we'll
0631             // have to adopt the bestBadRect
0632             if (TriedPathIndex.size() == 4) {
0633                 bestRect = bestBadRect;
0634                 flagStop = true; // halt iteration
0635                 break;
0636             }
0637 
0638             // If we haven't yet tried all of the first-step paths, start over
0639             if (TriedPathIndex.size() < 4) {
0640                 iter = -1; // anticipating the ++iter below
0641                 bestRect = fm.boundingRect(QRectF(pos.x(), pos.y(), 1, 1), textFlags, pp->label());
0642                 bestCost = d->rectCost(bestRect);
0643             }
0644             break;
0645         }
0646 
0647         // Halt iteration, because we've tried all directions and
0648         // haven't gotten below maxCost (we'll adopt the best
0649         // local minimum found)
0650         if (flagStop) {
0651             break;
0652         }
0653 
0654         ++iter;
0655     }
0656 
0657     painter->drawText(bestRect, textFlags, pp->label());
0658 
0659     // Is a line needed to connect the label to the point?
0660     float deltax = pos.x() - bestRect.center().x();
0661     float deltay = pos.y() - bestRect.center().y();
0662     float rbest = sqrt(deltax * deltax + deltay * deltay);
0663     if (rbest > 20.0) {
0664         // Draw a rectangle around the label
0665         painter->setBrush(QBrush());
0666         // QPen pen = painter->pen();
0667         // pen.setStyle( Qt::DotLine );
0668         // painter->setPen( pen );
0669         painter->drawRoundedRect(bestRect, 25, 25, Qt::RelativeSize);
0670 
0671         // Now connect the label to the point with a line.
0672         // The line is drawn from the center of the near edge of the rectangle
0673         float xline = bestRect.center().x();
0674         if (bestRect.left() > pos.x()) {
0675             xline = bestRect.left();
0676         }
0677         if (bestRect.right() < pos.x()) {
0678             xline = bestRect.right();
0679         }
0680 
0681         float yline = bestRect.center().y();
0682         if (bestRect.top() > pos.y()) {
0683             yline = bestRect.top();
0684         }
0685         if (bestRect.bottom() < pos.y()) {
0686             yline = bestRect.bottom();
0687         }
0688 
0689         painter->drawLine(QPointF(xline, yline), pos);
0690     }
0691 
0692     // Mask the label's rectangle so other labels won't overlap it.
0693     maskRect(bestRect);
0694 }
0695 
0696 float KPlotWidget::Private::rectCost(const QRectF &r) const
0697 {
0698     if (!plotMask.rect().contains(r.toRect())) {
0699         return 10000.;
0700     }
0701 
0702     // Compute sum of mask values in the rect r
0703     QImage subMask = plotMask.copy(r.toRect());
0704     int cost = 0;
0705     for (int ix = 0; ix < subMask.width(); ++ix) {
0706         for (int iy = 0; iy < subMask.height(); ++iy) {
0707             cost += QColor(subMask.pixel(ix, iy)).red();
0708         }
0709     }
0710 
0711     return float(cost);
0712 }
0713 
0714 void KPlotWidget::paintEvent(QPaintEvent *e)
0715 {
0716     // let QFrame draw its default stuff (like the frame)
0717     QFrame::paintEvent(e);
0718     QPainter p;
0719 
0720     p.begin(this);
0721     p.setRenderHint(QPainter::Antialiasing, d->useAntialias);
0722     p.fillRect(rect(), backgroundColor());
0723     p.translate(leftPadding() + 0.5, topPadding() + 0.5);
0724 
0725     setPixRect();
0726     p.setClipRect(d->pixRect);
0727     p.setClipping(true);
0728 
0729     resetPlotMask();
0730 
0731     for (KPlotObject *po : std::as_const(d->objectList)) {
0732         po->draw(&p, this);
0733     }
0734 
0735     // DEBUG: Draw the plot mask
0736     //    p.drawImage( 0, 0, d->plotMask );
0737 
0738     p.setClipping(false);
0739     drawAxes(&p);
0740 
0741     p.end();
0742 }
0743 
0744 void KPlotWidget::drawAxes(QPainter *p)
0745 {
0746     if (d->showGrid) {
0747         p->setPen(gridColor());
0748 
0749         // Grid lines are placed at locations of primary axes' major tickmarks
0750         // vertical grid lines
0751         const QList<double> majMarks = axis(BottomAxis)->majorTickMarks();
0752         for (const double xx : majMarks) {
0753             double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
0754             p->drawLine(QPointF(px, 0.0), QPointF(px, double(d->pixRect.height())));
0755         }
0756         // horizontal grid lines
0757         const QList<double> leftTickMarks = axis(LeftAxis)->majorTickMarks();
0758         for (const double yy : leftTickMarks) {
0759             double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
0760             p->drawLine(QPointF(0.0, py), QPointF(double(d->pixRect.width()), py));
0761         }
0762     }
0763 
0764     p->setPen(foregroundColor());
0765     p->setBrush(Qt::NoBrush);
0766 
0767     // set small font for tick labels
0768     QFont f = p->font();
0769     int s = f.pointSize();
0770     f.setPointSize(s - 2);
0771     p->setFont(f);
0772 
0773     /*** BottomAxis ***/
0774     KPlotAxis *a = axis(BottomAxis);
0775     if (a->isVisible()) {
0776         // Draw axis line
0777         p->drawLine(0, d->pixRect.height(), d->pixRect.width(), d->pixRect.height());
0778 
0779         // Draw major tickmarks
0780         const QList<double> majMarks = a->majorTickMarks();
0781         for (const double xx : majMarks) {
0782             double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
0783             if (px > 0 && px < d->pixRect.width()) {
0784                 p->drawLine(QPointF(px, double(d->pixRect.height() - TICKOFFSET)), //
0785                             QPointF(px, double(d->pixRect.height() - BIGTICKSIZE - TICKOFFSET)));
0786 
0787                 // Draw ticklabel
0788                 if (a->areTickLabelsShown()) {
0789                     QRect r(int(px) - BIGTICKSIZE, d->pixRect.height() + BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
0790                     p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, a->tickLabel(xx));
0791                 }
0792             }
0793         }
0794 
0795         // Draw minor tickmarks
0796         const QList<double> minTickMarks = a->minorTickMarks();
0797         for (const double xx : minTickMarks) {
0798             double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
0799             if (px > 0 && px < d->pixRect.width()) {
0800                 p->drawLine(QPointF(px, double(d->pixRect.height() - TICKOFFSET)), //
0801                             QPointF(px, double(d->pixRect.height() - SMALLTICKSIZE - TICKOFFSET)));
0802             }
0803         }
0804 
0805         // Draw BottomAxis Label
0806         if (!a->label().isEmpty()) {
0807             QRect r(0, d->pixRect.height() + 2 * YPADDING, d->pixRect.width(), YPADDING);
0808             p->drawText(r, Qt::AlignCenter, a->label());
0809         }
0810     } // End of BottomAxis
0811 
0812     /*** LeftAxis ***/
0813     a = axis(LeftAxis);
0814     if (a->isVisible()) {
0815         // Draw axis line
0816         p->drawLine(0, 0, 0, d->pixRect.height());
0817 
0818         // Draw major tickmarks
0819         const QList<double> majMarks = a->majorTickMarks();
0820         for (const double yy : majMarks) {
0821             double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
0822             if (py > 0 && py < d->pixRect.height()) {
0823                 p->drawLine(QPointF(TICKOFFSET, py), QPointF(double(TICKOFFSET + BIGTICKSIZE), py));
0824 
0825                 // Draw ticklabel
0826                 if (a->areTickLabelsShown()) {
0827                     QRect r(-2 * BIGTICKSIZE - SMALLTICKSIZE, int(py) - SMALLTICKSIZE, 2 * BIGTICKSIZE, 2 * SMALLTICKSIZE);
0828                     p->drawText(r, Qt::AlignRight | Qt::AlignVCenter | Qt::TextDontClip, a->tickLabel(yy));
0829                 }
0830             }
0831         }
0832 
0833         // Draw minor tickmarks
0834         const QList<double> minTickMarks = a->minorTickMarks();
0835         for (const double yy : minTickMarks) {
0836             double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
0837             if (py > 0 && py < d->pixRect.height()) {
0838                 p->drawLine(QPointF(TICKOFFSET, py), QPointF(double(TICKOFFSET + SMALLTICKSIZE), py));
0839             }
0840         }
0841 
0842         // Draw LeftAxis Label.  We need to draw the text sideways.
0843         if (!a->label().isEmpty()) {
0844             // store current painter translation/rotation state
0845             p->save();
0846 
0847             // translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
0848             p->translate(-3 * XPADDING, d->pixRect.height());
0849             p->rotate(-90.0);
0850 
0851             QRect r(0, 0, d->pixRect.height(), XPADDING);
0852             p->drawText(r, Qt::AlignCenter, a->label()); // draw the label, now that we are sideways
0853 
0854             p->restore(); // restore translation/rotation state
0855         }
0856     } // End of LeftAxis
0857 
0858     // Prepare for top and right axes; we may need the secondary data rect
0859     double x0 = d->dataRect.x();
0860     double y0 = d->dataRect.y();
0861     double dw = d->dataRect.width();
0862     double dh = d->dataRect.height();
0863     if (secondaryDataRect().isValid()) {
0864         x0 = secondaryDataRect().x();
0865         y0 = secondaryDataRect().y();
0866         dw = secondaryDataRect().width();
0867         dh = secondaryDataRect().height();
0868     }
0869 
0870     /*** TopAxis ***/
0871     a = axis(TopAxis);
0872     if (a->isVisible()) {
0873         // Draw axis line
0874         p->drawLine(0, 0, d->pixRect.width(), 0);
0875 
0876         // Draw major tickmarks
0877         const QList<double> majMarks = a->majorTickMarks();
0878         for (const double xx : majMarks) {
0879             double px = d->pixRect.width() * (xx - x0) / dw;
0880             if (px > 0 && px < d->pixRect.width()) {
0881                 p->drawLine(QPointF(px, TICKOFFSET), QPointF(px, double(BIGTICKSIZE + TICKOFFSET)));
0882 
0883                 // Draw ticklabel
0884                 if (a->areTickLabelsShown()) {
0885                     QRect r(int(px) - BIGTICKSIZE, (int)-1.5 * BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
0886                     p->drawText(r, Qt::AlignCenter | Qt::TextDontClip, a->tickLabel(xx));
0887                 }
0888             }
0889         }
0890 
0891         // Draw minor tickmarks
0892         const QList<double> minMarks = a->minorTickMarks();
0893         for (const double xx : minMarks) {
0894             double px = d->pixRect.width() * (xx - x0) / dw;
0895             if (px > 0 && px < d->pixRect.width()) {
0896                 p->drawLine(QPointF(px, TICKOFFSET), QPointF(px, double(SMALLTICKSIZE + TICKOFFSET)));
0897             }
0898         }
0899 
0900         // Draw TopAxis Label
0901         if (!a->label().isEmpty()) {
0902             QRect r(0, 0 - 3 * YPADDING, d->pixRect.width(), YPADDING);
0903             p->drawText(r, Qt::AlignCenter, a->label());
0904         }
0905     } // End of TopAxis
0906 
0907     /*** RightAxis ***/
0908     a = axis(RightAxis);
0909     if (a->isVisible()) {
0910         // Draw axis line
0911         p->drawLine(d->pixRect.width(), 0, d->pixRect.width(), d->pixRect.height());
0912 
0913         // Draw major tickmarks
0914         const QList<double> majMarks = a->majorTickMarks();
0915         for (const double yy : majMarks) {
0916             double py = d->pixRect.height() * (1.0 - (yy - y0) / dh);
0917             if (py > 0 && py < d->pixRect.height()) {
0918                 p->drawLine(QPointF(double(d->pixRect.width() - TICKOFFSET), py), //
0919                             QPointF(double(d->pixRect.width() - TICKOFFSET - BIGTICKSIZE), py));
0920 
0921                 // Draw ticklabel
0922                 if (a->areTickLabelsShown()) {
0923                     QRect r(d->pixRect.width() + SMALLTICKSIZE, int(py) - SMALLTICKSIZE, 2 * BIGTICKSIZE, 2 * SMALLTICKSIZE);
0924                     p->drawText(r, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextDontClip, a->tickLabel(yy));
0925                 }
0926             }
0927         }
0928 
0929         // Draw minor tickmarks
0930         const QList<double> minMarks = a->minorTickMarks();
0931         for (const double yy : minMarks) {
0932             double py = d->pixRect.height() * (1.0 - (yy - y0) / dh);
0933             if (py > 0 && py < d->pixRect.height()) {
0934                 p->drawLine(QPointF(double(d->pixRect.width() - 0.0), py), QPointF(double(d->pixRect.width() - 0.0 - SMALLTICKSIZE), py));
0935             }
0936         }
0937 
0938         // Draw RightAxis Label.  We need to draw the text sideways.
0939         if (!a->label().isEmpty()) {
0940             // store current painter translation/rotation state
0941             p->save();
0942 
0943             // translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
0944             p->translate(d->pixRect.width() + 2 * XPADDING, d->pixRect.height());
0945             p->rotate(-90.0);
0946 
0947             QRect r(0, 0, d->pixRect.height(), XPADDING);
0948             p->drawText(r, Qt::AlignCenter, a->label()); // draw the label, now that we are sideways
0949 
0950             p->restore(); // restore translation/rotation state
0951         }
0952     } // End of RightAxis
0953 }
0954 
0955 int KPlotWidget::leftPadding() const
0956 {
0957     if (d->leftPadding >= 0) {
0958         return d->leftPadding;
0959     }
0960     const KPlotAxis *a = axis(LeftAxis);
0961     if (a && a->isVisible() && a->areTickLabelsShown()) {
0962         return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING;
0963     }
0964     return XPADDING;
0965 }
0966 
0967 int KPlotWidget::rightPadding() const
0968 {
0969     if (d->rightPadding >= 0) {
0970         return d->rightPadding;
0971     }
0972     const KPlotAxis *a = axis(RightAxis);
0973     if (a && a->isVisible() && a->areTickLabelsShown()) {
0974         return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING;
0975     }
0976     return XPADDING;
0977 }
0978 
0979 int KPlotWidget::topPadding() const
0980 {
0981     if (d->topPadding >= 0) {
0982         return d->topPadding;
0983     }
0984     const KPlotAxis *a = axis(TopAxis);
0985     if (a && a->isVisible() && a->areTickLabelsShown()) {
0986         return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING;
0987     }
0988     return YPADDING;
0989 }
0990 
0991 int KPlotWidget::bottomPadding() const
0992 {
0993     if (d->bottomPadding >= 0) {
0994         return d->bottomPadding;
0995     }
0996     const KPlotAxis *a = axis(BottomAxis);
0997     if (a && a->isVisible() && a->areTickLabelsShown()) {
0998         return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING;
0999     }
1000     return YPADDING;
1001 }
1002 
1003 void KPlotWidget::setLeftPadding(int padding)
1004 {
1005     d->leftPadding = padding;
1006 }
1007 
1008 void KPlotWidget::setRightPadding(int padding)
1009 {
1010     d->rightPadding = padding;
1011 }
1012 
1013 void KPlotWidget::setTopPadding(int padding)
1014 {
1015     d->topPadding = padding;
1016 }
1017 
1018 void KPlotWidget::setBottomPadding(int padding)
1019 {
1020     d->bottomPadding = padding;
1021 }
1022 
1023 void KPlotWidget::setDefaultPaddings()
1024 {
1025     d->leftPadding = -1;
1026     d->rightPadding = -1;
1027     d->topPadding = -1;
1028     d->bottomPadding = -1;
1029 }
1030 
1031 #include "moc_kplotwidget.cpp"