OGS
DiagramScene.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) OpenGeoSys Community (opengeosys.org)
2// SPDX-License-Identifier: BSD-3-Clause
3
4#include "DiagramScene.h"
5
6#include <cmath>
7#include <limits>
8
9// default size of a new window
10static constexpr float kDefaultX = 500.0;
11static constexpr float kDefaultY = 300.0;
12
17DiagramScene::DiagramScene(QObject* parent) : QGraphicsScene(parent)
18{
19 _bounds.setRect(0, 0, 1, 1);
20 initialize();
21}
22
29 : QGraphicsScene(parent)
30{
32 initialize();
33}
34
36{
37 delete _grid;
38 delete _xAxis;
39 delete _yAxis;
40 delete _xLabel;
41 delete _yLabel;
42 delete _xUnit;
43 delete _yUnit;
44 for (auto& graphCaption : _graphCaptions)
45 {
46 delete graphCaption;
47 }
48 _graphCaptions.clear();
49 for (auto& graph : _graphs)
50 {
51 delete graph;
52 }
53 _graphs.clear();
54 for (auto& text : _xTicksText)
55 {
56 delete text;
57 }
58 _xTicksText.clear();
59 for (auto& text : _yTicksText)
60 {
61 delete text;
62 }
63 _yTicksText.clear();
64 for (auto& list : _lists)
65 {
66 delete list;
67 }
68 _lists.clear();
69}
70
73QArrow* DiagramScene::addArrow(qreal length, qreal angle, QPen& pen)
74{
75 auto* arrow = new QArrow(length, angle, 8, 5, pen);
76 addItem(arrow);
77 return arrow;
78}
79
81void DiagramScene::addCaption(const QString& name, QPen& pen)
82{
83 auto* caption = new QGraphicsItemGroup(nullptr);
84 QGraphicsLineItem* l = addLine(0, 0, 100, 0, pen);
85 QGraphicsTextItem* t = addText(name);
86 l->setPos(0, 0);
87 t->setPos(110, -(t->boundingRect()).height() / 2);
88 caption->addToGroup(l);
89 caption->addToGroup(t);
90 caption->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
91
92 _graphCaptions.push_back(caption);
93 addItem(_graphCaptions[_graphCaptions.size() - 1]);
94}
95
98{
101 _xLabel->setPlainText(list->getXLabel());
102 _yLabel->setPlainText(list->getYLabel());
103 _xUnit->setPlainText(list->getXUnit());
104 _yUnit->setPlainText(list->getYUnit());
105
106 clearGrid();
108
109 _lists.push_back(list);
110 for (auto& list : _lists)
111 {
112 drawGraph(list);
113 }
114
115 update();
116}
117
119QGraphicsGrid* DiagramScene::addGrid(const QRectF& rect, int xTicks, int yTicks,
120 const QPen& pen)
121{
122 QGraphicsGrid* g = new QGraphicsGrid(rect, xTicks, yTicks, true, pen);
123 addItem(g);
124 return g;
125}
126
129 const QString& text, const QFont& font)
130{
131 auto* item = new QNonScalableGraphicsTextItem(text);
132 item->setFont(font);
133 addItem(item);
134 return item;
135}
136
139void DiagramScene::adjustAxis(qreal& min, qreal& max, int& numberOfTicks)
140{
141 const int MinTicks = 4;
142 double grossStep = (max - min) / MinTicks;
143 double step = pow(10.0, std::floor(log10(grossStep)));
144 if (5 * step < grossStep)
145 {
146 step *= 5;
147 }
148 else if (2 * step < grossStep)
149 {
150 step *= 2;
151 }
152 numberOfTicks = int(ceil(max / step) - std::floor(min / step));
153 if (numberOfTicks < MinTicks)
154 {
155 numberOfTicks = MinTicks;
156 }
157 min = std::floor(min / step) * step;
158 max = ceil(max / step) * step;
159}
160
164{
165 if ((_unscaledBounds.width() > 0) && (_unscaledBounds.height() > 0))
166 {
167 _scaleX = kDefaultX / static_cast<float>(_unscaledBounds.width());
168 _scaleY = kDefaultY / static_cast<float>(_unscaledBounds.height());
169 }
170}
171
174{
175 if (!_lists.isEmpty())
176 {
177 removeItem(_grid);
178
179 for (auto& text : _xTicksText)
180 {
181 removeItem(text);
182 }
183 for (auto& text : _yTicksText)
184 {
185 removeItem(text);
186 }
187 for (auto& graph : _graphs)
188 {
189 removeItem(graph);
190 }
191 for (auto& graphCaption : _graphCaptions)
192 {
193 removeItem(graphCaption);
194 }
195
196 _xTicksText.clear();
197 _yTicksText.clear();
198 _graphs.clear();
199 _graphCaptions.clear();
200 }
201}
202
206{
207 // be very careful with scaling parameters here!
208 int numXTicks;
209 int numYTicks;
210 qreal xMin = _unscaledBounds.left();
211 qreal yMin = _unscaledBounds.top();
212 qreal xMax = _unscaledBounds.right();
213 qreal yMax = _unscaledBounds.bottom();
214
215 adjustAxis(xMin, xMax, numXTicks);
216 adjustAxis(yMin, yMax, numYTicks);
217
218 // adjust boundaries of coordinate system according to scaling
219 _bounds.setRect(xMin * _scaleX,
220 yMin * _scaleY,
221 (xMax - xMin) * _scaleX,
222 (yMax - yMin) * _scaleY);
223
224 QPen pen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
225 _grid = addGrid(_bounds, numXTicks, numYTicks, pen);
226
227 if (_startDate == QDateTime())
228 {
229 for (int i = 0; i <= numXTicks; ++i)
230 {
231 auto x =
232 static_cast<int>(_bounds.left() / _scaleX +
233 (i * (_bounds.width() / _scaleX) / numXTicks));
234 _xTicksText.push_back(addNonScalableText(QString::number(x)));
235 _xTicksText.last()->setPos(x * _scaleX, _bounds.bottom() + 15);
236 }
237 }
238 else
239 {
240 for (int i = 0; i <= numXTicks; ++i)
241 {
242 auto x =
243 static_cast<int>(_bounds.left() / _scaleX +
244 (i * (_bounds.width() / _scaleX) / numXTicks));
245 QDateTime currentDate = _startDate.addSecs(x);
246 _xTicksText.push_back(
247 addNonScalableText(currentDate.toString("dd.MM.yyyy")));
248 _xTicksText.last()->setPos(x * _scaleX, _bounds.bottom() + 15);
249 }
250 }
251
252 for (int j = 0; j <= numYTicks; ++j)
253 {
254 qreal y = _bounds.bottom() / _scaleY -
255 (j * (_bounds.height() / _scaleY) / numYTicks);
256 qreal label = _bounds.top() / _scaleY +
257 (j * (_bounds.height() / _scaleY) / numYTicks);
258 _yTicksText.push_back(addNonScalableText(QString::number(label)));
259 _yTicksText.last()->setPos(_bounds.left() - MARGIN / 2, y * _scaleY);
260 }
261}
262
265{
266 QPainterPath path;
267
268 if (list->getPath(path, _scaleX, _scaleY))
269 {
270 QPen pen(list->getColor(), 2, Qt::SolidLine, Qt::SquareCap,
271 Qt::RoundJoin);
272 pen.setCosmetic(true);
273 _graphs.push_back(addPath(path, pen));
274 addCaption(list->getName(), pen);
275
276 int last = _graphs.size() - 1;
277
282 int verticalShift =
283 static_cast<int>(2 * (list->minYValue() * _scaleY) +
284 (_graphs[last]->boundingRect()).height());
285 _graphs[last]->setTransform(
286 QTransform(QMatrix(1, 0, 0, -1, 0, verticalShift)));
287 }
288}
289
293{
294 return (_bounds.top() <= 0 && _bounds.bottom() > 0)
295 ? static_cast<int>(_bounds.bottom() + _bounds.top())
296 : static_cast<int>(_bounds.bottom());
297}
298
302{
303 return (_bounds.left() <= 0 && _bounds.right() > 0)
304 ? 0
305 : static_cast<int>(_bounds.left());
306}
307
311{
312 QPen pen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
313 pen.setCosmetic(true);
314
315 setXAxis(addArrow(_bounds.width(), 0, pen));
316 setYAxis(addArrow(_bounds.height(), -90, pen));
319 _yLabel->setRotation(-90);
320
323
324 update();
325}
326
330{
331 if (!_lists.isEmpty())
332 {
333 if (list->minXValue() < _unscaledBounds.left())
334 {
335 _unscaledBounds.setLeft(list->minXValue());
336 }
337 if (list->minYValue() < _unscaledBounds.top())
338 {
339 _unscaledBounds.setTop(list->minYValue());
340 }
341 if (list->maxXValue() > _unscaledBounds.right())
342 {
343 _unscaledBounds.setRight(list->maxXValue());
344 }
345 if (list->maxYValue() > _unscaledBounds.bottom())
346 {
347 _unscaledBounds.setBottom(list->maxYValue());
348 }
349 if (_startDate > list->getStartDate())
350 {
351 _startDate = list->getStartDate();
352 }
353 }
354 else
355 {
356 _unscaledBounds.setRect(list->minXValue(), list->minYValue(),
357 list->maxXValue() - list->minXValue(),
358 list->maxYValue() - list->minYValue());
359 _startDate = list->getStartDate();
360 }
361}
362
370{
371 _xAxis->setPos(_bounds.left(), getXAxisOffset());
372 _yAxis->setPos(getYAxisOffset(), _bounds.bottom());
373 _xAxis->setLength(_bounds.width());
374 _yAxis->setLength(_bounds.height());
375
376 _xLabel->setPos(_bounds.left() + _bounds.width() / 2,
377 _bounds.bottom() + 1.5 * MARGIN);
378 _yLabel->setPos(_bounds.left() - 1.5 * MARGIN,
379 _bounds.top() + _bounds.height() / 2);
380
381 _xUnit->setPos(_bounds.right(), _bounds.bottom() + 1.2 * MARGIN);
382 _yUnit->setPos(_bounds.left(), _bounds.top() - 0.5 * MARGIN);
383
384 /* update graphs and their captions */
385 QRectF rect;
386 for (int i = 0; i < _graphs.size(); i++)
387 {
388 rect = _graphs[i]->boundingRect();
389 auto offset = static_cast<int>(fabs(rect.bottom() - _bounds.bottom()) -
390 fabs(rect.top() - _bounds.top()));
391 _graphs[i]->setPos(0, offset);
392
393 rect = itemsBoundingRect();
394 _graphCaptions[i]->setPos(_bounds.left(), rect.bottom() + 10);
395 }
396}
static constexpr float kDefaultX
static constexpr float kDefaultY
A List of data points and all the necessary meta-information to draw a graph.
Definition DiagramList.h:18
float minYValue() const
Returns the minimum y-value.
Definition DiagramList.h:37
float maxXValue() const
Returns the maximum x-value.
Definition DiagramList.h:34
QString getYUnit() const
Returns the unit associated with the y-axis.
Definition DiagramList.h:76
const QDateTime getStartDate() const
Returns the start date of this list.
Definition DiagramList.h:43
QString getXLabel() const
Returns the label associated with the x-axis.
Definition DiagramList.h:67
float maxYValue() const
Returns the maximum y-value.
Definition DiagramList.h:40
QString getXUnit() const
Returns the unit associated with the x-axis.
Definition DiagramList.h:73
QString getName() const
Returns the name of the diagram.
Definition DiagramList.h:46
float minXValue() const
Returns the minimum x-value.
Definition DiagramList.h:31
QColor getColor() const
Returns the colour of the graph.
Definition DiagramList.h:25
QString getYLabel() const
Returns the label associated with the y-axis.
Definition DiagramList.h:70
bool getPath(QPainterPath &path, float scaleX, float scaleY)
DiagramScene(QObject *parent=nullptr)
QDateTime _startDate
void drawGraph(DiagramList *list)
Plots the graph.
QVector< QNonScalableGraphicsTextItem * > _xTicksText
~DiagramScene() override
void adjustAxis(qreal &min, qreal &max, int &numberOfTicks)
QNonScalableGraphicsTextItem * _xUnit
QArrow * _xAxis
QVector< QGraphicsPathItem * > _graphs
QVector< QGraphicsItemGroup * > _graphCaptions
QVector< QNonScalableGraphicsTextItem * > _yTicksText
void addGraph(DiagramList *list)
Adds a graph to the scene, including all data points and meta-information.
QArrow * addArrow(qreal length, qreal angle, QPen &pen)
void addCaption(const QString &name, QPen &pen)
The margin between the boundary of the scene and the bounding box of all items within the scene.
QNonScalableGraphicsTextItem * _yUnit
QGraphicsGrid * addGrid(const QRectF &rect, int xTicks, int yTicks, const QPen &pen)
Adds a grid-object to the scene.
void setDiagramBoundaries(DiagramList *list)
QNonScalableGraphicsTextItem * _yLabel
QNonScalableGraphicsTextItem * _xLabel
QNonScalableGraphicsTextItem * addNonScalableText(const QString &text, const QFont &font=QFont())
Adds a non-scalable text object to the scene.
static const int MARGIN
void setYAxis(QArrow *arrow)
Sets an arrow as y-axis.
QVector< DiagramList * > _lists
QGraphicsGrid * _grid
void clearGrid()
Destroys the grid object (coordinate system) when a new graph is added.
QArrow * _yAxis
QRectF _unscaledBounds
void setXAxis(QArrow *arrow)
Sets an arrow as x-axis.
An arrow as a QGraphicsObject.
Definition QArrow.h:15
A 2D cartesian grid as a QGraphicsItem.
A QGraphicsTextItem that will ignore all geometric transformations.