├── .gitignore ├── README.md ├── ColorMaps.h ├── ExportDialog.h ├── LICENSE ├── FindQwt.cmake ├── ExportDialog.ui ├── ExportDialog.cpp ├── CMakeLists.txt ├── ColorMaps.cpp ├── Waterfallplot.h ├── main.cpp ├── WaterfallData.h └── Waterfallplot.cpp /.gitignore: -------------------------------------------------------------------------------- 1 | build* 2 | CMakeLists.txt.user 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QwtWaterfallplot 2 | A waterfall plot powered with Qwt for Qt/C++ applications. 3 | 4 | Interesting features : 5 | - Vertical axis's (time) labels are falling with waterfall layers. 6 | - Projection of the vertical and horizontal layer on two curves of a particular point of the waterfall. 7 | - Color rescaling as data is preserved (no QImage is used) and colors are computed with each replot. 8 | 9 | ![QwtWaterfallplot in action](https://mmzoughi.files.wordpress.com/2020/01/qwtwaterfallplot-1.png?w=840) 10 | -------------------------------------------------------------------------------- /ColorMaps.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFALLCOLORMAPS_H 2 | #define WATERFALLCOLORMAPS_H 3 | 4 | #include 5 | #include 6 | 7 | namespace ColorMaps 8 | { 9 | 10 | /* x (control point), red, green, blue 11 | * all values must be defined between 0.0 and 1.0 12 | * ControlPoints must be sorted in ascending order of 'x' points. 13 | */ 14 | typedef std::tuple ControlPoint; 15 | typedef std::vector ControlPoints; 16 | 17 | ControlPoints BlackBodyRadiation(); 18 | ControlPoints CoolToWarm(); 19 | ControlPoints Jet(); 20 | 21 | } 22 | 23 | #endif // WATERFALLCOLORMAPS_H 24 | -------------------------------------------------------------------------------- /ExportDialog.h: -------------------------------------------------------------------------------- 1 | #ifndef EXPORTDIALOG_H 2 | #define EXPORTDIALOG_H 3 | 4 | #include 5 | 6 | class ExportDialog : public QDialog 7 | { 8 | Q_OBJECT 9 | typedef QDialog Superclass; 10 | 11 | public: 12 | explicit ExportDialog(QWidget* const parent = nullptr); 13 | ~ExportDialog() override; 14 | 15 | bool getExportWaterfallCurve() const; 16 | bool getExportHorizontalCurve() const; 17 | bool getExportVerticalCurve() const; 18 | 19 | private: 20 | Q_DISABLE_COPY(ExportDialog) 21 | 22 | struct Internals; 23 | Internals* m_Internals; 24 | }; 25 | 26 | #endif // EXPORTDIALOG_H 27 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Amine Mzoughi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /FindQwt.cmake: -------------------------------------------------------------------------------- 1 | # Find Qwt 2 | # ~~~~~~~~ 3 | # Copyright (c) 2010, Tim Sutton 4 | # Redistribution and use is allowed according to the terms of the BSD license. 5 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 6 | # 7 | # Once run this will define: 8 | # 9 | # QWT_FOUND = system has QWT lib 10 | # QWT_LIBRARY = full path to the QWT library 11 | # QWT_INCLUDE_DIR = where to find headers 12 | # 13 | 14 | 15 | set(QWT_LIBRARY_NAMES qwt-qt5 qwt6-qt5 qwt qwt6) 16 | 17 | find_library(QWT_LIBRARY 18 | NAMES ${QWT_LIBRARY_NAMES} 19 | PATHS 20 | /usr/lib 21 | /usr/local/lib 22 | /usr/local/lib/qt5 23 | "$ENV{LIB_DIR}/lib" 24 | "$ENV{LIB}" 25 | ) 26 | 27 | set(_qwt_fw) 28 | if(QWT_LIBRARY MATCHES "/qwt.*\\.framework") 29 | string(REGEX REPLACE "^(.*/qwt.*\\.framework).*$" "\\1" _qwt_fw "${QWT_LIBRARY}") 30 | endif() 31 | 32 | FIND_PATH(QWT_INCLUDE_DIR NAMES qwt.h PATHS 33 | "${_qwt_fw}/Headers" 34 | /usr/include 35 | /usr/local/include 36 | /usr/local/include/qt5 37 | "$ENV{LIB_DIR}/include" 38 | "$ENV{INCLUDE}" 39 | PATH_SUFFIXES qwt-qt5 qwt qwt6 40 | ) 41 | 42 | IF (QWT_INCLUDE_DIR AND QWT_LIBRARY) 43 | SET(QWT_FOUND TRUE) 44 | ENDIF (QWT_INCLUDE_DIR AND QWT_LIBRARY) 45 | 46 | IF (QWT_FOUND) 47 | FILE(READ ${QWT_INCLUDE_DIR}/qwt_global.h qwt_header) 48 | STRING(REGEX REPLACE "^.*QWT_VERSION_STR +\"([^\"]+)\".*$" "\\1" QWT_VERSION_STR "${qwt_header}") 49 | IF (NOT QWT_FIND_QUIETLY) 50 | MESSAGE(STATUS "Found Qwt: ${QWT_LIBRARY} (${QWT_VERSION_STR})") 51 | ENDIF (NOT QWT_FIND_QUIETLY) 52 | ELSE (QWT_FOUND) 53 | IF (QWT_FIND_REQUIRED) 54 | MESSAGE(FATAL_ERROR "Could not find Qwt") 55 | ENDIF (QWT_FIND_REQUIRED) 56 | ENDIF (QWT_FOUND) 57 | -------------------------------------------------------------------------------- /ExportDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | ExportDialog 4 | 5 | 6 | 7 | 0 8 | 0 9 | 400 10 | 300 11 | 12 | 13 | 14 | Plots to export 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | Qt::Vertical 24 | 25 | 26 | QDialogButtonBox::Cancel|QDialogButtonBox::Ok 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | ButtonBox 36 | accepted() 37 | ExportDialog 38 | accept() 39 | 40 | 41 | 248 42 | 254 43 | 44 | 45 | 157 46 | 274 47 | 48 | 49 | 50 | 51 | ButtonBox 52 | rejected() 53 | ExportDialog 54 | reject() 55 | 56 | 57 | 316 58 | 260 59 | 60 | 61 | 286 62 | 274 63 | 64 | 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /ExportDialog.cpp: -------------------------------------------------------------------------------- 1 | #include "ExportDialog.h" 2 | 3 | #include "ui_ExportDialog.h" 4 | 5 | struct ExportDialog::Internals 6 | { 7 | Ui::ExportDialog Ui; 8 | }; 9 | 10 | ExportDialog::ExportDialog(QWidget* const parent) : 11 | QDialog(parent), 12 | m_Internals(new ExportDialog::Internals) 13 | { 14 | m_Internals->Ui.setupUi(this); 15 | 16 | connect(m_Internals->Ui.ButtonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); 17 | connect(m_Internals->Ui.ButtonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); 18 | connect(m_Internals->Ui.PlotsList, &QListWidget::itemChanged, 19 | this, [this](QListWidgetItem* item) 20 | { 21 | if (item->checkState() == Qt::Checked) 22 | { 23 | item->setBackgroundColor(QColor("#ffffb2")); 24 | } 25 | else 26 | { 27 | item->setBackgroundColor(QColor("#ffffff")); 28 | } 29 | }); 30 | 31 | QStringList plotsList; 32 | plotsList << "Horizontal Plot" << "Vertical Plot" << "Waterfall Plot"; 33 | m_Internals->Ui.PlotsList->addItems(plotsList); 34 | 35 | for (int i = 0; i < m_Internals->Ui.PlotsList->count(); ++i) 36 | { 37 | QListWidgetItem* item = m_Internals->Ui.PlotsList->item(i); 38 | item->setFlags(item->flags() | Qt::ItemIsUserCheckable); 39 | item->setCheckState(Qt::Unchecked); 40 | } 41 | } 42 | 43 | ExportDialog::~ExportDialog() 44 | { 45 | delete m_Internals; 46 | } 47 | 48 | bool ExportDialog::getExportHorizontalCurve() const 49 | { 50 | return m_Internals->Ui.PlotsList->item(0)->checkState() == Qt::Checked; 51 | } 52 | 53 | bool ExportDialog::getExportVerticalCurve() const 54 | { 55 | return m_Internals->Ui.PlotsList->item(1)->checkState() == Qt::Checked; 56 | } 57 | 58 | bool ExportDialog::getExportWaterfallCurve() const 59 | { 60 | return m_Internals->Ui.PlotsList->item(2)->checkState() == Qt::Checked; 61 | } 62 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.0) 2 | 3 | project(QwtWaterfallplotExample) 4 | 5 | # Set some Win32 Specific Settings 6 | if(WIN32) 7 | set(GUI_TYPE WIN32) 8 | add_definitions(-DQWT_DLL) 9 | endif(WIN32) 10 | # Set some Apple MacOS Specific settings 11 | if(APPLE) 12 | set(GUI_TYPE MACOSX_BUNDLE) 13 | endif(APPLE) 14 | 15 | find_package(Qt5Widgets REQUIRED) 16 | find_package(Qt5Gui REQUIRED) 17 | find_package(Qt5Core REQUIRED) 18 | find_package(Qt5SerialPort REQUIRED) 19 | find_package(Qt5PrintSupport REQUIRED) 20 | 21 | if(NOT WIN32) 22 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -std=gnu++0x") 23 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Wall -Wextra -Wpedantic -g -O0 -std=gnu++0x") 24 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O2 -Wall -Wextra -Wpedantic -std=gnu++0x") 25 | endif() 26 | 27 | # QWT 28 | INCLUDE(FindQwt.cmake) 29 | find_library(Qwt REQUIRED) 30 | include_directories(${QWT_INCLUDE_DIR}) 31 | 32 | include_directories(${QT_INCLUDES}) 33 | include_directories(${QWT_HEADERS}) 34 | include_directories(.) 35 | 36 | # ============================================================================== 37 | # Source 38 | # ============================================================================== 39 | set(APP_SOURCE main.cpp Waterfallplot.cpp ExportDialog.cpp ColorMaps.cpp) 40 | set(UISrcs ExportDialog.ui) 41 | 42 | # ============================================================================== 43 | # Target 44 | # ============================================================================== 45 | add_executable(qwtwaterfallplot ${GUI_TYPE} ${APP_SOURCE} ${UISrcs} ${MOCSrcs}) 46 | 47 | set_target_properties(qwtwaterfallplot PROPERTIES 48 | AUTOMOC TRUE 49 | AUTORCC TRUE 50 | AUTOUIC TRUE) 51 | 52 | target_link_libraries(qwtwaterfallplot Qt5::Core Qt5::Gui Qt5::Widgets Qt5::SerialPort Qt5::PrintSupport 53 | ${QWT_LIBRARY}) 54 | 55 | target_include_directories(qwtwaterfallplot PRIVATE 56 | ${CMAKE_CURRENT_BINARY_DIR} 57 | ${CMAKE_CURRENT_SOURCE_DIR} 58 | ${CMAKE_SOURCE_DIR}) 59 | 60 | 61 | set_property(TARGET qwtwaterfallplot PROPERTY C_STANDARD 99) 62 | -------------------------------------------------------------------------------- /ColorMaps.cpp: -------------------------------------------------------------------------------- 1 | #include "ColorMaps.h" 2 | 3 | #include 4 | 5 | namespace ColorMaps 6 | { 7 | 8 | ControlPoints BlackBodyRadiation() 9 | { 10 | const double rgbPoints[] = { 11 | 0, 0, 0, 0, 12 | 0.4, 0.901960784314, 0, 0, 13 | 0.8, 0.901960784314, 0.901960784314, 0, 14 | 1, 1, 1, 1 }; 15 | 16 | ControlPoints ctrlPts; 17 | for (size_t rgb = 0; rgb < sizeof(rgbPoints) / sizeof(double); rgb += 4) 18 | { 19 | ctrlPts.push_back(ControlPoint(rgbPoints[rgb], 20 | rgbPoints[rgb + 1], 21 | rgbPoints[rgb + 2], 22 | rgbPoints[rgb + 3])); 23 | } 24 | 25 | return ctrlPts; 26 | } 27 | 28 | ControlPoints CoolToWarm() 29 | { 30 | const double rgbPoints[] = { 31 | 0, 0.23137254902000001, 0.298039215686, 0.75294117647100001, 32 | 0.5, 0.86499999999999999, 0.86499999999999999, 0.86499999999999999, 33 | 1, 0.70588235294099999, 0.015686274509800001, 0.149019607843 }; 34 | 35 | ControlPoints ctrlPts; 36 | for (size_t rgb = 0; rgb < sizeof(rgbPoints) / sizeof(double); rgb += 4) 37 | { 38 | ctrlPts.push_back(ControlPoint(rgbPoints[rgb], 39 | rgbPoints[rgb + 1], 40 | rgbPoints[rgb + 2], 41 | rgbPoints[rgb + 3])); 42 | } 43 | 44 | return ctrlPts; 45 | } 46 | 47 | ControlPoints Jet() 48 | { 49 | QColor darkBlue(Qt::darkBlue); // 0.0 50 | QColor blue(Qt::blue); // 0.2 51 | QColor cyan(Qt::cyan); // 0.4 52 | QColor yellow(Qt::yellow); // 0.6 53 | QColor red(Qt::red); // 0.8 54 | QColor darkRed(Qt::darkRed); // 1.0 55 | 56 | const double rgbPoints[] = { 57 | 0.0, darkBlue.redF(), darkBlue.greenF(), darkBlue.blueF(), 58 | 0.2, blue.redF(), blue.greenF(), blue.blueF(), 59 | 0.4, cyan.redF(), cyan.greenF(), cyan.blueF(), 60 | 0.6, yellow.redF(), yellow.greenF(), yellow.blueF(), 61 | 0.8, red.redF(), red.greenF(), red.blueF(), 62 | 1.0, darkRed.redF(), darkRed.greenF(), darkRed.blueF() }; 63 | 64 | ControlPoints ctrlPts; 65 | for (size_t rgb = 0; rgb < sizeof(rgbPoints) / sizeof(double); rgb += 4) 66 | { 67 | ctrlPts.push_back(ControlPoint(rgbPoints[rgb], 68 | rgbPoints[rgb + 1], 69 | rgbPoints[rgb + 2], 70 | rgbPoints[rgb + 3])); 71 | } 72 | 73 | return ctrlPts; 74 | } 75 | 76 | } 77 | -------------------------------------------------------------------------------- /Waterfallplot.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFALLPLOT_H 2 | #define WATERFALLPLOT_H 3 | 4 | #include 5 | 6 | #include "ColorMaps.h" 7 | #include "WaterfallData.h" 8 | 9 | class QwtPlot; 10 | class QwtPlotCurve; 11 | class QwtPlotMarker; 12 | class QwtPlotPanner; 13 | class QwtPlotPicker; 14 | class QwtPlotSpectrogram; 15 | class QwtPlotZoomer; 16 | 17 | class Waterfallplot : public QWidget 18 | { 19 | public: 20 | Waterfallplot(QWidget* parent, const ColorMaps::ControlPoints& ctrlPts = ColorMaps::Jet()); 21 | ~Waterfallplot() override; 22 | 23 | void setDataDimensions(double dXMin, double dXMax, // X bounds, fixed once for all 24 | const size_t historyExtent, // Will define Y width (number of layers) 25 | const size_t layerPoints); // FFT/Data points in a single layer) 26 | void getDataDimensions(double& dXMin, 27 | double& dXMax, 28 | size_t& historyExtent, 29 | size_t& layerPoints) const; 30 | 31 | bool setMarker(const double x, const double y); 32 | 33 | // view 34 | void replot(bool forceRepaint = false); 35 | void setWaterfallVisibility(const bool bVisible); 36 | void setTitle(const QString& qstrNewTitle); 37 | void setXLabel(const QString& qstrTitle, const int fontPointSize = 12); 38 | void setYLabel(const QString& qstrTitle, const int fontPointSize = 12); 39 | void setZLabel(const QString& qstrTitle, const int fontPointSize = 12); 40 | void setXTooltipUnit(const QString& xUnit); 41 | void setZTooltipUnit(const QString& zUnit); 42 | bool setColorMap(const ColorMaps::ControlPoints& colorMap); 43 | ColorMaps::ControlPoints getColorMap() const; 44 | QwtPlot* getHorizontalCurvePlot() const { return m_plotHorCurve; } 45 | QwtPlot* getVerticalCurvePlot() const { return m_plotVertCurve; } 46 | QwtPlot* getSpectrogramPlot() const { return m_plotSpectrogram; } 47 | 48 | // data 49 | bool addData(const double* const dataPtr, const size_t dataLen, const time_t timestamp); 50 | void setRange(double dLower, double dUpper); 51 | void getRange(double& rangeMin, double& rangeMax) const; 52 | void getDataRange(double& rangeMin, double& rangeMax) const; 53 | void clear(); 54 | time_t getLayerDate(const double y) const; 55 | 56 | double getOffset() const { return (m_data) ? m_data->getOffset() : 0; } 57 | 58 | QString m_xUnit; 59 | QString m_zUnit; 60 | 61 | public slots: 62 | void setPickerEnabled(const bool enabled); 63 | 64 | protected slots: 65 | void autoRescale(const QRectF& rect); 66 | 67 | void selectedPoint(const QPointF& pt); 68 | 69 | protected: 70 | QwtPlot* const m_plotHorCurve = nullptr; 71 | QwtPlot* const m_plotVertCurve = nullptr; 72 | QwtPlot* const m_plotSpectrogram = nullptr; 73 | QwtPlotCurve* m_horCurve = nullptr; 74 | QwtPlotCurve* m_vertCurve = nullptr; 75 | QwtPlotPicker* const m_picker = nullptr; 76 | QwtPlotPanner* const m_panner = nullptr; 77 | QwtPlotSpectrogram* const m_spectrogram = nullptr; 78 | QwtPlotZoomer* const m_zoomer = nullptr; 79 | QwtPlotMarker* const m_horCurveMarker = nullptr; 80 | QwtPlotMarker* const m_vertCurveMarker = nullptr; 81 | 82 | // later, the type can be parametrized when instanciating Waterfallplot 83 | // m_pData will be owned (freed) by m_spectrogram 84 | // Sadly, to avoid compile problems with a templated class, the implementation 85 | // needs to be moved in this header file ! 86 | WaterfallData* m_data = nullptr; 87 | 88 | bool m_bColorBarInitialized = false; 89 | 90 | double* m_horCurveXAxisData = nullptr; 91 | double* m_horCurveYAxisData = nullptr; 92 | 93 | double* m_vertCurveXAxisData = nullptr; 94 | double* m_vertCurveYAxisData = nullptr; 95 | 96 | mutable bool m_inScaleSync = false; 97 | 98 | double m_markerX = 0; 99 | double m_markerY = 0; 100 | 101 | ColorMaps::ControlPoints m_ctrlPts; 102 | 103 | bool m_zoomActive = false; 104 | 105 | protected slots: 106 | void scaleDivChanged(); 107 | 108 | protected: 109 | void updateLayout(); 110 | 111 | void allocateCurvesData(); 112 | void freeCurvesData(); 113 | void setupCurves(); 114 | void updateCurvesData(); 115 | 116 | private: 117 | //Q_DISABLE_COPY(Waterfallplot) 118 | 119 | void alignAxis(int axisId); 120 | void alignAxisForColorBar(); 121 | }; 122 | 123 | #endif // WATERFALLPLOT_ 124 | -------------------------------------------------------------------------------- /main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include 19 | #include 20 | 21 | #include "ExportDialog.h" 22 | #include "Waterfallplot.h" 23 | 24 | class MainWindow: public QMainWindow 25 | { 26 | public: 27 | MainWindow(QWidget * = NULL); 28 | 29 | public slots: 30 | void changeColorMap(); 31 | void exportPlots(); 32 | void playData(); 33 | void clearWaterfall(); 34 | 35 | private: 36 | Waterfallplot* m_waterfall = nullptr; 37 | }; 38 | 39 | MainWindow::MainWindow( QWidget *parent ) : 40 | QMainWindow( parent ) 41 | { 42 | srand(time(nullptr)); 43 | 44 | QWidget *centralWidget = new QWidget(this); 45 | 46 | QVBoxLayout* layout = new QVBoxLayout(centralWidget); 47 | layout->setSpacing(6); 48 | layout->setContentsMargins(11, 11, 11, 11); 49 | 50 | m_waterfall = new Waterfallplot(nullptr); 51 | 52 | m_waterfall->setTitle("Waterfall Demo"); 53 | m_waterfall->setXLabel("Distance (m)", 10); 54 | m_waterfall->setXTooltipUnit("m"); 55 | m_waterfall->setZTooltipUnit("°C"); 56 | m_waterfall->setYLabel("Time", 10); 57 | m_waterfall->setZLabel("Temperature (°C)", 10); 58 | 59 | layout->addWidget(m_waterfall); 60 | 61 | setCentralWidget(centralWidget); 62 | 63 | // Toolbar 64 | QToolBar *toolBar = new QToolBar( this ); 65 | QToolButton *btnExport = new QToolButton( toolBar ); 66 | btnExport->setText( "Export" ); 67 | btnExport->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); 68 | toolBar->addWidget( btnExport ); 69 | connect( btnExport, &QToolButton::clicked, this, &MainWindow::exportPlots ); 70 | 71 | QToolButton *btnChangeColorMap = new QToolButton( toolBar ); 72 | btnChangeColorMap->setText( "Change color map" ); 73 | btnChangeColorMap->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); 74 | toolBar->addWidget( btnChangeColorMap ); 75 | connect( btnChangeColorMap, &QToolButton::clicked, this, &MainWindow::changeColorMap ); 76 | 77 | toolBar->addSeparator(); 78 | 79 | QToolButton* btnPlayData = new QToolButton(toolBar); 80 | btnPlayData->setIcon(style()->standardIcon(QStyle::SP_MediaPlay)); 81 | btnPlayData->setToolButtonStyle(Qt::ToolButtonIconOnly); 82 | btnPlayData->setToolTip("Play 32 random data (layers) on the waterfall view."); 83 | toolBar->addWidget(btnPlayData); 84 | QObject::connect(btnPlayData, &QToolButton::clicked, this, &MainWindow::playData); 85 | 86 | QToolButton* btnPicker = new QToolButton(toolBar); 87 | btnPicker->setText( "Pick" ); 88 | //btnPicker->setIcon( QPixmap( zoom_xpm ) ); 89 | btnPicker->setCheckable( true ); 90 | btnPicker->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); 91 | toolBar->addWidget(btnPicker); 92 | QObject::connect(btnPicker, &QToolButton::toggled, m_waterfall, &Waterfallplot::setPickerEnabled); 93 | 94 | QToolButton* btnClear = new QToolButton(toolBar); 95 | btnClear->setText("Clear"); 96 | btnClear->setToolButtonStyle( Qt::ToolButtonTextUnderIcon ); 97 | toolBar->addWidget(btnClear); 98 | QObject::connect(btnClear, &QToolButton::clicked, this, &MainWindow::clearWaterfall); 99 | 100 | addToolBar(toolBar); 101 | } 102 | 103 | int main( int argc, char **argv ) 104 | { 105 | QApplication a( argc, argv ); 106 | a.setStyle( "Windows" ); 107 | 108 | MainWindow mainWindow; 109 | mainWindow.resize( 1152, 768 ); 110 | mainWindow.show(); 111 | 112 | return a.exec(); 113 | } 114 | 115 | void MainWindow::playData() 116 | { 117 | for (auto i = 0u; i < 32; ++i) 118 | { 119 | std::vector dummyData(126); 120 | std::generate(dummyData.begin(), dummyData.end(), []{ return (std::rand() % 256); }); 121 | 122 | double xMin, xMax; 123 | size_t historyLength, layerPoints; 124 | m_waterfall->getDataDimensions(xMin, xMax, historyLength, layerPoints); 125 | 126 | if (xMin != 0 || xMax != 500 || 127 | 126 != layerPoints || 128 | 64 != historyLength) 129 | { 130 | // log/notify for debug purposes... 131 | m_waterfall->setDataDimensions(0, 500, 64, dummyData.size()); 132 | } 133 | 134 | const bool bRet = m_waterfall->addData(dummyData.data(), dummyData.size(), std::time(nullptr)); 135 | assert(bRet); 136 | 137 | // set the range only once (data range) 138 | static bool s_setRangeOnlyOnce = true; 139 | if (s_setRangeOnlyOnce) 140 | { 141 | double dataRng[2]; 142 | m_waterfall->getDataRange(dataRng[0], dataRng[1]); 143 | m_waterfall->setRange(dataRng[0], dataRng[1]); 144 | s_setRangeOnlyOnce = false; 145 | } 146 | 147 | m_waterfall->replot(true); // true: force repaint 148 | 149 | QThread::msleep(10); 150 | } 151 | } 152 | 153 | void MainWindow::clearWaterfall() 154 | { 155 | m_waterfall->clear(); 156 | m_waterfall->replot(true); // true: force repaint 157 | } 158 | 159 | void MainWindow::exportPlots() 160 | { 161 | ExportDialog dialog(this); 162 | if (dialog.exec() == QDialog::Accepted) 163 | { 164 | QwtPlotRenderer renderer; 165 | 166 | if (dialog.getExportWaterfallCurve()) 167 | { 168 | renderer.exportTo(m_waterfall->getSpectrogramPlot(), "waterfall.pdf"); 169 | } 170 | 171 | if (dialog.getExportHorizontalCurve()) 172 | { 173 | renderer.exportTo(m_waterfall->getHorizontalCurvePlot(), "horizontal_plot.pdf"); 174 | } 175 | 176 | if (dialog.getExportVerticalCurve()) 177 | { 178 | renderer.exportTo(m_waterfall->getVerticalCurvePlot(), "vertical_plot.pdf"); 179 | } 180 | } 181 | } 182 | 183 | void MainWindow::changeColorMap() 184 | { 185 | static bool s_useBBRColorMap = true; 186 | if (s_useBBRColorMap) 187 | { 188 | 189 | m_waterfall->setColorMap(ColorMaps::BlackBodyRadiation()); 190 | m_waterfall->replot(); 191 | 192 | s_useBBRColorMap = false; 193 | } 194 | else 195 | { 196 | m_waterfall->setColorMap(ColorMaps::Jet()); 197 | m_waterfall->replot(); 198 | 199 | s_useBBRColorMap = true; 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /WaterfallData.h: -------------------------------------------------------------------------------- 1 | #ifndef WATERFALLDATA_H 2 | #define WATERFALLDATA_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | template 9 | class WaterfallData : public QwtMatrixRasterData 10 | { 11 | static_assert(std::is_arithmetic::value, "WaterfallData's data must be numeric !"); 12 | 13 | public: 14 | WaterfallData(double dXMin, double dXMax, // X bounds 15 | const size_t historyExtent, // will define Y width 16 | const size_t layerPoints) : 17 | m_data(new T[historyExtent * layerPoints]), 18 | m_offset(0), 19 | m_layerPoints(layerPoints), 20 | m_maxHistoryLength(historyExtent), 21 | m_currentHistoryLength(0), 22 | m_layersTimestamps(new time_t[historyExtent]) 23 | { 24 | if (m_layerPoints == 0 || m_maxHistoryLength == 0) 25 | { 26 | throw "Bad usage of WaterfallData !"; // better: call abort(); 27 | } 28 | 29 | // initialize data with zeroes or the minimal value of T type 30 | clear(); 31 | 32 | // sanitize 33 | if (dXMin > dXMax) 34 | { 35 | std::swap(dXMin, dXMax); 36 | } 37 | 38 | m_xMin = dXMin; 39 | m_xMax = dXMax; 40 | 41 | setInterval(Qt::XAxis, 42 | QwtInterval(dXMin, dXMax, QwtInterval::ExcludeMaximum)); 43 | setInterval(Qt::YAxis, 44 | QwtInterval(m_offset, m_maxHistoryLength + m_offset, QwtInterval::ExcludeMaximum)); 45 | } 46 | 47 | ~WaterfallData() override 48 | { 49 | delete [] m_data; 50 | delete [] m_layersTimestamps; 51 | } 52 | 53 | // overriden methods 54 | double value(double x, double y) const override 55 | { 56 | const QwtInterval xInterval = interval(Qt::XAxis); 57 | const QwtInterval yInterval = interval(Qt::YAxis); 58 | 59 | // + valid 60 | if (!(xInterval.contains(x) && yInterval.contains(y))) 61 | { 62 | return qQNaN(); 63 | } 64 | 65 | // spacing ! 66 | double dx = xInterval.width() / m_layerPoints; 67 | double dy = yInterval.width() / m_maxHistoryLength; 68 | 69 | int row = int((y - yInterval.minValue()) / dy); 70 | int col = int((x - xInterval.minValue()) / dx); 71 | 72 | if (row >= m_maxHistoryLength) 73 | { 74 | row = m_maxHistoryLength - 1; 75 | } 76 | if (col >= m_layerPoints) 77 | { 78 | col = m_layerPoints - 1; 79 | } 80 | 81 | return double(m_data[row * m_layerPoints + col]); 82 | } 83 | 84 | /* pixelHint() returns the geometry of a pixel, that can be used 85 | to calculate the resolution and alignment of the plot item, that is 86 | representing the data. 87 | 88 | - NearestNeighbour\n 89 | pixelHint() returns the surrounding pixel of the top left value 90 | in the matrix. 91 | 92 | - BilinearInterpolation\n 93 | Returns an empty rectangle recommending 94 | to render in target device ( f.e. screen ) resolution. 95 | */ 96 | QRectF pixelHint(const QRectF& area) const override 97 | { 98 | Q_UNUSED(area) 99 | 100 | QRectF rect; 101 | if (resampleMode() == NearestNeighbour) 102 | { 103 | const QwtInterval intervalX = interval(Qt::XAxis); 104 | const QwtInterval intervalY = interval(Qt::YAxis); 105 | if (intervalX.isValid() && intervalY.isValid()) 106 | { 107 | // spacing in X and Y 108 | const double dx = intervalX.width() / m_layerPoints; 109 | const double dy = intervalY.width() / m_maxHistoryLength; 110 | 111 | rect = QRectF(intervalX.minValue(), intervalY.minValue(), 112 | dx, dy); 113 | } 114 | } 115 | return rect; 116 | } 117 | 118 | bool addData(const T* const fftData, const size_t length, const time_t timestamp) 119 | { 120 | if (length != m_layerPoints) 121 | { 122 | return false; 123 | } 124 | 125 | // another solution is to use move_backward and use m_currentHistoryLength 126 | // the only benefit is to move only the filled layers 127 | // and not all the waterfall layers - 1 when this last is not completely filled ! 128 | std::move(m_data + m_layerPoints, 129 | m_data + m_layerPoints + (m_maxHistoryLength - 1) * m_layerPoints, 130 | m_data); 131 | 132 | std::copy(fftData, fftData + length, &m_data[m_layerPoints * (m_maxHistoryLength - 1)]); 133 | 134 | // do the same for the array of timestamps ! 135 | std::move(m_layersTimestamps + 1, 136 | m_layersTimestamps + 1 + (m_maxHistoryLength - 1), 137 | m_layersTimestamps); 138 | 139 | m_layersTimestamps[m_maxHistoryLength - 1] = timestamp; 140 | 141 | if (m_currentHistoryLength < m_maxHistoryLength) 142 | { 143 | ++m_currentHistoryLength; 144 | } 145 | 146 | ++m_offset; 147 | setInterval(Qt::YAxis, 148 | QwtInterval(m_offset, m_maxHistoryLength + m_offset, QwtInterval::ExcludeMaximum)); 149 | 150 | return true; 151 | } 152 | 153 | void clear() 154 | { 155 | std::fill(m_data, m_data + m_layerPoints * m_maxHistoryLength, 0.); 156 | m_currentHistoryLength = 0; 157 | 158 | std::fill(m_layersTimestamps, m_layersTimestamps + m_maxHistoryLength, 0); 159 | 160 | m_offset = 0; 161 | setInterval(Qt::YAxis, 162 | QwtInterval(0, m_maxHistoryLength, QwtInterval::ExcludeMaximum)); 163 | } 164 | 165 | inline size_t getLayerPoints() const { return m_layerPoints; } 166 | inline size_t getMaxHistoryLength() const { return m_maxHistoryLength; } 167 | inline size_t getHistoryLength() const { return m_currentHistoryLength; } 168 | 169 | // representation/view data range (may not be equal to the stored data range) 170 | void setRange(double dLower, double dUpper) 171 | { 172 | if (dLower > dUpper) 173 | { 174 | std::swap(dLower, dUpper); 175 | } 176 | setInterval(Qt::ZAxis, QwtInterval(dLower, dUpper)); 177 | } 178 | 179 | void getRange(double& rangeMin, double& rangeMax) const 180 | { 181 | const QwtInterval& range = interval(Qt::ZAxis); 182 | rangeMin = range.minValue(); 183 | rangeMax = range.maxValue(); 184 | } 185 | 186 | // stored data range ! 187 | void getDataRange(double& rangeMin, double& rangeMax) const 188 | { 189 | if (m_currentHistoryLength > 0) 190 | { 191 | auto resultPair = std::minmax_element( 192 | m_data + (m_maxHistoryLength - m_currentHistoryLength) * m_layerPoints, 193 | m_data + m_layerPoints * m_maxHistoryLength); 194 | rangeMin = double(*resultPair.first); 195 | rangeMax = double(*resultPair.second); 196 | } 197 | else 198 | { 199 | rangeMin = rangeMax = 0; 200 | } 201 | } 202 | 203 | size_t getCurrentHistoryLength() const { return m_currentHistoryLength; } 204 | 205 | time_t getLayerDate(const double y) const 206 | { 207 | const size_t index = y; 208 | if (index < m_maxHistoryLength) 209 | { 210 | return m_layersTimestamps[index]; 211 | } 212 | return 0; 213 | } 214 | 215 | const T* getData() const { return m_data; } 216 | const time_t* getTimes() const { return m_layersTimestamps; } 217 | 218 | double getXMin() const { return m_xMin; } 219 | double getXMax() const { return m_xMax; } 220 | 221 | double getOffset() const { return m_offset; } 222 | 223 | protected: 224 | T* const m_data; 225 | double m_offset; 226 | const size_t m_layerPoints; // fft points 227 | const size_t m_maxHistoryLength; // max number of layers (Y width) 228 | size_t m_currentHistoryLength; // filled layers count 229 | 230 | time_t* const m_layersTimestamps; 231 | 232 | double m_xMin; 233 | double m_xMax; 234 | }; 235 | 236 | #endif // WATERFALLDATA_H 237 | -------------------------------------------------------------------------------- /Waterfallplot.cpp: -------------------------------------------------------------------------------- 1 | #include "Waterfallplot.h" 2 | 3 | // Qt includes 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Qwt includes 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | #include 24 | 25 | // C++ STL and its standard lib includes 26 | #include 27 | 28 | namespace 29 | { 30 | 31 | QwtColorMap* controlPointsToQwtColorMap(const ColorMaps::ControlPoints& ctrlPts) 32 | { 33 | using namespace ColorMaps; 34 | 35 | if (ctrlPts.size() < 2 || 36 | std::get<0>(ctrlPts.front()) != 0. || 37 | std::get<0>(ctrlPts.back()) != 1. || 38 | !std::is_sorted(ctrlPts.cbegin(), ctrlPts.cend(), 39 | [](const ControlPoint& x, const ControlPoint& y) 40 | { 41 | // strict weak ordering 42 | return std::get<0>(x) < std::get<0>(y); 43 | })) 44 | { 45 | return nullptr; 46 | } 47 | 48 | QColor from, to; 49 | from.setRgbF(std::get<1>(ctrlPts.front()), std::get<2>(ctrlPts.front()), std::get<3>(ctrlPts.front())); 50 | to.setRgbF(std::get<1>(ctrlPts.back()), std::get<2>(ctrlPts.back()), std::get<3>(ctrlPts.back())); 51 | 52 | QwtLinearColorMap* lcm = new QwtLinearColorMap(from, to, QwtColorMap::RGB); 53 | 54 | for (size_t i = 1; i < ctrlPts.size() - 1; ++i) 55 | { 56 | QColor cs; 57 | cs.setRgbF(std::get<1>(ctrlPts[i]), std::get<2>(ctrlPts[i]), std::get<3>(ctrlPts[i])); 58 | lcm->addColorStop(std::get<0>(ctrlPts[i]), cs); 59 | } 60 | 61 | return lcm; 62 | } 63 | 64 | class MyZoomer: public QwtPlotZoomer 65 | { 66 | QwtPlotSpectrogram* m_spectro; 67 | const Waterfallplot& m_waterfallPlot; // to retrieve time 68 | mutable QDateTime m_dateTime; 69 | 70 | public: 71 | MyZoomer( QWidget* canvas, QwtPlotSpectrogram* spectro, const Waterfallplot& waterfall ): 72 | QwtPlotZoomer( canvas ), 73 | m_spectro( spectro ), 74 | m_waterfallPlot( waterfall ) 75 | { 76 | Q_ASSERT(m_spectro); 77 | setTrackerMode( AlwaysOn ); 78 | } 79 | 80 | virtual QwtText trackerTextF( const QPointF& pos ) const 81 | { 82 | QColor bg( Qt::white ); 83 | bg.setAlpha( 200 ); 84 | 85 | const double distVal = pos.x(); 86 | QwtText text; 87 | if (m_spectro->data()) 88 | { 89 | QString date; 90 | const double histVal = pos.y(); 91 | time_t timeVal = m_waterfallPlot.getLayerDate(histVal - m_waterfallPlot.getOffset()); 92 | if (timeVal > 0) 93 | { 94 | m_dateTime.setTime_t(timeVal); 95 | date = m_dateTime.toString("dd.MM.yy - hh:mm:ss"); 96 | } 97 | 98 | const double tempVal = m_spectro->data()->value(pos.x(), pos.y()); 99 | text = QString("%1%2, %3: %4%5") 100 | .arg(distVal) 101 | .arg(m_waterfallPlot.m_xUnit) 102 | .arg(date) 103 | .arg(tempVal) 104 | .arg(m_waterfallPlot.m_zUnit); 105 | } 106 | else 107 | { 108 | text = QString("%1%2: -%3") 109 | .arg(distVal) 110 | .arg(m_waterfallPlot.m_xUnit) 111 | .arg(m_waterfallPlot.m_zUnit); 112 | } 113 | 114 | text.setBackgroundBrush( QBrush( bg ) ); 115 | return text; 116 | } 117 | }; 118 | 119 | class WaterfallTimeScaleDraw: public QwtScaleDraw 120 | { 121 | const Waterfallplot& m_waterfallPlot; 122 | mutable QDateTime m_dateTime; 123 | 124 | public: 125 | WaterfallTimeScaleDraw(const Waterfallplot& waterfall) : 126 | m_waterfallPlot(waterfall) 127 | { 128 | } 129 | 130 | // make it public ! 131 | using QwtScaleDraw::invalidateCache; 132 | 133 | virtual QwtText label(double v) const 134 | { 135 | time_t ret = m_waterfallPlot.getLayerDate(v - m_waterfallPlot.getOffset()); 136 | if (ret > 0) 137 | { 138 | m_dateTime.setTime_t(ret); 139 | // need something else other than time_t to have 'zzz' 140 | //return m_dateTime.toString("hh:mm:ss:zzz"); 141 | return m_dateTime.toString("dd.MM.yy\nhh:mm:ss"); 142 | } 143 | return QwtText(); 144 | } 145 | }; 146 | 147 | } 148 | 149 | Waterfallplot::Waterfallplot(QWidget* parent, const ColorMaps::ControlPoints& ctrlPts /*= ColorMaps::Jet()*/) : 150 | QWidget(parent), 151 | m_plotHorCurve(new QwtPlot), 152 | m_plotVertCurve(new QwtPlot), 153 | m_plotSpectrogram(new QwtPlot), 154 | m_picker(new QwtPlotPicker(QwtPlot::xBottom, QwtPlot::yLeft, 155 | QwtPlotPicker::CrossRubberBand, QwtPicker::AlwaysOn, m_plotSpectrogram->canvas())), 156 | m_panner(new QwtPlotPanner(m_plotSpectrogram->canvas())), 157 | m_spectrogram(new QwtPlotSpectrogram), 158 | m_zoomer(new MyZoomer(m_plotSpectrogram->canvas(), m_spectrogram, *this)), 159 | m_horCurveMarker(new QwtPlotMarker), 160 | m_vertCurveMarker(new QwtPlotMarker), 161 | m_ctrlPts(ctrlPts) 162 | { 163 | //m_plotHorCurve->setFixedHeight(200); 164 | //m_plotVertCurve->setFixedWidth(400); 165 | 166 | m_plotHorCurve->setAutoReplot(false); 167 | m_plotVertCurve->setAutoReplot(false); 168 | m_plotSpectrogram->setAutoReplot(false); 169 | 170 | m_spectrogram->setRenderThreadCount(0); // use system specific thread count 171 | m_spectrogram->setCachePolicy(QwtPlotRasterItem::PaintCache); 172 | m_spectrogram->attach(m_plotSpectrogram); 173 | 174 | // Setup color map 175 | setColorMap(m_ctrlPts); 176 | 177 | // We need to enable yRight axis in order to align it with the spectrogram's one 178 | m_plotHorCurve->enableAxis(QwtPlot::yRight); 179 | m_plotHorCurve->axisWidget(QwtPlot::yRight)->scaleDraw()->enableComponent(QwtScaleDraw::Ticks, false); 180 | m_plotHorCurve->axisWidget(QwtPlot::yRight)->scaleDraw()->enableComponent(QwtScaleDraw::Labels, false); 181 | QPalette palette = m_plotHorCurve->axisWidget(QwtPlot::yRight)->palette(); 182 | palette.setColor(QPalette::WindowText, Qt::white); 183 | palette.setColor(QPalette::Text, Qt::white); 184 | m_plotHorCurve->axisWidget(QwtPlot::yRight)->setPalette(palette); 185 | 186 | // Auto rescale 187 | m_plotHorCurve->setAxisAutoScale(QwtPlot::xBottom, true); 188 | m_plotHorCurve->setAxisAutoScale(QwtPlot::yLeft, false); 189 | m_plotHorCurve->setAxisAutoScale(QwtPlot::yRight, true); 190 | m_plotVertCurve->setAxisAutoScale(QwtPlot::xBottom, false); 191 | m_plotVertCurve->setAxisAutoScale(QwtPlot::yLeft, true); 192 | m_plotVertCurve->setAxisAutoScale(QwtPlot::yRight, true); 193 | m_plotSpectrogram->setAxisAutoScale(QwtPlot::xBottom, true); 194 | m_plotSpectrogram->setAxisAutoScale(QwtPlot::yLeft, true); 195 | 196 | // the canvas should be perfectly aligned to the boundaries of your curve. 197 | m_plotSpectrogram->axisScaleEngine(QwtPlot::xBottom)->setAttribute(QwtScaleEngine::Floating, true); 198 | m_plotSpectrogram->axisScaleEngine(QwtPlot::yLeft)->setAttribute(QwtScaleEngine::Floating, true); 199 | m_plotSpectrogram->plotLayout()->setAlignCanvasToScales(true); 200 | 201 | m_plotHorCurve->axisScaleEngine(QwtPlot::xBottom)->setAttribute(QwtScaleEngine::Floating, true); 202 | m_plotHorCurve->axisScaleEngine(QwtPlot::yLeft)->setAttribute(QwtScaleEngine::Floating, true); 203 | m_plotHorCurve->axisScaleEngine(QwtPlot::yRight)->setAttribute(QwtScaleEngine::Floating, true); 204 | m_plotHorCurve->plotLayout()->setAlignCanvasToScales(true); 205 | 206 | m_plotVertCurve->axisScaleEngine(QwtPlot::xBottom)->setAttribute(QwtScaleEngine::Floating, true); 207 | m_plotVertCurve->axisScaleEngine(QwtPlot::yLeft)->setAttribute(QwtScaleEngine::Floating, true); 208 | m_plotVertCurve->axisScaleEngine(QwtPlot::yRight)->setAttribute(QwtScaleEngine::Floating, true); 209 | m_plotVertCurve->plotLayout()->setAlignCanvasToScales(true); 210 | 211 | m_plotSpectrogram->setAutoFillBackground(true); 212 | m_plotSpectrogram->setPalette(Qt::white); 213 | m_plotSpectrogram->setCanvasBackground(Qt::white); 214 | 215 | m_plotHorCurve->setAutoFillBackground(true); 216 | m_plotHorCurve->setPalette(Qt::white); 217 | m_plotHorCurve->setCanvasBackground(Qt::white); 218 | 219 | m_plotVertCurve->setAutoFillBackground(true); 220 | m_plotVertCurve->setPalette(Qt::white); 221 | m_plotVertCurve->setCanvasBackground(Qt::white); 222 | 223 | QwtPlotCanvas* const spectroCanvas = dynamic_cast(m_plotSpectrogram->canvas()); 224 | spectroCanvas->setFrameStyle(QFrame::NoFrame); 225 | 226 | QwtPlotCanvas* const horCurveCanvas = dynamic_cast(m_plotHorCurve->canvas()); 227 | horCurveCanvas->setFrameStyle(QFrame::NoFrame); 228 | 229 | QwtPlotCanvas* const vertCurveCanvas = dynamic_cast(m_plotVertCurve->canvas()); 230 | vertCurveCanvas->setFrameStyle(QFrame::NoFrame); 231 | 232 | // Y axis labels should represent the insert time of a layer (fft) 233 | m_plotSpectrogram->setAxisScaleDraw(QwtPlot::yLeft, new WaterfallTimeScaleDraw(*this)); 234 | //m_plot->setAxisLabelRotation(QwtPlot::yLeft, -50.0); 235 | //m_plot->setAxisLabelAlignment(QwtPlot::yLeft, Qt::AlignLeft | Qt::AlignBottom); 236 | 237 | // test color bar... 238 | m_plotSpectrogram->enableAxis(QwtPlot::yRight); 239 | QwtScaleWidget* axis = m_plotSpectrogram->axisWidget(QwtPlot::yRight); 240 | axis->setColorBarEnabled(true); 241 | axis->setColorBarWidth(20); 242 | 243 | #if 1 244 | QGridLayout* const gridLayout = new QGridLayout(this); 245 | gridLayout->addWidget(m_plotHorCurve, 0, 1); 246 | gridLayout->addWidget(m_plotSpectrogram, 1, 1); 247 | gridLayout->addWidget(m_plotVertCurve, 1, 0); 248 | gridLayout->setContentsMargins(0, 0, 0, 0); 249 | gridLayout->setSpacing(5); 250 | 251 | gridLayout->setRowStretch(0, 1); 252 | gridLayout->setRowStretch(1, 3); 253 | 254 | gridLayout->setColumnStretch(0, 1); 255 | gridLayout->setColumnStretch(1, 4); 256 | 257 | #else 258 | QVBoxLayout* layout = new QVBoxLayout(this); 259 | //layout->setSpacing(6); 260 | //layout->setContentsMargins(11, 11, 11, 11); 261 | //m_plotCurve->setFixedHeight(200); 262 | layout->addWidget(m_plotHorCurve, 20); 263 | layout->addWidget(m_plotSpectrogram, 60); 264 | layout->addWidget(m_plotVertCurve, 20); 265 | #endif 266 | 267 | QSizePolicy policy; 268 | policy.setVerticalPolicy(QSizePolicy::Ignored); 269 | policy.setHorizontalPolicy(QSizePolicy::Ignored); 270 | m_plotHorCurve->setSizePolicy(policy); 271 | m_plotVertCurve->setSizePolicy(policy); 272 | m_plotSpectrogram->setSizePolicy(policy); 273 | setSizePolicy(policy); 274 | 275 | // TODO : add align stuff (plotmatrix) 276 | 277 | // Zoomer - brought to you from the experimentations with G1X Brillouin plot ! 278 | m_zoomer->setMousePattern(QwtEventPattern::MouseSelect2, Qt::RightButton, Qt::ControlModifier); 279 | m_zoomer->setMousePattern(QwtEventPattern::MouseSelect3, Qt::RightButton); 280 | const QColor c( Qt::darkBlue ); 281 | //const QColor c(Qt::lightGray); 282 | m_zoomer->setRubberBandPen(c); 283 | m_zoomer->setTrackerPen(c); 284 | 285 | QObject::connect(m_zoomer, &QwtPlotZoomer::zoomed, this, &Waterfallplot::autoRescale); 286 | 287 | /*m_plotCurve->canvas()->setToolTip( 288 | "Zooming:\n" 289 | "- Mouse left button: zoom in an area by drawing a rectangle.\n" 290 | "- Mouse right button: previous zoomed in area.\n" 291 | "- Ctrl + mouse right button : zoom out to spectrogram full size.");*/ 292 | 293 | m_picker->setStateMachine( new QwtPickerDragPointMachine() ); 294 | m_picker->setRubberBandPen( QColor( Qt::green ) ); 295 | m_picker->setRubberBand( QwtPicker::CrossRubberBand ); 296 | //m_picker->setTrackerPen( QColor( Qt::white ) ); 297 | m_picker->setTrackerMode(QwtPicker::AlwaysOff); 298 | m_picker->setEnabled(false); 299 | connect(m_picker, static_cast(&QwtPlotPicker::selected), 300 | this, &Waterfallplot::selectedPoint); 301 | connect(m_picker, static_cast(&QwtPlotPicker::moved), 302 | this, &Waterfallplot::selectedPoint); 303 | 304 | m_panner->setMouseButton(Qt::MidButton); 305 | 306 | connect(m_plotHorCurve->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, 307 | this, &Waterfallplot::scaleDivChanged, Qt::QueuedConnection); 308 | connect(m_plotSpectrogram->axisWidget(QwtPlot::xBottom), &QwtScaleWidget::scaleDivChanged, 309 | this, &Waterfallplot::scaleDivChanged, Qt::QueuedConnection); 310 | connect(m_plotSpectrogram->axisWidget(QwtPlot::yLeft), &QwtScaleWidget::scaleDivChanged, 311 | this, &Waterfallplot::scaleDivChanged, Qt::QueuedConnection); 312 | 313 | //m_plotHorCurve->setTitle("Some plot"); 314 | /* you shouldn't put a title as when the window shrinks in size, m_plotVertCurve and m_plotSpectrogram Y axis 315 | * will misalign, currently, in Qwt there's no way to avoid titles to misalign axis */ 316 | m_plotVertCurve->setTitle(" "); 317 | 318 | { 319 | QwtPlotGrid* horCurveGrid = new QwtPlotGrid; 320 | horCurveGrid->enableXMin( true ); 321 | horCurveGrid->setMinorPen( QPen( Qt::gray, 0 , Qt::DotLine ) ); 322 | horCurveGrid->setMajorPen( QPen( Qt::gray, 0 , Qt::DotLine ) ); 323 | horCurveGrid->attach(m_plotHorCurve); 324 | 325 | QwtPlotGrid* vertCurveGrid = new QwtPlotGrid; 326 | vertCurveGrid->enableXMin( true ); 327 | vertCurveGrid->setMinorPen( QPen( Qt::gray, 0 , Qt::DotLine ) ); 328 | vertCurveGrid->setMajorPen( QPen( Qt::gray, 0 , Qt::DotLine ) ); 329 | vertCurveGrid->attach(m_plotVertCurve); 330 | } 331 | 332 | { 333 | m_horCurveMarker->setLineStyle(QwtPlotMarker::VLine); 334 | m_horCurveMarker->setLinePen(Qt::red, 0, Qt::SolidLine); 335 | m_horCurveMarker->attach(m_plotHorCurve); 336 | 337 | m_vertCurveMarker->setLineStyle( QwtPlotMarker::HLine ); 338 | m_vertCurveMarker->setLinePen(Qt::red, 0, Qt::SolidLine ); 339 | m_vertCurveMarker->attach(m_plotVertCurve); 340 | } 341 | } 342 | 343 | Waterfallplot::~Waterfallplot() 344 | { 345 | freeCurvesData(); 346 | } 347 | 348 | // From G1x Brillouin plot... 349 | void Waterfallplot::autoRescale(const QRectF& rect) 350 | { 351 | Q_UNUSED(rect) 352 | 353 | if (m_zoomer->zoomRectIndex() == 0) 354 | { 355 | // Rescale axis to data range 356 | m_plotSpectrogram->setAxisAutoScale(QwtPlot::xBottom, true); 357 | m_plotSpectrogram->setAxisAutoScale(QwtPlot::yLeft, true); 358 | 359 | m_zoomer->setZoomBase(); 360 | 361 | m_zoomActive = false; 362 | } 363 | else 364 | { 365 | m_zoomActive = true; 366 | } 367 | } 368 | 369 | void Waterfallplot::setDataDimensions(double dXMin, double dXMax, 370 | const size_t historyExtent, 371 | const size_t layerPoints) 372 | { 373 | // NB: m_data is just for convenience ! 374 | m_data = new WaterfallData(dXMin, dXMax, historyExtent, layerPoints); 375 | m_spectrogram->setData(m_data); // NB: owner of the data is m_spectrogram ! 376 | 377 | setupCurves(); 378 | freeCurvesData(); 379 | allocateCurvesData(); 380 | 381 | // After changing data dimensions, we need to reset curves markers 382 | // to show the last received data on the horizontal axis and the history 383 | // of the middle point 384 | m_markerX = (dXMax - dXMin) / 2; 385 | m_markerY = historyExtent - 1; 386 | 387 | m_horCurveMarker->setValue(m_markerX, 0.0); 388 | m_vertCurveMarker->setValue(0.0, m_markerY); 389 | 390 | // scale x 391 | m_plotHorCurve->setAxisScale(QwtPlot::xBottom, dXMin, dXMax); 392 | m_plotSpectrogram->setAxisScale(QwtPlot::xBottom, dXMin, dXMax); 393 | } 394 | 395 | void Waterfallplot::getDataDimensions(double& dXMin, 396 | double& dXMax, 397 | size_t& historyExtent, 398 | size_t& layerPoints) const 399 | { 400 | if (m_data) 401 | { 402 | dXMin = m_data->getXMin(); 403 | dXMax = m_data->getXMax(); 404 | historyExtent = m_data->getMaxHistoryLength(); 405 | layerPoints = m_data->getLayerPoints(); 406 | } 407 | else 408 | { 409 | dXMin = 0; 410 | dXMax = 0; 411 | historyExtent = 0; 412 | layerPoints = 0; 413 | } 414 | } 415 | 416 | void Waterfallplot::replot(bool forceRepaint /*= false*/) 417 | { 418 | if (!m_plotSpectrogram->isVisible()) 419 | { 420 | // temporary solution for older Qwt versions 421 | QApplication::postEvent(m_plotHorCurve, new QEvent(QEvent::LayoutRequest)); 422 | QApplication::postEvent(m_plotVertCurve, new QEvent(QEvent::LayoutRequest)); 423 | QApplication::postEvent(m_plotSpectrogram, new QEvent(QEvent::LayoutRequest)); 424 | } 425 | 426 | updateLayout(); 427 | 428 | /*m_plotHorCurve->replot(); 429 | m_plotVertCurve->replot(); 430 | m_plotSpectrogram->replot();*/ 431 | 432 | if (forceRepaint) 433 | { 434 | m_plotHorCurve->repaint(); 435 | m_plotVertCurve->repaint(); 436 | m_plotSpectrogram->repaint(); 437 | } 438 | } 439 | 440 | void Waterfallplot::setWaterfallVisibility(const bool bVisible) 441 | { 442 | // useful ? complete ? 443 | m_spectrogram->setVisible(bVisible); 444 | } 445 | 446 | bool Waterfallplot::addData(const double* const dataPtr, const size_t dataLen, const time_t timestamp) 447 | { 448 | if (!m_data) 449 | { 450 | return false; 451 | } 452 | 453 | const bool bRet = m_data->addData(dataPtr, dataLen, timestamp); 454 | if (bRet) 455 | { 456 | updateCurvesData(); 457 | 458 | // refresh spectrogram content and Y axis labels 459 | //m_spectrogram->invalidateCache(); 460 | 461 | auto const ySpectroLeftAxis = static_cast( 462 | m_plotSpectrogram->axisScaleDraw(QwtPlot::yLeft)); 463 | ySpectroLeftAxis->invalidateCache(); 464 | 465 | auto const yHistoLeftAxis = static_cast( 466 | m_plotVertCurve->axisScaleDraw(QwtPlot::yLeft)); 467 | yHistoLeftAxis->invalidateCache(); 468 | 469 | const double currentOffset = getOffset(); 470 | const size_t maxHistory = m_data->getMaxHistoryLength(); 471 | 472 | const QwtScaleDiv& yDiv = m_plotSpectrogram->axisScaleDiv(QwtPlot::yLeft); 473 | const double yMin = (m_zoomActive) ? yDiv.lowerBound() + 1 : currentOffset; 474 | const double yMax = (m_zoomActive) ? yDiv.upperBound() + 1 : maxHistory + currentOffset; 475 | 476 | m_plotSpectrogram->setAxisScale(QwtPlot::yLeft, yMin, yMax); 477 | m_plotVertCurve->setAxisScale(QwtPlot::yLeft, yMin, yMax); 478 | 479 | m_vertCurveMarker->setValue(0.0, m_markerY + currentOffset); 480 | } 481 | return bRet; 482 | } 483 | 484 | void Waterfallplot::setRange(double dLower, double dUpper) 485 | { 486 | if (dLower > dUpper) 487 | { 488 | std::swap(dLower, dUpper); 489 | } 490 | 491 | if (m_plotSpectrogram->axisEnabled(QwtPlot::yRight)) 492 | { 493 | m_plotSpectrogram->setAxisScale(QwtPlot::yRight, dLower, dUpper); 494 | 495 | QwtScaleWidget* axis = m_plotSpectrogram->axisWidget(QwtPlot::yRight); 496 | if (axis->isColorBarEnabled()) 497 | { 498 | // Waiting a proper method to get a reference to the QwtInterval 499 | // instead of resetting a new color map to the axis ! 500 | QwtColorMap* colorMap; 501 | if (m_bColorBarInitialized) 502 | { 503 | colorMap = const_cast(axis->colorMap()); 504 | } 505 | else 506 | { 507 | colorMap = controlPointsToQwtColorMap(m_ctrlPts); 508 | m_bColorBarInitialized = true; 509 | } 510 | axis->setColorMap(QwtInterval(dLower, dUpper), colorMap); 511 | } 512 | } 513 | 514 | // set vertical plot's X axis and horizontal plot's Y axis scales to the color bar min/max 515 | m_plotHorCurve->setAxisScale(QwtPlot::yLeft, dLower, dUpper); 516 | m_plotVertCurve->setAxisScale(QwtPlot::xBottom, dLower, dUpper); 517 | 518 | if (m_data) 519 | { 520 | m_data->setRange(dLower, dUpper); 521 | } 522 | 523 | m_spectrogram->invalidateCache(); 524 | } 525 | 526 | void Waterfallplot::getRange(double& rangeMin, double& rangeMax) const 527 | { 528 | if (m_data) 529 | { 530 | m_data->getRange(rangeMin, rangeMax); 531 | } 532 | else 533 | { 534 | rangeMin = 0; 535 | rangeMax = 1; 536 | } 537 | } 538 | 539 | void Waterfallplot::getDataRange(double& rangeMin, double& rangeMax) const 540 | { 541 | if (m_data) 542 | { 543 | m_data->getDataRange(rangeMin, rangeMax); 544 | } 545 | else 546 | { 547 | rangeMin = 0; 548 | rangeMax = 1; 549 | } 550 | } 551 | 552 | void Waterfallplot::clear() 553 | { 554 | if (m_data) 555 | { 556 | m_data->clear(); 557 | } 558 | 559 | setupCurves(); 560 | freeCurvesData(); 561 | allocateCurvesData(); 562 | } 563 | 564 | time_t Waterfallplot::getLayerDate(const double y) const 565 | { 566 | return m_data ? m_data->getLayerDate(y) : 0; 567 | } 568 | 569 | void Waterfallplot::setTitle(const QString& qstrNewTitle) 570 | { 571 | m_plotSpectrogram->setTitle(qstrNewTitle); 572 | } 573 | 574 | void Waterfallplot::setXLabel(const QString& qstrTitle, const int fontPointSize /*= 12*/) 575 | { 576 | QFont font; 577 | font.setPointSize(fontPointSize); 578 | 579 | QwtText title; 580 | title.setText(qstrTitle); 581 | title.setFont(font); 582 | 583 | m_plotSpectrogram->setAxisTitle(QwtPlot::xBottom, title); 584 | } 585 | 586 | void Waterfallplot::setYLabel(const QString& qstrTitle, const int fontPointSize /*= 12*/) 587 | { 588 | QFont font; 589 | font.setPointSize(fontPointSize); 590 | 591 | QwtText title; 592 | title.setText(qstrTitle); 593 | title.setFont(font); 594 | 595 | m_plotSpectrogram->setAxisTitle(QwtPlot::yLeft, title); 596 | } 597 | 598 | void Waterfallplot::setZLabel(const QString& qstrTitle, const int fontPointSize /*= 12*/) 599 | { 600 | QFont font; 601 | font.setPointSize(fontPointSize); 602 | 603 | QwtText title; 604 | title.setText(qstrTitle); 605 | title.setFont(font); 606 | 607 | m_plotSpectrogram->setAxisTitle(QwtPlot::yRight, title); 608 | m_plotHorCurve->setAxisTitle(QwtPlot::yLeft, title); 609 | m_plotHorCurve->setAxisTitle(QwtPlot::yRight, title); 610 | m_plotVertCurve->setAxisTitle(QwtPlot::xBottom, title); 611 | } 612 | 613 | void Waterfallplot::setXTooltipUnit(const QString& xUnit) 614 | { 615 | m_xUnit = xUnit; 616 | } 617 | 618 | void Waterfallplot::setZTooltipUnit(const QString& zUnit) 619 | { 620 | m_zUnit = zUnit; 621 | } 622 | 623 | bool Waterfallplot::setColorMap(const ColorMaps::ControlPoints& colorMap) 624 | { 625 | QwtColorMap* spectrogramColorMap = controlPointsToQwtColorMap(colorMap); 626 | if (!spectrogramColorMap) 627 | { 628 | return false; 629 | } 630 | m_ctrlPts = colorMap; 631 | m_spectrogram->setColorMap(spectrogramColorMap); 632 | 633 | if (m_plotSpectrogram->axisEnabled(QwtPlot::yRight)) 634 | { 635 | QwtScaleWidget* axis = m_plotSpectrogram->axisWidget(QwtPlot::yRight); 636 | if (axis->isColorBarEnabled()) 637 | { 638 | double dLower; 639 | double dUpper; 640 | getRange(dLower, dUpper); 641 | 642 | axis->setColorMap(QwtInterval(dLower, dUpper), controlPointsToQwtColorMap(m_ctrlPts)); 643 | } 644 | } 645 | 646 | m_spectrogram->invalidateCache(); 647 | 648 | return true; 649 | } 650 | 651 | ColorMaps::ControlPoints Waterfallplot::getColorMap() const 652 | { 653 | return m_ctrlPts; 654 | } 655 | 656 | void Waterfallplot::scaleDivChanged() 657 | { 658 | // apparently, m_inScaleSync is a hack that can be replaced by 659 | // blocking signals on a widget but that could be cumbersome 660 | // or not possible as the Qwt API doesn't provide any mean to do that 661 | if (m_inScaleSync) 662 | { 663 | return; 664 | } 665 | m_inScaleSync = true; 666 | 667 | QwtPlot* updatedPlot; 668 | int axisId; 669 | if (m_plotHorCurve->axisWidget(QwtPlot::xBottom) == sender()) 670 | { 671 | updatedPlot = m_plotHorCurve; 672 | axisId = QwtPlot::xBottom; 673 | } 674 | else if (m_plotSpectrogram->axisWidget(QwtPlot::xBottom) == sender()) 675 | { 676 | updatedPlot = m_plotSpectrogram; 677 | axisId = QwtPlot::xBottom; 678 | } 679 | else if (m_plotSpectrogram->axisWidget(QwtPlot::yLeft) == sender()) 680 | { 681 | updatedPlot = m_plotSpectrogram; 682 | axisId = QwtPlot::yLeft; 683 | } 684 | else 685 | { 686 | updatedPlot = nullptr; 687 | } 688 | 689 | if (updatedPlot) 690 | { 691 | QwtPlot* plotToUpdate; 692 | if (axisId == QwtPlot::xBottom) 693 | { 694 | plotToUpdate = (updatedPlot == m_plotHorCurve) ? m_plotSpectrogram : m_plotHorCurve; 695 | } 696 | else 697 | { 698 | plotToUpdate = m_plotVertCurve; 699 | } 700 | 701 | plotToUpdate->setAxisScaleDiv(axisId, updatedPlot->axisScaleDiv(axisId)); 702 | updateLayout(); 703 | } 704 | 705 | m_inScaleSync = false; 706 | } 707 | 708 | void Waterfallplot::alignAxis(int axisId) 709 | { 710 | // 1. Align Vertical Axis (only left or right) 711 | double maxExtent = 0; 712 | 713 | { 714 | QwtScaleWidget* scaleWidget = m_plotHorCurve->axisWidget(axisId); 715 | 716 | QwtScaleDraw* sd = scaleWidget->scaleDraw(); 717 | sd->setMinimumExtent(0.0); 718 | 719 | const double extent = sd->extent(scaleWidget->font()); 720 | if (extent > maxExtent) 721 | { 722 | maxExtent = extent; 723 | } 724 | } 725 | 726 | { 727 | QwtScaleWidget* scaleWidget = m_plotSpectrogram->axisWidget(axisId); 728 | 729 | QwtScaleDraw* sd = scaleWidget->scaleDraw(); 730 | sd->setMinimumExtent( 0.0 ); 731 | 732 | const double extent = sd->extent(scaleWidget->font()); 733 | if (extent > maxExtent) 734 | { 735 | maxExtent = extent; 736 | } 737 | } 738 | 739 | { 740 | QwtScaleWidget* scaleWidget = m_plotHorCurve->axisWidget(axisId); 741 | scaleWidget->scaleDraw()->setMinimumExtent(maxExtent); 742 | } 743 | 744 | { 745 | QwtScaleWidget* scaleWidget = m_plotSpectrogram->axisWidget(axisId); 746 | scaleWidget->scaleDraw()->setMinimumExtent(maxExtent); 747 | } 748 | } 749 | 750 | void Waterfallplot::alignAxisForColorBar() 751 | { 752 | auto s1 = m_plotSpectrogram->axisWidget(QwtPlot::yRight); 753 | auto s2 = m_plotHorCurve->axisWidget(QwtPlot::yRight); 754 | 755 | s2->scaleDraw()->setMinimumExtent( 0.0 ); 756 | 757 | qreal extent = s1->scaleDraw()->extent(s1->font()); 758 | extent -= s2->scaleDraw()->extent(s2->font()); 759 | extent += s1->colorBarWidth() + s1->spacing(); 760 | 761 | s2->scaleDraw()->setMinimumExtent(extent); 762 | } 763 | 764 | void Waterfallplot::updateLayout() 765 | { 766 | // 1. Align Vertical Axis (only left or right) 767 | alignAxis(QwtPlot::yLeft); 768 | alignAxisForColorBar(); 769 | 770 | // 2. Replot 771 | m_plotHorCurve->replot(); 772 | m_plotVertCurve->replot(); 773 | m_plotSpectrogram->replot(); 774 | } 775 | 776 | void Waterfallplot::updateCurvesData() 777 | { 778 | // refresh curve's data 779 | const size_t currentHistory = m_data->getHistoryLength(); 780 | const size_t layerPts = m_data->getLayerPoints(); 781 | const size_t maxHistory = m_data->getMaxHistoryLength(); 782 | const double* wfData = m_data->getData(); 783 | 784 | const size_t markerY = m_markerY; 785 | if (markerY >= maxHistory) 786 | { 787 | return; 788 | } 789 | 790 | if (m_horCurveXAxisData && m_horCurveYAxisData) 791 | { 792 | std::copy(wfData + layerPts * markerY, 793 | wfData + layerPts * (markerY + 1), 794 | m_horCurveYAxisData); 795 | m_horCurve->setRawSamples(m_horCurveXAxisData, m_horCurveYAxisData, layerPts); 796 | } 797 | 798 | const double offset = m_data->getOffset(); 799 | 800 | if (currentHistory > 0 && m_vertCurveXAxisData && m_vertCurveYAxisData) 801 | { 802 | size_t dataIndex = 0; 803 | for (size_t layer = maxHistory - currentHistory; layer < maxHistory; ++layer, ++dataIndex) 804 | { 805 | const double z = m_data->value(m_markerX, layer + size_t(offset)); 806 | const double t = double(layer) + offset; 807 | m_vertCurveXAxisData[dataIndex] = z; 808 | m_vertCurveYAxisData[dataIndex] = t; 809 | } 810 | 811 | m_vertCurve->setRawSamples(m_vertCurveXAxisData, m_vertCurveYAxisData, currentHistory); 812 | 813 | //auto resultPair = std::minmax_element(m_vertCurveXAxisData, m_vertCurveXAxisData + currentHistory); 814 | //const double rangeMin = *resultPair.first; 815 | //const double rangeMax = *resultPair.second; 816 | 817 | //m_plotVertCurve->setAxisScale(QwtPlot::xBottom, rangeMin, rangeMax); 818 | } 819 | } 820 | 821 | void Waterfallplot::allocateCurvesData() 822 | { 823 | if (m_horCurveXAxisData || m_horCurveYAxisData || m_vertCurveXAxisData || 824 | m_vertCurveYAxisData || !m_data) 825 | { 826 | return; 827 | } 828 | 829 | const size_t layerPoints = m_data->getLayerPoints(); 830 | const double dXMin = m_data->getXMin(); 831 | const double dXMax = m_data->getXMax(); 832 | const size_t historyExtent = m_data->getMaxHistoryLength(); 833 | 834 | m_horCurveXAxisData = new double[layerPoints]; 835 | m_horCurveYAxisData = new double[layerPoints]; 836 | m_vertCurveXAxisData = new double[historyExtent]; 837 | m_vertCurveYAxisData = new double[historyExtent]; 838 | 839 | // generate curve X axis data 840 | const double dx = (dXMax - dXMin) / layerPoints; // x spacing 841 | m_horCurveXAxisData[0] = dXMin; 842 | for (size_t x = 1u; x < layerPoints; ++x) 843 | { 844 | m_horCurveXAxisData[x] = m_horCurveXAxisData[x - 1] + dx; 845 | } 846 | 847 | // Reset marker to the default position 848 | m_markerX = (dXMax - dXMin) / 2; 849 | m_markerY = historyExtent - 1; 850 | } 851 | 852 | void Waterfallplot::freeCurvesData() 853 | { 854 | if (m_horCurveXAxisData) 855 | { 856 | delete [] m_horCurveXAxisData; 857 | m_horCurveXAxisData = nullptr; 858 | } 859 | if (m_horCurveYAxisData) 860 | { 861 | delete [] m_horCurveYAxisData; 862 | m_horCurveYAxisData = nullptr; 863 | } 864 | if (m_vertCurveXAxisData) 865 | { 866 | delete [] m_vertCurveXAxisData; 867 | m_vertCurveXAxisData = nullptr; 868 | } 869 | if (m_vertCurveYAxisData) 870 | { 871 | delete [] m_vertCurveYAxisData; 872 | m_vertCurveYAxisData = nullptr; 873 | } 874 | } 875 | 876 | void Waterfallplot::setPickerEnabled(const bool enabled) 877 | { 878 | m_panner->setEnabled(!enabled); 879 | 880 | m_picker->setEnabled(enabled); 881 | 882 | m_zoomer->setEnabled(!enabled); 883 | //m_zoomer->zoom(0); 884 | 885 | // clear plots ? 886 | } 887 | 888 | bool Waterfallplot::setMarker(const double x, const double y) 889 | { 890 | if (!m_data) 891 | { 892 | return false; 893 | } 894 | 895 | const QwtInterval xInterval = m_data->interval(Qt::XAxis); 896 | const QwtInterval yInterval = m_data->interval(Qt::YAxis); 897 | if (!(xInterval.contains(x) && yInterval.contains(y))) 898 | { 899 | return false; 900 | } 901 | 902 | m_markerX = x; 903 | 904 | const double offset = m_data->getOffset(); 905 | m_markerY = y - offset; 906 | 907 | // update curves's markers positions 908 | m_horCurveMarker->setValue(m_markerX, 0.0); 909 | m_vertCurveMarker->setValue(0.0, y); 910 | 911 | updateCurvesData(); 912 | 913 | m_plotHorCurve->replot(); 914 | m_plotVertCurve->replot(); 915 | 916 | return true; 917 | } 918 | 919 | void Waterfallplot::selectedPoint(const QPointF& pt) 920 | { 921 | setMarker(pt.x(), pt.y()); 922 | } 923 | 924 | void Waterfallplot::setupCurves() 925 | { 926 | m_plotHorCurve->detachItems(QwtPlotItem::Rtti_PlotCurve, true); 927 | m_plotVertCurve->detachItems(QwtPlotItem::Rtti_PlotCurve, true); 928 | 929 | m_horCurve = new QwtPlotCurve; 930 | m_vertCurve = new QwtPlotCurve; 931 | 932 | /* Horizontal Curve */ 933 | m_horCurve->attach(m_plotHorCurve); 934 | m_horCurve->setRenderHint(QwtPlotItem::RenderAntialiased, true); 935 | //m_curve->setTitle(""); 936 | //m_curve->setPen(...); // pen : color + width 937 | m_horCurve->setStyle(QwtPlotCurve::Lines); 938 | //m_curve->setSymbol(new QwtSymbol(QwtSymbol::Style...,Qt::NoBrush, QPen..., QSize(5, 5))); 939 | 940 | /* Vertical Curve */ 941 | m_vertCurve->attach(m_plotVertCurve); 942 | m_vertCurve->setRenderHint(QwtPlotItem::RenderAntialiased, true); 943 | m_vertCurve->setStyle(QwtPlotCurve::Lines); 944 | 945 | m_plotVertCurve->setAxisScaleDraw(QwtPlot::yLeft, new WaterfallTimeScaleDraw(*this)); 946 | } 947 | --------------------------------------------------------------------------------