KDChartRingDiagram.cpp

Go to the documentation of this file.
00001 /****************************************************************************
00002  ** Copyright (C) 2007 Klarälvdalens Datakonsult AB.  All rights reserved.
00003  **
00004  ** This file is part of the KD Chart library.
00005  **
00006  ** This file may be distributed and/or modified under the terms of the
00007  ** GNU General Public License version 2 as published by the Free Software
00008  ** Foundation and appearing in the file LICENSE.GPL included in the
00009  ** packaging of this file.
00010  **
00011  ** Licensees holding valid commercial KD Chart licenses may use this file in
00012  ** accordance with the KD Chart Commercial License Agreement provided with
00013  ** the Software.
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  ** See http://www.kdab.net/kdchart for
00019  **   information about KDChart Commercial License Agreements.
00020  **
00021  ** Contact info@kdab.net if any conditions of this
00022  ** licensing are not clear to you.
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     qDebug() <<"\n             RingDiagram::compare():";
00081             // compare own properties
00082     qDebug() << (type() == other->type());
00083     qDebug() << (relativeThickness()  == other->relativeThickness());
00084     qDebug() << (expandWhenExploded() == other->expandWhenExploded());
00085     */
00086     return  // compare the base class
00087             ( static_cast<const AbstractPieDiagram*>(this)->compare( other ) ) &&
00088             // compare own properties
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     // If we explode, we need extra space for the pie slice that has
00122     // the largest explosion distance.
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                         //qDebug() << columnAttrs.explodeFactor();
00132                         maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() );
00133                 }
00134                 maxExplode += maxExplodeInThisRow;
00135 
00136                 // FIXME: What if explode factor of inner ring is > 1.0 ?
00137                 if ( !d->expandWhenExploded )
00138                         break;
00139         }
00140         // explode factor is relative to width (outer r - inner r) of one ring
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     // note: Not having any data model assigned is no bug
00181     //       but we can not draw a diagram then either.
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     // compute position
00201     d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size
00202 
00203     // if the pies explode, we need to give them additional space =>
00204     // make the basic size smaller
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                 //qDebug() << cellAttrs.explodeFactor();
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         // FIXME: What if explode factor of inner ring is > 1.0 ?
00220         //if ( !d->expandWhenExploded )
00221         //      break;
00222     }
00223 
00224     // explode factor is relative to width (outer r - inner r) of one ring
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; // guard against completely empty tables
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 ) //nothing to draw
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                 // is there anything at all at this column?
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 { // mark as non-existent
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                 //qDebug() << "d->startAngles["<<iColumn<<"] == " << d->startAngles[ iColumn ]
00266                 //         << " +  d->angleLens["<<iColumn<<"]" << d->angleLens[ iColumn ]
00267                 //         << " = " << d->startAngles[ iColumn ]+d->angleLens[ iColumn ];
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     // Is there anything to draw at all?
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     // Is there anything to draw at all?
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;//piePosition( dataset, pie );
00330 
00331         QPen pen = this->pen( index );
00332         painter->setRenderHint ( QPainter::Antialiasing );
00333         painter->setBrush( brush( index ) );
00334 //        painter->setPen( pen );
00335         //painter->setPen( Qt::red );
00336         if ( angleLen == 360 ) {
00337             // full circle, avoid nasty line in the middle
00338             // FIXME: Draw a complete ring here
00339             //painter->drawEllipse( drawPosition );
00340         } else {
00341             bool perfectMatch = false;
00342 
00343             qreal circularGap = 0.0;
00344 
00345             if ( attrs.gapFactor( true ) > 0.0 )
00346             {
00347                     // FIXME: Measure in degrees!
00348                     circularGap = attrs.gapFactor( true );
00349                     //qDebug() << "gapFactor=" << attrs.gapFactor( false );
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                         //qDebug() << cellAttrs.explodeFactor();
00370                         if ( d->expandWhenExploded )
00371                                 maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) );
00372                         if ( !cellAttrs.explode() )
00373                                 continue;
00374                         // Don't use a gap for the very inner circle
00375                         if ( d->expandWhenExploded )
00376                                 maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() );
00377                 }
00378                 maxRadialExplode += maxRadialExplodeInThisRow;
00379                 maxRadialGap += maxRadialGapInThisRow;
00380 
00381                 // FIXME: What if explode factor of inner ring is > 1.0 ?
00382                 //if ( !d->expandWhenExploded )
00383                 //      break;
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             // The center point of the inner brink
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             // if necessary add one more point to fill the last small gap
00416             if( ! perfectMatch ){
00417                 poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle, totalRadialGap, totalRadialExplode ) );
00418                 iPoint++;
00419             }
00420 
00421             // The center point of the outer brink
00422             const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] );
00423             //qDebug() << poly;
00424             //find the value and paint it
00425             //fix value position
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     //const qreal gapFactor = attrs.gapFactor( false );
00452 
00453     //qDebug() << "##" << attrs.explode();
00454     //if ( attrs.explodeFactor() != 0.0 )
00455     //  qDebug() << attrs.explodeFactor();
00456 
00457 
00458     qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1;
00459 
00460 
00461     //maxExplode /= rCount;
00462 
00463     //qDebug() << "dataset=" << dataset << "maxExplode=" << maxExplode;
00464 
00465     //level += maxExplode;
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 /*virtual*/
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                 //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
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       //qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
00506     }
00507     return total;
00508 }
00509 
00510 /*virtual*/
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 /*virtual*/
00522 double RingDiagram::numberOfGridRings() const
00523 {
00524     return 1;
00525 }

Generated on Thu Mar 4 23:19:12 2010 for KD Chart 2 by  doxygen 1.5.4