diff --git a/inc/graph/server/gui/chart/chart.h b/inc/graph/server/gui/chart/chart.h index af542a7..bf3946a 100644 --- a/inc/graph/server/gui/chart/chart.h +++ b/inc/graph/server/gui/chart/chart.h @@ -5,6 +5,7 @@ #include #include #include +#include namespace Graph::GUI { class ChartWidget : public QWidget { @@ -19,18 +20,26 @@ class ChartWidget : public QWidget { protected: QSize sizeHint() const override; - void paintEvent(QPaintEvent *e) override; + void paintEvent(QPaintEvent* e) override; private: chart_spec_t spec; int decimateState = 0; QMutex mutex; - std::list points; + struct series_t { + double minX = std::numeric_limits::max(); + double maxX = std::numeric_limits::min(); + double minY = std::numeric_limits::max(); + double maxY = std::numeric_limits::min(); + std::list points; + }; + + std::map series; double scaleX, scaleY; int marginX = 40; - int marginY = 20; + int marginY = 40; QPointF pointToCoord(const point_t& point); }; diff --git a/src/graph/server/gui/chart/chart.cpp b/src/graph/server/gui/chart/chart.cpp index 4d498d2..fdbce14 100644 --- a/src/graph/server/gui/chart/chart.cpp +++ b/src/graph/server/gui/chart/chart.cpp @@ -1,16 +1,10 @@ #include #include -#include -#include -#include -#include -#include #include #include #include #include #include -#include "graph/plugins/plugin.h" #define MIN(a, b) (a < b ? a : b) #define MAX(a, b) (a > b ? a : b) @@ -18,9 +12,13 @@ namespace Graph::GUI { ChartWidget::ChartWidget(QWidget* parent, const chart_spec_t& spec) : QWidget(parent), spec(spec) { setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); - setMinimumSize(QSize(600, 350)); + for (const QString& name : spec.series) { + series.insert(std::pair(name.toStdString(), series_t())); + std::cout << "create a series named " << name.toStdString() << std::endl; + } + // context menu setContextMenuPolicy(Qt::ContextMenuPolicy::CustomContextMenu); 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) { QMutexLocker locker(&mutex); - double minX = std::numeric_limits::max(); - double minY = std::numeric_limits::max(); - double maxX = std::numeric_limits::min(); - double maxY = std::numeric_limits::min(); + if (!this->series.contains(std::string(seriesName))) + return; + + series_t& series = this->series.at(std::string(seriesName)); + + series.minX = std::numeric_limits::max(); + series.minY = std::numeric_limits::max(); + series.maxX = std::numeric_limits::min(); + series.maxY = std::numeric_limits::min(); if (!spec.keep_all) { if (count >= spec.keep_limit) { - this->points.clear(); - } else if (this->points.size() + count > spec.keep_limit) { - std::list::iterator end = std::next(this->points.begin(), this->points.size() - (spec.keep_limit - count)); - this->points.erase(this->points.begin(), end); + series.points.clear(); + } else if (series.points.size() + count > spec.keep_limit) { + std::list::iterator end = std::next(series.points.begin(), series.points.size() - (spec.keep_limit - count)); + 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) { - minX = MIN(minX, point.x); - maxX = MAX(maxX, point.x); + series.minX = MIN(series.minX, point.x); + series.maxX = MAX(series.maxX, point.x); } if (spec.axis.y.auto_limit) { - minY = MIN(minY, point.y); - maxY = MAX(maxY, point.y); + series.minY = MIN(series.minY, 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) continue; - this->points.push_back(points[i]); + series.points.push_back(points[i]); if (spec.axis.x.auto_limit) { - minX = MIN(minX, points[i].x); - maxX = MAX(maxX, points[i].x); + series.minX = MIN(series.minX, points[i].x); + series.maxX = MAX(series.maxX, points[i].x); } if (spec.axis.y.auto_limit) { - minY = MIN(minY, points[i].y); - maxY = MAX(maxY, points[i].y); + series.minY = MIN(series.minY, 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(); } void ChartWidget::clear() { QMutexLocker locker(&mutex); - points.clear(); + for (auto& series : this->series) { + series.second.points.clear(); + series.second.minX = std::numeric_limits::max(); + series.second.maxX = std::numeric_limits::min(); + series.second.minY = std::numeric_limits::max(); + series.second.maxY = std::numeric_limits::min(); + } } 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); } +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) { QMutexLocker locker(&mutex); + // axis ranges + if (spec.axis.x.auto_limit) { + spec.axis.x.min = std::numeric_limits::max(); + spec.axis.x.max = std::numeric_limits::min(); + } + if (spec.axis.y.auto_limit) { + spec.axis.y.min = std::numeric_limits::max(); + spec.axis.y.max = std::numeric_limits::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); // background @@ -119,15 +145,24 @@ void ChartWidget::paintEvent(QPaintEvent* e) { // limits // y max-mid-min - painter.drawText(QRect(0, marginY, marginX, 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(0, height() - marginY * 2, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.y.min)); + painter.drawText(QRect(marginX / 2, marginY, marginX / 2, marginY), Qt::AlignCenter, QString::number(spec.axis.y.max)); + painter.drawText(QRect(marginX / 2, height() / 2 - marginY / 2, marginX / 2, marginY), Qt::AlignCenter, + 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 - painter.drawText(QRect(marginX, height() - marginY, marginX, marginY), Qt::AlignCenter, QString::number(spec.axis.x.min)); - painter.drawText(QRect(width() / 2 - marginX / 2, height() - marginY, marginX, marginY), Qt::AlignCenter, + 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 / 2), Qt::AlignCenter, 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 if (spec.grid) { @@ -150,14 +185,29 @@ void ChartWidget::paintEvent(QPaintEvent* e) { painter.setPen(QPen(Qt::black, 1)); - QPolygonF polygon; - for (const point_t point : points) { - // TODO: only paint visible points - // (+ 1-1 before and after) - polygon << pointToCoord(point); - } + int i = 0; + for (const auto& series : this->series) { + QPolygonF polygon; + for (const point_t point : series.second.points) { + // TODO: only paint visible points + // (+ 1-1 before and after) + polygon << pointToCoord(point); + } - painter.drawPolyline(polygon); + 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); + + // 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) { @@ -169,11 +219,11 @@ ContextMenu::ContextMenu(QWidget* parent, ChartWidget* chartWidget) : QMenu(pare connect(clear, &QAction::triggered, this, [chartWidget]() { chartWidget->clear(); }); addAction(clear); - edit = new QAction(tr("&Edit"), this); - connect(edit, &QAction::triggered, this, []() { - // TODO: implement chart editing - }); - addAction(edit); + // edit = new QAction(tr("&Edit"), this); + // connect(edit, &QAction::triggered, this, []() { + // // TODO: implement chart editing + // }); + // addAction(edit); } ContextMenu::~ContextMenu() {} diff --git a/test/test.py b/test/test.py index 08c9ad2..1db0197 100755 --- a/test/test.py +++ b/test/test.py @@ -1,7 +1,7 @@ #!/bin/env python3 import requests -from math import sin +from math import sin, cos from time import sleep import numpy as np from argparse import ArgumentParser @@ -17,6 +17,16 @@ def sine(args): except KeyboardInterrupt: 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): start = 0 while True: @@ -80,7 +90,7 @@ def star(args): if __name__ == "__main__": 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("-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) @@ -91,6 +101,8 @@ if __name__ == "__main__": args = parser.parse_args() if args.test == "sine": sine(args) + if args.test == "cosine": + cosine(args) elif args.test == "sine_array": sine_array(args) elif args.test == "star":