KD Chart API Documentation  3.1
KDChartCartesianCoordinatePlane.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** This file is part of the KD Chart library.
4 **
5 ** SPDX-FileCopyrightText: 2001 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 **
7 ** SPDX-License-Identifier: MIT
8 **
9 ****************************************************************************/
10 
12 #include "KDChartCartesianCoordinatePlane_p.h"
13 
16 #include "KDChartAbstractDiagram.h"
17 #include "KDChartAbstractDiagram_p.h"
18 #include "KDChartBarDiagram.h"
19 #include "KDChartGridAttributes.h"
20 #include "KDChartPaintContext.h"
21 #include "KDChartPainterSaver_p.h"
22 #include "KDChartStockDiagram.h"
23 
24 #include <KDABLibFakes>
25 
26 #include <QApplication>
27 #include <QElapsedTimer>
28 #include <QFont>
29 #include <QList>
30 #include <QPainter>
31 #include <QtDebug>
32 
33 using namespace KDChart;
34 
35 #define d d_func()
36 
37 CartesianCoordinatePlane::Private::Private()
38  : AbstractCoordinatePlane::Private()
39 {
40 }
41 
42 CartesianCoordinatePlane::CartesianCoordinatePlane(Chart *parent)
43  : AbstractCoordinatePlane(new Private(), parent)
44 {
45  // this block left empty intentionally
46 }
47 
49 {
50  // this block left empty intentionally
51 }
52 
53 void CartesianCoordinatePlane::init()
54 {
55  // this block left empty intentionally
56 }
57 
59 {
60  Q_ASSERT_X(dynamic_cast<AbstractCartesianDiagram *>(diagram),
61  "CartesianCoordinatePlane::addDiagram", "Only cartesian "
62  "diagrams can be added to a cartesian coordinate plane!");
66 
68 }
69 
70 void CartesianCoordinatePlane::paint(QPainter *painter)
71 {
72  // prevent recursive call:
73  if (d->bPaintIsRunning) {
74  return;
75  }
76  d->bPaintIsRunning = true;
77 
78  AbstractDiagramList diags = diagrams();
79  if (!diags.isEmpty()) {
80  PaintContext ctx;
81  ctx.setPainter(painter);
82  ctx.setCoordinatePlane(this);
83  const QRectF drawArea(drawingArea());
84  ctx.setRectangle(drawArea);
85 
86  // enabling clipping so that we're not drawing outside
87  PainterSaver painterSaver(painter);
88  QRect clipRect = drawArea.toRect().adjusted(-1, -1, 1, 1);
89  QRegion clipRegion(clipRect);
90  painter->setClipRegion(clipRegion);
91 
92  // paint the coordinate system rulers:
93  d->grid->drawGrid(&ctx);
94 
95  // paint the diagrams:
96  for (int i = 0; i < diags.size(); i++) {
97  if (diags[i]->isHidden()) {
98  continue;
99  }
100  bool doDumpPaintTime = AbstractDiagram::Private::get(diags[i])->doDumpPaintTime;
101  QElapsedTimer stopWatch;
102  if (doDumpPaintTime) {
103  stopWatch.start();
104  }
105 
106  PainterSaver diagramPainterSaver(painter);
107  diags[i]->paint(&ctx);
108 
109  if (doDumpPaintTime) {
110  qDebug() << "Painting diagram" << i << "took" << stopWatch.elapsed() << "milliseconds";
111  }
112  }
113  }
114  d->bPaintIsRunning = false;
115 }
116 
118 {
119  layoutDiagrams();
120 }
121 
123 {
124  // determine unit of the rectangles of all involved diagrams:
125  qreal minX = 0;
126  qreal maxX = 0;
127  qreal minY = 0;
128  qreal maxY = 0;
129  bool bStarting = true;
130  const auto constDiagrams = diagrams();
131  for (const AbstractDiagram *diagram : constDiagrams) {
132  QPair<QPointF, QPointF> dataBoundariesPair = diagram->dataBoundaries();
133  // qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\ngets diagram->dataBoundaries: " << dataBoundariesPair.first << dataBoundariesPair.second;
134  if (bStarting || dataBoundariesPair.first.x() < minX)
135  minX = dataBoundariesPair.first.x();
136  if (bStarting || dataBoundariesPair.first.y() < minY)
137  minY = dataBoundariesPair.first.y();
138  if (bStarting || dataBoundariesPair.second.x() > maxX)
139  maxX = dataBoundariesPair.second.x();
140  if (bStarting || dataBoundariesPair.second.y() > maxY)
141  maxY = dataBoundariesPair.second.y();
142  bStarting = false;
143  }
144  // qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\nreturns data boundaries: " << QRectF( QPointF(minX, minY), QSizeF(maxX - minX, maxY - minY) );
145  QRectF dataBoundingRect;
146  dataBoundingRect.setBottomLeft(QPointF(minX, minY));
147  dataBoundingRect.setTopRight(QPointF(maxX, maxY));
148  return dataBoundingRect;
149 }
150 
152  const QRectF &r, unsigned int percentX, unsigned int percentY) const
153 {
154  QRectF ret = r;
155  if ((axesCalcModeX() != Logarithmic || r.left() < 0.0) && percentX > 0 && percentX != 100) {
156  const bool isPositive = r.left() >= 0;
157  if ((r.right() >= 0) == isPositive) {
158  qreal upperBound = qMax(r.left(), r.right());
159  qreal lowerBound = qMin(r.left(), r.right());
160  qreal innerBound = isPositive ? lowerBound : upperBound;
161  qreal outerBound = isPositive ? upperBound : lowerBound;
162  if (innerBound / outerBound * 100 <= percentX && d->xAxisStartAtZero) {
163  if (isPositive) {
164  ret.setLeft(0.0);
165  } else {
166  ret.setRight(0.0);
167  }
168  }
169  }
170  }
171  // ### this doesn't seem to take into account that Qt's y coordinate is inverted
172  if ((axesCalcModeY() != Logarithmic || r.bottom() < 0.0) && percentY > 0 && percentY != 100) {
173  const bool isPositive = r.bottom() >= 0;
174  if ((r.top() >= 0) == isPositive) {
175  qreal upperBound = qMax(r.top(), r.bottom());
176  qreal lowerBound = qMin(r.top(), r.bottom());
177  const qreal innerBound = isPositive ? lowerBound : upperBound;
178  const qreal outerBound = isPositive ? upperBound : lowerBound;
179  if (innerBound / outerBound * 100 <= percentY) {
180  if (isPositive) {
181  ret.setBottom(0.0);
182  } else {
183  ret.setTop(0.0);
184  }
185  }
186  }
187  }
188  return ret;
189 }
190 
192 {
193  // are manually set ranges to be applied?
194  const bool bAutoAdjustHorizontalRange = d->autoAdjustHorizontalRangeToData < 100;
195  const bool bAutoAdjustVerticalRange = d->autoAdjustVerticalRangeToData < 100;
196 
197  const bool bHardHorizontalRange = (!bAutoAdjustHorizontalRange) && (d->horizontalMin != d->horizontalMax || (ISNAN(d->horizontalMin) != ISNAN(d->horizontalMax)));
198  const bool bHardVerticalRange = (!bAutoAdjustVerticalRange) && (d->verticalMin != d->verticalMax || (ISNAN(d->verticalMin) != ISNAN(d->verticalMax)));
199  QRectF dataBoundingRect;
200 
201  // if custom boundaries are set on the plane, use them
202  if (bHardHorizontalRange && bHardVerticalRange) {
203  dataBoundingRect.setLeft(d->horizontalMin);
204  dataBoundingRect.setRight(d->horizontalMax);
205  dataBoundingRect.setBottom(d->verticalMin);
206  dataBoundingRect.setTop(d->verticalMax);
207  } else {
208  // determine unit of the rectangles of all involved diagrams:
209  dataBoundingRect = getRawDataBoundingRectFromDiagrams();
210  if (bHardHorizontalRange) {
211  if (!ISNAN(d->horizontalMin))
212  dataBoundingRect.setLeft(d->horizontalMin);
213  if (!ISNAN(d->horizontalMax))
214  dataBoundingRect.setRight(d->horizontalMax);
215  }
216  if (bHardVerticalRange) {
217  if (!ISNAN(d->verticalMin))
218  dataBoundingRect.setBottom(d->verticalMin);
219  if (!ISNAN(d->verticalMax))
220  dataBoundingRect.setTop(d->verticalMax);
221  }
222  }
223  // recalculate the bounds, if automatic adjusting of ranges is desired AND
224  // both bounds are at the same side of the zero line
225  dataBoundingRect = adjustedToMaxEmptyInnerPercentage(
226  dataBoundingRect, d->autoAdjustHorizontalRangeToData, d->autoAdjustVerticalRangeToData);
227  if (bAutoAdjustHorizontalRange) {
228  const_cast<CartesianCoordinatePlane *>(this)->d->horizontalMin = dataBoundingRect.left();
229  const_cast<CartesianCoordinatePlane *>(this)->d->horizontalMax = dataBoundingRect.right();
230  }
231  if (bAutoAdjustVerticalRange) {
232  const_cast<CartesianCoordinatePlane *>(this)->d->verticalMin = dataBoundingRect.bottom();
233  const_cast<CartesianCoordinatePlane *>(this)->d->verticalMax = dataBoundingRect.top();
234  }
235  // qDebug() << Q_FUNC_INFO << dataBoundingRect;
236  return dataBoundingRect;
237 }
238 
240 {
241  const AbstractCartesianDiagram *dgr = diagrams().isEmpty() ? 0 : qobject_cast<const AbstractCartesianDiagram *>(diagrams().first());
242  if (dgr && dgr->referenceDiagram()) {
243  dgr = dgr->referenceDiagram();
244  }
245  const auto *barDiagram = qobject_cast<const BarDiagram *>(dgr);
246  const auto *stockDiagram = qobject_cast<const StockDiagram *>(dgr);
247 
248  // note:
249  // It does make sense to retrieve the orientation from the first diagram. This is because
250  // a coordinate plane can either be for horizontal *or* for vertical diagrams. Both at the
251  // same time won't work, and thus the orientation for all diagrams is the same as for the first one.
252  const Qt::Orientation diagramOrientation = barDiagram != nullptr ? barDiagram->orientation() : Qt::Vertical;
253  const bool diagramIsVertical = diagramOrientation == Qt::Vertical;
254 
256  if (dgr) {
257  const QRectF r(calculateRawDataBoundingRect());
258  // We do not access d->gridAttributesHorizontal/Vertical here, but we use the getter function,
259  // to get the global attrs, if no special ones have been set for the given orientation.
260  const GridAttributes gaH(gridAttributes(Qt::Horizontal));
261  const GridAttributes gaV(gridAttributes(Qt::Vertical));
262  // append the first dimension: for Abscissa axes
263  l.append(
265  r.left(), r.right(),
266  diagramIsVertical ? (!stockDiagram && dgr->datasetDimension() > 1) : true,
267  axesCalcModeX(),
269  gaH.gridStepWidth(),
270  gaH.gridSubStepWidth()));
271  // append the second dimension: for Ordinate axes
272  l.append(
274  r.bottom(), r.top(),
275  diagramIsVertical ? true : (dgr->datasetDimension() > 1),
276  axesCalcModeY(),
278  gaV.gridStepWidth(),
279  gaV.gridSubStepWidth()));
280  } else {
281  l.append(DataDimension()); // This gets us the default 1..0 / 1..0 grid
282  l.append(DataDimension()); // shown, if there is no diagram on this plane.
283  }
284  return l;
285 }
286 
288 {
289  // the rectangle the diagrams cover in the *plane*:
290  // We reserve 1px on each side for antialiased drawing, and respect the way QPainter calculates
291  // the width of a painted rect (the size is the rectangle size plus the pen width). The latter
292  // accounts for another pixel that we subtract from height and width.
293  // This way, most clipping for regular pens should be avoided. When pens with a width larger
294  // than 1 are used, this may not be sufficient.
295  return QRectF(areaGeometry()).adjusted(1.0, 1.0, -2.0, -2.0);
296 }
297 
299 {
300  if (d->dimensions.isEmpty())
301  return QRectF();
302 
303  const DataDimension dimX = d->dimensions.first();
304  const DataDimension dimY = d->dimensions.last();
305  const QPointF pt(qMin(dimX.start, dimX.end), qMax(dimY.start, dimY.end));
306  const QSizeF siz(qAbs(dimX.distance()), -qAbs(dimY.distance()));
307  const QRectF dataBoundingRect(pt, siz);
308 
309  // determine logical top left, taking the "reverse" options into account
310  const QPointF topLeft(d->reverseHorizontalPlane ? dataBoundingRect.right() : dataBoundingRect.left(),
311  d->reverseVerticalPlane ? dataBoundingRect.bottom() : dataBoundingRect.top());
312 
313  const qreal width = dataBoundingRect.width() * (d->reverseHorizontalPlane ? -1.0 : 1.0);
314  const qreal height = dataBoundingRect.height() * (d->reverseVerticalPlane ? -1.0 : 1.0);
315 
316  return QRectF(topLeft, QSizeF(width, height));
317 }
318 
320 {
321  const QRectF logArea(logicalArea());
322  QPointF physicalTopLeft = d->coordinateTransformation.translate(logArea.topLeft());
323  QPointF physicalBottomRight = d->coordinateTransformation.translate(logArea.bottomRight());
324 
325  return QRectF(physicalTopLeft, physicalBottomRight).normalized();
326 }
327 
329 {
330  return diagramArea().intersected(drawingArea());
331 }
332 
334 {
335  d->dimensions = gridDimensionsList();
336  Q_ASSERT_X(d->dimensions.count() == 2, "CartesianCoordinatePlane::layoutDiagrams",
337  "Error: gridDimensionsList() did not return exactly two dimensions.");
338 
339  // physical area of the plane
340  const QRectF physicalArea(drawingArea());
341  // .. in contrast to the logical area
342  const QRectF logArea(logicalArea());
343 
344  // TODO: isometric scaling for zooming?
345 
346  // the plane area might have changed, so the zoom values might also be changed
348 
349  d->coordinateTransformation.updateTransform(logArea, physicalArea);
350 
351  update();
352 }
353 
355 {
356  d->fixedDataCoordinateSpaceRelation = fixed;
357  d->fixedDataCoordinateSpaceRelationPinnedSize = QSize();
359 }
360 
362 {
363  return d->fixedDataCoordinateSpaceRelation;
364 }
365 
367 {
368  if (d->xAxisStartAtZero == fixedStart)
369  return;
370 
371  d->xAxisStartAtZero = fixedStart;
372 }
373 
375 {
376  return d->xAxisStartAtZero;
377 }
378 
380 {
381  if (!d->fixedDataCoordinateSpaceRelation) {
382  return;
383  }
384  // is the new geometry ok?
385  if (!geometry.isValid()) {
386  return;
387  }
388 
389  // note that the pinned size can be invalid even after setting it, if geometry wasn't valid.
390  // this is relevant for the cooperation between this method, setFixedDataCoordinateSpaceRelation(),
391  // and handleFixedDataCoordinateSpaceRelation().
392  if (!d->fixedDataCoordinateSpaceRelationPinnedSize.isValid()) {
393  d->fixedDataCoordinateSpaceRelationPinnedSize = geometry.size();
394  d->fixedDataCoordinateSpaceRelationPinnedZoom = ZoomParameters(zoomFactorX(), zoomFactorY(), zoomCenter());
395  return;
396  }
397 
398  // if the plane size was changed, change zoom factors to keep the diagram size constant
399  if (d->fixedDataCoordinateSpaceRelationPinnedSize != geometry.size()) {
400  const qreal widthScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.width() / geometry.width();
401  const qreal heightScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.height() / geometry.height();
402 
403  const qreal newZoomX = d->fixedDataCoordinateSpaceRelationPinnedZoom.xFactor * widthScaling;
404  const qreal newZoomY = d->fixedDataCoordinateSpaceRelationPinnedZoom.yFactor * heightScaling;
405 
406  const QPointF newCenter = QPointF(d->fixedDataCoordinateSpaceRelationPinnedZoom.xCenter / widthScaling,
407  d->fixedDataCoordinateSpaceRelationPinnedZoom.yCenter / heightScaling);
408  // Use these internal methods to avoid sending the propertiesChanged signal more than once
409  bool changed = false;
410  if (doneSetZoomFactorY(newZoomY))
411  changed = true;
412  if (doneSetZoomFactorX(newZoomX))
413  changed = true;
414  if (doneSetZoomCenter(newCenter))
415  changed = true;
416  if (changed)
417  Q_EMIT propertiesChanged();
418  }
419 }
420 
421 const QPointF CartesianCoordinatePlane::translate(const QPointF &diagramPoint) const
422 {
423  // Note: We do not test if the point lays inside of the data area,
424  // but we just apply the transformation calculations to the point.
425  // This allows for basic calculations done by the user, see e.g.
426  // the file examples/Lines/BubbleChart/mainwindow.cpp
427  return d->coordinateTransformation.translate(diagramPoint);
428 }
429 
430 const QPointF CartesianCoordinatePlane::translateBack(const QPointF &screenPoint) const
431 {
432  return d->coordinateTransformation.translateBack(screenPoint);
433 }
434 
436 {
437  if (d->isometricScaling != isOn) {
438  d->isometricScaling = isOn;
439  layoutDiagrams();
440  Q_EMIT propertiesChanged();
441  }
442 }
443 
445 {
446  return d->isometricScaling;
447 }
448 
450 {
451  if (d->coordinateTransformation.zoom.xFactor == factor) {
452  return false;
453  }
454  d->coordinateTransformation.zoom.xFactor = factor;
455  if (d->autoAdjustGridToZoom) {
456  d->grid->setNeedRecalculate();
457  }
458  return true;
459 }
460 
462 {
463  if (d->coordinateTransformation.zoom.yFactor == factor) {
464  return false;
465  }
466  d->coordinateTransformation.zoom.yFactor = factor;
467  if (d->autoAdjustGridToZoom) {
468  d->grid->setNeedRecalculate();
469  }
470  return true;
471 }
472 
474 {
475  if (d->coordinateTransformation.zoom.center() == point) {
476  return false;
477  }
478  d->coordinateTransformation.zoom.setCenter(point);
479  if (d->autoAdjustGridToZoom) {
480  d->grid->setNeedRecalculate();
481  }
482  return true;
483 }
484 
485 void CartesianCoordinatePlane::setZoomFactors(qreal factorX, qreal factorY)
486 {
487  if (doneSetZoomFactorX(factorX) || doneSetZoomFactorY(factorY)) {
488  d->coordinateTransformation.updateTransform(logicalArea(), drawingArea());
489  Q_EMIT propertiesChanged();
490  }
491 }
492 
494 {
495  if (doneSetZoomFactorX(factor)) {
496  d->coordinateTransformation.updateTransform(logicalArea(), drawingArea());
497  Q_EMIT propertiesChanged();
498  }
499 }
500 
502 {
503  if (doneSetZoomFactorY(factor)) {
504  d->coordinateTransformation.updateTransform(logicalArea(), drawingArea());
505  Q_EMIT propertiesChanged();
506  }
507 }
508 
509 void CartesianCoordinatePlane::setZoomCenter(const QPointF &point)
510 {
511  if (doneSetZoomCenter(point)) {
512  d->coordinateTransformation.updateTransform(logicalArea(), drawingArea());
513  Q_EMIT propertiesChanged();
514  }
515 }
516 
518 {
519  return d->coordinateTransformation.zoom.center();
520 }
521 
523 {
524  return d->coordinateTransformation.zoom.xFactor;
525 }
526 
528 {
529  return d->coordinateTransformation.zoom.yFactor;
530 }
531 
533 {
534  return d->coordinateTransformation.axesCalcModeY;
535 }
536 
538 {
539  return d->coordinateTransformation.axesCalcModeX;
540 }
541 
543 {
544  if (d->coordinateTransformation.axesCalcModeY != mode || d->coordinateTransformation.axesCalcModeX != mode) {
545  d->coordinateTransformation.axesCalcModeY = mode;
546  d->coordinateTransformation.axesCalcModeX = mode;
547  Q_EMIT propertiesChanged();
549  const auto constDiagrams = diagrams();
550  for (AbstractDiagram *diag : constDiagrams) {
551  slotLayoutChanged(diag);
552  }
553  }
554 }
555 
557 {
558  if (d->coordinateTransformation.axesCalcModeY != mode) {
559  d->coordinateTransformation.axesCalcModeY = mode;
560  Q_EMIT propertiesChanged();
563  }
564 }
565 
567 {
568  if (d->coordinateTransformation.axesCalcModeX != mode) {
569  d->coordinateTransformation.axesCalcModeX = mode;
570  Q_EMIT propertiesChanged();
572  }
573 }
574 
575 namespace {
576 inline bool fuzzyCompare(qreal a, qreal b)
577 {
578  if (ISNAN(a) && ISNAN(b))
579  return true;
580  if (qFuzzyIsNull(a) && qFuzzyIsNull(b))
581  return true;
582  return qFuzzyCompare(a, b);
583 }
584 }
585 
586 void CartesianCoordinatePlane::setHorizontalRange(const QPair<qreal, qreal> &range)
587 {
588  const bool bAutoAdjustHorizontalRange = d->autoAdjustHorizontalRangeToData < 100;
589  if (!fuzzyCompare(d->horizontalMin, range.first) || !fuzzyCompare(d->horizontalMax, range.second) || bAutoAdjustHorizontalRange) {
590  d->autoAdjustHorizontalRangeToData = 100;
591  d->horizontalMin = range.first;
592  d->horizontalMax = range.second;
593  layoutDiagrams();
594  Q_EMIT propertiesChanged();
595  Q_EMIT boundariesChanged();
596  }
597 }
598 
599 void CartesianCoordinatePlane::setVerticalRange(const QPair<qreal, qreal> &range)
600 {
601  const bool bAutoAdjustVerticalRange = d->autoAdjustVerticalRangeToData < 100;
602  if (!fuzzyCompare(d->verticalMin, range.first) || !fuzzyCompare(d->verticalMax, range.second) || bAutoAdjustVerticalRange) {
603  d->autoAdjustVerticalRangeToData = 100;
604  d->verticalMin = range.first;
605  d->verticalMax = range.second;
606  layoutDiagrams();
607  Q_EMIT propertiesChanged();
608  Q_EMIT boundariesChanged();
609  }
610 }
611 
612 QPair<qreal, qreal> CartesianCoordinatePlane::horizontalRange() const
613 {
614  return QPair<qreal, qreal>(d->horizontalMin, d->horizontalMax);
615 }
616 
617 QPair<qreal, qreal> CartesianCoordinatePlane::verticalRange() const
618 {
619  return QPair<qreal, qreal>(d->verticalMin, d->verticalMax);
620 }
621 
623 {
624  const QRectF dataBoundingRect(getRawDataBoundingRectFromDiagrams());
625  d->horizontalMin = dataBoundingRect.left();
626  d->horizontalMax = dataBoundingRect.right();
627  d->verticalMin = dataBoundingRect.top();
628  d->verticalMax = dataBoundingRect.bottom();
629  layoutDiagrams();
630  Q_EMIT propertiesChanged();
631 }
632 
634 {
635  const QRectF dataBoundingRect(getRawDataBoundingRectFromDiagrams());
636  d->horizontalMin = dataBoundingRect.left();
637  d->horizontalMax = dataBoundingRect.right();
638  layoutDiagrams();
639  Q_EMIT propertiesChanged();
640 }
641 
643 {
644  const QRectF dataBoundingRect(getRawDataBoundingRectFromDiagrams());
645  d->verticalMin = dataBoundingRect.bottom();
646  d->verticalMax = dataBoundingRect.top();
647  layoutDiagrams();
648  Q_EMIT propertiesChanged();
649 }
650 
652 {
653  if (d->autoAdjustHorizontalRangeToData != percentEmpty) {
654  d->autoAdjustHorizontalRangeToData = percentEmpty;
655  d->horizontalMin = 0.0;
656  d->horizontalMax = 0.0;
657  layoutDiagrams();
658  Q_EMIT propertiesChanged();
659  }
660 }
661 
663 {
664  if (d->autoAdjustVerticalRangeToData != percentEmpty) {
665  d->autoAdjustVerticalRangeToData = percentEmpty;
666  d->verticalMin = 0.0;
667  d->verticalMax = 0.0;
668  layoutDiagrams();
669  Q_EMIT propertiesChanged();
670  }
671 }
672 
674 {
675  return d->autoAdjustHorizontalRangeToData;
676 }
677 
679 {
680  return d->autoAdjustVerticalRangeToData;
681 }
682 
684  Qt::Orientation orientation,
685  const GridAttributes &a)
686 {
687  if (orientation == Qt::Horizontal)
688  d->gridAttributesHorizontal = a;
689  else
690  d->gridAttributesVertical = a;
691  setHasOwnGridAttributes(orientation, true);
692  update();
693  Q_EMIT propertiesChanged();
694 }
695 
696 void CartesianCoordinatePlane::resetGridAttributes(Qt::Orientation orientation)
697 {
698  setHasOwnGridAttributes(orientation, false);
699  update();
700 }
701 
702 const GridAttributes CartesianCoordinatePlane::gridAttributes(Qt::Orientation orientation) const
703 {
704  if (hasOwnGridAttributes(orientation)) {
705  if (orientation == Qt::Horizontal)
706  return d->gridAttributesHorizontal;
707  else
708  return d->gridAttributesVertical;
709  } else {
710  return globalGridAttributes();
711  }
712 }
713 
714 void CartesianCoordinatePlane::setHasOwnGridAttributes(Qt::Orientation orientation, bool on)
715 {
716  if (orientation == Qt::Horizontal)
717  d->hasOwnGridAttributesHorizontal = on;
718  else
719  d->hasOwnGridAttributesVertical = on;
720  Q_EMIT propertiesChanged();
721 }
722 
723 bool CartesianCoordinatePlane::hasOwnGridAttributes(Qt::Orientation orientation) const
724 {
725  return orientation == Qt::Horizontal ? d->hasOwnGridAttributesHorizontal
726  : d->hasOwnGridAttributesVertical;
727 }
728 
730 {
731  if (d->autoAdjustGridToZoom != autoAdjust) {
732  d->autoAdjustGridToZoom = autoAdjust;
733  d->grid->setNeedRecalculate();
734  Q_EMIT propertiesChanged();
735  }
736 }
737 
738 #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) && defined(Q_COMPILER_MANGLES_RETURN_TYPE)
739 const
740 #endif
741  bool
743 {
744  return d->autoAdjustGridToZoom;
745 }
746 
748 {
749  CartesianCoordinatePlane *plane = this;
750  auto *diag = dynamic_cast<AbstractCartesianDiagram *>(plane->diagram());
751  const CartesianAxis *sharedAxis = nullptr;
752  if (diag != nullptr) {
753  const CartesianAxisList axes = diag->axes();
754  for (const CartesianAxis *a : axes) {
755  auto *p = const_cast<CartesianCoordinatePlane *>(
756  dynamic_cast<const CartesianCoordinatePlane *>(a->coordinatePlane()));
757  if (p != nullptr && p != this) {
758  plane = p;
759  sharedAxis = a;
760  }
761  }
762  }
763 
764  if (plane == this || painter == nullptr)
765  return plane;
766 
767  const QPointF zero = QPointF(0, 0);
768  const QPointF tenX = QPointF(10, 0);
769  const QPointF tenY = QPointF(0, 10);
770 
771  if (sharedAxis->isOrdinate()) {
772  painter->translate(translate(zero).x(), 0.0);
773  const qreal factor = (translate(tenX) - translate(zero)).x() / (plane->translate(tenX) - plane->translate(zero)).x();
774  painter->scale(factor, 1.0);
775  painter->translate(-plane->translate(zero).x(), 0.0);
776  }
777  if (sharedAxis->isAbscissa()) {
778  painter->translate(0.0, translate(zero).y());
779  const qreal factor = (translate(tenY) - translate(zero)).y() / (plane->translate(tenY) - plane->translate(zero)).y();
780  painter->scale(1.0, factor);
781  painter->translate(0.0, -plane->translate(zero).y());
782  }
783 
784  return plane;
785 }
786 
788 {
789  if (d->reverseHorizontalPlane == reverse)
790  return;
791 
792  d->reverseHorizontalPlane = reverse;
793  layoutDiagrams();
794  Q_EMIT propertiesChanged();
795 }
796 
798 {
799  return d->reverseHorizontalPlane;
800 }
801 
803 {
804  if (d->reverseVerticalPlane == reverse)
805  return;
806 
807  d->reverseVerticalPlane = reverse;
808  layoutDiagrams();
809  Q_EMIT propertiesChanged();
810 }
811 
813 {
814  return d->reverseVerticalPlane;
815 }
816 
818 {
819  QRectF result;
820 
821  const QRectF drawArea = drawingArea();
822 
823  result.setTopLeft(translateBack(drawArea.topLeft()));
824  result.setBottomRight(translateBack(drawArea.bottomRight()));
825 
826  return result;
827 }
828 
829 void CartesianCoordinatePlane::setGeometry(const QRect &rectangle)
830 {
831  if (rectangle == geometry()) {
832  return;
833  }
834 
835  d->geometry = rectangle;
836  if (d->isometricScaling) {
837  const int hfw = heightForWidth(rectangle.width());
838  // same scaling for x and y means a fixed aspect ratio, which is enforced here
839  // always shrink the too large dimension
840  if (hfw < rectangle.height()) {
841  d->geometry.setHeight(hfw);
842  } else {
843  d->geometry.setWidth(qRound(qreal(rectangle.width()) * qreal(rectangle.height()) / qreal(hfw)));
844  }
845  }
846 
848 
849  const auto constDiagrams = diagrams();
850  for (AbstractDiagram *diagram : constDiagrams) {
851  diagram->resize(d->geometry.size());
852  }
853 }
854 
856 {
857  // not completely sure why this is required for isometric scaling...
858  return d->isometricScaling ? Qt::Horizontal : (Qt::Horizontal | Qt::Vertical);
859 }
860 
862 {
863  return d->isometricScaling;
864 }
865 
867 {
868  // ### using anything for dataRect that depends on geometry will close a feedback loop which
869  // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on
870  // drawingArea(), and no good will come out of using it here.
871  QRectF dataRect = logicalArea();
872  return qRound(qreal(w) * qAbs(qreal(dataRect.height()) / qreal(dataRect.width())));
873 }
874 
876 {
878  if (d->isometricScaling) {
879  // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation
880  sh = d->geometry.size();
881  sh.setHeight(heightForWidth(sh.width()));
882  }
883  return sh;
884 }
QRect areaGeometry() const override
Base class for diagrams based on a cartesian coordianate system.
virtual AbstractCartesianDiagram * referenceDiagram() const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
virtual void addDiagram(AbstractDiagram *diagram)
AbstractDiagram defines the interface for diagram classes.
void layoutChanged(AbstractDiagram *)
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
virtual void resize(const QSizeF &area)=0
virtual bool isOrdinate() const
virtual bool isAbscissa() const
DataDimensionsList getDataDimensionsList() const override
bool hasOwnGridAttributes(Qt::Orientation orientation) const
void setVerticalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in vertical direction.
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
void setZoomFactors(qreal factorX, qreal factorY) override
void adjustRangesToData()
Adjust both, horizontal and vertical range settings to the ranges covered by the model's data values.
const QPointF translate(const QPointF &diagramPoint) const override
AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr) override
Qt::Orientations expandingDirections() const override
void setAutoAdjustVerticalRangeToData(unsigned int percentEmpty=67)
Automatically adjust vertical range settings to the ranges covered by the model's values,...
const GridAttributes gridAttributes(Qt::Orientation orientation) const
void addDiagram(AbstractDiagram *diagram) override
const QPointF translateBack(const QPointF &screenPoint) const
void setAutoAdjustHorizontalRangeToData(unsigned int percentEmpty=67)
Automatically adjust horizontal range settings to the ranges covered by the model's values,...
void setZoomCenter(const QPointF &center) override
QRectF adjustedToMaxEmptyInnerPercentage(const QRectF &r, unsigned int percentX, unsigned int percentY) const
void setHorizontalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in horizontal direction.
void resetGridAttributes(Qt::Orientation orientation)
void setGridAttributes(Qt::Orientation orientation, const GridAttributes &)
void handleFixedDataCoordinateSpaceRelation(const QRectF &geometry)
A chart with one or more diagrams.
Definition: KDChartChart.h:84
Helper class for one dimension of data, e.g. for the rows in a data model, or for the labels of an ax...
A set of attributes controlling the appearance of grids.
KDChartEnums::GranularitySequence gridGranularitySequence() const
Stores information about painting diagrams.
void setPainter(QPainter *painter)
void setCoordinatePlane(AbstractCoordinatePlane *plane)
void setRectangle(const QRectF &rect)
QList< AbstractDiagram * > AbstractDiagramList
QList< DataDimension > DataDimensionsList
QList< CartesianAxis * > CartesianAxisList

© 2001 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-chart/
Generated by doxygen 1.9.1