KDChartPieDiagram.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 <QDebug>
00024 #include <QPainter>
00025 #include <QStack>
00026 
00027 #include "KDChartPieDiagram.h"
00028 #include "KDChartPieDiagram_p.h"
00029 
00030 #include "KDChartAttributesModel.h"
00031 #include "KDChartPaintContext.h"
00032 #include "KDChartPieAttributes.h"
00033 #include "KDChartThreeDPieAttributes.h"
00034 #include "KDChartPainterSaver_p.h"
00035 #include "KDChartDataValueAttributes.h"
00036 #include "KDChartNullPaintDevice.h"
00037 
00038 #include <KDABLibFakes>
00039 
00040 
00041 using namespace KDChart;
00042 
00043 PieDiagram::Private::Private()
00044 {
00045 }
00046 
00047 PieDiagram::Private::~Private() {}
00048 
00049 #define d d_func()
00050 
00051 PieDiagram::PieDiagram( QWidget* parent, PolarCoordinatePlane* plane ) :
00052     AbstractPieDiagram( new Private(), parent, plane )
00053 {
00054     init();
00055 }
00056 
00057 PieDiagram::~PieDiagram()
00058 {
00059 }
00060 
00061 void PieDiagram::init()
00062 {
00063 }
00064 
00068 PieDiagram * PieDiagram::clone() const
00069 {
00070     return new PieDiagram( new Private( *d ) );
00071 }
00072 
00073 const QPair<QPointF, QPointF> PieDiagram::calculateDataBoundaries () const
00074 {
00075     if ( !checkInvariants( true ) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
00076 
00077     const PieAttributes attrs( pieAttributes( model()->index( 0, 0, rootIndex() ) ) );
00078 
00079     QPointF bottomLeft ( QPointF( 0, 0 ) );
00080     QPointF topRight;
00081     // If we explode, we need extra space for the pie slice that has
00082     // the largest explosion distance.
00083     if ( attrs.explode() ) {
00084         const int colCount = columnCount();
00085         qreal maxExplode = 0.0;
00086         for( int j = 0; j < colCount; ++j ){
00087             const PieAttributes columnAttrs( pieAttributes( model()->index( 0, j, rootIndex() ) ) );
00088             maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() );
00089         }
00090         topRight = QPointF( 1.0+maxExplode, 1.0+maxExplode );
00091     }else{
00092         topRight = QPointF( 1.0, 1.0 );
00093     }
00094     return QPair<QPointF, QPointF> ( bottomLeft,  topRight );
00095 }
00096 
00097 
00098 void PieDiagram::paintEvent( QPaintEvent* )
00099 {
00100     QPainter painter ( viewport() );
00101     PaintContext ctx;
00102     ctx.setPainter ( &painter );
00103     ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
00104     paint ( &ctx );
00105 }
00106 
00107 void PieDiagram::resizeEvent ( QResizeEvent*)
00108 {
00109 }
00110 
00111 void PieDiagram::resize ( const QSizeF& )
00112 {
00113 }
00114 
00115 static QRectF buildReferenceRect( const PolarCoordinatePlane* plane )
00116 {
00117     QRectF contentsRect;
00118 //qDebug() << "..........................................";
00119     QPointF referencePointAtTop = plane->translate( QPointF( 1, 0 ) );
00120     QPointF temp = plane->translate( QPointF( 0, 0 ) ) - referencePointAtTop;
00121     const double offset = temp.y();
00122     referencePointAtTop.setX( referencePointAtTop.x() - offset );
00123     contentsRect.setTopLeft( referencePointAtTop );
00124     contentsRect.setBottomRight( referencePointAtTop + QPointF( 2*offset, 2*offset) );
00125 //qDebug() << contentsRect;
00126     return contentsRect;
00127 }
00128 /*
00129 void PieDiagram::paint( PaintContext* ctx )
00130 {
00131     if ( !checkInvariants(true) ) return;
00132     const int colCount = model()->columnCount(rootIndex());
00133     QRectF contentsRect = buildReferenceRect( polarCoordinatePlane() );
00134     DataValueTextInfoList list;
00135     double startAngle = startPosition();
00136     double startAngleValueSpace = valueTotals() / 360 * startAngle;
00137     for ( int j=0; j<colCount; ++j ) {
00138         const double nextValue = qAbs( model()->data( model()->index( 0, j,rootIndex() ) ).toDouble() );
00139         double spanAngle = polarCoordinatePlane()->translatePolar( QPointF( nextValue, 1 ) ).x();
00140         if ( spanAngle == 0 ) continue;
00141         QBrush brush = qVariantValue<QBrush>( attributesModel()->headerData( j, Qt::Vertical, KDChart::DatasetBrushRole ) );
00142         QPen pen = qVariantValue<QPen>( attributesModel()->headerData( j, Qt::Vertical, KDChart::DatasetPenRole ) );
00143         PainterSaver painterSaver( ctx->painter() );
00144         ctx->painter()->setRenderHint ( QPainter::Antialiasing );
00145         ctx->painter()->setBrush( brush );
00146         ctx->painter()->setPen( pen );
00147 
00148         // Explosion support
00149         QRectF pieRect = contentsRect;
00150         if( explode() ) {
00151             QPointF oldCenter = contentsRect.center();
00152             QPointF newCenter = polarCoordinatePlane()->translate( QPointF( explodeFactor( j ),
00153                                                                             startAngleValueSpace + nextValue/2.0 ) );
00154             QPointF difference = newCenter - oldCenter;
00155             pieRect.translate( difference );
00156         }
00157 
00158         ctx->painter()->drawPie( pieRect, ( int ) ((-startAngle + 90 )), ( int ) (-spanAngle) );
00159         startAngle += spanAngle;
00160         startAngleValueSpace += nextValue;
00161     }
00162     d->clearListOfAlreadyDrawnDataValueTexts();
00163     DataValueTextInfoListIterator it( list );
00164     while ( it.hasNext() ) {
00165         const DataValueTextInfo& info = it.next();
00166         paintDataValueText( ctx->painter(), info.index, info.pos, info.value );
00167     }
00168 }
00169 */
00170 
00171 void PieDiagram::paint(PaintContext* ctx)
00172 {
00173     // Painting is a two stage process
00174     // In the first stage we figure out how much space is needed
00175     // for text labels.
00176     // In the second stage, we make use of that information and
00177     // perform the actual painting.
00178     QPainter* actualPainter = ctx->painter();
00179     QRectF textBoundingRect;
00180 
00181     // Use a null paint device and perform the first painting.
00182     KDChart::NullPaintDevice nullPd(ctx->rectangle().size().toSize());
00183     QPainter nullPainter(&nullPd);
00184     ctx->setPainter(&nullPainter);
00185     paintInternal(ctx, textBoundingRect);
00186         
00187         //edit start
00188         // point from the text is getting printed 
00189         /*QPoint currentPosition = textBoundingRect.bottomLeft().toPoint();
00190         
00191         QPoint textRectCenter = textBoundingRect.center().toPoint();
00192 
00193         qreal newX = currentPosition.x() - textRectCenter.x();
00194         qreal newY =  currentPosition.y() - textRectCenter.y();
00195         currentPosition.setX(newX);
00196         currentPosition.setY(newY);
00197 
00198         textBoundingRect.translate(currentPosition);*/
00199         //edit end
00200     // Now perform the real painting
00201     ctx->setPainter(actualPainter);
00202     paintInternal(ctx, textBoundingRect);
00203 }
00204 
00205 void PieDiagram::paintInternal(PaintContext* ctx, QRectF& textBoundingRect)
00206 {
00207     // note: Not having any data model assigned is no bug
00208     //       but we can not draw a diagram then either.
00209     if ( !checkInvariants(true) )
00210         return;
00211 
00212     d->reverseMapper.clear();
00213 
00214     const PieAttributes attrs( pieAttributes() );
00215     const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( model()->index( 0, 0, rootIndex() ) ) );
00216 
00217     const int colCount = columnCount();
00218 
00219     QRectF contentsRect( buildReferenceRect( polarCoordinatePlane() ) );
00220     contentsRect = ctx->rectangle();
00221 //    contentsRect = geometry();
00222 //qDebug() << contentsRect;
00223     if( contentsRect.isEmpty() )
00224         return;
00225 
00226     DataValueTextInfoList list;
00227     const qreal sum = valueTotals();
00228 
00229     if( sum == 0.0 ) //nothing to draw
00230         return;
00231 
00232     d->startAngles.resize( colCount );
00233     d->angleLens.resize( colCount );
00234 
00235     // compute position
00236     d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size
00237 
00238     // if the pies explode, we need to give them additional space =>
00239     // make the basic size smaller
00240     qreal maxExplode = 0.0;
00241     for( int j = 0; j < colCount; ++j ){
00242         const PieAttributes columnAttrs( pieAttributes( model()->index( 0, j, rootIndex() ) ) );
00243         maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() );
00244     }
00245     d->size /= ( 1.0 + 1.0 * maxExplode );
00246 
00247     if(!textBoundingRect.isEmpty())
00248     {
00249         // Find out the maximum distance from every corner of the rectangle with
00250         // the center.
00251         double maxDistance = 0, dist = 0;
00252 
00253         QPointF center = ctx->rectangle().center();
00254                 
00255                 dist = qAbs(textBoundingRect.right() - center.x());
00256         if(dist > maxDistance)
00257             maxDistance = dist;
00258 
00259         dist = qAbs(textBoundingRect.left() - center.x());
00260         if(dist > maxDistance)
00261             maxDistance = dist;
00262 
00263         dist = qAbs(textBoundingRect.top() - center.y());
00264         if(dist > maxDistance)
00265             maxDistance = dist;
00266 
00267         dist = qAbs(textBoundingRect.bottom() - center.y());
00268         if(dist > maxDistance)
00269             maxDistance = dist;
00270 
00271         double size = d->size;
00272         double diff = (2*maxDistance - d->size);
00273         if(diff > 0)
00274             d->size *= 1.0-(diff/size);
00275     }
00276 
00277     if(d->size < 0)
00278         d->size = 0; 
00279 
00280     qreal sizeFor3DEffect = 0.0;
00281     if ( ! threeDAttrs.isEnabled() ) {
00282 
00283         qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 );
00284         qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 );
00285         d->position = QRectF( x, y, d->size, d->size );
00286         d->position.translate( contentsRect.left(), contentsRect.top() );
00287     } else {
00288         // threeD: width is the maximum possible width; height is 1/2 of that
00289         qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 );
00290         qreal height = d->size;
00291         // make sure that the height plus the threeDheight is not more than the
00292         // available size
00293         if ( threeDAttrs.depth() >= 0.0 ) {
00294             // positive pie height: absolute value
00295             sizeFor3DEffect = threeDAttrs.depth();
00296             height = d->size - sizeFor3DEffect;
00297         } else {
00298             // negative pie height: relative value
00299             sizeFor3DEffect = - threeDAttrs.depth() / 100.0 * height;
00300             height = d->size - sizeFor3DEffect;
00301         }
00302         qreal y = ( contentsRect.height() == height ) ? 0.0 : ( ( contentsRect.height() - height - sizeFor3DEffect ) / 2.0 );
00303 
00304         d->position = QRectF( contentsRect.left() + x, contentsRect.top() + y,
00305                 d->size, height );
00306         //  d->position.moveBy( contentsRect.left(), contentsRect.top() );
00307     }
00308 
00309     const PolarCoordinatePlane * plane = polarCoordinatePlane();
00310     const qreal sectorsPerValue = 360.0 / sum;
00311     qreal currentValue = plane ? plane->startPosition() : 0.0;
00312 
00313     bool atLeastOneValue = false; // guard against completely empty tables
00314     QVariant vValY;
00315     for ( int iColumn = 0; iColumn < colCount; ++iColumn ) {
00316         // is there anything at all at this column?
00317         bool bOK;
00318         const double cellValue = qAbs( model()->data( model()->index( 0, iColumn, rootIndex() ) )
00319             .toDouble( &bOK ) );
00320 
00321         if( bOK ){
00322             d->startAngles[ iColumn ] = currentValue;
00323             d->angleLens[ iColumn ] = cellValue * sectorsPerValue;
00324             atLeastOneValue = true;
00325         } else { // mark as non-existent
00326             d->angleLens[ iColumn ] = 0.0;
00327             if ( iColumn > 0.0 )
00328                 d->startAngles[ iColumn ] = d->startAngles[ iColumn - 1 ];
00329             else
00330                 d->startAngles[ iColumn ] = currentValue;
00331         }
00332         //qDebug() << "d->startAngles["<<iColumn<<"] == " << d->startAngles[ iColumn ]
00333         //         << " +  d->angleLens["<<iColumn<<"]" << d->angleLens[ iColumn ]
00334         //         << " = " << d->startAngles[ iColumn ]+d->angleLens[ iColumn ];
00335 
00336         currentValue = d->startAngles[ iColumn ] + d->angleLens[ iColumn ];
00337     }
00338 
00339     // If there was no value at all, bail out, to avoid endless loops
00340     // later on (e.g. in findPieAt()).
00341     if( ! atLeastOneValue )
00342         return;
00343 
00344 
00345     // Find the backmost pie which is at +90° and needs to be drawn
00346     // first
00347     int backmostpie = findPieAt( 90, colCount );
00348     // Find the frontmost pie (at -90°/+270°) that should be drawn last
00349     int frontmostpie = findPieAt( 270, colCount );
00350     // the right- and the leftmost (only needed in some special cases...)
00351     int rightmostpie = findPieAt( 0, colCount );
00352     int leftmostpie = findPieAt( 180, colCount );
00353 
00354 
00355     int currentLeftPie = backmostpie;
00356     int currentRightPie = backmostpie;
00357 
00358     d->clearListOfAlreadyDrawnDataValueTexts();
00359 
00360     drawOnePie( ctx->painter(), &list, 0, backmostpie, granularity(), sizeFor3DEffect );
00361 
00362     if( backmostpie == frontmostpie )
00363     {
00364         if( backmostpie == leftmostpie )
00365             currentLeftPie = findLeftPie( currentLeftPie, colCount );
00366         if( backmostpie == rightmostpie )
00367             currentRightPie = findRightPie( currentRightPie, colCount );
00368     }
00369     while( currentLeftPie != frontmostpie )
00370     {
00371         if( currentLeftPie != backmostpie )
00372             drawOnePie( ctx->painter(), &list, 0, currentLeftPie, granularity(), sizeFor3DEffect );
00373         currentLeftPie = findLeftPie( currentLeftPie, colCount );
00374     }
00375     while( currentRightPie != frontmostpie )
00376     {
00377         if( currentRightPie != backmostpie )
00378             drawOnePie( ctx->painter(), &list, 0, currentRightPie, granularity(), sizeFor3DEffect );
00379         currentRightPie = findRightPie( currentRightPie, colCount );
00380     }
00381 
00382     // if the backmost pie is not the frontmost pie, we draw the frontmost at last
00383     if( backmostpie != frontmostpie || ! threeDPieAttributes().isEnabled() )
00384     {
00385         drawOnePie( ctx->painter(), &list, 0, frontmostpie, granularity(), sizeFor3DEffect );
00386     // otherwise, this gets a bit more complicated...
00387 /*    } else if( threeDPieAttributes().isEnabled() ) {
00388         //drawPieSurface( ctx->painter(), 0, frontmostpie, granularity() );
00389         const QModelIndex index = model()->index( 0, frontmostpie, rootIndex() );
00390         QPen pen = this->pen( index );
00391         ctx->painter()->setBrush( brush( index ) );
00392         if ( threeDAttrs.isEnabled() )
00393             pen.setColor( QColor( 0, 0, 0 ) );
00394         ctx->painter()->setPen( pen );
00395 
00396         qreal startAngle = d->startAngles[ frontmostpie ];
00397         if( startAngle > 360 )
00398             startAngle -= 360;
00399 
00400         qreal endAngle = startAngle + d->angleLens[ frontmostpie ];
00401         startAngle = qMax( startAngle, 180.0 );
00402 
00403         drawArcEffectSegment( ctx->painter(), piePosition( 0, frontmostpie),
00404                 sizeFor3DEffect, startAngle, endAngle, granularity() );*/
00405     }
00406 
00407     d->paintDataValueTextsAndMarkers(  this,  ctx,  list,  false, false, &textBoundingRect );
00408 }
00409 
00410 #if defined ( Q_WS_WIN)
00411 #define trunc(x) ((int)(x))
00412 #endif
00413 
00414 QRectF PieDiagram::piePosition( uint dataset, uint pie ) const
00415 {
00416     Q_UNUSED( dataset );
00417     qreal angleLen = d->angleLens[ pie ];
00418     qreal startAngle = d->startAngles[ pie ];
00419     QModelIndex index( model()->index( 0, pie, rootIndex() ) );
00420     const PieAttributes attrs( pieAttributes( index ) );
00421     const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) );
00422 
00423     QRectF drawPosition( d->position );
00424 
00425     if ( attrs.explode() ) {
00426         qreal explodeAngle = ( startAngle + angleLen / 2.0 );
00427         qreal explodeAngleRad = DEGTORAD( explodeAngle );
00428         qreal cosAngle = cos( explodeAngleRad );
00429         qreal sinAngle = -sin( explodeAngleRad );
00430         qreal explodeX = attrs.explodeFactor() * d->size / 2.0 * cosAngle;
00431         qreal explodeY = attrs.explodeFactor() * d->size / 2.0 * sinAngle;
00432         drawPosition.translate( explodeX, explodeY );
00433     }
00434     return drawPosition;
00435  }
00436 
00445 void PieDiagram::drawOnePie( QPainter* painter,
00446         DataValueTextInfoList* list,
00447         uint dataset, uint pie,
00448         qreal granularity,
00449         qreal threeDPieHeight )
00450 {
00451     Q_UNUSED( threeDPieHeight );
00452     // Is there anything to draw at all?
00453     const qreal angleLen = d->angleLens[ pie ];
00454     if ( angleLen ) {
00455         const QModelIndex index( model()->index( 0, pie, rootIndex() ) );
00456         const PieAttributes attrs( pieAttributes( index ) );
00457         const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) );
00458 
00459         const QRectF drawPosition = piePosition( dataset, pie );
00460 
00461         draw3DEffect( painter,
00462             drawPosition, dataset, pie,
00463             granularity,
00464             threeDAttrs,
00465             attrs.explode() );
00466 
00467         drawPieSurface( painter, list, dataset, pie, granularity );
00468     }
00469 }
00470 
00478 void PieDiagram::drawPieSurface( QPainter* painter,
00479         DataValueTextInfoList* list,
00480         uint dataset, uint pie,
00481         qreal granularity )
00482 {
00483     // Is there anything to draw at all?
00484     qreal angleLen = d->angleLens[ pie ];
00485     if ( angleLen ) {
00486         qreal startAngle = d->startAngles[ pie ];
00487 
00488         QModelIndex index( model()->index( 0, pie, rootIndex() ) );
00489         const PieAttributes attrs( pieAttributes( index ) );
00490         const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) );
00491 
00492         QRectF drawPosition = piePosition( dataset, pie );
00493 
00494         painter->setRenderHint ( QPainter::Antialiasing );
00495         painter->setBrush( brush( index ) );
00496         painter->setPen( pen( index ) );
00497 //        if ( threeDAttrs.isEnabled() )
00498 //            pen.setColor( QColor( 0, 0, 0 ) );
00499 //        painter->setPen( pen );
00500 
00501         if ( angleLen == 360 ) {
00502             // full circle, avoid nasty line in the middle
00503             painter->drawEllipse( drawPosition );
00504 
00505             //Add polygon to Reverse mapper for showing tool tips.
00506             QPolygonF poly( drawPosition );
00507             d->reverseMapper.addPolygon( index.row(), index.column(), poly );
00508         } else {
00509             // draw the top of this piece
00510             // Start with getting the points for the arc.
00511             const int arcPoints = static_cast<int>(trunc( angleLen / granularity ));
00512             QPolygonF poly( arcPoints+2 );
00513             qreal degree=0.0;
00514             int iPoint = 0;
00515             bool perfectMatch = false;
00516 
00517             while ( degree <= angleLen ){
00518                 poly[ iPoint ] = pointOnCircle( drawPosition, startAngle + degree );
00519                 //qDebug() << degree << angleLen << poly[ iPoint ];
00520                 perfectMatch = (degree == angleLen);
00521                 degree += granularity;
00522                 ++iPoint;
00523             }
00524             // if necessary add one more point to fill the last small gap
00525             if( ! perfectMatch ){
00526                 poly[ iPoint ] = pointOnCircle( drawPosition, startAngle + angleLen );
00527 
00528                 // add the center point of the piece
00529                 poly.append( drawPosition.center() );
00530             }else{
00531                 poly[ iPoint ] = drawPosition.center();
00532             }
00533             //find the value and paint it
00534             //fix value position
00535             d->reverseMapper.addPolygon( index.row(), index.column(), poly );
00536                         
00537             painter->drawPolygon( poly );
00538         }
00539         // the new code is setting the needed position points according to the slice:
00540         // all is calculated as if the slice were 'standing' on it's tip and the border
00541         // were on top, so North is the middle of the curved outside line and South is the tip
00542         //
00543         const qreal sum = valueTotals();
00544         const QPointF south = drawPosition.center();
00545         const QPointF southEast = south;
00546         const QPointF southWest = south;
00547         const QPointF north = pointOnCircle( drawPosition, startAngle + angleLen/2.0 );
00548 
00549         const QPointF northEast = pointOnCircle( drawPosition, startAngle );
00550         const QPointF northWest = pointOnCircle( drawPosition, startAngle + angleLen );
00551         QPointF center    = (south + north) / 2.0;
00552         const QPointF east      = (south + northEast) / 2.0;
00553         const QPointF west      = (south + northWest) / 2.0;
00554 
00555         CartesianDiagramDataCompressor::DataValueAttributesList allAttrs( d->aggregatedAttrs( this, index, 0 ) );
00556         const QFontMetrics * fm = (d->cachedFontMetrics( allAttrs.value(index).textAttributes().calculatedFont(d->plane,KDChartEnums::MeasureOrientationMinimum ), this ));
00557         if(!list->isEmpty())
00558         {
00559                 QRect textRect = fm->boundingRect(QString::number(list->last().value));
00560                 textRect.translated(center.toPoint());
00561                 QPoint textRectCenter = textRect.center();
00562                 qreal newX = center.x() - textRectCenter.x();
00563                 qreal newY =  center.y() - textRectCenter.y();
00564                 center.setX(newX);
00565                 center.setY(newY);
00566         }
00567 
00568         PositionPoints points( center, northWest, north, northEast, east, southEast, south, southWest, west);
00569         qreal topAngle = startAngle - 90;
00570         if( topAngle < 0.0 )
00571             topAngle += 360;
00572         points.setDegrees(KDChartEnums::PositionEast,      topAngle);
00573         points.setDegrees(KDChartEnums::PositionNorthEast, topAngle);
00574         points.setDegrees(KDChartEnums::PositionWest,      topAngle + angleLen);
00575         points.setDegrees(KDChartEnums::PositionNorthWest, topAngle + angleLen);
00576         points.setDegrees(KDChartEnums::PositionCenter,    topAngle + angleLen/2.0);
00577         points.setDegrees(KDChartEnums::PositionNorth,     topAngle + angleLen/2.0);
00578 
00579         //painter->drawText(points.mPositionCenter,QLatin1String("P"));
00580 
00581         d->appendDataValueTextInfoToList(
00582                 this, *list, index, 0,
00583                 points, Position::Center, Position::Center,
00584                 angleLen*sum / 360 );
00585 
00586         // The following, old code (since kdc 2.0.0) was not correct:
00587         // Settings made for the position had been totally ignored,
00588         // AND the center was NOT the center - except for pieces of 45 degrees size
00589         //
00590         // QLineF centerLine(  drawPosition.center(),
00591         //                 QPointF( (poly[ last - 2].x() + poly.first().x())/2,
00592         //                          ( poly.first().y() + poly[last-2].y() )/2 ) );
00593         // QPointF valuePos( ( centerLine.x1() + centerLine.x2() )/2,
00594         //                       ( centerLine.y1() + centerLine.y2() )/2 ) ;
00595         //
00596         // paintDataValueText( painter, index, valuePos, angleLen*sum / 360  );
00597     }
00598 }
00599 
00600 
00610 void PieDiagram::draw3DEffect( QPainter* painter,
00611         const QRectF& drawPosition,
00612         uint dataset, uint pie,
00613         qreal granularity,
00614         const ThreeDPieAttributes& threeDAttrs,
00615         bool /*explode*/ )
00616 {
00617     Q_UNUSED( dataset );
00618 
00619     if( ! threeDAttrs.isEnabled() )
00620         return;
00621 
00622     // NOTE: We cannot optimize away drawing some of the effects (even
00623     // when not exploding), because some of the pies might be left out
00624     // in future versions which would make some of the normally hidden
00625     // pies visible. Complex hidden-line algorithms would be much more
00626     // expensive than just drawing for nothing.
00627 
00628     // No need to save the brush, will be changed on return from this
00629     // method anyway.
00630     if( threeDAttrs.useShadowColors() ){
00631         const QPen pen = this->pen( model()->index( 0, pie, rootIndex() ) );
00632         painter->setBrush( QBrush( pen.color() ) );
00633     }
00634     //painter->setBrush( QBrush( threeDAttrs.dataShadow1Color( pie ),
00635     //            params()->shadowPattern() ) );
00636 
00637     qreal startAngle = d->startAngles[ pie ];
00638     qreal endAngle = startAngle + d->angleLens[ pie ];
00639     // Normalize angles
00640     while ( startAngle >= 360 )
00641         startAngle -= 360;
00642     while ( endAngle >= 360 )
00643         endAngle -= 360;
00644     Q_ASSERT( startAngle >= 0 && startAngle <= 360 );
00645     Q_ASSERT( endAngle >= 0 && endAngle <= 360 );
00646 
00647     //int centerY = drawPosition.center().y();
00648 
00649     if ( startAngle == endAngle ||
00650             startAngle == endAngle - 360 ) { // full circle
00651         drawArcEffectSegment( painter, drawPosition,
00652                 threeDAttrs.depth(),
00653                 180, 360, granularity );
00654     } else if ( startAngle <= 90 ) {
00655         if ( endAngle <= 90 ) {
00656             if ( startAngle <= endAngle ) {
00658                 drawStraightEffectSegment( painter, drawPosition,
00659                     threeDAttrs.depth(), startAngle );
00660                 drawUpperBrinkEffect( painter, drawPosition, endAngle );
00661             } else {
00663                 drawStraightEffectSegment( painter, drawPosition,
00664                     threeDAttrs.depth(), startAngle );
00665                 drawUpperBrinkEffect( painter, drawPosition, endAngle );
00666                 drawArcEffectSegment( painter, drawPosition,
00667                     threeDAttrs.depth(),
00668                     180, 360, granularity );
00669             }
00670         } else if ( endAngle <= 180 ) {
00673             drawStraightEffectSegment( painter, drawPosition,
00674                 threeDAttrs.depth(), startAngle );
00675             drawStraightEffectSegment( painter, drawPosition,
00676                 threeDAttrs.depth(), endAngle );
00677         } else if ( endAngle <= 270 ) {
00679             drawStraightEffectSegment( painter, drawPosition,
00680                 threeDAttrs.depth(), startAngle );
00681             drawStraightEffectSegment( painter, drawPosition,
00682                 threeDAttrs.depth(), endAngle );
00683             drawArcEffectSegment( painter, drawPosition,
00684                 threeDAttrs.depth(),
00685                 180, endAngle, granularity );
00686         } else { // 270*16 < endAngle < 360*16
00689             drawStraightEffectSegment( painter, drawPosition,
00690                 threeDAttrs.depth(), startAngle );
00691             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00692             drawArcEffectSegment( painter, drawPosition,
00693                 threeDAttrs.depth(),
00694                 180, endAngle, granularity );
00695         }
00696     } else if ( startAngle <= 180 ) {
00697         if ( endAngle <= 90 ) {
00698             drawArcEffectSegment( painter, drawPosition,
00699                 threeDAttrs.depth(),
00700                 180, 360, granularity );
00701             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00702             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00703         } else if ( endAngle <= 180 ) {
00704             if ( startAngle <= endAngle ) {
00707                 drawStraightEffectSegment( painter, drawPosition,
00708                     threeDAttrs.depth(), endAngle );
00709                 drawUpperBrinkEffect( painter, drawPosition, startAngle );
00710             } else {
00713                 drawStraightEffectSegment( painter, drawPosition,
00714                     threeDAttrs.depth(), endAngle );
00715                 drawUpperBrinkEffect( painter, drawPosition, startAngle );
00716                 drawArcEffectSegment( painter, drawPosition,
00717                     threeDAttrs.depth(),
00718                     180, 360, granularity );
00719             }
00720         } else if ( endAngle <= 270 ) {
00721             drawStraightEffectSegment( painter, drawPosition,
00722                 threeDAttrs.depth(), endAngle );
00723             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00724             drawArcEffectSegment( painter, drawPosition,
00725                 threeDAttrs.depth(),
00726                 180, endAngle, granularity );
00727         } else { // 270*16 < endAngle < 360*16
00728             drawArcEffectSegment( painter, drawPosition,
00729                 threeDAttrs.depth(),
00730                 180, endAngle, granularity );
00731             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00732             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00733         }
00734     } else if ( startAngle <= 270 ) {
00735         if ( endAngle <= 90 ) {
00736             drawArcEffectSegment( painter, drawPosition,
00737                 threeDAttrs.depth(),
00738                 startAngle, 360, granularity );
00739             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00740             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00741         } else if ( endAngle <= 180 ) {
00742             drawStraightEffectSegment( painter, drawPosition,
00743                 threeDAttrs.depth(), endAngle );
00744             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00745             drawArcEffectSegment( painter, drawPosition,
00746                 threeDAttrs.depth(),
00747                 startAngle, 360, granularity );
00748         } else if ( endAngle <= 270 ) {
00749             if ( startAngle <= endAngle ) {
00752                 drawStraightEffectSegment( painter, drawPosition,
00753                     threeDAttrs.depth(), endAngle );
00754                 drawUpperBrinkEffect( painter, drawPosition, startAngle );
00755                 drawArcEffectSegment( painter, drawPosition,
00756                     threeDAttrs.depth(),
00757                     startAngle, endAngle, granularity );
00758             } else {
00761                 drawStraightEffectSegment( painter, drawPosition,
00762                     threeDAttrs.depth(), endAngle );
00763                 drawUpperBrinkEffect( painter, drawPosition, startAngle );
00764                 drawArcEffectSegment( painter, drawPosition,
00765                     threeDAttrs.depth(),
00766                     180, endAngle, granularity );
00767                 drawArcEffectSegment( painter, drawPosition,
00768                     threeDAttrs.depth(),
00769                     startAngle, 360, granularity );
00770             }
00771         } else { // 270*16 < endAngle < 360*16
00772             drawArcEffectSegment( painter, drawPosition,
00773                 threeDAttrs.depth(),
00774                 startAngle, endAngle, granularity );
00775             drawUpperBrinkEffect( painter, drawPosition, startAngle );
00776             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00777         }
00778     } else { // 270*16 < startAngle < 360*16
00779         if ( endAngle <= 90 ) {
00780             drawStraightEffectSegment( painter, drawPosition,
00781                 threeDAttrs.depth(), startAngle );
00782             drawUpperBrinkEffect( painter, drawPosition, endAngle );
00783             drawArcEffectSegment( painter, drawPosition,
00784                 threeDAttrs.depth(),
00785                 startAngle, 360, granularity );
00786         } else if ( endAngle <= 180 ) {
00787             drawStraightEffectSegment( painter, drawPosition,
00788                 threeDAttrs.depth(), startAngle );
00789             drawStraightEffectSegment( painter, drawPosition,
00790                 threeDAttrs.depth(), endAngle );
00791             drawArcEffectSegment( painter, drawPosition,
00792                 threeDAttrs.depth(),
00793                 startAngle, 360, granularity );
00794         } else if ( endAngle <= 270 ) {
00795             drawStraightEffectSegment( painter, drawPosition,
00796                 threeDAttrs.depth(), startAngle );
00797             drawStraightEffectSegment( painter, drawPosition,
00798                 threeDAttrs.depth(), endAngle );
00799             drawArcEffectSegment( painter, drawPosition,
00800                 threeDAttrs.depth(),
00801                 180, endAngle, granularity );
00802             drawArcEffectSegment( painter, drawPosition,
00803                 threeDAttrs.depth(),
00804                 startAngle, 360, granularity );
00805         } else { // 270*16 < endAngle < 360*16
00806             if ( startAngle <= endAngle ) {
00809                 drawStraightEffectSegment( painter, drawPosition,
00810                     threeDAttrs.depth(), startAngle );
00811                 drawUpperBrinkEffect( painter, drawPosition, endAngle );
00812                 drawArcEffectSegment( painter, drawPosition,
00813                     threeDAttrs.depth(),
00814                     startAngle, endAngle, granularity );
00815             } else {
00818                 drawStraightEffectSegment( painter, drawPosition,
00819                     threeDAttrs.depth(), startAngle );
00820                 drawUpperBrinkEffect( painter, drawPosition, endAngle );
00821                 drawArcEffectSegment( painter, drawPosition,
00822                     threeDAttrs.depth(),
00823                     startAngle, 360, granularity );
00824                 drawArcEffectSegment( painter, drawPosition,
00825                     threeDAttrs.depth(),
00826                     180, endAngle, granularity );
00827             }
00828         }
00829     }
00830     drawArcUpperBrinkEffectSegment( painter, drawPosition, startAngle, endAngle, granularity );
00831 }
00832 
00833 
00842 void PieDiagram::drawStraightEffectSegment( QPainter* painter,
00843         const QRectF& rect,
00844         qreal threeDHeight,
00845         qreal angle )
00846 {
00847     QPolygonF poly( 4 );
00848     const QPointF center = rect.center();
00849     const QPointF circlePoint = pointOnCircle( rect, angle );
00850     poly[0] = center;
00851     poly[1] = circlePoint;
00852     poly[2] = QPointF( circlePoint.x(), circlePoint.y() + threeDHeight );
00853     poly[3] = QPointF( center.x(), center.y() + threeDHeight );
00854     // TODO: add polygon to ReverseMapper
00855     painter->drawPolygon( poly );
00856 //    if ( region )
00857 //        *region += QRegion( points );
00858 }
00859 
00867 void PieDiagram::drawUpperBrinkEffect( QPainter* painter,
00868         const QRectF& rect,
00869         qreal angle )
00870 {
00871     const QPointF center = rect.center();
00872     const QPointF circlePoint = pointOnCircle( rect, angle );
00873     painter->drawLine( center, circlePoint );
00874 }
00875 
00885 void PieDiagram::drawArcEffectSegment( QPainter* painter,
00886         const QRectF& rect,
00887         qreal threeDHeight,
00888         qreal startAngle,
00889         qreal endAngle,
00890         qreal granularity )
00891 {
00892     // Start with getting the points for the inner arc.
00893     qreal startA = qMin( startAngle, endAngle );
00894     qreal endA   = qMax( startAngle, endAngle );
00895 
00896     // sometimes we have to draw two segments, which are on different sides of the pie
00897     if( endA > 540 )
00898         drawArcEffectSegment( painter, rect, threeDHeight, 180, endA - 360, granularity );
00899     if( endA > 360 )
00900         endA = qMin( endA, qreal( 360.0 ) );
00901 
00902     int numHalfPoints = static_cast<int>( trunc( ( endA - startA ) / granularity ) ) + 1;
00903 
00904     QPolygonF poly( numHalfPoints );
00905 
00906     qreal degree = endA;
00907     int iPoint = 0;
00908     bool perfectMatch = false;
00909     while ( degree >= startA ){
00910         poly[ numHalfPoints - iPoint - 1 ] = pointOnCircle( rect, degree );
00911 
00912         perfectMatch = (degree == startA);
00913         degree -= granularity;
00914         ++iPoint;
00915     }
00916     // if necessary add one more point to fill the last small gap
00917     if( ! perfectMatch ){
00918         poly.prepend( pointOnCircle( rect, startA ) );
00919         ++numHalfPoints;
00920     }
00921 
00922     poly.resize( numHalfPoints * 2 );
00923 
00924     // Now copy these arcs again into the final array, but in the
00925     // opposite direction and moved down by the 3D height.
00926     for ( int i = numHalfPoints - 1; i >= 0; --i ) {
00927         QPointF pointOnFirstArc( poly[ i ] );
00928         pointOnFirstArc.setY( pointOnFirstArc.y() + threeDHeight );
00929         poly[ numHalfPoints * 2 - i - 1 ] = pointOnFirstArc;
00930     }
00931 
00932     // TODO: Add polygon to ReverseMapper
00933     painter->drawPolygon( poly );
00934 //    if ( region )
00935 //        *region += QRegion( collect );
00936 }
00937 
00946 void PieDiagram::drawArcUpperBrinkEffectSegment( QPainter* painter,
00947         const QRectF& rect,
00948         qreal startAngle,
00949         qreal endAngle,
00950         qreal granularity )
00951 {
00952     if ( endAngle < startAngle )
00953         endAngle += 360;
00954     // Start with getting the poits for the inner arc.
00955     const qreal startA = qMin( startAngle, endAngle );
00956     const qreal endA   = qMax( startAngle, endAngle );
00957 
00958     int numHalfPoints = static_cast<int>( trunc( ( endA - startA ) / granularity ) ) + 1;
00959 
00960     QPolygonF poly( numHalfPoints );
00961 
00962     qreal degree = endA;
00963     int iPoint = 0;
00964     bool perfectMatch = false;
00965     while ( degree >= startA ){
00966         poly[ numHalfPoints - iPoint - 1 ] = pointOnCircle( rect, degree );
00967 
00968         perfectMatch = (degree == startA);
00969         degree -= granularity;
00970         ++iPoint;
00971     }
00972     // if necessary add one more point to fill the last small gap
00973     if( ! perfectMatch ){
00974         poly.prepend( pointOnCircle( rect, startA ) );
00975         ++numHalfPoints;
00976     }
00977 
00978     painter->drawPolyline( poly );
00979 //    if ( region )
00980 //        *region += QRegion( collect );
00981 }
00982 
00990 uint PieDiagram::findPieAt( qreal angle, int colCount )
00991 {
00992     for ( int i = 0; i < colCount; ++i ) {
00993         qreal endseg = d->startAngles[ i ] + d->angleLens[ i ];
00994         if ( ( d->startAngles[ i ] <= angle ) &&
00995                 ( endseg >= angle ) )
00996             // found!
00997             return i;
00998     }
00999 
01000     // If we have not found it, try wrap around
01001     // but only if the current searched angle is < 360 degree
01002     if ( angle < 360 )
01003         return findPieAt( angle + 360, colCount );
01004     // otherwise - what ever went wrong - we return 0
01005     return 0;
01006 }
01007 
01008 
01016 uint PieDiagram::findLeftPie( uint pie, int colCount )
01017 {
01018     if ( pie == 0 )
01019         if ( colCount > 1 )
01020             return colCount - 1;
01021         else
01022             return 0;
01023     else {
01024         return pie - 1;
01025     }
01026 }
01027 
01028 
01036 uint PieDiagram::findRightPie( uint pie, int colCount  )
01037 {
01038     int rightpie = pie + 1;
01039     if ( rightpie == colCount )
01040         rightpie = 0;
01041     return rightpie;
01042 }
01043 
01044 /*
01045 / **
01046   This method is a specialization that returns a fallback legend text
01047   appropriate for pies that do not have more than one dataset
01048 
01049   This method is only used when automatic legends are used, because
01050   manual and first-column legends do not need fallback texts.
01051 
01052   \param uint dataset the dataset number for which to generate a
01053   fallback text
01054   \return the fallback text to use for describing the specified
01055   dataset in the legend
01056   * /
01057 QString PieDiagram::fallbackLegendText( uint dataset ) const
01058 {
01059     return QObject::tr( "Item " ) + QString::number( dataset + 1 );
01060 }
01061 
01062 
01063 / **
01064   This methods returns the number of elements to be shown in the
01065   legend in case fallback texts are used.
01066 
01067   This method is only used when automatic legends are used, because
01068   manual and first-column legends do not need fallback texts.
01069 
01070   \return the number of fallback texts to use
01071   * /
01072 uint PieDiagram::numLegendFallbackTexts( KDChartTableDataBase* data ) const
01073 {
01074     return data->usedCols();
01075 }
01076 */
01077 
01082 QPointF PieDiagram::pointOnCircle( const QRectF& rect, qreal angle )
01083 {
01084     qreal angleRad = DEGTORAD( angle );
01085     qreal cosAngle = cos( angleRad );
01086     qreal sinAngle = -sin( angleRad );
01087     qreal posX = cosAngle * rect.width() / 2.0;
01088     qreal posY = sinAngle * rect.height() / 2.0;
01089     return QPointF( posX + rect.center().x(),
01090                     posY + rect.center().y() );
01091 
01092 }
01093 
01094 /*virtual*/
01095 double PieDiagram::valueTotals() const
01096 {
01097     const int colCount = columnCount();
01098     double total = 0.0;
01099     for ( int j = 0; j < colCount; ++j ) {
01100       total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toDouble());
01101       //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
01102     }
01103     return total;
01104 }
01105 
01106 /*virtual*/
01107 double PieDiagram::numberOfValuesPerDataset() const
01108 {
01109     return model() ? model()->columnCount( rootIndex() ) : 0.0;
01110 }
01111 
01112 /*virtual*/
01113 double PieDiagram::numberOfGridRings() const
01114 {
01115     return 1;
01116 }