OGS
DiagramScene.cpp
Go to the documentation of this file.
1
15#include "DiagramScene.h"
16
17#include <cmath>
18#include <limits>
19
20// default size of a new window
21static constexpr float kDefaultX = 500.0;
22static constexpr float kDefaultY = 300.0;
23
28DiagramScene::DiagramScene(QObject* parent) : QGraphicsScene(parent)
29{
30 _bounds.setRect(0, 0, 1, 1);
31 initialize();
32}
33
40 : QGraphicsScene(parent)
41{
43 initialize();
44}
45
47{
48 delete _grid;
49 delete _xAxis;
50 delete _yAxis;
51 delete _xLabel;
52 delete _yLabel;
53 delete _xUnit;
54 delete _yUnit;
55 for (auto& graphCaption : _graphCaptions)
56 {
57 delete graphCaption;
58 }
59 _graphCaptions.clear();
60 for (auto& graph : _graphs)
61 {
62 delete graph;
63 }
64 _graphs.clear();
65 for (auto& text : _xTicksText)
66 {
67 delete text;
68 }
69 _xTicksText.clear();
70 for (auto& text : _yTicksText)
71 {
72 delete text;
73 }
74 _yTicksText.clear();
75 for (auto& list : _lists)
76 {
77 delete list;
78 }
79 _lists.clear();
80}
81
84QArrow* DiagramScene::addArrow(qreal length, qreal angle, QPen& pen)
85{
86 auto* arrow = new QArrow(length, angle, 8, 5, pen);
87 addItem(arrow);
88 return arrow;
89}
90
92void DiagramScene::addCaption(const QString& name, QPen& pen)
93{
94 auto* caption = new QGraphicsItemGroup(nullptr);
95 QGraphicsLineItem* l = addLine(0, 0, 100, 0, pen);
96 QGraphicsTextItem* t = addText(name);
97 l->setPos(0, 0);
98 t->setPos(110, -(t->boundingRect()).height() / 2);
99 caption->addToGroup(l);
100 caption->addToGroup(t);
101 caption->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
102
103 _graphCaptions.push_back(caption);
104 addItem(_graphCaptions[_graphCaptions.size() - 1]);
105}
106
109{
112 _xLabel->setPlainText(list->getXLabel());
113 _yLabel->setPlainText(list->getYLabel());
114 _xUnit->setPlainText(list->getXUnit());
115 _yUnit->setPlainText(list->getYUnit());
116
117 clearGrid();
119
120 _lists.push_back(list);
121 for (auto& list : _lists)
122 {
123 drawGraph(list);
124 }
125
126 update();
127}
128
130QGraphicsGrid* DiagramScene::addGrid(const QRectF& rect, int xTicks, int yTicks,
131 const QPen& pen)
132{
133 QGraphicsGrid* g = new QGraphicsGrid(rect, xTicks, yTicks, true, pen);
134 addItem(g);
135 return g;
136}
137
140 const QString& text, const QFont& font)
141{
142 auto* item = new QNonScalableGraphicsTextItem(text);
143 item->setFont(font);
144 addItem(item);
145 return item;
146}
147
150void DiagramScene::adjustAxis(qreal& min, qreal& max, int& numberOfTicks)
151{
152 const int MinTicks = 4;
153 double grossStep = (max - min) / MinTicks;
154 double step = pow(10.0, std::floor(log10(grossStep)));
155 if (5 * step < grossStep)
156 {
157 step *= 5;
158 }
159 else if (2 * step < grossStep)
160 {
161 step *= 2;
162 }
163 numberOfTicks = int(ceil(max / step) - std::floor(min / step));
164 if (numberOfTicks < MinTicks)
165 {
166 numberOfTicks = MinTicks;
167 }
168 min = std::floor(min / step) * step;
169 max = ceil(max / step) * step;
170}
171
175{
176 if ((_unscaledBounds.width() > 0) && (_unscaledBounds.height() > 0))
177 {
178 _scaleX = kDefaultX / static_cast<float>(_unscaledBounds.width());
179 _scaleY = kDefaultY / static_cast<float>(_unscaledBounds.height());
180 }
181}
182
185{
186 if (!_lists.isEmpty())
187 {
188 removeItem(_grid);
189
190 for (auto& text : _xTicksText)
191 {
192 removeItem(text);
193 }
194 for (auto& text : _yTicksText)
195 {
196 removeItem(text);
197 }
198 for (auto& graph : _graphs)
199 {
200 removeItem(graph);
201 }
202 for (auto& graphCaption : _graphCaptions)
203 {
204 removeItem(graphCaption);
205 }
206
207 _xTicksText.clear();
208 _yTicksText.clear();
209 _graphs.clear();
210 _graphCaptions.clear();
211 }
212}
213
217{
218 // be very careful with scaling parameters here!
219 int numXTicks;
220 int numYTicks;
221 qreal xMin = _unscaledBounds.left();
222 qreal yMin = _unscaledBounds.top();
223 qreal xMax = _unscaledBounds.right();
224 qreal yMax = _unscaledBounds.bottom();
225
226 adjustAxis(xMin, xMax, numXTicks);
227 adjustAxis(yMin, yMax, numYTicks);
228
229 // adjust boundaries of coordinate system according to scaling
230 _bounds.setRect(xMin * _scaleX,
231 yMin * _scaleY,
232 (xMax - xMin) * _scaleX,
233 (yMax - yMin) * _scaleY);
234
235 QPen pen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
236 _grid = addGrid(_bounds, numXTicks, numYTicks, pen);
237
238 if (_startDate == QDateTime())
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 _xTicksText.push_back(addNonScalableText(QString::number(x)));
246 _xTicksText.last()->setPos(x * _scaleX, _bounds.bottom() + 15);
247 }
248 }
249 else
250 {
251 for (int i = 0; i <= numXTicks; ++i)
252 {
253 auto x =
254 static_cast<int>(_bounds.left() / _scaleX +
255 (i * (_bounds.width() / _scaleX) / numXTicks));
256 QDateTime currentDate = _startDate.addSecs(x);
257 _xTicksText.push_back(
258 addNonScalableText(currentDate.toString("dd.MM.yyyy")));
259 _xTicksText.last()->setPos(x * _scaleX, _bounds.bottom() + 15);
260 }
261 }
262
263 for (int j = 0; j <= numYTicks; ++j)
264 {
265 qreal y = _bounds.bottom() / _scaleY -
266 (j * (_bounds.height() / _scaleY) / numYTicks);
267 qreal label = _bounds.top() / _scaleY +
268 (j * (_bounds.height() / _scaleY) / numYTicks);
269 _yTicksText.push_back(addNonScalableText(QString::number(label)));
270 _yTicksText.last()->setPos(_bounds.left() - MARGIN / 2, y * _scaleY);
271 }
272}
273
276{
277 QPainterPath path;
278
279 if (list->getPath(path, _scaleX, _scaleY))
280 {
281 QPen pen(list->getColor(), 2, Qt::SolidLine, Qt::SquareCap,
282 Qt::RoundJoin);
283 pen.setCosmetic(true);
284 _graphs.push_back(addPath(path, pen));
285 addCaption(list->getName(), pen);
286
287 int last = _graphs.size() - 1;
288
293 int verticalShift =
294 static_cast<int>(2 * (list->minYValue() * _scaleY) +
295 (_graphs[last]->boundingRect()).height());
296 _graphs[last]->setTransform(
297 QTransform(QMatrix(1, 0, 0, -1, 0, verticalShift)));
298 }
299}
300
304{
305 return (_bounds.top() <= 0 && _bounds.bottom() > 0)
306 ? static_cast<int>(_bounds.bottom() + _bounds.top())
307 : static_cast<int>(_bounds.bottom());
308}
309
313{
314 return (_bounds.left() <= 0 && _bounds.right() > 0)
315 ? 0
316 : static_cast<int>(_bounds.left());
317}
318
322{
323 QPen pen(Qt::black, 1, Qt::SolidLine, Qt::SquareCap, Qt::RoundJoin);
324 pen.setCosmetic(true);
325
326 setXAxis(addArrow(_bounds.width(), 0, pen));
327 setYAxis(addArrow(_bounds.height(), -90, pen));
330 _yLabel->setRotation(-90);
331
334
335 update();
336}
337
341{
342 if (!_lists.isEmpty())
343 {
344 if (list->minXValue() < _unscaledBounds.left())
345 {
346 _unscaledBounds.setLeft(list->minXValue());
347 }
348 if (list->minYValue() < _unscaledBounds.top())
349 {
350 _unscaledBounds.setTop(list->minYValue());
351 }
352 if (list->maxXValue() > _unscaledBounds.right())
353 {
354 _unscaledBounds.setRight(list->maxXValue());
355 }
356 if (list->maxYValue() > _unscaledBounds.bottom())
357 {
358 _unscaledBounds.setBottom(list->maxYValue());
359 }
360 if (_startDate > list->getStartDate())
361 {
362 _startDate = list->getStartDate();
363 }
364 }
365 else
366 {
367 _unscaledBounds.setRect(list->minXValue(), list->minYValue(),
368 list->maxXValue() - list->minXValue(),
369 list->maxYValue() - list->minYValue());
370 _startDate = list->getStartDate();
371 }
372}
373
381{
382 _xAxis->setPos(_bounds.left(), getXAxisOffset());
383 _yAxis->setPos(getYAxisOffset(), _bounds.bottom());
384 _xAxis->setLength(_bounds.width());
385 _yAxis->setLength(_bounds.height());
386
387 _xLabel->setPos(_bounds.left() + _bounds.width() / 2,
388 _bounds.bottom() + 1.5 * MARGIN);
389 _yLabel->setPos(_bounds.left() - 1.5 * MARGIN,
390 _bounds.top() + _bounds.height() / 2);
391
392 _xUnit->setPos(_bounds.right(), _bounds.bottom() + 1.2 * MARGIN);
393 _yUnit->setPos(_bounds.left(), _bounds.top() - 0.5 * MARGIN);
394
395 /* update graphs and their captions */
396 QRectF rect;
397 for (int i = 0; i < _graphs.size(); i++)
398 {
399 rect = _graphs[i]->boundingRect();
400 auto offset = static_cast<int>(fabs(rect.bottom() - _bounds.bottom()) -
401 fabs(rect.top() - _bounds.top()));
402 _graphs[i]->setPos(0, offset);
403
404 rect = itemsBoundingRect();
405 _graphCaptions[i]->setPos(_bounds.left(), rect.bottom() + 10);
406 }
407}
static constexpr float kDefaultX
static constexpr float kDefaultY
Definition of the DiagramScene class.
A List of data points and all the necessary meta-information to draw a graph.
Definition DiagramList.h:29
float minYValue() const
Returns the minimum y-value.
Definition DiagramList.h:48
float maxXValue() const
Returns the maximum x-value.
Definition DiagramList.h:45
QString getYUnit() const
Returns the unit associated with the y-axis.
Definition DiagramList.h:87
const QDateTime getStartDate() const
Returns the start date of this list.
Definition DiagramList.h:54
QString getXLabel() const
Returns the label associated with the x-axis.
Definition DiagramList.h:78
float maxYValue() const
Returns the maximum y-value.
Definition DiagramList.h:51
QString getXUnit() const
Returns the unit associated with the x-axis.
Definition DiagramList.h:84
QString getName() const
Returns the name of the diagram.
Definition DiagramList.h:57
float minXValue() const
Returns the minimum x-value.
Definition DiagramList.h:42
QColor getColor() const
Returns the colour of the graph.
Definition DiagramList.h:36
QString getYLabel() const
Returns the label associated with the y-axis.
Definition DiagramList.h:81
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:26
void setLength(qreal l)
Changes the length of the arrow.
Definition QArrow.cpp:130
A 2D cartesian grid as a QGraphicsItem.
A QGraphicsTextItem that will ignore all geometric transformations.
QRectF boundingRect() const override
Returns the bounding rectangle of the text item.