diff --git a/SciQLopPlots/bindings/bindings.xml b/SciQLopPlots/bindings/bindings.xml index 52a670c..729088b 100644 --- a/SciQLopPlots/bindings/bindings.xml +++ b/SciQLopPlots/bindings/bindings.xml @@ -87,6 +87,16 @@ + + + + + + + + + + @@ -362,6 +372,15 @@ + + + + + + + + + diff --git a/SciQLopPlots/bindings/helper_scripts/shiboken-gen.py b/SciQLopPlots/bindings/helper_scripts/shiboken-gen.py index b3710e9..b893841 100644 --- a/SciQLopPlots/bindings/helper_scripts/shiboken-gen.py +++ b/SciQLopPlots/bindings/helper_scripts/shiboken-gen.py @@ -39,11 +39,19 @@ def cpp_flags(build_dir, ref_build_target): '-std=c++17', '--generator-set=shiboken'] -if os.path.exists('/opt/rh/gcc-toolset-11/root/usr/include/c++/11'): - shiboken_constant_args += [ - '-I/opt/rh/gcc-toolset-11/root/usr/include/c++/11', - '-I/opt/rh/gcc-toolset-11/root/usr/include/c++/11/x86_64-redhat-linux' - ] +if 'linux' in platform.system().lower(): + if os.path.exists('/opt/rh/gcc-toolset-11/root/usr/include/c++/11'): + shiboken_constant_args += [ + '-I/opt/rh/gcc-toolset-11/root/usr/include/c++/11', + '-I/opt/rh/gcc-toolset-11/root/usr/include/c++/11/x86_64-redhat-linux' + ] + elif os.path.exists('/usr/include/c++/14'): + shiboken_constant_args += [ + '-I/usr/include/c++/14', + '-I/usr/include/c++/14/x86_64-redhat-linux' + ] + + cmd = [args.shiboken, args.input_header, args.input_xml ] + shiboken_constant_args + cpp_flags(args.build_directory, args.ref_build_target) + [ f'--typesystem-paths={args.typesystem_paths}', f'--output-directory={args.output_directory}'] diff --git a/SciQLopPlots/meson.build b/SciQLopPlots/meson.build index 8ca7691..87956bd 100644 --- a/SciQLopPlots/meson.build +++ b/SciQLopPlots/meson.build @@ -53,11 +53,13 @@ shiboken_dep = declare_dependency(compile_args: shiboken_build_flags, link_args: moc_headers = [ 'bindings/_QCustomPlot.hpp', project_source_root + '/include/SciQLopPlots/SciQLopGraph.hpp', + project_source_root + '/include/SciQLopPlots/SciQLopCurve.hpp', project_source_root + '/include/SciQLopPlots/SciQLopColorMap.hpp', project_source_root + '/include/SciQLopPlots/SciQLopVerticalSpan.hpp', project_source_root + '/include/SciQLopPlots/SciQLopPlotItem.hpp', project_source_root + '/include/SciQLopPlots/SciQLopPlot.hpp', project_source_root + '/include/SciQLopPlots/SciQLopGraphResampler.hpp', + project_source_root + '/include/SciQLopPlots/SciQLopCurveResampler.hpp', project_source_root + '/include/SciQLopPlots/SciQLopColorMapResampler.hpp', project_source_root + '/qcustomplot-source/qcustomplot.h' ] @@ -82,6 +84,7 @@ sources = moc_files \ '../src/SciQLopPlotItem.cpp', '../src/SciQLopVerticalSpan.cpp', '../src/SciQLopGraph.cpp', + '../src/SciQLopCurve.cpp', '../src/SciQLopColorMap.cpp', '../src/SciQLopColorMapResampler.cpp', '../src/BufferProtocol.cpp', diff --git a/include/SciQLopPlots/SciQLopCurve.hpp b/include/SciQLopPlots/SciQLopCurve.hpp new file mode 100644 index 0000000..ba679e9 --- /dev/null +++ b/include/SciQLopPlots/SciQLopCurve.hpp @@ -0,0 +1,79 @@ +/*------------------------------------------------------------------------------ +-- This file is a part of the SciQLop Software +-- Copyright (C) 2024, Plasma Physics Laboratory - CNRS +-- +-- This program is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation; either version 2 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +-------------------------------------------------------------------------------*/ +/*-- Author : Alexis Jeandet +-- Mail : alexis.jeandet@member.fsf.org +----------------------------------------------------------------------------*/ +#pragma once + + +#include "BufferProtocol.hpp" +#include "SciQLopGraph.hpp" +#include +class QThread; +struct CurveResampler; + +class SciQLopCurve : public QObject +{ + CurveResampler* _resampler = nullptr; + QThread* _resampler_thread = nullptr; + + QCPAxis* _keyAxis; + QCPAxis* _valueAxis; + QList _curves; + + Q_OBJECT + + inline QCustomPlot* _plot() const { return qobject_cast(this->parent()); } + + + void _range_changed(const QCPRange& newRange, const QCPRange& oldRange); + + void _setCurveData(std::size_t index, QVector data); + + Q_SIGNAL void _setCurveDataSig(std::size_t index, QVector data); + + void clear_curves(bool curve_already_removed = false); + void clear_resampler(); + void create_resampler(const QStringList& labels); + void curve_got_removed_from_plot(QCPCurve* curve); + +public: + Q_ENUMS(FractionStyle) + explicit SciQLopCurve(QCustomPlot* parent, QCPAxis* keyAxis, QCPAxis* valueAxis, + const QStringList& labels, + SciQLopGraph::DataOrder dataOrder = SciQLopGraph::DataOrder::xFirst); + + explicit SciQLopCurve(QCustomPlot* parent, QCPAxis* keyAxis, QCPAxis* valueAxis, + SciQLopGraph::DataOrder dataOrder = SciQLopGraph::DataOrder::xFirst); + + virtual ~SciQLopCurve() override; + + void setData(Array_view&& x, Array_view&& y, bool ignoreCurrentRange = false); + inline QCPCurve* graphAt(std::size_t index) const { return _curves[index]; } + void create_graphs(const QStringList& labels); + + inline std::size_t line_count() { return std::size(this->_curves); } + +#ifndef BINDINGS_H + Q_SIGNAL void range_changed(const QCPRange& newRange, bool missData); +#endif + +private: + SciQLopGraph::DataOrder _dataOrder = SciQLopGraph::DataOrder::xFirst; +}; diff --git a/include/SciQLopPlots/SciQLopCurveResampler.hpp b/include/SciQLopPlots/SciQLopCurveResampler.hpp new file mode 100644 index 0000000..f34d44c --- /dev/null +++ b/include/SciQLopPlots/SciQLopCurveResampler.hpp @@ -0,0 +1,130 @@ +/*------------------------------------------------------------------------------ +-- This file is a part of the SciQLop Software +-- Copyright (C) 2024, Plasma Physics Laboratory - CNRS +-- +-- This program is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation; either version 2 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +-------------------------------------------------------------------------------*/ +/*-- Author : Alexis Jeandet +-- Mail : alexis.jeandet@member.fsf.org +----------------------------------------------------------------------------*/ +#pragma once + +#include "BufferProtocol.hpp" +#include "SciQLopCurve.hpp" +#include "SciQLopGraph.hpp" +#include +#include + + +static inline QVector copy_data( + const double* x, const double* y, std::size_t x_size, const int y_incr = 1) +{ + QVector data(x_size); + const double* current_y_it = y; + for (auto i = 0UL; i < x_size; i++) + { + data[i] = QCPCurveData { static_cast(i), x[i], *current_y_it }; + current_y_it += y_incr; + } + return data; +} + + +struct CurveResampler : public QObject +{ + Q_OBJECT + QMutex _data_mutex; + QMutex _range_mutex; + Array_view _x; + Array_view _y; + SciQLopGraph::DataOrder _dataOrder; + QCPRange _data_x_range; + std::size_t _line_cnt; + + Q_SIGNAL void _resample_sig(const QCPRange newRange); + inline void _resample_slot(const QCPRange newRange) + { + QMutexLocker locker(&_data_mutex); + if (_x.data() != nullptr && _x.flat_size() > 0) + { + + + const auto y_incr = (_dataOrder == SciQLopGraph::DataOrder::xFirst) ? 1UL : _line_cnt; + for (auto line_index = 0UL; line_index < _line_cnt; line_index++) + { + const auto count = std::size(_x); + const auto start_y = _y.data() + + (line_index + * ((_dataOrder == SciQLopGraph::DataOrder::xFirst) ? _x.flat_size() : 1)); + emit this->setGraphData(line_index, copy_data(_x.data(), start_y, count, y_incr)); + } + _x.release(); + _y.release(); + } + emit this->refreshPlot(); + } + +public: +#ifndef BINDINGS_H + Q_SIGNAL void setGraphData(std::size_t index, QVector data); + Q_SIGNAL void refreshPlot(); +#endif + + CurveResampler(SciQLopGraph::DataOrder dataOrder, std::size_t line_cnt) + : _dataOrder { dataOrder }, _line_cnt { line_cnt } + { + + connect(this, &CurveResampler::_resample_sig, this, &CurveResampler::_resample_slot, + Qt::QueuedConnection); + } + + inline void resample(const QCPRange newRange) { emit this->_resample_sig(newRange); } + + inline QCPRange x_range() + { + QMutexLocker locker(&_range_mutex); + auto rng = this->_data_x_range; + return rng; + } + + inline void set_line_count(std::size_t line_cnt) + { + QMutexLocker locker(&_data_mutex); + this->_line_cnt = line_cnt; + } + + inline void setData(Array_view&& x, Array_view&& y) + { + { + QMutexLocker locker(&_data_mutex); + + _x = std::move(x); + _y = std::move(y); + + const auto len = _x.flat_size(); + if (len > 0) + { + _data_x_range.lower = _x.data()[0]; + _data_x_range.upper = _x.data()[len - 1]; + } + else + { + _data_x_range.lower = std::nan(""); + _data_x_range.upper = std::nan(""); + } + this->resample(_data_x_range); + } + } +}; diff --git a/include/SciQLopPlots/SciQLopPlot.hpp b/include/SciQLopPlots/SciQLopPlot.hpp index 7b79a62..52141a3 100644 --- a/include/SciQLopPlots/SciQLopPlot.hpp +++ b/include/SciQLopPlots/SciQLopPlot.hpp @@ -25,6 +25,7 @@ #include "constants.hpp" #include +#include #include #include #include @@ -67,6 +68,20 @@ class SciQLopPlot : public QCustomPlot return sg; } + inline SciQLopCurve* addSciQLopCurve(QCPAxis* x, QCPAxis* y, QStringList labels, + SciQLopGraph::DataOrder dataOrder = SciQLopGraph::DataOrder::xFirst) + { + auto sg = new SciQLopCurve(this, x, y, labels, dataOrder); + return sg; + } + + inline SciQLopCurve* addSciQLopCurve( + QCPAxis* x, QCPAxis* y, SciQLopGraph::DataOrder dataOrder = SciQLopGraph::DataOrder::xFirst) + { + auto sg = new SciQLopCurve(this, x, y, dataOrder); + return sg; + } + inline SciQLopColorMap* addSciQLopColorMap(QCPAxis* x, QCPAxis* y, const QString& name, SciQLopColorMap::DataOrder dataOrder = SciQLopColorMap::DataOrder::xFirst) { diff --git a/meson.build b/meson.build index 8b8b577..b07dd52 100644 --- a/meson.build +++ b/meson.build @@ -10,6 +10,8 @@ extra_files=files( 'pyproject.toml', 'setup.cfg', '.github/workflows/build_wheels.yml', + 'Docker/Dockerfile', + 'Docker/build.sh', 'README.md', 'COPYING' ) diff --git a/src/SciQLopCurve.cpp b/src/SciQLopCurve.cpp new file mode 100644 index 0000000..3d79954 --- /dev/null +++ b/src/SciQLopCurve.cpp @@ -0,0 +1,118 @@ +/*------------------------------------------------------------------------------ +-- This file is a part of the SciQLop Software +-- Copyright (C) 2023, Plasma Physics Laboratory - CNRS +-- +-- This program is free software; you can redistribute it and/or modify +-- it under the terms of the GNU General Public License as published by +-- the Free Software Foundation; either version 2 of the License, or +-- (at your option) any later version. +-- +-- This program is distributed in the hope that it will be useful, +-- but WITHOUT ANY WARRANTY; without even the implied warranty of +-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +-- GNU General Public License for more details. +-- +-- You should have received a copy of the GNU General Public License +-- along with this program; if not, write to the Free Software +-- Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +-------------------------------------------------------------------------------*/ +/*-- Author : Alexis Jeandet +-- Mail : alexis.jeandet@member.fsf.org +----------------------------------------------------------------------------*/ +#include "SciQLopPlots/SciQLopCurve.hpp" +#include "SciQLopPlots/SciQLopCurveResampler.hpp" + +void SciQLopCurve::_range_changed(const QCPRange& newRange, const QCPRange& oldRange) +{ + this->_resampler->resample(newRange); + emit this->range_changed(newRange, false); +} + +void SciQLopCurve::_setCurveData(std::size_t index, QVector data) +{ + if (index < std::size(_curves)) + _curves[index]->data()->set(std::move(data), true); +} + +void SciQLopCurve::clear_curves(bool curve_already_removed) +{ + if (!curve_already_removed) + { + for (auto curve : _curves) + { + this->_plot()->removePlottable(curve); + } + } + this->_curves.clear(); +} + +void SciQLopCurve::clear_resampler() +{ + connect(this->_resampler_thread, &QThread::finished, this->_resampler, &QThread::deleteLater); + disconnect(this->_resampler, &CurveResampler::setGraphData, this, &SciQLopCurve::_setCurveData); + disconnect(this->_resampler, &CurveResampler::refreshPlot, nullptr, nullptr); + this->_resampler_thread->quit(); + this->_resampler_thread->wait(); + delete this->_resampler_thread; + this->_resampler = nullptr; + this->_resampler_thread = nullptr; +} + +void SciQLopCurve::create_resampler(const QStringList& labels) +{ + this->_resampler = new CurveResampler(_dataOrder, std::size(labels)); + this->_resampler_thread = new QThread(); + this->_resampler->moveToThread(this->_resampler_thread); + this->_resampler_thread->start(QThread::LowPriority); + connect(this->_resampler, &CurveResampler::setGraphData, this, &SciQLopCurve::_setCurveData, + Qt::QueuedConnection); + connect( + this->_resampler, &CurveResampler::refreshPlot, this, + [this]() { this->_plot()->replot(QCustomPlot::rpQueuedReplot); }, Qt::QueuedConnection); +} + +void SciQLopCurve::curve_got_removed_from_plot(QCPCurve* curve) +{ + this->_curves.removeOne(curve); +} + +SciQLopCurve::SciQLopCurve(QCustomPlot* parent, QCPAxis* keyAxis, QCPAxis* valueAxis, + const QStringList& labels, SciQLopGraph::DataOrder dataOrder) + : QObject(parent), _keyAxis { keyAxis }, _valueAxis { valueAxis }, _dataOrder { dataOrder } +{ + create_resampler(labels); + this->create_graphs(labels); +} + +SciQLopCurve::SciQLopCurve( + QCustomPlot* parent, QCPAxis* keyAxis, QCPAxis* valueAxis, SciQLopGraph::DataOrder dataOrder) + : QObject(parent), _keyAxis { keyAxis }, _valueAxis { valueAxis }, _dataOrder { dataOrder } +{ + create_resampler({}); +} + +SciQLopCurve::~SciQLopCurve() +{ + clear_curves(); + clear_resampler(); +} + +void SciQLopCurve::setData(Array_view&& x, Array_view&& y, bool ignoreCurrentRange) +{ + this->_resampler->setData(std::move(x), std::move(y)); +} + +void SciQLopCurve::create_graphs(const QStringList& labels) +{ + if (std::size(_curves)) + clear_curves(); + for (const auto& label : labels) + { + const auto curve = new QCPCurve(_keyAxis, _valueAxis); + _curves.append(curve); + curve->setName(label); + connect(curve, &QCPCurve::destroyed, this, + [this, curve]() { this->curve_got_removed_from_plot(curve); }); + } + _resampler->set_line_count(std::size(_curves)); +}