KD Chart 2  [rev.2.5]
KDChartCartesianAxis.cpp
Go to the documentation of this file.
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 "KDChartCartesianAxis.h"
00024 #include "KDChartCartesianAxis_p.h"
00025 
00026 #include <cmath>
00027 
00028 #include <QtDebug>
00029 #include <QPainter>
00030 #include <QPen>
00031 #include <QBrush>
00032 #include <QApplication>
00033 
00034 #include "KDChartPaintContext.h"
00035 #include "KDChartChart.h"
00036 #include "KDChartAbstractCartesianDiagram.h"
00037 #include "KDChartAbstractGrid.h"
00038 #include "KDChartPainterSaver_p.h"
00039 #include "KDChartLayoutItems.h"
00040 #include "KDChartBarDiagram.h"
00041 #include "KDChartStockDiagram.h"
00042 #include "KDChartLineDiagram.h"
00043 #include "KDChartPrintingParameters.h"
00044 
00045 #include <KDABLibFakes>
00046 
00047 using namespace KDChart;
00048 
00049 #define d (d_func())
00050 
00051 static qreal slightlyLessThan( qreal r )
00052 {
00053     if ( r == 0.0 ) {
00054         // scale down the epsilon somewhat arbitrarily
00055         return r - std::numeric_limits< qreal >::epsilon() * 1e-6;
00056     }
00057     // scale the epsilon so that it (hopefully) changes at least the least significant bit of r
00058     qreal diff = qAbs( r ) * std::numeric_limits< qreal >::epsilon() * 2.0;
00059     return r - diff;
00060 }
00061 
00062 TickIterator::TickIterator( CartesianAxis* a, CartesianCoordinatePlane* plane, uint majorThinningFactor,
00063                             bool isBarChartAbscissa )
00064    : m_axis( a ),
00065      m_majorThinningFactor( majorThinningFactor ),
00066      m_majorLabelCount( 0 ),
00067      m_type( NoTick )
00068 {
00069     // deal with the things that are specfic to axes (like annotations), before the generic init().
00070     XySwitch xy( m_axis->isOrdinate() );
00071     m_dimension = xy( plane->gridDimensionsList().first(), plane->gridDimensionsList().last() );
00072 
00073     m_annotations = m_axis->d_func()->annotations;
00074     m_customTicks = m_axis->d_func()->customTicksPositions;
00075 
00076     const qreal inf = std::numeric_limits< qreal >::infinity();
00077 
00078     if ( m_customTicks.count() ) {
00079         qSort( m_customTicks.begin(), m_customTicks.end() );
00080         m_customTickIndex = 0;
00081         m_customTick = m_customTicks.at( m_customTickIndex );
00082     } else {
00083         m_customTickIndex = -1;
00084         m_customTick = inf;
00085     }
00086 
00087     if ( m_majorThinningFactor > 1 && hasShorterLabels() ) {
00088         m_manualLabelTexts = m_axis->shortLabels();
00089     } else {
00090         m_manualLabelTexts = m_axis->labels();
00091     }
00092     m_manualLabelIndex = m_manualLabelTexts.isEmpty() ? -1 : 0;
00093 
00094     bool hasMajorTicks = m_axis->rulerAttributes().showMajorTickMarks();
00095     bool hasMinorTicks = m_axis->rulerAttributes().showMinorTickMarks();
00096 
00097     init( xy.isY, hasMajorTicks, hasMinorTicks, plane, isBarChartAbscissa );
00098 }
00099 
00100 TickIterator::TickIterator( bool isY, const DataDimension& dimension, bool hasMajorTicks, bool hasMinorTicks,
00101                             CartesianCoordinatePlane* plane, uint majorThinningFactor )
00102    : m_axis( 0 ),
00103      m_dimension( dimension ),
00104      m_majorThinningFactor( majorThinningFactor ),
00105      m_majorLabelCount( 0 ),
00106      m_customTickIndex( -1 ),
00107      m_manualLabelIndex( -1 ),
00108      m_type( NoTick ),
00109      m_customTick( std::numeric_limits< qreal >::infinity() )
00110 {
00111     init( isY, hasMajorTicks, hasMinorTicks, plane, false );
00112 }
00113 
00114 void TickIterator::init( bool isY, bool hasMajorTicks, bool hasMinorTicks,
00115                          CartesianCoordinatePlane* plane, bool isBarChartAbscissa )
00116 {
00117     Q_ASSERT( std::numeric_limits< qreal >::has_infinity );
00118 
00119     m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
00120     // sanity check against infinite loops
00121     hasMajorTicks = hasMajorTicks && ( m_dimension.stepWidth > 0 || m_isLogarithmic );
00122     hasMinorTicks = hasMinorTicks && ( m_dimension.subStepWidth > 0 || m_isLogarithmic );
00123 
00124     if ( isBarChartAbscissa ) {
00125         // In bar charts the last x tick is a fencepost with no associated value, which is convenient
00126         // for grid painting. Here we have to manually exclude it to avoid overpainting.
00127         m_dimension.end -= m_dimension.stepWidth;
00128     }
00129 
00130     XySwitch xy( isY );
00131 
00132     GridAttributes gridAttributes = plane->gridAttributes( xy( Qt::Horizontal, Qt::Vertical ) );
00133     m_isLogarithmic = m_dimension.calcMode == AbstractCoordinatePlane::Logarithmic;
00134     if ( !m_isLogarithmic ) {
00135         // adjustedLowerUpperRange() is intended for use with linear scaling; specifically it would
00136         // round lower bounds < 1 to 0.
00137         m_dimension = AbstractGrid::adjustedLowerUpperRange( m_dimension,
00138                                                              gridAttributes.adjustLowerBoundToGrid(),
00139                                                              gridAttributes.adjustUpperBoundToGrid() );
00140     }
00141 
00142     const qreal inf = std::numeric_limits< qreal >::infinity();
00143 
00144     // try to place m_position just in front of the first tick to be drawn so that operator++()
00145     // can be used to find the first tick
00146     if ( m_isLogarithmic ) {
00147         if ( ISNAN( m_dimension.start ) || ISNAN( m_dimension.end ) ) {
00148             // this can happen in a spurious paint operation before everything is set up;
00149             // just bail out to avoid an infinite loop in that case.
00150             m_dimension.start = 0.0;
00151             m_dimension.end = 0.0;
00152             m_position = inf;
00153             m_majorTick = inf;
00154             m_minorTick = inf;
00155         } else if ( m_dimension.start >= 0 ) {
00156             m_position = m_dimension.start ? pow( 10.0, floor( log10( m_dimension.start ) ) - 1.0 )
00157                                            : 1e-6;
00158             m_majorTick = hasMajorTicks ? m_position : inf;
00159             m_minorTick = hasMinorTicks ? m_position * 20.0 : inf;
00160         } else {
00161             m_position = -pow( 10.0, ceil( log10( -m_dimension.start ) ) + 1.0 );
00162             m_majorTick = hasMajorTicks ? m_position : inf;
00163             m_minorTick = hasMinorTicks ? m_position * 0.09 : inf;
00164         }
00165     } else {
00166         m_majorTick = hasMajorTicks ? m_dimension.start : inf;
00167         m_minorTick = hasMinorTicks ? m_dimension.start : inf;
00168         m_position = slightlyLessThan( m_dimension.start );
00169     }
00170 
00171     ++( *this );
00172 }
00173 
00174 bool TickIterator::areAlmostEqual( qreal r1, qreal r2 ) const
00175 {
00176     if ( !m_isLogarithmic ) {
00177         return qAbs( r2 - r1 ) < ( m_dimension.end - m_dimension.start ) * 1e-6;
00178     } else {
00179         return qAbs( r2 - r1 ) < qMax( qAbs( r1 ), qAbs( r2 ) ) * 0.01;
00180     }
00181 }
00182 
00183 bool TickIterator::isHigherPrecedence( qreal importantTick, qreal unimportantTick ) const
00184 {
00185     return importantTick != std::numeric_limits< qreal >::infinity() &&
00186            ( importantTick <= unimportantTick || areAlmostEqual( importantTick, unimportantTick ) );
00187 }
00188 
00189 void TickIterator::computeMajorTickLabel()
00190 {
00191     if ( m_manualLabelIndex >= 0 ) {
00192         m_text = m_manualLabelTexts[ m_manualLabelIndex++ ];
00193         if ( m_manualLabelIndex >= m_manualLabelTexts.count() ) {
00194             // manual label texts repeat if there are less label texts than ticks on an axis
00195             m_manualLabelIndex = 0;
00196         }
00197         m_type = m_majorThinningFactor > 1 ? MajorTickManualShort : MajorTickManualLong;
00198     } else {
00199         // if m_axis is null, we are dealing with grid lines. grid lines never need labels.
00200         if ( m_axis && ( m_majorLabelCount++ % m_majorThinningFactor ) == 0 ) {
00201             m_text = QString::number( m_position ); // TODO proper number formatting
00202         } else {
00203             m_text.clear();
00204         }
00205         m_type = MajorTick;
00206     }
00207 }
00208 
00209 void TickIterator::operator++()
00210 {
00211     if ( isAtEnd() ) {
00212         return;
00213     }
00214     const qreal inf = std::numeric_limits< qreal >::infinity();
00215 
00216     // make sure to find the next tick at a value strictly greater than m_position
00217 
00218     if ( !m_annotations.isEmpty() ) {
00219         QMap< qreal, QString >::ConstIterator it = m_annotations.upperBound( m_position );
00220         if ( it != m_annotations.constEnd() ) {
00221             m_position = it.key();
00222             m_text = it.value();
00223             m_type = CustomTick;
00224         } else {
00225             m_position = inf;
00226         }
00227     } else {
00228         // advance the calculated ticks
00229         if ( m_isLogarithmic ) {
00230             while ( m_majorTick <= m_position ) {
00231                 m_majorTick *= m_position >= 0 ? 10 : 0.1;
00232             }
00233             while ( m_minorTick <= m_position ) {
00234                 // the next major tick position should be greater than this
00235                 m_minorTick += m_majorTick * ( m_position >= 0 ? 0.1 : 1.0 );
00236             }
00237         } else {
00238             while ( m_majorTick <= m_position ) {
00239                 m_majorTick += m_dimension.stepWidth;
00240             }
00241             while ( m_minorTick <= m_position ) {
00242                 m_minorTick += m_dimension.subStepWidth;
00243             }
00244         }
00245 
00246         while ( m_customTickIndex >= 0 && m_customTick <= m_position ) {
00247             if ( ++m_customTickIndex >= m_customTicks.count() ) {
00248                 m_customTickIndex = -1;
00249                 m_customTick = inf;
00250                 break;
00251             }
00252             m_customTick = m_customTicks.at( m_customTickIndex );
00253         }
00254 
00255         // now see which kind of tick we'll have
00256         if ( isHigherPrecedence( m_customTick, m_majorTick ) && isHigherPrecedence( m_customTick, m_minorTick )  ) {
00257             m_position = m_customTick;
00258             computeMajorTickLabel();
00259             // override the MajorTick type here because those tick's labels are collision-tested, which we don't want
00260             // for custom ticks. they may be arbitrarily close to other ticks, causing excessive label thinning.
00261             if ( m_type == MajorTick ) {
00262                 m_type = CustomTick;
00263             }
00264         } else if ( isHigherPrecedence( m_majorTick, m_minorTick ) ) {
00265             m_position = m_majorTick;
00266             if ( m_minorTick != inf ) {
00267                 // realign minor to major
00268                 m_minorTick = m_majorTick;
00269             }
00270             computeMajorTickLabel();
00271        } else if ( m_minorTick != inf ) {
00272             m_position = m_minorTick;
00273             m_text.clear();
00274             m_type = MinorTick;
00275         } else {
00276             m_position = inf;
00277         }
00278     }
00279 
00280     if ( m_position > m_dimension.end ) {
00281         m_position = inf; // make isAtEnd() return true
00282         m_text.clear();
00283         m_type = NoTick;
00284     }
00285 }
00286 
00287 CartesianAxis::CartesianAxis( AbstractCartesianDiagram* diagram )
00288     : AbstractAxis ( new Private( diagram, this ), diagram )
00289 {
00290     init();
00291 }
00292 
00293 CartesianAxis::~CartesianAxis()
00294 {
00295     // when we remove the first axis it will unregister itself and
00296     // propagate the next one to the primary, thus the while loop
00297     while ( d->mDiagram ) {
00298         AbstractCartesianDiagram *cd = qobject_cast< AbstractCartesianDiagram* >( d->mDiagram );
00299         cd->takeAxis( this );
00300     }
00301     KDAB_FOREACH( AbstractDiagram *diagram, d->secondaryDiagrams ) {
00302         AbstractCartesianDiagram *cd = qobject_cast< AbstractCartesianDiagram* >( diagram );
00303         cd->takeAxis( this );
00304     }
00305 }
00306 
00307 void CartesianAxis::init()
00308 {
00309     d->customTickLength = 3;
00310     d->position = Bottom;
00311     setCachedSizeDirty();
00312     connect( this, SIGNAL( coordinateSystemChanged() ), SLOT( coordinateSystemChanged() ) );
00313 }
00314 
00315 
00316 bool CartesianAxis::compare( const CartesianAxis* other ) const
00317 {
00318     if ( other == this ) {
00319         return true;
00320     }
00321     if ( !other ) {
00322         return false;
00323     }
00324     return  AbstractAxis::compare( other ) && ( position() == other->position() ) &&
00325             ( titleText() == other->titleText() ) &&
00326             ( titleTextAttributes() == other->titleTextAttributes() );
00327 }
00328 
00329 void CartesianAxis::coordinateSystemChanged()
00330 {
00331     layoutPlanes();
00332 }
00333 
00334 
00335 void CartesianAxis::setTitleText( const QString& text )
00336 {
00337     d->titleText = text;
00338     layoutPlanes();
00339 }
00340 
00341 QString CartesianAxis::titleText() const
00342 {
00343     return d->titleText;
00344 }
00345 
00346 void CartesianAxis::setTitleTextAttributes( const TextAttributes &a )
00347 {
00348     d->titleTextAttributes = a;
00349     d->useDefaultTextAttributes = false;
00350     layoutPlanes();
00351 }
00352 
00353 TextAttributes CartesianAxis::titleTextAttributes() const
00354 {
00355     if ( hasDefaultTitleTextAttributes() ) {
00356         TextAttributes ta( textAttributes() );
00357         Measure me( ta.fontSize() );
00358         me.setValue( me.value() * 1.5 );
00359         ta.setFontSize( me );
00360         return ta;
00361     }
00362     return d->titleTextAttributes;
00363 }
00364 
00365 void CartesianAxis::resetTitleTextAttributes()
00366 {
00367     d->useDefaultTextAttributes = true;
00368     layoutPlanes();
00369 }
00370 
00371 bool CartesianAxis::hasDefaultTitleTextAttributes() const
00372 {
00373     return d->useDefaultTextAttributes;
00374 }
00375 
00376 
00377 void CartesianAxis::setPosition ( Position p )
00378 {
00379     d->position = p;
00380     layoutPlanes();
00381 }
00382 
00383 #if QT_VERSION < 0x040400 || defined(Q_COMPILER_MANGLES_RETURN_TYPE)
00384 const
00385 #endif
00386 CartesianAxis::Position CartesianAxis::position() const
00387 {
00388     return d->position;
00389 }
00390 
00391 void CartesianAxis::layoutPlanes()
00392 {
00393     if ( ! d->diagram() || ! d->diagram()->coordinatePlane() ) {
00394         return;
00395     }
00396     AbstractCoordinatePlane* plane = d->diagram()->coordinatePlane();
00397     if ( plane ) {
00398         plane->layoutPlanes();
00399     }
00400 }
00401 
00402 static bool referenceDiagramIsBarDiagram( const AbstractDiagram * diagram )
00403 {
00404     const AbstractCartesianDiagram * dia =
00405             qobject_cast< const AbstractCartesianDiagram * >( diagram );
00406     if ( dia && dia->referenceDiagram() )
00407         dia = dia->referenceDiagram();
00408     return qobject_cast< const BarDiagram* >( dia ) != 0;
00409 }
00410 
00411 static bool referenceDiagramNeedsCenteredAbscissaTicks( const AbstractDiagram *diagram )
00412 {
00413     const AbstractCartesianDiagram * dia =
00414             qobject_cast< const AbstractCartesianDiagram * >( diagram );
00415     if ( dia && dia->referenceDiagram() )
00416         dia = dia->referenceDiagram();
00417     if ( qobject_cast< const BarDiagram* >( dia ) )
00418         return true;
00419     if ( qobject_cast< const StockDiagram* >( dia ) )
00420         return true;
00421 
00422     const LineDiagram * lineDiagram = qobject_cast< const LineDiagram* >( dia );
00423     return lineDiagram && lineDiagram->centerDataPoints();
00424 }
00425 
00426 bool CartesianAxis::isAbscissa() const
00427 {
00428     const Qt::Orientation diagramOrientation = referenceDiagramIsBarDiagram( d->diagram() ) ? ( ( BarDiagram* )( d->diagram() ) )->orientation()
00429                                                                                             : Qt::Vertical;
00430     return diagramOrientation == Qt::Vertical ? position() == Bottom || position() == Top
00431                                               : position() == Left   || position() == Right;
00432 }
00433 
00434 bool CartesianAxis::isOrdinate() const
00435 {
00436     return !isAbscissa();
00437 }
00438 
00439 void CartesianAxis::paint( QPainter* painter )
00440 {
00441     if ( !d->diagram() || !d->diagram()->coordinatePlane() ) {
00442         return;
00443     }
00444     PaintContext ctx;
00445     ctx.setPainter ( painter );
00446     ctx.setCoordinatePlane( d->diagram()->coordinatePlane() );
00447 
00448     ctx.setRectangle( QRectF( areaGeometry() ) );
00449     PainterSaver painterSaver( painter );
00450     paintCtx( &ctx );
00451 }
00452 
00453 const TextAttributes CartesianAxis::Private::titleTextAttributesWithAdjustedRotation() const
00454 {
00455     TextAttributes titleTA( titleTextAttributes );
00456     int rotation = titleTA.rotation();
00457     if ( position == Left || position == Right ) {
00458         rotation += 270;
00459     }
00460     if ( rotation >= 360 ) {
00461         rotation -= 360;
00462     }
00463     // limit the allowed values to 0, 90, 180, 270
00464     rotation = ( rotation / 90 ) * 90;
00465     titleTA.setRotation( rotation );
00466     return titleTA;
00467 }
00468 
00469 QString CartesianAxis::Private::customizedLabelText( const QString& text, Qt::Orientation orientation,
00470                                                      qreal value ) const
00471 {
00472     // ### like in the old code, using int( value ) as column number...
00473     QString withUnits = diagram()->unitPrefix( int( value ), orientation, true ) +
00474                         text +
00475                         diagram()->unitSuffix( int( value ), orientation, true );
00476     return axis()->customizedLabel( withUnits );
00477 }
00478 
00479 void CartesianAxis::setTitleSpace( qreal axisTitleSpace )
00480 {
00481     d->axisTitleSpace = axisTitleSpace;
00482 }
00483 
00484 qreal CartesianAxis::titleSpace() const
00485 {
00486     return d->axisTitleSpace;
00487 }
00488 
00489 void CartesianAxis::setTitleSize( qreal value )
00490 {
00491     d->axisSize = value;
00492 }
00493 
00494 qreal CartesianAxis::titleSize() const
00495 {
00496     return d->axisSize;
00497 }
00498 
00499 void CartesianAxis::Private::drawTitleText( QPainter* painter, CartesianCoordinatePlane* plane,
00500                                             const QRect& geoRect ) const
00501 {
00502     const TextAttributes titleTA( titleTextAttributesWithAdjustedRotation() );
00503     if ( titleTA.isVisible() ) {
00504         TextLayoutItem titleItem( titleText, titleTA, plane->parent(), KDChartEnums::MeasureOrientationMinimum,
00505                                   Qt::AlignHCenter | Qt::AlignVCenter );
00506         QPointF point;
00507         QSize size = titleItem.sizeHint();
00508         //FIXME(khz): We definitely need to provide a way that users can decide
00509         //            the position of an axis title.
00510         switch ( position ) {
00511         case Top:
00512             point.setX( geoRect.left() + geoRect.width() / 2 );
00513             point.setY( geoRect.top() + ( size.height() / 2 ) / axisTitleSpace );
00514             size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
00515             break;
00516         case Bottom:
00517             point.setX( geoRect.left() + geoRect.width() / 2 );
00518             point.setY( geoRect.bottom() - ( size.height() / 2 ) / axisTitleSpace );
00519             size.setWidth( qMin( size.width(), axis()->geometry().width() ) );
00520             break;
00521         case Left:
00522             point.setX( geoRect.left() + ( size.width() / 2 ) / axisTitleSpace );
00523             point.setY( geoRect.top() + geoRect.height() / 2 );
00524             size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
00525             break;
00526         case Right:
00527             point.setX( geoRect.right() - ( size.width() / 2 ) / axisTitleSpace );
00528             point.setY( geoRect.top() + geoRect.height() / 2 );
00529             size.setHeight( qMin( size.height(), axis()->geometry().height() ) );
00530             break;
00531         }
00532         const PainterSaver painterSaver( painter );
00533         painter->translate( point );
00534         titleItem.setGeometry( QRect( QPoint( -size.width() / 2, -size.height() / 2 ), size ) );
00535         titleItem.paint( painter );
00536     }
00537 }
00538 
00539 bool CartesianAxis::Private::isVertical() const
00540 {
00541     // Determine the diagram that specifies the orientation of the diagram
00542     // That diagram is the reference diagram, if it exists, or otherwise the diagram itself.
00543     // Note: In KDChart 2.3 or earlier, only a bar diagram can be vertical instead of horizontal.
00544     const AbstractCartesianDiagram* refDiagram = qobject_cast< const AbstractCartesianDiagram * >( diagram() );
00545     if ( refDiagram && refDiagram->referenceDiagram() ) {
00546         refDiagram = refDiagram->referenceDiagram();
00547     }
00548     const BarDiagram* barDiagram = qobject_cast< const BarDiagram* >( refDiagram );
00549     Qt::Orientation diagramOrientation = barDiagram ? barDiagram->orientation() : Qt::Vertical;
00550     bool isDiagramVertical = diagramOrientation == Qt::Vertical;
00551     return ( axis()->isOrdinate() && isDiagramVertical ) || ( axis()->isAbscissa() && !isDiagramVertical );
00552 }
00553 
00554 void CartesianAxis::paintCtx( PaintContext* context )
00555 {
00556     Q_ASSERT_X ( d->diagram(), "CartesianAxis::paint",
00557                  "Function call not allowed: The axis is not assigned to any diagram." );
00558 
00559     CartesianCoordinatePlane* plane = dynamic_cast<CartesianCoordinatePlane*>( context->coordinatePlane() );
00560     Q_ASSERT_X ( plane, "CartesianAxis::paint",
00561                  "Bad function call: PaintContext::coordinatePlane() NOT a cartesian plane." );
00562 
00563     // note: Not having any data model assigned is no bug
00564     //       but we can not draw an axis then either.
00565     if ( !d->diagram()->model() ) {
00566         return;
00567     }
00568 
00569     const bool centerTicks = referenceDiagramNeedsCenteredAbscissaTicks( d->diagram() ) && isAbscissa();
00570 
00571     XySwitch geoXy( d->isVertical() );
00572 
00573     QPainter* const painter = context->painter();
00574 
00575     // determine the position of the axis (also required for labels) and paint it
00576 
00577     qreal transversePosition;
00578     {
00579         DataDimension dimX = plane->gridDimensionsList().first();
00580         DataDimension dimY = plane->gridDimensionsList().last();
00581         QPointF start( dimX.start, dimY.start );
00582         QPointF end( dimX.end, dimY.end );
00583         // consider this: you can turn a diagonal line into a horizontal or vertical line on any
00584         // edge by changing just one of its four coordinates.
00585         switch ( position() ) {
00586         case CartesianAxis::Bottom:
00587             end.setY( dimY.start );
00588             break;
00589         case CartesianAxis::Top:
00590             start.setY( dimY.end );
00591             break;
00592         case CartesianAxis::Left:
00593             end.setX( dimX.start );
00594             break;
00595         case CartesianAxis::Right:
00596             start.setX( dimX.end );
00597             break;
00598         }
00599         if ( rulerAttributes().showRulerLine() ) {
00600             bool clipSaved = context->painter()->hasClipping();
00601             painter->setClipping( false );
00602             painter->drawLine( plane->translate( start ), plane->translate( end ) );
00603             painter->setClipping( clipSaved );
00604         }
00605         transversePosition = geoXy( start.y(), start.x() );
00606     }
00607 
00608     // paint ticks and labels
00609 
00610     TextAttributes labelTA = textAttributes();
00611     RulerAttributes rulerAttr = rulerAttributes();
00612     const bool isBarChartAbscissa = referenceDiagramIsBarDiagram( d->diagram() ) && isAbscissa();
00613     if ( isBarChartAbscissa ) {
00614         // in the abscissa of a bar diagram the x values are not usually displayed as numbers,
00615         // so the number zero has no special meaning
00616         rulerAttr.setShowZeroLabel( true );
00617     }
00618 
00619     int labelThinningFactor = 1;
00620     // TODO: label thinning also when grid line distance < 4 pixels, not only when labels collide
00621     TextLayoutItem *tickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(),
00622                                                     KDChartEnums::MeasureOrientationMinimum, Qt::AlignLeft );
00623     TextLayoutItem *prevTickLabel = 0;
00624     QPointF prevTickLabelPos;
00625     enum {
00626         Layout = 0,
00627         Painting,
00628         Done
00629     };
00630     for ( int step = labelTA.isVisible() ? Layout : Painting; step < Done; step++ ) {
00631         delete prevTickLabel;
00632         prevTickLabel = 0;
00633         for ( TickIterator it( this, plane, labelThinningFactor, isBarChartAbscissa ); !it.isAtEnd(); ++it ) {
00634             if ( !rulerAttr.showZeroLabel() && it.areAlmostEqual( it.position(), 0.0 ) ) {
00635                 continue;
00636             }
00637             const qreal drawPos = it.position() + ( centerTicks ? 0.5 : 0. );
00638             QPointF onAxis = plane->translate( geoXy( QPointF( drawPos, transversePosition ) ,
00639                                                       QPointF( transversePosition, drawPos ) ) );
00640             const bool isOutwardsPositive = position() == Bottom || position() == Right;
00641 
00642             // paint the tick mark
00643 
00644             QPointF tickEnd = onAxis;
00645             qreal tickLen = tickLength( it.type() == TickIterator::MinorTick );
00646             geoXy.lvalue( tickEnd.ry(), tickEnd.rx() ) += isOutwardsPositive ? tickLen : -tickLen;
00647 
00648             // those adjustments are required to paint the ticks exactly on the axis and of the right length
00649             if ( position() == Top ) {
00650                 onAxis.ry() += 1;
00651                 tickEnd.ry() += 1;
00652             } else if ( position() == Left ) {
00653                 tickEnd.rx() += 1;
00654             }
00655 
00656             if ( step == Painting ) {
00657                 painter->save();
00658                 if ( rulerAttr.hasTickMarkPenAt( it.position() ) ) {
00659                     painter->setPen( rulerAttr.tickMarkPen( it.position() ) );
00660                 } else {
00661                     painter->setPen( it.type() == TickIterator::MinorTick ? rulerAttr.minorTickMarkPen()
00662                                                                           : rulerAttr.majorTickMarkPen() );
00663                 }
00664                 painter->drawLine( onAxis, tickEnd );
00665                 painter->restore();
00666             }
00667 
00668             if ( it.text().isEmpty() || !labelTA.isVisible() ) {
00669                 // the following code in the loop is only label painting, so skip it
00670                 continue;
00671             }
00672 
00673             // paint the label
00674 
00675             QString text = it.text();
00676             if ( it.type() == TickIterator::MajorTick ) {
00677                 text = d->customizedLabelText( text, geoXy( Qt::Horizontal, Qt::Vertical ), it.position() );
00678             }
00679             tickLabel->setText( text );
00680             QSizeF size = QSizeF( tickLabel->sizeHint() );
00681             QPolygon labelPoly = tickLabel->boundingPolygon();
00682             Q_ASSERT( labelPoly.count() == 4 );
00683 
00684             // for alignment, find the label polygon edge "most parallel" and closest to the axis
00685 
00686             int axisAngle = 0;
00687             switch ( position() ) {
00688             case Bottom:
00689                 axisAngle = 0; break;
00690             case Top:
00691                 axisAngle = 180; break;
00692             case Right:
00693                 axisAngle = 270; break;
00694             case Left:
00695                 axisAngle = 90; break;
00696             default:
00697                 Q_ASSERT( false );
00698             }
00699             // the left axis is not actually pointing down and the top axis not actually pointing
00700             // left, but their corresponding closest edges of a rectangular unrotated label polygon are.
00701 
00702             int relAngle = axisAngle - labelTA.rotation() + 45;
00703             if ( relAngle < 0 ) {
00704                 relAngle += 360;
00705             }
00706             int polyCorner1 = relAngle / 90;
00707             QPoint p1 = labelPoly.at( polyCorner1 );
00708             QPoint p2 = labelPoly.at( polyCorner1 == 3 ? 0 : ( polyCorner1 + 1 ) );
00709 
00710             QPointF labelPos = tickEnd;
00711 
00712             qreal labelMargin = rulerAttr.labelMargin();
00713             if ( labelMargin < 0 ) {
00714                 labelMargin = QFontMetricsF( tickLabel->realFont() ).height() * 0.5;
00715             }
00716             labelMargin -= tickLabel->marginWidth(); // make up for the margin that's already there
00717 
00718             switch ( position() ) {
00719             case Left:
00720                 labelPos += QPointF( -size.width() - labelMargin,
00721                                      -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
00722                 break;
00723             case Right:
00724                 labelPos += QPointF( labelMargin,
00725                                      -0.45 * size.height() - 0.5 * ( p1.y() + p2.y() ) );
00726                 break;
00727             case Top:
00728                 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
00729                                      -size.height() - labelMargin );
00730                 break;
00731             case Bottom:
00732                 labelPos += QPointF( -0.45 * size.width() - 0.5 * ( p1.x() + p2.x() ),
00733                                      labelMargin );
00734                 break;
00735             }
00736 
00737             tickLabel->setGeometry( QRect( labelPos.toPoint(), size.toSize() ) );
00738 
00739             if ( step == Painting ) {
00740                 tickLabel->paint( painter );
00741             }
00742 
00743             // collision check the current label against the previous one
00744 
00745             // like in the old code, we don't shorten or decimate labels if they are already the
00746             // manual short type, or if they are the manual long type and on the vertical axis
00747             // ### they can still collide though, especially when they're rotated!
00748             if ( step == Layout ) {
00749                 int spaceSavingRotation = geoXy( 270, 0 );
00750                 bool canRotate = labelTA.autoRotate() && labelTA.rotation() != spaceSavingRotation;
00751                 const bool canShortenLabels = !geoXy.isY && it.type() == TickIterator::MajorTickManualLong &&
00752                                               it.hasShorterLabels();
00753                 bool collides = false;
00754                 if ( it.type() == TickIterator::MajorTick || canShortenLabels || canRotate ) {
00755                     if ( !prevTickLabel ) {
00756                         // this is the first label of the axis, so just instantiate the second
00757                         prevTickLabel = tickLabel;
00758                         tickLabel = new TextLayoutItem( QString(), labelTA, plane->parent(),
00759                                                         KDChartEnums::MeasureOrientationMinimum, Qt::AlignLeft );
00760                     } else {
00761                         collides = tickLabel->intersects( *prevTickLabel, labelPos, prevTickLabelPos );
00762                         qSwap( prevTickLabel, tickLabel );
00763                     }
00764                     prevTickLabelPos = labelPos;
00765                 }
00766                 if ( collides ) {
00767                     // to make room, we try in order: shorten, rotate, decimate
00768                     if ( canRotate && !canShortenLabels ) {
00769                         labelTA.setRotation( spaceSavingRotation );
00770                         // tickLabel will be reused in the next round
00771                         tickLabel->setTextAttributes( labelTA );
00772                     } else {
00773                         labelThinningFactor++;
00774                     }
00775                     step--; // relayout
00776                     break;
00777                 }
00778             }
00779         }
00780     }
00781     delete tickLabel;
00782     tickLabel = 0;
00783     delete prevTickLabel;
00784     prevTickLabel = 0;
00785 
00786     if ( ! titleText().isEmpty() ) {
00787         d->drawTitleText( painter, plane, d->axis()->geometry() );
00788     }
00789 }
00790 
00791 /* pure virtual in QLayoutItem */
00792 bool CartesianAxis::isEmpty() const
00793 {
00794     return false; // if the axis exists, it has some (perhaps default) content
00795 }
00796 
00797 /* pure virtual in QLayoutItem */
00798 Qt::Orientations CartesianAxis::expandingDirections() const
00799 {
00800     Qt::Orientations ret;
00801     switch ( position() ) {
00802     case Bottom:
00803     case Top:
00804         ret = Qt::Horizontal;
00805         break;
00806     case Left:
00807     case Right:
00808         ret = Qt::Vertical;
00809         break;
00810     default:
00811         Q_ASSERT( false );
00812         break;
00813     };
00814     return ret;
00815 }
00816 
00817 void CartesianAxis::setCachedSizeDirty() const
00818 {
00819     d->cachedMaximumSize = QSize();
00820 }
00821 
00822 /* pure virtual in QLayoutItem */
00823 QSize CartesianAxis::maximumSize() const
00824 {
00825     if ( ! d->cachedMaximumSize.isValid() )
00826         d->cachedMaximumSize = d->calculateMaximumSize();
00827     return d->cachedMaximumSize;
00828 }
00829 
00830 QSize CartesianAxis::Private::calculateMaximumSize() const
00831 {
00832     if ( !diagram() ) {
00833         return QSize();
00834     }
00835 
00836     CartesianCoordinatePlane* plane = dynamic_cast< CartesianCoordinatePlane* >( diagram()->coordinatePlane() );
00837     Q_ASSERT( plane );
00838     QObject* refArea = plane->parent();
00839     const bool isBarChartAbscissa = referenceDiagramIsBarDiagram( diagram() ) && axis()->isAbscissa();
00840 
00841     // we ignore:
00842     // - label thinning (expensive, not worst case and we want worst case)
00843     // - label autorotation (expensive, obscure feature(?))
00844     // - axis length (it is determined by the plane / diagram / chart anyway)
00845     // - the title's influence on axis length; this one might be TODO. See KDCH-863.
00846 
00847     XySwitch geoXy( isVertical() );
00848     qreal size = 0; // this is the size transverse to the axis direction
00849 
00850     if ( mAxis->textAttributes().isVisible() ) {
00851         TextLayoutItem tickLabel( QString(), mAxis->textAttributes(), refArea,
00852                                   KDChartEnums::MeasureOrientationMinimum, Qt::AlignLeft );
00853         const qreal tickLabelSpacing = 1; // this is implicitly defined in paintCtx()
00854 
00855         for ( TickIterator it( axis(), plane, 1, isBarChartAbscissa ); !it.isAtEnd(); ++it ) {
00856             qreal labelSize = 0.0;
00857             if ( !it.text().isEmpty() ) {
00858                 tickLabel.setText( it.text() );
00859                 QSize sz = tickLabel.sizeHint();
00860                 labelSize = geoXy( sz.height(), sz.width() );
00861             }
00862             qreal tickLength = axis()->tickLength( it.type() == TickIterator::MinorTick );
00863             size = qMax( size, tickLength + tickLabelSpacing + labelSize );
00864         }
00865     }
00866 
00867     const TextAttributes titleTA = titleTextAttributesWithAdjustedRotation();
00868     if ( titleTA.isVisible() && !axis()->titleText().isEmpty() ) {
00869         TextLayoutItem title( axis()->titleText(), titleTA, refArea, KDChartEnums::MeasureOrientationMinimum,
00870                               Qt::AlignHCenter | Qt::AlignVCenter );
00871 
00872         QFontMetricsF titleFM( title.realFont(), GlobalMeasureScaling::paintDevice() );
00873         size += geoXy( titleFM.height() * 0.33, titleFM.averageCharWidth() * 0.55 ); // spacing
00874         size += geoXy( title.sizeHint().height(), title.sizeHint().width() );
00875     }
00876 
00877     // the size parallel to the axis direction is not determined by us, so we just return 1
00878     return QSize( geoXy( 1, int( size ) ), geoXy( int ( size ), 1 ) );
00879 }
00880 
00881 /* pure virtual in QLayoutItem */
00882 QSize CartesianAxis::minimumSize() const
00883 {
00884     return maximumSize();
00885 }
00886 
00887 /* pure virtual in QLayoutItem */
00888 QSize CartesianAxis::sizeHint() const
00889 {
00890     return maximumSize();
00891 }
00892 
00893 /* pure virtual in QLayoutItem */
00894 void CartesianAxis::setGeometry( const QRect& r )
00895 {
00896     if ( d->geometry != r ) {
00897         d->geometry = r;
00898         setCachedSizeDirty();
00899     }
00900 }
00901 
00902 /* pure virtual in QLayoutItem */
00903 QRect CartesianAxis::geometry() const
00904 {
00905     return d->geometry;
00906 }
00907 
00908 void CartesianAxis::setCustomTickLength( int value )
00909 {
00910     d->customTickLength = value;
00911 }
00912 
00913 int CartesianAxis::customTickLength() const
00914 {
00915     return d->customTickLength;
00916 }
00917 
00918 int CartesianAxis::tickLength( bool subUnitTicks ) const
00919 {
00920     const RulerAttributes& rulerAttr = rulerAttributes();
00921     return subUnitTicks ? rulerAttr.minorTickMarkLength() : rulerAttr.majorTickMarkLength();
00922 }
00923 
00924 QMap< qreal, QString > CartesianAxis::annotations() const
00925 {
00926     return d->annotations;
00927 }
00928 
00929 void CartesianAxis::setAnnotations( const QMap< qreal, QString >& annotations )
00930 {
00931     if ( d->annotations == annotations )
00932         return;
00933 
00934     d->annotations = annotations;
00935     update();
00936 }
00937 
00938 QList< qreal > CartesianAxis::customTicks() const
00939 {
00940     return d->customTicksPositions;
00941 }
00942 
00943 void CartesianAxis::setCustomTicks( const QList< qreal >& customTicksPositions )
00944 {
00945     if ( d->customTicksPositions == customTicksPositions )
00946         return;
00947 
00948     d->customTicksPositions = customTicksPositions;
00949     update();
00950 }
•All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Defines

Klarälvdalens Datakonsult AB (KDAB)
Qt-related services and products
http://www.kdab.com/
http://www.kdab.com/products/kd-chart/