KChart

KChartRingDiagram.cpp
1 /*
2  * SPDX-FileCopyrightText: 2001-2015 Klaralvdalens Datakonsult AB. All rights reserved.
3  *
4  * This file is part of the KD Chart library.
5  *
6  * SPDX-License-Identifier: GPL-2.0-or-later
7  */
8 
9 #include "KChartRingDiagram.h"
10 #include "KChartRingDiagram_p.h"
11 
12 #include "KChartAttributesModel.h"
13 #include "KChartPaintContext.h"
14 #include "KChartPainterSaver_p.h"
15 #include "KChartPieAttributes.h"
16 #include "KChartPolarCoordinatePlane_p.h"
17 #include "KChartThreeDPieAttributes.h"
19 #include "KChartMath_p.h"
20 
21 #include <QPainter>
22 
23 using namespace KChart;
24 
25 RingDiagram::Private::Private()
26  : relativeThickness( false )
27  , expandWhenExploded( false )
28 {
29 }
30 
31 RingDiagram::Private::~Private() {}
32 
33 #define d d_func()
34 
35 RingDiagram::RingDiagram( QWidget* parent, PolarCoordinatePlane* plane ) :
36  AbstractPieDiagram( new Private(), parent, plane )
37 {
38  init();
39 }
40 
41 RingDiagram::~RingDiagram()
42 {
43 }
44 
45 void RingDiagram::init()
46 {
47 }
48 
50 {
51  return new RingDiagram( new Private( *d ) );
52 }
53 
54 bool RingDiagram::compare( const RingDiagram* other ) const
55 {
56  if ( other == this ) return true;
57  if ( ! other ) {
58  return false;
59  }
60  return // compare the base class
61  ( static_cast<const AbstractPieDiagram*>(this)->compare( other ) ) &&
62  // compare own properties
63  (relativeThickness() == other->relativeThickness()) &&
64  (expandWhenExploded() == other->expandWhenExploded());
65 }
66 
67 void RingDiagram::setRelativeThickness( bool relativeThickness )
68 {
69  d->relativeThickness = relativeThickness;
70 }
71 
72 bool RingDiagram::relativeThickness() const
73 {
74  return d->relativeThickness;
75 }
76 
77 void RingDiagram::setExpandWhenExploded( bool expand )
78 {
79  d->expandWhenExploded = expand;
80 }
81 
82 bool RingDiagram::expandWhenExploded() const
83 {
84  return d->expandWhenExploded;
85 }
86 
88 {
89  if ( !checkInvariants( true ) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
90 
91  const PieAttributes attrs( pieAttributes() );
92 
93  QPointF bottomLeft( 0, 0 );
94  QPointF topRight;
95  // If we explode, we need extra space for the pie slice that has the largest explosion distance.
96  if ( attrs.explode() ) {
97  const int rCount = rowCount();
98  const int colCount = columnCount();
99  qreal maxExplode = 0.0;
100  for ( int i = 0; i < rCount; ++i ) {
101  qreal maxExplodeInThisRow = 0.0;
102  for ( int j = 0; j < colCount; ++j ) {
103  const PieAttributes columnAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked
104  maxExplodeInThisRow = qMax( maxExplodeInThisRow, columnAttrs.explodeFactor() );
105  }
106  maxExplode += maxExplodeInThisRow;
107 
108  // FIXME: What if explode factor of inner ring is > 1.0 ?
109  if ( !d->expandWhenExploded ) {
110  break;
111  }
112  }
113  // explode factor is relative to width (outer r - inner r) of one ring
114  maxExplode /= ( rCount + 1);
115  topRight = QPointF( 1.0 + maxExplode, 1.0 + maxExplode );
116  } else {
117  topRight = QPointF( 1.0, 1.0 );
118  }
119  return QPair<QPointF, QPointF>( bottomLeft, topRight );
120 }
121 
122 void RingDiagram::paintEvent( QPaintEvent* )
123 {
124  QPainter painter ( viewport() );
125  PaintContext ctx;
126  ctx.setPainter ( &painter );
127  ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
128  paint ( &ctx );
129 }
130 
131 void RingDiagram::resizeEvent( QResizeEvent* )
132 {
133 }
134 
136 {
137  // note: Not having any data model assigned is no bug
138  // but we can not draw a diagram then either.
139  if ( !checkInvariants(true) )
140  return;
141 
142  d->reverseMapper.clear();
143 
144  const PieAttributes attrs( pieAttributes() );
145 
146  const int rCount = rowCount();
147  const int colCount = columnCount();
148 
149  //QRectF contentsRect = PolarCoordinatePlane::Private::contentsRect( polarCoordinatePlane() );
150  QRectF contentsRect = ctx->rectangle();
151  if ( contentsRect.isEmpty() )
152  return;
153 
154  d->startAngles = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
155  d->angleLens = QVector< QVector<qreal> >( rCount, QVector<qreal>( colCount ) );
156 
157  // compute position
158  d->size = qMin( contentsRect.width(), contentsRect.height() ); // initial size
159 
160  // if the slices explode, we need to give them additional space =>
161  // make the basic size smaller
162  qreal totalOffset = 0.0;
163  for ( int i = 0; i < rCount; ++i ) {
164  qreal maxOffsetInThisRow = 0.0;
165  for ( int j = 0; j < colCount; ++j ) {
166  const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked
167  //qDebug() << cellAttrs.explodeFactor();
168  const qreal explode = cellAttrs.explode() ? cellAttrs.explodeFactor() : 0.0;
169  maxOffsetInThisRow = qMax( maxOffsetInThisRow, cellAttrs.gapFactor( false ) + explode );
170  }
171  if ( !d->expandWhenExploded ) {
172  maxOffsetInThisRow -= qreal( i );
173  }
174  totalOffset += qMax<qreal>( maxOffsetInThisRow, 0.0 );
175  // FIXME: What if explode factor of inner ring is > 1.0 ?
176  //if ( !d->expandWhenExploded )
177  // break;
178  }
179 
180  // explode factor is relative to width (outer r - inner r) of one ring
181  if ( rCount > 0 )
182  totalOffset /= ( rCount + 1 );
183  d->size /= ( 1.0 + totalOffset );
184 
185 
186  qreal x = ( contentsRect.width() == d->size ) ? 0.0 : ( ( contentsRect.width() - d->size ) / 2.0 );
187  qreal y = ( contentsRect.height() == d->size ) ? 0.0 : ( ( contentsRect.height() - d->size ) / 2.0 );
188  d->position = QRectF( x, y, d->size, d->size );
189  d->position.translate( contentsRect.left(), contentsRect.top() );
190 
191  const PolarCoordinatePlane * plane = polarCoordinatePlane();
192 
193  d->forgetAlreadyPaintedDataValues();
194  for ( int iRow = 0; iRow < rCount; ++iRow ) {
195  const qreal sum = valueTotals( iRow );
196  if ( sum == 0.0 ) //nothing to draw
197  continue;
198  qreal currentValue = plane ? plane->startPosition() : 0.0;
199  const qreal sectorsPerValue = 360.0 / sum;
200 
201  for ( int iColumn = 0; iColumn < colCount; ++iColumn ) {
202  // is there anything at all at this column?
203  bool bOK;
204  const qreal cellValue = qAbs( model()->data( model()->index( iRow, iColumn, rootIndex() ) ) // checked
205  .toReal( &bOK ) );
206 
207  if ( bOK ) {
208  d->startAngles[ iRow ][ iColumn ] = currentValue;
209  d->angleLens[ iRow ][ iColumn ] = cellValue * sectorsPerValue;
210  } else { // mark as non-existent
211  d->angleLens[ iRow ][ iColumn ] = 0.0;
212  if ( iColumn > 0.0 ) {
213  d->startAngles[ iRow ][ iColumn ] = d->startAngles[ iRow ][ iColumn - 1 ];
214  } else {
215  d->startAngles[ iRow ][ iColumn ] = currentValue;
216  }
217  }
218 
219  currentValue = d->startAngles[ iRow ][ iColumn ] + d->angleLens[ iRow ][ iColumn ];
220 
221  drawOneSlice( ctx->painter(), iRow, iColumn, granularity() );
222  }
223  }
224 }
225 
226 #if defined ( Q_WS_WIN)
227 #define trunc(x) ((int)(x))
228 #endif
229 
230 void RingDiagram::drawOneSlice( QPainter* painter, uint dataset, uint slice, qreal granularity )
231 {
232  // Is there anything to draw at all?
233  const qreal angleLen = d->angleLens[ dataset ][ slice ];
234  if ( angleLen ) {
235  drawPieSurface( painter, dataset, slice, granularity );
236  }
237 }
238 
239 void RingDiagram::resize( const QSizeF& size)
240 {
242 }
243 
244 void RingDiagram::drawPieSurface( QPainter* painter, uint dataset, uint slice, qreal granularity )
245 {
246  // Is there anything to draw at all?
247  qreal angleLen = d->angleLens[ dataset ][ slice ];
248  if ( angleLen ) {
249  qreal startAngle = d->startAngles[ dataset ][ slice ];
250 
251  QModelIndex index( model()->index( dataset, slice, rootIndex() ) ); // checked
252  const PieAttributes attrs( pieAttributes( index ) );
253  const ThreeDPieAttributes threeDAttrs( threeDPieAttributes( index ) );
254 
255  const int rCount = rowCount();
256  const int colCount = columnCount();
257 
258  int iPoint = 0;
259 
260  QRectF drawPosition = d->position;
261 
263 
264  QBrush br = brush( index );
265  if ( threeDAttrs.isEnabled() ) {
266  br = threeDAttrs.threeDBrush( br, drawPosition );
267  }
268  painter->setBrush( br );
269 
270  painter->setPen( pen( index ) );
271 
272  if ( angleLen == 360 ) {
273  // full circle, avoid nasty line in the middle
274  // FIXME: Draw a complete ring here
275  //painter->drawEllipse( drawPosition );
276  } else {
277  bool perfectMatch = false;
278 
279  qreal circularGap = 0.0;
280 
281  if ( attrs.gapFactor( true ) > 0.0 ) {
282  // FIXME: Measure in degrees!
283  circularGap = attrs.gapFactor( true );
284  }
285 
286  QPolygonF poly;
287 
288  qreal degree = 0;
289 
290  qreal actualStartAngle = startAngle + circularGap;
291  qreal actualAngleLen = angleLen - 2 * circularGap;
292 
293  qreal totalRadialExplode = 0.0;
294  qreal maxRadialExplode = 0.0;
295 
296  qreal totalRadialGap = 0.0;
297  qreal maxRadialGap = 0.0;
298  for ( uint i = rCount - 1; i > dataset; --i ) {
299  qreal maxRadialExplodeInThisRow = 0.0;
300  qreal maxRadialGapInThisRow = 0.0;
301  for ( int j = 0; j < colCount; ++j ) {
302  const PieAttributes cellAttrs( pieAttributes( model()->index( i, j, rootIndex() ) ) ); // checked
303  if ( d->expandWhenExploded ) {
304  maxRadialGapInThisRow = qMax( maxRadialGapInThisRow, cellAttrs.gapFactor( false ) );
305  }
306 
307  // Don't use a gap for the very inner circle
308  if ( cellAttrs.explode() && d->expandWhenExploded ) {
309  maxRadialExplodeInThisRow = qMax( maxRadialExplodeInThisRow, cellAttrs.explodeFactor() );
310  }
311  }
312  maxRadialExplode += maxRadialExplodeInThisRow;
313  maxRadialGap += maxRadialGapInThisRow;
314 
315  // FIXME: What if explode factor of inner ring is > 1.0 ?
316  //if ( !d->expandWhenExploded )
317  // break;
318  }
319  totalRadialGap = maxRadialGap + attrs.gapFactor( false );
320  totalRadialExplode = attrs.explode() ? maxRadialExplode + attrs.explodeFactor() : maxRadialExplode;
321 
322  while ( degree <= actualAngleLen ) {
323  const QPointF p = pointOnEllipse( drawPosition, dataset, slice, false, actualStartAngle + degree,
324  totalRadialGap, totalRadialExplode );
325  poly.append( p );
326  degree += granularity;
327  iPoint++;
328  }
329  if ( ! perfectMatch ) {
330  poly.append( pointOnEllipse( drawPosition, dataset, slice, false, actualStartAngle + actualAngleLen,
331  totalRadialGap, totalRadialExplode ) );
332  iPoint++;
333  }
334 
335  // The center point of the inner brink
336  const QPointF innerCenterPoint( poly[ int(iPoint / 2) ] );
337 
338  actualStartAngle = startAngle + circularGap;
339  actualAngleLen = angleLen - 2 * circularGap;
340 
341  degree = actualAngleLen;
342 
343  const int lastInnerBrinkPoint = iPoint;
344  while ( degree >= 0 ) {
345  poly.append( pointOnEllipse( drawPosition, dataset, slice, true, actualStartAngle + degree,
346  totalRadialGap, totalRadialExplode ) );
347  perfectMatch = (degree == 0);
348  degree -= granularity;
349  iPoint++;
350  }
351  // if necessary add one more point to fill the last small gap
352  if ( ! perfectMatch ) {
353  poly.append( pointOnEllipse( drawPosition, dataset, slice, true, actualStartAngle,
354  totalRadialGap, totalRadialExplode ) );
355  iPoint++;
356  }
357 
358  // The center point of the outer brink
359  const QPointF outerCenterPoint( poly[ lastInnerBrinkPoint + int((iPoint - lastInnerBrinkPoint) / 2) ] );
360  //qDebug() << poly;
361  //find the value and paint it
362  //fix value position
363  const qreal sum = valueTotals( dataset );
364  painter->drawPolygon( poly );
365 
366  d->reverseMapper.addPolygon( index.row(), index.column(), poly );
367 
368  const QPointF centerPoint = (innerCenterPoint + outerCenterPoint) / 2.0;
369 
370  const PainterSaver ps( painter );
371  const TextAttributes ta = dataValueAttributes( index ).textAttributes();
372  if ( !ta.hasRotation() && autoRotateLabels() )
373  {
374  const QPointF& p1 = poly.last();
375  const QPointF& p2 = poly[ lastInnerBrinkPoint ];
376  const QLineF line( p1, p2 );
377  // TODO: do the label rotation like in PieDiagram
378  const qreal angle = line.dx() == 0 ? 0.0 : atan( line.dy() / line.dx() );
379  painter->translate( centerPoint );
380  painter->rotate( angle / 2.0 / 3.141592653589793 * 360.0 );
381  painter->translate( -centerPoint );
382  }
383 
384  paintDataValueText( painter, index, centerPoint, angleLen*sum / 360 );
385  }
386  }
387 }
388 
389 
390 QPointF RingDiagram::pointOnEllipse( const QRectF& rect, int dataset, int slice, bool outer, qreal angle,
391  qreal totalGapFactor, qreal totalExplodeFactor )
392 {
393  qreal angleLen = d->angleLens[ dataset ][ slice ];
394  qreal startAngle = d->startAngles[ dataset ][ slice ];
395 
396  const int rCount = rowCount() * 2;
397 
398  qreal level = outer ? ( rCount - dataset - 1 ) + 2 : ( rCount - dataset - 1 ) + 1;
399 
400  const qreal offsetX = rCount > 0 ? level * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
401  const qreal offsetY = rCount > 0 ? level * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0;
402  const qreal centerOffsetX = rCount > 0 ? totalExplodeFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
403  const qreal centerOffsetY = rCount > 0 ? totalExplodeFactor * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0;
404  const qreal gapOffsetX = rCount > 0 ? totalGapFactor * rect.width() / ( ( rCount + 1 ) * 2 ) : 0.0;
405  const qreal gapOffsetY = rCount > 0 ? totalGapFactor * rect.height() / ( ( rCount + 1 ) * 2 ) : 0.0;
406 
407  qreal explodeAngleRad = DEGTORAD( angle );
408  qreal cosAngle = cos( explodeAngleRad );
409  qreal sinAngle = -sin( explodeAngleRad );
410  qreal explodeAngleCenterRad = DEGTORAD( startAngle + angleLen / 2.0 );
411  qreal cosAngleCenter = cos( explodeAngleCenterRad );
412  qreal sinAngleCenter = -sin( explodeAngleCenterRad );
413  return QPointF( ( offsetX + gapOffsetX ) * cosAngle + centerOffsetX * cosAngleCenter + rect.center().x(),
414  ( offsetY + gapOffsetY ) * sinAngle + centerOffsetY * sinAngleCenter + rect.center().y() );
415 }
416 
417 /*virtual*/
419 {
420  const int rCount = rowCount();
421  const int colCount = columnCount();
422  qreal total = 0.0;
423  for ( int i = 0; i < rCount; ++i ) {
424  for ( int j = 0; j < colCount; ++j ) {
425  total += qAbs( model()->data( model()->index( i, j, rootIndex() ) ).toReal() ); // checked
426  }
427  }
428  return total;
429 }
430 
431 qreal RingDiagram::valueTotals( int dataset ) const
432 {
433  Q_ASSERT( dataset < model()->rowCount() );
434  const int colCount = columnCount();
435  qreal total = 0.0;
436  for ( int j = 0; j < colCount; ++j ) {
437  total += qAbs( model()->data( model()->index( dataset, j, rootIndex() ) ).toReal() ); // checked
438  }
439  return total;
440 }
441 
442 /*virtual*/
444 {
445  return model() ? model()->columnCount( rootIndex() ) : 0.0;
446 }
447 
448 qreal RingDiagram::numberOfDatasets() const
449 {
450  return model() ? model()->rowCount( rootIndex() ) : 0.0;
451 }
452 
453 /*virtual*/
455 {
456  return 1;
457 }
Class only listed here to document inheritance of some KChart classes.
void setRenderHint(QPainter::RenderHint hint, bool on)
void append(const T &value)
T & last()
void drawPolygon(const QPointF *points, int pointCount, Qt::FillRule fillRule)
qreal top() const const
qreal numberOfGridRings() const override
void rotate(qreal angle)
qreal left() const const
Declaring the class KChart::DataValueAttributes.
qreal dx() const const
qreal dy() const const
qreal x() const const
qreal y() const const
Stores information about painting diagrams.
void setPen(const QColor &color)
void paint(PaintContext *paintContext) override
int row() const const
RingDiagram defines a common ring diagram.
void setBrush(const QBrush &brush)
QPointF center() const const
virtual RingDiagram * clone() const
Creates an exact copy of this diagram.
bool isEmpty() const const
A set of attributes controlling the appearance of pie charts.
qreal startPosition() const
Retrieve the rotation of the coordinate plane.
QCA_EXPORT void init()
void resize(const QSizeF &area) override
A set of 3D pie attributes.
virtual void resize(const QSizeF &area)
Called by the widget&#39;s sizeEvent.
qreal width() const const
qreal numberOfValuesPerDataset() const override
Base class for any diagram type.
int column() const const
void translate(const QPointF &offset)
A set of text attributes.
qreal height() const const
Global namespace.
qreal valueTotals() const override
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
bool compare(const RingDiagram *other) const
Returns true if both diagrams have the same settings.
This file is part of the KDE documentation.
Documentation copyright © 1996-2022 The KDE developers.
Generated on Thu Jan 27 2022 22:33:23 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.