KChart

KChartRadarDiagram.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 "KChartRadarDiagram.h"
10#include "KChartRadarDiagram_p.h"
11
12#include "KChartPaintContext.h"
13#include "KChartPainterSaver_p.h"
14#include "KChartMath_p.h"
15
16#include <QPainter>
17
18using namespace KChart;
19
20RadarDiagram::Private::Private() :
21 closeDatasets( false ),
22 reverseData( false ),
23 fillAlpha( 0.0 )
24{
25}
26
27RadarDiagram::Private::~Private() {}
28
29#define d d_func()
30
31RadarDiagram::RadarDiagram( QWidget* parent, RadarCoordinatePlane* plane ) :
32 AbstractPolarDiagram( new Private( ), parent, plane )
33{
34 //init();
35}
36
37RadarDiagram::~RadarDiagram()
38{
39
40}
41
42void RadarDiagram::init()
43{
44}
45
46
48{
49 RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) );
50 // This needs to be copied after the fact
51 newDiagram->d->closeDatasets = d->closeDatasets;
52 return newDiagram;
53}
54
55const QPair<QPointF, QPointF> RadarDiagram::calculateDataBoundaries () const
56{
57 if ( !checkInvariants(true) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
58 const int rowCount = model()->rowCount(rootIndex());
59 const int colCount = model()->columnCount(rootIndex());
60 qreal xMin = 0.0;
61 qreal xMax = colCount;
62 qreal yMin = 0, yMax = 0;
63 for ( int iCol=0; iCol<colCount; ++iCol ) {
64 for ( int iRow=0; iRow< rowCount; ++iRow ) {
65 qreal value = model()->data( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked
66 yMax = qMax( yMax, value );
67 yMin = qMin( yMin, value );
68 }
69 }
70 QPointF bottomLeft ( QPointF( xMin, yMin ) );
71 QPointF topRight ( QPointF( xMax, yMax ) );
72 return QPair<QPointF, QPointF> ( bottomLeft, topRight );
73}
74
75
76
77void RadarDiagram::paintEvent ( QPaintEvent*)
78{
79 QPainter painter ( viewport() );
81 ctx.setPainter ( &painter );
82 ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
83 paint ( &ctx );
84}
85
86void RadarDiagram::paint( PaintContext* ctx )
87{
88 qreal dummy1, dummy2;
89 paint( ctx, true, dummy1, dummy2 );
90 paint( ctx, false, dummy1, dummy2 );
91}
92
93static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta )
94{
95 QFont f = font;
96 const qreal origResult = f.pointSizeF();
97 qreal result = origResult;
98 const QSizeF mySize = geometry.size();
99 if ( mySize.isNull() )
100 return result;
101
102 const QString t = text;
103 QFontMetrics fm( f );
104 while ( true )
105 {
106 const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size();
107
108 if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() )
109 return result;
110
111 result -= 0.5;
112 if ( result <= 0.0 )
113 return origResult;
114 f.setPointSizeF( result );
115 fm = QFontMetrics( f );
116 }
117}
118
119static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane )
120{
121 QPointF result = plane.translate( origin );
122 result -= sourceRect.topLeft();
123 result.setX( result.x() / sourceRect.width() * destRect.width() );
124 result.setY( result.y() / sourceRect.height() * destRect.height() );
125 result += destRect.topLeft();
126 return result;
127}
128
130{
131 d->reverseData = val;
132}
133bool RadarDiagram::reverseData()
134{
135 return d->reverseData;
136}
137
138// local structure to remember the settings of a polygon inclusive the used color and pen.
139struct Polygon {
140 QPolygonF polygon;
141 QBrush brush;
142 QPen pen;
143 Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {}
144};
145
146void RadarDiagram::paint( PaintContext* ctx,
147 bool calculateListAndReturnScale,
148 qreal& newZoomX, qreal& newZoomY )
149{
150 // note: Not having any data model assigned is no bug
151 // but we can not draw a diagram then either.
152 if ( !checkInvariants(true) )
153 return;
154 d->reverseMapper.clear();
155
156 const int rowCount = model()->rowCount( rootIndex() );
157 const int colCount = model()->columnCount( rootIndex() );
158
159 int iRow, iCol;
160
161 const qreal min = dataBoundaries().first.y();
162 const qreal r = qAbs( min ) + dataBoundaries().second.y();
163 const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() );
164
165 RadarCoordinatePlane* plane = dynamic_cast<RadarCoordinatePlane*>(ctx->coordinatePlane());
167 QRectF fontRect = ctx->rectangle();
168 fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) );
169 const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta );
170 QFont labelFont = ta.font();
171 ctx->painter()->setPen( ta.pen() );
172 labelFont.setPointSizeF( labelFontSize );
174 const qreal labelHeight = metric.height();
175 QRectF destRect = ctx->rectangle();
176 if ( ta.isVisible() )
177 {
178 destRect.setY( destRect.y() + 2 * labelHeight );
179 destRect.setHeight( destRect.height() - 4 * labelHeight );
180 }
181
183 ctx->painter()->save();
184 // Check if all of the data value texts / data comments will fit
185 // into the available space:
186 d->labelPaintCache.clear();
187 ctx->painter()->save();
188 for ( iCol=0; iCol < colCount; ++iCol ) {
189 for ( iRow=0; iRow < rowCount; ++iRow ) {
190 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
191 const qreal value = model()->data( index ).toReal();
192 QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
193 d->addLabel( &d->labelPaintCache, index, nullptr, PositionPoints( point ),
194 Position::Center, Position::Center, value );
195 }
196 }
197 ctx->painter()->restore();
198 const qreal oldZoomX = coordinatePlane()->zoomFactorX();
199 const qreal oldZoomY = coordinatePlane()->zoomFactorY();
202 if ( d->labelPaintCache.paintReplay.count() ) {
204 d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF );
205 const QRect txtRect = txtRectF.toRect();
207 const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right() - txtRect.right() );
208 const qreal gapY = qMin( txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom() );
211 if ( gapX < 0.0 )
212 newZoomX *= 1.0 + (gapX-1.0) / curRect.width();
213 if ( gapY < 0.0 )
214 newZoomY *= 1.0 + (gapY-1.0) / curRect.height();
215 }
216 ctx->painter()->restore();
217
218 } else {
219 // Iterate through data sets and create a list of polygons out of them.
220 QList<Polygon> polygons;
221 for ( iCol=0; iCol < colCount; ++iCol ) {
222 //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes
223 // but it draws every polyline in one go - using one color.
224 // This needs to be enhanced to allow for cell-specific settings
225 // in the same way as LineDiagram does it.
226 QPolygonF polygon;
228 for ( iRow=0; iRow < rowCount; ++iRow ) {
229 QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
230 const qreal value = model()->data( index ).toReal();
231 QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
232 polygon.append( point );
233 if ( ! iRow )
234 point0= point;
235 }
236 if ( closeDatasets() && rowCount )
237 polygon.append( point0 );
238
239 QBrush brush = d->datasetAttrs( iCol, KChart::DatasetBrushRole ).value<QBrush>();
240 QPen p = d->datasetAttrs( iCol, KChart::DatasetPenRole ).value< QPen >();
241 if ( p.style() != Qt::NoPen )
242 {
243 polygons.append( Polygon( polygon, brush, PrintingParameters::scalePen( p ) ) );
244 }
245 }
246
247 // first fill the areas with the brush-color and the defined alpha-value.
248 if (d->fillAlpha > 0.0) {
249 for (const Polygon& p : qAsConst(polygons)) {
250 PainterSaver painterSaver( ctx->painter() );
251 ctx->painter()->setRenderHint ( QPainter::Antialiasing );
252 QBrush br = p.brush;
253 QColor c = br.color();
254 c.setAlphaF(d->fillAlpha);
255 br.setColor(c);
256 ctx->painter()->setBrush( br );
257 ctx->painter()->setPen( p.pen );
258 ctx->painter()->drawPolygon( p.polygon );
259 }
260 }
261
262 // then draw the poly-lines.
263 for (const Polygon& p : qAsConst(polygons)) {
264 PainterSaver painterSaver( ctx->painter() );
265 ctx->painter()->setRenderHint ( QPainter::Antialiasing );
266 ctx->painter()->setBrush( p.brush );
267 ctx->painter()->setPen( p.pen );
268 ctx->painter()->drawPolyline( p.polygon );
269 }
270
271 d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true );
272 }
273}
274
275void RadarDiagram::resize ( const QSizeF& size )
276{
278}
279
280/*virtual*/
282{
283 return model()->rowCount(rootIndex());
284}
285
286/*virtual*/
288{
289 return model() ? model()->rowCount(rootIndex()) : 0.0;
290}
291
292/*virtual*/
294{
295 return 5; // FIXME
296}
297
298void RadarDiagram::setCloseDatasets( bool closeDatasets )
299{
300 d->closeDatasets = closeDatasets;
301}
302
303bool RadarDiagram::closeDatasets() const
304{
305 return d->closeDatasets;
306}
307
309{
310 return d->fillAlpha;
311}
312
313void RadarDiagram::setFillAlpha(qreal alphaF)
314{
315 d->fillAlpha = alphaF;
316}
317
318void RadarDiagram::resizeEvent ( QResizeEvent*)
319{
320}
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
virtual const QPointF translate(const QPointF &diagramPoint) const =0
Translate the given point in value space coordinates to a position in pixel space.
QRect geometry() const override
pure virtual in QLayoutItem
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
AbstractCoordinatePlane * coordinatePlane() const
The coordinate plane associated with the diagram.
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
Base class for diagrams based on a polar coordinate system.
Stores information about painting diagrams.
Stores the absolute target points of a Position.
const TextAttributes textAttributes() const
RadarDiagram defines a common radar diagram.
virtual RadarDiagram * clone() const
Creates an exact copy of this diagram.
void setReverseData(bool val)
if val is true the diagram will mirror the diagram datapoints
qreal numberOfGridRings() const override
\reimpl
qreal fillAlpha() const
Fill the areas of the radar chart with there respective color defined via KChart::DatasetBrushRole.
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
\reimpl
void resize(const QSizeF &area) override
\reimpl
qreal numberOfValuesPerDataset() const override
\reimpl
qreal valueTotals() const override
\reimpl
void setCloseDatasets(bool closeDatasets)
Close each of the data series by connecting the last point to its respective start point.
A set of text attributes.
virtual int columnCount(const QModelIndex &parent) const const=0
virtual QVariant data(const QModelIndex &index, int role) const const=0
virtual QModelIndex index(int row, int column, 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
void setAlphaF(float alpha)
qreal pointSizeF() const const
void setPointSizeF(qreal pointSize)
void append(QList< T > &&value)
T qobject_cast(QObject *object)
void setX(qreal x)
void setY(qreal y)
qreal x() const const
qreal y() const const
qreal height() const const
QRectF normalized() const const
QSizeF size() const const
QPointF topLeft() const const
qreal width() const const
qreal height() const const
bool isNull() const const
qreal width() const const
QString fromLatin1(QByteArrayView str)
qreal toReal(bool *ok) const const
virtual int metric(PaintDeviceMetric m) const const override
void resize(const QSize &)
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.