1 #include <QSet>
2 #include <QGraphicsScene>
3 #include <QEvent>
4 #include <QMouseEvent>
5 #include <QScrollBar>
6 #include <QGraphicsSimpleTextItem>
7 #include <QPalette>
8 #include <QLocale>
9 #include "data/graph.h"
10 #include "opengl.h"
11 #include "axisitem.h"
12 #include "slideritem.h"
13 #include "sliderinfoitem.h"
14 #include "infoitem.h"
15 #include "griditem.h"
16 #include "graphitem.h"
17 #include "pathitem.h"
18 #include "format.h"
19 #include "graphicsscene.h"
20 #include "graphview.h"
21
22
23 #define MARGIN 10.0
24
GraphView(QWidget * parent)25 GraphView::GraphView(QWidget *parent)
26 : QGraphicsView(parent)
27 {
28 _scene = new GraphicsScene(this);
29 setScene(_scene);
30
31 setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
32 setRenderHint(QPainter::Antialiasing, true);
33 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
34 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
35 setBackgroundBrush(QBrush(palette().brush(QPalette::Base)));
36
37 _xAxis = new AxisItem(AxisItem::X);
38 _xAxis->setZValue(1.0);
39 _yAxis = new AxisItem(AxisItem::Y);
40 _yAxis->setZValue(1.0);
41 _slider = new SliderItem();
42 _slider->setZValue(4.0);
43 _sliderInfo = new SliderInfoItem(_slider);
44 _sliderInfo->setZValue(4.0);
45 _info = new InfoItem();
46 _grid = new GridItem();
47 _message = new QGraphicsSimpleTextItem(tr("Data not available"));
48 _message->setBrush(QPalette().brush(QPalette::Disabled,
49 QPalette::WindowText));
50
51 connect(_slider, SIGNAL(positionChanged(const QPointF&)), this,
52 SLOT(emitSliderPositionChanged(const QPointF&)));
53
54 _width = 1;
55
56 _xScale = 1;
57 _yScale = 1;
58 _yOffset = 0;
59
60 _precision = 0;
61 _minYRange = 0.01;
62
63 _sliderPos = 0;
64
65 _units = Metric;
66 _graphType = Distance;
67 _xLabel = tr("Distance");
68
69 _zoom = 1.0;
70 }
71
~GraphView()72 GraphView::~GraphView()
73 {
74 delete _xAxis;
75 delete _yAxis;
76 delete _slider;
77 delete _info;
78 delete _grid;
79 delete _message;
80 }
81
createXLabel()82 void GraphView::createXLabel()
83 {
84 _xAxis->setLabel(QString("%1 [%2]").arg(_xLabel,
85 _xUnits.isEmpty() ? "-" : _xUnits));
86 }
87
createYLabel()88 void GraphView::createYLabel()
89 {
90 _yAxis->setLabel(QString("%1 [%2]").arg(_yLabel,
91 _yUnits.isEmpty() ? "-" : _yUnits));
92 }
93
setYLabel(const QString & label)94 void GraphView::setYLabel(const QString &label)
95 {
96 _yLabel = label;
97 createYLabel();
98 }
99
setYUnits(const QString & units)100 void GraphView::setYUnits(const QString &units)
101 {
102 _yUnits = units;
103 createYLabel();
104 }
105
setXUnits()106 void GraphView::setXUnits()
107 {
108 if (_graphType == Distance) {
109 if (_units == Imperial) {
110 if (bounds().width() < MIINM) {
111 _xUnits = tr("ft");
112 _xScale = M2FT;
113 } else {
114 _xUnits = tr("mi");
115 _xScale = M2MI;
116 }
117 } else if (_units == Nautical) {
118 if (bounds().width() < NMIINM) {
119 _xUnits = tr("ft");
120 _xScale = M2FT;
121 } else {
122 _xUnits = tr("nmi");
123 _xScale = M2NMI;
124 }
125 } else {
126 if (bounds().width() < KMINM) {
127 _xUnits = tr("m");
128 _xScale = 1;
129 } else {
130 _xUnits = tr("km");
131 _xScale = M2KM;
132 }
133 }
134 } else {
135 if (bounds().width() < MININS) {
136 _xUnits = tr("s");
137 _xScale = 1;
138 } else if (bounds().width() < HINS) {
139 _xUnits = tr("min");
140 _xScale = MIN2S;
141 } else {
142 _xUnits = tr("h");
143 _xScale = H2S;
144 }
145 }
146
147 createXLabel();
148 }
149
setUnits(Units units)150 void GraphView::setUnits(Units units)
151 {
152 _units = units;
153
154 for (int i = 0; i < _graphs.count(); i++)
155 _graphs.at(i)->setUnits(units);
156
157 setXUnits();
158
159 redraw();
160 }
161
setGraphType(GraphType type)162 void GraphView::setGraphType(GraphType type)
163 {
164 _graphType = type;
165 _bounds = QRectF();
166
167 for (int i = 0; i < _graphs.count(); i++) {
168 GraphItem *gi = _graphs.at(i);
169 gi->setGraphType(type);
170 if (gi->bounds().isNull())
171 removeItem(gi);
172 else
173 addItem(gi);
174 _bounds |= gi->bounds();
175 }
176
177 if (type == Distance)
178 _xLabel = tr("Distance");
179 else
180 _xLabel = tr("Time");
181 setXUnits();
182
183 redraw();
184 }
185
showGrid(bool show)186 void GraphView::showGrid(bool show)
187 {
188 _grid->setVisible(show);
189 }
190
showSliderInfo(bool show)191 void GraphView::showSliderInfo(bool show)
192 {
193 _sliderInfo->setVisible(show);
194 }
195
addGraph(GraphItem * graph)196 void GraphView::addGraph(GraphItem *graph)
197 {
198 connect(this, SIGNAL(sliderPositionChanged(qreal)), graph,
199 SLOT(emitSliderPositionChanged(qreal)));
200
201 _graphs.append(graph);
202 if (!graph->bounds().isNull())
203 _scene->addItem(graph);
204 _bounds |= graph->bounds();
205
206 setXUnits();
207 }
208
removeGraph(GraphItem * graph)209 void GraphView::removeGraph(GraphItem *graph)
210 {
211 disconnect(this, SIGNAL(sliderPositionChanged(qreal)), graph,
212 SLOT(emitSliderPositionChanged(qreal)));
213
214 _graphs.removeOne(graph);
215 _scene->removeItem(graph);
216
217 _bounds = QRectF();
218 for (int i = 0; i < _graphs.count(); i++)
219 _bounds |= _graphs.at(i)->bounds();
220
221 setXUnits();
222 }
223
removeItem(QGraphicsItem * item)224 void GraphView::removeItem(QGraphicsItem *item)
225 {
226 if (item->scene() == _scene)
227 _scene->removeItem(item);
228 }
229
addItem(QGraphicsItem * item)230 void GraphView::addItem(QGraphicsItem *item)
231 {
232 if (item->scene() != _scene)
233 _scene->addItem(item);
234 }
235
bounds() const236 QRectF GraphView::bounds() const
237 {
238 QRectF br(_bounds);
239 br.moveTopLeft(QPointF(br.left(), -br.top() - br.height()));
240 return br;
241 }
242
redraw()243 void GraphView::redraw()
244 {
245 if (!_graphs.isEmpty())
246 redraw(viewport()->size() - QSizeF(MARGIN, MARGIN));
247 }
248
redraw(const QSizeF & size)249 void GraphView::redraw(const QSizeF &size)
250 {
251 QRectF r;
252 QSizeF mx, my;
253 RangeF rx, ry;
254 qreal sx, sy;
255
256
257 if (_bounds.isNull()) {
258 removeItem(_xAxis);
259 removeItem(_yAxis);
260 removeItem(_slider);
261 removeItem(_info);
262 removeItem(_grid);
263 addItem(_message);
264 _scene->setSceneRect(_scene->itemsBoundingRect());
265 return;
266 }
267
268 removeItem(_message);
269 addItem(_xAxis);
270 addItem(_yAxis);
271 addItem(_slider);
272 addItem(_info);
273 addItem(_grid);
274
275 rx = RangeF(bounds().left() * _xScale, bounds().right() * _xScale);
276 ry = RangeF(bounds().top() * _yScale + _yOffset, bounds().bottom() * _yScale
277 + _yOffset);
278 if (ry.size() < _minYRange * _yScale)
279 ry.resize(_minYRange * _yScale);
280
281 _xAxis->setRange(rx);
282 _yAxis->setRange(ry);
283 mx = _xAxis->margin();
284 my = _yAxis->margin();
285
286 r = _bounds;
287 if (r.height() < _minYRange)
288 r.adjust(0, -(_minYRange/2 - r.height()/2), 0,
289 _minYRange/2 - r.height()/2);
290
291 sx = (size.width() - (my.width() + mx.width())) / r.width();
292 sy = (size.height() - (mx.height() + my.height())
293 - _info->boundingRect().height()) / r.height();
294 sx *= _zoom;
295
296 for (int i = 0; i < _graphs.size(); i++)
297 _graphs.at(i)->setScale(sx, sy);
298
299 QPointF p(r.left() * sx, r.top() * sy);
300 QSizeF s(r.width() * sx, r.height() * sy);
301 r = QRectF(p, s);
302 if (r.height() < _minYRange * sy)
303 r.adjust(0, -(_minYRange/2 * sy - r.height()/2), 0,
304 (_minYRange/2) * sy - r.height()/2);
305 r = r.toRect();
306
307 _xAxis->setSize(r.width());
308 _yAxis->setSize(r.height());
309 _xAxis->setPos(r.bottomLeft());
310 _yAxis->setPos(r.bottomLeft());
311
312 _grid->setSize(r.size());
313 _grid->setTicks(_xAxis->ticks(), _yAxis->ticks());
314 _grid->setPos(r.bottomLeft());
315
316 _slider->setArea(r);
317 updateSliderPosition();
318
319 r |= _xAxis->sceneBoundingRect();
320 r |= _yAxis->sceneBoundingRect();
321 _info->setPos(r.topLeft() + QPointF(r.width()/2
322 - _info->boundingRect().width()/2, -_info->boundingRect().height()));
323
324 _scene->setSceneRect(_scene->itemsBoundingRect());
325 }
326
resizeEvent(QResizeEvent * e)327 void GraphView::resizeEvent(QResizeEvent *e)
328 {
329 redraw(e->size() - QSizeF(MARGIN, MARGIN));
330
331 QGraphicsView::resizeEvent(e);
332 }
333
mousePressEvent(QMouseEvent * e)334 void GraphView::mousePressEvent(QMouseEvent *e)
335 {
336 if (e->button() == Qt::LeftButton)
337 newSliderPosition(mapToScene(e->pos()));
338
339 QGraphicsView::mousePressEvent(e);
340 }
341
wheelEvent(QWheelEvent * e)342 void GraphView::wheelEvent(QWheelEvent *e)
343 {
344 static int deg = 0;
345
346 deg += e->delta() / 8;
347 if (qAbs(deg) < 15)
348 return;
349 deg = 0;
350
351 QPointF pos = mapToScene(e->pos());
352 QRectF gr(_grid->boundingRect());
353 QPointF r(pos.x() / gr.width(), pos.y() / gr.height());
354
355 _zoom = (e->delta() > 0) ? _zoom * 1.25 : qMax(_zoom / 1.25, 1.0);
356 redraw();
357
358 QRectF ngr(_grid->boundingRect());
359 QPointF npos(mapFromScene(QPointF(r.x() * ngr.width(),
360 r.y() * ngr.height())));
361 QScrollBar *sb = horizontalScrollBar();
362 sb->setSliderPosition(sb->sliderPosition() + npos.x() - e->pos().x());
363
364 QGraphicsView::wheelEvent(e);
365 }
366
paintEvent(QPaintEvent * e)367 void GraphView::paintEvent(QPaintEvent *e)
368 {
369 QRectF viewRect(mapToScene(rect()).boundingRect());
370 _info->setPos(QPointF(viewRect.left() + (viewRect.width()
371 - _info->boundingRect().width())/2.0, _info->pos().y()));
372
373 QGraphicsView::paintEvent(e);
374 }
375
plot(QPainter * painter,const QRectF & target,qreal scale)376 void GraphView::plot(QPainter *painter, const QRectF &target, qreal scale)
377 {
378 QSizeF canvas = QSizeF(target.width() / scale, target.height() / scale);
379
380 setUpdatesEnabled(false);
381 redraw(canvas);
382 if (_slider->pos().x() == _slider->area().left())
383 _slider->hide();
384 _scene->render(painter, target);
385 _slider->show();
386 redraw();
387 setUpdatesEnabled(true);
388 }
389
clear()390 void GraphView::clear()
391 {
392 _graphs.clear();
393
394 _slider->clear();
395 _info->clear();
396
397 _palette.reset();
398
399 _bounds = QRectF();
400 _sliderPos = 0;
401 _zoom = 1.0;
402
403 _scene->setSceneRect(0, 0, 0, 0);
404 }
405
updateSliderPosition()406 void GraphView::updateSliderPosition()
407 {
408 if (_sliderPos <= bounds().right() && _sliderPos >= bounds().left()) {
409 _slider->setPos((_sliderPos / bounds().width())
410 * _slider->area().width(), _slider->area().bottom());
411 _slider->setVisible(true);
412 updateSliderInfo();
413 } else {
414 _slider->setPos(_slider->area().left(), _slider->area().bottom());
415 _slider->setVisible(false);
416 }
417 }
418
updateSliderInfo()419 void GraphView::updateSliderInfo()
420 {
421 QLocale l(QLocale::system());
422 qreal r = 0, y = 0;
423 GraphItem *cardinal = (_graphs.count() == 1 || (_graphs.count() == 2
424 && _graphs.first()->secondaryGraph())) ? _graphs.first() : 0;
425
426 if (cardinal) {
427 QRectF br(_bounds);
428 if (br.height() < _minYRange)
429 br.adjust(0, -(_minYRange/2 - br.height()/2), 0,
430 _minYRange/2 - br.height()/2);
431
432 y = cardinal->yAtX(_sliderPos);
433 r = (y - br.bottom()) / br.height();
434 }
435
436 qreal pos = (_sliderPos / bounds().width()) * _slider->area().width();
437 SliderInfoItem::Side s = (pos + _sliderInfo->boundingRect().width()
438 > _slider->area().right()) ? SliderInfoItem::Left : SliderInfoItem::Right;
439
440 _sliderInfo->setSide(s);
441 _sliderInfo->setPos(QPointF(0, _slider->boundingRect().height() * r));
442 QString xText(_graphType == Time ? Format::timeSpan(_sliderPos,
443 bounds().width() > 3600) : l.toString(_sliderPos * _xScale, 'f', 1)
444 + UNIT_SPACE + _xUnits);
445 QString yText((!cardinal) ? QString() : l.toString(-y * _yScale + _yOffset,
446 'f', _precision) + UNIT_SPACE + _yUnits);
447 if (cardinal && cardinal->secondaryGraph()) {
448 qreal delta = y - cardinal->secondaryGraph()->yAtX(_sliderPos);
449 yText += " " + QChar(0x0394) + l.toString(-delta * _yScale + _yOffset,
450 'f', _precision) + UNIT_SPACE + _yUnits;
451 }
452 _sliderInfo->setText(xText, yText);
453 }
454
emitSliderPositionChanged(const QPointF & pos)455 void GraphView::emitSliderPositionChanged(const QPointF &pos)
456 {
457 if (_slider->area().width() <= 0)
458 return;
459
460 _sliderPos = (pos.x() / _slider->area().width()) * bounds().width();
461 _sliderPos = qMax(_sliderPos, bounds().left());
462 _sliderPos = qMin(_sliderPos, bounds().right());
463 updateSliderPosition();
464
465 emit sliderPositionChanged(_sliderPos);
466 }
467
setSliderPosition(qreal pos)468 void GraphView::setSliderPosition(qreal pos)
469 {
470 if (_graphs.isEmpty())
471 return;
472
473 _sliderPos = pos;
474 updateSliderPosition();
475 }
476
newSliderPosition(const QPointF & pos)477 void GraphView::newSliderPosition(const QPointF &pos)
478 {
479 if (_slider->area().contains(pos))
480 _slider->setPos(pos);
481 }
482
addInfo(const QString & key,const QString & value)483 void GraphView::addInfo(const QString &key, const QString &value)
484 {
485 _info->insert(key, value);
486 }
487
clearInfo()488 void GraphView::clearInfo()
489 {
490 _info->clear();
491 }
492
setPalette(const Palette & palette)493 void GraphView::setPalette(const Palette &palette)
494 {
495 _palette = palette;
496 _palette.reset();
497
498 QSet<GraphItem*> secondary;
499 for (int i = 0; i < _graphs.count(); i++) {
500 GraphItem *g = _graphs[i];
501 if (g->secondaryGraph())
502 secondary.insert(g->secondaryGraph());
503 }
504
505 for (int i = 0; i < _graphs.count(); i++) {
506 GraphItem *g = _graphs[i];
507 if (secondary.contains(g))
508 continue;
509
510 QColor color(_palette.nextColor());
511 g->setColor(color);
512 if (g->secondaryGraph())
513 g->secondaryGraph()->setColor(color);
514 }
515 }
516
setGraphWidth(int width)517 void GraphView::setGraphWidth(int width)
518 {
519 _width = width;
520
521 for (int i = 0; i < _graphs.count(); i++)
522 _graphs.at(i)->setWidth(width);
523
524 redraw();
525 }
526
useOpenGL(bool use)527 void GraphView::useOpenGL(bool use)
528 {
529 if (use)
530 setViewport(new OPENGL_WIDGET);
531 else
532 setViewport(new QWidget);
533 }
534
useAntiAliasing(bool use)535 void GraphView::useAntiAliasing(bool use)
536 {
537 setRenderHint(QPainter::Antialiasing, use);
538 }
539
setSliderColor(const QColor & color)540 void GraphView::setSliderColor(const QColor &color)
541 {
542 _slider->setColor(color);
543 _sliderInfo->setColor(color);
544 }
545
changeEvent(QEvent * e)546 void GraphView::changeEvent(QEvent *e)
547 {
548 if (e->type() == QEvent::PaletteChange) {
549 _message->setBrush(QPalette().brush(QPalette::Disabled,
550 QPalette::WindowText));
551 setBackgroundBrush(QBrush(palette().brush(QPalette::Base)));
552 }
553
554 QGraphicsView::changeEvent(e);
555 }
556