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