KPlotting

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

KDE's Doxygen guidelines are available online.