KD Chart API Documentation 3.0
Loading...
Searching...
No Matches
KDChartRingDiagram.cpp
Go to the documentation of this file.
1/****************************************************************************
2**
3** This file is part of the KD Chart library.
4**
5** SPDX-FileCopyrightText: 2001 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6**
7** SPDX-License-Identifier: MIT
8**
9****************************************************************************/
10
11#include "KDChartRingDiagram.h"
12#include "KDChartRingDiagram_p.h"
13
16#include "KDChartPaintContext.h"
17#include "KDChartPainterSaver_p.h"
19#include "KDChartPolarCoordinatePlane_p.h"
21
22#include <QPainter>
23
24#include <KDABLibFakes>
25
26using namespace KDChart;
27
28RingDiagram::Private::Private()
29{
30}
31
32RingDiagram::Private::~Private()
33{
34}
35
36#define d d_func()
37
39 : AbstractPieDiagram(new Private(), parent, plane)
40{
41 init();
42}
43
47
48void RingDiagram::init()
49{
50}
51
56{
57 return new RingDiagram(new Private(*d));
58}
59
60bool RingDiagram::compare(const RingDiagram *other) const
61{
62 if (other == this)
63 return true;
64 if (!other) {
65 return false;
66 }
67 return // compare the base class
68 (static_cast<const AbstractPieDiagram *>(this)->compare(other)) &&
69 // compare own properties
70 (relativeThickness() == other->relativeThickness()) && (expandWhenExploded() == other->expandWhenExploded());
71}
72
73void RingDiagram::setRelativeThickness(bool relativeThickness)
74{
75 d->relativeThickness = relativeThickness;
76}
77
79{
80 return d->relativeThickness;
81}
82
84{
85 d->expandWhenExploded = expand;
86}
87
89{
90 return d->expandWhenExploded;
91}
92
94{
95 if (!checkInvariants(true))
96 return QPair<QPointF, QPointF>(QPointF(0, 0), QPointF(0, 0));
97
99
100 QPointF bottomLeft(0, 0);
101 QPointF topRight;
102 // If we explode, we need extra space for the pie slice that has the largest explosion distance.
103 if (attrs.explode()) {
104 const int rCount = rowCount();
105 const int colCount = columnCount();
106 qreal maxExplode = 0.0;
107 for (int i = 0; i < rCount; ++i) {
109 for (int j = 0; j < colCount; ++j) {
110 const PieAttributes columnAttrs(pieAttributes(model()->index(i, j, rootIndex()))); // checked
112 }
114
115 // FIXME: What if explode factor of inner ring is > 1.0 ?
116 if (!d->expandWhenExploded) {
117 break;
118 }
119 }
120 // explode factor is relative to width (outer r - inner r) of one ring
121 maxExplode /= (rCount + 1);
122 topRight = QPointF(1.0 + maxExplode, 1.0 + maxExplode);
123 } else {
124 topRight = QPointF(1.0, 1.0);
125 }
126 return QPair<QPointF, QPointF>(bottomLeft, topRight);
127}
128
130{
131 QPainter painter(viewport());
133 ctx.setPainter(&painter);
134 ctx.setRectangle(QRectF(0, 0, width(), height()));
135 paint(&ctx);
136}
137
141
143{
144 // note: Not having any data model assigned is no bug
145 // but we can not draw a diagram then either.
146 if (!checkInvariants(true))
147 return;
148
149 d->reverseMapper.clear();
150
152
153 const int rCount = rowCount();
154 const int colCount = columnCount();
155
156 // QRectF contentsRect = PolarCoordinatePlane::Private::contentsRect( polarCoordinatePlane() );
157 QRectF contentsRect = ctx->rectangle();
158 if (contentsRect.isEmpty())
159 return;
160
163
164 // compute position
165 d->size = qMin(contentsRect.width(), contentsRect.height()); // initial size
166
167 // if the slices explode, we need to give them additional space =>
168 // make the basic size smaller
169 qreal totalOffset = 0.0;
170 for (int i = 0; i < rCount; ++i) {
172 for (int j = 0; j < colCount; ++j) {
173 const PieAttributes cellAttrs(pieAttributes(model()->index(i, j, rootIndex()))); // checked
174 // qDebug() << cellAttrs.explodeFactor();
175 const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0;
176 maxOffsetInThisRow = qMax(maxOffsetInThisRow, cellAttrs.gapFactor(false) + explode);
177 }
178 if (!d->expandWhenExploded) {
180 }
182 // FIXME: What if explode factor of inner ring is > 1.0 ?
183 // if ( !d->expandWhenExploded )
184 // break;
185 }
186
187 // explode factor is relative to width (outer r - inner r) of one ring
188 if (rCount > 0)
189 totalOffset /= (rCount + 1);
190 d->size /= (1.0 + totalOffset);
191
192 qreal x = (contentsRect.width() == d->size) ? 0.0 : ((contentsRect.width() - d->size) / 2.0);
193 qreal y = (contentsRect.height() == d->size) ? 0.0 : ((contentsRect.height() - d->size) / 2.0);
194 d->position = QRectF(x, y, d->size, d->size);
195 d->position.translate(contentsRect.left(), contentsRect.top());
196
198
200
201 d->forgetAlreadyPaintedDataValues();
202 for (int iRow = 0; iRow < rCount; ++iRow) {
203 const qreal sum = valueTotals(iRow);
204 if (sum == 0.0) // nothing to draw
205 continue;
206 qreal currentValue = plane ? plane->startPosition() : 0.0;
207 const qreal sectorsPerValue = 360.0 / sum;
208
209 for (int iColumn = 0; iColumn < colCount; ++iColumn) {
210 // is there anything at all at this column?
211 bool bOK;
212 const qreal cellValue = qAbs(model()->data(model()->index(iRow, iColumn, rootIndex())) // checked
213 .toReal(&bOK));
214
215 if (bOK) {
216 d->startAngles[iRow][iColumn] = currentValue;
217 d->angleLens[iRow][iColumn] = cellValue * sectorsPerValue;
218 } else { // mark as non-existent
219 d->angleLens[iRow][iColumn] = 0.0;
220 if (iColumn > 0.0) {
221 d->startAngles[iRow][iColumn] = d->startAngles[iRow][iColumn - 1];
222 } else {
223 d->startAngles[iRow][iColumn] = currentValue;
224 }
225 }
226
227 currentValue = d->startAngles[iRow][iColumn] + d->angleLens[iRow][iColumn];
228
229 drawOneSlice(ctx->painter(), iRow, iColumn, granularity());
230 }
231 }
232}
233
234#if defined(Q_WS_WIN)
235#define trunc(x) (( int )(x))
236#endif
237
243void RingDiagram::drawOneSlice(QPainter *painter, uint dataset, uint slice, qreal granularity)
244{
245 // Is there anything to draw at all?
246 const qreal angleLen = d->angleLens[dataset][slice];
247 if (angleLen) {
248 drawPieSurface(painter, dataset, slice, granularity);
249 }
250}
251
253{
254}
255
263void RingDiagram::drawPieSurface(QPainter *painter, uint dataset, uint slice, qreal granularity)
264{
265 // Is there anything to draw at all?
266 qreal angleLen = d->angleLens[dataset][slice];
267 if (angleLen) {
268 qreal startAngle = d->startAngles[dataset][slice];
269
270 QModelIndex index(model()->index(dataset, slice, rootIndex())); // checked
271 const PieAttributes attrs(pieAttributes(index));
273
274 const int rCount = rowCount();
275 const int colCount = columnCount();
276
277 int iPoint = 0;
278
279 QRectF drawPosition = d->position;
280
282
283 QBrush br = brush(index);
284 if (threeDAttrs.isEnabled()) {
285 br = threeDAttrs.threeDBrush(br, drawPosition);
286 }
287 painter->setBrush(br);
288
289 painter->setPen(pen(index));
290
291 if (angleLen == 360) {
292 // full circle, avoid nasty line in the middle
293 // FIXME: Draw a complete ring here
294 // painter->drawEllipse( drawPosition );
295 } else {
296 bool perfectMatch = false;
297
298 qreal circularGap = 0.0;
299
300 if (attrs.gapFactor(true) > 0.0) {
301 // FIXME: Measure in degrees!
302 circularGap = attrs.gapFactor(true);
303 }
304
306
307 qreal degree = 0;
308
309 qreal actualStartAngle = startAngle + circularGap;
311
314
315 qreal totalRadialGap = 0.0;
316 qreal maxRadialGap = 0.0;
317 for (uint i = rCount - 1; i > dataset; --i) {
320 for (int j = 0; j < colCount; ++j) {
321 const PieAttributes cellAttrs(pieAttributes(model()->index(i, j, rootIndex()))); // checked
322 if (d->expandWhenExploded) {
324 }
325
326 // Don't use a gap for the very inner circle
327 if (cellAttrs.explode() && d->expandWhenExploded) {
329 }
330 }
333
334 // FIXME: What if explode factor of inner ring is > 1.0 ?
335 // if ( !d->expandWhenExploded )
336 // break;
337 }
338 totalRadialGap = maxRadialGap + attrs.gapFactor(false);
339 totalRadialExplode = attrs.explode() ? maxRadialExplode + attrs.explodeFactor() : maxRadialExplode;
340
341 while (degree <= actualAngleLen) {
342 const QPointF p = pointOnEllipse(drawPosition, dataset, slice, false, actualStartAngle + degree,
344 poly.append(p);
346 iPoint++;
347 }
348 if (!perfectMatch) {
349 poly.append(pointOnEllipse(drawPosition, dataset, slice, false, actualStartAngle + actualAngleLen,
351 iPoint++;
352 }
353
354 // The center point of the inner brink
355 const QPointF innerCenterPoint(poly[int(iPoint / 2)]);
356
357 actualStartAngle = startAngle + circularGap;
359
361
362 const int lastInnerBrinkPoint = iPoint;
363 while (degree >= 0) {
364 poly.append(pointOnEllipse(drawPosition, dataset, slice, true, actualStartAngle + degree,
366 perfectMatch = (degree == 0);
368 iPoint++;
369 }
370 // if necessary add one more point to fill the last small gap
371 if (!perfectMatch) {
372 poly.append(pointOnEllipse(drawPosition, dataset, slice, true, actualStartAngle,
374 iPoint++;
375 }
376
377 // The center point of the outer brink
379 // qDebug() << poly;
380 // find the value and paint it
381 // fix value position
382 const qreal sum = valueTotals(dataset);
383 painter->drawPolygon(poly);
384
385 d->reverseMapper.addPolygon(index.row(), index.column(), poly);
386
387 const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0;
388
389 const PainterSaver ps(painter);
391 if (!ta.hasRotation() && autoRotateLabels()) {
392 const QPointF &p1 = poly.last();
393 const QPointF &p2 = poly[lastInnerBrinkPoint];
394 const QLineF line(p1, p2);
395 // TODO: do the label rotation like in PieDiagram
396 const qreal angle = line.dx() == 0 ? 0.0 : atan(line.dy() / line.dx());
397 painter->translate(centerPoint);
398 painter->rotate(angle / 2.0 / 3.141592653589793 * 360.0);
399 painter->translate(-centerPoint);
400 }
401
402 paintDataValueText(painter, index, centerPoint, angleLen * sum / 360);
403 }
404 }
405}
406
411QPointF RingDiagram::pointOnEllipse(const QRectF &rect, int dataset, int slice, bool outer, qreal angle,
412 qreal totalGapFactor, qreal totalExplodeFactor)
413{
414 qreal angleLen = d->angleLens[dataset][slice];
415 qreal startAngle = d->startAngles[dataset][slice];
416
417 const int rCount = rowCount() * 2;
418
419 qreal level = outer ? (rCount - dataset - 1) + 2 : (rCount - dataset - 1) + 1;
420
421 const qreal offsetX = rCount > 0 ? level * rect.width() / ((rCount + 1) * 2) : 0.0;
422 const qreal offsetY = rCount > 0 ? level * rect.height() / ((rCount + 1) * 2) : 0.0;
423 const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ((rCount + 1) * 2) : 0.0;
424 const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ((rCount + 1) * 2) : 0.0;
425 const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ((rCount + 1) * 2) : 0.0;
426 const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ((rCount + 1) * 2) : 0.0;
427
431 qreal explodeAngleCenterRad = DEGTORAD(startAngle + angleLen / 2.0);
434 return QPointF((offsetX + gapOffsetX) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(),
436}
437
438/*virtual*/
440{
441 const int rCount = rowCount();
442 const int colCount = columnCount();
443 qreal total = 0.0;
444 for (int i = 0; i < rCount; ++i) {
445 for (int j = 0; j < colCount; ++j) {
446 total += qAbs(model()->data(model()->index(i, j, rootIndex())).toReal()); // checked
447 }
448 }
449 return total;
450}
451
452qreal RingDiagram::valueTotals(int dataset) const
453{
455 const int colCount = columnCount();
456 qreal total = 0.0;
457 for (int j = 0; j < colCount; ++j) {
458 total += qAbs(model()->data(model()->index(dataset, j, rootIndex())).toReal()); // checked
459 }
460 return total;
461}
462
463/*virtual*/
465{
466 return model() ? model()->columnCount(rootIndex()) : 0.0;
467}
468
470{
471 return model() ? model()->rowCount(rootIndex()) : 0.0;
472}
473
474/*virtual*/
476{
477 return 1;
478}
Declaring the class KDChart::DataValueAttributes.
virtual bool checkInvariants(bool justReturnTheStatus=false) const
DataValueAttributes dataValueAttributes() const
void paintDataValueText(QPainter *painter, const QModelIndex &index, const QPointF &pos, qreal value)
Base class for any diagram type.
ThreeDPieAttributes threeDPieAttributes() const
const PolarCoordinatePlane * polarCoordinatePlane() const
Stores information about painting diagrams.
void setPainter(QPainter *painter)
A set of attributes controlling the appearance of pie charts.
RingDiagram defines a common ring diagram.
void setRelativeThickness(bool relativeThickness)
void paint(PaintContext *paintContext) override
void resize(const QSizeF &area) override
qreal numberOfGridRings() const override
qreal valueTotals() const override
void paintEvent(QPaintEvent *) override
qreal numberOfDatasets() const override
bool compare(const RingDiagram *other) const
qreal numberOfValuesPerDataset() const override
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
virtual void setExpandWhenExploded(bool expand)
virtual bool expandWhenExploded() const
virtual RingDiagram * clone() const
RingDiagram(QWidget *parent=nullptr, PolarCoordinatePlane *plane=nullptr)
void resizeEvent(QResizeEvent *) override
A set of text attributes.
virtual int columnCount(const QModelIndex &parent) const const=0
virtual int rowCount(const QModelIndex &parent) const const=0
QAbstractItemModel * model() const const
QModelIndex rootIndex() const const
QWidget * viewport() const const
T qobject_cast(QObject *object)
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
void rotate(qreal angle)
void setBrush(const QBrush &brush)
void setPen(const QColor &color)
void setRenderHint(QPainter::RenderHint hint, bool on)
void translate(const QPointF &offset)
int height() const const
bool isEmpty() const const
int left() const const
int top() const const
int width() const const
int size() const const
QRect contentsRect() const const

© 2001 Klarälvdalens Datakonsult AB (KDAB)
"The Qt, C++ and OpenGL Experts"
https://www.kdab.com/
https://www.kdab.com/development-resources/qt-tools/kd-chart/
Generated on Fri Feb 23 2024 00:02:59 for KD Chart API Documentation by doxygen 1.9.8