From 78f242d808bdccb6398d71659b5dee2bd51fe1d4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Du=C5=A1an=20Poizl?= Date: Wed, 23 Apr 2025 13:37:47 +0200 Subject: [PATCH] Extending script plot() function --- CMakeLists.txt | 1 + batchprocessing.cpp | 28 +----- batchprocessing.h | 2 +- chartgraph.cpp | 238 ++++++++++++++++++++++++++++++++++++++++++++ chartgraph.h | 23 +++++ scriptengine.cpp | 18 ++-- 6 files changed, 273 insertions(+), 37 deletions(-) create mode 100644 chartgraph.cpp create mode 100644 chartgraph.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 37242ee..6ced6f1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -30,6 +30,7 @@ add_subdirectory(libXISF) set(TENMON_SRC about.cpp about.h batchprocessing.cpp batchprocessing.h batchprocessing.ui + chartgraph.h chartgraph.cpp database.cpp database.h databaseview.cpp databaseview.h delete.cpp diff --git a/batchprocessing.cpp b/batchprocessing.cpp index 8ebc475..4133402 100644 --- a/batchprocessing.cpp +++ b/batchprocessing.cpp @@ -14,6 +14,7 @@ #include #include #include "scriptengine.h" +#include "chartgraph.h" #ifdef Q_OS_LINUX #include @@ -271,31 +272,10 @@ QJSValue BatchProcessing::getItem(const QStringList &items, const QString &label return ok ? ret : QJSValue(); } -void BatchProcessing::plot(const QVector &points) +void BatchProcessing::plot(const QVariant &graph) { - QDialog *diag = new QDialog(this); - diag->setAttribute(Qt::WA_DeleteOnClose); - diag->setModal(false); - diag->setWindowTitle(tr("Chart")); - - QChartView *chartView = new QChartView(diag); - diag->setLayout(new QVBoxLayout); - diag->layout()->addWidget(chartView); - - QChart *chart = new QChart; - chart->setParent(chartView); - - auto series = new QLineSeries(chartView); - series->append(points); - chart->addSeries(series); - chart->createDefaultAxes(); - chart->setTitle("Simple line graph"); - chart->legend()->hide(); - - chartView->setChart(chart); - chartView->setRenderHint(QPainter::Antialiasing); - diag->resize(640, 480); - diag->show(); + ChartGraph *chart = new ChartGraph(this); + chart->plot(graph); } void openDir(const QString &path) diff --git a/batchprocessing.h b/batchprocessing.h index 57d6709..b225b96 100644 --- a/batchprocessing.h +++ b/batchprocessing.h @@ -42,7 +42,7 @@ public slots: QJSValue getFloat(const QString &label, double value, int decimals); QJSValue getItem(const QStringList &items, const QString &label, int current); - void plot(const QVector &points); + void plot(const QVariant &graph); }; void openDir(const QString &path); diff --git a/chartgraph.cpp b/chartgraph.cpp new file mode 100644 index 0000000..9f30f12 --- /dev/null +++ b/chartgraph.cpp @@ -0,0 +1,238 @@ +#include "chartgraph.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class ChartView : public QChartView +{ + QPointF _mousePos; + bool _scroll = false; +public: + ChartView(QWidget *parent) : QChartView(parent) + { + } +protected: + void keyPressEvent(QKeyEvent *event) override + { + switch(event->key()) + { + case Qt::Key_Plus: + chart()->zoomIn(); + break; + case Qt::Key_Minus: + chart()->zoomOut(); + break; + case Qt::Key_Left: + chart()->scroll(-10, 0); + break; + case Qt::Key_Right: + chart()->scroll(10, 0); + break; + case Qt::Key_Up: + chart()->scroll(0, 10); + break; + case Qt::Key_Down: + chart()->scroll(0, -10); + break; + default: + QGraphicsView::keyPressEvent(event); + break; + } + } + void mousePressEvent(QMouseEvent *event) override + { + if(event->button() == Qt::LeftButton) + { + _scroll = true; + _mousePos = event->position(); + } + + QChartView::mousePressEvent(event); + } + void mouseMoveEvent(QMouseEvent *event) override + { + if(_scroll) + { + QPointF pos = event->position(); + chart()->scroll(_mousePos.x() - pos.x(), pos.y() - _mousePos.y()); + _mousePos = pos; + } + QChartView::mouseMoveEvent(event); + } + void mouseReleaseEvent(QMouseEvent *event) override + { + _scroll = false; + QChartView::mouseReleaseEvent(event); + } +}; + +ChartGraph::ChartGraph(QWidget *parent) : QDialog(parent) +{ + setAttribute(Qt::WA_DeleteOnClose); + setModal(false); + + _chartView = new ChartView(this); + setLayout(new QVBoxLayout); + layout()->addWidget(_chartView); + + _chart = new QChart; + _chartView->setChart(_chart); + _chartView->setRenderHint(QPainter::Antialiasing); + resize(1024, 768); + + QMenuBar *menuBar = new QMenuBar(this); + menuBar->addAction(tr("Save"), this, &ChartGraph::save); + layout()->setMenuBar(menuBar); +} + +void ChartGraph::plot(const QVariant &graph) +{ + QVariantMap map = graph.toMap(); + + _chart->setTitle(map["title"].toString()); + if(map.contains("legend")) + { + QVariantMap legend = map["legend"].toMap(); + if(legend.contains("visible")) + _chart->legend()->setVisible(legend["visible"].toBool()); + + QString align = legend["align"].toString(); + if(align == "top") + _chart->legend()->setAlignment(Qt::AlignTop); + else if(align == "left") + _chart->legend()->setAlignment(Qt::AlignLeft); + else if(align == "bottom") + _chart->legend()->setAlignment(Qt::AlignBottom); + else if(align == "right") + _chart->legend()->setAlignment(Qt::AlignRight); + } + + QBarSeries *barSeries = nullptr; + + qreal minY = INFINITY; + qreal maxY = -INFINITY; + + for(auto s : map["series"].toList()) + { + QVariantMap serie = s.toMap(); + QString type = serie["type"].toString(); + if(type == "line" || type == "points" || type == "linePoints" || type.isEmpty()) + { + QXYSeries *series = nullptr; + if(type == "points") + { + QScatterSeries *scatter = new QScatterSeries(_chart); + series = scatter; + QString shape = serie["shape"].toString(); + if(shape == "circle")scatter->setMarkerShape(QScatterSeries::MarkerShapeCircle); + else if(shape == "rectangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeRectangle); + else if(shape == "triangle")scatter->setMarkerShape(QScatterSeries::MarkerShapeTriangle); + else if(shape == "star")scatter->setMarkerShape(QScatterSeries::MarkerShapeStar); + else if(shape == "pentagon")scatter->setMarkerShape(QScatterSeries::MarkerShapePentagon); + } + else + { + series = new QLineSeries(_chart); + } + + series->setName(serie["title"].toString()); + QVariantList x = serie["x"].toList(); + QVariantList y = serie["y"].toList(); + if(x.isEmpty()) + { + for(int i = 0; i < y.size(); i++) + { + qreal val = y[i].toDouble(); + minY = std::min(minY, val); + maxY = std::max(maxY, val); + series->append(i, val); + } + } + else + { + int size = std::min(x.size(), y.size()); + for(int i = 0; i < size; i++) + { + qreal val = y[i].toDouble(); + minY = std::min(minY, val); + maxY = std::max(maxY, val); + series->append(x[i].toDouble(), val); + } + } + + _chart->addSeries(series); + if(serie["bestFit"].toBool()) + { + series->setBestFitLineVisible(true); + QPen pen = series->bestFitLinePen(); + pen.setColor(series->color()); + pen.setStyle(Qt::DashLine); + series->setBestFitLinePen(pen); + } + + if(type == "linePoints") + series->setPointsVisible(true); + + if(serie.contains("color")) + { + QString color = serie["color"].toString(); + if(QColor::isValidColorName(color))series->setColor(QColor::fromString(color)); + } + } + else if(type == "bar") + { + if(!barSeries) + { + barSeries = new QBarSeries(_chart); + _chart->addSeries(barSeries); + } + QBarSet *set = new QBarSet(serie["title"].toString()); + QVariantList y = serie["y"].toList(); + for(int i = 0; i < y.size(); i++) + { + qreal val = y[i].toDouble(); + minY = std::min(minY, val); + maxY = std::max(maxY, val); + set->append(val); + } + + barSeries->append(set); + if(serie.contains("color")) + { + QString color = serie["color"].toString(); + if(QColor::isValidColorName(color))set->setColor(QColor::fromString(color)); + } + } + } + + _chart->createDefaultAxes(); + QValueAxis *yaxis = qobject_cast(_chart->axes(Qt::Vertical).front()); + if(yaxis) + { + qreal off = (maxY - minY) * 0.05; + yaxis->setRange(std::min(minY - off, 0.0), maxY + off); + } + + show(); +} + +void ChartGraph::save() +{ + QSettings settings; + QString dir = settings.value("mainwindow/lastdir").toString(); + QString output = QFileDialog::getSaveFileName(this, tr("Save as"), dir, "PNG (*.png)"); + + if(!output.isEmpty()) + { + QPixmap graph = _chartView->grab(); + graph.toImage().save(output); + } +} diff --git a/chartgraph.h b/chartgraph.h new file mode 100644 index 0000000..4bcb953 --- /dev/null +++ b/chartgraph.h @@ -0,0 +1,23 @@ +#ifndef CHARTGRAPH_H +#define CHARTGRAPH_H + +#include +#include +#include + +class ChartView; + +class ChartGraph : public QDialog +{ + Q_OBJECT + QChart *_chart; + ChartView *_chartView; +public: + explicit ChartGraph(QWidget *parent = nullptr); + void plot(const QVariant &graph); +public slots: + void save(); +signals: +}; + +#endif // CHARTGRAPH_H diff --git a/scriptengine.cpp b/scriptengine.cpp index c15a5c6..e514f8e 100644 --- a/scriptengine.cpp +++ b/scriptengine.cpp @@ -136,19 +136,13 @@ QJSValue ScriptEngine::getItem(const QStringList &items, const QString &label, i return ret; } -void ScriptEngine::plot(const QJSValue &pointsArray) +void ScriptEngine::plot(const QJSValue &graph) { - if(pointsArray.isArray()) - { - int len = pointsArray.property("length").toInt(); - QVector points; - for(int i = 0; i < len; i++) - { - QJSValue point = pointsArray.property(i); - points.append(QPointF(point.property("x").toNumber(), point.property("y").toNumber())); - } - QMetaObject::invokeMethod(_parent, "plot", Qt::QueuedConnection, Q_ARG(QVector, points)); - } + QVariant graphV = graph.toVariant(QJSValue::ConvertJSObjects); + if(graphV.isValid()) + QMetaObject::invokeMethod(_parent, "plot", Qt::QueuedConnection, Q_ARG(QVariant, graphV)); + else + logError("Invalid value to be plotted"); } QJSValue ScriptEngine::openFile(const QString &fileName, const QString &mode)