KDChartCartesianCoordinatePlane.cpp

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