9#include "KChartPieDiagram.h"
10#include "KChartPieDiagram_p.h"
12#include "KChartPaintContext.h"
13#include "KChartPieAttributes.h"
14#include "KChartPolarCoordinatePlane_p.h"
15#include "KChartThreeDPieAttributes.h"
16#include "KChartPainterSaver_p.h"
17#include "KChartMath_p.h"
26PieDiagram::Private::Private()
27 : labelDecorations(
PieDiagram::NoDecoration ),
28 isCollisionAvoidanceEnabled( false )
32PieDiagram::Private::~Private() {}
42PieDiagram::~PieDiagram()
46void PieDiagram::init()
57 d->labelDecorations = decorations;
62 return d->labelDecorations;
67 d->isCollisionAvoidanceEnabled =
enabled;
72 return d->isCollisionAvoidanceEnabled;
77 if ( !checkInvariants(
true ) ||
model()->rowCount() < 1 )
return QPair<QPointF, QPointF>(
QPointF( 0, 0 ),
QPointF( 0, 0 ) );
85 const int colCount = columnCount();
86 qreal maxExplode = 0.0;
87 for (
int j = 0; j < colCount; ++j ) {
91 topRight =
QPointF( 1.0 + maxExplode, 1.0 + maxExplode );
95 return QPair<QPointF, QPointF> ( bottomLeft, topRight );
103 ctx.setPainter ( &painter );
125 paintInternal( ctx );
128void PieDiagram::calcSliceAngles()
132 const qreal sectorsPerValue = 360.0 / sum;
136 const int colCount = columnCount();
137 d->startAngles.resize( colCount );
138 d->angleLens.resize( colCount );
140 bool atLeastOneValue =
false;
141 for (
int iColumn = 0; iColumn < colCount; ++iColumn ) {
146 atLeastOneValue = atLeastOneValue || isOk;
148 d->startAngles[ iColumn ] = currentValue;
149 d->angleLens[ iColumn ] = cellValue * sectorsPerValue;
151 currentValue = d->startAngles[ iColumn ] + d->angleLens[ iColumn ];
155 if ( !atLeastOneValue ) {
156 d->startAngles.clear();
157 d->angleLens.clear();
161void PieDiagram::calcPieSize(
const QRectF &contentsRect )
166 qreal maxExplode = 0.0;
167 const int colCount = columnCount();
168 for (
int j = 0; j < colCount; ++j ) {
170 maxExplode = qMax( maxExplode, columnAttrs.explodeFactor() );
172 d->size /= ( 1.0 + 1.0 * maxExplode );
174 if ( d->size < 0.0 ) {
183 if ( !threeDAttrs.isEnabled() ) {
189 qreal sizeFor3DEffect = 0.0;
195 if ( threeDAttrs.depth() >= 0.0 ) {
197 sizeFor3DEffect = threeDAttrs.depth();
198 height = d->size - sizeFor3DEffect;
201 sizeFor3DEffect = - threeDAttrs.depth() / 100.0 *
height;
202 height = d->size - sizeFor3DEffect;
211void PieDiagram::placeLabels(
PaintContext* paintContext )
213 if ( !checkInvariants(
true) ||
model()->rowCount() < 1 ) {
221 const int colCount = columnCount();
223 d->reverseMapper.clear();
226 if ( d->startAngles.isEmpty() ) {
230 calcPieSize( paintContext->rectangle() );
234 bool tryAgain =
true;
238 QRectF pieRect = twoDPieRect( paintContext->rectangle(), threeDAttrs );
239 d->forgetAlreadyPaintedDataValues();
240 d->labelPaintCache.clear();
242 for (
int slice = 0; slice < colCount; slice++ ) {
243 if ( d->angleLens[ slice ] != 0.0 ) {
244 const QRectF explodedPieRect = explodedDrawPosition( pieRect, slice );
245 addSliceLabel( &d->labelPaintCache, explodedPieRect, slice );
250 d->paintDataValueTextsAndMarkers( paintContext, d->labelPaintCache,
false,
true,
252 if ( d->isCollisionAvoidanceEnabled ) {
253 shuffleLabels( &textBoundingRect );
256 if ( !textBoundingRect.
isEmpty() && d->size > 0.0 ) {
257 const QRectF &clipRect = paintContext->rectangle();
259 qreal
right = qMax( qreal( 0.0 ), textBoundingRect.
right() - clipRect.
right() );
260 qreal
left = qMax( qreal( 0.0 ), clipRect.
left() - textBoundingRect.
left() );
262 qreal top = qMax( qreal( 0.0 ), clipRect.
top() - textBoundingRect.
top() );
263 qreal bottom = qMax( qreal( 0.0 ), textBoundingRect.
bottom() - clipRect.
bottom() );
264 qreal maxOverhang = qMax( qMax( right, left ), qMax( top, bottom ) );
266 if ( maxOverhang > 0.0 ) {
269 d->size -= qMin<qreal>( d->size, maxOverhang * 2.0 );
276static int wraparound(
int i,
int size )
281 while ( i >= size ) {
289void PieDiagram::shuffleLabels(
QRectF* textBoundingRect )
299 LabelPaintCache& lpc = d->labelPaintCache;
300 const int n = lpc.paintReplay.size();
301 bool modified =
false;
302 qreal direction = 5.0;
304 offsets.
fill( 0.0, n );
306 for (
bool lastRoundModified =
true; lastRoundModified; ) {
307 lastRoundModified =
false;
309 for (
int i = 0; i < n; i++ ) {
310 const int neighborsToCheck = qMax( 10, lpc.paintReplay.size() - 1 );
311 const int minComp = wraparound( i - neighborsToCheck / 2, n );
312 const int maxComp = wraparound( i + ( neighborsToCheck + 1 ) / 2, n );
316 for (
int j = minComp; j != maxComp; j = wraparound( j + 1, n ) ) {
320 QPainterPath& otherPath = lpc.paintReplay[ j ].labelArea;
322 while ( ( offsets[ i ] + direction > 0 ) && otherPath.
intersects( path ) ) {
324 qDebug() <<
"collision involving" << j <<
"and" << i <<
" -- n =" << n;
327 lpc.paintReplay[ i ].attrs.setTextAttributes( ta );
329 uint slice = lpc.paintReplay[ i ].index.column();
330 qreal angle = DEGTORAD( d->startAngles[ slice ] + d->angleLens[ slice ] / 2.0 );
331 qreal dx = cos( angle ) * direction;
332 qreal dy = -sin( angle ) * direction;
333 offsets[ i ] += direction;
334 path.translate( dx, dy );
335 lastRoundModified =
true;
340 modified = modified || lastRoundModified;
344 for (
int i = 0; i < lpc.paintReplay.size(); i++ ) {
345 *textBoundingRect |= lpc.paintReplay[ i ].labelArea.boundingRect();
362static qreal normProjection(
const QLineF &l1,
const QLineF &l2 )
364 const qreal dotProduct = l1.
dx() * l2.
dx() + l1.
dy() * l2.
dy();
365 return qAbs( dotProduct / ( l1.
length() * l2.
length() ) );
370 Q_ASSERT (
label.elementCount() == 5 );
378 for (
int i = 0; i < 4; i++ ) {
388 int closestIndex = 0;
389 for (
int i = 0; i < 4; i++ ) {
391 if (
QLineF( p, center ).length() <
QLineF( closest, center ).length() ) {
397 closeCorners[ 0 ] =
label.elementAt( wraparound( closestIndex - 1, 4 ) );
398 closeCorners[ 1 ] = closest;
399 closeCorners[ 2 ] =
label.elementAt( wraparound( closestIndex + 1, 4 ) );
402 QLineF edge1 =
QLineF( closeCorners[ 0 ], closeCorners[ 1 ] );
403 QLineF edge2 =
QLineF( closeCorners[ 1 ], closeCorners[ 2 ] );
404 QLineF connection1 =
QLineF( ( closeCorners[ 0 ] + closeCorners[ 1 ] ) / 2.0, center );
405 QLineF connection2 =
QLineF( ( closeCorners[ 1 ] + closeCorners[ 2 ] ) / 2.0, center );
408 if ( normProjection( edge1, connection1 ) < normProjection( edge2, connection2 ) ) {
424void PieDiagram::paintInternal(
PaintContext* paintContext )
428 if ( !checkInvariants(
true ) ||
model()->rowCount() < 1 ) {
431 if ( d->startAngles.isEmpty() || paintContext->rectangle().
isEmpty() ||
valueTotals() == 0.0 ) {
436 const int colCount = columnCount();
441 QRectF pieRect = twoDPieRect( paintContext->rectangle(), threeDAttrs );
442 const int backmostSlice = findSliceAt( 90, colCount );
443 const int frontmostSlice = findSliceAt( 270, colCount );
444 int currentLeftSlice = backmostSlice;
445 int currentRightSlice = backmostSlice;
447 drawSlice( paintContext->painter(), pieRect, backmostSlice );
449 if ( backmostSlice == frontmostSlice ) {
450 const int rightmostSlice = findSliceAt( 0, colCount );
451 const int leftmostSlice = findSliceAt( 180, colCount );
453 if ( backmostSlice == leftmostSlice ) {
454 currentLeftSlice = findLeftSlice( currentLeftSlice, colCount );
456 if ( backmostSlice == rightmostSlice ) {
457 currentRightSlice = findRightSlice( currentRightSlice, colCount );
461 while ( currentLeftSlice != frontmostSlice ) {
462 if ( currentLeftSlice != backmostSlice ) {
463 drawSlice( paintContext->painter(), pieRect, currentLeftSlice );
465 currentLeftSlice = findLeftSlice( currentLeftSlice, colCount );
468 while ( currentRightSlice != frontmostSlice ) {
469 if ( currentRightSlice != backmostSlice ) {
470 drawSlice( paintContext->painter(), pieRect, currentRightSlice );
472 currentRightSlice = findRightSlice( currentRightSlice, colCount );
476 if ( backmostSlice != frontmostSlice || ! threeDPieAttributes().
isEnabled() ) {
477 drawSlice( paintContext->painter(), pieRect, frontmostSlice );
480 d->paintDataValueTextsAndMarkers( paintContext, d->labelPaintCache,
false,
false );
482 d->forgetAlreadyPaintedDataValues();
485 const PainterSaver painterSaver( paintContext->painter() );
487 for(
const LabelPaintInfo &pi : std::as_const(d->labelPaintCache.paintReplay) ) {
489 if ( pi.labelArea.elementCount() != 5 ) {
493 paintContext->painter()->
setPen(
pen( pi.index ) );
495 paintContext->painter()->
drawLine( labelAttachmentLine( center, pi.markerPos, pi.labelArea ) );
498 paintContext->painter()->
drawPath( pi.labelArea );
500 d->reverseMapper.addPolygon( pi.index.row(), pi.index.column(),
501 polygonFromPainterPath( pi.labelArea ) );
503 d->labelPaintCache.clear();
504 d->startAngles.clear();
505 d->angleLens.clear();
508#if defined ( Q_OS_WIN)
509#define trunc(x) ((int)(x))
512QRectF PieDiagram::explodedDrawPosition(
const QRectF& drawPosition, uint slice )
const
517 QRectF adjustedDrawPosition = drawPosition;
518 if ( attrs.explode() ) {
519 qreal startAngle = d->startAngles[ slice ];
520 qreal angleLen = d->angleLens[ slice ];
521 qreal explodeAngle = ( DEGTORAD( startAngle + angleLen / 2.0 ) );
522 qreal explodeDistance = attrs.explodeFactor() * d->size / 2.0;
524 adjustedDrawPosition.
translate( explodeDistance * cos( explodeAngle ),
525 explodeDistance * - sin( explodeAngle ) );
527 return adjustedDrawPosition;
530void PieDiagram::drawSlice(
QPainter* painter,
const QRectF& drawPosition, uint slice)
533 if ( d->angleLens[ slice ] == 0.0 ) {
536 const QRectF adjustedDrawPosition = explodedDrawPosition( drawPosition, slice );
537 draw3DEffect( painter, adjustedDrawPosition, slice );
538 drawSliceSurface( painter, adjustedDrawPosition, slice );
541void PieDiagram::drawSliceSurface(
QPainter* painter,
const QRectF& drawPosition, uint slice )
544 const qreal angleLen = d->angleLens[ slice ];
545 const qreal startAngle = d->startAngles[ slice ];
553 if ( threeDAttrs.isEnabled() ) {
554 br = threeDAttrs.threeDBrush( br, drawPosition );
559 if ( threeDAttrs.isEnabled() ) {
564 if ( angleLen == 360 ) {
570 d->reverseMapper.addPolygon( index.row(), index.column(), poly );
574 const int arcPoints =
static_cast<int>(trunc( angleLen /
granularity() ));
578 bool perfectMatch =
false;
580 while ( degree <= angleLen ) {
581 poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + degree );
583 perfectMatch = ( degree == angleLen );
588 if ( !perfectMatch ) {
589 poly[ iPoint ] = pointOnEllipse( drawPosition, startAngle + angleLen );
592 poly.append( drawPosition.
center() );
594 poly[ iPoint ] = drawPosition.
center();
598 d->reverseMapper.addPolygon( index.row(), index.column(), poly );
605void PieDiagram::addSliceLabel( LabelPaintCache* lpc,
const QRectF& drawPosition, uint slice )
607 const qreal angleLen = d->angleLens[ slice ];
608 const qreal startAngle = d->startAngles[ slice ];
617 const QPointF southEast = south;
618 const QPointF southWest = south;
619 const QPointF north = pointOnEllipse( drawPosition, startAngle + angleLen / 2.0 );
621 const QPointF northEast = pointOnEllipse( drawPosition, startAngle );
622 const QPointF northWest = pointOnEllipse( drawPosition, startAngle + angleLen );
624 const QPointF east = ( south + northEast ) / 2.0;
625 const QPointF west = ( south + northWest ) / 2.0;
627 PositionPoints points( center, northWest, north, northEast, east, southEast, south, southWest, west );
628 qreal topAngle = startAngle - 90;
629 if ( topAngle < 0.0 ) {
633 points.setDegrees( KChartEnums::PositionEast, topAngle );
634 points.setDegrees( KChartEnums::PositionNorthEast, topAngle );
635 points.setDegrees( KChartEnums::PositionWest, topAngle + angleLen );
636 points.setDegrees( KChartEnums::PositionNorthWest, topAngle + angleLen );
637 points.setDegrees( KChartEnums::PositionCenter, topAngle + angleLen / 2.0 );
638 points.setDegrees( KChartEnums::PositionNorth, topAngle + angleLen / 2.0 );
640 qreal favoriteTextAngle = 0.0;
642 favoriteTextAngle = - ( startAngle + angleLen / 2 ) + 90.0;
643 while ( favoriteTextAngle <= 0.0 ) {
644 favoriteTextAngle += 360.0;
647 if ( favoriteTextAngle > 90.0 && favoriteTextAngle < 270.0 ) {
648 favoriteTextAngle = favoriteTextAngle - 180.0;
651 if ( favoriteTextAngle <= 0.0 ) {
652 favoriteTextAngle += 360.0;
656 d->addLabel( lpc, index,
nullptr, points, Position::Center, Position::Center,
657 angleLen * sum / 360, favoriteTextAngle );
660static bool doSpansOverlap( qreal s1Start, qreal s1End, qreal s2Start, qreal s2End )
662 if ( s1Start < s2Start ) {
663 return s1End >= s2Start;
665 return s1Start <= s2End;
669static bool doArcsOverlap( qreal a1Start, qreal a1End, qreal a2Start, qreal a2End )
671 Q_ASSERT( a1Start >= 0 && a1Start <= 360 && a1End >= 0 && a1End <= 360 &&
672 a2Start >= 0 && a2Start <= 360 && a2End >= 0 && a2End <= 360 );
674 if ( a1End < a1Start ) {
677 if ( a2End < a2Start ) {
681 if ( doSpansOverlap( a1Start, a1End, a2Start, a2End ) ) {
684 if ( a1Start > a2Start ) {
685 return doSpansOverlap( a1Start - 360.0, a1End - 360.0, a2Start, a2End );
687 return doSpansOverlap( a1Start + 360.0, a1End + 360.0, a2Start, a2End );
691void PieDiagram::draw3DEffect(
QPainter* painter,
const QRectF& drawPosition, uint slice )
695 if ( ! threeDAttrs.isEnabled() ) {
708 if ( threeDAttrs.useShadowColors() ) {
714 qreal startAngle = d->startAngles[ slice ];
715 qreal endAngle = startAngle + d->angleLens[ slice ];
717 while ( startAngle >= 360 )
719 while ( endAngle >= 360 )
721 Q_ASSERT( startAngle >= 0 && startAngle <= 360 );
722 Q_ASSERT( endAngle >= 0 && endAngle <= 360 );
726 const int depth = threeDAttrs.depth() >= 0.0 ? threeDAttrs.depth() : -threeDAttrs.depth() / 100.0 * drawPosition.
height();
728 if ( startAngle == endAngle || startAngle == endAngle - 360 ) {
729 draw3dOuterRim( painter, drawPosition,
depth, 180, 360 );
731 if ( doArcsOverlap( startAngle, endAngle, 180, 360 ) ) {
732 draw3dOuterRim( painter, drawPosition,
depth, startAngle, endAngle );
735 if ( startAngle >= 270 || startAngle <= 90 ) {
736 draw3dCutSurface( painter, drawPosition,
depth, startAngle );
738 if ( endAngle >= 90 && endAngle <= 270 ) {
739 draw3dCutSurface( painter, drawPosition,
depth, endAngle );
745void PieDiagram::draw3dCutSurface(
QPainter* painter,
752 const QPointF circlePoint = pointOnEllipse(
rect, angle );
754 poly[1] = circlePoint;
755 poly[2] =
QPointF( circlePoint.
x(), circlePoint.
y() + threeDHeight );
761void PieDiagram::draw3dOuterRim(
QPainter* painter,
768 if ( endAngle < startAngle ) {
771 startAngle = qMax( startAngle, qreal( 180.0 ) );
772 endAngle = qMin( endAngle, qreal( 360.0 ) );
774 int numHalfPoints = trunc( ( endAngle - startAngle ) /
granularity() ) + 1;
775 if ( numHalfPoints < 2 ) {
781 qreal degree = endAngle;
783 bool perfectMatch =
false;
784 while ( degree >= startAngle ) {
785 poly[ numHalfPoints - iPoint - 1 ] = pointOnEllipse(
rect, degree );
787 perfectMatch = (degree == startAngle);
792 if ( !perfectMatch ) {
793 poly.prepend( pointOnEllipse(
rect, startAngle ) );
797 poly.resize( numHalfPoints * 2 );
801 for (
int i = numHalfPoints - 1; i >= 0; --i ) {
802 QPointF pointOnFirstArc( poly[ i ] );
803 pointOnFirstArc.setY( pointOnFirstArc.y() + threeDHeight );
804 poly[ numHalfPoints * 2 - i - 1 ] = pointOnFirstArc;
811uint PieDiagram::findSliceAt( qreal angle,
int colCount )
813 for (
int i = 0; i < colCount; ++i ) {
814 qreal endseg = d->startAngles[ i ] + d->angleLens[ i ];
815 if ( d->startAngles[ i ] <= angle && endseg >= angle ) {
823 return findSliceAt( angle + 360, colCount );
829uint PieDiagram::findLeftSlice( uint slice,
int colCount )
832 if ( colCount > 1 ) {
843uint PieDiagram::findRightSlice( uint slice,
int colCount )
845 int rightSlice = slice + 1;
846 if ( rightSlice == colCount ) {
853QPointF PieDiagram::pointOnEllipse(
const QRectF& boundingBox, qreal angle )
855 qreal angleRad = DEGTORAD( angle );
856 qreal cosAngle = cos( angleRad );
857 qreal sinAngle = -sin( angleRad );
858 qreal posX = cosAngle * boundingBox.
width() / 2.0;
859 qreal posY = sinAngle * boundingBox.
height() / 2.0;
861 posY + boundingBox.
center().
y() );
870 const int colCount = columnCount();
873 Q_ASSERT( colCount == 0 ||
model()->rowCount() >= 1 );
874 for (
int j = 0; j < colCount; ++j ) {
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
Base class for any diagram type.
qreal granularity() const
bool autoRotateLabels() const
Stores information about painting diagrams.
A set of attributes controlling the appearance of pie charts.
qreal explodeFactor() const
PieDiagram defines a common pie diagram.
bool isLabelCollisionAvoidanceEnabled() const
Return whether overlapping labels will be moved to until they don't overlap anymore.
LabelDecorations labelDecorations() const
Return the decorations to be painted around data labels.
void setLabelDecorations(LabelDecorations decorations)
Set the decorations to be painted around data labels according to decorations.
qreal valueTotals() const override
\reimpl
void paint(PaintContext *paintContext) override
\reimpl
qreal numberOfGridRings() const override
\reimpl
virtual PieDiagram * clone() const
Creates an exact copy of this diagram.
void resize(const QSizeF &area) override
\reimpl
@ FrameDecoration
A rectangular frame is painted around the label text.
@ LineFromSliceDecoration
A line is drawn from the pie slice to its label.
qreal numberOfValuesPerDataset() const override
\reimpl
void setLabelCollisionAvoidanceEnabled(bool enabled)
If enabled is set to true, labels that would overlap will be shuffled to avoid overlap.
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
\reimpl
qreal startPosition() const
Retrieve the rotation of the coordinate plane.
Stores the absolute target points of a Position.
A set of text attributes.
void setPen(const QPen &pen)
Set the pen to use for rendering the text.
A set of 3D pie attributes.
Q_SCRIPTABLE Q_NOREPLY void start()
void init(KXmlGuiWindow *window, KGameDifficulty *difficulty=nullptr)
QString path(const QString &relativePath)
QString label(StandardShortcut id)
virtual int columnCount(const QModelIndex &parent) const const=0
QAbstractItemModel * model() const const
QModelIndex rootIndex() const const
const QColor & color() const const
QColor darker(int factor) const const
qreal length() const const
void setLength(qreal length)
void setP2(const QPointF &p2)
void append(QList< T > &&value)
QList< T > & fill(parameter_type value, qsizetype size)
void drawEllipse(const QPoint ¢er, int rx, int ry)
void drawLine(const QLine &line)
void drawPath(const QPainterPath &path)
void drawPolygon(const QPoint *points, int pointCount, Qt::FillRule fillRule)
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
QPainterPath::Element elementAt(int index) const const
int elementCount() const const
bool intersects(const QPainterPath &p) const const
void setColor(const QColor &color)
qreal bottom() const const
QPointF center() const const
qreal height() const const
bool isEmpty() const const
qreal right() const const
void translate(const QPointF &offset)
qreal width() const const
QTextStream & center(QTextStream &stream)
QTextStream & left(QTextStream &stream)
QTextStream & right(QTextStream &stream)
QRect contentsRect() const const