KChart

KChartCartesianCoordinatePlane.cpp
1 /*
2  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "KChartCartesianCoordinatePlane.h"
10 #include "KChartCartesianCoordinatePlane_p.h"
11 
12 #include "KChartAbstractDiagram.h"
13 #include "KChartAbstractDiagram_p.h"
14 #include "KChartAbstractCartesianDiagram.h"
15 #include "CartesianCoordinateTransformation.h"
16 #include "KChartGridAttributes.h"
17 #include "KChartPaintContext.h"
18 #include "KChartPainterSaver_p.h"
19 #include "KChartBarDiagram.h"
20 #include "KChartStockDiagram.h"
21 
22 #include <QApplication>
23 #include <QFont>
24 #include <QList>
25 #include <QtDebug>
26 #include <QPainter>
27 #include <QTime>
28 #include <QElapsedTimer>
29 
30 
31 using namespace KChart;
32 
33 #define d d_func()
34 
35 CartesianCoordinatePlane::Private::Private()
36  : AbstractCoordinatePlane::Private()
37  , bPaintIsRunning( false )
38  , hasOwnGridAttributesHorizontal( false )
39  , hasOwnGridAttributesVertical( false )
40  , isometricScaling( false )
41  , horizontalMin( 0 )
42  , horizontalMax( 0 )
43  , verticalMin( 0 )
44  , verticalMax( 0 )
45  , autoAdjustHorizontalRangeToData( 67 )
46  , autoAdjustVerticalRangeToData( 67 )
47  , autoAdjustGridToZoom( true )
48  , fixedDataCoordinateSpaceRelation( false )
49  , xAxisStartAtZero( true )
50  , reverseVerticalPlane( false )
51  , reverseHorizontalPlane( false )
52 {
53 }
54 
55 CartesianCoordinatePlane::CartesianCoordinatePlane( Chart* parent )
56  : AbstractCoordinatePlane( new Private(), parent )
57 {
58  // this block left empty intentionally
59 }
60 
61 CartesianCoordinatePlane::~CartesianCoordinatePlane()
62 {
63  // this block left empty intentionally
64 }
65 
66 void CartesianCoordinatePlane::init()
67 {
68  // this block left empty intentionally
69 }
70 
71 
73 {
74  Q_ASSERT_X( dynamic_cast<AbstractCartesianDiagram*>( diagram ),
75  "CartesianCoordinatePlane::addDiagram", "Only cartesian "
76  "diagrams can be added to a cartesian coordinate plane!" );
79  this, &CartesianCoordinatePlane::slotLayoutChanged );
80 
82 }
83 
84 
86 {
87  // prevent recursive call:
88  if ( d->bPaintIsRunning ) {
89  return;
90  }
91  d->bPaintIsRunning = true;
92 
93  AbstractDiagramList diags = diagrams();
94  if ( !diags.isEmpty() )
95  {
96  PaintContext ctx;
97  ctx.setPainter ( painter );
98  ctx.setCoordinatePlane ( this );
99  const QRectF drawArea( drawingArea() );
100  ctx.setRectangle ( drawArea );
101 
102  // enabling clipping so that we're not drawing outside
103  PainterSaver painterSaver( painter );
104  QRect clipRect = drawArea.toRect().adjusted( -1, -1, 1, 1 );
105  QRegion clipRegion( clipRect );
106  painter->setClipRegion( clipRegion );
107 
108  // paint the coordinate system rulers:
109  d->grid->drawGrid( &ctx );
110 
111  // paint the diagrams:
112  for ( int i = 0; i < diags.size(); i++ )
113  {
114  if ( diags[i]->isHidden() ) {
115  continue;
116  }
117  bool doDumpPaintTime = AbstractDiagram::Private::get( diags[ i ] )->doDumpPaintTime;
118  QElapsedTimer stopWatch;
119  if ( doDumpPaintTime ) {
120  stopWatch.start();
121  }
122 
123  PainterSaver diagramPainterSaver( painter );
124  diags[i]->paint( &ctx );
125 
126  if ( doDumpPaintTime ) {
127  qDebug() << "Painting diagram" << i << "took" << stopWatch.elapsed() << "milliseconds";
128  }
129  }
130 
131  }
132  d->bPaintIsRunning = false;
133 }
134 
135 
136 void CartesianCoordinatePlane::slotLayoutChanged( AbstractDiagram* )
137 {
138  layoutDiagrams();
139 }
140 
141 QRectF CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams() const
142 {
143  // determine unit of the rectangles of all involved diagrams:
144  qreal minX = 0;
145  qreal maxX = 0;
146  qreal minY = 0;
147  qreal maxY = 0;
148  bool bStarting = true;
149  const auto ds = diagrams();
150  for (const AbstractDiagram* diagram : ds)
151  {
152  QPair<QPointF, QPointF> dataBoundariesPair = diagram->dataBoundaries();
153  //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\ngets diagram->dataBoundaries: " << dataBoundariesPair.first << dataBoundariesPair.second;
154  if ( bStarting || dataBoundariesPair.first.x() < minX ) minX = dataBoundariesPair.first.x();
155  if ( bStarting || dataBoundariesPair.first.y() < minY ) minY = dataBoundariesPair.first.y();
156  if ( bStarting || dataBoundariesPair.second.x() > maxX ) maxX = dataBoundariesPair.second.x();
157  if ( bStarting || dataBoundariesPair.second.y() > maxY ) maxY = dataBoundariesPair.second.y();
158  bStarting = false;
159  }
160  //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\nreturns data boundaries: " << QRectF( QPointF(minX, minY), QSizeF(maxX - minX, maxY - minY) );
161  QRectF dataBoundingRect;
162  dataBoundingRect.setBottomLeft( QPointF( minX, minY ) );
163  dataBoundingRect.setTopRight( QPointF( maxX, maxY ) );
164  return dataBoundingRect;
165 }
166 
167 
168 QRectF CartesianCoordinatePlane::adjustedToMaxEmptyInnerPercentage(
169  const QRectF& r, unsigned int percentX, unsigned int percentY ) const
170 {
171  QRectF ret = r;
172  if ( ( axesCalcModeX() != Logarithmic || r.left() < 0.0 ) && percentX > 0 && percentX != 100 ) {
173  const bool isPositive = r.left() >= 0;
174  if ( ( r.right() >= 0 ) == isPositive ) {
175  qreal upperBound = qMax( r.left(), r.right() );
176  qreal lowerBound = qMin( r.left(), r.right() );
177  qreal innerBound = isPositive ? lowerBound : upperBound;
178  qreal outerBound = isPositive ? upperBound : lowerBound;
179  if ( innerBound / outerBound * 100 <= percentX && d->xAxisStartAtZero ) {
180  if ( isPositive ) {
181  ret.setLeft( 0.0 );
182  } else {
183  ret.setRight( 0.0 );
184  }
185  }
186  }
187  }
188  // ### this doesn't seem to take into account that Qt's y coordinate is inverted
189  if ( ( axesCalcModeY() != Logarithmic || r.bottom() < 0.0 ) && percentY > 0 && percentY != 100 ) {
190  const bool isPositive = r.bottom() >= 0;
191  if ( ( r.top() >= 0 ) == isPositive ) {
192  qreal upperBound = qMax( r.top(), r.bottom() );
193  qreal lowerBound = qMin( r.top(), r.bottom() );
194  const qreal innerBound = isPositive ? lowerBound : upperBound;
195  const qreal outerBound = isPositive ? upperBound : lowerBound;
196  if ( innerBound / outerBound * 100 <= percentY ) {
197  if ( isPositive ) {
198  ret.setBottom( 0.0 );
199  } else {
200  ret.setTop( 0.0 );
201  }
202  }
203  }
204  }
205  return ret;
206 }
207 
208 
209 QRectF CartesianCoordinatePlane::calculateRawDataBoundingRect() const
210 {
211  // are manually set ranges to be applied?
212  const bool bAutoAdjustHorizontalRange = d->autoAdjustHorizontalRangeToData < 100;
213  const bool bAutoAdjustVerticalRange = d->autoAdjustVerticalRangeToData < 100;
214 
215  const bool bHardHorizontalRange = (!bAutoAdjustHorizontalRange) && (d->horizontalMin != d->horizontalMax || (ISNAN(d->horizontalMin) != ISNAN(d->horizontalMax)));
216  const bool bHardVerticalRange = (!bAutoAdjustVerticalRange) && (d->verticalMin != d->verticalMax || (ISNAN(d->verticalMin) != ISNAN(d->verticalMax)));
217  QRectF dataBoundingRect;
218 
219  // if custom boundaries are set on the plane, use them
220  if ( bHardHorizontalRange && bHardVerticalRange ) {
221  dataBoundingRect.setLeft( d->horizontalMin );
222  dataBoundingRect.setRight( d->horizontalMax );
223  dataBoundingRect.setBottom( d->verticalMin );
224  dataBoundingRect.setTop( d->verticalMax );
225  } else {
226  // determine unit of the rectangles of all involved diagrams:
227  dataBoundingRect = getRawDataBoundingRectFromDiagrams();
228  if ( bHardHorizontalRange ) {
229  if (!ISNAN(d->horizontalMin))
230  dataBoundingRect.setLeft( d->horizontalMin );
231  if (!ISNAN(d->horizontalMax))
232  dataBoundingRect.setRight( d->horizontalMax );
233  }
234  if ( bHardVerticalRange ) {
235  if (!ISNAN(d->verticalMin))
236  dataBoundingRect.setBottom( d->verticalMin );
237  if (!ISNAN(d->verticalMax))
238  dataBoundingRect.setTop( d->verticalMax );
239  }
240  }
241  // recalculate the bounds, if automatic adjusting of ranges is desired AND
242  // both bounds are at the same side of the zero line
243  dataBoundingRect = adjustedToMaxEmptyInnerPercentage(
244  dataBoundingRect, d->autoAdjustHorizontalRangeToData, d->autoAdjustVerticalRangeToData );
245  if ( bAutoAdjustHorizontalRange ) {
246  const_cast<CartesianCoordinatePlane*>( this )->d->horizontalMin = dataBoundingRect.left();
247  const_cast<CartesianCoordinatePlane*>( this )->d->horizontalMax = dataBoundingRect.right();
248  }
249  if ( bAutoAdjustVerticalRange ) {
250  const_cast<CartesianCoordinatePlane*>( this )->d->verticalMin = dataBoundingRect.bottom();
251  const_cast<CartesianCoordinatePlane*>( this )->d->verticalMax = dataBoundingRect.top();
252  }
253  // qDebug() << Q_FUNC_INFO << dataBoundingRect;
254  return dataBoundingRect;
255 }
256 
257 
258 DataDimensionsList CartesianCoordinatePlane::getDataDimensionsList() const
259 {
260  const AbstractCartesianDiagram* dgr = diagrams().isEmpty() ? nullptr :
261  qobject_cast< const AbstractCartesianDiagram* >( diagrams().first() );
262  if ( dgr && dgr->referenceDiagram() ) {
263  dgr = dgr->referenceDiagram();
264  }
265  const BarDiagram *barDiagram = qobject_cast< const BarDiagram* >( dgr );
266  const StockDiagram *stockDiagram = qobject_cast< const StockDiagram* >( dgr );
267 
268  // note:
269  // It does make sense to retrieve the orientation from the first diagram. This is because
270  // a coordinate plane can either be for horizontal *or* for vertical diagrams. Both at the
271  // same time won't work, and thus the orientation for all diagrams is the same as for the first one.
272  const Qt::Orientation diagramOrientation = barDiagram != nullptr ? barDiagram->orientation() : Qt::Vertical;
273  const bool diagramIsVertical = diagramOrientation == Qt::Vertical;
274 
276  if ( dgr ) {
277  const QRectF r( calculateRawDataBoundingRect() );
278  // We do not access d->gridAttributesHorizontal/Vertical here, but we use the getter function,
279  // to get the global attrs, if no special ones have been set for the given orientation.
282  // append the first dimension: for Abscissa axes
283  l.append(
285  r.left(), r.right(),
286  diagramIsVertical ? ( !stockDiagram && dgr->datasetDimension() > 1 ) : true,
287  axesCalcModeX(),
288  gaH.gridGranularitySequence(),
289  gaH.gridStepWidth(),
290  gaH.gridSubStepWidth() ) );
291  // append the second dimension: for Ordinate axes
292  l.append(
294  r.bottom(), r.top(),
295  diagramIsVertical ? true : ( dgr->datasetDimension() > 1 ),
296  axesCalcModeY(),
297  gaV.gridGranularitySequence(),
298  gaV.gridStepWidth(),
299  gaV.gridSubStepWidth() ) );
300  } else {
301  l.append( DataDimension() ); // This gets us the default 1..0 / 1..0 grid
302  l.append( DataDimension() ); // shown, if there is no diagram on this plane.
303  }
304  return l;
305 }
306 
307 QRectF CartesianCoordinatePlane::drawingArea() const
308 {
309  // the rectangle the diagrams cover in the *plane*:
310  // We reserve 1px on each side for antialiased drawing, and respect the way QPainter calculates
311  // the width of a painted rect (the size is the rectangle size plus the pen width). The latter
312  // accounts for another pixel that we subtract from height and width.
313  // This way, most clipping for regular pens should be avoided. When pens with a width larger
314  // than 1 are used, this may not be sufficient.
315  return QRectF( areaGeometry() ).adjusted( 1.0, 1.0, -2.0, -2.0 );
316 }
317 
318 
320 {
321  if ( d->dimensions.isEmpty() )
322  return QRectF();
323 
324  const DataDimension dimX = d->dimensions.first();
325  const DataDimension dimY = d->dimensions.last();
326  const QPointF pt( qMin( dimX.start, dimX.end ), qMax( dimY.start, dimY.end ) );
327  const QSizeF siz( qAbs( dimX.distance() ), -qAbs( dimY.distance() ) );
328  const QRectF dataBoundingRect( pt, siz );
329 
330  // determine logical top left, taking the "reverse" options into account
331  const QPointF topLeft( d->reverseHorizontalPlane ? dataBoundingRect.right() : dataBoundingRect.left(),
332  d->reverseVerticalPlane ? dataBoundingRect.bottom() : dataBoundingRect.top() );
333 
334  const qreal width = dataBoundingRect.width() * ( d->reverseHorizontalPlane ? -1.0 : 1.0 );
335  const qreal height = dataBoundingRect.height() * ( d->reverseVerticalPlane ? -1.0 : 1.0 );
336 
337  return QRectF( topLeft, QSizeF( width, height ) );
338 }
339 
341 {
342  const QRectF logArea( logicalArea() );
343  QPointF physicalTopLeft = d->coordinateTransformation.translate( logArea.topLeft() );
344  QPointF physicalBottomRight = d->coordinateTransformation.translate( logArea.bottomRight() );
345 
346  return QRectF( physicalTopLeft, physicalBottomRight ).normalized();
347 }
348 
350 {
351  return diagramArea().intersected( drawingArea() );
352 }
353 
355 {
356  d->dimensions = gridDimensionsList();
357  Q_ASSERT_X ( d->dimensions.count() == 2, "CartesianCoordinatePlane::layoutDiagrams",
358  "Error: gridDimensionsList() did not return exactly two dimensions." );
359 
360  // physical area of the plane
361  const QRectF physicalArea( drawingArea() );
362  // .. in contrast to the logical area
363  const QRectF logArea( logicalArea() );
364 
365  // TODO: isometric scaling for zooming?
366 
367  // the plane area might have changed, so the zoom values might also be changed
368  handleFixedDataCoordinateSpaceRelation( physicalArea );
369 
370  d->coordinateTransformation.updateTransform( logArea, physicalArea );
371 
372  update();
373 }
374 
376 {
377  d->fixedDataCoordinateSpaceRelation = fixed;
378  d->fixedDataCoordinateSpaceRelationPinnedSize = QSize();
379  handleFixedDataCoordinateSpaceRelation( drawingArea() );
380 }
381 
382 bool CartesianCoordinatePlane::hasFixedDataCoordinateSpaceRelation() const
383 {
384  return d->fixedDataCoordinateSpaceRelation;
385 }
386 
388 {
389  if (d->xAxisStartAtZero == fixedStart)
390  return;
391 
392  d->xAxisStartAtZero = fixedStart;
393 }
394 
395 bool CartesianCoordinatePlane::xAxisStartAtZero() const
396 {
397  return d->xAxisStartAtZero;
398 }
399 
400 void CartesianCoordinatePlane::handleFixedDataCoordinateSpaceRelation( const QRectF& geometry )
401 {
402  if ( !d->fixedDataCoordinateSpaceRelation ) {
403  return;
404  }
405  // is the new geometry ok?
406  if ( !geometry.isValid() ) {
407  return;
408  }
409 
410  // note that the pinned size can be invalid even after setting it, if geometry wasn't valid.
411  // this is relevant for the cooperation between this method, setFixedDataCoordinateSpaceRelation(),
412  // and handleFixedDataCoordinateSpaceRelation().
413  if ( !d->fixedDataCoordinateSpaceRelationPinnedSize.isValid() ) {
414  d->fixedDataCoordinateSpaceRelationPinnedSize = geometry.size();
415  d->fixedDataCoordinateSpaceRelationPinnedZoom = ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() );
416  return;
417  }
418 
419  // if the plane size was changed, change zoom factors to keep the diagram size constant
420  if ( d->fixedDataCoordinateSpaceRelationPinnedSize != geometry.size() ) {
421  const qreal widthScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.width() / geometry.width();
422  const qreal heightScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.height() / geometry.height();
423 
424  const qreal newZoomX = d->fixedDataCoordinateSpaceRelationPinnedZoom.xFactor * widthScaling;
425  const qreal newZoomY = d->fixedDataCoordinateSpaceRelationPinnedZoom.yFactor * heightScaling;
426 
427  const QPointF newCenter = QPointF( d->fixedDataCoordinateSpaceRelationPinnedZoom.xCenter / widthScaling,
428  d->fixedDataCoordinateSpaceRelationPinnedZoom.yCenter / heightScaling );
429  // Use these internal methods to avoid sending the propertiesChanged signal more than once
430  bool changed = false;
431  if ( doneSetZoomFactorY( newZoomY ) )
432  changed = true;
433  if ( doneSetZoomFactorX( newZoomX ) )
434  changed = true;
435  if ( doneSetZoomCenter( newCenter ) )
436  changed = true;
437  if ( changed )
439  }
440 }
441 
442 const QPointF CartesianCoordinatePlane::translate( const QPointF& diagramPoint ) const
443 {
444  // Note: We do not test if the point lays inside of the data area,
445  // but we just apply the transformation calculations to the point.
446  // This allows for basic calculations done by the user, see e.g.
447  // the file examples/Lines/BubbleChart/mainwindow.cpp
448  return d->coordinateTransformation.translate( diagramPoint );
449 }
450 
451 const QPointF CartesianCoordinatePlane::translateBack( const QPointF& screenPoint ) const
452 {
453  return d->coordinateTransformation.translateBack( screenPoint );
454 }
455 
457 {
458  if ( d->isometricScaling != isOn ) {
459  d->isometricScaling = isOn;
460  layoutDiagrams();
462  }
463 }
464 
465 bool CartesianCoordinatePlane::doesIsometricScaling() const
466 {
467  return d->isometricScaling;
468 }
469 
470 bool CartesianCoordinatePlane::doneSetZoomFactorX( qreal factor )
471 {
472  if ( d->coordinateTransformation.zoom.xFactor == factor ) {
473  return false;
474  }
475  d->coordinateTransformation.zoom.xFactor = factor;
476  if ( d->autoAdjustGridToZoom ) {
477  d->grid->setNeedRecalculate();
478  }
479  return true;
480 }
481 
482 bool CartesianCoordinatePlane::doneSetZoomFactorY( qreal factor )
483 {
484  if ( d->coordinateTransformation.zoom.yFactor == factor ) {
485  return false;
486  }
487  d->coordinateTransformation.zoom.yFactor = factor;
488  if ( d->autoAdjustGridToZoom ) {
489  d->grid->setNeedRecalculate();
490  }
491  return true;
492 }
493 
494 bool CartesianCoordinatePlane::doneSetZoomCenter( const QPointF& point )
495 {
496  if ( d->coordinateTransformation.zoom.center() == point ) {
497  return false;
498  }
499  d->coordinateTransformation.zoom.setCenter( point );
500  if ( d->autoAdjustGridToZoom ) {
501  d->grid->setNeedRecalculate();
502  }
503  return true;
504 }
505 
506 void CartesianCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY )
507 {
508  if ( doneSetZoomFactorX( factorX ) || doneSetZoomFactorY( factorY ) ) {
509  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
511  }
512 }
513 
515 {
516  if ( doneSetZoomFactorX( factor ) ) {
517  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
519  }
520 }
521 
523 {
524  if ( doneSetZoomFactorY( factor ) ) {
525  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
527  }
528 }
529 
531 {
532  if ( doneSetZoomCenter( point ) ) {
533  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
535  }
536 }
537 
539 {
540  return d->coordinateTransformation.zoom.center();
541 }
542 
544 {
545  return d->coordinateTransformation.zoom.xFactor;
546 }
547 
549 {
550  return d->coordinateTransformation.zoom.yFactor;
551 }
552 
553 
554 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeY() const
555 {
556  return d->coordinateTransformation.axesCalcModeY;
557 }
558 
559 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeX() const
560 {
561  return d->coordinateTransformation.axesCalcModeX;
562 }
563 
565 {
566  if ( d->coordinateTransformation.axesCalcModeY != mode ||
567  d->coordinateTransformation.axesCalcModeX != mode ) {
568  d->coordinateTransformation.axesCalcModeY = mode;
569  d->coordinateTransformation.axesCalcModeX = mode;
572  const auto ds = diagrams();
573  for (AbstractDiagram* diag : ds)
574  slotLayoutChanged( diag );
575  }
576 }
577 
579 {
580  if ( d->coordinateTransformation.axesCalcModeY != mode ) {
581  d->coordinateTransformation.axesCalcModeY = mode;
585  }
586 }
587 
589 {
590  if ( d->coordinateTransformation.axesCalcModeX != mode ) {
591  d->coordinateTransformation.axesCalcModeX = mode;
594  }
595 }
596 
597 namespace {
598  inline bool fuzzyCompare( qreal a, qreal b )
599  {
600  if ( ISNAN(a) && ISNAN(b) )
601  return true;
602  if ( qFuzzyIsNull(a) && qFuzzyIsNull(b) )
603  return true;
604  return qFuzzyCompare( a, b );
605  }
606 }
607 
609 {
610  if ( !fuzzyCompare(d->horizontalMin, range.first) || !fuzzyCompare(d->horizontalMax, range.second) ) {
611  d->autoAdjustHorizontalRangeToData = 100;
612  d->horizontalMin = range.first;
613  d->horizontalMax = range.second;
614  layoutDiagrams();
616  Q_EMIT boundariesChanged();
617  }
618 }
619 
621 {
622  if ( !fuzzyCompare(d->verticalMin, range.first) || !fuzzyCompare(d->verticalMax, range.second) ) {
623  d->autoAdjustVerticalRangeToData = 100;
624  d->verticalMin = range.first;
625  d->verticalMax = range.second;
626  layoutDiagrams();
628  Q_EMIT boundariesChanged();
629  }
630 }
631 
633 {
634  return QPair<qreal, qreal>( d->horizontalMin, d->horizontalMax );
635 }
636 
638 {
639  return QPair<qreal, qreal>( d->verticalMin, d->verticalMax );
640 }
641 
643 {
644  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
645  d->horizontalMin = dataBoundingRect.left();
646  d->horizontalMax = dataBoundingRect.right();
647  d->verticalMin = dataBoundingRect.top();
648  d->verticalMax = dataBoundingRect.bottom();
649  layoutDiagrams();
651 }
652 
654 {
655  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
656  d->horizontalMin = dataBoundingRect.left();
657  d->horizontalMax = dataBoundingRect.right();
658  layoutDiagrams();
660 }
661 
663 {
664  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
665  d->verticalMin = dataBoundingRect.bottom();
666  d->verticalMax = dataBoundingRect.top();
667  layoutDiagrams();
669 }
670 
672 {
673  if ( d->autoAdjustHorizontalRangeToData != percentEmpty )
674  {
675  d->autoAdjustHorizontalRangeToData = percentEmpty;
676  d->horizontalMin = 0.0;
677  d->horizontalMax = 0.0;
678  layoutDiagrams();
680  }
681 }
682 
684 {
685  if ( d->autoAdjustVerticalRangeToData != percentEmpty )
686  {
687  d->autoAdjustVerticalRangeToData = percentEmpty;
688  d->verticalMin = 0.0;
689  d->verticalMax = 0.0;
690  layoutDiagrams();
692  }
693 }
694 
696 {
697  return d->autoAdjustHorizontalRangeToData;
698 }
699 
701 {
702  return d->autoAdjustVerticalRangeToData;
703 }
704 
706  Qt::Orientation orientation,
707  const GridAttributes& a )
708 {
709  if ( orientation == Qt::Horizontal )
710  d->gridAttributesHorizontal = a;
711  else
712  d->gridAttributesVertical = a;
713  setHasOwnGridAttributes( orientation, true );
714  update();
716 }
717 
719 {
720  setHasOwnGridAttributes( orientation, false );
721  update();
722 }
723 
725 {
726  if ( hasOwnGridAttributes( orientation ) ) {
727  if ( orientation == Qt::Horizontal )
728  return d->gridAttributesHorizontal;
729  else
730  return d->gridAttributesVertical;
731  } else {
732  return globalGridAttributes();
733  }
734 }
735 
736 void CartesianCoordinatePlane::setHasOwnGridAttributes( Qt::Orientation orientation, bool on )
737 {
738  if ( orientation == Qt::Horizontal )
739  d->hasOwnGridAttributesHorizontal = on;
740  else
741  d->hasOwnGridAttributesVertical = on;
743 }
744 
746 {
747  return orientation == Qt::Horizontal ? d->hasOwnGridAttributesHorizontal
748  : d->hasOwnGridAttributesVertical;
749 }
750 
752 {
753  if ( d->autoAdjustGridToZoom != autoAdjust ) {
754  d->autoAdjustGridToZoom = autoAdjust;
755  d->grid->setNeedRecalculate();
757  }
758 }
759 
760 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
761 const
762 #endif
764 {
765  return d->autoAdjustGridToZoom;
766 }
767 
769 {
770  CartesianCoordinatePlane* plane = this;
771  AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() );
772  const CartesianAxis* sharedAxis = nullptr;
773  if ( diag != nullptr )
774  {
775  const CartesianAxisList axes = diag->axes();
776  for ( const CartesianAxis* a : axes )
777  {
779  dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) );
780  if ( p != nullptr && p != this )
781  {
782  plane = p;
783  sharedAxis = a;
784  }
785  }
786  }
787 
788  if ( plane == this || painter == nullptr )
789  return plane;
790 
791  const QPointF zero = QPointF( 0, 0 );
792  const QPointF tenX = QPointF( 10, 0 );
793  const QPointF tenY = QPointF( 0, 10 );
794 
795 
796  if ( sharedAxis->isOrdinate() )
797  {
798  painter->translate( translate( zero ).x(), 0.0 );
799  const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x();
800  painter->scale( factor, 1.0 );
801  painter->translate( -plane->translate( zero ).x(), 0.0 );
802  }
803  if ( sharedAxis->isAbscissa() )
804  {
805  painter->translate( 0.0, translate( zero ).y() );
806  const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y();
807  painter->scale( 1.0, factor );
808  painter->translate( 0.0, -plane->translate( zero ).y() );
809  }
810 
811 
812  return plane;
813 }
814 
816 {
817  if ( d->reverseHorizontalPlane == reverse )
818  return;
819 
820  d->reverseHorizontalPlane = reverse;
821  layoutDiagrams();
823 }
824 
826 {
827  return d->reverseHorizontalPlane;
828 }
829 
831 {
832  if ( d->reverseVerticalPlane == reverse )
833  return;
834 
835  d->reverseVerticalPlane = reverse;
836  layoutDiagrams();
838 }
839 
841 {
842  return d->reverseVerticalPlane;
843 }
844 
846 {
847  QRectF result;
848 
849  const QRectF drawArea = drawingArea();
850 
851  result.setTopLeft( translateBack( drawArea.topLeft() ) );
852  result.setBottomRight( translateBack( drawArea.bottomRight() ) );
853 
854  return result;
855 }
856 
858 {
859  if ( rectangle == geometry() ) {
860  return;
861  }
862 
863  d->geometry = rectangle;
864  if ( d->isometricScaling ) {
865  const int hfw = heightForWidth( rectangle.width() );
866  // same scaling for x and y means a fixed aspect ratio, which is enforced here
867  // always shrink the too large dimension
868  if ( hfw < rectangle.height() ) {
869  d->geometry.setHeight( hfw );
870  } else {
871  d->geometry.setWidth( qRound( qreal( rectangle.width() ) *
872  qreal( rectangle.height() ) / qreal( hfw ) ) );
873  }
874  }
875 
877 
878  const auto ds = diagrams();
879  for (AbstractDiagram* diagram : ds) {
880  diagram->resize( d->geometry.size() );
881  }
882 }
883 
884 Qt::Orientations CartesianCoordinatePlane::expandingDirections() const
885 {
886  // not completely sure why this is required for isometric scaling...
887  return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical );
888 }
889 
890 bool CartesianCoordinatePlane::hasHeightForWidth() const
891 {
892  return d->isometricScaling;
893 }
894 
895 int CartesianCoordinatePlane::heightForWidth( int w ) const
896 {
897  // ### using anything for dataRect that depends on geometry will close a feedback loop which
898  // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on
899  // drawingArea(), and no good will come out of using it here.
900  QRectF dataRect = logicalArea();
901  return qRound( qreal( w ) * qAbs( qreal( dataRect.height() ) / qreal( dataRect.width() ) ) );
902 }
903 
904 QSize CartesianCoordinatePlane::sizeHint() const
905 {
907  if ( d->isometricScaling ) {
908  // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation
909  sh = d->geometry.size();
910  sh.setHeight( heightForWidth( sh.width() ) );
911  }
912  return sh;
913 }
void append(const T &value)
qreal distance() const
Returns the size of the distance, equivalent to the width() (or height(), resp.) of a QRectF.
qreal left() const const
Qt::Orientation orientation() const
void setBottomLeft(const QPointF &position)
const GridAttributes gridAttributes(Qt::Orientation orientation) const
QRect areaGeometry() const override
QSize size() const const
void addDiagram(AbstractDiagram *diagram) override
Adds a diagram to this coordinate plane.
Q_EMITQ_EMIT
void setAxesCalcModeX(AxesCalcMode mode)
Specifies the calculation mode for all Abscissa axes.
void setTopLeft(const QPointF &position)
QRect toRect() const const
AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr) override
reimpl
int width() const const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
void propertiesChanged()
Emitted upon change of a property of the Diagram.
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
Stores information about painting diagrams.
void viewportCoordinateSystemChanged()
Emitted upon change of the view coordinate system.
QRectF visibleDataRange() const
Returns the currently visible data range.
int width() const const
void setTopRight(const QPointF &position)
void setVerticalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in vertical direction.
QRectF diagramArea() const
Returns the (physical) area occupied by the diagram.
BarDiagram defines a common bar diagram.
void setLeft(qreal x)
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
void setGridAttributes(Qt::Orientation orientation, const GridAttributes &)
Set the attributes to be used for grid lines drawn in horizontal direction (or in vertical direction,...
void setBottom(qreal y)
void setGeometry(const QRect &r) override
reimplemented from AbstractCoordinatePlane
int size() const const
void setXAxisStartAtZero(bool fixedStart)
Allows to fix the lower bound of X axis to zero when diagram is in first quadrant.
void scale(qreal sx, qreal sy)
QPointF topLeft() const const
QPointF bottomRight() const const
ZoomParameters stores the center and the factor of zooming internally.
virtual KChart::CartesianAxisList axes() const
Orientation
void setBottomRight(const QPointF &position)
bool isValid() const const
QRectF logicalArea() const
Returns the logical area, i.e., the rectangle defined by the very top left and very bottom right coor...
void setHorizontalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in horizontal direction.
qreal bottom() const const
bool hasOwnGridAttributes(Qt::Orientation orientation) const
bool isEmpty() const const
QRect geometry() const override
pure virtual in QLayoutItem
virtual void addDiagram(AbstractDiagram *diagram)
Adds a diagram to this coordinate plane.
void setHorizontalRangeReversed(bool reverse)
Sets whether the horizontal range should be reversed or not, i.e.
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
void setRight(qreal x)
qint64 elapsed() const const
A set of attributes controlling the appearance of grids.
int datasetDimension() const
The dataset dimension of a diagram determines how many value dimensions it expects each datapoint to ...
virtual AbstractCartesianDiagram * referenceDiagram() const
qreal top() const const
qreal right() const const
void setVerticalRangeReversed(bool reverse)
Sets whether the vertical range should be reversed or not, i.e.
void setAutoAdjustGridToZoom(bool autoAdjust)
Disable / re-enable the built-in grid adjusting feature.
int height() const const
DataDimensionsList gridDimensionsList()
Returns the dimensions used for drawing the grid lines.
void setAxesCalcModes(AxesCalcMode mode)
Specifies the calculation modes for all axes.
qreal x() const const
qreal y() const const
void propertiesChanged()
Emitted upon change of a property of the Coordinate Plane or any of its components.
qreal width() const const
QSize sizeHint() const override
pure virtual in QLayoutItem
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
void translate(const QPointF &offset)
void update()
Calling update() on the plane triggers the global KChart::Chart::update()
void setAutoAdjustHorizontalRangeToData(unsigned int percentEmpty=67)
Automatically adjust horizontal range settings to the ranges covered by the model's values,...
The class for cartesian axes.
virtual void resize(const QSizeF &area)
Called by the widget's sizeEvent.
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
void setTop(qreal y)
void setIsometricScaling(bool onOff)
If onOff is true, enforce that X and Y distances are scaled by the same factor.
AbstractDiagram defines the interface for diagram classes.
void setAxesCalcModeY(AxesCalcMode mode)
Specifies the calculation mode for all Ordinate axes.
bool autoAdjustGridToZoom() const
Return the status of the built-in grid adjusting feature.
QRectF intersected(const QRectF &rectangle) const const
QRectF normalized() const const
void setAutoAdjustVerticalRangeToData(unsigned int percentEmpty=67)
Automatically adjust vertical range settings to the ranges covered by the model's values,...
void layoutDiagrams() override
Distribute the available space among the diagrams and axes.
void adjustHorizontalRangeToData()
Adjust horizontal range settings to the ranges covered by the model's data values.
Helper class for one dimension of data, e.g.
A chart with one or more diagrams.
Definition: KChartChart.h:84
void resetGridAttributes(Qt::Orientation orientation)
Reset the attributes to be used for grid lines drawn in horizontal direction (or in vertical directio...
void layoutChanged(KChart::AbstractDiagram *)
Diagrams are supposed to emit this signal, when the layout of one of their element changes.
void setFixedDataCoordinateSpaceRelation(bool fixed)
Allows to specify a fixed data-space / coordinate-space relation.
void setHeight(int height)
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
void setZoomCenter(const QPointF &center) override
void adjustVerticalRangeToData()
Adjust vertical range settings to the ranges covered by the model's data values.
void setGridNeedsRecalculate()
Used by the chart to clear the cached grid data.
QRectF visibleDiagramArea() const
Returns the visible part of the diagram area, i.e.
virtual QVariant get(ScriptableExtension *callerPrincipal, quint64 objId, const QString &propName)
void adjustRangesToData()
Adjust both, horizontal and vertical range settings to the ranges covered by the model's data values.
void setZoomFactors(qreal factorX, qreal factorY) override
Base class for diagrams based on a cartesian coordianate system.
qreal height() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Fri Jul 1 2022 05:09:19 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.