KPlotting

kplotwidget.cpp
1/* -*- C++ -*-
2 This file is part of the KDE libraries
3 SPDX-FileCopyrightText: 2003 Jason Harris <kstars@30doradus.org>
4
5 SPDX-License-Identifier: LGPL-2.0-or-later
6*/
7
8#include "kplotwidget.h"
9
10#include <math.h>
11
12#include <QHash>
13#include <QHelpEvent>
14#include <QPainter>
15#include <QToolTip>
16#include <QtAlgorithms>
17
18#include "kplotaxis.h"
19#include "kplotobject.h"
20#include "kplotpoint.h"
21
22#define XPADDING 20
23#define YPADDING 20
24#define BIGTICKSIZE 10
25#define SMALLTICKSIZE 4
26#define TICKOFFSET 0
27
28class Q_DECL_HIDDEN KPlotWidget::Private
29{
30public:
31 Private(KPlotWidget *qq)
32 : q(qq)
33 , cBackground(Qt::black)
34 , cForeground(Qt::white)
35 , cGrid(Qt::gray)
36 , showGrid(false)
37 , showObjectToolTip(true)
38 , useAntialias(false)
39 , autoDelete(true)
40 {
41 // create the axes and setting their default properties
42 KPlotAxis *leftAxis = new KPlotAxis();
43 leftAxis->setTickLabelsShown(true);
44 axes.insert(LeftAxis, leftAxis);
45 KPlotAxis *bottomAxis = new KPlotAxis();
46 bottomAxis->setTickLabelsShown(true);
47 axes.insert(BottomAxis, bottomAxis);
48 KPlotAxis *rightAxis = new KPlotAxis();
49 axes.insert(RightAxis, rightAxis);
50 KPlotAxis *topAxis = new KPlotAxis();
51 axes.insert(TopAxis, topAxis);
52 }
53
54 ~Private()
55 {
56 if (autoDelete) {
57 qDeleteAll(objectList);
58 }
59 qDeleteAll(axes);
60 }
61
62 KPlotWidget *q;
63
64 void calcDataRectLimits(double x1, double x2, double y1, double y2);
65 /**
66 * @return a value indicating how well the given rectangle is
67 * avoiding masked regions in the plot. A higher returned value
68 * indicates that the rectangle is intersecting a larger portion
69 * of the masked region, or a portion of the masked region which
70 * is weighted higher.
71 * @param r The rectangle to be tested
72 */
73 float rectCost(const QRectF &r) const;
74
75 // Colors
76 QColor cBackground, cForeground, cGrid;
77 // draw options
78 bool showGrid;
79 bool showObjectToolTip;
80 bool useAntialias;
81 bool autoDelete;
82 // padding
83 int leftPadding, rightPadding, topPadding, bottomPadding;
84 // hashmap with the axes we have
86 // List of KPlotObjects
87 QList<KPlotObject *> objectList;
88 // Limits of the plot area in data units
89 QRectF dataRect, secondDataRect;
90 // Limits of the plot area in pixel units
91 QRect pixRect;
92 // Array holding the mask of "used" regions of the plot
93 QImage plotMask;
94};
95
97 : QFrame(parent)
98 , d(new Private(this))
99{
102
103 d->secondDataRect = QRectF(); // default: no secondary data rect
104 // sets the default limits
105 d->calcDataRectLimits(0.0, 1.0, 0.0, 1.0);
106
108}
109
110KPlotWidget::~KPlotWidget() = default;
111
113{
114 return QSize(150, 150);
115}
116
118{
119 return size();
120}
121
122void KPlotWidget::setLimits(double x1, double x2, double y1, double y2)
123{
124 d->calcDataRectLimits(x1, x2, y1, y2);
125 update();
126}
127
128void KPlotWidget::Private::calcDataRectLimits(double x1, double x2, double y1, double y2)
129{
130 double XA1;
131 double XA2;
132 double YA1;
133 double YA2;
134 if (x2 < x1) {
135 XA1 = x2;
136 XA2 = x1;
137 } else {
138 XA1 = x1;
139 XA2 = x2;
140 }
141 if (y2 < y1) {
142 YA1 = y2;
143 YA2 = y1;
144 } else {
145 YA1 = y1;
146 YA2 = y2;
147 }
148
149 if (XA2 == XA1) {
150 // qWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0";
151 XA2 = XA1 + 1.0;
152 }
153 if (YA2 == YA1) {
154 // qWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0";
155 YA2 = YA1 + 1.0;
156 }
157 dataRect = QRectF(XA1, YA1, XA2 - XA1, YA2 - YA1);
158
159 q->axis(LeftAxis)->setTickMarks(dataRect.y(), dataRect.height());
160 q->axis(BottomAxis)->setTickMarks(dataRect.x(), dataRect.width());
161
162 if (secondDataRect.isNull()) {
163 q->axis(RightAxis)->setTickMarks(dataRect.y(), dataRect.height());
164 q->axis(TopAxis)->setTickMarks(dataRect.x(), dataRect.width());
165 }
166}
167
168void KPlotWidget::setSecondaryLimits(double x1, double x2, double y1, double y2)
169{
170 double XA1;
171 double XA2;
172 double YA1;
173 double YA2;
174 if (x2 < x1) {
175 XA1 = x2;
176 XA2 = x1;
177 } else {
178 XA1 = x1;
179 XA2 = x2;
180 }
181 if (y2 < y1) {
182 YA1 = y2;
183 YA2 = y1;
184 } else {
185 YA1 = y1;
186 YA2 = y2;
187 }
188
189 if (XA2 == XA1) {
190 // qWarning() << "x1 and x2 cannot be equal. Setting x2 = x1 + 1.0";
191 XA2 = XA1 + 1.0;
192 }
193 if (YA2 == YA1) {
194 // qWarning() << "y1 and y2 cannot be equal. Setting y2 = y1 + 1.0";
195 YA2 = YA1 + 1.0;
196 }
197 d->secondDataRect = QRectF(XA1, YA1, XA2 - XA1, YA2 - YA1);
198
199 axis(RightAxis)->setTickMarks(d->secondDataRect.y(), d->secondDataRect.height());
200 axis(TopAxis)->setTickMarks(d->secondDataRect.x(), d->secondDataRect.width());
201
202 update();
203}
204
206{
207 d->secondDataRect = QRectF();
208 axis(RightAxis)->setTickMarks(d->dataRect.y(), d->dataRect.height());
209 axis(TopAxis)->setTickMarks(d->dataRect.x(), d->dataRect.width());
210
211 update();
212}
213
215{
216 return d->dataRect;
217}
218
220{
221 return d->secondDataRect;
222}
223
225{
226 // skip null pointers
227 if (!object) {
228 return;
229 }
230 d->objectList.append(object);
231 update();
232}
233
235{
236 bool addedsome = false;
237 for (KPlotObject *o : objects) {
238 if (!o) {
239 continue;
240 }
241
242 d->objectList.append(o);
243 addedsome = true;
244 }
245 if (addedsome) {
246 update();
247 }
248}
249
251{
252 return d->objectList;
253}
254
256{
257 d->autoDelete = autoDelete;
258}
259
261{
262 if (d->objectList.isEmpty()) {
263 return;
264 }
265
266 if (d->autoDelete) {
267 qDeleteAll(d->objectList);
268 }
269 d->objectList.clear();
270 update();
271}
272
274{
275 d->plotMask = QImage(pixRect().size(), QImage::Format_ARGB32);
276 QColor fillColor = Qt::black;
277 fillColor.setAlpha(128);
278 d->plotMask.fill(fillColor.rgb());
279}
280
282{
283 if (d->autoDelete) {
284 qDeleteAll(d->objectList);
285 }
286 d->objectList.clear();
288 d->calcDataRectLimits(0.0, 1.0, 0.0, 1.0);
290 a->setLabel(QString());
291 a->setTickLabelsShown(false);
292 a = axis(TopAxis);
293 a->setLabel(QString());
294 a->setTickLabelsShown(false);
298}
299
301{
302 // skip null pointers and invalid indexes
303 if (!o || i < 0 || i >= d->objectList.count()) {
304 return;
305 }
306 if (d->objectList.at(i) == o) {
307 return;
308 }
309 if (d->autoDelete) {
310 delete d->objectList.at(i);
311 }
312 d->objectList.replace(i, o);
313 update();
314}
315
316QColor KPlotWidget::backgroundColor() const
317{
318 return d->cBackground;
319}
320
321QColor KPlotWidget::foregroundColor() const
322{
323 return d->cForeground;
324}
325
326QColor KPlotWidget::gridColor() const
327{
328 return d->cGrid;
329}
330
332{
333 d->cBackground = bg;
334 update();
335}
336
338{
339 d->cForeground = fg;
340 update();
341}
342
344{
345 d->cGrid = gc;
346 update();
347}
348
350{
351 return d->showGrid;
352}
353
355{
356 return d->showObjectToolTip;
357}
358
360{
361 return d->useAntialias;
362}
363
365{
366 d->useAntialias = b;
367 update();
368}
369
371{
372 d->showGrid = show;
373 update();
374}
375
377{
378 d->showObjectToolTip = show;
379}
380
382{
383 QHash<Axis, KPlotAxis *>::Iterator it = d->axes.find(type);
384 return it != d->axes.end() ? it.value() : nullptr;
385}
386
388{
389 QHash<Axis, KPlotAxis *>::ConstIterator it = d->axes.constFind(type);
390 return it != d->axes.constEnd() ? it.value() : nullptr;
391}
392
394{
395 return d->pixRect;
396}
397
399{
401 for (const KPlotObject *po : std::as_const(d->objectList)) {
402 const auto pointsList = po->points();
403 for (KPlotPoint *pp : pointsList) {
404 if ((p - mapToWidget(pp->position()).toPoint()).manhattanLength() <= 4) {
405 pts << pp;
406 }
407 }
408 }
409
410 return pts;
411}
412
414{
415 if (e->type() == QEvent::ToolTip) {
416 if (d->showObjectToolTip) {
417 QHelpEvent *he = static_cast<QHelpEvent *>(e);
418 QList<KPlotPoint *> pts = pointsUnderPoint(he->pos() - QPoint(leftPadding(), topPadding()) - contentsRect().topLeft());
419 if (!pts.isEmpty()) {
420 QToolTip::showText(he->globalPos(), pts.front()->label(), this);
421 }
422 }
423 e->accept();
424 return true;
425 } else {
426 return QFrame::event(e);
427 }
428}
429
436
438{
439 int newWidth = contentsRect().width() - leftPadding() - rightPadding();
440 int newHeight = contentsRect().height() - topPadding() - bottomPadding();
441 // PixRect starts at (0,0) because we will translate by leftPadding(), topPadding()
442 d->pixRect = QRect(0, 0, newWidth, newHeight);
443}
444
446{
447 float px = d->pixRect.left() + d->pixRect.width() * (p.x() - d->dataRect.x()) / d->dataRect.width();
448 float py = d->pixRect.top() + d->pixRect.height() * (d->dataRect.y() + d->dataRect.height() - p.y()) / d->dataRect.height();
449 return QPointF(px, py);
450}
451
452void KPlotWidget::maskRect(const QRectF &rf, float fvalue)
453{
454 QRect r = rf.toRect().intersected(d->pixRect);
455 int value = int(fvalue);
456 QColor newColor;
457 for (int ix = r.left(); ix < r.right(); ++ix) {
458 for (int iy = r.top(); iy < r.bottom(); ++iy) {
459 newColor = QColor(d->plotMask.pixel(ix, iy));
460 newColor.setAlpha(200);
461 newColor.setRed(qMin(newColor.red() + value, 255));
462 d->plotMask.setPixel(ix, iy, newColor.rgba());
463 }
464 }
465}
466
467void KPlotWidget::maskAlongLine(const QPointF &p1, const QPointF &p2, float fvalue)
468{
469 if (!d->pixRect.contains(p1.toPoint()) && !d->pixRect.contains(p2.toPoint())) {
470 return;
471 }
472
473 int value = int(fvalue);
474
475 // Determine slope and zeropoint of line
476 double m = (p2.y() - p1.y()) / (p2.x() - p1.x());
477 double y0 = p1.y() - m * p1.x();
478 QColor newColor;
479
480 // Mask each pixel along the line joining p1 and p2
481 if (m > 1.0 || m < -1.0) { // step in y-direction
482 int y1 = int(p1.y());
483 int y2 = int(p2.y());
484 if (y1 > y2) {
485 y1 = int(p2.y());
486 y2 = int(p1.y());
487 }
488
489 for (int y = y1; y <= y2; ++y) {
490 int x = int((y - y0) / m);
491 if (d->pixRect.contains(x, y)) {
492 newColor = QColor(d->plotMask.pixel(x, y));
493 newColor.setAlpha(100);
494 newColor.setRed(qMin(newColor.red() + value, 255));
495 d->plotMask.setPixel(x, y, newColor.rgba());
496 }
497 }
498
499 } else { // step in x-direction
500 int x1 = int(p1.x());
501 int x2 = int(p2.x());
502 if (x1 > x2) {
503 x1 = int(p2.x());
504 x2 = int(p1.x());
505 }
506
507 for (int x = x1; x <= x2; ++x) {
508 int y = int(y0 + m * x);
509 if (d->pixRect.contains(x, y)) {
510 newColor = QColor(d->plotMask.pixel(x, y));
511 newColor.setAlpha(100);
512 newColor.setRed(qMin(newColor.red() + value, 255));
513 d->plotMask.setPixel(x, y, newColor.rgba());
514 }
515 }
516 }
517}
518
519// Determine optimal placement for a text label for point pp. We want
520// the label to be near point pp, but we don't want it to overlap with
521// other labels or plot elements. We will use a "downhill simplex"
522// algorithm to find a label position that minimizes the pixel values
523// in the plotMask image over the label's rect(). The sum of pixel
524// values in the label's rect is the "cost" of placing the label there.
525//
526// Because a downhill simplex follows the local gradient to find low
527// values, it can get stuck in local minima. To mitigate this, we will
528// iteratively attempt each of the initial path offset directions (up,
529// down, right, left) in the order of increasing cost at each location.
531{
532 int textFlags = Qt::TextSingleLine | Qt::AlignCenter;
533
535 if (!d->pixRect.contains(pos.toPoint())) {
536 return;
537 }
538
539 QFontMetricsF fm(painter->font(), painter->device());
540 QRectF bestRect = fm.boundingRect(QRectF(pos.x(), pos.y(), 1, 1), textFlags, pp->label());
541 float xStep = 0.5 * bestRect.width();
542 float yStep = 0.5 * bestRect.height();
543 float maxCost = 0.05 * bestRect.width() * bestRect.height();
544 float bestCost = d->rectCost(bestRect);
545
546 // We will travel along a path defined by the maximum decrease in
547 // the cost at each step. If this path takes us to a local minimum
548 // whose cost exceeds maxCost, then we will restart at the
549 // beginning and select the next-best path. The indices of
550 // already-tried paths are stored in the TriedPathIndex list.
551 //
552 // If we try all four first-step paths and still don't get below
553 // maxCost, then we'll adopt the local minimum position with the
554 // best cost (designated as bestBadCost).
555 int iter = 0;
556 QList<int> TriedPathIndex;
557 float bestBadCost = 10000;
558 QRectF bestBadRect;
559
560 // needed to halt iteration from inside the switch
561 bool flagStop = false;
562
563 while (bestCost > maxCost) {
564 // Displace the label up, down, left, right; determine which
565 // step provides the lowest cost
566 QRectF upRect = bestRect;
567 upRect.moveTop(upRect.top() + yStep);
568 float upCost = d->rectCost(upRect);
569 QRectF downRect = bestRect;
570 downRect.moveTop(downRect.top() - yStep);
571 float downCost = d->rectCost(downRect);
572 QRectF leftRect = bestRect;
573 leftRect.moveLeft(leftRect.left() - xStep);
574 float leftCost = d->rectCost(leftRect);
575 QRectF rightRect = bestRect;
576 rightRect.moveLeft(rightRect.left() + xStep);
577 float rightCost = d->rectCost(rightRect);
578
579 // which direction leads to the lowest cost?
580 QList<float> costList;
581 costList << upCost << downCost << leftCost << rightCost;
582 int imin = -1;
583 for (int i = 0; i < costList.size(); ++i) {
584 if (iter == 0 && TriedPathIndex.contains(i)) {
585 continue; // Skip this first-step path, we already tried it!
586 }
587
588 // If this first-step path doesn't improve the cost,
589 // skip this direction from now on
590 if (iter == 0 && costList[i] >= bestCost) {
591 TriedPathIndex.append(i);
592 continue;
593 }
594
595 if (costList[i] < bestCost && (imin < 0 || costList[i] < costList[imin])) {
596 imin = i;
597 }
598 }
599
600 // Make a note that we've tried the current first-step path
601 if (iter == 0 && imin >= 0) {
602 TriedPathIndex.append(imin);
603 }
604
605 // Adopt the step that produced the best cost
606 switch (imin) {
607 case 0: // up
608 bestRect.moveTop(upRect.top());
609 bestCost = upCost;
610 break;
611 case 1: // down
612 bestRect.moveTop(downRect.top());
613 bestCost = downCost;
614 break;
615 case 2: // left
616 bestRect.moveLeft(leftRect.left());
617 bestCost = leftCost;
618 break;
619 case 3: // right
620 bestRect.moveLeft(rightRect.left());
621 bestCost = rightCost;
622 break;
623 case -1: // no lower cost found!
624 // We hit a local minimum. Keep the best of these as bestBadRect
625 if (bestCost < bestBadCost) {
626 bestBadCost = bestCost;
627 bestBadRect = bestRect;
628 }
629
630 // If all of the first-step paths have now been searched, we'll
631 // have to adopt the bestBadRect
632 if (TriedPathIndex.size() == 4) {
633 bestRect = bestBadRect;
634 flagStop = true; // halt iteration
635 break;
636 }
637
638 // If we haven't yet tried all of the first-step paths, start over
639 if (TriedPathIndex.size() < 4) {
640 iter = -1; // anticipating the ++iter below
641 bestRect = fm.boundingRect(QRectF(pos.x(), pos.y(), 1, 1), textFlags, pp->label());
642 bestCost = d->rectCost(bestRect);
643 }
644 break;
645 }
646
647 // Halt iteration, because we've tried all directions and
648 // haven't gotten below maxCost (we'll adopt the best
649 // local minimum found)
650 if (flagStop) {
651 break;
652 }
653
654 ++iter;
655 }
656
657 painter->drawText(bestRect, textFlags, pp->label());
658
659 // Is a line needed to connect the label to the point?
660 float deltax = pos.x() - bestRect.center().x();
661 float deltay = pos.y() - bestRect.center().y();
662 float rbest = sqrt(deltax * deltax + deltay * deltay);
663 if (rbest > 20.0) {
664 // Draw a rectangle around the label
665 painter->setBrush(QBrush());
666 // QPen pen = painter->pen();
667 // pen.setStyle( Qt::DotLine );
668 // painter->setPen( pen );
669 painter->drawRoundedRect(bestRect, 25, 25, Qt::RelativeSize);
670
671 // Now connect the label to the point with a line.
672 // The line is drawn from the center of the near edge of the rectangle
673 float xline = bestRect.center().x();
674 if (bestRect.left() > pos.x()) {
675 xline = bestRect.left();
676 }
677 if (bestRect.right() < pos.x()) {
678 xline = bestRect.right();
679 }
680
681 float yline = bestRect.center().y();
682 if (bestRect.top() > pos.y()) {
683 yline = bestRect.top();
684 }
685 if (bestRect.bottom() < pos.y()) {
686 yline = bestRect.bottom();
687 }
688
689 painter->drawLine(QPointF(xline, yline), pos);
690 }
691
692 // Mask the label's rectangle so other labels won't overlap it.
693 maskRect(bestRect);
694}
695
696float KPlotWidget::Private::rectCost(const QRectF &r) const
697{
698 if (!plotMask.rect().contains(r.toRect())) {
699 return 10000.;
700 }
701
702 // Compute sum of mask values in the rect r
703 QImage subMask = plotMask.copy(r.toRect());
704 int cost = 0;
705 for (int ix = 0; ix < subMask.width(); ++ix) {
706 for (int iy = 0; iy < subMask.height(); ++iy) {
707 cost += QColor(subMask.pixel(ix, iy)).red();
708 }
709 }
710
711 return float(cost);
712}
713
715{
716 // let QFrame draw its default stuff (like the frame)
718 QPainter p;
719
720 p.begin(this);
721 p.setRenderHint(QPainter::Antialiasing, d->useAntialias);
722 p.fillRect(rect(), backgroundColor());
723 p.translate(leftPadding() + 0.5, topPadding() + 0.5);
724
725 setPixRect();
726 p.setClipRect(d->pixRect);
727 p.setClipping(true);
728
730
731 for (KPlotObject *po : std::as_const(d->objectList)) {
732 po->draw(&p, this);
733 }
734
735 // DEBUG: Draw the plot mask
736 // p.drawImage( 0, 0, d->plotMask );
737
738 p.setClipping(false);
739 drawAxes(&p);
740
741 p.end();
742}
743
745{
746 if (d->showGrid) {
747 p->setPen(gridColor());
748
749 // Grid lines are placed at locations of primary axes' major tickmarks
750 // vertical grid lines
751 const QList<double> majMarks = axis(BottomAxis)->majorTickMarks();
752 for (const double xx : majMarks) {
753 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
754 p->drawLine(QPointF(px, 0.0), QPointF(px, double(d->pixRect.height())));
755 }
756 // horizontal grid lines
757 const QList<double> leftTickMarks = axis(LeftAxis)->majorTickMarks();
758 for (const double yy : leftTickMarks) {
759 double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
760 p->drawLine(QPointF(0.0, py), QPointF(double(d->pixRect.width()), py));
761 }
762 }
763
764 p->setPen(foregroundColor());
766
767 // set small font for tick labels
768 QFont f = p->font();
769 int s = f.pointSize();
770 f.setPointSize(s - 2);
771 p->setFont(f);
772
773 /*** BottomAxis ***/
775 if (a->isVisible()) {
776 // Draw axis line
777 p->drawLine(0, d->pixRect.height(), d->pixRect.width(), d->pixRect.height());
778
779 // Draw major tickmarks
780 const QList<double> majMarks = a->majorTickMarks();
781 for (const double xx : majMarks) {
782 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
783 if (px > 0 && px < d->pixRect.width()) {
784 p->drawLine(QPointF(px, double(d->pixRect.height() - TICKOFFSET)), //
785 QPointF(px, double(d->pixRect.height() - BIGTICKSIZE - TICKOFFSET)));
786
787 // Draw ticklabel
788 if (a->areTickLabelsShown()) {
789 QRect r(int(px) - BIGTICKSIZE, d->pixRect.height() + BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
791 }
792 }
793 }
794
795 // Draw minor tickmarks
796 const QList<double> minTickMarks = a->minorTickMarks();
797 for (const double xx : minTickMarks) {
798 double px = d->pixRect.width() * (xx - d->dataRect.x()) / d->dataRect.width();
799 if (px > 0 && px < d->pixRect.width()) {
800 p->drawLine(QPointF(px, double(d->pixRect.height() - TICKOFFSET)), //
801 QPointF(px, double(d->pixRect.height() - SMALLTICKSIZE - TICKOFFSET)));
802 }
803 }
804
805 // Draw BottomAxis Label
806 if (!a->label().isEmpty()) {
807 QRect r(0, d->pixRect.height() + 2 * YPADDING, d->pixRect.width(), YPADDING);
808 p->drawText(r, Qt::AlignCenter, a->label());
809 }
810 } // End of BottomAxis
811
812 /*** LeftAxis ***/
813 a = axis(LeftAxis);
814 if (a->isVisible()) {
815 // Draw axis line
816 p->drawLine(0, 0, 0, d->pixRect.height());
817
818 // Draw major tickmarks
819 const QList<double> majMarks = a->majorTickMarks();
820 for (const double yy : majMarks) {
821 double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
822 if (py > 0 && py < d->pixRect.height()) {
823 p->drawLine(QPointF(TICKOFFSET, py), QPointF(double(TICKOFFSET + BIGTICKSIZE), py));
824
825 // Draw ticklabel
826 if (a->areTickLabelsShown()) {
827 QRect r(-2 * BIGTICKSIZE - SMALLTICKSIZE, int(py) - SMALLTICKSIZE, 2 * BIGTICKSIZE, 2 * SMALLTICKSIZE);
829 }
830 }
831 }
832
833 // Draw minor tickmarks
834 const QList<double> minTickMarks = a->minorTickMarks();
835 for (const double yy : minTickMarks) {
836 double py = d->pixRect.height() * (1.0 - (yy - d->dataRect.y()) / d->dataRect.height());
837 if (py > 0 && py < d->pixRect.height()) {
838 p->drawLine(QPointF(TICKOFFSET, py), QPointF(double(TICKOFFSET + SMALLTICKSIZE), py));
839 }
840 }
841
842 // Draw LeftAxis Label. We need to draw the text sideways.
843 if (!a->label().isEmpty()) {
844 // store current painter translation/rotation state
845 p->save();
846
847 // translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
848 p->translate(-3 * XPADDING, d->pixRect.height());
849 p->rotate(-90.0);
850
851 QRect r(0, 0, d->pixRect.height(), XPADDING);
852 p->drawText(r, Qt::AlignCenter, a->label()); // draw the label, now that we are sideways
853
854 p->restore(); // restore translation/rotation state
855 }
856 } // End of LeftAxis
857
858 // Prepare for top and right axes; we may need the secondary data rect
859 double x0 = d->dataRect.x();
860 double y0 = d->dataRect.y();
861 double dw = d->dataRect.width();
862 double dh = d->dataRect.height();
863 if (secondaryDataRect().isValid()) {
864 x0 = secondaryDataRect().x();
865 y0 = secondaryDataRect().y();
866 dw = secondaryDataRect().width();
867 dh = secondaryDataRect().height();
868 }
869
870 /*** TopAxis ***/
871 a = axis(TopAxis);
872 if (a->isVisible()) {
873 // Draw axis line
874 p->drawLine(0, 0, d->pixRect.width(), 0);
875
876 // Draw major tickmarks
877 const QList<double> majMarks = a->majorTickMarks();
878 for (const double xx : majMarks) {
879 double px = d->pixRect.width() * (xx - x0) / dw;
880 if (px > 0 && px < d->pixRect.width()) {
881 p->drawLine(QPointF(px, TICKOFFSET), QPointF(px, double(BIGTICKSIZE + TICKOFFSET)));
882
883 // Draw ticklabel
884 if (a->areTickLabelsShown()) {
885 QRect r(int(px) - BIGTICKSIZE, (int)-1.5 * BIGTICKSIZE, 2 * BIGTICKSIZE, BIGTICKSIZE);
887 }
888 }
889 }
890
891 // Draw minor tickmarks
892 const QList<double> minMarks = a->minorTickMarks();
893 for (const double xx : minMarks) {
894 double px = d->pixRect.width() * (xx - x0) / dw;
895 if (px > 0 && px < d->pixRect.width()) {
896 p->drawLine(QPointF(px, TICKOFFSET), QPointF(px, double(SMALLTICKSIZE + TICKOFFSET)));
897 }
898 }
899
900 // Draw TopAxis Label
901 if (!a->label().isEmpty()) {
902 QRect r(0, 0 - 3 * YPADDING, d->pixRect.width(), YPADDING);
903 p->drawText(r, Qt::AlignCenter, a->label());
904 }
905 } // End of TopAxis
906
907 /*** RightAxis ***/
908 a = axis(RightAxis);
909 if (a->isVisible()) {
910 // Draw axis line
911 p->drawLine(d->pixRect.width(), 0, d->pixRect.width(), d->pixRect.height());
912
913 // Draw major tickmarks
914 const QList<double> majMarks = a->majorTickMarks();
915 for (const double yy : majMarks) {
916 double py = d->pixRect.height() * (1.0 - (yy - y0) / dh);
917 if (py > 0 && py < d->pixRect.height()) {
918 p->drawLine(QPointF(double(d->pixRect.width() - TICKOFFSET), py), //
919 QPointF(double(d->pixRect.width() - TICKOFFSET - BIGTICKSIZE), py));
920
921 // Draw ticklabel
922 if (a->areTickLabelsShown()) {
923 QRect r(d->pixRect.width() + SMALLTICKSIZE, int(py) - SMALLTICKSIZE, 2 * BIGTICKSIZE, 2 * SMALLTICKSIZE);
925 }
926 }
927 }
928
929 // Draw minor tickmarks
930 const QList<double> minMarks = a->minorTickMarks();
931 for (const double yy : minMarks) {
932 double py = d->pixRect.height() * (1.0 - (yy - y0) / dh);
933 if (py > 0 && py < d->pixRect.height()) {
934 p->drawLine(QPointF(double(d->pixRect.width() - 0.0), py), QPointF(double(d->pixRect.width() - 0.0 - SMALLTICKSIZE), py));
935 }
936 }
937
938 // Draw RightAxis Label. We need to draw the text sideways.
939 if (!a->label().isEmpty()) {
940 // store current painter translation/rotation state
941 p->save();
942
943 // translate coord sys to left corner of axis label rectangle, then rotate 90 degrees.
944 p->translate(d->pixRect.width() + 2 * XPADDING, d->pixRect.height());
945 p->rotate(-90.0);
946
947 QRect r(0, 0, d->pixRect.height(), XPADDING);
948 p->drawText(r, Qt::AlignCenter, a->label()); // draw the label, now that we are sideways
949
950 p->restore(); // restore translation/rotation state
951 }
952 } // End of RightAxis
953}
954
955int KPlotWidget::leftPadding() const
956{
957 if (d->leftPadding >= 0) {
958 return d->leftPadding;
959 }
960 const KPlotAxis *a = axis(LeftAxis);
961 if (a && a->isVisible() && a->areTickLabelsShown()) {
962 return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING;
963 }
964 return XPADDING;
965}
966
967int KPlotWidget::rightPadding() const
968{
969 if (d->rightPadding >= 0) {
970 return d->rightPadding;
971 }
972 const KPlotAxis *a = axis(RightAxis);
973 if (a && a->isVisible() && a->areTickLabelsShown()) {
974 return !a->label().isEmpty() ? 3 * XPADDING : 2 * XPADDING;
975 }
976 return XPADDING;
977}
978
979int KPlotWidget::topPadding() const
980{
981 if (d->topPadding >= 0) {
982 return d->topPadding;
983 }
984 const KPlotAxis *a = axis(TopAxis);
985 if (a && a->isVisible() && a->areTickLabelsShown()) {
986 return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING;
987 }
988 return YPADDING;
989}
990
991int KPlotWidget::bottomPadding() const
992{
993 if (d->bottomPadding >= 0) {
994 return d->bottomPadding;
995 }
996 const KPlotAxis *a = axis(BottomAxis);
997 if (a && a->isVisible() && a->areTickLabelsShown()) {
998 return !a->label().isEmpty() ? 3 * YPADDING : 2 * YPADDING;
999 }
1000 return YPADDING;
1001}
1002
1004{
1005 d->leftPadding = padding;
1006}
1007
1009{
1010 d->rightPadding = padding;
1011}
1012
1014{
1015 d->topPadding = padding;
1016}
1017
1019{
1020 d->bottomPadding = padding;
1021}
1022
1024{
1025 d->leftPadding = -1;
1026 d->rightPadding = -1;
1027 d->topPadding = -1;
1028 d->bottomPadding = -1;
1029}
1030
1031#include "moc_kplotwidget.cpp"
Axis for KPlotWidget.
Definition kplotaxis.h:27
QList< double > majorTickMarks() const
void setTickMarks(double x0, double length)
Determine the positions of major and minor tickmarks for this axis.
Definition kplotaxis.cpp:96
bool isVisible() const
Definition kplotaxis.cpp:44
void setLabel(const QString &label)
Sets the axis label.
Definition kplotaxis.cpp:64
QString label() const
Definition kplotaxis.cpp:69
void setTickLabelsShown(bool b)
Determine whether tick labels will be drawn for this axis.
Definition kplotaxis.cpp:59
bool areTickLabelsShown() const
Definition kplotaxis.cpp:54
QList< double > minorTickMarks() const
QString tickLabel(double value) const
Encapsulates a data set to be plotted in a KPlotWidget.
Definition kplotobject.h:43
Encapsulates a point in the plot.
Definition kplotpoint.h:30
QPointF position() const
QString label() const
Generic data plotting widget.
Definition kplotwidget.h:70
void setAutoDeletePlotObjects(bool autoDelete)
Enables auto-deletion of plot objects if autoDelete is true; otherwise auto-deletion is disabled.
void maskRect(const QRectF &r, float value=1.0f)
Indicate that object labels should try to avoid the given rectangle in the plot.
QRectF secondaryDataRect() const
KPlotWidget(QWidget *parent=nullptr)
Constructor.
bool isObjectToolTipShown() const
void resizeEvent(QResizeEvent *) override
The resize event handler, called when the widget is resized.
QList< KPlotObject * > plotObjects() const
void resetPlot()
Clear the object list, reset the data limits, and remove axis labels If auto-delete was not disabled,...
void setPixRect()
Synchronize the PixRect with the current widget size and padding settings.
void maskAlongLine(const QPointF &p1, const QPointF &p2, float value=1.0f)
Indicate that object labels should try to avoid the line joining the two given points (in pixel coord...
void setSecondaryLimits(double x1, double x2, double y1, double y2)
Reset the secondary data limits, which control the values displayed along the top and right axes.
~KPlotWidget() override
Destructor.
void setRightPadding(int padding)
Set the number of pixels to the right of the plot area.
void setBackgroundColor(const QColor &bg)
Set the background color.
QRectF dataRect() const
QSize sizeHint() const override
bool event(QEvent *) override
Generic event handler.
void paintEvent(QPaintEvent *) override
The paint event handler, executed when update() or repaint() is called.
QPointF mapToWidget(const QPointF &p) const
Map a coordinate.
KPlotAxis * axis(Axis type)
void setObjectToolTipShown(bool show)
Toggle the display of a tooltip for point objects.
void setShowGrid(bool show)
Toggle whether grid lines are drawn at major tickmarks.
QList< KPlotPoint * > pointsUnderPoint(const QPoint &p) const
void placeLabel(QPainter *painter, KPlotPoint *pp)
Place an object label optimally in the plot.
virtual void drawAxes(QPainter *p)
Draws the plot axes and axis labels.
QSize minimumSizeHint() const override
void setForegroundColor(const QColor &fg)
Set the foreground color.
void replacePlotObject(int i, KPlotObject *o)
Replace an item in the KPlotObject list.
void setLimits(double x1, double x2, double y1, double y2)
Set new data limits for the plot.
void addPlotObject(KPlotObject *object)
Add an item to the list of KPlotObjects to be plotted.
void setLeftPadding(int padding)
Set the number of pixels to the left of the plot area.
void setGridColor(const QColor &gc)
Set the grid color.
void setAntialiasing(bool b)
Toggle antialiased drawing.
void resetPlotMask()
Reset the mask used for non-overlapping labels so that all regions of the plot area are considered em...
QRect pixRect() const
Axis
The four types of plot axes.
Definition kplotwidget.h:96
@ BottomAxis
the bottom axis
Definition kplotwidget.h:98
@ TopAxis
the top axis
@ LeftAxis
the left axis
Definition kplotwidget.h:97
@ RightAxis
the right axis
Definition kplotwidget.h:99
void setDefaultPaddings()
Revert all four padding values to -1, so that they will be automatically determined.
void setBottomPadding(int padding)
Set the number of pixels below the plot area.
void addPlotObjects(const QList< KPlotObject * > &objects)
Add more than one KPlotObject at one time.
bool isGridShown() const
void clearSecondaryLimits()
Unset the secondary limits, so the top and right axes show the same tickmarks as the bottom and left ...
bool antialiasing() const
void setTopPadding(int padding)
Set the number of pixels above the plot area.
void removeAllPlotObjects()
Removes all plot objects that were added to the widget.
int red() const const
QRgb rgb() const const
QRgb rgba() const const
void setAlpha(int alpha)
void setRed(int red)
void accept()
Type type() const const
int pointSize() const const
void setPointSize(int pointSize)
QRectF boundingRect(QChar ch) const const
virtual bool event(QEvent *e) override
virtual void paintEvent(QPaintEvent *) override
T value(const Key &key) const const
const QPoint & globalPos() const const
const QPoint & pos() const const
QImage copy(const QRect &rectangle) const const
int height() const const
QRgb pixel(const QPoint &position) const const
int width() const const
void append(QList< T > &&value)
bool contains(const AT &value) const const
reference front()
bool isEmpty() const const
qsizetype size() const const
bool begin(QPaintDevice *device)
QPaintDevice * device() const const
void drawLine(const QLine &line)
void drawRoundedRect(const QRect &rect, qreal xRadius, qreal yRadius, Qt::SizeMode mode)
void drawText(const QPoint &position, const QString &text)
bool end()
void fillRect(const QRect &rectangle, QGradient::Preset preset)
const QFont & font() const const
void restore()
void rotate(qreal angle)
void save()
void setBrush(Qt::BrushStyle style)
void setClipRect(const QRect &rectangle, Qt::ClipOperation operation)
void setClipping(bool enable)
void setFont(const QFont &font)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void translate(const QPoint &offset)
QPoint toPoint() const const
qreal x() const const
qreal y() const const
int bottom() const const
int height() const const
QRect intersected(const QRect &rectangle) const const
int left() const const
int right() const const
int top() const const
int width() const const
qreal bottom() const const
QPointF center() const const
qreal height() const const
bool isNull() const const
qreal left() const const
void moveLeft(qreal x)
void moveTop(qreal y)
qreal right() const const
QRect toRect() const const
qreal top() const const
qreal width() const const
qreal x() const const
qreal y() const const
bool isEmpty() const const
AlignCenter
RelativeSize
TextSingleLine
WA_OpaquePaintEvent
void showText(const QPoint &pos, const QString &text, QWidget *w, const QRect &rect, int msecDisplayTime)
QRect contentsRect() const const
virtual void resizeEvent(QResizeEvent *event)
void setAttribute(Qt::WidgetAttribute attribute, bool on)
void show()
void update()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Dec 27 2024 11:55:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.