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"