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 "KDChartRingDiagram.h" 00024 #include "KDChartRingDiagram_p.h" 00025 00026 #include "KDChartAttributesModel.h" 00027 #include "KDChartPaintContext.h" 00028 #include "KDChartPainterSaver_p.h" 00029 #include "KDChartPieAttributes.h" 00030 #include "KDChartDataValueAttributes.h" 00031 00032 #include <QPainter> 00033 00034 #include <KDABLibFakes> 00035 00036 using namespace KDChart; 00037 00038 RingDiagram::Private::Private() 00039 : relativeThickness( false ) 00040 , expandWhenExploded( false ) 00041 { 00042 } 00043 00044 RingDiagram::Private::~Private() {} 00045 00046 #define d d_func() 00047 00048 RingDiagram::RingDiagram( QWidget* parent, PolarCoordinatePlane* plane ) : 00049 AbstractPieDiagram( new Private(), parent, plane ) 00050 { 00051 init(); 00052 } 00053 00054 RingDiagram::~RingDiagram() 00055 { 00056 } 00057 00058 void RingDiagram::init() 00059 { 00060 } 00061 00065 RingDiagram * RingDiagram::clone() const 00066 { 00067 return new RingDiagram( new Private( *d ) ); 00068 } 00069 00070 bool RingDiagram::compare( const RingDiagram* other )const 00071 { 00072 if( other == this ) return true; 00073 if( ! other ){ 00074 return false; 00075 } 00076 /* 00077 qDebug() <<"\n RingDiagram::compare():"; 00078 // compare own properties 00079 qDebug() << (type() == other->type()); 00080 qDebug() << (relativeThickness() == other->relativeThickness()); 00081 qDebug() << (expandWhenExploded() == other->expandWhenExploded()); 00082 */ 00083 return // compare the base class 00084 ( static_cast<const AbstractPieDiagram*>(this)->compare( other ) ) && 00085 // compare own properties 00086 (relativeThickness() == other->relativeThickness()) && 00087 (expandWhenExploded() == other->expandWhenExploded()); 00088 } 00089 00090 void RingDiagram::setRelativeThickness( bool relativeThickness ) 00091 { 00092 d->relativeThickness = relativeThickness; 00093 } 00094 00095 bool RingDiagram::relativeThickness() const 00096 { 00097 return d->relativeThickness; 00098 } 00099 00100 void RingDiagram::setExpandWhenExploded( bool expand ) 00101 { 00102 d->expandWhenExploded = expand; 00103 } 00104 00105 bool RingDiagram::expandWhenExploded() const 00106 { 00107 return d->expandWhenExploded; 00108 } 00109 00110 const QPair<QPointF, QPointF> RingDiagram::calculateDataBoundaries () const 00111 { 00112 if ( !checkInvariants( true ) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) ); 00113 00114 const PieAttributes attrs( pieAttributes( model()->index( 0, 0, rootIndex() ) ) ); 00115 00116 QPointF bottomLeft ( QPointF( 0, 0 ) ); 00117 QPointF topRight; 00118 // If we explode, we need extra space for the pie slice that has 00119 // the largest explosion distance. 00120 if ( attrs.explode() ) { 00121 const int rCount = rowCount(); 00122 const int colCount = columnCount(); 00123 qreal maxExplode = 0.0; 00124 for( int i = 0; i < rCount; ++i ){ 00125 qreal maxExplodeInThisRow = 0.0; 00126 for( int j = 0; j < colCount; ++j ){ 00127 const PieAttributes columnAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); 00128 //qDebug() << columnAttrs.explodeFactor(); 00129 maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() ); 00130 } 00131 maxExplode += maxExplodeInThisRow; 00132 00133 // FIXME: What if explode factor of inner ring is > 1.0 ? 00134 if ( !d->expandWhenExploded ) 00135 break; 00136 } 00137 // explode factor is relative to width (outer r - inner r) of one ring 00138 maxExplode /= ( rCount + 1); 00139 topRight = QPointF( 1.0+maxExplode, 1.0+maxExplode ); 00140 }else{ 00141 topRight = QPointF( 1.0, 1.0 ); 00142 } 00143 return QPair<QPointF, QPointF> ( bottomLeft, topRight ); 00144 } 00145 00146 void RingDiagram::paintEvent( QPaintEvent* ) 00147 { 00148 QPainter painter ( viewport() ); 00149 PaintContext ctx; 00150 ctx.setPainter ( &painter ); 00151 ctx.setRectangle( QRectF ( 0, 0, width(), height() ) ); 00152 paint ( &ctx ); 00153 } 00154 00155 void RingDiagram::resizeEvent( QResizeEvent* ) 00156 { 00157 } 00158 00159 static QRectF buildReferenceRect( const PolarCoordinatePlane* plane ) 00160 { 00161 QRectF contentsRect; 00162 QPointF referencePointAtTop = plane->translate( QPointF( 1, 0 ) ); 00163 QPointF temp = plane->translate( QPointF( 0, 0 ) ) - referencePointAtTop; 00164 const double offset = temp.y(); 00165 referencePointAtTop.setX( referencePointAtTop.x() - offset ); 00166 contentsRect.setTopLeft( referencePointAtTop ); 00167 contentsRect.setBottomRight( referencePointAtTop + QPointF( 2*offset, 2*offset) ); 00168 return contentsRect; 00169 } 00170 /* 00171 00172 */ 00173 00174 00175 void RingDiagram::paint( PaintContext* ctx ) 00176 { 00177 // note: Not having any data model assigned is no bug 00178 // but we can not draw a diagram then either. 00179 if ( !checkInvariants(true) ) 00180 return; 00181 00182 d->reverseMapper.clear(); 00183 00184 const PieAttributes attrs( pieAttributes() ); 00185 00186 const int rCount = rowCount(); 00187 const int colCount = columnCount(); 00188 00189 QRectF contentsRect( buildReferenceRect( polarCoordinatePlane() ) ); 00190 contentsRect = ctx->rectangle(); 00191 if( contentsRect.isEmpty() ) 00192 return; 00193 00194 DataValueTextInfoList list; 00195 00196 d->startAngles = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) ); 00197 d->angleLens = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) ); 00198 00199 // compute position 00200 d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size 00201 00202 // if the pies explode, we need to give them additional space => 00203 // make the basic size smaller 00204 qreal totalOffset = 0.0; 00205 for( int i = 0; i < rCount; ++i ){ 00206 qreal maxOffsetInThisRow = 0.0; 00207 for( int j = 0; j < colCount; ++j ){ 00208 const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); 00209 //qDebug() << cellAttrs.explodeFactor(); 00210 const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0; 00211 maxOffsetInThisRow = qMax( maxOffsetInThisRow, cellAttrs.gapFactor( false ) + explode ); 00212 } 00213 if ( !d->expandWhenExploded ) 00214 maxOffsetInThisRow -= (qreal)i; 00215 if ( maxOffsetInThisRow > 0.0 ) 00216 totalOffset += maxOffsetInThisRow; 00217 00218 // FIXME: What if explode factor of inner ring is > 1.0 ? 00219 //if ( !d->expandWhenExploded ) 00220 // break; 00221 } 00222 00223 // explode factor is relative to width (outer r - inner r) of one ring 00224 if ( rCount > 0 ) 00225 totalOffset /= ( rCount + 1 ); 00226 d->size /= ( 1.0 + totalOffset ); 00227 00228 00229 qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 ); 00230 qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 ); 00231 d->position = QRectF( x, y, d->size, d->size ); 00232 d->position.translate( contentsRect.left(), contentsRect.top() ); 00233 00234 const PolarCoordinatePlane * plane = polarCoordinatePlane(); 00235 00236 QVariant vValY; 00237 00238 d->clearListOfAlreadyDrawnDataValueTexts(); 00239 for ( int iRow = 0; iRow < rCount; ++iRow ) { 00240 const qreal sum = valueTotals( iRow ); 00241 if( sum == 0.0 ) //nothing to draw 00242 continue; 00243 qreal currentValue = plane ? plane->startPosition() : 0.0; 00244 const qreal sectorsPerValue = 360.0 / sum; 00245 00246 for ( int iColumn = 0; iColumn < colCount; ++iColumn ) { 00247 // is there anything at all at this column? 00248 bool bOK; 00249 const double cellValue = qAbs( model()->data( model()->index( iRow, iColumn, rootIndex() ) ) 00250 .toDouble( &bOK ) ); 00251 00252 if( bOK ){ 00253 d->startAngles[ iRow ][ iColumn ] = currentValue; 00254 d->angleLens[ iRow ][ iColumn ] = cellValue * sectorsPerValue; 00255 } else { // mark as non-existent 00256 d->angleLens[ iRow ][ iColumn ] = 0.0; 00257 if ( iColumn > 0.0 ) 00258 d->startAngles[ iRow ][ iColumn ] = d->startAngles[ iRow ][ iColumn - 1 ]; 00259 else 00260 d->startAngles[ iRow ][ iColumn ] = currentValue; 00261 } 00262 //qDebug() << "d->startAngles["<<iColumn<<"] == " << d->startAngles[ iColumn ] 00263 // << " + d->angleLens["<<iColumn<<"]" << d->angleLens[ iColumn ] 00264 // << " = " << d->startAngles[ iColumn ]+d->angleLens[ iColumn ]; 00265 00266 currentValue = d->startAngles[ iRow ][ iColumn ] + d->angleLens[ iRow ][ iColumn ]; 00267 00268 drawOnePie( ctx->painter(), iRow, iColumn, granularity() ); 00269 } 00270 } 00271 } 00272 00273 #if defined ( Q_WS_WIN) 00274 #define trunc(x) ((int)(x)) 00275 #endif 00276 00284 void RingDiagram::drawOnePie( QPainter* painter, 00285 uint dataset, uint pie, 00286 qreal granularity ) 00287 { 00288 // Is there anything to draw at all? 00289 const qreal angleLen = d->angleLens[ dataset ][ pie ]; 00290 if ( angleLen ) { 00291 const QModelIndex index( model()->index( dataset, pie, rootIndex() ) ); 00292 const PieAttributes attrs( pieAttributes( index ) ); 00293 00294 drawPieSurface( painter, dataset, pie, granularity ); 00295 } 00296 } 00297 00298 void RingDiagram::resize( const QSizeF& ) 00299 { 00300 } 00301 00309 void RingDiagram::drawPieSurface( QPainter* painter, 00310 uint dataset, uint pie, 00311 qreal granularity ) 00312 { 00313 // Is there anything to draw at all? 00314 qreal angleLen = d->angleLens[ dataset ][ pie ]; 00315 if ( angleLen ) { 00316 qreal startAngle = d->startAngles[ dataset ][ pie ]; 00317 00318 QModelIndex index( model()->index( dataset, pie, rootIndex() ) ); 00319 const PieAttributes attrs( pieAttributes( index ) ); 00320 00321 const int rCount = rowCount(); 00322 const int colCount = columnCount(); 00323 00324 int iPoint = 0; 00325 00326 QRectF drawPosition = d->position;//piePosition( dataset, pie ); 00327 00328 painter->setRenderHint ( QPainter::Antialiasing ); 00329 painter->setBrush( brush( index ) ); 00330 painter->setPen( pen( index ) ); 00331 // painter->setPen( pen ); 00332 //painter->setPen( Qt::red ); 00333 if ( angleLen == 360 ) { 00334 // full circle, avoid nasty line in the middle 00335 // FIXME: Draw a complete ring here 00336 //painter->drawEllipse( drawPosition ); 00337 } else { 00338 bool perfectMatch = false; 00339 00340 qreal circularGap = 0.0; 00341 00342 if ( attrs.gapFactor( true ) > 0.0 ) 00343 { 00344 // FIXME: Measure in degrees! 00345 circularGap = attrs.gapFactor( true ); 00346 //qDebug() << "gapFactor=" << attrs.gapFactor( false ); 00347 } 00348 00349 QPolygonF poly; 00350 00351 qreal degree = 0; 00352 00353 qreal actualStartAngle = startAngle + circularGap; 00354 qreal actualAngleLen = angleLen - 2 * circularGap; 00355 00356 qreal totalRadialExplode = 0.0; 00357 qreal maxRadialExplode = 0.0; 00358 00359 qreal totalRadialGap = 0.0; 00360 qreal maxRadialGap = 0.0; 00361 for( uint i = rCount - 1; i > dataset; --i ){ 00362 qreal maxRadialExplodeInThisRow = 0.0; 00363 qreal maxRadialGapInThisRow = 0.0; 00364 for( int j = 0; j < colCount; ++j ){ 00365 const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); 00366 //qDebug() << cellAttrs.explodeFactor(); 00367 if ( d->expandWhenExploded ) 00368 maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) ); 00369 if ( !cellAttrs.explode() ) 00370 continue; 00371 // Don't use a gap for the very inner circle 00372 if ( d->expandWhenExploded ) 00373 maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() ); 00374 } 00375 maxRadialExplode += maxRadialExplodeInThisRow; 00376 maxRadialGap += maxRadialGapInThisRow; 00377 00378 // FIXME: What if explode factor of inner ring is > 1.0 ? 00379 //if ( !d->expandWhenExploded ) 00380 // break; 00381 } 00382 00383 totalRadialGap = maxRadialGap + attrs.gapFactor( false ); 00384 totalRadialExplode = attrs.explode() ? maxRadialExplode + attrs.explodeFactor() : maxRadialExplode; 00385 00386 while ( degree <= actualAngleLen ) { 00387 const QPointF p = pointOnCircle( drawPosition, dataset, pie, false, actualStartAngle + degree, totalRadialGap, totalRadialExplode ); 00388 poly.append( p ); 00389 degree += granularity; 00390 iPoint++; 00391 } 00392 if( ! perfectMatch ){ 00393 poly.append( pointOnCircle( drawPosition, dataset, pie, false, actualStartAngle + actualAngleLen, totalRadialGap, totalRadialExplode ) ); 00394 iPoint++; 00395 } 00396 00397 // The center point of the inner brink 00398 const QPointF innerCenterPoint( poly[ int(iPoint / 2) ] ); 00399 00400 actualStartAngle = startAngle + circularGap; 00401 actualAngleLen = angleLen - 2 * circularGap; 00402 00403 degree = actualAngleLen; 00404 00405 const int lastInnerBrinkPoint = iPoint; 00406 while ( degree >= 0 ){ 00407 poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle + degree, totalRadialGap, totalRadialExplode ) ); 00408 perfectMatch = (degree == 0); 00409 degree -= granularity; 00410 iPoint++; 00411 } 00412 // if necessary add one more point to fill the last small gap 00413 if( ! perfectMatch ){ 00414 poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle, totalRadialGap, totalRadialExplode ) ); 00415 iPoint++; 00416 } 00417 00418 // The center point of the outer brink 00419 const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] ); 00420 //qDebug() << poly; 00421 //find the value and paint it 00422 //fix value position 00423 const qreal sum = valueTotals( dataset ); 00424 painter->drawPolygon( poly ); 00425 00426 d->reverseMapper.addPolygon( index.row(), index.column(), poly ); 00427 00428 const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0; 00429 00430 paintDataValueText( painter, index, centerPoint, angleLen*sum / 360 ); 00431 00432 } 00433 } 00434 } 00435 00436 00441 QPointF RingDiagram::pointOnCircle( const QRectF& rect, int dataset, int pie, bool outer, qreal angle, qreal totalGapFactor, qreal totalExplodeFactor ) 00442 { 00443 qreal angleLen = d->angleLens[ dataset ][ pie ]; 00444 qreal startAngle = d->startAngles[ dataset ][ pie ]; 00445 QModelIndex index( model()->index( dataset, pie, rootIndex() ) ); 00446 const PieAttributes attrs( pieAttributes( index ) ); 00447 00448 const int rCount = rowCount(); 00449 00450 //const qreal gapFactor = attrs.gapFactor( false ); 00451 00452 //qDebug() << "##" << attrs.explode(); 00453 //if ( attrs.explodeFactor() != 0.0 ) 00454 // qDebug() << attrs.explodeFactor(); 00455 00456 00457 qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1; 00458 00459 00460 //maxExplode /= rCount; 00461 00462 //qDebug() << "dataset=" << dataset << "maxExplode=" << maxExplode; 00463 00464 //level += maxExplode; 00465 00466 const qreal offsetX = rCount > 0 ? level * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; 00467 const qreal offsetY = rCount > 0 ? level * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0; 00468 const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; 00469 const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0; 00470 const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0; 00471 const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0; 00472 00473 qreal explodeAngleRad = DEGTORAD( angle ); 00474 qreal cosAngle = cos( explodeAngleRad ); 00475 qreal sinAngle = -sin( explodeAngleRad ); 00476 qreal explodeAngleCenterRad = DEGTORAD( startAngle + angleLen / 2.0 ); 00477 qreal cosAngleCenter = cos( explodeAngleCenterRad ); 00478 qreal sinAngleCenter = -sin( explodeAngleCenterRad ); 00479 return QPointF( ( offsetX + gapOffsetX ) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(), 00480 ( offsetY + gapOffsetY ) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y() ); 00481 } 00482 00483 /*virtual*/ 00484 double RingDiagram::valueTotals() const 00485 { 00486 const int rCount = rowCount(); 00487 const int colCount = columnCount(); 00488 double total = 0.0; 00489 for ( int i = 0; i < rCount; ++i ) { 00490 for ( int j = 0; j < colCount; ++j ) { 00491 total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toDouble()); 00492 //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble(); 00493 } 00494 } 00495 return total; 00496 } 00497 00498 double RingDiagram::valueTotals( int dataset ) const 00499 { 00500 const int colCount = columnCount(); 00501 double total = 0.0; 00502 for ( int j = 0; j < colCount; ++j ) { 00503 total += qAbs(model()->data( model()->index( dataset, j, rootIndex() ) ).toDouble()); 00504 //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble(); 00505 } 00506 return total; 00507 } 00508 00509 /*virtual*/ 00510 double RingDiagram::numberOfValuesPerDataset() const 00511 { 00512 return model() ? model()->columnCount( rootIndex() ) : 0.0; 00513 } 00514 00515 double RingDiagram::numberOfDatasets() const 00516 { 00517 return model() ? model()->rowCount( rootIndex() ) : 0.0; 00518 } 00519 00520 /*virtual*/ 00521 double RingDiagram::numberOfGridRings() const 00522 { 00523 return 1; 00524 }