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

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/