2016-09-19 00:56:10 +02:00
|
|
|
#include <QPainter>
|
2019-10-13 20:20:32 +02:00
|
|
|
#include <QGraphicsSceneMouseEvent>
|
|
|
|
#include "popup.h"
|
2016-08-19 19:48:44 +02:00
|
|
|
#include "graphitem.h"
|
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
|
2019-08-25 10:54:25 +02:00
|
|
|
GraphItem::GraphItem(const Graph &graph, GraphType type, int width,
|
2020-03-25 23:08:26 +01:00
|
|
|
const QColor &color, Qt::PenStyle style, QGraphicsItem *parent)
|
|
|
|
: GraphicsItem(parent), _graph(graph), _type(type), _secondaryGraph(0)
|
2016-09-19 00:56:10 +02:00
|
|
|
{
|
2019-02-11 23:28:08 +01:00
|
|
|
Q_ASSERT(_graph.isValid());
|
|
|
|
|
2019-10-13 20:20:32 +02:00
|
|
|
_units = Metric;
|
2020-11-23 22:17:19 +01:00
|
|
|
_pen = QPen(color, width, style, Qt::FlatCap);
|
2019-08-25 10:54:25 +02:00
|
|
|
_sx = 0; _sy = 0;
|
2019-02-11 23:28:08 +01:00
|
|
|
_time = _graph.hasTime();
|
|
|
|
setZValue(2.0);
|
2019-08-25 10:54:25 +02:00
|
|
|
setAcceptHoverEvents(true);
|
2016-12-06 01:48:26 +01:00
|
|
|
|
2016-10-12 20:38:18 +02:00
|
|
|
updateBounds();
|
2017-09-24 19:54:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GraphItem::updateShape()
|
|
|
|
{
|
|
|
|
QPainterPathStroker s;
|
2019-08-25 10:54:25 +02:00
|
|
|
s.setWidth(_pen.width() + 1);
|
2017-09-24 19:54:13 +02:00
|
|
|
_shape = s.createStroke(_path);
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GraphItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
|
|
|
|
QWidget *widget)
|
|
|
|
{
|
|
|
|
Q_UNUSED(option);
|
|
|
|
Q_UNUSED(widget);
|
|
|
|
|
|
|
|
painter->setPen(_pen);
|
2016-10-12 20:38:18 +02:00
|
|
|
painter->drawPath(_path);
|
2016-09-22 20:59:13 +02:00
|
|
|
|
|
|
|
/*
|
|
|
|
QPen p = QPen(QBrush(Qt::red), 0);
|
|
|
|
painter->setPen(p);
|
|
|
|
painter->drawRect(boundingRect());
|
|
|
|
*/
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
2016-09-25 18:08:39 +02:00
|
|
|
void GraphItem::setGraphType(GraphType type)
|
|
|
|
{
|
2017-10-04 23:15:39 +02:00
|
|
|
if (type == _type)
|
|
|
|
return;
|
|
|
|
|
2016-09-25 18:08:39 +02:00
|
|
|
prepareGeometryChange();
|
2016-10-12 20:38:18 +02:00
|
|
|
|
2016-09-25 18:08:39 +02:00
|
|
|
_type = type;
|
2016-10-12 20:38:18 +02:00
|
|
|
updatePath();
|
|
|
|
updateBounds();
|
2016-09-25 18:08:39 +02:00
|
|
|
}
|
|
|
|
|
2016-08-19 19:48:44 +02:00
|
|
|
void GraphItem::setColor(const QColor &color)
|
|
|
|
{
|
2017-10-04 23:15:39 +02:00
|
|
|
if (_pen.color() == color)
|
|
|
|
return;
|
|
|
|
|
2016-09-22 20:59:13 +02:00
|
|
|
_pen.setColor(color);
|
|
|
|
update();
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
2016-12-06 01:48:26 +01:00
|
|
|
void GraphItem::setWidth(int width)
|
|
|
|
{
|
2019-08-25 10:54:25 +02:00
|
|
|
if (width == _pen.width())
|
2017-10-04 23:15:39 +02:00
|
|
|
return;
|
|
|
|
|
2016-12-06 01:48:26 +01:00
|
|
|
prepareGeometryChange();
|
|
|
|
|
|
|
|
_pen.setWidth(width);
|
2017-09-24 19:54:13 +02:00
|
|
|
|
|
|
|
updateShape();
|
2016-12-06 01:48:26 +01:00
|
|
|
}
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
const GraphSegment *GraphItem::segment(qreal x, GraphType type) const
|
|
|
|
{
|
|
|
|
for (int i = 0; i < _graph.size(); i++)
|
|
|
|
if (x <= _graph.at(i).last().x(type))
|
|
|
|
return &(_graph.at(i));
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2021-01-17 16:02:37 +01:00
|
|
|
qreal GraphItem::yAtX(qreal x) const
|
2016-09-19 00:56:10 +02:00
|
|
|
{
|
2019-02-11 23:28:08 +01:00
|
|
|
const GraphSegment *seg = segment(x, _type);
|
|
|
|
if (!seg)
|
|
|
|
return NAN;
|
|
|
|
|
2016-10-12 20:38:18 +02:00
|
|
|
int low = 0;
|
2019-02-11 23:28:08 +01:00
|
|
|
int high = seg->count() - 1;
|
2016-10-12 20:38:18 +02:00
|
|
|
int mid = 0;
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
if (!(x >= seg->at(low).x(_type) && x <= seg->at(high).x(_type)))
|
|
|
|
return NAN;
|
2016-10-12 20:38:18 +02:00
|
|
|
|
|
|
|
while (low <= high) {
|
|
|
|
mid = low + ((high - low) / 2);
|
2019-02-11 23:28:08 +01:00
|
|
|
const GraphPoint &p = seg->at(mid);
|
2016-10-12 20:38:18 +02:00
|
|
|
if (p.x(_type) > x)
|
|
|
|
high = mid - 1;
|
|
|
|
else if (p.x(_type) < x)
|
|
|
|
low = mid + 1;
|
|
|
|
else
|
2021-01-17 16:02:37 +01:00
|
|
|
return p.y();
|
2016-10-12 20:38:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
QLineF l;
|
2019-02-11 23:28:08 +01:00
|
|
|
if (seg->at(mid).x(_type) < x)
|
|
|
|
l = QLineF(seg->at(mid).x(_type), seg->at(mid).y(),
|
|
|
|
seg->at(mid+1).x(_type), seg->at(mid+1).y());
|
2016-10-12 20:38:18 +02:00
|
|
|
else
|
2019-02-11 23:28:08 +01:00
|
|
|
l = QLineF(seg->at(mid-1).x(_type), seg->at(mid-1).y(),
|
|
|
|
seg->at(mid).x(_type), seg->at(mid).y());
|
2016-10-12 20:38:18 +02:00
|
|
|
|
2021-01-17 16:02:37 +01:00
|
|
|
return l.pointAt((x - l.p1().x()) / (l.p2().x() - l.p1().x())).y();
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
2021-01-17 16:02:37 +01:00
|
|
|
qreal GraphItem::distanceAtTime(qreal time) const
|
2016-09-19 00:56:10 +02:00
|
|
|
{
|
2021-01-17 16:02:37 +01:00
|
|
|
if (!_time)
|
|
|
|
return NAN;
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
const GraphSegment *seg = segment(time, Time);
|
|
|
|
if (!seg)
|
|
|
|
return NAN;
|
|
|
|
|
2016-09-19 00:56:10 +02:00
|
|
|
int low = 0;
|
2019-02-11 23:28:08 +01:00
|
|
|
int high = seg->count() - 1;
|
2016-09-19 00:56:10 +02:00
|
|
|
int mid = 0;
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
if (!(time >= seg->at(low).t() && time <= seg->at(high).t()))
|
|
|
|
return NAN;
|
2016-09-19 00:56:10 +02:00
|
|
|
|
|
|
|
while (low <= high) {
|
|
|
|
mid = low + ((high - low) / 2);
|
2019-02-11 23:28:08 +01:00
|
|
|
const GraphPoint &p = seg->at(mid);
|
2016-10-12 20:38:18 +02:00
|
|
|
if (p.t() > time)
|
2016-09-19 00:56:10 +02:00
|
|
|
high = mid - 1;
|
2016-10-12 20:38:18 +02:00
|
|
|
else if (p.t() < time)
|
2016-09-19 00:56:10 +02:00
|
|
|
low = mid + 1;
|
|
|
|
else
|
2019-02-11 23:28:08 +01:00
|
|
|
return seg->at(mid).s();
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
2016-09-27 01:48:45 +02:00
|
|
|
QLineF l;
|
2019-02-11 23:28:08 +01:00
|
|
|
if (seg->at(mid).t() < time)
|
|
|
|
l = QLineF(seg->at(mid).t(), seg->at(mid).s(), seg->at(mid+1).t(),
|
|
|
|
seg->at(mid+1).s());
|
2016-09-19 00:56:10 +02:00
|
|
|
else
|
2019-02-11 23:28:08 +01:00
|
|
|
l = QLineF(seg->at(mid-1).t(), seg->at(mid-1).s(),
|
|
|
|
seg->at(mid).t(), seg->at(mid).s());
|
2016-09-27 01:48:45 +02:00
|
|
|
|
|
|
|
return l.pointAt((time - l.p1().x()) / (l.p2().x() - l.p1().x())).y();
|
2016-09-19 00:56:10 +02:00
|
|
|
}
|
|
|
|
|
2021-01-17 16:02:37 +01:00
|
|
|
qreal GraphItem::timeAtDistance(qreal distance) const
|
2016-09-19 00:56:10 +02:00
|
|
|
{
|
2021-01-17 16:02:37 +01:00
|
|
|
if (!_time)
|
|
|
|
return NAN;
|
|
|
|
|
|
|
|
const GraphSegment *seg = segment(distance, Distance);
|
|
|
|
if (!seg)
|
|
|
|
return NAN;
|
|
|
|
|
|
|
|
int low = 0;
|
|
|
|
int high = seg->count() - 1;
|
|
|
|
int mid = 0;
|
|
|
|
|
|
|
|
if (!(distance >= seg->at(low).s() && distance <= seg->at(high).s()))
|
|
|
|
return NAN;
|
|
|
|
|
|
|
|
while (low <= high) {
|
|
|
|
mid = low + ((high - low) / 2);
|
|
|
|
const GraphPoint &p = seg->at(mid);
|
|
|
|
if (p.s() > distance)
|
|
|
|
high = mid - 1;
|
|
|
|
else if (p.s() < distance)
|
|
|
|
low = mid + 1;
|
|
|
|
else
|
|
|
|
return seg->at(mid).t();
|
|
|
|
}
|
|
|
|
|
|
|
|
QLineF l;
|
|
|
|
if (seg->at(mid).s() < distance)
|
|
|
|
l = QLineF(seg->at(mid).s(), seg->at(mid).t(), seg->at(mid+1).s(),
|
|
|
|
seg->at(mid+1).t());
|
2019-02-11 23:28:08 +01:00
|
|
|
else
|
2021-01-17 16:02:37 +01:00
|
|
|
l = QLineF(seg->at(mid-1).s(), seg->at(mid-1).t(),
|
|
|
|
seg->at(mid).s(), seg->at(mid).t());
|
|
|
|
|
|
|
|
return l.pointAt((distance - l.p1().x()) / (l.p2().x() - l.p1().x())).y();
|
2016-08-19 19:48:44 +02:00
|
|
|
}
|
2016-09-22 20:59:13 +02:00
|
|
|
|
2017-09-24 19:54:13 +02:00
|
|
|
void GraphItem::hover(bool hover)
|
2016-09-22 20:59:13 +02:00
|
|
|
{
|
2017-09-24 19:54:13 +02:00
|
|
|
if (hover) {
|
2019-08-25 10:54:25 +02:00
|
|
|
_pen.setWidth(_pen.width() + 1);
|
2016-09-22 20:59:13 +02:00
|
|
|
setZValue(zValue() + 1.0);
|
|
|
|
} else {
|
2019-08-25 10:54:25 +02:00
|
|
|
_pen.setWidth(_pen.width() - 1);
|
2016-09-22 20:59:13 +02:00
|
|
|
setZValue(zValue() - 1.0);
|
|
|
|
}
|
|
|
|
|
|
|
|
update();
|
|
|
|
}
|
2016-10-12 20:38:18 +02:00
|
|
|
|
|
|
|
void GraphItem::setScale(qreal sx, qreal sy)
|
|
|
|
{
|
|
|
|
if (_sx == sx && _sy == sy)
|
|
|
|
return;
|
|
|
|
|
|
|
|
prepareGeometryChange();
|
|
|
|
|
|
|
|
_sx = sx; _sy = sy;
|
|
|
|
updatePath();
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphItem::updatePath()
|
|
|
|
{
|
2019-08-25 10:54:25 +02:00
|
|
|
if (_sx == 0 && _sy == 0)
|
2016-10-12 20:38:18 +02:00
|
|
|
return;
|
|
|
|
|
2019-08-25 10:54:25 +02:00
|
|
|
prepareGeometryChange();
|
|
|
|
|
|
|
|
_path = QPainterPath();
|
2019-02-11 23:28:08 +01:00
|
|
|
|
2019-08-25 10:54:25 +02:00
|
|
|
if (!(_type == Time && !_time)) {
|
|
|
|
for (int i = 0; i < _graph.size(); i++) {
|
|
|
|
const GraphSegment &segment = _graph.at(i);
|
|
|
|
|
|
|
|
_path.moveTo(segment.first().x(_type) * _sx, -segment.first().y()
|
|
|
|
* _sy);
|
|
|
|
for (int i = 1; i < segment.size(); i++)
|
|
|
|
_path.lineTo(segment.at(i).x(_type) * _sx, -segment.at(i).y()
|
|
|
|
* _sy);
|
|
|
|
}
|
2019-02-11 23:28:08 +01:00
|
|
|
}
|
2019-08-25 10:54:25 +02:00
|
|
|
|
|
|
|
updateShape();
|
2016-10-12 20:38:18 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
void GraphItem::updateBounds()
|
|
|
|
{
|
2017-05-22 18:42:23 +02:00
|
|
|
if (_type == Time && !_time) {
|
2016-10-12 20:38:18 +02:00
|
|
|
_bounds = QRectF();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal bottom, top, left, right;
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
QPointF p = QPointF(_graph.first().first().x(_type),
|
|
|
|
-_graph.first().first().y());
|
2016-10-12 20:38:18 +02:00
|
|
|
bottom = p.y(); top = p.y(); left = p.x(); right = p.x();
|
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
for (int i = 0; i < _graph.size(); i++) {
|
|
|
|
const GraphSegment &segment = _graph.at(i);
|
|
|
|
|
|
|
|
for (int j = 0; j < segment.size(); j++) {
|
|
|
|
p = QPointF(segment.at(j).x(_type), -segment.at(j).y());
|
|
|
|
bottom = qMax(bottom, p.y()); top = qMin(top, p.y());
|
|
|
|
right = qMax(right, p.x()); left = qMin(left, p.x());
|
|
|
|
}
|
2016-10-12 20:38:18 +02:00
|
|
|
}
|
|
|
|
|
2019-10-27 22:51:36 +01:00
|
|
|
if (left == right)
|
|
|
|
_bounds = QRectF();
|
|
|
|
else
|
|
|
|
_bounds = QRectF(QPointF(left, top), QPointF(right, bottom));
|
2016-10-12 20:38:18 +02:00
|
|
|
}
|
2017-09-24 19:54:13 +02:00
|
|
|
|
2019-02-11 23:28:08 +01:00
|
|
|
qreal GraphItem::max() const
|
|
|
|
{
|
|
|
|
qreal ret = _graph.first().first().y();
|
|
|
|
|
|
|
|
for (int i = 0; i < _graph.size(); i++) {
|
|
|
|
const GraphSegment &segment = _graph.at(i);
|
|
|
|
|
|
|
|
for (int j = 0; j < segment.size(); j++) {
|
|
|
|
qreal y = segment.at(j).y();
|
|
|
|
if (y > ret)
|
|
|
|
ret = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal GraphItem::min() const
|
|
|
|
{
|
|
|
|
qreal ret = _graph.first().first().y();
|
|
|
|
|
|
|
|
for (int i = 0; i < _graph.size(); i++) {
|
|
|
|
const GraphSegment &segment = _graph.at(i);
|
|
|
|
|
|
|
|
for (int j = 0; j < segment.size(); j++) {
|
|
|
|
qreal y = segment.at(j).y();
|
|
|
|
if (y < ret)
|
|
|
|
ret = y;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
qreal GraphItem::avg() const
|
|
|
|
{
|
|
|
|
qreal sum = 0;
|
|
|
|
|
|
|
|
for (int i = 0; i < _graph.size(); i++) {
|
|
|
|
const GraphSegment &segment = _graph.at(i);
|
|
|
|
|
|
|
|
for (int j = 1; j < segment.size(); j++)
|
|
|
|
sum += segment.at(j).y() * (segment.at(j).s() - segment.at(j-1).s());
|
|
|
|
}
|
|
|
|
|
|
|
|
return sum/_graph.last().last().s();
|
|
|
|
}
|
|
|
|
|
2017-09-24 19:54:13 +02:00
|
|
|
void GraphItem::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
|
|
|
|
{
|
|
|
|
Q_UNUSED(event);
|
|
|
|
|
2019-08-25 10:54:25 +02:00
|
|
|
_pen.setWidth(_pen.width() + 1);
|
2017-09-24 19:54:13 +02:00
|
|
|
setZValue(zValue() + 1.0);
|
|
|
|
update();
|
|
|
|
|
|
|
|
emit selected(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
void GraphItem::hoverLeaveEvent(QGraphicsSceneHoverEvent *event)
|
|
|
|
{
|
|
|
|
Q_UNUSED(event);
|
|
|
|
|
2019-08-25 10:54:25 +02:00
|
|
|
_pen.setWidth(_pen.width() - 1);
|
2017-09-24 19:54:13 +02:00
|
|
|
setZValue(zValue() - 1.0);
|
|
|
|
update();
|
|
|
|
|
|
|
|
emit selected(false);
|
|
|
|
}
|
2019-10-13 20:20:32 +02:00
|
|
|
|
|
|
|
void GraphItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
|
|
|
{
|
2019-10-15 23:59:15 +02:00
|
|
|
Popup::show(event->screenPos(), info(), event->widget());
|
|
|
|
GraphicsItem::mousePressEvent(event);
|
2019-10-13 20:20:32 +02:00
|
|
|
}
|