KChart

KChartCartesianCoordinatePlane.cpp
1 /*
2  * Copyright (C) 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License as
8  * published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program. If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #include "KChartCartesianCoordinatePlane.h"
21 #include "KChartCartesianCoordinatePlane_p.h"
22 
23 #include "KChartAbstractDiagram.h"
24 #include "KChartAbstractDiagram_p.h"
25 #include "KChartAbstractCartesianDiagram.h"
26 #include "CartesianCoordinateTransformation.h"
27 #include "KChartGridAttributes.h"
28 #include "KChartPaintContext.h"
29 #include "KChartPainterSaver_p.h"
30 #include "KChartBarDiagram.h"
31 #include "KChartStockDiagram.h"
32 
33 #include <QApplication>
34 #include <QFont>
35 #include <QList>
36 #include <QtDebug>
37 #include <QPainter>
38 #include <QTime>
39 #include <QElapsedTimer>
40 
41 
42 using namespace KChart;
43 
44 #define d d_func()
45 
46 CartesianCoordinatePlane::Private::Private()
47  : AbstractCoordinatePlane::Private()
48  , bPaintIsRunning( false )
49  , hasOwnGridAttributesHorizontal( false )
50  , hasOwnGridAttributesVertical( false )
51  , isometricScaling( false )
52  , horizontalMin( 0 )
53  , horizontalMax( 0 )
54  , verticalMin( 0 )
55  , verticalMax( 0 )
56  , autoAdjustHorizontalRangeToData( 67 )
57  , autoAdjustVerticalRangeToData( 67 )
58  , autoAdjustGridToZoom( true )
59  , fixedDataCoordinateSpaceRelation( false )
60  , xAxisStartAtZero( true )
61  , reverseVerticalPlane( false )
62  , reverseHorizontalPlane( false )
63 {
64 }
65 
66 CartesianCoordinatePlane::CartesianCoordinatePlane( Chart* parent )
67  : AbstractCoordinatePlane( new Private(), parent )
68 {
69  // this bloc left empty intentionally
70 }
71 
72 CartesianCoordinatePlane::~CartesianCoordinatePlane()
73 {
74  // this bloc left empty intentionally
75 }
76 
77 void CartesianCoordinatePlane::init()
78 {
79  // this bloc left empty intentionally
80 }
81 
82 
84 {
85  Q_ASSERT_X( dynamic_cast<AbstractCartesianDiagram*>( diagram ),
86  "CartesianCoordinatePlane::addDiagram", "Only cartesian "
87  "diagrams can be added to a cartesian coordinate plane!" );
89  connect( diagram, SIGNAL(layoutChanged(AbstractDiagram*)),
90  SLOT(slotLayoutChanged(AbstractDiagram*)) );
91 
92  connect( diagram, SIGNAL(propertiesChanged()), this, SIGNAL(propertiesChanged()) );
93 }
94 
95 
97 {
98  // prevent recursive call:
99  if ( d->bPaintIsRunning ) {
100  return;
101  }
102  d->bPaintIsRunning = true;
103 
104  AbstractDiagramList diags = diagrams();
105  if ( !diags.isEmpty() )
106  {
107  PaintContext ctx;
108  ctx.setPainter ( painter );
109  ctx.setCoordinatePlane ( this );
110  const QRectF drawArea( drawingArea() );
111  ctx.setRectangle ( drawArea );
112 
113  // enabling clipping so that we're not drawing outside
114  PainterSaver painterSaver( painter );
115  QRect clipRect = drawArea.toRect().adjusted( -1, -1, 1, 1 );
116  QRegion clipRegion( clipRect );
117  painter->setClipRegion( clipRegion );
118 
119  // paint the coordinate system rulers:
120  d->grid->drawGrid( &ctx );
121 
122  // paint the diagrams:
123  for ( int i = 0; i < diags.size(); i++ )
124  {
125  if ( diags[i]->isHidden() ) {
126  continue;
127  }
128  bool doDumpPaintTime = AbstractDiagram::Private::get( diags[ i ] )->doDumpPaintTime;
129  QElapsedTimer stopWatch;
130  if ( doDumpPaintTime ) {
131  stopWatch.start();
132  }
133 
134  PainterSaver diagramPainterSaver( painter );
135  diags[i]->paint( &ctx );
136 
137  if ( doDumpPaintTime ) {
138  qDebug() << "Painting diagram" << i << "took" << stopWatch.elapsed() << "milliseconds";
139  }
140  }
141 
142  }
143  d->bPaintIsRunning = false;
144 }
145 
146 
147 void CartesianCoordinatePlane::slotLayoutChanged( AbstractDiagram* )
148 {
149  layoutDiagrams();
150 }
151 
152 QRectF CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams() const
153 {
154  // determine unit of the rectangles of all involved diagrams:
155  qreal minX = 0;
156  qreal maxX = 0;
157  qreal minY = 0;
158  qreal maxY = 0;
159  bool bStarting = true;
160  Q_FOREACH( const AbstractDiagram* diagram, diagrams() )
161  {
162  QPair<QPointF, QPointF> dataBoundariesPair = diagram->dataBoundaries();
163  //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\ngets diagram->dataBoundaries: " << dataBoundariesPair.first << dataBoundariesPair.second;
164  if ( bStarting || dataBoundariesPair.first.x() < minX ) minX = dataBoundariesPair.first.x();
165  if ( bStarting || dataBoundariesPair.first.y() < minY ) minY = dataBoundariesPair.first.y();
166  if ( bStarting || dataBoundariesPair.second.x() > maxX ) maxX = dataBoundariesPair.second.x();
167  if ( bStarting || dataBoundariesPair.second.y() > maxY ) maxY = dataBoundariesPair.second.y();
168  bStarting = false;
169  }
170  //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\nreturns data boundaries: " << QRectF( QPointF(minX, minY), QSizeF(maxX - minX, maxY - minY) );
171  QRectF dataBoundingRect;
172  dataBoundingRect.setBottomLeft( QPointF( minX, minY ) );
173  dataBoundingRect.setTopRight( QPointF( maxX, maxY ) );
174  return dataBoundingRect;
175 }
176 
177 
178 QRectF CartesianCoordinatePlane::adjustedToMaxEmptyInnerPercentage(
179  const QRectF& r, unsigned int percentX, unsigned int percentY ) const
180 {
181  QRectF ret = r;
182  if ( ( axesCalcModeX() != Logarithmic || r.left() < 0.0 ) && percentX > 0 && percentX != 100 ) {
183  const bool isPositive = r.left() >= 0;
184  if ( ( r.right() >= 0 ) == isPositive ) {
185  qreal upperBound = qMax( r.left(), r.right() );
186  qreal lowerBound = qMin( r.left(), r.right() );
187  qreal innerBound = isPositive ? lowerBound : upperBound;
188  qreal outerBound = isPositive ? upperBound : lowerBound;
189  if ( innerBound / outerBound * 100 <= percentX && d->xAxisStartAtZero ) {
190  if ( isPositive ) {
191  ret.setLeft( 0.0 );
192  } else {
193  ret.setRight( 0.0 );
194  }
195  }
196  }
197  }
198  // ### this doesn't seem to take into account that Qt's y coordinate is inverted
199  if ( ( axesCalcModeY() != Logarithmic || r.bottom() < 0.0 ) && percentY > 0 && percentY != 100 ) {
200  const bool isPositive = r.bottom() >= 0;
201  if ( ( r.top() >= 0 ) == isPositive ) {
202  qreal upperBound = qMax( r.top(), r.bottom() );
203  qreal lowerBound = qMin( r.top(), r.bottom() );
204  const qreal innerBound = isPositive ? lowerBound : upperBound;
205  const qreal outerBound = isPositive ? upperBound : lowerBound;
206  if ( innerBound / outerBound * 100 <= percentY ) {
207  if ( isPositive ) {
208  ret.setBottom( 0.0 );
209  } else {
210  ret.setTop( 0.0 );
211  }
212  }
213  }
214  }
215  return ret;
216 }
217 
218 
219 QRectF CartesianCoordinatePlane::calculateRawDataBoundingRect() const
220 {
221  // are manually set ranges to be applied?
222  const bool bAutoAdjustHorizontalRange = d->autoAdjustHorizontalRangeToData < 100;
223  const bool bAutoAdjustVerticalRange = d->autoAdjustVerticalRangeToData < 100;
224 
225  const bool bHardHorizontalRange = (!bAutoAdjustHorizontalRange) && (d->horizontalMin != d->horizontalMax || (ISNAN(d->horizontalMin) != ISNAN(d->horizontalMax)));
226  const bool bHardVerticalRange = (!bAutoAdjustVerticalRange) && (d->verticalMin != d->verticalMax || (ISNAN(d->verticalMin) != ISNAN(d->verticalMax)));
227  QRectF dataBoundingRect;
228 
229  // if custom boundaries are set on the plane, use them
230  if ( bHardHorizontalRange && bHardVerticalRange ) {
231  dataBoundingRect.setLeft( d->horizontalMin );
232  dataBoundingRect.setRight( d->horizontalMax );
233  dataBoundingRect.setBottom( d->verticalMin );
234  dataBoundingRect.setTop( d->verticalMax );
235  } else {
236  // determine unit of the rectangles of all involved diagrams:
237  dataBoundingRect = getRawDataBoundingRectFromDiagrams();
238  if ( bHardHorizontalRange ) {
239  if (!ISNAN(d->horizontalMin))
240  dataBoundingRect.setLeft( d->horizontalMin );
241  if (!ISNAN(d->horizontalMax))
242  dataBoundingRect.setRight( d->horizontalMax );
243  }
244  if ( bHardVerticalRange ) {
245  if (!ISNAN(d->verticalMin))
246  dataBoundingRect.setBottom( d->verticalMin );
247  if (!ISNAN(d->verticalMax))
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 
268 DataDimensionsList CartesianCoordinatePlane::getDataDimensionsList() const
269 {
270  const AbstractCartesianDiagram* dgr = diagrams().isEmpty() ? nullptr :
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 != nullptr ? 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 
317 QRectF CartesianCoordinatePlane::drawingArea() const
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
378  handleFixedDataCoordinateSpaceRelation( physicalArea );
379 
380  d->coordinateTransformation.updateTransform( logArea, physicalArea );
381 
382  update();
383 }
384 
386 {
387  d->fixedDataCoordinateSpaceRelation = fixed;
388  d->fixedDataCoordinateSpaceRelationPinnedSize = QSize();
389  handleFixedDataCoordinateSpaceRelation( drawingArea() );
390 }
391 
392 bool CartesianCoordinatePlane::hasFixedDataCoordinateSpaceRelation() const
393 {
394  return d->fixedDataCoordinateSpaceRelation;
395 }
396 
398 {
399  if (d->xAxisStartAtZero == fixedStart)
400  return;
401 
402  d->xAxisStartAtZero = fixedStart;
403 }
404 
405 bool CartesianCoordinatePlane::xAxisStartAtZero() const
406 {
407  return d->xAxisStartAtZero;
408 }
409 
410 void CartesianCoordinatePlane::handleFixedDataCoordinateSpaceRelation( const QRectF& geometry )
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 
475 bool CartesianCoordinatePlane::doesIsometricScaling() const
476 {
477  return d->isometricScaling;
478 }
479 
480 bool CartesianCoordinatePlane::doneSetZoomFactorX( qreal factor )
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 
492 bool CartesianCoordinatePlane::doneSetZoomFactorY( qreal factor )
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 
504 bool CartesianCoordinatePlane::doneSetZoomCenter( const QPointF& point )
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 
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 
564 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeY() const
565 {
566  return d->coordinateTransformation.axesCalcModeY;
567 }
568 
569 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeX() const
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();
581  emit viewportCoordinateSystemChanged();
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();
592  setGridNeedsRecalculate();
593  emit viewportCoordinateSystemChanged();
594  }
595 }
596 
598 {
599  if ( d->coordinateTransformation.axesCalcModeX != mode ) {
600  d->coordinateTransformation.axesCalcModeX = mode;
601  emit propertiesChanged();
602  emit viewportCoordinateSystemChanged();
603  }
604 }
605 
606 namespace {
607  inline bool fuzzyCompare( qreal a, qreal b )
608  {
609  if ( ISNAN(a) && ISNAN(b) )
610  return true;
611  if ( qFuzzyIsNull(a) && qFuzzyIsNull(b) )
612  return true;
613  return qFuzzyCompare( a, b );
614  }
615 }
616 
618 {
619  if ( !fuzzyCompare(d->horizontalMin, range.first) || !fuzzyCompare(d->horizontalMax, range.second) ) {
620  d->autoAdjustHorizontalRangeToData = 100;
621  d->horizontalMin = range.first;
622  d->horizontalMax = range.second;
623  layoutDiagrams();
624  emit propertiesChanged();
625  emit boundariesChanged();
626  }
627 }
628 
630 {
631  if ( !fuzzyCompare(d->verticalMin, range.first) || !fuzzyCompare(d->verticalMax, range.second) ) {
632  d->autoAdjustVerticalRangeToData = 100;
633  d->verticalMin = range.first;
634  d->verticalMax = range.second;
635  layoutDiagrams();
636  emit propertiesChanged();
637  emit boundariesChanged();
638  }
639 }
640 
642 {
643  return QPair<qreal, qreal>( d->horizontalMin, d->horizontalMax );
644 }
645 
647 {
648  return QPair<qreal, qreal>( d->verticalMin, d->verticalMax );
649 }
650 
652 {
653  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
654  d->horizontalMin = dataBoundingRect.left();
655  d->horizontalMax = dataBoundingRect.right();
656  d->verticalMin = dataBoundingRect.top();
657  d->verticalMax = dataBoundingRect.bottom();
658  layoutDiagrams();
659  emit propertiesChanged();
660 }
661 
663 {
664  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
665  d->horizontalMin = dataBoundingRect.left();
666  d->horizontalMax = dataBoundingRect.right();
667  layoutDiagrams();
668  emit propertiesChanged();
669 }
670 
672 {
673  const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() );
674  d->verticalMin = dataBoundingRect.bottom();
675  d->verticalMax = dataBoundingRect.top();
676  layoutDiagrams();
677  emit propertiesChanged();
678 }
679 
681 {
682  if ( d->autoAdjustHorizontalRangeToData != percentEmpty )
683  {
684  d->autoAdjustHorizontalRangeToData = percentEmpty;
685  d->horizontalMin = 0.0;
686  d->horizontalMax = 0.0;
687  layoutDiagrams();
688  emit propertiesChanged();
689  }
690 }
691 
693 {
694  if ( d->autoAdjustVerticalRangeToData != percentEmpty )
695  {
696  d->autoAdjustVerticalRangeToData = percentEmpty;
697  d->verticalMin = 0.0;
698  d->verticalMax = 0.0;
699  layoutDiagrams();
700  emit propertiesChanged();
701  }
702 }
703 
705 {
706  return d->autoAdjustHorizontalRangeToData;
707 }
708 
710 {
711  return d->autoAdjustVerticalRangeToData;
712 }
713 
715  Qt::Orientation orientation,
716  const GridAttributes& a )
717 {
718  if ( orientation == Qt::Horizontal )
719  d->gridAttributesHorizontal = a;
720  else
721  d->gridAttributesVertical = a;
722  setHasOwnGridAttributes( orientation, true );
723  update();
724  emit propertiesChanged();
725 }
726 
728 {
729  setHasOwnGridAttributes( orientation, false );
730  update();
731 }
732 
734 {
735  if ( hasOwnGridAttributes( orientation ) ) {
736  if ( orientation == Qt::Horizontal )
737  return d->gridAttributesHorizontal;
738  else
739  return d->gridAttributesVertical;
740  } else {
741  return globalGridAttributes();
742  }
743 }
744 
745 void CartesianCoordinatePlane::setHasOwnGridAttributes( Qt::Orientation orientation, bool on )
746 {
747  if ( orientation == Qt::Horizontal )
748  d->hasOwnGridAttributesHorizontal = on;
749  else
750  d->hasOwnGridAttributesVertical = on;
751  emit propertiesChanged();
752 }
753 
755 {
756  return orientation == Qt::Horizontal ? d->hasOwnGridAttributesHorizontal
757  : d->hasOwnGridAttributesVertical;
758 }
759 
761 {
762  if ( d->autoAdjustGridToZoom != autoAdjust ) {
763  d->autoAdjustGridToZoom = autoAdjust;
764  d->grid->setNeedRecalculate();
765  emit propertiesChanged();
766  }
767 }
768 
769 #if defined(Q_COMPILER_MANGLES_RETURN_TYPE)
770 const
771 #endif
773 {
774  return d->autoAdjustGridToZoom;
775 }
776 
778 {
779  CartesianCoordinatePlane* plane = this;
780  AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() );
781  const CartesianAxis* sharedAxis = nullptr;
782  if ( diag != nullptr )
783  {
784  const CartesianAxisList axes = diag->axes();
785  Q_FOREACH( const CartesianAxis* a, axes )
786  {
788  dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) );
789  if ( p != nullptr && p != this )
790  {
791  plane = p;
792  sharedAxis = a;
793  }
794  }
795  }
796 
797  if ( plane == this || painter == nullptr )
798  return plane;
799 
800  const QPointF zero = QPointF( 0, 0 );
801  const QPointF tenX = QPointF( 10, 0 );
802  const QPointF tenY = QPointF( 0, 10 );
803 
804 
805  if ( sharedAxis->isOrdinate() )
806  {
807  painter->translate( translate( zero ).x(), 0.0 );
808  const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x();
809  painter->scale( factor, 1.0 );
810  painter->translate( -plane->translate( zero ).x(), 0.0 );
811  }
812  if ( sharedAxis->isAbscissa() )
813  {
814  painter->translate( 0.0, translate( zero ).y() );
815  const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y();
816  painter->scale( 1.0, factor );
817  painter->translate( 0.0, -plane->translate( zero ).y() );
818  }
819 
820 
821  return plane;
822 }
823 
825 {
826  if ( d->reverseHorizontalPlane == reverse )
827  return;
828 
829  d->reverseHorizontalPlane = reverse;
830  layoutDiagrams();
831  emit propertiesChanged();
832 }
833 
835 {
836  return d->reverseHorizontalPlane;
837 }
838 
840 {
841  if ( d->reverseVerticalPlane == reverse )
842  return;
843 
844  d->reverseVerticalPlane = reverse;
845  layoutDiagrams();
846  emit propertiesChanged();
847 }
848 
850 {
851  return d->reverseVerticalPlane;
852 }
853 
855 {
856  QRectF result;
857 
858  const QRectF drawArea = drawingArea();
859 
860  result.setTopLeft( translateBack( drawArea.topLeft() ) );
861  result.setBottomRight( translateBack( drawArea.bottomRight() ) );
862 
863  return result;
864 }
865 
867 {
868  if ( rectangle == geometry() ) {
869  return;
870  }
871 
872  d->geometry = rectangle;
873  if ( d->isometricScaling ) {
874  const int hfw = heightForWidth( rectangle.width() );
875  // same scaling for x and y means a fixed aspect ratio, which is enforced here
876  // always shrink the too large dimension
877  if ( hfw < rectangle.height() ) {
878  d->geometry.setHeight( hfw );
879  } else {
880  d->geometry.setWidth( qRound( qreal( rectangle.width() ) *
881  qreal( rectangle.height() ) / qreal( hfw ) ) );
882  }
883  }
884 
886 
887  Q_FOREACH( AbstractDiagram* diagram, diagrams() ) {
888  diagram->resize( d->geometry.size() );
889  }
890 }
891 
892 Qt::Orientations CartesianCoordinatePlane::expandingDirections() const
893 {
894  // not completely sure why this is required for isometric scaling...
895  return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical );
896 }
897 
898 bool CartesianCoordinatePlane::hasHeightForWidth() const
899 {
900  return d->isometricScaling;
901 }
902 
903 int CartesianCoordinatePlane::heightForWidth( int w ) const
904 {
905  // ### using anything for dataRect that depends on geometry will close a feedback loop which
906  // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on
907  // drawingArea(), and no good will come out of using it here.
908  QRectF dataRect = logicalArea();
909  return qRound( qreal( w ) * qAbs( qreal( dataRect.height() ) / qreal( dataRect.width() ) ) );
910 }
911 
912 QSize CartesianCoordinatePlane::sizeHint() const
913 {
915  if ( d->isometricScaling ) {
916  // not sure why the next line doesn't cause an infinite loop, but it improves initial size allocation
917  sh = d->geometry.size();
918  sh.setHeight( heightForWidth( sh.width() ) );
919  }
920  return sh;
921 }
Helper class for one dimension of data, e.g.
Qt::Orientation orientation() const
void setBottomLeft(const QPointF &position)
void setHeight(int height)
int width() const const
QRect toRect() const const
bool autoAdjustGridToZoom() const
Return the status of the built-in grid adjusting feature.
qreal gridSubStepWidth() const
Returns the sub-step width to be used for calculating the sub-grid lines.
AbstractDiagram defines the interface for diagram classes.
void setVerticalRangeReversed(bool reverse)
Sets whether the vertical range should be reversed or not, i.e.
void setAxesCalcModes(AxesCalcMode mode)
Specifies the calculation modes for all axes.
void setRight(qreal x)
void scale(qreal sx, qreal sy)
QSizeF size() const const
QRect areaGeometry() const override
bool hasOwnGridAttributes(Qt::Orientation orientation) const
qreal top() const const
int height() const const
void setAutoAdjustGridToZoom(bool autoAdjust)
Disable / re-enable the built-in grid adjusting feature.
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
void setClipRegion(const QRegion &region, Qt::ClipOperation operation)
QRectF intersected(const QRectF &rectangle) const const
void setIsometricScaling(bool onOff)
If onOff is true, enforce that X and Y distances are scaled by the same factor.
The class for cartesian axes.
qreal left() const const
void setLeft(qreal x)
int size() const const
void setAutoAdjustVerticalRangeToData(unsigned int percentEmpty=67)
Automatically adjust vertical range settings to the ranges covered by the model&#39;s values...
void layoutDiagrams() override
Distribute the available space among the diagrams and axes.
void setAutoAdjustHorizontalRangeToData(unsigned int percentEmpty=67)
Automatically adjust horizontal range settings to the ranges covered by the model&#39;s values...
qreal gridStepWidth() const
Returns the step width to be used for calculating the grid lines.
qreal bottom() const const
const GridAttributes gridAttributes(Qt::Orientation orientation) const
QRectF visibleDiagramArea() const
Returns the visible part of the diagram area, i.e.
void setAxesCalcModeY(AxesCalcMode mode)
Specifies the calculation mode for all Ordinate axes.
qreal x() const const
qreal y() const const
void append(const T &value)
void adjustVerticalRangeToData()
Adjust vertical range settings to the ranges covered by the model&#39;s data values.
Stores information about painting diagrams.
void adjustHorizontalRangeToData()
Adjust horizontal range settings to the ranges covered by the model&#39;s data values.
void adjustRangesToData()
Adjust both, horizontal and vertical range settings to the ranges covered by the model&#39;s data values...
void setZoomFactors(qreal factorX, qreal factorY) override
QRectF normalized() const const
Base class for diagrams based on a cartesian coordianate system.
bool isEmpty() const const
void setFixedDataCoordinateSpaceRelation(bool fixed)
Allows to specify a fixed data-space / coordinate-space relation.
void resetGridAttributes(Qt::Orientation orientation)
Reset the attributes to be used for grid lines drawn in horizontal direction (or in vertical directio...
QPointF topLeft() const const
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
ZoomParameters stores the center and the factor of zooming internally.
void setZoomCenter(const QPointF &center) override
QRectF logicalArea() const
Returns the logical area, i.e., the rectangle defined by the very top left and very bottom right coor...
unsigned int autoAdjustVerticalRangeToData() const
Returns the maximal allowed percent of the vertical space covered by the coordinate plane that may be...
virtual KChart::CartesianAxisList axes() const
void addDiagram(AbstractDiagram *diagram) override
Adds a diagram to this coordinate plane.
QRect adjusted(int dx1, int dy1, int dx2, int dy2) const const
QSize sizeHint() const override
pure virtual in QLayoutItem
qreal right() const const
virtual void addDiagram(AbstractDiagram *diagram)
Adds a diagram to this coordinate plane.
QRect geometry() const override
pure virtual in QLayoutItem
const QPointF translate(const QPointF &diagramPoint) const override
Translate the given point in value space coordinates to a position in pixel space.
AbstractCoordinatePlane * sharedAxisMasterPlane(QPainter *p=nullptr) override
reimpl
void setTopLeft(const QPointF &position)
bool isValid() const const
void setAxesCalcModeX(AxesCalcMode mode)
Specifies the calculation mode for all Abscissa axes.
void setGeometry(const QRect &r) override
pure virtual in QLayoutItem
virtual int heightForWidth(int) const const
virtual void resize(const QSizeF &area)
Called by the widget&#39;s sizeEvent.
int width() const const
qreal width() const const
void setBottom(qreal y)
BarDiagram defines a common bar diagram.
void setTop(qreal y)
A set of attributes controlling the appearance of grids.
void setGridAttributes(Qt::Orientation orientation, const GridAttributes &)
Set the attributes to be used for grid lines drawn in horizontal direction (or in vertical direction...
qreal distance() const
Returns the size of the distance, equivalent to the width() (or height(), resp.) of a QRectF...
void translate(const QPointF &offset)
void setVerticalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in vertical direction.
QRectF diagramArea() const
Returns the (physical) area occupied by the diagram.
QRectF adjusted(qreal dx1, qreal dy1, qreal dx2, qreal dy2) const const
qreal height() const const
void setXAxisStartAtZero(bool fixedStart)
Allows to fix the lower bound of X axis to zero when diagram is in first quadrant.
A chart with one or more diagrams.
Definition: KChartChart.h:95
Orientation
void setTopRight(const QPointF &position)
QPointF bottomRight() const const
void setGeometry(const QRect &r) override
reimplemented from AbstractCoordinatePlane
Global namespace.
QMetaObject::Connection connect(const QObject *sender, const char *signal, const QObject *receiver, const char *method, Qt::ConnectionType type)
T qobject_cast(QObject *object)
QObject * parent() const const
virtual AbstractCartesianDiagram * referenceDiagram() const
void setBottomRight(const QPointF &position)
qint64 elapsed() const const
int datasetDimension() const
The dataset dimension of a diagram determines how many value dimensions it expects each datapoint to ...
QPair< qreal, qreal > horizontalRange() const
QRectF visibleDataRange() const
Returns the currently visible data range.
const AbstractCoordinatePlane * coordinatePlane() const
Convenience function, returns the coordinate plane, in which this axis is used.
unsigned int autoAdjustHorizontalRangeToData() const
Returns the maximal allowed percent of the horizontal space covered by the coordinate plane that may ...
KChartEnums::GranularitySequence gridGranularitySequence() const
Returns the granularity sequence to be used for calculating the grid lines.
void setHorizontalRange(const QPair< qreal, qreal > &range)
Set the boundaries of the visible value space displayed in horizontal direction.
void setHorizontalRangeReversed(bool reverse)
Sets whether the horizontal range should be reversed or not, i.e.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Sep 29 2020 22:42:40 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.