KDChartPieDiagram.cpp

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

Generated on Thu Mar 4 23:19:12 2010 for KD Chart 2 by  doxygen 1.5.4