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.
280  const GridAttributes gaH( gridAttributes( Qt::Horizontal ) );
281  const GridAttributes gaV( gridAttributes( Qt::Vertical ) );
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(),
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(),
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 )
438  Q_EMIT propertiesChanged();
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();
461  Q_EMIT propertiesChanged();
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() );
510  Q_EMIT propertiesChanged();
511  }
512 }
513 
515 {
516  if ( doneSetZoomFactorX( factor ) ) {
517  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
518  Q_EMIT propertiesChanged();
519  }
520 }
521 
523 {
524  if ( doneSetZoomFactorY( factor ) ) {
525  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
526  Q_EMIT propertiesChanged();
527  }
528 }
529 
531 {
532  if ( doneSetZoomCenter( point ) ) {
533  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
534  Q_EMIT propertiesChanged();
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;
570  Q_EMIT propertiesChanged();
571  Q_EMIT viewportCoordinateSystemChanged();
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;
582  Q_EMIT propertiesChanged();
583  setGridNeedsRecalculate();
584  Q_EMIT viewportCoordinateSystemChanged();
585  }
586 }
587 
589 {
590  if ( d->coordinateTransformation.axesCalcModeX != mode ) {
591  d->coordinateTransformation.axesCalcModeX = mode;
592  Q_EMIT propertiesChanged();
593  Q_EMIT viewportCoordinateSystemChanged();
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();
615  Q_EMIT propertiesChanged();
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();
627  Q_EMIT propertiesChanged();
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();
650  Q_EMIT propertiesChanged();
651 }
652 
654 {
655  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
656  d->horizontalMin = dataBoundingRect.left();
657  d->horizontalMax = dataBoundingRect.right();
658  layoutDiagrams();
659  Q_EMIT propertiesChanged();
660 }
661 
663 {
664  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
665  d->verticalMin = dataBoundingRect.bottom();
666  d->verticalMax = dataBoundingRect.top();
667  layoutDiagrams();
668  Q_EMIT propertiesChanged();
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();
679  Q_EMIT propertiesChanged();
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();
691  Q_EMIT propertiesChanged();
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();
715  Q_EMIT propertiesChanged();
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;
742  Q_EMIT propertiesChanged();
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();
756  Q_EMIT propertiesChanged();
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();
822  Q_EMIT propertiesChanged();
823 }
824 
826 {
827  return d->reverseHorizontalPlane;
828 }
829 
831 {
832  if ( d->reverseVerticalPlane == reverse )
833  return;
834 
835  d->reverseVerticalPlane = reverse;
836  layoutDiagrams();
837  Q_EMIT propertiesChanged();
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 }
Helper class for one dimension of data, e.g.
Qt::Orientation orientation() const
void setBottomLeft(const QPointF &position)
void setHeight(int height)
int width() const const
QRect toRect() const const
bool autoAdjustGridToZoom() const
Return the status of the built-in grid adjusting feature.
qreal gridSubStepWidth() const
Returns the sub-step width to be used for calculating the sub-grid lines.
AbstractDiagram defines the interface for diagram classes.
void setVerticalRangeReversed(bool reverse)
Sets whether the vertical range should be reversed or not, i.e.
void setAxesCalcModes(AxesCalcMode mode)
Specifies the calculation modes for all axes.
void setRight(qreal x)
void scale(qreal sx, qreal sy)
QSizeF size() const const
QRect areaGeometry() const override
bool hasOwnGridAttributes(Qt::Orientation orientation) const
qreal top() const const
int height() const const
void setAutoAdjustGridToZoom(bool autoAdjust)
Disable / re-enable the built-in grid adjusting feature.
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
QRectF intersected(const QRectF &rectangle) const const
void setIsometricScaling(bool onOff)
If onOff is true, enforce that X and Y distances are scaled by the same factor.
The class for cartesian axes.
qreal left() const const
void setLeft(qreal x)
int size() const const
void setAutoAdjustVerticalRangeToData(unsigned int percentEmpty=67)
Automatically adjust vertical range settings to the ranges covered by the model&#39;s values...
void layoutDiagrams() override
Distribute the available space among the diagrams and axes.
void setAutoAdjustHorizontalRangeToData(unsigned int percentEmpty=67)
Automatically adjust horizontal range settings to the ranges covered by the model&#39;s values...
void layoutChanged(KChart::AbstractDiagram *)
Diagrams are supposed to emit this signal, when the layout of one of their element changes...
qreal gridStepWidth() const
Returns the step width to be used for calculating the grid lines.
qreal bottom() const const
void propertiesChanged()
Emitted upon change of a property of the Diagram.
void propertiesChanged()
Emitted upon change of a property of the Coordinate Plane or any of its components.
const GridAttributes gridAttributes(Qt::Orientation orientation) const
QRectF visibleDiagramArea() const
Returns the visible part of the diagram area, i.e.
void setAxesCalcModeY(AxesCalcMode mode)
Specifies the calculation mode for all Ordinate axes.
qreal x() const const
qreal y() const const
void append(const T &value)
void adjustVerticalRangeToData()
Adjust vertical range settings to the ranges covered by the model&#39;s data values.
Stores information about painting diagrams.
void adjustHorizontalRangeToData()
Adjust horizontal range settings to the ranges covered by the model&#39;s data values.
void adjustRangesToData()
Adjust both, horizontal and vertical range settings to the ranges covered by the model&#39;s data values...
void setZoomFactors(qreal factorX, qreal factorY) override
QRectF normalized() const const
Base class for diagrams based on a cartesian coordianate system.
bool isEmpty() const const
void setFixedDataCoordinateSpaceRelation(bool fixed)
Allows to specify a fixed data-space / coordinate-space relation.
void resetGridAttributes(Qt::Orientation orientation)
Reset the attributes to be used for grid lines drawn in horizontal direction (or in vertical directio...
QPointF topLeft() const const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
ZoomParameters stores the center and the factor of zooming internally.
void setZoomCenter(const QPointF &center) override
QRectF logicalArea() const
Returns the logical area, i.e., the rectangle defined by the very top left and very bottom right coor...
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
virtual KChart::CartesianAxisList axes() const
void addDiagram(AbstractDiagram *diagram) override
Adds a diagram to this coordinate plane.
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
QSize sizeHint() const override
pure virtual in QLayoutItem
qreal right() const const
virtual void addDiagram(AbstractDiagram *diagram)
Adds a diagram to this coordinate plane.
QRect geometry() const override
pure virtual in QLayoutItem
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr) override
reimpl
void setTopLeft(const QPointF &position)
bool isValid() const const
void setAxesCalcModeX(AxesCalcMode mode)
Specifies the calculation mode for all Abscissa axes.
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
virtual int heightForWidth(int) const const
virtual void resize(const QSizeF &area)
Called by the widget&#39;s sizeEvent.
int width() const const
qreal width() const const
void setBottom(qreal y)
BarDiagram defines a common bar diagram.
void setTop(qreal y)
A set of attributes controlling the appearance of grids.
void setGridAttributes(Qt::Orientation orientation, const GridAttributes &)
Set the attributes to be used for grid lines drawn in horizontal direction (or in vertical direction...
qreal distance() const
Returns the size of the distance, equivalent to the width() (or height(), resp.) of a QRectF...
void translate(const QPointF &offset)
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.
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
qreal height() const const
void setXAxisStartAtZero(bool fixedStart)
Allows to fix the lower bound of X axis to zero when diagram is in first quadrant.
A chart with one or more diagrams.
Definition: KChartChart.h:84
Orientation
void setTopRight(const QPointF &position)
QPointF bottomRight() const const
void setGeometry(const QRect &r) override
reimplemented from AbstractCoordinatePlane
Global namespace.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
virtual AbstractCartesianDiagram * referenceDiagram() const
void setBottomRight(const QPointF &position)
qint64 elapsed() const const
int datasetDimension() const
The dataset dimension of a diagram determines how many value dimensions it expects each datapoint to ...
QPair< qreal, qreal > horizontalRange() const
QRectF visibleDataRange() const
Returns the currently visible data range.
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
KChartEnums::GranularitySequence gridGranularitySequence() const
Returns the granularity sequence to be used for calculating the grid lines.
Q_EMITQ_EMIT
void setHorizontalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in horizontal direction.
void setHorizontalRangeReversed(bool reverse)
Sets whether the horizontal range should be reversed or not, i.e.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jan 27 2022 22:33:22 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.