KChart

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

KDE's Doxygen guidelines are available online.