KD Chart 2 [rev.2.4]
|
00001 /**************************************************************************** 00002 ** Copyright (C) 2001-2012 Klaralvdalens Datakonsult AB. All rights reserved. 00003 ** 00004 ** This file is part of the KD Chart library. 00005 ** 00006 ** Licensees holding valid commercial KD Chart licenses may use this file in 00007 ** accordance with the KD Chart Commercial License Agreement provided with 00008 ** the Software. 00009 ** 00010 ** 00011 ** This file may be distributed and/or modified under the terms of the 00012 ** GNU General Public License version 2 and version 3 as published by the 00013 ** Free Software Foundation and appearing in the file LICENSE.GPL.txt included. 00014 ** 00015 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE 00016 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. 00017 ** 00018 ** Contact info@kdab.com if any conditions of this licensing are not 00019 ** clear to you. 00020 ** 00021 **********************************************************************/ 00022 00023 #include <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, sum ); 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, sum ); 00373 currentLeftPie = findLeftPie( currentLeftPie, colCount ); 00374 } 00375 while( currentRightPie != frontmostpie ) 00376 { 00377 if( currentRightPie != backmostpie ) 00378 drawOnePie( ctx->painter(), &list, 0, currentRightPie, granularity(), sizeFor3DEffect, sum ); 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, sum ); 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, qreal totalValue ) 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, totalValue ); 00468 } 00469 } 00470 00478 void PieDiagram::drawPieSurface( QPainter* painter, 00479 DataValueTextInfoList* list, 00480 uint dataset, uint pie, 00481 qreal granularity, qreal totalValue ) 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 = totalValue; 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 } 01102 return total; 01103 } 01104 01105 /*virtual*/ 01106 double PieDiagram::numberOfValuesPerDataset() const 01107 { 01108 return model() ? model()->columnCount( rootIndex() ) : 0.0; 01109 } 01110 01111 /*virtual*/ 01112 double PieDiagram::numberOfGridRings() const 01113 { 01114 return 1; 01115 }