28 #include "KDChartPieDiagram_p.h" 32 #include "KDChartPolarCoordinatePlane_p.h" 34 #include "KDChartPainterSaver_p.h" 36 #include <KDABLibFakes> 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 * (qreal)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;
536 qreal startAngle =
d->startAngles[ slice ];
537 qreal angleLen =
d->angleLens[ slice ];
538 qreal explodeAngle = ( DEGTORAD( startAngle + angleLen / 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;
void resizeEvent(QResizeEvent *) override
const QRectF rectangle() const
void setPainter(QPainter *painter)
A line is drawn from the pie slice to its label.
virtual bool checkInvariants(bool justReturnTheStatus=false) const
static QLineF labelAttachmentLine(const QPointF ¢er, const QPointF &start, const QPainterPath &label)
qreal numberOfGridRings() const override
[reimplemented]
A rectangular frame is painted around the label text.
static bool doArcsOverlap(qreal a1Start, qreal a1End, qreal a2Start, qreal a2End)
void setLabelCollisionAvoidanceEnabled(bool enabled)
If enabled is set to true, labels that would overlap will be shuffled to avoid overlap.
void setPen(const QPen &pen)
Set the pen to use for rendering the text.
qreal numberOfValuesPerDataset() const override
[reimplemented]
virtual PieDiagram * clone() const
Creates an exact copy of this diagram.
static int wraparound(int i, int size)
QPainter * painter() const
static const Position & Center
qreal granularity() const
bool useShadowColors() const
PieAttributes pieAttributes() const
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
A set of attributes controlling the appearance of pie charts.
static qreal normProjection(const QLineF &l1, const QLineF &l2)
void setLabelDecorations(LabelDecorations decorations)
Set the decorations to be painted around data labels according to decorations.
void setDegrees(KDChartEnums::PositionValue pos, qreal degrees)
qreal valueTotals() const override
[reimplemented]
void resize(const QSizeF &area) override
[reimplemented]
const PolarCoordinatePlane * polarCoordinatePlane() const
qreal explodeFactor() const
static bool doSpansOverlap(qreal s1Start, qreal s1End, qreal s2Start, qreal s2End)
Stores information about painting diagrams.
A set of 3D pie attributes.
Stores the absolute target points of a Position.
void paintEvent(QPaintEvent *) override
PieDiagram(QWidget *parent=0, PolarCoordinatePlane *plane=0)
Base class for any diagram type.
ThreeDPieAttributes threeDPieAttributes() const
bool autoRotateLabels() const
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
virtual QBrush threeDBrush(const QBrush &brush, const QRectF &rect) const
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
[reimplemented]
qreal startPosition() const
Retrieve the rotation of the coordinate plane.
bool isLabelCollisionAvoidanceEnabled() const
Return whether overlapping labels will be moved to until they don't overlap anymore.
void setRectangle(const QRectF &rect)
PieDiagram defines a common pie diagram.
LabelDecorations labelDecorations() const
Return the decorations to be painted around data labels.
A set of text attributes.
void paint(PaintContext *paintContext) override
[reimplemented]
static QPolygonF polygonFromPainterPath(const QPainterPath &pp)