00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
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
00078
00079
00080
00081
00082
00083 return
00084 ( static_cast<const AbstractPieDiagram*>(this)->compare( other ) ) &&
00085
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
00119
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
00129 maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() );
00130 }
00131 maxExplode += maxExplodeInThisRow;
00132
00133
00134 if ( !d->expandWhenExploded )
00135 break;
00136 }
00137
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
00178
00179 if ( !checkInvariants(true) )
00180 return;
00181
00182 const PieAttributes attrs( pieAttributes() );
00183
00184 const int rCount = rowCount();
00185 const int colCount = columnCount();
00186
00187 QRectF contentsRect( buildReferenceRect( polarCoordinatePlane() ) );
00188 contentsRect = ctx->rectangle();
00189 if( contentsRect.isEmpty() )
00190 return;
00191
00192 DataValueTextInfoList list;
00193
00194 d->startAngles = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
00195 d->angleLens = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
00196
00197
00198 d->size = qMin( contentsRect.width(), contentsRect.height() );
00199
00200
00201
00202 qreal totalOffset = 0.0;
00203 for( int i = 0; i < rCount; ++i ){
00204 qreal maxOffsetInThisRow = 0.0;
00205 for( int j = 0; j < colCount; ++j ){
00206 const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) );
00207
00208 const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0;
00209 maxOffsetInThisRow = qMax( maxOffsetInThisRow, cellAttrs.gapFactor( false ) + explode );
00210 }
00211 if ( !d->expandWhenExploded )
00212 maxOffsetInThisRow -= (qreal)i;
00213 if ( maxOffsetInThisRow > 0.0 )
00214 totalOffset += maxOffsetInThisRow;
00215
00216
00217
00218
00219 }
00220
00221
00222 if ( rCount > 0 )
00223 totalOffset /= ( rCount + 1 );
00224 d->size /= ( 1.0 + totalOffset );
00225
00226
00227 qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 );
00228 qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 );
00229 d->position = QRectF( x, y, d->size, d->size );
00230 d->position.translate( contentsRect.left(), contentsRect.top() );
00231
00232 const PolarCoordinatePlane * plane = polarCoordinatePlane();
00233
00234 bool atLeastOneValue = false;
00235 QVariant vValY;
00236
00237 d->clearListOfAlreadyDrawnDataValueTexts();
00238 for ( int iRow = 0; iRow < rCount; ++iRow ) {
00239 const qreal sum = valueTotals( iRow );
00240 if( sum == 0.0 )
00241 continue;
00242 qreal currentValue = plane ? plane->startPosition() : 0.0;
00243 const qreal sectorsPerValue = 360.0 / sum;
00244
00245 for ( int iColumn = 0; iColumn < colCount; ++iColumn ) {
00246
00247 bool bOK;
00248 const double cellValue = qAbs( model()->data( model()->index( iRow, iColumn, rootIndex() ) )
00249 .toDouble( &bOK ) );
00250
00251 if( bOK ){
00252 d->startAngles[ iRow ][ iColumn ] = currentValue;
00253 d->angleLens[ iRow ][ iColumn ] = cellValue * sectorsPerValue;
00254 atLeastOneValue = true;
00255 } else {
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
00263
00264
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
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
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;
00327
00328 painter->setRenderHint ( QPainter::Antialiasing );
00329 painter->setBrush( brush( index ) );
00330 painter->setPen( pen( index ) );
00331
00332
00333 if ( angleLen == 360 ) {
00334
00335
00336
00337 } else {
00338 bool perfectMatch = false;
00339
00340 qreal circularGap = 0.0;
00341
00342 if ( attrs.gapFactor( true ) > 0.0 )
00343 {
00344
00345 circularGap = attrs.gapFactor( true );
00346
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
00367 if ( d->expandWhenExploded )
00368 maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) );
00369 if ( !cellAttrs.explode() )
00370 continue;
00371
00372 if ( d->expandWhenExploded )
00373 maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() );
00374 }
00375 maxRadialExplode += maxRadialExplodeInThisRow;
00376 maxRadialGap += maxRadialGapInThisRow;
00377
00378
00379
00380
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
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
00413 if( ! perfectMatch ){
00414 poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle, totalRadialGap, totalRadialExplode ) );
00415 iPoint++;
00416 }
00417
00418
00419 const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] );
00420
00421
00422
00423 const qreal sum = valueTotals( dataset );
00424 painter->drawPolygon( poly );
00425
00426 const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0;
00427
00428 paintDataValueText( painter, index, centerPoint, angleLen*sum / 360 );
00429
00430 }
00431 }
00432 }
00433
00434
00439 QPointF RingDiagram::pointOnCircle( const QRectF& rect, int dataset, int pie, bool outer, qreal angle, qreal totalGapFactor, qreal totalExplodeFactor )
00440 {
00441 qreal angleLen = d->angleLens[ dataset ][ pie ];
00442 qreal startAngle = d->startAngles[ dataset ][ pie ];
00443 QModelIndex index( model()->index( dataset, pie, rootIndex() ) );
00444 const PieAttributes attrs( pieAttributes( index ) );
00445
00446 const int rCount = rowCount();
00447
00448
00449
00450
00451
00452
00453
00454
00455 qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1;
00456
00457
00458
00459
00460
00461
00462
00463
00464 const qreal offsetX = rCount > 0 ? level * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
00465 const qreal offsetY = rCount > 0 ? level * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
00466 const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
00467 const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
00468 const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
00469 const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
00470
00471 qreal explodeAngleRad = DEGTORAD( angle );
00472 qreal cosAngle = cos( explodeAngleRad );
00473 qreal sinAngle = -sin( explodeAngleRad );
00474 qreal explodeAngleCenterRad = DEGTORAD( startAngle + angleLen / 2.0 );
00475 qreal cosAngleCenter = cos( explodeAngleCenterRad );
00476 qreal sinAngleCenter = -sin( explodeAngleCenterRad );
00477 return QPointF( ( offsetX + gapOffsetX ) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(),
00478 ( offsetY + gapOffsetY ) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y() );
00479 }
00480
00481
00482 double RingDiagram::valueTotals() const
00483 {
00484 const int rCount = rowCount();
00485 const int colCount = columnCount();
00486 double total = 0.0;
00487 for ( int i = 0; i < rCount; ++i ) {
00488 for ( int j = 0; j < colCount; ++j ) {
00489 total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toDouble());
00490
00491 }
00492 }
00493 return total;
00494 }
00495
00496 double RingDiagram::valueTotals( int dataset ) const
00497 {
00498 const int colCount = columnCount();
00499 double total = 0.0;
00500 for ( int j = 0; j < colCount; ++j ) {
00501 total += qAbs(model()->data( model()->index( dataset, j, rootIndex() ) ).toDouble());
00502
00503 }
00504 return total;
00505 }
00506
00507
00508 double RingDiagram::numberOfValuesPerDataset() const
00509 {
00510 return model() ? model()->columnCount( rootIndex() ) : 0.0;
00511 }
00512
00513 double RingDiagram::numberOfDatasets() const
00514 {
00515 return model() ? model()->rowCount( rootIndex() ) : 0.0;
00516 }
00517
00518
00519 double RingDiagram::numberOfGridRings() const
00520 {
00521 return 1;
00522 }