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