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
23using namespace KChart;
24
25RingDiagram::Private::Private()
26 : relativeThickness( false )
27 , expandWhenExploded( false )
28{
29}
30
31RingDiagram::Private::~Private() {}
32
33#define d d_func()
34
35RingDiagram::RingDiagram( QWidget* parent, PolarCoordinatePlane* plane ) :
36 AbstractPieDiagram( new Private(), parent, plane )
37{
38 init();
39}
40
41RingDiagram::~RingDiagram()
42{
43}
44
45void RingDiagram::init()
46{
47}
48
50{
51 return new RingDiagram( new Private( *d ) );
52}
53
54bool 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
67void RingDiagram::setRelativeThickness( bool relativeThickness )
68{
69 d->relativeThickness = relativeThickness;
70}
71
72bool RingDiagram::relativeThickness() const
73{
74 return d->relativeThickness;
75}
76
77void RingDiagram::setExpandWhenExploded( bool expand )
78{
79 d->expandWhenExploded = expand;
80}
81
82bool RingDiagram::expandWhenExploded() const
83{
84 return d->expandWhenExploded;
85}
86
87const QPair<QPointF, QPointF> RingDiagram::calculateDataBoundaries () const
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
105 }
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
122void RingDiagram::paintEvent( QPaintEvent* )
123{
124 QPainter painter ( viewport() );
126 ctx.setPainter ( &painter );
127 ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
128 paint ( &ctx );
129}
130
131void 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 ) );
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 }
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
230void 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
239void RingDiagram::resize( const QSizeF& size)
240{
241 AbstractPieDiagram ::resize( size );
242}
243
244void 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
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 ) {
305 }
306
307 // Don't use a gap for the very inner circle
308 if ( cellAttrs.explode() && d->expandWhenExploded ) {
310 }
311 }
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,
325 poly.append( p );
326 degree += granularity;
327 iPoint++;
328 }
329 if ( ! perfectMatch ) {
330 poly.append( pointOnEllipse( drawPosition, dataset, slice, false, actualStartAngle + actualAngleLen,
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;
340
341 degree = actualAngleLen;
342
343 const int lastInnerBrinkPoint = iPoint;
344 while ( degree >= 0 ) {
345 poly.append( pointOnEllipse( drawPosition, dataset, slice, true, actualStartAngle + degree,
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,
355 iPoint++;
356 }
357
358 // The center point of the outer brink
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 );
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
390QPointF 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 );
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
431qreal 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
448qreal RingDiagram::numberOfDatasets() const
449{
450 return model() ? model()->rowCount( rootIndex() ) : 0.0;
451}
452
453/*virtual*/
455{
456 return 1;
457}
Declaring the class KChart::DataValueAttributes.
DataValueAttributes dataValueAttributes() const
Retrieve the DataValueAttributes specified globally.
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
Base class for any diagram type.
Stores information about painting diagrams.
A set of attributes controlling the appearance of pie charts.
qreal startPosition() const
Retrieve the rotation of the coordinate plane.
RingDiagram defines a common ring diagram.
void paint(PaintContext *paintContext) override
\reimpl
void resize(const QSizeF &area) override
\reimpl
qreal numberOfGridRings() const override
\reimpl
qreal valueTotals() const override
\reimpl
bool compare(const RingDiagram *other) const
Returns true if both diagrams have the same settings.
qreal numberOfValuesPerDataset() const override
\reimpl
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
\reimpl
virtual RingDiagram * clone() const
Creates an exact copy of this diagram.
A set of text attributes.
A set of 3D pie attributes.
QStringView level(QStringView ifopt)
QCA_EXPORT void init()
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 QPoint *points, int pointCount, Qt::FillRule fillRule)
void rotate(qreal angle)
void setBrush(Qt::BrushStyle style)
void setPen(Qt::PenStyle style)
void setRenderHint(RenderHint hint, bool on)
void translate(const QPoint &offset)
int height() const const
bool isEmpty() const const
int left() const const
int top() const const
int width() const const
QRect contentsRect() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:14:24 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.