1
/****************************************************************************
2
** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved.
4
** This file is part of the KD Chart library.
6
** Licensees holding valid commercial KD Chart licenses may use this file in
7
** accordance with the KD Chart Commercial License Agreement provided with
11
** This file may be distributed and/or modified under the terms of the
12
** GNU General Public License version 2 and version 3 as published by the
13
** Free Software Foundation and appearing in the file LICENSE.GPL included.
15
** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16
** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
18
** Contact info@kdab.com if any conditions of this licensing are not
21
**********************************************************************/
23
#include "KDChartRingDiagram.h"
24
#include "KDChartRingDiagram_p.h"
26
#include "KDChartAttributesModel.h"
27
#include "KDChartPaintContext.h"
28
#include "KDChartPainterSaver_p.h"
29
#include "KDChartPieAttributes.h"
30
#include "KDChartDataValueAttributes.h"
34
#include <KDABLibFakes>
36
using namespace KDChart;
38
RingDiagram::Private::Private()
39
: relativeThickness( false )
40
, expandWhenExploded( false )
44
RingDiagram::Private::~Private() {}
48
RingDiagram::RingDiagram( QWidget* parent, PolarCoordinatePlane* plane ) :
49
AbstractPieDiagram( new Private(), parent, plane )
54
RingDiagram::~RingDiagram()
58
void RingDiagram::init()
63
* Creates an exact copy of this diagram.
65
RingDiagram * RingDiagram::clone() const
67
return new RingDiagram( new Private( *d ) );
70
bool RingDiagram::compare( const RingDiagram* other )const
72
if( other == this ) return true;
77
qDebug() <<"\n RingDiagram::compare():";
78
// compare own properties
79
qDebug() << (type() == other->type());
80
qDebug() << (relativeThickness() == other->relativeThickness());
81
qDebug() << (expandWhenExploded() == other->expandWhenExploded());
83
return // compare the base class
84
( static_cast<const AbstractPieDiagram*>(this)->compare( other ) ) &&
85
// compare own properties
86
(relativeThickness() == other->relativeThickness()) &&
87
(expandWhenExploded() == other->expandWhenExploded());
90
void RingDiagram::setRelativeThickness( bool relativeThickness )
92
d->relativeThickness = relativeThickness;
95
bool RingDiagram::relativeThickness() const
97
return d->relativeThickness;
100
void RingDiagram::setExpandWhenExploded( bool expand )
102
d->expandWhenExploded = expand;
105
bool RingDiagram::expandWhenExploded() const
107
return d->expandWhenExploded;
110
const QPair<QPointF, QPointF> RingDiagram::calculateDataBoundaries () const
112
if ( !checkInvariants( true ) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
114
const PieAttributes attrs( pieAttributes( model()->index( 0, 0, rootIndex() ) ) );
116
QPointF bottomLeft ( QPointF( 0, 0 ) );
118
// If we explode, we need extra space for the pie slice that has
119
// the largest explosion distance.
120
if ( attrs.explode() ) {
121
const int rCount = rowCount();
122
const int colCount = columnCount();
123
qreal maxExplode = 0.0;
124
for( int i = 0; i < rCount; ++i ){
125
qreal maxExplodeInThisRow = 0.0;
126
for( int j = 0; j < colCount; ++j ){
127
const PieAttributes columnAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) );
128
//qDebug() << columnAttrs.explodeFactor();
129
maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() );
131
maxExplode += maxExplodeInThisRow;
133
// FIXME: What if explode factor of inner ring is > 1.0 ?
134
if ( !d->expandWhenExploded )
137
// explode factor is relative to width (outer r - inner r) of one ring
138
maxExplode /= ( rCount + 1);
139
topRight = QPointF( 1.0+maxExplode, 1.0+maxExplode );
141
topRight = QPointF( 1.0, 1.0 );
143
return QPair<QPointF, QPointF> ( bottomLeft, topRight );
146
void RingDiagram::paintEvent( QPaintEvent* )
148
QPainter painter ( viewport() );
150
ctx.setPainter ( &painter );
151
ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
155
void RingDiagram::resizeEvent( QResizeEvent* )
159
static QRectF buildReferenceRect( const PolarCoordinatePlane* plane )
162
QPointF referencePointAtTop = plane->translate( QPointF( 1, 0 ) );
163
QPointF temp = plane->translate( QPointF( 0, 0 ) ) - referencePointAtTop;
164
const double offset = temp.y();
165
referencePointAtTop.setX( referencePointAtTop.x() - offset );
166
contentsRect.setTopLeft( referencePointAtTop );
167
contentsRect.setBottomRight( referencePointAtTop + QPointF( 2*offset, 2*offset) );
175
void RingDiagram::paint( PaintContext* ctx )
177
// note: Not having any data model assigned is no bug
178
// but we can not draw a diagram then either.
179
if ( !checkInvariants(true) )
182
const PieAttributes attrs( pieAttributes() );
184
const int rCount = rowCount();
185
const int colCount = columnCount();
187
QRectF contentsRect( buildReferenceRect( polarCoordinatePlane() ) );
188
contentsRect = ctx->rectangle();
189
if( contentsRect.isEmpty() )
192
DataValueTextInfoList list;
194
d->startAngles = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
195
d->angleLens = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
198
d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size
200
// if the pies explode, we need to give them additional space =>
201
// make the basic size smaller
202
qreal totalOffset = 0.0;
203
for( int i = 0; i < rCount; ++i ){
204
qreal maxOffsetInThisRow = 0.0;
205
for( int j = 0; j < colCount; ++j ){
206
const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) );
207
//qDebug() << cellAttrs.explodeFactor();
208
const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0;
209
maxOffsetInThisRow = qMax( maxOffsetInThisRow, cellAttrs.gapFactor( false ) + explode );
211
if ( !d->expandWhenExploded )
212
maxOffsetInThisRow -= (qreal)i;
213
if ( maxOffsetInThisRow > 0.0 )
214
totalOffset += maxOffsetInThisRow;
216
// FIXME: What if explode factor of inner ring is > 1.0 ?
217
//if ( !d->expandWhenExploded )
221
// explode factor is relative to width (outer r - inner r) of one ring
223
totalOffset /= ( rCount + 1 );
224
d->size /= ( 1.0 + totalOffset );
227
qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 );
228
qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 );
229
d->position = QRectF( x, y, d->size, d->size );
230
d->position.translate( contentsRect.left(), contentsRect.top() );
232
const PolarCoordinatePlane * plane = polarCoordinatePlane();
234
bool atLeastOneValue = false; // guard against completely empty tables
237
d->clearListOfAlreadyDrawnDataValueTexts();
238
for ( int iRow = 0; iRow < rCount; ++iRow ) {
239
const qreal sum = valueTotals( iRow );
240
if( sum == 0.0 ) //nothing to draw
242
qreal currentValue = plane ? plane->startPosition() : 0.0;
243
const qreal sectorsPerValue = 360.0 / sum;
245
for ( int iColumn = 0; iColumn < colCount; ++iColumn ) {
246
// is there anything at all at this column?
248
const double cellValue = qAbs( model()->data( model()->index( iRow, iColumn, rootIndex() ) )
252
d->startAngles[ iRow ][ iColumn ] = currentValue;
253
d->angleLens[ iRow ][ iColumn ] = cellValue * sectorsPerValue;
254
atLeastOneValue = true;
255
} else { // mark as non-existent
256
d->angleLens[ iRow ][ iColumn ] = 0.0;
258
d->startAngles[ iRow ][ iColumn ] = d->startAngles[ iRow ][ iColumn - 1 ];
260
d->startAngles[ iRow ][ iColumn ] = currentValue;
262
//qDebug() << "d->startAngles["<<iColumn<<"] == " << d->startAngles[ iColumn ]
263
// << " + d->angleLens["<<iColumn<<"]" << d->angleLens[ iColumn ]
264
// << " = " << d->startAngles[ iColumn ]+d->angleLens[ iColumn ];
266
currentValue = d->startAngles[ iRow ][ iColumn ] + d->angleLens[ iRow ][ iColumn ];
268
drawOnePie( ctx->painter(), iRow, iColumn, granularity() );
273
#if defined ( Q_WS_WIN)
274
#define trunc(x) ((int)(x))
278
Internal method that draws one of the pies in a pie chart.
280
\param painter the QPainter to draw in
281
\param dataset the dataset to draw the pie for
282
\param pie the pie to draw
284
void RingDiagram::drawOnePie( QPainter* painter,
285
uint dataset, uint pie,
288
// Is there anything to draw at all?
289
const qreal angleLen = d->angleLens[ dataset ][ pie ];
291
const QModelIndex index( model()->index( dataset, pie, rootIndex() ) );
292
const PieAttributes attrs( pieAttributes( index ) );
294
drawPieSurface( painter, dataset, pie, granularity );
298
void RingDiagram::resize( const QSizeF& size )
300
d->diagramSize = size;
304
Internal method that draws the surface of one of the pies in a pie chart.
306
\param painter the QPainter to draw in
307
\param dataset the dataset to draw the pie for
308
\param pie the pie to draw
310
void RingDiagram::drawPieSurface( QPainter* painter,
311
uint dataset, uint pie,
314
// Is there anything to draw at all?
315
qreal angleLen = d->angleLens[ dataset ][ pie ];
317
qreal startAngle = d->startAngles[ dataset ][ pie ];
319
QModelIndex index( model()->index( dataset, pie, rootIndex() ) );
320
const PieAttributes attrs( pieAttributes( index ) );
322
const int rCount = rowCount();
323
const int colCount = columnCount();
327
QRectF drawPosition = d->position;//piePosition( dataset, pie );
329
painter->setRenderHint ( QPainter::Antialiasing );
330
painter->setBrush( brush( index ) );
331
painter->setPen( pen( index ) );
332
// painter->setPen( pen );
333
//painter->setPen( Qt::red );
334
if ( angleLen == 360 ) {
335
// full circle, avoid nasty line in the middle
336
// FIXME: Draw a complete ring here
337
//painter->drawEllipse( drawPosition );
339
bool perfectMatch = false;
341
qreal circularGap = 0.0;
343
if ( attrs.gapFactor( true ) > 0.0 )
345
// FIXME: Measure in degrees!
346
circularGap = attrs.gapFactor( true );
347
//qDebug() << "gapFactor=" << attrs.gapFactor( false );
354
qreal actualStartAngle = startAngle + circularGap;
355
qreal actualAngleLen = angleLen - 2 * circularGap;
357
qreal totalRadialExplode = 0.0;
358
qreal maxRadialExplode = 0.0;
360
qreal totalRadialGap = 0.0;
361
qreal maxRadialGap = 0.0;
362
for( uint i = rCount - 1; i > dataset; --i ){
363
qreal maxRadialExplodeInThisRow = 0.0;
364
qreal maxRadialGapInThisRow = 0.0;
365
for( int j = 0; j < colCount; ++j ){
366
const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) );
367
//qDebug() << cellAttrs.explodeFactor();
368
if ( d->expandWhenExploded )
369
maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) );
370
if ( !cellAttrs.explode() )
372
// Don't use a gap for the very inner circle
373
if ( d->expandWhenExploded )
374
maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() );
376
maxRadialExplode += maxRadialExplodeInThisRow;
377
maxRadialGap += maxRadialGapInThisRow;
379
// FIXME: What if explode factor of inner ring is > 1.0 ?
380
//if ( !d->expandWhenExploded )
384
totalRadialGap = maxRadialGap + attrs.gapFactor( false );
385
totalRadialExplode = attrs.explode() ? maxRadialExplode + attrs.explodeFactor() : maxRadialExplode;
387
while ( degree <= actualAngleLen ) {
388
const QPointF p = pointOnCircle( drawPosition, dataset, pie, false, actualStartAngle + degree, totalRadialGap, totalRadialExplode );
390
degree += granularity;
393
if( ! perfectMatch ){
394
poly.append( pointOnCircle( drawPosition, dataset, pie, false, actualStartAngle + actualAngleLen, totalRadialGap, totalRadialExplode ) );
398
// The center point of the inner brink
399
const QPointF innerCenterPoint( poly[ int(iPoint / 2) ] );
401
actualStartAngle = startAngle + circularGap;
402
actualAngleLen = angleLen - 2 * circularGap;
404
degree = actualAngleLen;
406
const int lastInnerBrinkPoint = iPoint;
407
while ( degree >= 0 ){
408
poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle + degree, totalRadialGap, totalRadialExplode ) );
409
perfectMatch = (degree == 0);
410
degree -= granularity;
413
// if necessary add one more point to fill the last small gap
414
if( ! perfectMatch ){
415
poly.append( pointOnCircle( drawPosition, dataset, pie, true, actualStartAngle, totalRadialGap, totalRadialExplode ) );
419
// The center point of the outer brink
420
const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] );
422
//find the value and paint it
424
const qreal sum = valueTotals( dataset );
425
painter->drawPolygon( poly );
427
const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0;
429
paintDataValueText( painter, index, centerPoint, angleLen*sum / 360 );
437
* Auxiliary method returning a point to a given boundary
438
* rectangle of the enclosed ellipse and an angle.
440
QPointF RingDiagram::pointOnCircle( const QRectF& rect, int dataset, int pie, bool outer, qreal angle, qreal totalGapFactor, qreal totalExplodeFactor )
442
qreal angleLen = d->angleLens[ dataset ][ pie ];
443
qreal startAngle = d->startAngles[ dataset ][ pie ];
444
QModelIndex index( model()->index( dataset, pie, rootIndex() ) );
445
const PieAttributes attrs( pieAttributes( index ) );
447
const int rCount = rowCount();
449
//const qreal gapFactor = attrs.gapFactor( false );
451
//qDebug() << "##" << attrs.explode();
452
//if ( attrs.explodeFactor() != 0.0 )
453
// qDebug() << attrs.explodeFactor();
456
qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1;
459
//maxExplode /= rCount;
461
//qDebug() << "dataset=" << dataset << "maxExplode=" << maxExplode;
463
//level += maxExplode;
465
const qreal offsetX = rCount > 0 ? level * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
466
const qreal offsetY = rCount > 0 ? level * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
467
const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
468
const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
469
const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
470
const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ( ( rCount + 1 ) * 2 ): 0.0;
472
qreal explodeAngleRad = DEGTORAD( angle );
473
qreal cosAngle = cos( explodeAngleRad );
474
qreal sinAngle = -sin( explodeAngleRad );
475
qreal explodeAngleCenterRad = DEGTORAD( startAngle + angleLen / 2.0 );
476
qreal cosAngleCenter = cos( explodeAngleCenterRad );
477
qreal sinAngleCenter = -sin( explodeAngleCenterRad );
478
return QPointF( ( offsetX + gapOffsetX ) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(),
479
( offsetY + gapOffsetY ) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y() );
483
double RingDiagram::valueTotals() const
485
const int rCount = rowCount();
486
const int colCount = columnCount();
488
for ( int i = 0; i < rCount; ++i ) {
489
for ( int j = 0; j < colCount; ++j ) {
490
total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toDouble());
491
//qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
497
double RingDiagram::valueTotals( int dataset ) const
499
const int colCount = columnCount();
501
for ( int j = 0; j < colCount; ++j ) {
502
total += qAbs(model()->data( model()->index( dataset, j, rootIndex() ) ).toDouble());
503
//qDebug() << model()->data( model()->index( 0, j, rootIndex() ) ).toDouble();
509
double RingDiagram::numberOfValuesPerDataset() const
511
return model() ? model()->columnCount( rootIndex() ) : 0.0;
514
double RingDiagram::numberOfDatasets() const
516
return model() ? model()->rowCount( rootIndex() ) : 0.0;
520
double RingDiagram::numberOfGridRings() const