KD Chart 2
[rev.2.5]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB. All rights reserved. 00003 ** 00004 ** This file is part of the KD Chart library. 00005 ** 00006 ** Licensees holding valid commercial KD Chart licenses may use this file in 00007 ** accordance with the KD Chart Commercial License Agreement provided with 00008 ** the Software. 00009 ** 00010 ** 00011 ** This file may be distributed and/or modified under the terms of the 00012 ** GNU General Public License version 2 and version 3 as published by the 00013 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. 00014 ** 00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00017 ** 00018 ** Contact info@kdab.com if any conditions of this licensing are not 00019 ** clear to you. 00020 ** 00021 **********************************************************************/ 00022 00023 #include "KDChartCartesianCoordinatePlane.h" 00024 #include "KDChartCartesianCoordinatePlane_p.h" 00025 00026 #include "KDChartAbstractDiagram.h" 00027 #include "KDChartAbstractDiagram_p.h" 00028 #include "KDChartAbstractCartesianDiagram.h" 00029 #include "CartesianCoordinateTransformation.h" 00030 #include "KDChartGridAttributes.h" 00031 #include "KDChartPaintContext.h" 00032 #include "KDChartPainterSaver_p.h" 00033 #include "KDChartBarDiagram.h" 00034 #include "KDChartStockDiagram.h" 00035 00036 #include <KDABLibFakes> 00037 00038 #include <QApplication> 00039 #include <QFont> 00040 #include <QList> 00041 #include <QtDebug> 00042 #include <QPainter> 00043 #include <QTime> 00044 00045 00046 using namespace KDChart; 00047 00048 #define d d_func() 00049 00050 CartesianCoordinatePlane::Private::Private() 00051 : AbstractCoordinatePlane::Private() 00052 , bPaintIsRunning( false ) 00053 , hasOwnGridAttributesHorizontal ( false ) 00054 , hasOwnGridAttributesVertical ( false ) 00055 , isometricScaling ( false ) 00056 , horizontalMin(0) 00057 , horizontalMax(0) 00058 , verticalMin(0) 00059 , verticalMax(0) 00060 , autoAdjustHorizontalRangeToData(67) 00061 , autoAdjustVerticalRangeToData( 67) 00062 , autoAdjustGridToZoom( true ) 00063 , fixedDataCoordinateSpaceRelation( false ) 00064 , xAxisStartAtZero(true) 00065 , reverseVerticalPlane( false ) 00066 , reverseHorizontalPlane( false ) 00067 { 00068 } 00069 00070 CartesianCoordinatePlane::CartesianCoordinatePlane ( Chart* parent ) 00071 : AbstractCoordinatePlane ( new Private(), parent ) 00072 { 00073 // this bloc left empty intentionally 00074 } 00075 00076 CartesianCoordinatePlane::~CartesianCoordinatePlane() 00077 { 00078 // this bloc left empty intentionally 00079 } 00080 00081 void CartesianCoordinatePlane::init() 00082 { 00083 // this bloc left empty intentionally 00084 } 00085 00086 00087 void CartesianCoordinatePlane::addDiagram ( AbstractDiagram* diagram ) 00088 { 00089 Q_ASSERT_X ( dynamic_cast<AbstractCartesianDiagram*> ( diagram ), 00090 "CartesianCoordinatePlane::addDiagram", "Only cartesian " 00091 "diagrams can be added to a cartesian coordinate plane!" ); 00092 AbstractCoordinatePlane::addDiagram ( diagram ); 00093 connect ( diagram, SIGNAL ( layoutChanged ( AbstractDiagram* ) ), 00094 SLOT ( slotLayoutChanged ( AbstractDiagram* ) ) ); 00095 00096 connect( diagram, SIGNAL( propertiesChanged() ),this, SIGNAL( propertiesChanged() ) ); 00097 } 00098 00099 00100 void CartesianCoordinatePlane::paint ( QPainter* painter ) 00101 { 00102 // prevent recursive call: 00103 if( d->bPaintIsRunning ){ 00104 return; 00105 } 00106 d->bPaintIsRunning = true; 00107 00108 AbstractDiagramList diags = diagrams(); 00109 if ( !diags.isEmpty() ) 00110 { 00111 PaintContext ctx; 00112 ctx.setPainter ( painter ); 00113 ctx.setCoordinatePlane ( this ); 00114 const QRectF drawArea( drawingArea() ); 00115 ctx.setRectangle ( drawArea ); 00116 00117 // enabling clipping so that we're not drawing outside 00118 PainterSaver painterSaver( painter ); 00119 QRect clipRect = drawArea.toRect().adjusted( -1, -1, 1, 1 ); 00120 QRegion clipRegion( clipRect ); 00121 painter->setClipRegion( clipRegion ); 00122 00123 // paint the coordinate system rulers: 00124 d->grid->drawGrid( &ctx ); 00125 00126 // paint the diagrams: 00127 for ( int i = 0; i < diags.size(); i++ ) 00128 { 00129 if ( diags[i]->isHidden() ) { 00130 continue; 00131 } 00132 bool doDumpPaintTime = AbstractDiagram::Private::get( diags[ i ] )->doDumpPaintTime; 00133 QTime stopWatch; 00134 if ( doDumpPaintTime ) { 00135 stopWatch.start(); 00136 } 00137 00138 PainterSaver diagramPainterSaver( painter ); 00139 diags[i]->paint ( &ctx ); 00140 00141 if ( doDumpPaintTime ) { 00142 qDebug() << "Painting diagram" << i << "took" << stopWatch.elapsed() << "milliseconds"; 00143 } 00144 } 00145 00146 } 00147 d->bPaintIsRunning = false; 00148 } 00149 00150 00151 void CartesianCoordinatePlane::slotLayoutChanged ( AbstractDiagram* ) 00152 { 00153 layoutDiagrams(); 00154 } 00155 00156 QRectF CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams() const 00157 { 00158 // determine unit of the rectangles of all involved diagrams: 00159 qreal minX = 0; 00160 qreal maxX = 0; 00161 qreal minY = 0; 00162 qreal maxY = 0; 00163 bool bStarting = true; 00164 Q_FOREACH( const AbstractDiagram* diagram, diagrams() ) 00165 { 00166 QPair<QPointF, QPointF> dataBoundariesPair = diagram->dataBoundaries(); 00167 //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\ngets diagram->dataBoundaries: " << dataBoundariesPair.first << dataBoundariesPair.second; 00168 if ( bStarting || dataBoundariesPair.first.x() < minX ) minX = dataBoundariesPair.first.x(); 00169 if ( bStarting || dataBoundariesPair.first.y() < minY ) minY = dataBoundariesPair.first.y(); 00170 if ( bStarting || dataBoundariesPair.second.x() > maxX ) maxX = dataBoundariesPair.second.x(); 00171 if ( bStarting || dataBoundariesPair.second.y() > maxY ) maxY = dataBoundariesPair.second.y(); 00172 bStarting = false; 00173 } 00174 //qDebug() << "CartesianCoordinatePlane::getRawDataBoundingRectFromDiagrams()\nreturns data boundaries: " << QRectF( QPointF(minX, minY), QSizeF(maxX - minX, maxY - minY) ); 00175 QRectF dataBoundingRect; 00176 dataBoundingRect.setBottomLeft( QPointF(minX, minY) ); 00177 dataBoundingRect.setTopRight( QPointF(maxX, maxY) ); 00178 return dataBoundingRect; 00179 } 00180 00181 00182 QRectF CartesianCoordinatePlane::adjustedToMaxEmptyInnerPercentage( 00183 const QRectF& r, unsigned int percentX, unsigned int percentY ) const 00184 { 00185 QRectF ret = r; 00186 if ( ( axesCalcModeX() != Logarithmic || r.left() < 0.0 ) && percentX > 0 && percentX != 100 ) { 00187 const bool isPositive = r.left() >= 0; 00188 if ( ( r.right() >= 0 ) == isPositive ) { 00189 qreal upperBound = qMax( r.left(), r.right() ); 00190 qreal lowerBound = qMin( r.left(), r.right() ); 00191 qreal innerBound = isPositive ? lowerBound : upperBound; 00192 qreal outerBound = isPositive ? upperBound : lowerBound; 00193 if ( innerBound / outerBound * 100 <= percentX && d->xAxisStartAtZero ) { 00194 if ( isPositive ) { 00195 ret.setLeft( 0.0 ); 00196 } else { 00197 ret.setRight( 0.0 ); 00198 } 00199 } 00200 } 00201 } 00202 // ### this doesn't seem to take into account that Qt's y coordinate is inverted 00203 if ( ( axesCalcModeY() != Logarithmic || r.bottom() < 0.0 ) && percentY > 0 && percentY != 100 ) { 00204 const bool isPositive = r.bottom() >= 0; 00205 if ( ( r.top() >= 0 ) == isPositive ) { 00206 qreal upperBound = qMax( r.top(), r.bottom() ); 00207 qreal lowerBound = qMin( r.top(), r.bottom() ); 00208 const qreal innerBound = isPositive ? lowerBound : upperBound; 00209 const qreal outerBound = isPositive ? upperBound : lowerBound; 00210 if( innerBound / outerBound * 100 <= percentY ) { 00211 if ( isPositive ) { 00212 ret.setBottom( 0.0 ); 00213 } else { 00214 ret.setTop( 0.0 ); 00215 } 00216 } 00217 } 00218 } 00219 return ret; 00220 } 00221 00222 00223 QRectF CartesianCoordinatePlane::calculateRawDataBoundingRect() const 00224 { 00225 // are manually set ranges to be applied? 00226 const bool bAutoAdjustHorizontalRange = (d->autoAdjustHorizontalRangeToData < 100); 00227 const bool bAutoAdjustVerticalRange = (d->autoAdjustVerticalRangeToData < 100); 00228 00229 const bool bHardHorizontalRange = (d->horizontalMin != d->horizontalMax) && ! bAutoAdjustHorizontalRange; 00230 const bool bHardVerticalRange = (d->verticalMin != d->verticalMax) && ! bAutoAdjustVerticalRange; 00231 QRectF dataBoundingRect; 00232 00233 // if custom boundaries are set on the plane, use them 00234 if ( bHardHorizontalRange && bHardVerticalRange ) { 00235 dataBoundingRect.setLeft( d->horizontalMin ); 00236 dataBoundingRect.setRight( d->horizontalMax ); 00237 dataBoundingRect.setBottom( d->verticalMin ); 00238 dataBoundingRect.setTop( d->verticalMax ); 00239 }else{ 00240 // determine unit of the rectangles of all involved diagrams: 00241 dataBoundingRect = getRawDataBoundingRectFromDiagrams(); 00242 if ( bHardHorizontalRange ) { 00243 dataBoundingRect.setLeft( d->horizontalMin ); 00244 dataBoundingRect.setRight( d->horizontalMax ); 00245 } 00246 if ( bHardVerticalRange ) { 00247 dataBoundingRect.setBottom( d->verticalMin ); 00248 dataBoundingRect.setTop( d->verticalMax ); 00249 } 00250 } 00251 // recalculate the bounds, if automatic adjusting of ranges is desired AND 00252 // both bounds are at the same side of the zero line 00253 dataBoundingRect = adjustedToMaxEmptyInnerPercentage( 00254 dataBoundingRect, d->autoAdjustHorizontalRangeToData, d->autoAdjustVerticalRangeToData ); 00255 if( bAutoAdjustHorizontalRange ){ 00256 const_cast<CartesianCoordinatePlane::Private *>(d)->horizontalMin = dataBoundingRect.left(); 00257 const_cast<CartesianCoordinatePlane::Private *>(d)->horizontalMax = dataBoundingRect.right(); 00258 } 00259 if( bAutoAdjustVerticalRange ){ 00260 const_cast<CartesianCoordinatePlane*>(this)->d->verticalMin = dataBoundingRect.bottom(); 00261 const_cast<CartesianCoordinatePlane*>(this)->d->verticalMax = dataBoundingRect.top(); 00262 } 00263 //qDebug() << "CartesianCoordinatePlane::calculateRawDataBoundingRect()\nreturns data boundaries: " << dataBoundingRect; 00264 return dataBoundingRect; 00265 } 00266 00267 00268 DataDimensionsList CartesianCoordinatePlane::getDataDimensionsList() const 00269 { 00270 const AbstractCartesianDiagram* dgr = diagrams().isEmpty() ? 0 : 00271 qobject_cast< const AbstractCartesianDiagram* >( diagrams().first() ); 00272 if ( dgr && dgr->referenceDiagram() ) { 00273 dgr = dgr->referenceDiagram(); 00274 } 00275 const BarDiagram *barDiagram = qobject_cast< const BarDiagram* >( dgr ); 00276 const StockDiagram *stockDiagram = qobject_cast< const StockDiagram* >( dgr ); 00277 00278 // note: 00279 // It does make sense to retrieve the orientation from the first diagram. This is because 00280 // a coordinate plane can either be for horizontal *or* for vertical diagrams. Both at the 00281 // same time won't work, and thus the orientation for all diagrams is the same as for the first one. 00282 const Qt::Orientation diagramOrientation = barDiagram != 0 ? barDiagram->orientation() : Qt::Vertical; 00283 const bool diagramIsVertical = diagramOrientation == Qt::Vertical; 00284 00285 DataDimensionsList l; 00286 if ( dgr ) { 00287 const QRectF r( calculateRawDataBoundingRect() ); 00288 // note: 00289 // We do *not* access d->gridAttributesHorizontal here, but 00290 // we use the getter function, to get the global attrs, if no 00291 // special ones have been set for the respective orientation. 00292 const GridAttributes gaH( gridAttributes( Qt::Horizontal ) ); 00293 const GridAttributes gaV( gridAttributes( Qt::Vertical ) ); 00294 // append the first dimension: for Abscissa axes 00295 l.append( 00296 DataDimension( 00297 r.left(), r.right(), 00298 diagramIsVertical ? ( !stockDiagram && dgr->datasetDimension() > 1 ) : true, 00299 axesCalcModeX(), 00300 gaH.gridGranularitySequence(), 00301 gaH.gridStepWidth(), 00302 gaH.gridSubStepWidth() ) ); 00303 // append the second dimension: for Ordinate axes 00304 l.append( 00305 DataDimension( 00306 r.bottom(), r.top(), 00307 diagramIsVertical ? true : ( dgr->datasetDimension() > 1 ), 00308 axesCalcModeY(), 00309 gaV.gridGranularitySequence(), 00310 gaV.gridStepWidth(), 00311 gaV.gridSubStepWidth() ) ); 00312 } else { 00313 l.append( DataDimension() ); // This gets us the default 1..0 / 1..0 grid 00314 l.append( DataDimension() ); // shown, if there is no diagram on this plane. 00315 } 00316 return l; 00317 } 00318 00319 QRectF CartesianCoordinatePlane::drawingArea() const 00320 { 00321 // the rectangle the diagrams cover in the *plane*: 00322 // (Why -3? We save 1px on each side for the antialiased drawing, and 00323 // respect the way QPainter calculates the width of a painted rect (the 00324 // size is the rectangle size plus the pen width). This way, most clipping 00325 // for regular pens should be avoided. When pens with a penWidth or larger 00326 // than 1 are used, this may not be sufficient. 00327 const QRect rect( areaGeometry() ); 00328 return QRectF ( rect.left()+1, rect.top()+1, rect.width() - 3, rect.height() - 3 ); 00329 } 00330 00331 00332 QRectF CartesianCoordinatePlane::logicalArea() const 00333 { 00334 if ( d->dimensions.isEmpty() ) 00335 return QRectF(); 00336 00337 const DataDimension dimX = d->dimensions.first(); 00338 const DataDimension dimY = d->dimensions.last(); 00339 const QPointF pt( qMin( dimX.start, dimX.end ), qMax( dimY.start, dimY.end ) ); 00340 const QSizeF siz( qAbs( dimX.distance() ), -qAbs( dimY.distance() ) ); 00341 const QRectF dataBoundingRect( pt, siz ); 00342 00343 // determine logical top left, taking the "reverse" option of 00344 // horizontal and vertical dimension into account 00345 QPointF topLeft; 00346 if( !d->reverseVerticalPlane && !d->reverseHorizontalPlane ) 00347 topLeft = dataBoundingRect.topLeft(); 00348 else if( d->reverseVerticalPlane && !d->reverseHorizontalPlane ) 00349 topLeft = dataBoundingRect.bottomLeft(); 00350 else if( d->reverseVerticalPlane && d->reverseHorizontalPlane ) 00351 topLeft = dataBoundingRect.bottomRight(); 00352 else if( !d->reverseVerticalPlane && d->reverseHorizontalPlane ) 00353 topLeft = dataBoundingRect.topRight(); 00354 00355 const qreal width = dataBoundingRect.width() * ( d->reverseHorizontalPlane ? -1.0 : 1.0 ); 00356 const qreal height = dataBoundingRect.height() * ( d->reverseVerticalPlane ? -1.0 : 1.0 ); 00357 00358 return QRectF( topLeft, QSizeF( width, height ) ); 00359 } 00360 00361 QRectF CartesianCoordinatePlane::diagramArea() const 00362 { 00363 const QRectF logArea( logicalArea() ); 00364 QPointF physicalTopLeft = d->coordinateTransformation.translate( logArea.topLeft() ); 00365 QPointF physicalBottomRight = d->coordinateTransformation.translate( logArea.bottomRight() ); 00366 00367 return QRectF( physicalTopLeft, physicalBottomRight ).normalized(); 00368 } 00369 00370 QRectF CartesianCoordinatePlane::visibleDiagramArea() const 00371 { 00372 return diagramArea().intersected( drawingArea() ); 00373 } 00374 00375 void CartesianCoordinatePlane::layoutDiagrams() 00376 { 00377 if ( diagrams().isEmpty() ) 00378 { // FIXME evaluate what can still be prepared 00379 // FIXME decide default dimension if no diagrams are present (to make empty planes useable) 00380 } 00381 00382 d->dimensions = gridDimensionsList(); 00383 // test for programming errors: critical 00384 Q_ASSERT_X ( d->dimensions.count() == 2, "CartesianCoordinatePlane::layoutDiagrams", 00385 "Error: gridDimensionsList() did not return exactly two dimensions." ); 00386 00387 // physical area of the plane 00388 const QRectF physicalArea( drawingArea() ); 00389 // .. in contrast to the logical area 00390 const QRectF logArea( logicalArea() ); 00391 00392 // TODO: isometric scaling for zooming? 00393 00394 // the plane area might have changed, so the zoom values might also be changed 00395 handleFixedDataCoordinateSpaceRelation( physicalArea ); 00396 00397 d->coordinateTransformation.updateTransform( logArea, physicalArea ); 00398 00399 update(); 00400 } 00401 00402 void CartesianCoordinatePlane::setFixedDataCoordinateSpaceRelation( bool fixed ) 00403 { 00404 d->fixedDataCoordinateSpaceRelation = fixed; 00405 d->fixedDataCoordinateSpaceRelationOldSize = QRectF(); 00406 /* 00407 //TODO(khz): We need to discuss if we want to do this: 00408 if( ! fixed ){ 00409 bool bChanged = false; 00410 if( doneSetZoomFactorY( 1.0 ) ) 00411 bChanged = true; 00412 if( doneSetZoomFactorX( 1.0 ) ) 00413 bChanged = true; 00414 if( doneSetZoomCenter( QPointF(0.5, 0.5) ) ) 00415 bChanged = true; 00416 if( bChanged ){ 00417 emit propertiesChanged(); 00418 } 00419 } 00420 */ 00421 } 00422 00423 bool CartesianCoordinatePlane::hasFixedDataCoordinateSpaceRelation() const 00424 { 00425 return d->fixedDataCoordinateSpaceRelation; 00426 } 00427 00428 void CartesianCoordinatePlane::setXAxisStartAtZero(bool fixedStart) 00429 { 00430 if(d->xAxisStartAtZero == fixedStart) 00431 return; 00432 00433 d->xAxisStartAtZero = fixedStart; 00434 } 00435 00436 bool CartesianCoordinatePlane::xAxisStartAtZero() const 00437 { 00438 return d->xAxisStartAtZero; 00439 } 00440 00441 void CartesianCoordinatePlane::handleFixedDataCoordinateSpaceRelation( const QRectF& geometry ) 00442 { 00443 // is the feature enabled? 00444 if( !d->fixedDataCoordinateSpaceRelation ) 00445 return; 00446 00447 // is the new geometry ok? 00448 if( geometry.height() < 1 || geometry.width() < 1 ) 00449 return; 00450 00451 // if the size was changed, we calculate new zoom settings 00452 if( d->fixedDataCoordinateSpaceRelationOldSize != geometry && !d->fixedDataCoordinateSpaceRelationOldSize.isNull() ) 00453 { 00454 const qreal newZoomX = zoomFactorX() * d->fixedDataCoordinateSpaceRelationOldSize.width() / geometry.width(); 00455 const qreal newZoomY = zoomFactorY() * d->fixedDataCoordinateSpaceRelationOldSize.height() / geometry.height(); 00456 00457 const QPointF oldCenter = zoomCenter(); 00458 const QPointF newCenter = QPointF( oldCenter.x() * geometry.width() / d->fixedDataCoordinateSpaceRelationOldSize.width(), 00459 oldCenter.y() * geometry.height() / d->fixedDataCoordinateSpaceRelationOldSize.height() ); 00460 00461 // Use these internal methods to avoid sending 00462 // the propertiesChanged signal three times: 00463 bool bChanged = false; 00464 if( doneSetZoomFactorY( newZoomY ) ) 00465 bChanged = true; 00466 if( doneSetZoomFactorX( newZoomX ) ) 00467 bChanged = true; 00468 if( doneSetZoomCenter( newCenter ) ) 00469 bChanged = true; 00470 if( bChanged ){ 00471 emit propertiesChanged(); 00472 } 00473 } 00474 00475 d->fixedDataCoordinateSpaceRelationOldSize = geometry; 00476 } 00477 00478 const QPointF CartesianCoordinatePlane::translate( const QPointF& diagramPoint ) const 00479 { 00480 // Note: We do not test if the point lays inside of the data area, 00481 // but we just apply the transformation calculations to the point. 00482 // This allows for basic calculations done by the user, see e.g. 00483 // the file examples/Lines/BubbleChart/mainwindow.cpp 00484 return d->coordinateTransformation.translate( diagramPoint ); 00485 } 00486 00487 const QPointF CartesianCoordinatePlane::translateBack( const QPointF& screenPoint ) const 00488 { 00489 return d->coordinateTransformation.translateBack( screenPoint ); 00490 } 00491 00492 void CartesianCoordinatePlane::setIsometricScaling ( bool isOn ) 00493 { 00494 if ( d->isometricScaling != isOn ) { 00495 d->isometricScaling = isOn; 00496 layoutDiagrams(); 00497 emit propertiesChanged(); 00498 } 00499 } 00500 00501 bool CartesianCoordinatePlane::doesIsometricScaling () const 00502 { 00503 return d->isometricScaling; 00504 } 00505 00506 bool CartesianCoordinatePlane::doneSetZoomFactorX( qreal factor ) 00507 { 00508 if ( d->coordinateTransformation.zoom.xFactor == factor ) { 00509 return false; 00510 } 00511 d->coordinateTransformation.zoom.xFactor = factor; 00512 if ( d->autoAdjustGridToZoom ) { 00513 d->grid->setNeedRecalculate(); 00514 } 00515 return true; 00516 } 00517 00518 bool CartesianCoordinatePlane::doneSetZoomFactorY( qreal factor ) 00519 { 00520 if ( d->coordinateTransformation.zoom.yFactor == factor ) { 00521 return false; 00522 } 00523 d->coordinateTransformation.zoom.yFactor = factor; 00524 if ( d->autoAdjustGridToZoom ) { 00525 d->grid->setNeedRecalculate(); 00526 } 00527 return true; 00528 } 00529 00530 bool CartesianCoordinatePlane::doneSetZoomCenter( const QPointF& point ) 00531 { 00532 if ( d->coordinateTransformation.zoom.center() == point ) { 00533 return false; 00534 } 00535 d->coordinateTransformation.zoom.setCenter( point ); 00536 if ( d->autoAdjustGridToZoom ) { 00537 d->grid->setNeedRecalculate(); 00538 } 00539 return true; 00540 } 00541 00542 void CartesianCoordinatePlane::setZoomFactors( qreal factorX, qreal factorY ) 00543 { 00544 if ( doneSetZoomFactorX( factorX ) || doneSetZoomFactorY( factorY ) ) { 00545 d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); 00546 emit propertiesChanged(); 00547 } 00548 } 00549 00550 void CartesianCoordinatePlane::setZoomFactorX( qreal factor ) 00551 { 00552 if ( doneSetZoomFactorX( factor ) ) { 00553 d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); 00554 emit propertiesChanged(); 00555 } 00556 } 00557 00558 void CartesianCoordinatePlane::setZoomFactorY( qreal factor ) 00559 { 00560 if ( doneSetZoomFactorY( factor ) ) { 00561 d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); 00562 emit propertiesChanged(); 00563 } 00564 } 00565 00566 void CartesianCoordinatePlane::setZoomCenter( const QPointF& point ) 00567 { 00568 if ( doneSetZoomCenter( point ) ) { 00569 d->coordinateTransformation.updateTransform( logicalArea(), drawingArea() ); 00570 emit propertiesChanged(); 00571 } 00572 } 00573 00574 QPointF CartesianCoordinatePlane::zoomCenter() const 00575 { 00576 return d->coordinateTransformation.zoom.center(); 00577 } 00578 00579 qreal CartesianCoordinatePlane::zoomFactorX() const 00580 { 00581 return d->coordinateTransformation.zoom.xFactor; 00582 } 00583 00584 qreal CartesianCoordinatePlane::zoomFactorY() const 00585 { 00586 return d->coordinateTransformation.zoom.yFactor; 00587 } 00588 00589 00590 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeY() const 00591 { 00592 return d->coordinateTransformation.axesCalcModeY; 00593 } 00594 00595 CartesianCoordinatePlane::AxesCalcMode CartesianCoordinatePlane::axesCalcModeX() const 00596 { 00597 return d->coordinateTransformation.axesCalcModeX; 00598 } 00599 00600 void CartesianCoordinatePlane::setAxesCalcModes( AxesCalcMode mode ) 00601 { 00602 if( d->coordinateTransformation.axesCalcModeY != mode || 00603 d->coordinateTransformation.axesCalcModeX != mode ){ 00604 d->coordinateTransformation.axesCalcModeY = mode; 00605 d->coordinateTransformation.axesCalcModeX = mode; 00606 emit propertiesChanged(); 00607 emit viewportCoordinateSystemChanged(); 00608 Q_FOREACH( AbstractDiagram* diag, diagrams() ) 00609 slotLayoutChanged( diag ); 00610 } 00611 } 00612 00613 void CartesianCoordinatePlane::setAxesCalcModeY( AxesCalcMode mode ) 00614 { 00615 if( d->coordinateTransformation.axesCalcModeY != mode ){ 00616 d->coordinateTransformation.axesCalcModeY = mode; 00617 emit propertiesChanged(); 00618 setGridNeedsRecalculate(); 00619 emit viewportCoordinateSystemChanged(); 00620 } 00621 } 00622 00623 void CartesianCoordinatePlane::setAxesCalcModeX( AxesCalcMode mode ) 00624 { 00625 if( d->coordinateTransformation.axesCalcModeX != mode ){ 00626 d->coordinateTransformation.axesCalcModeX = mode; 00627 emit propertiesChanged(); 00628 emit viewportCoordinateSystemChanged(); 00629 } 00630 } 00631 00632 void CartesianCoordinatePlane::setHorizontalRange( const QPair< qreal, qreal > & range ) 00633 { 00634 if ( d->horizontalMin != range.first || d->horizontalMax != range.second ) { 00635 d->autoAdjustHorizontalRangeToData = 100; 00636 d->horizontalMin = range.first; 00637 d->horizontalMax = range.second; 00638 layoutDiagrams(); 00639 emit propertiesChanged(); 00640 emit boundariesChanged(); 00641 } 00642 } 00643 00644 void CartesianCoordinatePlane::setVerticalRange( const QPair< qreal, qreal > & range ) 00645 { 00646 00647 if ( d->verticalMin != range.first || d->verticalMax != range.second ) { 00648 d->autoAdjustVerticalRangeToData = 100; 00649 d->verticalMin = range.first; 00650 d->verticalMax = range.second; 00651 layoutDiagrams(); 00652 emit propertiesChanged(); 00653 emit boundariesChanged(); 00654 } 00655 } 00656 00657 QPair< qreal, qreal > CartesianCoordinatePlane::horizontalRange( ) const 00658 { 00659 QRectF visibleRange = visibleDataRange(); 00660 return QPair<qreal, qreal>( visibleRange.left(), visibleRange.right() ); 00661 } 00662 00663 QPair< qreal, qreal > CartesianCoordinatePlane::verticalRange( ) const 00664 { 00665 QRectF visibleRange = visibleDataRange(); 00666 return QPair<qreal, qreal>( visibleRange.bottom(), visibleRange.top() ); 00667 } 00668 00669 void CartesianCoordinatePlane::adjustRangesToData() 00670 { 00671 const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); 00672 d->horizontalMin = dataBoundingRect.left(); 00673 d->horizontalMax = dataBoundingRect.right(); 00674 d->verticalMin = dataBoundingRect.top(); 00675 d->verticalMax = dataBoundingRect.bottom(); 00676 layoutDiagrams(); 00677 emit propertiesChanged(); 00678 } 00679 00680 void CartesianCoordinatePlane::adjustHorizontalRangeToData() 00681 { 00682 const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); 00683 d->horizontalMin = dataBoundingRect.left(); 00684 d->horizontalMax = dataBoundingRect.right(); 00685 layoutDiagrams(); 00686 emit propertiesChanged(); 00687 } 00688 00689 void CartesianCoordinatePlane::adjustVerticalRangeToData() 00690 { 00691 const QRectF dataBoundingRect( getRawDataBoundingRectFromDiagrams() ); 00692 d->verticalMin = dataBoundingRect.bottom(); 00693 d->verticalMax = dataBoundingRect.top(); 00694 layoutDiagrams(); 00695 emit propertiesChanged(); 00696 } 00697 00698 void CartesianCoordinatePlane::setAutoAdjustHorizontalRangeToData( unsigned int percentEmpty ) 00699 { 00700 if ( d->autoAdjustHorizontalRangeToData != percentEmpty ) 00701 { 00702 d->autoAdjustHorizontalRangeToData = percentEmpty; 00703 d->horizontalMin = 0.0; 00704 d->horizontalMax = 0.0; 00705 layoutDiagrams(); 00706 emit propertiesChanged(); 00707 } 00708 } 00709 00710 void CartesianCoordinatePlane::setAutoAdjustVerticalRangeToData( unsigned int percentEmpty ) 00711 { 00712 if ( d->autoAdjustVerticalRangeToData != percentEmpty ) 00713 { 00714 d->autoAdjustVerticalRangeToData = percentEmpty; 00715 d->verticalMin = 0.0; 00716 d->verticalMax = 0.0; 00717 layoutDiagrams(); 00718 emit propertiesChanged(); 00719 } 00720 } 00721 00722 unsigned int CartesianCoordinatePlane::autoAdjustHorizontalRangeToData() const 00723 { 00724 return d->autoAdjustHorizontalRangeToData; 00725 } 00726 00727 unsigned int CartesianCoordinatePlane::autoAdjustVerticalRangeToData() const 00728 { 00729 return d->autoAdjustVerticalRangeToData; 00730 } 00731 00732 void CartesianCoordinatePlane::setGridAttributes( 00733 Qt::Orientation orientation, 00734 const GridAttributes& a ) 00735 { 00736 if( orientation == Qt::Horizontal ) 00737 d->gridAttributesHorizontal = a; 00738 else 00739 d->gridAttributesVertical = a; 00740 setHasOwnGridAttributes( orientation, true ); 00741 update(); 00742 emit propertiesChanged(); 00743 } 00744 00745 void CartesianCoordinatePlane::resetGridAttributes( 00746 Qt::Orientation orientation ) 00747 { 00748 setHasOwnGridAttributes( orientation, false ); 00749 update(); 00750 } 00751 00752 const GridAttributes CartesianCoordinatePlane::gridAttributes( 00753 Qt::Orientation orientation ) const 00754 { 00755 if( hasOwnGridAttributes( orientation ) ){ 00756 if( orientation == Qt::Horizontal ) 00757 return d->gridAttributesHorizontal; 00758 else 00759 return d->gridAttributesVertical; 00760 }else{ 00761 return globalGridAttributes(); 00762 } 00763 } 00764 00765 void CartesianCoordinatePlane::setHasOwnGridAttributes( 00766 Qt::Orientation orientation, bool on ) 00767 { 00768 if( orientation == Qt::Horizontal ) 00769 d->hasOwnGridAttributesHorizontal = on; 00770 else 00771 d->hasOwnGridAttributesVertical = on; 00772 emit propertiesChanged(); 00773 } 00774 00775 bool CartesianCoordinatePlane::hasOwnGridAttributes( 00776 Qt::Orientation orientation ) const 00777 { 00778 return 00779 ( orientation == Qt::Horizontal ) 00780 ? d->hasOwnGridAttributesHorizontal 00781 : d->hasOwnGridAttributesVertical; 00782 } 00783 00784 void CartesianCoordinatePlane::setAutoAdjustGridToZoom( bool autoAdjust ) 00785 { 00786 if( d->autoAdjustGridToZoom != autoAdjust ){ 00787 d->autoAdjustGridToZoom = autoAdjust; 00788 d->grid->setNeedRecalculate(); 00789 emit propertiesChanged(); 00790 } 00791 } 00792 00793 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE) 00794 const 00795 #endif 00796 bool CartesianCoordinatePlane::autoAdjustGridToZoom() const 00797 { 00798 return d->autoAdjustGridToZoom; 00799 } 00800 00801 AbstractCoordinatePlane* CartesianCoordinatePlane::sharedAxisMasterPlane( QPainter* painter ) 00802 { 00803 CartesianCoordinatePlane* plane = this; 00804 AbstractCartesianDiagram* diag = dynamic_cast< AbstractCartesianDiagram* >( plane->diagram() ); 00805 const CartesianAxis* sharedAxis = 0; 00806 if( diag != 0 ) 00807 { 00808 const CartesianAxisList axes = diag->axes(); 00809 KDAB_FOREACH( const CartesianAxis* a, axes ) 00810 { 00811 CartesianCoordinatePlane* p = const_cast< CartesianCoordinatePlane* >( 00812 dynamic_cast< const CartesianCoordinatePlane* >( a->coordinatePlane() ) ); 00813 if( p != 0 && p != this ) 00814 { 00815 plane = p; 00816 sharedAxis = a; 00817 } 00818 } 00819 } 00820 00821 if( plane == this || painter == 0 ) 00822 return plane; 00823 00824 const QPointF zero = QPointF( 0, 0 ); 00825 const QPointF tenX = QPointF( 10, 0 ); 00826 const QPointF tenY = QPointF( 0, 10 ); 00827 00828 00829 if( sharedAxis->isOrdinate() ) 00830 { 00831 painter->translate( translate( zero ).x(), 0.0 ); 00832 const qreal factor = (translate( tenX ) - translate( zero ) ).x() / ( plane->translate( tenX ) - plane->translate( zero ) ).x(); 00833 painter->scale( factor, 1.0 ); 00834 painter->translate( -plane->translate( zero ).x(), 0.0 ); 00835 } 00836 if( sharedAxis->isAbscissa() ) 00837 { 00838 painter->translate( 0.0, translate( zero ).y() ); 00839 const qreal factor = (translate( tenY ) - translate( zero ) ).y() / ( plane->translate( tenY ) - plane->translate( zero ) ).y(); 00840 painter->scale( 1.0, factor ); 00841 painter->translate( 0.0, -plane->translate( zero ).y() ); 00842 } 00843 00844 00845 return plane; 00846 } 00847 00848 void CartesianCoordinatePlane::setHorizontalRangeReversed( bool reverse ) 00849 { 00850 if( d->reverseHorizontalPlane == reverse ) 00851 return; 00852 00853 d->reverseHorizontalPlane = reverse; 00854 layoutDiagrams(); 00855 emit propertiesChanged(); 00856 } 00857 00858 bool CartesianCoordinatePlane::isHorizontalRangeReversed() const 00859 { 00860 return d->reverseHorizontalPlane; 00861 } 00862 00863 void CartesianCoordinatePlane::setVerticalRangeReversed( bool reverse ) 00864 { 00865 if( d->reverseVerticalPlane == reverse ) 00866 return; 00867 00868 d->reverseVerticalPlane = reverse; 00869 layoutDiagrams(); 00870 emit propertiesChanged(); 00871 } 00872 00873 bool CartesianCoordinatePlane::isVerticalRangeReversed() const 00874 { 00875 return d->reverseVerticalPlane; 00876 } 00877 00878 QRectF CartesianCoordinatePlane::visibleDataRange() const 00879 { 00880 QRectF result; 00881 00882 const QRectF drawArea = drawingArea(); 00883 00884 result.setTopLeft( translateBack( drawArea.topLeft() ) ); 00885 result.setBottomRight( translateBack( drawArea.bottomRight() ) ); 00886 00887 return result; 00888 } 00889 00890 void CartesianCoordinatePlane::setGeometry( const QRect& rectangle ) 00891 { 00892 if ( rectangle == geometry() ) { 00893 return; 00894 } 00895 00896 d->geometry = rectangle; 00897 if ( d->isometricScaling && rectangle.width() != d->geometry.width() ) { 00898 // same scaling for x and y means a fixed aspect ratio, which is enforced here 00899 d->geometry.setHeight( heightForWidth( rectangle.width() ) ); 00900 } 00901 AbstractCoordinatePlane::setGeometry( d->geometry ); 00902 00903 Q_FOREACH( AbstractDiagram* diagram, diagrams() ) { 00904 diagram->resize( d->geometry.size() ); 00905 } 00906 } 00907 00908 QSize CartesianCoordinatePlane::minimumSize() const 00909 { 00910 if ( d->isometricScaling ) { 00911 // HACK(?): we use the fact that minimumSize() is considered quite late in 00912 // the layout process (cf. "Layout Management" documentation) to enforce our 00913 // height-for-width requirement without interference from other constraints. 00914 return QSize( AbstractCoordinatePlane::minimumSize().width(), 00915 heightForWidth( geometry().width() ) ); 00916 } else { 00917 return AbstractCoordinatePlane::minimumSize(); 00918 } 00919 } 00920 00921 Qt::Orientations CartesianCoordinatePlane::expandingDirections() const 00922 { 00923 // this works together with the hack in minimumSize() 00924 return d->isometricScaling ? Qt::Horizontal : ( Qt::Horizontal | Qt::Vertical ); 00925 } 00926 00927 bool CartesianCoordinatePlane::hasHeightForWidth() const 00928 { 00929 return d->isometricScaling; 00930 } 00931 00932 int CartesianCoordinatePlane::heightForWidth( int w ) const 00933 { 00934 // ### using anything for dataRect that depends on geometry will close a feedback loop which 00935 // prevents the geometry from stabilizing. specifically, visibleDataRange() depends on 00936 // drawingArea(), and no good will come out of using it here. 00937 QRectF dataRect = logicalArea(); 00938 return qRound( qreal( w ) * qAbs( dataRect.height() / dataRect.width() ) ); 00939 }