28 #include "KDChartPieDiagram_p.h"
32 #include "KDChartPolarCoordinatePlane_p.h"
34 #include "KDChartPainterSaver_p.h"
36 #include <KDABLibFakes>
39 using namespace KDChart;
41 PieDiagram::Private::Private()
42 : labelDecorations(
PieDiagram::NoDecoration ),
43 isCollisionAvoidanceEnabled( false )
47 PieDiagram::Private::~Private() {}
61 void PieDiagram::init()
75 d->labelDecorations = decorations;
80 return d->labelDecorations;
85 d->isCollisionAvoidanceEnabled = enabled;
90 return d->isCollisionAvoidanceEnabled;
99 QPointF bottomLeft( QPointF( 0, 0 ) );
102 if ( attrs.explode() ) {
104 qreal maxExplode = 0.0;
105 for (
int j = 0; j < colCount; ++j ) {
107 maxExplode = qMax( maxExplode, columnAttrs.
explodeFactor() );
109 topRight = QPointF( 1.0 + maxExplode, 1.0 + maxExplode );
111 topRight = QPointF( 1.0, 1.0 );
119 QPainter painter ( viewport() );
142 paintInternal( ctx );
145 void PieDiagram::calcSliceAngles()
149 const qreal sectorsPerValue = 360.0 / sum;
154 d->startAngles.resize( colCount );
155 d->angleLens.resize( colCount );
157 bool atLeastOneValue =
false;
158 for (
int iColumn = 0; iColumn < colCount; ++iColumn ) {
160 const qreal cellValue = qAbs( model()->data( model()->index( 0, iColumn, rootIndex() ) )
163 atLeastOneValue = atLeastOneValue || isOk;
165 d->startAngles[ iColumn ] = currentValue;
166 d->angleLens[ iColumn ] = cellValue * sectorsPerValue;
168 currentValue =
d->startAngles[ iColumn ] +
d->angleLens[ iColumn ];
172 if ( !atLeastOneValue ) {
173 d->startAngles.clear();
174 d->angleLens.clear();
178 void PieDiagram::calcPieSize(
const QRectF &contentsRect )
180 d->size = qMin( contentsRect.width(), contentsRect.height() );
183 qreal maxExplode = 0.0;
185 for (
int j = 0; j < colCount; ++j ) {
187 maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() );
189 d->size /= ( 1.0 + 1.0 * maxExplode );
191 if (
d->size < 0.0 ) {
197 QRectF PieDiagram::twoDPieRect(
const QRectF &contentsRect,
const ThreeDPieAttributes& threeDAttrs )
const
201 qreal x = ( contentsRect.width() -
d->size ) / 2.0;
202 qreal y = ( contentsRect.height() -
d->size ) / 2.0;
203 pieRect = QRectF( contentsRect.left() + x, contentsRect.top() + y,
d->size,
d->size );
206 qreal sizeFor3DEffect = 0.0;
208 qreal x = ( contentsRect.width() -
d->size ) / 2.0;
209 qreal height =
d->size;
212 if ( threeDAttrs.
depth() >= 0.0 ) {
214 sizeFor3DEffect = threeDAttrs.
depth();
215 height =
d->size - sizeFor3DEffect;
218 sizeFor3DEffect = - threeDAttrs.
depth() / 100.0 * height;
219 height =
d->size - sizeFor3DEffect;
221 qreal y = ( contentsRect.height() - height - sizeFor3DEffect ) / 2.0;
223 pieRect = QRectF( contentsRect.left() + x, contentsRect.top() + y,
d->size, height );
228 void PieDiagram::placeLabels(
PaintContext* paintContext )
240 d->reverseMapper.clear();
243 if (
d->startAngles.isEmpty() ) {
247 calcPieSize( paintContext->
rectangle() );
251 bool tryAgain =
true;
255 QRectF pieRect = twoDPieRect( paintContext->
rectangle(), threeDAttrs );
256 d->forgetAlreadyPaintedDataValues();
257 d->labelPaintCache.clear();
259 for (
int slice = 0; slice < colCount; slice++ ) {
260 if (
d->angleLens[ slice ] != 0.0 ) {
261 const QRectF explodedPieRect = explodedDrawPosition( pieRect, slice );
262 addSliceLabel( &
d->labelPaintCache, explodedPieRect, slice );
266 QRectF textBoundingRect;
267 d->paintDataValueTextsAndMarkers( paintContext,
d->labelPaintCache,
false,
true,
269 if (
d->isCollisionAvoidanceEnabled ) {
270 shuffleLabels( &textBoundingRect );
273 if ( !textBoundingRect.isEmpty() &&
d->size > 0.0 ) {
274 const QRectF &clipRect = paintContext->
rectangle();
276 qreal right = qMax( qreal( 0.0 ), textBoundingRect.right() - clipRect.right() );
277 qreal left = qMax( qreal( 0.0 ), clipRect.left() - textBoundingRect.left() );
279 qreal top = qMax( qreal( 0.0 ), clipRect.top() - textBoundingRect.top() );
280 qreal bottom = qMax( qreal( 0.0 ), textBoundingRect.bottom() - clipRect.bottom() );
281 qreal maxOverhang = qMax( qMax( right, left ), qMax( top, bottom ) );
283 if ( maxOverhang > 0.0 ) {
286 d->size -= qMin(
d->size, maxOverhang * 2.0 );
298 while ( i >= size ) {
306 void PieDiagram::shuffleLabels( QRectF* textBoundingRect )
316 LabelPaintCache& lpc =
d->labelPaintCache;
317 const int n = lpc.paintReplay.size();
318 bool modified =
false;
319 qreal direction = 5.0;
321 offsets.fill( 0.0, n );
323 for (
bool lastRoundModified =
true; lastRoundModified; ) {
324 lastRoundModified =
false;
326 for (
int i = 0; i < n; i++ ) {
327 const int neighborsToCheck = qMax( 10, lpc.paintReplay.size() - 1 );
328 const int minComp =
wraparound( i - neighborsToCheck / 2, n );
329 const int maxComp =
wraparound( i + ( neighborsToCheck + 1 ) / 2, n );
331 QPainterPath& path = lpc.paintReplay[ i ].labelArea;
333 for (
int j = minComp; j != maxComp; j =
wraparound( j + 1, n ) ) {
337 QPainterPath& otherPath = lpc.paintReplay[ j ].labelArea;
339 while ( ( offsets[ i ] + direction > 0 ) && otherPath.intersects( path ) ) {
341 qDebug() <<
"collision involving" << j <<
"and" << i <<
" -- n =" << n;
343 ta.
setPen( QPen( Qt::white ) );
344 lpc.paintReplay[ i ].attrs.setTextAttributes( ta );
346 uint slice = lpc.paintReplay[ i ].index.column();
347 qreal angle = DEGTORAD(
d->startAngles[ slice ] +
d->angleLens[ slice ] / 2.0 );
348 qreal dx = cos( angle ) * direction;
349 qreal dy = -sin( angle ) * direction;
350 offsets[ i ] += direction;
351 path.translate( dx, dy );
352 lastRoundModified =
true;
357 modified = modified || lastRoundModified;
361 for (
int i = 0; i < lpc.paintReplay.size(); i++ ) {
362 *textBoundingRect |= lpc.paintReplay[ i ].labelArea.boundingRect();
370 for (
int i = 0; i < pp.elementCount(); i++ ) {
371 const QPainterPath::Element& el = pp.elementAt( i );
372 Q_ASSERT( el.type == QPainterPath::MoveToElement || el.type == QPainterPath::LineToElement );
381 const qreal dotProduct = l1.dx() * l2.dx() + l1.dy() * l2.dy();
382 return qAbs( dotProduct / ( l1.length() * l2.length() ) );
385 static QLineF
labelAttachmentLine(
const QPointF ¢er,
const QPointF &start,
const QPainterPath &label )
387 Q_ASSERT ( label.elementCount() == 5 );
391 const qreal pieRadius = QLineF( center, start ).length();
395 for (
int i = 0; i < 4; i++ ) {
396 if ( QLineF( label.elementAt( i ), center ).length() < pieRadius ) {
402 QPointF closeCorners[3];
404 QPointF closest = QPointF( 1000000, 1000000 );
405 int closestIndex = 0;
406 for (
int i = 0; i < 4; i++ ) {
407 QPointF p = label.elementAt( i );
408 if ( QLineF( p, center ).length() < QLineF( closest, center ).length() ) {
414 closeCorners[ 0 ] = label.elementAt(
wraparound( closestIndex - 1, 4 ) );
415 closeCorners[ 1 ] = closest;
416 closeCorners[ 2 ] = label.elementAt(
wraparound( closestIndex + 1, 4 ) );
419 QLineF edge1 = QLineF( closeCorners[ 0 ], closeCorners[ 1 ] );
420 QLineF edge2 = QLineF( closeCorners[ 1 ], closeCorners[ 2 ] );
421 QLineF connection1 = QLineF( ( closeCorners[ 0 ] + closeCorners[ 1 ] ) / 2.0, center );
422 QLineF connection2 = QLineF( ( closeCorners[ 1 ] + closeCorners[ 2 ] ) / 2.0, center );
432 ret.setP2( ( start + center ) / 2.0 );
435 qreal p1Radius = QLineF( ret.p1(), center ).length();
436 ret.setLength( p1Radius - pieRadius );
441 void PieDiagram::paintInternal(
PaintContext* paintContext )
458 QRectF pieRect = twoDPieRect( paintContext->
rectangle(), threeDAttrs );
459 const int backmostSlice = findSliceAt( 90, colCount );
460 const int frontmostSlice = findSliceAt( 270, colCount );
461 int currentLeftSlice = backmostSlice;
462 int currentRightSlice = backmostSlice;
464 drawSlice( paintContext->
painter(), pieRect, backmostSlice );
466 if ( backmostSlice == frontmostSlice ) {
467 const int rightmostSlice = findSliceAt( 0, colCount );
468 const int leftmostSlice = findSliceAt( 180, colCount );
470 if ( backmostSlice == leftmostSlice ) {
471 currentLeftSlice = findLeftSlice( currentLeftSlice, colCount );
473 if ( backmostSlice == rightmostSlice ) {
474 currentRightSlice = findRightSlice( currentRightSlice, colCount );
478 while ( currentLeftSlice != frontmostSlice ) {
479 if ( currentLeftSlice != backmostSlice ) {
480 drawSlice( paintContext->
painter(), pieRect, currentLeftSlice );
482 currentLeftSlice = findLeftSlice( currentLeftSlice, colCount );
485 while ( currentRightSlice != frontmostSlice ) {
486 if ( currentRightSlice != backmostSlice ) {
487 drawSlice( paintContext->
painter(), pieRect, currentRightSlice );
489 currentRightSlice = findRightSlice( currentRightSlice, colCount );
494 drawSlice( paintContext->
painter(), pieRect, frontmostSlice );
497 d->paintDataValueTextsAndMarkers( paintContext,
d->labelPaintCache,
false,
false );
499 d->forgetAlreadyPaintedDataValues();
501 const QPointF center = paintContext->
rectangle().center();
502 const PainterSaver painterSaver( paintContext->
painter() );
503 paintContext->
painter()->setBrush( Qt::NoBrush );
504 KDAB_FOREACH(
const LabelPaintInfo &pi,
d->labelPaintCache.paintReplay ) {
506 if ( pi.labelArea.elementCount() != 5 ) {
510 paintContext->
painter()->setPen(
pen( pi.index ) );
515 paintContext->
painter()->drawPath( pi.labelArea );
517 d->reverseMapper.addPolygon( pi.index.row(), pi.index.column(),
520 d->labelPaintCache.clear();
521 d->startAngles.clear();
522 d->angleLens.clear();
525 #if defined ( Q_OS_WIN)
526 #define trunc(x) ((int)(x))
529 QRectF PieDiagram::explodedDrawPosition(
const QRectF& drawPosition, uint slice )
const
531 const QModelIndex index( model()->index( 0, slice, rootIndex() ) );
534 QRectF adjustedDrawPosition = drawPosition;
535 if ( attrs.explode() ) {
536 qreal startAngle =
d->startAngles[ slice ];
537 qreal angleLen =
d->angleLens[ slice ];
538 qreal explodeAngle = ( DEGTORAD( startAngle + angleLen / 2.0 ) );
539 qreal explodeDistance = attrs.explodeFactor() *
d->size / 2.0;
541 adjustedDrawPosition.translate( explodeDistance * cos( explodeAngle ),
542 explodeDistance * - sin( explodeAngle ) );
544 return adjustedDrawPosition;
555 void PieDiagram::drawSlice( QPainter* painter,
const QRectF& drawPosition, uint slice)
558 if (
d->angleLens[ slice ] == 0.0 ) {
561 const QRectF adjustedDrawPosition = explodedDrawPosition( drawPosition, slice );
562 draw3DEffect( painter, adjustedDrawPosition, slice );
563 drawSliceSurface( painter, adjustedDrawPosition, slice );
573 void PieDiagram::drawSliceSurface( QPainter* painter,
const QRectF& drawPosition, uint slice )
576 const qreal angleLen =
d->angleLens[ slice ];
577 const qreal startAngle =
d->startAngles[ slice ];
578 const QModelIndex index( model()->index( 0, slice, rootIndex() ) );
583 painter->setRenderHint ( QPainter::Antialiasing );
584 QBrush br =
brush( index );
588 painter->setBrush( br );
590 QPen
pen = this->
pen( index );
592 pen.setColor( Qt::black );
594 painter->setPen( pen );
596 if ( angleLen == 360 ) {
598 painter->drawEllipse( drawPosition );
601 QPolygonF poly( drawPosition );
602 d->reverseMapper.addPolygon( index.row(), index.column(), poly );
606 const int arcPoints =
static_cast<int>(trunc( angleLen /
granularity() ));
607 QPolygonF poly( arcPoints + 2 );
610 bool perfectMatch =
false;
612 while ( degree <= angleLen ) {
613 poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + degree );
615 perfectMatch = ( degree == angleLen );
620 if ( !perfectMatch ) {
621 poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + angleLen );
624 poly.append( drawPosition.center() );
626 poly[ iPoint ] = drawPosition.center();
630 d->reverseMapper.addPolygon( index.row(), index.column(), poly );
632 painter->drawPolygon( poly );
637 void PieDiagram::addSliceLabel( LabelPaintCache* lpc,
const QRectF& drawPosition, uint slice )
639 const qreal angleLen =
d->angleLens[ slice ];
640 const qreal startAngle =
d->startAngles[ slice ];
641 const QModelIndex index( model()->index( 0, slice, rootIndex() ) );
648 const QPointF south = drawPosition.center();
649 const QPointF southEast = south;
650 const QPointF southWest = south;
651 const QPointF north = pointOnEllipse( drawPosition, startAngle + angleLen / 2.0 );
653 const QPointF northEast = pointOnEllipse( drawPosition, startAngle );
654 const QPointF northWest = pointOnEllipse( drawPosition, startAngle + angleLen );
655 QPointF center = ( south + north ) / 2.0;
656 const QPointF east = ( south + northEast ) / 2.0;
657 const QPointF west = ( south + northWest ) / 2.0;
659 PositionPoints points( center, northWest, north, northEast, east, southEast, south, southWest, west );
660 qreal topAngle = startAngle - 90;
661 if ( topAngle < 0.0 ) {
672 qreal favoriteTextAngle = 0.0;
674 favoriteTextAngle = - ( startAngle + angleLen / 2 ) + 90.0;
675 while ( favoriteTextAngle <= 0.0 ) {
676 favoriteTextAngle += 360.0;
679 if ( favoriteTextAngle > 90.0 && favoriteTextAngle < 270.0 ) {
680 favoriteTextAngle = favoriteTextAngle - 180.0;
683 if ( favoriteTextAngle <= 0.0 ) {
684 favoriteTextAngle += 360.0;
689 angleLen * sum / 360, favoriteTextAngle );
692 static bool doSpansOverlap( qreal s1Start, qreal s1End, qreal s2Start, qreal s2End )
694 if ( s1Start < s2Start ) {
695 return s1End >= s2Start;
697 return s1Start <= s2End;
701 static bool doArcsOverlap( qreal a1Start, qreal a1End, qreal a2Start, qreal a2End )
703 Q_ASSERT( a1Start >= 0 && a1Start <= 360 && a1End >= 0 && a1End <= 360 &&
704 a2Start >= 0 && a2Start <= 360 && a2End >= 0 && a2End <= 360 );
706 if ( a1End < a1Start ) {
709 if ( a2End < a2Start ) {
716 if ( a1Start > a2Start ) {
717 return doSpansOverlap( a1Start - 360.0, a1End - 360.0, a2Start, a2End );
719 return doSpansOverlap( a1Start + 360.0, a1End + 360.0, a2Start, a2End );
730 void PieDiagram::draw3DEffect( QPainter* painter,
const QRectF& drawPosition, uint slice )
732 const QModelIndex index( model()->index( 0, slice, rootIndex() ) );
746 const QBrush
brush = this->
brush( model()->index( 0, slice, rootIndex() ) );
748 painter->setBrush( QBrush( brush.color().darker() ) );
750 painter->setBrush( brush );
753 qreal startAngle =
d->startAngles[ slice ];
754 qreal endAngle = startAngle +
d->angleLens[ slice ];
756 while ( startAngle >= 360 )
758 while ( endAngle >= 360 )
760 Q_ASSERT( startAngle >= 0 && startAngle <= 360 );
761 Q_ASSERT( endAngle >= 0 && endAngle <= 360 );
765 const int depth = threeDAttrs.
depth() >= 0.0 ? threeDAttrs.
depth() : -threeDAttrs.
depth() / 100.0 * drawPosition.height();
767 if ( startAngle == endAngle || startAngle == endAngle - 360 ) {
768 draw3dOuterRim( painter, drawPosition, depth, 180, 360 );
771 draw3dOuterRim( painter, drawPosition, depth, startAngle, endAngle );
774 if ( startAngle >= 270 || startAngle <= 90 ) {
775 draw3dCutSurface( painter, drawPosition, depth, startAngle );
777 if ( endAngle >= 90 && endAngle <= 270 ) {
778 draw3dCutSurface( painter, drawPosition, depth, endAngle );
793 void PieDiagram::draw3dCutSurface( QPainter* painter,
799 const QPointF center = rect.center();
800 const QPointF circlePoint = pointOnEllipse( rect, angle );
802 poly[1] = circlePoint;
803 poly[2] = QPointF( circlePoint.x(), circlePoint.y() + threeDHeight );
804 poly[3] = QPointF( center.x(), center.y() + threeDHeight );
806 painter->drawPolygon( poly );
818 void PieDiagram::draw3dOuterRim( QPainter* painter,
825 if ( endAngle < startAngle ) {
828 startAngle = qMax( startAngle, qreal( 180.0 ) );
829 endAngle = qMin( endAngle, qreal( 360.0 ) );
831 int numHalfPoints = trunc( ( endAngle - startAngle ) /
granularity() ) + 1;
832 if ( numHalfPoints < 2 ) {
836 QPolygonF poly( numHalfPoints );
838 qreal degree = endAngle;
840 bool perfectMatch =
false;
841 while ( degree >= startAngle ) {
842 poly[ numHalfPoints - iPoint - 1 ] = pointOnEllipse( rect, degree );
844 perfectMatch = (degree == startAngle);
849 if ( !perfectMatch ) {
850 poly.prepend( pointOnEllipse( rect, startAngle ) );
854 poly.resize( numHalfPoints * 2 );
858 for (
int i = numHalfPoints - 1; i >= 0; --i ) {
859 QPointF pointOnFirstArc( poly[ i ] );
860 pointOnFirstArc.setY( pointOnFirstArc.y() + threeDHeight );
861 poly[ numHalfPoints * 2 - i - 1 ] = pointOnFirstArc;
865 painter->drawPolygon( poly );
874 uint PieDiagram::findSliceAt( qreal angle,
int colCount )
876 for (
int i = 0; i < colCount; ++i ) {
877 qreal endseg =
d->startAngles[ i ] +
d->angleLens[ i ];
878 if (
d->startAngles[ i ] <= angle && endseg >= angle ) {
886 return findSliceAt( angle + 360, colCount );
898 uint PieDiagram::findLeftSlice( uint slice,
int colCount )
901 if ( colCount > 1 ) {
918 uint PieDiagram::findRightSlice( uint slice,
int colCount )
920 int rightSlice = slice + 1;
921 if ( rightSlice == colCount ) {
932 QPointF PieDiagram::pointOnEllipse(
const QRectF& boundingBox, qreal angle )
934 qreal angleRad = DEGTORAD( angle );
935 qreal cosAngle = cos( angleRad );
936 qreal sinAngle = -sin( angleRad );
937 qreal posX = cosAngle * boundingBox.width() / 2.0;
938 qreal posY = sinAngle * boundingBox.height() / 2.0;
939 return QPointF( posX + boundingBox.center().x(),
940 posY + boundingBox.center().y() );
951 Q_ASSERT( model()->
rowCount() >= 1 );
952 for (
int j = 0; j < colCount; ++j ) {
953 total += qAbs(model()->data( model()->index( 0, j, rootIndex() ) ).toReal());
961 return model() ? model()->columnCount( rootIndex() ) : 0.0;