KD Chart 2  [rev.2.7]
KDChartRadarDiagram.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 ** Copyright (C) 2001-2020 Klaralvdalens Datakonsult AB. All rights reserved.
3 **
4 ** This file is part of the KD Chart library.
5 **
6 ** Licensees holding valid commercial KD Chart licenses may use this file in
7 ** accordance with the KD Chart Commercial License Agreement provided with
8 ** the Software.
9 **
10 **
11 ** This file may be distributed and/or modified under the terms of the
12 ** GNU General Public License version 2 and version 3 as published by the
13 ** Free Software Foundation and appearing in the file LICENSE.GPL included.
14 **
15 ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
16 ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
17 **
18 ** Contact info@kdab.com if any conditions of this licensing are not
19 ** clear to you.
20 **
21 **********************************************************************/
22 
23 #include "KDChartRadarDiagram.h"
24 #include "KDChartRadarDiagram_p.h"
25 
26 #include <QPainter>
27 #include "KDChartPaintContext.h"
28 #include "KDChartPainterSaver_p.h"
29 
30 #include <KDABLibFakes>
31 
32 using namespace KDChart;
33 
34 RadarDiagram::Private::Private() :
35  closeDatasets( false ),
36  reverseData( false ),
37  fillAlpha( 0.0 )
38 {
39 }
40 
41 RadarDiagram::Private::~Private() {}
42 
43 #define d d_func()
44 
46  AbstractPolarDiagram( new Private( ), parent, plane )
47 {
48  //init();
49 }
50 
52 {
53 
54 }
55 
56 void RadarDiagram::init()
57 {
58 }
59 
60 
65 {
66  RadarDiagram* newDiagram = new RadarDiagram( new Private( *d ) );
67  // This needs to be copied after the fact
68  newDiagram->d->closeDatasets = d->closeDatasets;
69  return newDiagram;
70 }
71 
73 {
74  if ( !checkInvariants(true) ) return QPair<QPointF, QPointF>( QPointF( 0, 0 ), QPointF( 0, 0 ) );
75  const int rowCount = model()->rowCount(rootIndex());
76  const int colCount = model()->columnCount(rootIndex());
77  qreal xMin = 0.0;
78  qreal xMax = colCount;
79  qreal yMin = 0, yMax = 0;
80  for ( int iCol=0; iCol<colCount; ++iCol ) {
81  for ( int iRow=0; iRow< rowCount; ++iRow ) {
82  qreal value = model()->data( model()->index( iRow, iCol, rootIndex() ) ).toReal(); // checked
83  yMax = qMax( yMax, value );
84  yMin = qMin( yMin, value );
85  }
86  }
87  QPointF bottomLeft ( QPointF( xMin, yMin ) );
88  QPointF topRight ( QPointF( xMax, yMax ) );
89  return QPair<QPointF, QPointF> ( bottomLeft, topRight );
90 }
91 
92 
93 
94 void RadarDiagram::paintEvent ( QPaintEvent*)
95 {
96  QPainter painter ( viewport() );
97  PaintContext ctx;
98  ctx.setPainter ( &painter );
99  ctx.setRectangle( QRectF ( 0, 0, width(), height() ) );
100  paint ( &ctx );
101 }
102 
104 {
105  qreal dummy1, dummy2;
106  paint( ctx, true, dummy1, dummy2 );
107  paint( ctx, false, dummy1, dummy2 );
108 }
109 
110 static qreal fitFontSizeToGeometry( const QString& text, const QFont& font, const QRectF& geometry, const TextAttributes& ta )
111 {
112  QFont f = font;
113  const qreal origResult = f.pointSizeF();
114  qreal result = origResult;
115  const QSizeF mySize = geometry.size();
116  if ( mySize.isNull() )
117  return result;
118 
119  const QString t = text;
120  QFontMetrics fm( f );
121  while ( true )
122  {
123  const QSizeF textSize = rotatedRect( fm.boundingRect( t ), ta.rotation() ).normalized().size();
124 
125  if ( textSize.height() <= mySize.height() && textSize.width() <= mySize.width() )
126  return result;
127 
128  result -= 0.5;
129  if ( result <= 0.0 )
130  return origResult;
131  f.setPointSizeF( result );
132  fm = QFontMetrics( f );
133  }
134 }
135 
136 static QPointF scaleToRealPosition( const QPointF& origin, const QRectF& sourceRect, const QRectF& destRect, const AbstractCoordinatePlane& plane )
137 {
138  QPointF result = plane.translate( origin );
139  result -= sourceRect.topLeft();
140  result.setX( result.x() / sourceRect.width() * destRect.width() );
141  result.setY( result.y() / sourceRect.height() * destRect.height() );
142  result += destRect.topLeft();
143  return result;
144 }
145 
147 {
148  d->reverseData = val;
149 }
151 {
152  return d->reverseData;
153 }
154 
155 // local structure to remember the settings of a polygon inclusive the used color and pen.
156 struct Polygon {
157  QPolygonF polygon;
158  QBrush brush;
159  QPen pen;
160  Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen) : polygon(polygon), brush(brush), pen(pen) {}
161 };
162 
164  bool calculateListAndReturnScale,
165  qreal& newZoomX, qreal& newZoomY )
166 {
167  // note: Not having any data model assigned is no bug
168  // but we can not draw a diagram then either.
169  if ( !checkInvariants(true) )
170  return;
171  d->reverseMapper.clear();
172 
173  const int rowCount = model()->rowCount( rootIndex() );
174  const int colCount = model()->columnCount( rootIndex() );
175 
176  int iRow, iCol;
177 
178  const qreal min = dataBoundaries().first.y();
179  const qreal r = qAbs( min ) + dataBoundaries().second.y();
180  const qreal step = ( r - qAbs( min ) ) / ( numberOfGridRings() );
181 
182  RadarCoordinatePlane* plane = dynamic_cast<RadarCoordinatePlane*>(ctx->coordinatePlane());
183  TextAttributes ta = plane->textAttributes();
184  QRectF fontRect = ctx->rectangle();
185  fontRect.setSize( QSizeF( fontRect.width(), step / 2.0 ) );
186  const qreal labelFontSize = fitFontSizeToGeometry( QString::fromLatin1( "TestXYWQgqy" ), ta.font(), fontRect, ta );
187  QFont labelFont = ta.font();
188  ctx->painter()->setPen( ta.pen() );
189  labelFont.setPointSizeF( labelFontSize );
190  const QFontMetricsF metric( labelFont );
191  const qreal labelHeight = metric.height();
192  QPointF offset;
193  QRectF destRect = ctx->rectangle();
194  if ( ta.isVisible() )
195  {
196  destRect.setY( destRect.y() + 2 * labelHeight );
197  destRect.setHeight( destRect.height() - 4 * labelHeight );
198  }
199 
200  if ( calculateListAndReturnScale ) {
201  ctx->painter()->save();
202  // Check if all of the data value texts / data comments will fit
203  // into the available space:
204  d->labelPaintCache.clear();
205  ctx->painter()->save();
206  for ( iCol=0; iCol < colCount; ++iCol ) {
207  for ( iRow=0; iRow < rowCount; ++iRow ) {
208  QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
209  const qreal value = model()->data( index ).toReal();
210  QPointF point = scaleToRealPosition( QPointF( value, iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
211  d->addLabel( &d->labelPaintCache, index, 0, PositionPoints( point ),
213  }
214  }
215  ctx->painter()->restore();
216  const qreal oldZoomX = coordinatePlane()->zoomFactorX();
217  const qreal oldZoomY = coordinatePlane()->zoomFactorY();
218  newZoomX = oldZoomX;
219  newZoomY = oldZoomY;
220  if ( d->labelPaintCache.paintReplay.count() ) {
221  QRectF txtRectF;
222  d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true, true, &txtRectF );
223  const QRect txtRect = txtRectF.toRect();
224  const QRect curRect = coordinatePlane()->geometry();
225  const qreal gapX = qMin( txtRect.left() - curRect.left(), curRect.right() - txtRect.right() );
226  const qreal gapY = qMin( txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom() );
227  newZoomX = oldZoomX;
228  newZoomY = oldZoomY;
229  if ( gapX < 0.0 )
230  newZoomX *= 1.0 + (gapX-1.0) / curRect.width();
231  if ( gapY < 0.0 )
232  newZoomY *= 1.0 + (gapY-1.0) / curRect.height();
233  }
234  ctx->painter()->restore();
235 
236  } else {
237  // Iterate through data sets and create a list of polygons out of them.
238  QList<Polygon> polygons;
239  for ( iCol=0; iCol < colCount; ++iCol ) {
240  //TODO(khz): As of yet RadarDiagram can not show per-segment line attributes
241  // but it draws every polyline in one go - using one color.
242  // This needs to be enhanced to allow for cell-specific settings
243  // in the same way as LineDiagram does it.
244  QPolygonF polygon;
245  QPointF point0;
246  for ( iRow=0; iRow < rowCount; ++iRow ) {
247  QModelIndex index = model()->index( iRow, iCol, rootIndex() ); // checked
248  const qreal value = model()->data( index ).toReal();
249  QPointF point = scaleToRealPosition( QPointF( value, d->reverseData ? ( rowCount - iRow ) : iRow ), ctx->rectangle(), destRect, *ctx->coordinatePlane() );
250  polygon.append( point );
251  if ( ! iRow )
252  point0= point;
253  }
254  if ( closeDatasets() && rowCount )
255  polygon.append( point0 );
256 
257  QBrush brush = d->datasetAttrs( iCol, KDChart::DatasetBrushRole ).value<QBrush>();
258  QPen p = d->datasetAttrs( iCol, KDChart::DatasetPenRole ).value< QPen >();
259  if ( p.style() != Qt::NoPen )
260  {
261  polygons.append( Polygon( polygon, brush, PrintingParameters::scalePen( p ) ) );
262  }
263  }
264 
265  // first fill the areas with the brush-color and the defined alpha-value.
266  if (d->fillAlpha > 0.0) {
267  Q_FOREACH(const Polygon& p, polygons) {
268  PainterSaver painterSaver( ctx->painter() );
269  ctx->painter()->setRenderHint ( QPainter::Antialiasing );
270  QBrush br = p.brush;
271  QColor c = br.color();
272  c.setAlphaF(d->fillAlpha);
273  br.setColor(c);
274  ctx->painter()->setBrush( br );
275  ctx->painter()->setPen( p.pen );
276  ctx->painter()->drawPolygon( p.polygon );
277  }
278  }
279 
280  // then draw the poly-lines.
281  Q_FOREACH(const Polygon& p, polygons) {
282  PainterSaver painterSaver( ctx->painter() );
283  ctx->painter()->setRenderHint ( QPainter::Antialiasing );
284  ctx->painter()->setBrush( p.brush );
285  ctx->painter()->setPen( p.pen );
286  ctx->painter()->drawPolyline( p.polygon );
287  }
288 
289  d->paintDataValueTextsAndMarkers( ctx, d->labelPaintCache, true );
290  }
291 }
292 
293 void RadarDiagram::resize ( const QSizeF& )
294 {
295 }
296 
297 /*virtual*/
299 {
300  return model()->rowCount(rootIndex());
301 }
302 
303 /*virtual*/
305 {
306  return model() ? model()->rowCount(rootIndex()) : 0.0;
307 }
308 
309 /*virtual*/
311 {
312  return 5; // FIXME
313 }
314 
316 {
317  d->closeDatasets = closeDatasets;
318 }
319 
321 {
322  return d->closeDatasets;
323 }
324 
326 {
327  return d->fillAlpha;
328 }
329 
330 void RadarDiagram::setFillAlpha(qreal alphaF)
331 {
332  d->fillAlpha = alphaF;
333 }
334 
335 void RadarDiagram::resizeEvent ( QResizeEvent*)
336 {
337 }
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane, TernaryCoordinatePlane.
const QRectF rectangle() const
void setPainter(QPainter *painter)
void paintEvent(QPaintEvent *) override
virtual bool checkInvariants(bool justReturnTheStatus=false) const
void setCloseDatasets(bool closeDatasets)
Close each of the data series by connecting the last point to its respective start point...
virtual const QPointF translate(const QPointF &diagramPoint) const =0
Translate the given point in value space coordinates to a position in pixel space.
AbstractCoordinatePlane * coordinatePlane() const
QRect geometry() const override
pure virtual in QLayoutItem
QPainter * painter() const
static const Position & Center
AbstractCoordinatePlane * coordinatePlane() const
The coordinate plane associated with the diagram.
static QPointF scaleToRealPosition(const QPointF &origin, const QRectF &sourceRect, const QRectF &destRect, const AbstractCoordinatePlane &plane)
virtual RadarDiagram * clone() const
Creates an exact copy of this diagram.
Base class for diagrams based on a polar coordinate system.
QRectF rotatedRect(const QRectF &rect, qreal rotation)
RadarDiagram defines a common radar diagram.
QBrush brush() const
Retrieve the brush to be used for painting datapoints globally.
const QPair< QPointF, QPointF > dataBoundaries() const
Return the bottom left and top right data point, that the diagram will display (unless the grid adjus...
virtual void paint(PaintContext *paintContext, bool calculateListAndReturnScale, qreal &newZoomX, qreal &newZoomY)
qreal numberOfGridRings() const override
[reimplemented]
void setReverseData(bool val)
if val is true the diagram will mirror the diagram datapoints
qreal valueTotals() const override
[reimplemented]
Stores information about painting diagrams.
void resize(const QSizeF &area) override
[reimplemented]
Stores the absolute target points of a Position.
QPen pen() const
Retrieve the pen to be used for painting datapoints globally.
qreal fillAlpha() const
Fill the areas of the radar chart with there respective color defined via KDChart::DatasetBrushRole.
qreal numberOfValuesPerDataset() const override
[reimplemented]
static QPen scalePen(const QPen &pen)
void setRectangle(const QRectF &rect)
Class only listed here to document inheritance of some KDChart classes.
RadarDiagram(QWidget *parent=0, RadarCoordinatePlane *plane=0)
#define d
static qreal fitFontSizeToGeometry(const QString &text, const QFont &font, const QRectF &geometry, const TextAttributes &ta)
void setFillAlpha(qreal alphaF)
const TextAttributes textAttributes() const
void resizeEvent(QResizeEvent *) override
A set of text attributes.
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
[reimplemented]

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/