KDChartRingDiagram.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002 ** Copyright (C) 2001-2010 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 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     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     // compute position
00198     d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size
00199 
00200     // if the pies explode, we need to give them additional space =>
00201     // make the basic size smaller
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                 //qDebug() << cellAttrs.explodeFactor();
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         // FIXME: What if explode factor of inner ring is > 1.0 ?
00217         //if ( !d->expandWhenExploded )
00218         //      break;
00219     }
00220 
00221     // explode factor is relative to width (outer r - inner r) of one ring
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; // guard against completely empty tables
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 ) //nothing to draw
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                 // is there anything at all at this column?
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 { // 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             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     //const qreal gapFactor = attrs.gapFactor( false );
00449 
00450     //qDebug() << "##" << attrs.explode();
00451     //if ( attrs.explodeFactor() != 0.0 )
00452     //  qDebug() << attrs.explodeFactor();
00453 
00454 
00455     qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1;
00456 
00457 
00458     //maxExplode /= rCount;
00459 
00460     //qDebug() << "dataset=" << dataset << "maxExplode=" << maxExplode;
00461 
00462     //level += maxExplode;
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 /*virtual*/
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                 //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
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       //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
00503     }
00504     return total;
00505 }
00506 
00507 /*virtual*/
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 /*virtual*/
00519 double RingDiagram::numberOfGridRings() const
00520 {
00521     return 1;
00522 }