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