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

KDE's Doxygen guidelines are available online.