KChart

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

KDE's Doxygen guidelines are available online.