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