KD Chart 2  [rev.2.6]
KDChartCartesianCoordinatePlane.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2018 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  if ( !fuzzyCompare(d->horizontalMin, range.first) || !fuzzyCompare(d->horizontalMax, range.second) ) {
624  d->autoAdjustHorizontalRangeToData = 100;
625  d->horizontalMin = range.first;
626  d->horizontalMax = range.second;
627  layoutDiagrams();
628  emit propertiesChanged();
629  emit boundariesChanged();
630  }
631 }
632 
634 {
635  if ( !fuzzyCompare(d->verticalMin, range.first) || !fuzzyCompare(d->verticalMax, range.second) ) {
636  d->autoAdjustVerticalRangeToData = 100;
637  d->verticalMin = range.first;
638  d->verticalMax = range.second;
639  layoutDiagrams();
640  emit propertiesChanged();
641  emit boundariesChanged();
642  }
643 }
644 
646 {
647  return QPair<qreal, qreal>( d->horizontalMin, d->horizontalMax );
648 }
649 
651 {
652  return QPair<qreal, qreal>( d->verticalMin, d->verticalMax );
653 }
654 
656 {
657  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
658  d->horizontalMin = dataBoundingRect.left();
659  d->horizontalMax = dataBoundingRect.right();
660  d->verticalMin = dataBoundingRect.top();
661  d->verticalMax = dataBoundingRect.bottom();
662  layoutDiagrams();
663  emit propertiesChanged();
664 }
665 
667 {
668  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
669  d->horizontalMin = dataBoundingRect.left();
670  d->horizontalMax = dataBoundingRect.right();
671  layoutDiagrams();
672  emit propertiesChanged();
673 }
674 
676 {
677  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
678  d->verticalMin = dataBoundingRect.bottom();
679  d->verticalMax = dataBoundingRect.top();
680  layoutDiagrams();
681  emit propertiesChanged();
682 }
683 
685 {
686  if ( d->autoAdjustHorizontalRangeToData != percentEmpty )
687  {
688  d->autoAdjustHorizontalRangeToData = percentEmpty;
689  d->horizontalMin = 0.0;
690  d->horizontalMax = 0.0;
691  layoutDiagrams();
692  emit propertiesChanged();
693  }
694 }
695 
697 {
698  if ( d->autoAdjustVerticalRangeToData != percentEmpty )
699  {
700  d->autoAdjustVerticalRangeToData = percentEmpty;
701  d->verticalMin = 0.0;
702  d->verticalMax = 0.0;
703  layoutDiagrams();
704  emit propertiesChanged();
705  }
706 }
707 
709 {
710  return d->autoAdjustHorizontalRangeToData;
711 }
712 
714 {
715  return d->autoAdjustVerticalRangeToData;
716 }
717 
719  Qt::Orientation orientation,
720  const GridAttributes& a )
721 {
722  if ( orientation == Qt::Horizontal )
723  d->gridAttributesHorizontal = a;
724  else
725  d->gridAttributesVertical = a;
726  setHasOwnGridAttributes( orientation, true );
727  update();
728  emit propertiesChanged();
729 }
730 
731 void CartesianCoordinatePlane::resetGridAttributes( Qt::Orientation orientation )
732 {
733  setHasOwnGridAttributes( orientation, false );
734  update();
735 }
736 
737 const GridAttributes CartesianCoordinatePlane::gridAttributes( Qt::Orientation orientation ) const
738 {
739  if ( hasOwnGridAttributes( orientation ) ) {
740  if ( orientation == Qt::Horizontal )
741  return d->gridAttributesHorizontal;
742  else
743  return d->gridAttributesVertical;
744  } else {
745  return globalGridAttributes();
746  }
747 }
748 
749 void CartesianCoordinatePlane::setHasOwnGridAttributes( Qt::Orientation orientation, bool on )
750 {
751  if ( orientation == Qt::Horizontal )
752  d->hasOwnGridAttributesHorizontal = on;
753  else
754  d->hasOwnGridAttributesVertical = on;
755  emit propertiesChanged();
756 }
757 
758 bool CartesianCoordinatePlane::hasOwnGridAttributes( Qt::Orientation orientation ) const
759 {
760  return orientation == Qt::Horizontal ? d->hasOwnGridAttributesHorizontal
761  : d->hasOwnGridAttributesVertical;
762 }
763 
765 {
766  if ( d->autoAdjustGridToZoom != autoAdjust ) {
767  d->autoAdjustGridToZoom = autoAdjust;
768  d->grid->setNeedRecalculate();
769  emit propertiesChanged();
770  }
771 }
772 
773 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE)
774 const
775 #endif
777 {
778  return d->autoAdjustGridToZoom;
779 }
780 
782 {
783  CartesianCoordinatePlane* plane = this;
784  AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() );
785  const CartesianAxis* sharedAxis = 0;
786  if ( diag != 0 )
787  {
788  const CartesianAxisList axes = diag->axes();
789  KDAB_FOREACH( const CartesianAxis* a, axes )
790  {
792  dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) );
793  if ( p != 0 && p != this )
794  {
795  plane = p;
796  sharedAxis = a;
797  }
798  }
799  }
800 
801  if ( plane == this || painter == 0 )
802  return plane;
803 
804  const QPointF zero = QPointF( 0, 0 );
805  const QPointF tenX = QPointF( 10, 0 );
806  const QPointF tenY = QPointF( 0, 10 );
807 
808 
809  if ( sharedAxis->isOrdinate() )
810  {
811  painter->translate( translate( zero ).x(), 0.0 );
812  const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x();
813  painter->scale( factor, 1.0 );
814  painter->translate( -plane->translate( zero ).x(), 0.0 );
815  }
816  if ( sharedAxis->isAbscissa() )
817  {
818  painter->translate( 0.0, translate( zero ).y() );
819  const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y();
820  painter->scale( 1.0, factor );
821  painter->translate( 0.0, -plane->translate( zero ).y() );
822  }
823 
824 
825  return plane;
826 }
827 
829 {
830  if ( d->reverseHorizontalPlane == reverse )
831  return;
832 
833  d->reverseHorizontalPlane = reverse;
834  layoutDiagrams();
835  emit propertiesChanged();
836 }
837 
839 {
840  return d->reverseHorizontalPlane;
841 }
842 
844 {
845  if ( d->reverseVerticalPlane == reverse )
846  return;
847 
848  d->reverseVerticalPlane = reverse;
849  layoutDiagrams();
850  emit propertiesChanged();
851 }
852 
854 {
855  return d->reverseVerticalPlane;
856 }
857 
859 {
860  QRectF result;
861 
862  const QRectF drawArea = drawingArea();
863 
864  result.setTopLeft( translateBack( drawArea.topLeft() ) );
865  result.setBottomRight( translateBack( drawArea.bottomRight() ) );
866 
867  return result;
868 }
869 
870 void CartesianCoordinatePlane::setGeometry( const QRect& rectangle )
871 {
872  if ( rectangle == geometry() ) {
873  return;
874  }
875 
876  d->geometry = rectangle;
877  if ( d->isometricScaling ) {
878  const int hfw = heightForWidth( rectangle.width() );
879  // same scaling for x and y means a fixed aspect ratio, which is enforced here
880  // always shrink the too large dimension
881  if ( hfw < rectangle.height() ) {
882  d->geometry.setHeight( hfw );
883  } else {
884  d->geometry.setWidth( qRound( qreal( rectangle.width() ) *
885  qreal( rectangle.height() ) / qreal( hfw ) ) );
886  }
887  }
888 
890 
891  Q_FOREACH( AbstractDiagram* diagram, diagrams() ) {
892  diagram->resize( d->geometry.size() );
893  }
894 }
895 
897 {
898  // not completely sure why this is required for isometric scaling...
899  return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical );
900 }
901 
903 {
904  return d->isometricScaling;
905 }
906 
908 {
909  // ### using anything for dataRect that depends on geometry will close a feedback loop which
910  // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on
911  // drawingArea(), and no good will come out of using it here.
912  QRectF dataRect = logicalArea();
913  return qRound( qreal( w ) * qAbs( qreal( dataRect.height() ) / qreal( dataRect.width() ) ) );
914 }
915 
917 {
919  if ( d->isometricScaling ) {
920  // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation
921  sh = d->geometry.size();
922  sh.setHeight( heightForWidth( sh.width() ) );
923  }
924  return sh;
925 }
void handleFixedDataCoordinateSpaceRelation(const QRectF &geometry)
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
QSize sizeHint() const
pure virtual in QLayoutItem
Qt::Orientations expandingDirections() const
pure virtual in QLayoutItem
void setIsometricScaling(bool onOff)
If onOff is true, enforce that X and Y distances are scaled by the same factor.
virtual DataDimensionsList getDataDimensionsList() const
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)
const QPointF translate(const QPointF &diagramPoint) const
Translate the given point in value space coordinates to a position in pixel space.
void setHorizontalRangeReversed(bool reverse)
Sets whether the horizontal range should be reversed or not, i.e.
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.
const QPointF translateBack(const QPointF &screenPoint) const
const GridAttributes gridAttributes(Qt::Orientation orientation) const
virtual void setZoomFactors(qreal factorX, qreal factorY)
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 ...
qreal gridSubStepWidth() const
Returns the sub-step width to be used for calculating the sub-grid lines.
virtual QSize sizeHint() const
pure virtual in QLayoutItem
virtual QRect areaGeometry() const
AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=0)
reimpl
QRectF adjustedToMaxEmptyInnerPercentage(const QRectF &r, unsigned int percentX, unsigned int percentY) const
BarDiagram defines a common bar diagram.
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.
qreal gridStepWidth() const
Returns the step width to be used for calculating the grid lines.
virtual void setGeometry(const QRect &r)
pure virtual in QLayoutItem
virtual bool isOrdinate() const
virtual AbstractCartesianDiagram * referenceDiagram() const
virtual KDChart::CartesianAxisList axes() const
void addDiagram(AbstractDiagram *diagram)
Adds a diagram to this coordinate plane.
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.
virtual QRect geometry() const
pure virtual in QLayoutItem
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...
void layoutDiagrams()
Distribute the available space among the diagrams and axes.
A set of attributes controlling the appearance of grids.
void setGeometry(const QRect &r)
reimplemented from AbstractCoordinatePlane
DataDimensionsList gridDimensionsList()
Returns the dimensions used for drawing the grid lines.
void setAxesCalcModeX(AxesCalcMode mode)
Specifies the calculation mode for all Abscissa axes.
virtual void setZoomCenter(const QPointF &center)
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.
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...

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/