KD Chart API Documentation  3.1
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 
14 #include "KDChartAttributesModel.h"
16 #include "KDChartPaintContext.h"
17 #include "KDChartPainterSaver_p.h"
18 #include "KDChartPieAttributes.h"
19 #include "KDChartPolarCoordinatePlane_p.h"
21 
22 #include <QPainter>
23 
24 #include <KDABLibFakes>
25 
26 using namespace KDChart;
27 
28 RingDiagram::Private::Private()
29 {
30 }
31 
32 RingDiagram::Private::~Private()
33 {
34 }
35 
36 #define d d_func()
37 
39  : AbstractPieDiagram(new Private(), parent, plane)
40 {
41  init();
42 }
43 
45 {
46 }
47 
48 void RingDiagram::init()
49 {
50 }
51 
56 {
57  return new RingDiagram(new Private(*d));
58 }
59 
60 bool 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
71 }
72 
73 void 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 
93 const QPair<QPointF, QPointF> RingDiagram::calculateDataBoundaries() const
94 {
95  if (!checkInvariants(true))
96  return QPair<QPointF, QPointF>(QPointF(0, 0), QPointF(0, 0));
97 
98  const PieAttributes attrs(pieAttributes());
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) {
108  qreal maxExplodeInThisRow = 0.0;
109  for (int j = 0; j < colCount; ++j) {
110  const PieAttributes columnAttrs(pieAttributes(model()->index(i, j, rootIndex()))); // checked
111  maxExplodeInThisRow = qMax(maxExplodeInThisRow, columnAttrs.explodeFactor());
112  }
113  maxExplode += maxExplodeInThisRow;
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 
129 void RingDiagram::paintEvent(QPaintEvent *)
130 {
131  QPainter painter(viewport());
132  PaintContext ctx;
133  ctx.setPainter(&painter);
134  ctx.setRectangle(QRectF(0, 0, width(), height()));
135  paint(&ctx);
136 }
137 
138 void RingDiagram::resizeEvent(QResizeEvent *)
139 {
140 }
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 
151  const PieAttributes attrs(pieAttributes());
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 
161  d->startAngles = QVector<QVector<qreal>>(rCount, QVector<qreal>(colCount));
162  d->angleLens = QVector<QVector<qreal>>(rCount, QVector<qreal>(colCount));
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) {
171  qreal maxOffsetInThisRow = 0.0;
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) {
179  maxOffsetInThisRow -= qreal(i);
180  }
181  totalOffset += qMax(maxOffsetInThisRow, ( qreal )0.0);
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 
199  QVariant vValY;
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 
243 void 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 
252 void RingDiagram::resize(const QSizeF &)
253 {
254 }
255 
263 void 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));
272  const ThreeDPieAttributes threeDAttrs(threeDPieAttributes(index));
273 
274  const int rCount = rowCount();
275  const int colCount = columnCount();
276 
277  int iPoint = 0;
278 
279  QRectF drawPosition = d->position;
280 
281  painter->setRenderHint(QPainter::Antialiasing);
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 
305  QPolygonF poly;
306 
307  qreal degree = 0;
308 
309  qreal actualStartAngle = startAngle + circularGap;
310  qreal actualAngleLen = angleLen - 2 * circularGap;
311 
312  qreal totalRadialExplode = 0.0;
313  qreal maxRadialExplode = 0.0;
314 
315  qreal totalRadialGap = 0.0;
316  qreal maxRadialGap = 0.0;
317  for (uint i = rCount - 1; i > dataset; --i) {
318  qreal maxRadialExplodeInThisRow = 0.0;
319  qreal maxRadialGapInThisRow = 0.0;
320  for (int j = 0; j < colCount; ++j) {
321  const PieAttributes cellAttrs(pieAttributes(model()->index(i, j, rootIndex()))); // checked
322  if (d->expandWhenExploded) {
323  maxRadialGapInThisRow = qMax(maxRadialGapInThisRow, cellAttrs.gapFactor(false));
324  }
325 
326  // Don't use a gap for the very inner circle
327  if (cellAttrs.explode() && d->expandWhenExploded) {
328  maxRadialExplodeInThisRow = qMax(maxRadialExplodeInThisRow, cellAttrs.explodeFactor());
329  }
330  }
331  maxRadialExplode += maxRadialExplodeInThisRow;
332  maxRadialGap += maxRadialGapInThisRow;
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,
343  totalRadialGap, totalRadialExplode);
344  poly.append(p);
345  degree += granularity;
346  iPoint++;
347  }
348  if (!perfectMatch) {
349  poly.append(pointOnEllipse(drawPosition, dataset, slice, false, actualStartAngle + actualAngleLen,
350  totalRadialGap, totalRadialExplode));
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;
358  actualAngleLen = angleLen - 2 * circularGap;
359 
360  degree = actualAngleLen;
361 
362  const int lastInnerBrinkPoint = iPoint;
363  while (degree >= 0) {
364  poly.append(pointOnEllipse(drawPosition, dataset, slice, true, actualStartAngle + degree,
365  totalRadialGap, totalRadialExplode));
366  perfectMatch = (degree == 0);
367  degree -= granularity;
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,
373  totalRadialGap, totalRadialExplode));
374  iPoint++;
375  }
376 
377  // The center point of the outer brink
378  const QPointF outerCenterPoint(poly[lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2)]);
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 
411 QPointF 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 
428  qreal explodeAngleRad = DEGTORAD(angle);
429  qreal cosAngle = cos(explodeAngleRad);
430  qreal sinAngle = -sin(explodeAngleRad);
431  qreal explodeAngleCenterRad = DEGTORAD(startAngle + angleLen / 2.0);
432  qreal cosAngleCenter = cos(explodeAngleCenterRad);
433  qreal sinAngleCenter = -sin(explodeAngleCenterRad);
434  return QPointF((offsetX + gapOffsetX) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(),
435  (offsetY + gapOffsetY) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y());
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 
452 qreal RingDiagram::valueTotals(int dataset) const
453 {
454  Q_ASSERT(dataset < model()->rowCount());
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.
#define d
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)
void setRectangle(const QRectF &rect)
const QRectF rectangle() const
QPainter * painter() const
A set of attributes controlling the appearance of pie charts.
qreal gapFactor(bool circular) const
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.

© 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 by doxygen 1.9.1