16 #include "KDChartPieDiagram_p.h"
19 #include "KDChartPainterSaver_p.h"
21 #include "KDChartPolarCoordinatePlane_p.h"
24 #include <KDABLibFakes>
28 PieDiagram::Private::Private()
33 PieDiagram::Private::~Private()
49 void PieDiagram::init()
63 d->labelDecorations = decorations;
68 return d->labelDecorations;
73 d->isCollisionAvoidanceEnabled = enabled;
78 return d->isCollisionAvoidanceEnabled;
84 return QPair<QPointF, QPointF>(QPointF(0, 0), QPointF(0, 0));
88 QPointF bottomLeft(QPointF(0, 0));
93 qreal maxExplode = 0.0;
94 for (
int j = 0; j < colCount; ++j) {
98 topRight = QPointF(1.0 + maxExplode, 1.0 + maxExplode);
100 topRight = QPointF(1.0, 1.0);
102 return QPair<QPointF, QPointF>(bottomLeft, topRight);
107 QPainter painter(viewport());
133 void PieDiagram::calcSliceAngles()
137 const qreal sectorsPerValue = 360.0 / sum;
142 d->startAngles.resize(colCount);
143 d->angleLens.resize(colCount);
145 bool atLeastOneValue =
false;
146 for (
int iColumn = 0; iColumn < colCount; ++iColumn) {
148 const qreal cellValue = qAbs(model()->data(model()->index(0, iColumn, rootIndex()))
151 atLeastOneValue = atLeastOneValue || isOk;
153 d->startAngles[iColumn] = currentValue;
154 d->angleLens[iColumn] = cellValue * sectorsPerValue;
156 currentValue =
d->startAngles[iColumn] +
d->angleLens[iColumn];
160 if (!atLeastOneValue) {
161 d->startAngles.clear();
162 d->angleLens.clear();
166 void PieDiagram::calcPieSize(
const QRectF &contentsRect)
168 d->size = qMin(contentsRect.width(), contentsRect.height());
171 qreal maxExplode = 0.0;
173 for (
int j = 0; j < colCount; ++j) {
175 maxExplode = qMax(maxExplode, columnAttrs.explodeFactor());
177 d->size /= (1.0 + 1.0 * maxExplode);
185 QRectF PieDiagram::twoDPieRect(
const QRectF &contentsRect,
const ThreeDPieAttributes &threeDAttrs)
const
189 qreal x = (contentsRect.width() -
d->size) / 2.0;
190 qreal y = (contentsRect.height() -
d->size) / 2.0;
191 pieRect = QRectF(contentsRect.left() + x, contentsRect.top() + y,
d->size,
d->size);
194 qreal sizeFor3DEffect = 0.0;
196 qreal x = (contentsRect.width() -
d->size) / 2.0;
197 qreal height =
d->size;
200 if (threeDAttrs.
depth() >= 0.0) {
202 sizeFor3DEffect = threeDAttrs.
depth();
203 height =
d->size - sizeFor3DEffect;
206 sizeFor3DEffect = -threeDAttrs.
depth() / 100.0 * height;
207 height =
d->size - sizeFor3DEffect;
209 qreal y = (contentsRect.height() - height - sizeFor3DEffect) / 2.0;
211 pieRect = QRectF(contentsRect.left() + x, contentsRect.top() + y,
d->size, height);
216 void PieDiagram::placeLabels(
PaintContext *paintContext)
228 d->reverseMapper.clear();
231 if (
d->startAngles.isEmpty()) {
239 bool tryAgain =
true;
243 QRectF pieRect = twoDPieRect(paintContext->
rectangle(), threeDAttrs);
244 d->forgetAlreadyPaintedDataValues();
245 d->labelPaintCache.clear();
247 for (
int slice = 0; slice < colCount; slice++) {
248 if (
d->angleLens[slice] != 0.0) {
249 const QRectF explodedPieRect = explodedDrawPosition(pieRect, slice);
250 addSliceLabel(&
d->labelPaintCache, explodedPieRect, slice);
254 QRectF textBoundingRect;
255 d->paintDataValueTextsAndMarkers(paintContext,
d->labelPaintCache,
false,
true,
257 if (
d->isCollisionAvoidanceEnabled) {
258 shuffleLabels(&textBoundingRect);
261 if (!textBoundingRect.isEmpty() &&
d->size > 0.0) {
262 const QRectF &clipRect = paintContext->
rectangle();
264 qreal right = qMax(qreal(0.0), textBoundingRect.right() - clipRect.right());
265 qreal left = qMax(qreal(0.0), clipRect.left() - textBoundingRect.left());
267 qreal top = qMax(qreal(0.0), clipRect.top() - textBoundingRect.top());
268 qreal bottom = qMax(qreal(0.0), textBoundingRect.bottom() - clipRect.bottom());
269 qreal maxOverhang = qMax(qMax(right, left), qMax(top, bottom));
271 if (maxOverhang > 0.0) {
274 d->size -= qMin(
d->size, maxOverhang * ( qreal )2.0);
294 void PieDiagram::shuffleLabels(QRectF *textBoundingRect)
304 LabelPaintCache &lpc =
d->labelPaintCache;
305 const int n = lpc.paintReplay.size();
306 bool modified =
false;
307 qreal direction = 5.0;
308 QVector<qreal> offsets;
309 offsets.fill(0.0, n);
311 for (
bool lastRoundModified =
true; lastRoundModified;) {
312 lastRoundModified =
false;
314 for (
int i = 0; i < n; i++) {
315 const int neighborsToCheck = qMax(10, lpc.paintReplay.size() - 1);
316 const int minComp =
wraparound(i - neighborsToCheck / 2, n);
317 const int maxComp =
wraparound(i + (neighborsToCheck + 1) / 2, n);
319 QPainterPath &path = lpc.paintReplay[i].labelArea;
321 for (
int j = minComp; j != maxComp; j =
wraparound(j + 1, n)) {
325 QPainterPath &otherPath = lpc.paintReplay[j].labelArea;
327 while ((offsets[i] + direction > 0) && otherPath.intersects(path)) {
329 qDebug() <<
"collision involving" << j <<
"and" << i <<
" -- n =" << n;
331 ta.
setPen(QPen(Qt::white));
332 lpc.paintReplay[i].attrs.setTextAttributes(ta);
334 uint slice = lpc.paintReplay[i].index.column();
335 qreal angle = DEGTORAD(
d->startAngles[slice] +
d->angleLens[slice] / 2.0);
336 qreal dx = cos(angle) * direction;
337 qreal dy = -sin(angle) * direction;
338 offsets[i] += direction;
339 path.translate(dx, dy);
340 lastRoundModified =
true;
345 modified = modified || lastRoundModified;
349 for (
int i = 0; i < lpc.paintReplay.size(); i++) {
350 *textBoundingRect |= lpc.paintReplay[i].labelArea.boundingRect();
358 for (
int i = 0; i < pp.elementCount(); i++) {
359 const QPainterPath::Element &el = pp.elementAt(i);
360 Q_ASSERT(el.type == QPainterPath::MoveToElement || el.type == QPainterPath::LineToElement);
369 const qreal dotProduct = l1.dx() * l2.dx() + l1.dy() * l2.dy();
370 return qAbs(dotProduct / (l1.length() * l2.length()));
375 Q_ASSERT(label.elementCount() == 5);
379 const qreal pieRadius = QLineF(center, start).length();
383 for (
int i = 0; i < 4; i++) {
384 if (QLineF(label.elementAt(i), center).length() < pieRadius) {
390 QPointF closeCorners[3];
392 QPointF closest = QPointF(1000000, 1000000);
393 int closestIndex = 0;
394 for (
int i = 0; i < 4; i++) {
395 QPointF p = label.elementAt(i);
396 if (QLineF(p, center).length() < QLineF(closest, center).length()) {
402 closeCorners[0] = label.elementAt(
wraparound(closestIndex - 1, 4));
403 closeCorners[1] = closest;
404 closeCorners[2] = label.elementAt(
wraparound(closestIndex + 1, 4));
407 QLineF edge1 = QLineF(closeCorners[0], closeCorners[1]);
408 QLineF edge2 = QLineF(closeCorners[1], closeCorners[2]);
409 QLineF connection1 = QLineF((closeCorners[0] + closeCorners[1]) / 2.0, center);
410 QLineF connection2 = QLineF((closeCorners[1] + closeCorners[2]) / 2.0, center);
420 ret.setP2((start + center) / 2.0);
423 qreal p1Radius = QLineF(ret.p1(), center).length();
424 ret.setLength(p1Radius - pieRadius);
429 void PieDiagram::paintInternal(
PaintContext *paintContext)
446 QRectF pieRect = twoDPieRect(paintContext->
rectangle(), threeDAttrs);
447 const int backmostSlice = findSliceAt(90, colCount);
448 const int frontmostSlice = findSliceAt(270, colCount);
449 int currentLeftSlice = backmostSlice;
450 int currentRightSlice = backmostSlice;
452 drawSlice(paintContext->
painter(), pieRect, backmostSlice);
454 if (backmostSlice == frontmostSlice) {
455 const int rightmostSlice = findSliceAt(0, colCount);
456 const int leftmostSlice = findSliceAt(180, colCount);
458 if (backmostSlice == leftmostSlice) {
459 currentLeftSlice = findLeftSlice(currentLeftSlice, colCount);
461 if (backmostSlice == rightmostSlice) {
462 currentRightSlice = findRightSlice(currentRightSlice, colCount);
466 while (currentLeftSlice != frontmostSlice) {
467 if (currentLeftSlice != backmostSlice) {
468 drawSlice(paintContext->
painter(), pieRect, currentLeftSlice);
470 currentLeftSlice = findLeftSlice(currentLeftSlice, colCount);
473 while (currentRightSlice != frontmostSlice) {
474 if (currentRightSlice != backmostSlice) {
475 drawSlice(paintContext->
painter(), pieRect, currentRightSlice);
477 currentRightSlice = findRightSlice(currentRightSlice, colCount);
482 drawSlice(paintContext->
painter(), pieRect, frontmostSlice);
485 d->paintDataValueTextsAndMarkers(paintContext,
d->labelPaintCache,
false,
false);
487 d->forgetAlreadyPaintedDataValues();
489 const QPointF center = paintContext->
rectangle().center();
490 const PainterSaver painterSaver(paintContext->
painter());
491 paintContext->
painter()->setBrush(Qt::NoBrush);
492 for (
const LabelPaintInfo &pi : qAsConst(
d->labelPaintCache.paintReplay)) {
494 if (pi.labelArea.elementCount() != 5) {
498 paintContext->
painter()->setPen(
pen(pi.index));
503 paintContext->
painter()->drawPath(pi.labelArea);
505 d->reverseMapper.addPolygon(pi.index.row(), pi.index.column(),
508 d->labelPaintCache.clear();
509 d->startAngles.clear();
510 d->angleLens.clear();
513 #if defined(Q_OS_WIN)
514 #define trunc(x) (( int )(x))
517 QRectF PieDiagram::explodedDrawPosition(
const QRectF &drawPosition, uint slice)
const
519 const QModelIndex index(model()->index(0, slice, rootIndex()));
522 QRectF adjustedDrawPosition = drawPosition;
523 if (attrs.explode()) {
524 qreal startAngle =
d->startAngles[slice];
525 qreal angleLen =
d->angleLens[slice];
526 qreal explodeAngle = (DEGTORAD(startAngle + angleLen / 2.0));
527 qreal explodeDistance = attrs.explodeFactor() *
d->size / 2.0;
529 adjustedDrawPosition.translate(explodeDistance * cos(explodeAngle),
530 explodeDistance * -sin(explodeAngle));
532 return adjustedDrawPosition;
543 void PieDiagram::drawSlice(QPainter *painter,
const QRectF &drawPosition, uint slice)
546 if (
d->angleLens[slice] == 0.0) {
549 const QRectF adjustedDrawPosition = explodedDrawPosition(drawPosition, slice);
550 draw3DEffect(painter, adjustedDrawPosition, slice);
551 drawSliceSurface(painter, adjustedDrawPosition, slice);
561 void PieDiagram::drawSliceSurface(QPainter *painter,
const QRectF &drawPosition, uint slice)
564 const qreal angleLen =
d->angleLens[slice];
565 const qreal startAngle =
d->startAngles[slice];
566 const QModelIndex index(model()->index(0, slice, rootIndex()));
571 painter->setRenderHint(QPainter::Antialiasing);
572 QBrush br =
brush(index);
576 painter->setBrush(br);
578 QPen
pen = this->
pen(index);
580 pen.setColor(Qt::black);
582 painter->setPen(
pen);
584 if (angleLen == 360) {
586 painter->drawEllipse(drawPosition);
589 QPolygonF poly(drawPosition);
590 d->reverseMapper.addPolygon(index.row(), index.column(), poly);
594 const int arcPoints =
static_cast<int>(trunc(angleLen /
granularity()));
595 QPolygonF poly(arcPoints + 2);
598 bool perfectMatch =
false;
600 while (degree <= angleLen) {
601 poly[iPoint] = pointOnEllipse(drawPosition, startAngle + degree);
603 perfectMatch = (degree == angleLen);
609 poly[iPoint] = pointOnEllipse(drawPosition, startAngle + angleLen);
612 poly.append(drawPosition.center());
614 poly[iPoint] = drawPosition.center();
618 d->reverseMapper.addPolygon(index.row(), index.column(), poly);
620 painter->drawPolygon(poly);
625 void PieDiagram::addSliceLabel(LabelPaintCache *lpc,
const QRectF &drawPosition, uint slice)
627 const qreal angleLen =
d->angleLens[slice];
628 const qreal startAngle =
d->startAngles[slice];
629 const QModelIndex index(model()->index(0, slice, rootIndex()));
636 const QPointF south = drawPosition.center();
637 const QPointF southEast = south;
638 const QPointF southWest = south;
639 const QPointF north = pointOnEllipse(drawPosition, startAngle + angleLen / 2.0);
641 const QPointF northEast = pointOnEllipse(drawPosition, startAngle);
642 const QPointF northWest = pointOnEllipse(drawPosition, startAngle + angleLen);
643 QPointF center = (south + north) / 2.0;
644 const QPointF east = (south + northEast) / 2.0;
645 const QPointF west = (south + northWest) / 2.0;
647 PositionPoints points(center, northWest, north, northEast, east, southEast, south, southWest, west);
648 qreal topAngle = startAngle - 90;
649 if (topAngle < 0.0) {
660 qreal favoriteTextAngle = 0.0;
662 favoriteTextAngle = -(startAngle + angleLen / 2) + 90.0;
663 while (favoriteTextAngle <= 0.0) {
664 favoriteTextAngle += 360.0;
667 if (favoriteTextAngle > 90.0 && favoriteTextAngle < 270.0) {
668 favoriteTextAngle = favoriteTextAngle - 180.0;
671 if (favoriteTextAngle <= 0.0) {
672 favoriteTextAngle += 360.0;
677 angleLen * sum / 360, favoriteTextAngle);
680 static bool doSpansOverlap(qreal s1Start, qreal s1End, qreal s2Start, qreal s2End)
682 if (s1Start < s2Start) {
683 return s1End >= s2Start;
685 return s1Start <= s2End;
689 static bool doArcsOverlap(qreal a1Start, qreal a1End, qreal a2Start, qreal a2End)
691 Q_ASSERT(a1Start >= 0 && a1Start <= 360 && a1End >= 0 && a1End <= 360 && a2Start >= 0 && a2Start <= 360 && a2End >= 0 && a2End <= 360);
693 if (a1End < a1Start) {
696 if (a2End < a2Start) {
703 if (a1Start > a2Start) {
704 return doSpansOverlap(a1Start - 360.0, a1End - 360.0, a2Start, a2End);
706 return doSpansOverlap(a1Start + 360.0, a1End + 360.0, a2Start, a2End);
717 void PieDiagram::draw3DEffect(QPainter *painter,
const QRectF &drawPosition, uint slice)
719 const QModelIndex index(model()->index(0, slice, rootIndex()));
733 const QBrush
brush = this->
brush(model()->index(0, slice, rootIndex()));
735 painter->setBrush(QBrush(
brush.color().darker()));
737 painter->setBrush(
brush);
740 qreal startAngle =
d->startAngles[slice];
741 qreal endAngle = startAngle +
d->angleLens[slice];
743 while (startAngle >= 360)
745 while (endAngle >= 360)
747 Q_ASSERT(startAngle >= 0 && startAngle <= 360);
748 Q_ASSERT(endAngle >= 0 && endAngle <= 360);
752 const int depth = threeDAttrs.
depth() >= 0.0 ? threeDAttrs.
depth() : -threeDAttrs.
depth() / 100.0 * drawPosition.height();
754 if (startAngle == endAngle || startAngle == endAngle - 360) {
755 draw3dOuterRim(painter, drawPosition, depth, 180, 360);
758 draw3dOuterRim(painter, drawPosition, depth, startAngle, endAngle);
761 if (startAngle >= 270 || startAngle <= 90) {
762 draw3dCutSurface(painter, drawPosition, depth, startAngle);
764 if (endAngle >= 90 && endAngle <= 270) {
765 draw3dCutSurface(painter, drawPosition, depth, endAngle);
779 void PieDiagram::draw3dCutSurface(QPainter *painter,
785 const QPointF center = rect.center();
786 const QPointF circlePoint = pointOnEllipse(rect, angle);
788 poly[1] = circlePoint;
789 poly[2] = QPointF(circlePoint.x(), circlePoint.y() + threeDHeight);
790 poly[3] = QPointF(center.x(), center.y() + threeDHeight);
792 painter->drawPolygon(poly);
804 void PieDiagram::draw3dOuterRim(QPainter *painter,
811 if (endAngle < startAngle) {
814 startAngle = qMax(startAngle, qreal(180.0));
815 endAngle = qMin(endAngle, qreal(360.0));
817 int numHalfPoints = trunc((endAngle - startAngle) /
granularity()) + 1;
818 if (numHalfPoints < 2) {
822 QPolygonF poly(numHalfPoints);
824 qreal degree = endAngle;
826 bool perfectMatch =
false;
827 while (degree >= startAngle) {
828 poly[numHalfPoints - iPoint - 1] = pointOnEllipse(rect, degree);
830 perfectMatch = (degree == startAngle);
836 poly.prepend(pointOnEllipse(rect, startAngle));
840 poly.resize(numHalfPoints * 2);
844 for (
int i = numHalfPoints - 1; i >= 0; --i) {
845 QPointF pointOnFirstArc(poly[i]);
846 pointOnFirstArc.setY(pointOnFirstArc.y() + threeDHeight);
847 poly[numHalfPoints * 2 - i - 1] = pointOnFirstArc;
851 painter->drawPolygon(poly);
860 uint PieDiagram::findSliceAt(qreal angle,
int colCount)
862 for (
int i = 0; i < colCount; ++i) {
863 qreal endseg =
d->startAngles[i] +
d->angleLens[i];
864 if (
d->startAngles[i] <= angle && endseg >= angle) {
872 return findSliceAt(angle + 360, colCount);
883 uint PieDiagram::findLeftSlice(uint slice,
int colCount)
902 uint PieDiagram::findRightSlice(uint slice,
int colCount)
904 int rightSlice = slice + 1;
905 if (rightSlice == colCount) {
915 QPointF PieDiagram::pointOnEllipse(
const QRectF &boundingBox, qreal angle)
917 qreal angleRad = DEGTORAD(angle);
918 qreal cosAngle = cos(angleRad);
919 qreal sinAngle = -sin(angleRad);
920 qreal posX = cosAngle * boundingBox.width() / 2.0;
921 qreal posY = sinAngle * boundingBox.height() / 2.0;
922 return QPointF(posX + boundingBox.center().x(),
923 posY + boundingBox.center().y());
934 for (
int j = 0; j < colCount; ++j) {
935 total += qAbs(model()->data(model()->index(0, j, rootIndex())).toReal());
943 return model() ? model()->columnCount(rootIndex()) : 0.0;
static bool doArcsOverlap(qreal a1Start, qreal a1End, qreal a2Start, qreal a2End)
static QLineF labelAttachmentLine(const QPointF ¢er, const QPointF &start, const QPainterPath &label)
static QPolygonF polygonFromPainterPath(const QPainterPath &pp)
static qreal normProjection(const QLineF &l1, const QLineF &l2)
static int wraparound(int i, int size)
static bool doSpansOverlap(qreal s1Start, qreal s1End, qreal s2Start, qreal s2End)
virtual bool checkInvariants(bool justReturnTheStatus=false) const
Base class for any diagram type.
PieAttributes pieAttributes() const
qreal granularity() const
bool autoRotateLabels() const
ThreeDPieAttributes threeDPieAttributes() const
const PolarCoordinatePlane * polarCoordinatePlane() const
virtual QBrush threeDBrush(const QBrush &brush, const QRectF &rect) const
Stores information about painting diagrams.
void setPainter(QPainter *painter)
void setRectangle(const QRectF &rect)
const QRectF rectangle() const
QPainter * painter() const
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
void resizeEvent(QResizeEvent *) override
PieDiagram(QWidget *parent=nullptr, PolarCoordinatePlane *plane=nullptr)
void paint(PaintContext *paintContext) override
qreal numberOfGridRings() const override
virtual PieDiagram * clone() const
void paintEvent(QPaintEvent *) override
void resize(const QSizeF &area) override
@ LineFromSliceDecoration
A line is drawn from the pie slice to its label.
@ FrameDecoration
A rectangular frame is painted around the label text.
qreal numberOfValuesPerDataset() const override
void setLabelCollisionAvoidanceEnabled(bool enabled)
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
qreal startPosition() const
Stores the absolute target points of a Position.
static const Position & Center
A set of text attributes.
void setPen(const QPen &pen)
A set of 3D pie attributes.
bool useShadowColors() const