KD Chart 2  [rev.2.5.1]
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Macros Pages
KDChartCartesianCoordinatePlane.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2013 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 = d->horizontalMin != d->horizontalMax && !bAutoAdjustHorizontalRange;
230  const bool bHardVerticalRange = d->verticalMin != d->verticalMax && !bAutoAdjustVerticalRange;
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  dataBoundingRect.setLeft( d->horizontalMin );
244  dataBoundingRect.setRight( d->horizontalMax );
245  }
246  if ( bHardVerticalRange ) {
247  dataBoundingRect.setBottom( d->verticalMin );
248  dataBoundingRect.setTop( d->verticalMax );
249  }
250  }
251  // recalculate the bounds, if automatic adjusting of ranges is desired AND
252  // both bounds are at the same side of the zero line
253  dataBoundingRect = adjustedToMaxEmptyInnerPercentage(
254  dataBoundingRect, d->autoAdjustHorizontalRangeToData, d->autoAdjustVerticalRangeToData );
255  if ( bAutoAdjustHorizontalRange ) {
256  const_cast<CartesianCoordinatePlane*>( this )->d->horizontalMin = dataBoundingRect.left();
257  const_cast<CartesianCoordinatePlane*>( this )->d->horizontalMax = dataBoundingRect.right();
258  }
259  if ( bAutoAdjustVerticalRange ) {
260  const_cast<CartesianCoordinatePlane*>( this )->d->verticalMin = dataBoundingRect.bottom();
261  const_cast<CartesianCoordinatePlane*>( this )->d->verticalMax = dataBoundingRect.top();
262  }
263  // qDebug() << Q_FUNC_INFO << dataBoundingRect;
264  return dataBoundingRect;
265 }
266 
267 
269 {
270  const AbstractCartesianDiagram* dgr = diagrams().isEmpty() ? 0 :
271  qobject_cast< const AbstractCartesianDiagram* >( diagrams().first() );
272  if ( dgr && dgr->referenceDiagram() ) {
273  dgr = dgr->referenceDiagram();
274  }
275  const BarDiagram *barDiagram = qobject_cast< const BarDiagram* >( dgr );
276  const StockDiagram *stockDiagram = qobject_cast< const StockDiagram* >( dgr );
277 
278  // note:
279  // It does make sense to retrieve the orientation from the first diagram. This is because
280  // a coordinate plane can either be for horizontal *or* for vertical diagrams. Both at the
281  // same time won't work, and thus the orientation for all diagrams is the same as for the first one.
282  const Qt::Orientation diagramOrientation = barDiagram != 0 ? barDiagram->orientation() : Qt::Vertical;
283  const bool diagramIsVertical = diagramOrientation == Qt::Vertical;
284 
286  if ( dgr ) {
287  const QRectF r( calculateRawDataBoundingRect() );
288  // We do not access d->gridAttributesHorizontal/Vertical here, but we use the getter function,
289  // to get the global attrs, if no special ones have been set for the given orientation.
290  const GridAttributes gaH( gridAttributes( Qt::Horizontal ) );
291  const GridAttributes gaV( gridAttributes( Qt::Vertical ) );
292  // append the first dimension: for Abscissa axes
293  l.append(
295  r.left(), r.right(),
296  diagramIsVertical ? ( !stockDiagram && dgr->datasetDimension() > 1 ) : true,
297  axesCalcModeX(),
299  gaH.gridStepWidth(),
300  gaH.gridSubStepWidth() ) );
301  // append the second dimension: for Ordinate axes
302  l.append(
304  r.bottom(), r.top(),
305  diagramIsVertical ? true : ( dgr->datasetDimension() > 1 ),
306  axesCalcModeY(),
308  gaV.gridStepWidth(),
309  gaV.gridSubStepWidth() ) );
310  } else {
311  l.append( DataDimension() ); // This gets us the default 1..0 / 1..0 grid
312  l.append( DataDimension() ); // shown, if there is no diagram on this plane.
313  }
314  return l;
315 }
316 
318 {
319  // the rectangle the diagrams cover in the *plane*:
320  // We reserve 1px on each side for antialiased drawing, and respect the way QPainter calculates
321  // the width of a painted rect (the size is the rectangle size plus the pen width). The latter
322  // accounts for another pixel that we subtract from height and width.
323  // This way, most clipping for regular pens should be avoided. When pens with a width larger
324  // than 1 are used, this may not be sufficient.
325  return QRectF( areaGeometry() ).adjusted( 1.0, 1.0, -2.0, -2.0 );
326 }
327 
328 
330 {
331  if ( d->dimensions.isEmpty() )
332  return QRectF();
333 
334  const DataDimension dimX = d->dimensions.first();
335  const DataDimension dimY = d->dimensions.last();
336  const QPointF pt( qMin( dimX.start, dimX.end ), qMax( dimY.start, dimY.end ) );
337  const QSizeF siz( qAbs( dimX.distance() ), -qAbs( dimY.distance() ) );
338  const QRectF dataBoundingRect( pt, siz );
339 
340  // determine logical top left, taking the "reverse" options into account
341  const QPointF topLeft( d->reverseHorizontalPlane ? dataBoundingRect.right() : dataBoundingRect.left(),
342  d->reverseVerticalPlane ? dataBoundingRect.bottom() : dataBoundingRect.top() );
343 
344  const qreal width = dataBoundingRect.width() * ( d->reverseHorizontalPlane ? -1.0 : 1.0 );
345  const qreal height = dataBoundingRect.height() * ( d->reverseVerticalPlane ? -1.0 : 1.0 );
346 
347  return QRectF( topLeft, QSizeF( width, height ) );
348 }
349 
351 {
352  const QRectF logArea( logicalArea() );
353  QPointF physicalTopLeft = d->coordinateTransformation.translate( logArea.topLeft() );
354  QPointF physicalBottomRight = d->coordinateTransformation.translate( logArea.bottomRight() );
355 
356  return QRectF( physicalTopLeft, physicalBottomRight ).normalized();
357 }
358 
360 {
361  return diagramArea().intersected( drawingArea() );
362 }
363 
365 {
366  d->dimensions = gridDimensionsList();
367  Q_ASSERT_X ( d->dimensions.count() == 2, "CartesianCoordinatePlane::layoutDiagrams",
368  "Error: gridDimensionsList() did not return exactly two dimensions." );
369 
370  // physical area of the plane
371  const QRectF physicalArea( drawingArea() );
372  // .. in contrast to the logical area
373  const QRectF logArea( logicalArea() );
374 
375  // TODO: isometric scaling for zooming?
376 
377  // the plane area might have changed, so the zoom values might also be changed
379 
380  d->coordinateTransformation.updateTransform( logArea, physicalArea );
381 
382  update();
383 }
384 
386 {
387  d->fixedDataCoordinateSpaceRelation = fixed;
388  d->fixedDataCoordinateSpaceRelationPinnedSize = QSize();
390 }
391 
393 {
394  return d->fixedDataCoordinateSpaceRelation;
395 }
396 
398 {
399  if (d->xAxisStartAtZero == fixedStart)
400  return;
401 
402  d->xAxisStartAtZero = fixedStart;
403 }
404 
406 {
407  return d->xAxisStartAtZero;
408 }
409 
411 {
412  if ( !d->fixedDataCoordinateSpaceRelation ) {
413  return;
414  }
415  // is the new geometry ok?
416  if ( !geometry.isValid() ) {
417  return;
418  }
419 
420  // note that the pinned size can be invalid even after setting it, if geometry wasn't valid.
421  // this is relevant for the cooperation between this method, setFixedDataCoordinateSpaceRelation(),
422  // and handleFixedDataCoordinateSpaceRelation().
423  if ( !d->fixedDataCoordinateSpaceRelationPinnedSize.isValid() ) {
424  d->fixedDataCoordinateSpaceRelationPinnedSize = geometry.size();
425  d->fixedDataCoordinateSpaceRelationPinnedZoom = ZoomParameters( zoomFactorX(), zoomFactorY(), zoomCenter() );
426  return;
427  }
428 
429  // if the plane size was changed, change zoom factors to keep the diagram size constant
430  if ( d->fixedDataCoordinateSpaceRelationPinnedSize != geometry.size() ) {
431  const qreal widthScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.width() / geometry.width();
432  const qreal heightScaling = d->fixedDataCoordinateSpaceRelationPinnedSize.height() / geometry.height();
433 
434  const qreal newZoomX = d->fixedDataCoordinateSpaceRelationPinnedZoom.xFactor * widthScaling;
435  const qreal newZoomY = d->fixedDataCoordinateSpaceRelationPinnedZoom.yFactor * heightScaling;
436 
437  const QPointF newCenter = QPointF( d->fixedDataCoordinateSpaceRelationPinnedZoom.xCenter / widthScaling,
438  d->fixedDataCoordinateSpaceRelationPinnedZoom.yCenter / heightScaling );
439  // Use these internal methods to avoid sending the propertiesChanged signal more than once
440  bool changed = false;
441  if ( doneSetZoomFactorY( newZoomY ) )
442  changed = true;
443  if ( doneSetZoomFactorX( newZoomX ) )
444  changed = true;
445  if ( doneSetZoomCenter( newCenter ) )
446  changed = true;
447  if ( changed )
448  emit propertiesChanged();
449  }
450 }
451 
452 const QPointF CartesianCoordinatePlane::translate( const QPointF& diagramPoint ) const
453 {
454  // Note: We do not test if the point lays inside of the data area,
455  // but we just apply the transformation calculations to the point.
456  // This allows for basic calculations done by the user, see e.g.
457  // the file examples/Lines/BubbleChart/mainwindow.cpp
458  return d->coordinateTransformation.translate( diagramPoint );
459 }
460 
461 const QPointF CartesianCoordinatePlane::translateBack( const QPointF& screenPoint ) const
462 {
463  return d->coordinateTransformation.translateBack( screenPoint );
464 }
465 
467 {
468  if ( d->isometricScaling != isOn ) {
469  d->isometricScaling = isOn;
470  layoutDiagrams();
471  emit propertiesChanged();
472  }
473 }
474 
476 {
477  return d->isometricScaling;
478 }
479 
481 {
482  if ( d->coordinateTransformation.zoom.xFactor == factor ) {
483  return false;
484  }
485  d->coordinateTransformation.zoom.xFactor = factor;
486  if ( d->autoAdjustGridToZoom ) {
487  d->grid->setNeedRecalculate();
488  }
489  return true;
490 }
491 
493 {
494  if ( d->coordinateTransformation.zoom.yFactor == factor ) {
495  return false;
496  }
497  d->coordinateTransformation.zoom.yFactor = factor;
498  if ( d->autoAdjustGridToZoom ) {
499  d->grid->setNeedRecalculate();
500  }
501  return true;
502 }
503 
505 {
506  if ( d->coordinateTransformation.zoom.center() == point ) {
507  return false;
508  }
509  d->coordinateTransformation.zoom.setCenter( point );
510  if ( d->autoAdjustGridToZoom ) {
511  d->grid->setNeedRecalculate();
512  }
513  return true;
514 }
515 
516 void CartesianCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY )
517 {
518  if ( doneSetZoomFactorX( factorX ) || doneSetZoomFactorY( factorY ) ) {
519  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
520  emit propertiesChanged();
521  }
522 }
523 
525 {
526  if ( doneSetZoomFactorX( factor ) ) {
527  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
528  emit propertiesChanged();
529  }
530 }
531 
533 {
534  if ( doneSetZoomFactorY( factor ) ) {
535  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
536  emit propertiesChanged();
537  }
538 }
539 
540 void CartesianCoordinatePlane::setZoomCenter( const QPointF& point )
541 {
542  if ( doneSetZoomCenter( point ) ) {
543  d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() );
544  emit propertiesChanged();
545  }
546 }
547 
549 {
550  return d->coordinateTransformation.zoom.center();
551 }
552 
554 {
555  return d->coordinateTransformation.zoom.xFactor;
556 }
557 
559 {
560  return d->coordinateTransformation.zoom.yFactor;
561 }
562 
563 
565 {
566  return d->coordinateTransformation.axesCalcModeY;
567 }
568 
570 {
571  return d->coordinateTransformation.axesCalcModeX;
572 }
573 
575 {
576  if ( d->coordinateTransformation.axesCalcModeY != mode ||
577  d->coordinateTransformation.axesCalcModeX != mode ) {
578  d->coordinateTransformation.axesCalcModeY = mode;
579  d->coordinateTransformation.axesCalcModeX = mode;
580  emit propertiesChanged();
582  Q_FOREACH( AbstractDiagram* diag, diagrams() )
583  slotLayoutChanged( diag );
584  }
585 }
586 
588 {
589  if ( d->coordinateTransformation.axesCalcModeY != mode ) {
590  d->coordinateTransformation.axesCalcModeY = mode;
591  emit propertiesChanged();
594  }
595 }
596 
598 {
599  if ( d->coordinateTransformation.axesCalcModeX != mode ) {
600  d->coordinateTransformation.axesCalcModeX = mode;
601  emit propertiesChanged();
603  }
604 }
605 
607 {
608  if ( d->horizontalMin != range.first || d->horizontalMax != range.second ) {
609  d->autoAdjustHorizontalRangeToData = 100;
610  d->horizontalMin = range.first;
611  d->horizontalMax = range.second;
612  layoutDiagrams();
613  emit propertiesChanged();
614  emit boundariesChanged();
615  }
616 }
617 
619 {
620 
621  if ( d->verticalMin != range.first || d->verticalMax != range.second ) {
622  d->autoAdjustVerticalRangeToData = 100;
623  d->verticalMin = range.first;
624  d->verticalMax = range.second;
625  layoutDiagrams();
626  emit propertiesChanged();
627  emit boundariesChanged();
628  }
629 }
630 
632 {
633  QRectF visibleRange = visibleDataRange();
634  return QPair<qreal, qreal>( visibleRange.left(), visibleRange.right() );
635 }
636 
638 {
639  QRectF visibleRange = visibleDataRange();
640  return QPair<qreal, qreal>( visibleRange.bottom(), visibleRange.top() );
641 }
642 
644 {
645  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
646  d->horizontalMin = dataBoundingRect.left();
647  d->horizontalMax = dataBoundingRect.right();
648  d->verticalMin = dataBoundingRect.top();
649  d->verticalMax = dataBoundingRect.bottom();
650  layoutDiagrams();
651  emit propertiesChanged();
652 }
653 
655 {
656  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
657  d->horizontalMin = dataBoundingRect.left();
658  d->horizontalMax = dataBoundingRect.right();
659  layoutDiagrams();
660  emit propertiesChanged();
661 }
662 
664 {
665  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
666  d->verticalMin = dataBoundingRect.bottom();
667  d->verticalMax = dataBoundingRect.top();
668  layoutDiagrams();
669  emit propertiesChanged();
670 }
671 
673 {
674  if ( d->autoAdjustHorizontalRangeToData != percentEmpty )
675  {
676  d->autoAdjustHorizontalRangeToData = percentEmpty;
677  d->horizontalMin = 0.0;
678  d->horizontalMax = 0.0;
679  layoutDiagrams();
680  emit propertiesChanged();
681  }
682 }
683 
685 {
686  if ( d->autoAdjustVerticalRangeToData != percentEmpty )
687  {
688  d->autoAdjustVerticalRangeToData = percentEmpty;
689  d->verticalMin = 0.0;
690  d->verticalMax = 0.0;
691  layoutDiagrams();
692  emit propertiesChanged();
693  }
694 }
695 
697 {
698  return d->autoAdjustHorizontalRangeToData;
699 }
700 
702 {
703  return d->autoAdjustVerticalRangeToData;
704 }
705 
707  Qt::Orientation orientation,
708  const GridAttributes& a )
709 {
710  if ( orientation == Qt::Horizontal )
711  d->gridAttributesHorizontal = a;
712  else
713  d->gridAttributesVertical = a;
714  setHasOwnGridAttributes( orientation, true );
715  update();
716  emit propertiesChanged();
717 }
718 
720  Qt::Orientation orientation )
721 {
722  setHasOwnGridAttributes( orientation, false );
723  update();
724 }
725 
727  Qt::Orientation orientation ) const
728 {
729  if ( hasOwnGridAttributes( orientation ) ) {
730  if ( orientation == Qt::Horizontal )
731  return d->gridAttributesHorizontal;
732  else
733  return d->gridAttributesVertical;
734  } else {
735  return globalGridAttributes();
736  }
737 }
738 
739 void CartesianCoordinatePlane::setHasOwnGridAttributes(
740  Qt::Orientation orientation, bool on )
741 {
742  if ( orientation == Qt::Horizontal )
743  d->hasOwnGridAttributesHorizontal = on;
744  else
745  d->hasOwnGridAttributesVertical = on;
746  emit propertiesChanged();
747 }
748 
750  Qt::Orientation orientation ) const
751 {
752  return
753  ( orientation == Qt::Horizontal )
754  ? d->hasOwnGridAttributesHorizontal
755  : d->hasOwnGridAttributesVertical;
756 }
757 
759 {
760  if ( d->autoAdjustGridToZoom != autoAdjust ) {
761  d->autoAdjustGridToZoom = autoAdjust;
762  d->grid->setNeedRecalculate();
763  emit propertiesChanged();
764  }
765 }
766 
767 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE)
768 const
769 #endif
771 {
772  return d->autoAdjustGridToZoom;
773 }
774 
776 {
777  CartesianCoordinatePlane* plane = this;
778  AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() );
779  const CartesianAxis* sharedAxis = 0;
780  if ( diag != 0 )
781  {
782  const CartesianAxisList axes = diag->axes();
783  KDAB_FOREACH( const CartesianAxis* a, axes )
784  {
786  dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) );
787  if ( p != 0 && p != this )
788  {
789  plane = p;
790  sharedAxis = a;
791  }
792  }
793  }
794 
795  if ( plane == this || painter == 0 )
796  return plane;
797 
798  const QPointF zero = QPointF( 0, 0 );
799  const QPointF tenX = QPointF( 10, 0 );
800  const QPointF tenY = QPointF( 0, 10 );
801 
802 
803  if ( sharedAxis->isOrdinate() )
804  {
805  painter->translate( translate( zero ).x(), 0.0 );
806  const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x();
807  painter->scale( factor, 1.0 );
808  painter->translate( -plane->translate( zero ).x(), 0.0 );
809  }
810  if ( sharedAxis->isAbscissa() )
811  {
812  painter->translate( 0.0, translate( zero ).y() );
813  const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y();
814  painter->scale( 1.0, factor );
815  painter->translate( 0.0, -plane->translate( zero ).y() );
816  }
817 
818 
819  return plane;
820 }
821 
823 {
824  if ( d->reverseHorizontalPlane == reverse )
825  return;
826 
827  d->reverseHorizontalPlane = reverse;
828  layoutDiagrams();
829  emit propertiesChanged();
830 }
831 
833 {
834  return d->reverseHorizontalPlane;
835 }
836 
838 {
839  if ( d->reverseVerticalPlane == reverse )
840  return;
841 
842  d->reverseVerticalPlane = reverse;
843  layoutDiagrams();
844  emit propertiesChanged();
845 }
846 
848 {
849  return d->reverseVerticalPlane;
850 }
851 
853 {
854  QRectF result;
855 
856  const QRectF drawArea = drawingArea();
857 
858  result.setTopLeft( translateBack( drawArea.topLeft() ) );
859  result.setBottomRight( translateBack( drawArea.bottomRight() ) );
860 
861  return result;
862 }
863 
864 void CartesianCoordinatePlane::setGeometry( const QRect& rectangle )
865 {
866  if ( rectangle == geometry() ) {
867  return;
868  }
869 
870  d->geometry = rectangle;
871  if ( d->isometricScaling ) {
872  const int hfw = heightForWidth( rectangle.width() );
873  // same scaling for x and y means a fixed aspect ratio, which is enforced here
874  // always shrink the too large dimension
875  if ( hfw < rectangle.height() ) {
876  d->geometry.setHeight( hfw );
877  } else {
878  d->geometry.setWidth( qRound( qreal( rectangle.width() ) *
879  qreal( rectangle.height() ) / qreal( hfw ) ) );
880  }
881  }
882 
884 
885  Q_FOREACH( AbstractDiagram* diagram, diagrams() ) {
886  diagram->resize( d->geometry.size() );
887  }
888 }
889 
891 {
892  // not completely sure why this is required for isometric scaling...
893  return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical );
894 }
895 
897 {
898  return d->isometricScaling;
899 }
900 
902 {
903  // ### using anything for dataRect that depends on geometry will close a feedback loop which
904  // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on
905  // drawingArea(), and no good will come out of using it here.
906  QRectF dataRect = logicalArea();
907  return qRound( qreal( w ) * qAbs( qreal( dataRect.height() ) / qreal( dataRect.width() ) ) );
908 }
909 
911 {
913  if ( d->isometricScaling ) {
914  // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation
915  sh = d->geometry.size();
916  sh.setHeight( heightForWidth( sh.width() ) );
917  }
918  return sh;
919 }

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