full chart widget
This commit is contained in:
parent
df8cd70b0d
commit
590b302016
@ -5,6 +5,7 @@
|
|||||||
#include <QAction>
|
#include <QAction>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
#include <QMutex>
|
#include <QMutex>
|
||||||
|
#include <limits>
|
||||||
|
|
||||||
namespace Graph::GUI {
|
namespace Graph::GUI {
|
||||||
class ChartWidget : public QWidget {
|
class ChartWidget : public QWidget {
|
||||||
@ -19,18 +20,26 @@ class ChartWidget : public QWidget {
|
|||||||
|
|
||||||
protected:
|
protected:
|
||||||
QSize sizeHint() const override;
|
QSize sizeHint() const override;
|
||||||
void paintEvent(QPaintEvent *e) override;
|
void paintEvent(QPaintEvent* e) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
chart_spec_t spec;
|
chart_spec_t spec;
|
||||||
int decimateState = 0;
|
int decimateState = 0;
|
||||||
QMutex mutex;
|
QMutex mutex;
|
||||||
|
|
||||||
|
struct series_t {
|
||||||
|
double minX = std::numeric_limits<double>::max();
|
||||||
|
double maxX = std::numeric_limits<double>::min();
|
||||||
|
double minY = std::numeric_limits<double>::max();
|
||||||
|
double maxY = std::numeric_limits<double>::min();
|
||||||
std::list<point_t> points;
|
std::list<point_t> points;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::map<std::string, series_t> series;
|
||||||
|
|
||||||
double scaleX, scaleY;
|
double scaleX, scaleY;
|
||||||
int marginX = 40;
|
int marginX = 40;
|
||||||
int marginY = 20;
|
int marginY = 40;
|
||||||
QPointF pointToCoord(const point_t& point);
|
QPointF pointToCoord(const point_t& point);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,16 +1,10 @@
|
|||||||
#include <graph/server/gui/chart/chart.h>
|
#include <graph/server/gui/chart/chart.h>
|
||||||
#include <graph/server/gui/mainwindow.h>
|
#include <graph/server/gui/mainwindow.h>
|
||||||
#include <qbrush.h>
|
|
||||||
#include <qnamespace.h>
|
|
||||||
#include <qpoint.h>
|
|
||||||
#include <qpolygon.h>
|
|
||||||
#include <qsize.h>
|
|
||||||
#include <QPainter>
|
#include <QPainter>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <iterator>
|
#include <iterator>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include "graph/plugins/plugin.h"
|
|
||||||
|
|
||||||
#define MIN(a, b) (a < b ? a : b)
|
#define MIN(a, b) (a < b ? a : b)
|
||||||
#define MAX(a, b) (a > b ? a : b)
|
#define MAX(a, b) (a > b ? a : b)
|
||||||
@ -18,9 +12,13 @@
|
|||||||
namespace Graph::GUI {
|
namespace Graph::GUI {
|
||||||
ChartWidget::ChartWidget(QWidget* parent, const chart_spec_t& spec) : QWidget(parent), spec(spec) {
|
ChartWidget::ChartWidget(QWidget* parent, const chart_spec_t& spec) : QWidget(parent), spec(spec) {
|
||||||
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||||
|
|
||||||
setMinimumSize(QSize(600, 350));
|
setMinimumSize(QSize(600, 350));
|
||||||
|
|
||||||
|
for (const QString& name : spec.series) {
|
||||||
|
series.insert(std::pair<std::string, series_t>(name.toStdString(), series_t()));
|
||||||
|
std::cout << "create a series named " << name.toStdString() << std::endl;
|
||||||
|
}
|
||||||
|
|
||||||
// context menu
|
// context menu
|
||||||
setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu);
|
||||||
connect(this, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
connect(this, &QWidget::customContextMenuRequested, this, [this](const QPoint& pos) {
|
||||||
@ -34,28 +32,33 @@ ChartWidget::~ChartWidget() {}
|
|||||||
void ChartWidget::addData(const char* seriesName, const point_t* points, unsigned long count) {
|
void ChartWidget::addData(const char* seriesName, const point_t* points, unsigned long count) {
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&mutex);
|
||||||
|
|
||||||
double minX = std::numeric_limits<double>::max();
|
if (!this->series.contains(std::string(seriesName)))
|
||||||
double minY = std::numeric_limits<double>::max();
|
return;
|
||||||
double maxX = std::numeric_limits<double>::min();
|
|
||||||
double maxY = std::numeric_limits<double>::min();
|
series_t& series = this->series.at(std::string(seriesName));
|
||||||
|
|
||||||
|
series.minX = std::numeric_limits<double>::max();
|
||||||
|
series.minY = std::numeric_limits<double>::max();
|
||||||
|
series.maxX = std::numeric_limits<double>::min();
|
||||||
|
series.maxY = std::numeric_limits<double>::min();
|
||||||
|
|
||||||
if (!spec.keep_all) {
|
if (!spec.keep_all) {
|
||||||
if (count >= spec.keep_limit) {
|
if (count >= spec.keep_limit) {
|
||||||
this->points.clear();
|
series.points.clear();
|
||||||
} else if (this->points.size() + count > spec.keep_limit) {
|
} else if (series.points.size() + count > spec.keep_limit) {
|
||||||
std::list<point_t>::iterator end = std::next(this->points.begin(), this->points.size() - (spec.keep_limit - count));
|
std::list<point_t>::iterator end = std::next(series.points.begin(), series.points.size() - (spec.keep_limit - count));
|
||||||
this->points.erase(this->points.begin(), end);
|
series.points.erase(series.points.begin(), end);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const point_t& point : this->points) {
|
for (const point_t& point : series.points) {
|
||||||
if (spec.axis.x.auto_limit) {
|
if (spec.axis.x.auto_limit) {
|
||||||
minX = MIN(minX, point.x);
|
series.minX = MIN(series.minX, point.x);
|
||||||
maxX = MAX(maxX, point.x);
|
series.maxX = MAX(series.maxX, point.x);
|
||||||
}
|
}
|
||||||
if (spec.axis.y.auto_limit) {
|
if (spec.axis.y.auto_limit) {
|
||||||
minY = MIN(minY, point.y);
|
series.minY = MIN(series.minY, point.y);
|
||||||
maxY = MAX(maxY, point.y);
|
series.maxY = MAX(series.maxY, point.y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,33 +68,30 @@ void ChartWidget::addData(const char* seriesName, const point_t* points, unsigne
|
|||||||
if (decimateState++ % spec.decimate != 0)
|
if (decimateState++ % spec.decimate != 0)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
this->points.push_back(points[i]);
|
series.points.push_back(points[i]);
|
||||||
|
|
||||||
if (spec.axis.x.auto_limit) {
|
if (spec.axis.x.auto_limit) {
|
||||||
minX = MIN(minX, points[i].x);
|
series.minX = MIN(series.minX, points[i].x);
|
||||||
maxX = MAX(maxX, points[i].x);
|
series.maxX = MAX(series.maxX, points[i].x);
|
||||||
}
|
}
|
||||||
if (spec.axis.y.auto_limit) {
|
if (spec.axis.y.auto_limit) {
|
||||||
minY = MIN(minY, points[i].y);
|
series.minY = MIN(series.minY, points[i].y);
|
||||||
maxY = MAX(maxY, points[i].y);
|
series.maxY = MAX(series.maxY, points[i].y);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (spec.axis.x.auto_limit) {
|
|
||||||
spec.axis.x.min = minX;
|
|
||||||
spec.axis.x.max = maxX;
|
|
||||||
}
|
|
||||||
if (spec.axis.y.auto_limit) {
|
|
||||||
spec.axis.y.min = minY;
|
|
||||||
spec.axis.y.max = maxY;
|
|
||||||
}
|
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ChartWidget::clear() {
|
void ChartWidget::clear() {
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&mutex);
|
||||||
points.clear();
|
for (auto& series : this->series) {
|
||||||
|
series.second.points.clear();
|
||||||
|
series.second.minX = std::numeric_limits<double>::max();
|
||||||
|
series.second.maxX = std::numeric_limits<double>::min();
|
||||||
|
series.second.minY = std::numeric_limits<double>::max();
|
||||||
|
series.second.maxY = std::numeric_limits<double>::min();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QSize ChartWidget::sizeHint() const {
|
QSize ChartWidget::sizeHint() const {
|
||||||
@ -104,9 +104,35 @@ QPointF ChartWidget::pointToCoord(const point_t& point) {
|
|||||||
return QPointF((point.x - spec.axis.x.min) * scaleX + marginX, height() - (point.y - spec.axis.y.min) * scaleY - marginY);
|
return QPointF((point.x - spec.axis.x.min) * scaleX + marginX, height() - (point.y - spec.axis.y.min) * scaleY - marginY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QColor interpolateColor(const QColor& a, const QColor& b, float ratio) {
|
||||||
|
return QColor(255 * (b.redF() * ratio + a.redF() * (1 - ratio)), 255 * (b.greenF() * ratio + a.greenF() * (1 - ratio)),
|
||||||
|
255 * (b.blueF() * ratio + a.blueF() * (1 - ratio)), 255 * (b.alphaF() * ratio + a.alphaF() * (1 - ratio)));
|
||||||
|
}
|
||||||
|
|
||||||
void ChartWidget::paintEvent(QPaintEvent* e) {
|
void ChartWidget::paintEvent(QPaintEvent* e) {
|
||||||
QMutexLocker locker(&mutex);
|
QMutexLocker locker(&mutex);
|
||||||
|
|
||||||
|
// axis ranges
|
||||||
|
if (spec.axis.x.auto_limit) {
|
||||||
|
spec.axis.x.min = std::numeric_limits<double>::max();
|
||||||
|
spec.axis.x.max = std::numeric_limits<double>::min();
|
||||||
|
}
|
||||||
|
if (spec.axis.y.auto_limit) {
|
||||||
|
spec.axis.y.min = std::numeric_limits<double>::max();
|
||||||
|
spec.axis.y.max = std::numeric_limits<double>::min();
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& series : this->series) {
|
||||||
|
if (spec.axis.x.auto_limit) {
|
||||||
|
spec.axis.x.min = MIN(spec.axis.x.min, series.second.minX);
|
||||||
|
spec.axis.x.max = MAX(spec.axis.x.max, series.second.maxX);
|
||||||
|
}
|
||||||
|
if (spec.axis.y.auto_limit) {
|
||||||
|
spec.axis.y.min = MIN(spec.axis.y.min, series.second.minY);
|
||||||
|
spec.axis.y.max = MAX(spec.axis.y.max, series.second.maxY);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
QPainter painter(this);
|
QPainter painter(this);
|
||||||
|
|
||||||
// background
|
// background
|
||||||
@ -119,15 +145,24 @@ void ChartWidget::paintEvent(QPaintEvent* e) {
|
|||||||
|
|
||||||
// limits
|
// limits
|
||||||
// y max-mid-min
|
// y max-mid-min
|
||||||
painter.drawText(QRect(0, marginY, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.y.max));
|
painter.drawText(QRect(marginX / 2, marginY, marginX / 2, marginY), Qt::AlignCenter, QString::number(spec.axis.y.max));
|
||||||
painter.drawText(QRect(0, height() / 2 - marginY / 2, marginX, marginY), Qt::AlignCenter, QString::number((spec.axis.y.max + spec.axis.y.min) / 2));
|
painter.drawText(QRect(marginX / 2, height() / 2 - marginY / 2, marginX / 2, marginY), Qt::AlignCenter,
|
||||||
painter.drawText(QRect(0, height() - marginY * 2, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.y.min));
|
QString::number((spec.axis.y.max + spec.axis.y.min) / 2));
|
||||||
|
painter.drawText(QRect(marginX / 2, height() - marginY * 2, marginX / 2, marginY), Qt::AlignCenter, QString::number(spec.axis.y.min));
|
||||||
|
|
||||||
// x min-mid-max
|
// x min-mid-max
|
||||||
painter.drawText(QRect(marginX, height() - marginY, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.x.min));
|
painter.drawText(QRect(marginX, height() - marginY, marginX, marginY / 2), Qt::AlignCenter, QString::number(spec.axis.x.min));
|
||||||
painter.drawText(QRect(width() / 2 - marginX / 2, height() - marginY, marginX, marginY), Qt::AlignCenter,
|
painter.drawText(QRect(width() / 2 - marginX / 2, height() - marginY, marginX, marginY / 2), Qt::AlignCenter,
|
||||||
QString::number((spec.axis.x.min + spec.axis.x.max) / 2));
|
QString::number((spec.axis.x.min + spec.axis.x.max) / 2));
|
||||||
painter.drawText(QRect(width() - marginX * 2, height() - marginY, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.x.max));
|
painter.drawText(QRect(width() - marginX * 2, height() - marginY, marginX, marginY / 2), Qt::AlignCenter, QString::number(spec.axis.x.max));
|
||||||
|
|
||||||
|
// axis labels
|
||||||
|
painter.drawText(QRect(0, height() - marginY / 2, width(), marginY / 2), Qt::AlignCenter, spec.axis.x.label);
|
||||||
|
painter.save();
|
||||||
|
painter.rotate(-90);
|
||||||
|
painter.translate(-height(), 0);
|
||||||
|
painter.drawText(QRect(0, 0, height(), marginX / 2), Qt::AlignCenter, spec.axis.y.label);
|
||||||
|
painter.restore();
|
||||||
|
|
||||||
// grid
|
// grid
|
||||||
if (spec.grid) {
|
if (spec.grid) {
|
||||||
@ -150,14 +185,29 @@ void ChartWidget::paintEvent(QPaintEvent* e) {
|
|||||||
|
|
||||||
painter.setPen(QPen(Qt::black, 1));
|
painter.setPen(QPen(Qt::black, 1));
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
for (const auto& series : this->series) {
|
||||||
QPolygonF polygon;
|
QPolygonF polygon;
|
||||||
for (const point_t point : points) {
|
for (const point_t point : series.second.points) {
|
||||||
// TODO: only paint visible points
|
// TODO: only paint visible points
|
||||||
// (+ 1-1 before and after)
|
// (+ 1-1 before and after)
|
||||||
polygon << pointToCoord(point);
|
polygon << pointToCoord(point);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
painter.setBrush(interpolateColor(Qt::darkBlue, Qt::green, i / (float)this->series.size()));
|
||||||
|
painter.setPen(interpolateColor(Qt::darkBlue, Qt::green, i / (float)this->series.size()));
|
||||||
|
|
||||||
|
// line
|
||||||
painter.drawPolyline(polygon);
|
painter.drawPolyline(polygon);
|
||||||
|
|
||||||
|
// legend
|
||||||
|
if (spec.legend) {
|
||||||
|
painter.fillRect(QRect(marginX + 5, marginY + 5 + i * 15, 10, 10), painter.brush());
|
||||||
|
painter.drawText(QRect(marginX + 17, marginY + 2 + i * 15, width() - 2 * marginX, 15), QString::fromStdString(series.first));
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu::ContextMenu(QWidget* parent, ChartWidget* chartWidget) : QMenu(parent) {
|
ContextMenu::ContextMenu(QWidget* parent, ChartWidget* chartWidget) : QMenu(parent) {
|
||||||
@ -169,11 +219,11 @@ ContextMenu::ContextMenu(QWidget* parent, ChartWidget* chartWidget) : QMenu(pare
|
|||||||
connect(clear, &QAction::triggered, this, [chartWidget]() { chartWidget->clear(); });
|
connect(clear, &QAction::triggered, this, [chartWidget]() { chartWidget->clear(); });
|
||||||
addAction(clear);
|
addAction(clear);
|
||||||
|
|
||||||
edit = new QAction(tr("&Edit"), this);
|
// edit = new QAction(tr("&Edit"), this);
|
||||||
connect(edit, &QAction::triggered, this, []() {
|
// connect(edit, &QAction::triggered, this, []() {
|
||||||
// TODO: implement chart editing
|
// // TODO: implement chart editing
|
||||||
});
|
// });
|
||||||
addAction(edit);
|
// addAction(edit);
|
||||||
}
|
}
|
||||||
|
|
||||||
ContextMenu::~ContextMenu() {}
|
ContextMenu::~ContextMenu() {}
|
||||||
|
16
test/test.py
16
test/test.py
@ -1,7 +1,7 @@
|
|||||||
#!/bin/env python3
|
#!/bin/env python3
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from math import sin
|
from math import sin, cos
|
||||||
from time import sleep
|
from time import sleep
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from argparse import ArgumentParser
|
from argparse import ArgumentParser
|
||||||
@ -17,6 +17,16 @@ def sine(args):
|
|||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
def cosine(args):
|
||||||
|
x = 0.0
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
requests.get(f"http://{args.address}:{args.port}/add?chart={args.chart}&series={args.series}&x={x}&y={cos(x)}")
|
||||||
|
x += 0.01
|
||||||
|
sleep(0.01)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
break
|
||||||
|
|
||||||
def sine_array(args):
|
def sine_array(args):
|
||||||
start = 0
|
start = 0
|
||||||
while True:
|
while True:
|
||||||
@ -80,7 +90,7 @@ def star(args):
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
parser = ArgumentParser()
|
parser = ArgumentParser()
|
||||||
parser.add_argument("-t", action="store", dest="test", help="test type", type=str, choices=["sine", "sine_array", "star"], required=False, default="sin")
|
parser.add_argument("-t", action="store", dest="test", help="test type", type=str, choices=["sine", "cosine", "sine_array", "star"], required=False, default="sine")
|
||||||
parser.add_argument("-n", action="store", dest="num", help="sin_array size/iteration", type=int, required=False, default=100)
|
parser.add_argument("-n", action="store", dest="num", help="sin_array size/iteration", type=int, required=False, default=100)
|
||||||
parser.add_argument("-d", action="store", dest="delay", help="delay between iteration", type=float, required=False, default=0.25)
|
parser.add_argument("-d", action="store", dest="delay", help="delay between iteration", type=float, required=False, default=0.25)
|
||||||
parser.add_argument("-c", action="store", dest="chart", help="chart id", type=int, required=True)
|
parser.add_argument("-c", action="store", dest="chart", help="chart id", type=int, required=True)
|
||||||
@ -91,6 +101,8 @@ if __name__ == "__main__":
|
|||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
if args.test == "sine":
|
if args.test == "sine":
|
||||||
sine(args)
|
sine(args)
|
||||||
|
if args.test == "cosine":
|
||||||
|
cosine(args)
|
||||||
elif args.test == "sine_array":
|
elif args.test == "sine_array":
|
||||||
sine_array(args)
|
sine_array(args)
|
||||||
elif args.test == "star":
|
elif args.test == "star":
|
||||||
|
Loading…
Reference in New Issue
Block a user