KD Chart API Documentation  3.1
KDChartRadarDiagram.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** This file is part of the KD Chart library.
4 **
5 ** SPDX-FileCopyrightText: 2001 Klarälvdalens Datakonsult AB, a KDAB Group company <info@kdab.com>
6 **
7 ** SPDX-License-Identifier: MIT
8 **
9 ****************************************************************************/
10 
11 #include "KDChartRadarDiagram.h"
12 #include "KDChartRadarDiagram_p.h"
13 
14 #include "KDChartPaintContext.h"
15 #include "KDChartPainterSaver_p.h"
16 #include <QPainter>
17 
18 #include <KDABLibFakes>
19 
20 using namespace KDChart;
21 
22 RadarDiagram::Private::Private()
23 {
24 }
25 
26 RadarDiagram::Private::~Private()
27 {
28 }
29 
30 #define d d_func()
31 
33  : AbstractPolarDiagram(new Private(), parent, plane)
34 {
35  // init();
36 }
37 
39 {
40 }
41 
42 void RadarDiagram::init()
43 {
44 }
45 
50 {
51  auto *newDiagram = new RadarDiagram(new Private(*d));
52  // This needs to be copied after the fact
53  newDiagram->d->closeDatasets = d->closeDatasets;
54  return newDiagram;
55 }
56 
57 const QPair<QPointF, QPointF> RadarDiagram::calculateDataBoundaries() const
58 {
59  if (!checkInvariants(true))
60  return QPair<QPointF, QPointF>(QPointF(0, 0), QPointF(0, 0));
61  const int rowCount = model()->rowCount(rootIndex());
62  const int colCount = model()->columnCount(rootIndex());
63  qreal xMin = 0.0;
64  qreal xMax = colCount;
65  qreal yMin = 0, yMax = 0;
66  for (int iCol = 0; iCol < colCount; ++iCol) {
67  for (int iRow = 0; iRow < rowCount; ++iRow) {
68  qreal value = model()->data(model()->index(iRow, iCol, rootIndex())).toReal(); // checked
69  yMax = qMax(yMax, value);
70  yMin = qMin(yMin, value);
71  }
72  }
73  QPointF bottomLeft(QPointF(xMin, yMin));
74  QPointF topRight(QPointF(xMax, yMax));
75  return QPair<QPointF, QPointF>(bottomLeft, topRight);
76 }
77 
78 void RadarDiagram::paintEvent(QPaintEvent *)
79 {
80  QPainter painter(viewport());
81  PaintContext ctx;
82  ctx.setPainter(&painter);
83  ctx.setRectangle(QRectF(0, 0, width(), height()));
84  paint(&ctx);
85 }
86 
88 {
89  qreal dummy1, dummy2;
90  paint(ctx, true, dummy1, dummy2);
91  paint(ctx, false, dummy1, dummy2);
92 }
93 
94 static qreal fitFontSizeToGeometry(const QString &text, const QFont &font, const QRectF &geometry, const TextAttributes &ta)
95 {
96  QFont f = font;
97  const qreal origResult = f.pointSizeF();
98  qreal result = origResult;
99  const QSizeF mySize = geometry.size();
100  if (mySize.isNull())
101  return result;
102 
103  const QString t = text;
104  QFontMetrics fm(f);
105  while (true) {
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 
119 static 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 }
134 {
135  return d->reverseData;
136 }
137 
138 // local structure to remember the settings of a polygon inclusive the used color and pen.
139 struct Polygon
140 {
141  QPolygonF polygon;
142  QBrush brush;
143  QPen pen;
144  Polygon(const QPolygonF &polygon, const QBrush &brush, const QPen &pen)
145  : polygon(polygon)
146  , brush(brush)
147  , pen(pen)
148  {
149  }
150 };
151 
153  bool calculateListAndReturnScale,
154  qreal &newZoomX, qreal &newZoomY)
155 {
156  // note: Not having any data model assigned is no bug
157  // but we can not draw a diagram then either.
158  if (!checkInvariants(true))
159  return;
160  d->reverseMapper.clear();
161 
162  const int rowCount = model()->rowCount(rootIndex());
163  const int colCount = model()->columnCount(rootIndex());
164 
165  int iRow, iCol;
166 
167  const qreal min = dataBoundaries().first.y();
168  const qreal r = qAbs(min) + dataBoundaries().second.y();
169  const qreal step = (r - qAbs(min)) / (numberOfGridRings());
170 
171  auto *plane = dynamic_cast<RadarCoordinatePlane *>(ctx->coordinatePlane());
172  TextAttributes ta = plane->textAttributes();
173  QRectF fontRect = ctx->rectangle();
174  fontRect.setSize(QSizeF(fontRect.width(), step / 2.0));
175  const qreal labelFontSize = fitFontSizeToGeometry(QString::fromLatin1("TestXYWQgqy"), ta.font(), fontRect, ta);
176  QFont labelFont = ta.font();
177  ctx->painter()->setPen(ta.pen());
178  labelFont.setPointSizeF(labelFontSize);
179  const QFontMetricsF metric(labelFont);
180  const qreal labelHeight = metric.height();
181  QRectF destRect = ctx->rectangle();
182  if (ta.isVisible()) {
183  destRect.setY(destRect.y() + 2 * labelHeight);
184  destRect.setHeight(destRect.height() - 4 * labelHeight);
185  }
186 
187  if (calculateListAndReturnScale) {
188  ctx->painter()->save();
189  // Check if all of the data value texts / data comments will fit
190  // into the available space:
191  d->labelPaintCache.clear();
192  ctx->painter()->save();
193  for (iCol = 0; iCol < colCount; ++iCol) {
194  for (iRow = 0; iRow < rowCount; ++iRow) {
195  QModelIndex index = model()->index(iRow, iCol, rootIndex()); // checked
196  const qreal value = model()->data(index).toReal();
197  QPointF point = scaleToRealPosition(QPointF(value, iRow), ctx->rectangle(), destRect, *ctx->coordinatePlane());
198  d->addLabel(&d->labelPaintCache, index, nullptr, PositionPoints(point),
200  }
201  }
202  ctx->painter()->restore();
203  const qreal oldZoomX = coordinatePlane()->zoomFactorX();
204  const qreal oldZoomY = coordinatePlane()->zoomFactorY();
205  newZoomX = oldZoomX;
206  newZoomY = oldZoomY;
207  if (d->labelPaintCache.paintReplay.count()) {
208  QRectF txtRectF;
209  d->paintDataValueTextsAndMarkers(ctx, d->labelPaintCache, true, true, &txtRectF);
210  const QRect txtRect = txtRectF.toRect();
211  const QRect curRect = coordinatePlane()->geometry();
212  const qreal gapX = qMin(txtRect.left() - curRect.left(), curRect.right() - txtRect.right());
213  const qreal gapY = qMin(txtRect.top() - curRect.top(), curRect.bottom() - txtRect.bottom());
214  newZoomX = oldZoomX;
215  newZoomY = oldZoomY;
216  if (gapX < 0.0)
217  newZoomX *= 1.0 + (gapX - 1.0) / curRect.width();
218  if (gapY < 0.0)
219  newZoomY *= 1.0 + (gapY - 1.0) / curRect.height();
220  }
221  ctx->painter()->restore();
222 
223  } else {
224  // Iterate through data sets and create a list of polygons out of them.
225  QList<Polygon> polygons;
226  for (iCol = 0; iCol < colCount; ++iCol) {
227  // TODO(khz): As of yet RadarDiagram can not show per-segment line attributes
228  // but it draws every polyline in one go - using one color.
229  // This needs to be enhanced to allow for cell-specific settings
230  // in the same way as LineDiagram does it.
231  QPolygonF polygon;
232  QPointF point0;
233  for (iRow = 0; iRow < rowCount; ++iRow) {
234  QModelIndex index = model()->index(iRow, iCol, rootIndex()); // checked
235  const qreal value = model()->data(index).toReal();
236  QPointF point = scaleToRealPosition(QPointF(value, d->reverseData ? (rowCount - iRow) : iRow), ctx->rectangle(), destRect, *ctx->coordinatePlane());
237  polygon.append(point);
238  if (!iRow)
239  point0 = point;
240  }
241  if (closeDatasets() && rowCount)
242  polygon.append(point0);
243 
244  QBrush brush = d->datasetAttrs(iCol, KDChart::DatasetBrushRole).value<QBrush>();
245  QPen p = d->datasetAttrs(iCol, KDChart::DatasetPenRole).value<QPen>();
246  if (p.style() != Qt::NoPen) {
247  polygons.append(Polygon(polygon, brush, PrintingParameters::scalePen(p)));
248  }
249  }
250 
251  // first fill the areas with the brush-color and the defined alpha-value.
252  if (d->fillAlpha > 0.0) {
253  for (const Polygon &p : polygons) {
254  PainterSaver painterSaver(ctx->painter());
255  ctx->painter()->setRenderHint(QPainter::Antialiasing);
256  QBrush br = p.brush;
257  QColor c = br.color();
258  c.setAlphaF(d->fillAlpha);
259  br.setColor(c);
260  ctx->painter()->setBrush(br);
261  ctx->painter()->setPen(p.pen);
262  ctx->painter()->drawPolygon(p.polygon);
263  }
264  }
265 
266  // then draw the poly-lines.
267  for (const Polygon &p : polygons) {
268  PainterSaver painterSaver(ctx->painter());
269  ctx->painter()->setRenderHint(QPainter::Antialiasing);
270  ctx->painter()->setBrush(p.brush);
271  ctx->painter()->setPen(p.pen);
272  ctx->painter()->drawPolyline(p.polygon);
273  }
274 
275  d->paintDataValueTextsAndMarkers(ctx, d->labelPaintCache, true);
276  }
277 }
278 
279 void RadarDiagram::resize(const QSizeF &)
280 {
281 }
282 
283 /*virtual*/
285 {
286  return model()->rowCount(rootIndex());
287 }
288 
289 /*virtual*/
291 {
292  return model() ? model()->rowCount(rootIndex()) : 0.0;
293 }
294 
295 /*virtual*/
297 {
298  return 5; // FIXME
299 }
300 
301 void RadarDiagram::setCloseDatasets(bool closeDatasets)
302 {
303  d->closeDatasets = closeDatasets;
304 }
305 
307 {
308  return d->closeDatasets;
309 }
310 
312 {
313  return d->fillAlpha;
314 }
315 
316 void RadarDiagram::setFillAlpha(qreal alphaF)
317 {
318  d->fillAlpha = alphaF;
319 }
320 
321 void RadarDiagram::resizeEvent(QResizeEvent *)
322 {
323 }
QRectF rotatedRect(const QRectF &rect, qreal rotation)
static QPointF scaleToRealPosition(const QPointF &origin, const QRectF &sourceRect, const QRectF &destRect, const AbstractCoordinatePlane &plane)
#define d
static qreal fitFontSizeToGeometry(const QString &text, const QFont &font, const QRectF &geometry, const TextAttributes &ta)
Base class common for all coordinate planes, CartesianCoordinatePlane, PolarCoordinatePlane,...
virtual const QPointF translate(const QPointF &diagramPoint) const =0
virtual bool checkInvariants(bool justReturnTheStatus=false) const
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
Base class for diagrams based on a polar coordinate system.
Stores information about painting diagrams.
void setPainter(QPainter *painter)
void setRectangle(const QRectF &rect)
const QRectF rectangle() const
AbstractCoordinatePlane * coordinatePlane() const
QPainter * painter() const
Stores the absolute target points of a Position.
static const Position & Center
static QPen scalePen(const QPen &pen)
RadarDiagram defines a common radar diagram.
virtual RadarDiagram * clone() const
qreal numberOfGridRings() const override
void resizeEvent(QResizeEvent *) override
const QPair< QPointF, QPointF > calculateDataBoundaries() const override
void resize(const QSizeF &area) override
qreal numberOfValuesPerDataset() const override
void paintEvent(QPaintEvent *) override
void setFillAlpha(qreal alphaF)
virtual void paint(PaintContext *paintContext, bool calculateListAndReturnScale, qreal &newZoomX, qreal &newZoomY)
qreal valueTotals() const override
RadarDiagram(QWidget *parent=nullptr, RadarCoordinatePlane *plane=nullptr)
void setCloseDatasets(bool closeDatasets)
A set of text attributes.
@ DatasetBrushRole
@ DatasetPenRole

© 2001 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/
Generated by doxygen 1.9.1