From 64fe3251252bf937e599390a010a3c083077a53e Mon Sep 17 00:00:00 2001 From: =?utf8?q?Michal=20Mal=C3=BD?= Date: Fri, 13 Dec 2013 23:19:03 +0100 Subject: [PATCH] Add possibility to export graphs to images. --- Anyanka.pro | 9 +- datamanager.cpp | 95 +++++++++++++ datamanager.h | 1 + graphdrawer.cpp | 1 - gui/exportgraphtoimagedialog.cpp | 172 ++++++++++++++++++++++++ gui/exportgraphtoimagedialog.h | 45 +++++++ gui/exportgraphtoimagedialog.ui | 220 +++++++++++++++++++++++++++++++ gui/mainwindow.cpp | 1 + gui/mainwindow.h | 2 + main.cpp | 1 + signal.h | 1 - signalcontroller.cpp | 68 +++++++--- signalcontroller.h | 7 + singlerundata.h | 3 +- 14 files changed, 600 insertions(+), 26 deletions(-) create mode 100644 gui/exportgraphtoimagedialog.cpp create mode 100644 gui/exportgraphtoimagedialog.h create mode 100644 gui/exportgraphtoimagedialog.ui diff --git a/Anyanka.pro b/Anyanka.pro index 76443c8..d17c3ff 100644 --- a/Anyanka.pro +++ b/Anyanka.pro @@ -50,7 +50,8 @@ SOURCES += main.cpp\ datafileexporter.cpp \ datawriterbackend.cpp \ csvdatawriterbackend.cpp \ - graphdrawer.cpp + graphdrawer.cpp \ + gui/exportgraphtoimagedialog.cpp HEADERS += \ datafilesloader.h \ @@ -81,13 +82,15 @@ HEADERS += \ datafileexporter.h \ datawriterbackend.h \ csvdatawriterbackend.h \ - graphdrawer.h + graphdrawer.h \ + gui/exportgraphtoimagedialog.h FORMS += \ gui/mainwindow.ui \ gui/signalview.ui \ gui/aboutanyanka.ui \ - gui/exportrawdatadialog.ui + gui/exportrawdatadialog.ui \ + gui/exportgraphtoimagedialog.ui RESOURCES += \ imgresources.qrc diff --git a/datamanager.cpp b/datamanager.cpp index d00e082..09957a9 100644 --- a/datamanager.cpp +++ b/datamanager.cpp @@ -22,7 +22,9 @@ #include "datamanager.h" #include "logger.h" +#include "gui/exportgraphtoimagedialog.h" #include "gui/exportrawdatadialog.h" +#include #include #include @@ -370,6 +372,99 @@ void DataManager::showOneSignal(std::shared_ptr ctrl) /* Public slots */ +void DataManager::onExportGraphToImage() +{ + QImageWriter imageWriter; + ExportGraphToImageDialog dlg(imageWriter.supportedImageFormats()); + std::shared_ptr sr; + + if (m_activeSequence == nullptr) + return; + sr = m_activeSequence->selectedRun(); + if (sr == nullptr) + return; + + for (const std::pair>& p : sr->allControllers()) + dlg.addSignal(p.second->currentStartX(), p.second->currentStartY(), p.second->currentStopX(), p.second->currentStopY(), p.first); + + if (dlg.exec() == QDialog::Accepted) { + GraphDrawer drawer; + double* data; + size_t fromIdx, toIdx; + double fromX = 0, toX = 0; + double fromY = 0, toY = 0; + int iw, ih; + const std::string key = dlg.selectedSignal().toStdString(); + std::shared_ptr ctrl = sr->controllerAt(key); + std::shared_ptr sig; + + if (ctrl == nullptr) { + Logger::log(Logger::Level::CRITICAL, ME_SENDER_STR, "Invalid key " + QString::fromStdString(key) + " passed to image exporter"); + return; + } + fromX = dlg.fromX(); + toX = dlg.toX(); + fromY = dlg.fromY(); + toY = dlg.toY(); + iw = dlg.imageWidth(); + ih = dlg.imageHeight(); + sig = ctrl->signal(); + + if (iw < 1) { + QMessageBox::warning(nullptr, "Error while exporting to image", "Invalid width value"); + return; + } + if (ih < 1) { + QMessageBox::warning(nullptr, "Error while exporting to image", "Invalid width value"); + return; + } + if (dlg.path().length() == 0) { + QMessageBox::warning(nullptr, "Error while exporting to image", "Invalid path"); + return; + } + qDebug() << fromX << fromY << toX << toY; + + /* Get from an to indices */ + const std::vector& tvpairs = sig->values(); + uint idx; + for (idx = 0; idx < tvpairs.size(); idx++) { + if (tvpairs.at(idx).first >= fromX) { + fromIdx = idx; + break; + } + } + if (idx == tvpairs.size()) { + QMessageBox::warning(nullptr, "Error while exporting to image", "Value \"From X\" is higher than the range of the dataset."); + return; + } + for (;idx < tvpairs.size(); idx++) { + if (tvpairs.at(idx).first >= toX) { + toIdx = idx; + break; + } + } + if (idx == tvpairs.size()) + toIdx = idx - 1; + + qDebug() << "fromIdx" << fromIdx << "toIdx" << toIdx << "size" << tvpairs.size(); + data = ctrl->generateDrawData(fromIdx, toIdx); + if (data == nullptr) { + QMessageBox::warning(nullptr, "Error while exporting to image", "Cannot generate data to draw. Some values are probably invalid."); + return; + } + + QPixmap* pixmap = drawer.drawGraph(data, toIdx - fromIdx, iw, ih, fromY, toY); + QImage result = pixmap->toImage(); + imageWriter.setFileName(dlg.path() + "." + dlg.imageFormat()); + imageWriter.setFormat(dlg.imageFormat().toLatin1()); + if (!imageWriter.write(result)) + QMessageBox::critical(nullptr, "Error while exporting to image", "Image could not have been written."); + + delete pixmap; + delete data; + } +} + void DataManager::onExportPeaks() { if (m_activeSequence == nullptr) diff --git a/datamanager.h b/datamanager.h index 93be0be..ff16b0e 100644 --- a/datamanager.h +++ b/datamanager.h @@ -86,6 +86,7 @@ signals: void setSingleRunInfo(const QString& method, const QString& operatorname, const QString& sample, const QString& datetime); public slots: + void onExportGraphToImage(); void onExportPeaks(); void onExportRawData(); void onLoadSequence(const QString& dir); diff --git a/graphdrawer.cpp b/graphdrawer.cpp index 86142c5..44c0ed8 100644 --- a/graphdrawer.cpp +++ b/graphdrawer.cpp @@ -33,7 +33,6 @@ QPixmap* GraphDrawer::drawGraph(const double* const data, const size_t len, size int toY = h - yStep * (data[i] - min); p.drawLine(fromX, fromY, toX, toY); - qDebug() << "fromX" << fromX << "fromY" << fromY << "toX" << toX << "toY" << toY; fromX = toX; fromY = toY; } diff --git a/gui/exportgraphtoimagedialog.cpp b/gui/exportgraphtoimagedialog.cpp new file mode 100644 index 0000000..eea1c1d --- /dev/null +++ b/gui/exportgraphtoimagedialog.cpp @@ -0,0 +1,172 @@ +#include "exportgraphtoimagedialog.h" +#include "ui_exportgraphtoimagedialog.h" +#include +#include + +/* Public functions */ + +ExportGraphToImageDialog::ExportGraphToImageDialog(QList formats, QWidget *parent) : + QDialog(parent), + m_curKey(""), + ui(new Ui::ExportGraphToImageDialog) +{ + ui->setupUi(this); + + for (const QByteArray ba : formats) + ui->qcbox_outputFormat->addItem(QString::fromLatin1(ba)); + + connect(ui->qpb_browse, SIGNAL(clicked()), this, SLOT(onBrowseClicked())); + connect(ui->qpb_cancel, SIGNAL(clicked()), this, SLOT(reject())); + connect(ui->qpb_ok, SIGNAL(clicked()), this, SLOT(checkAndAccept())); + connect(ui->qcbox_signal, SIGNAL(currentIndexChanged(QString)), this, SLOT(changeBounds(QString))); +} + +void ExportGraphToImageDialog::addSignal(const double fromX, const double fromY, const double toX, const double toY, const std::string& key) +{ + m_signalKeys.push_back(key); + m_bounds[key] = Bounds(fromX, fromY, toX, toY); + + ui->qcbox_signal->addItem(QString::fromStdString(key)); +} + +double ExportGraphToImageDialog::fromX() const +{ + try { + return std::get<0>(m_bounds.at(m_curKey)); + } catch (std::out_of_range&) { + return 0; + } +} + +double ExportGraphToImageDialog::fromY() const +{ + try { + return std::get<1>(m_bounds.at(m_curKey)); + } catch (std::out_of_range&) { + return 0; + } +} + +QString ExportGraphToImageDialog::imageFormat() const +{ + return ui->qcbox_outputFormat->currentText(); +} + +int ExportGraphToImageDialog::imageHeight() const +{ + bool ok; + double h = ui->qle_height->text().toInt(&ok); + if (!ok) return 0; + return h; +} + +QString ExportGraphToImageDialog::path() const +{ + return ui->qle_destPath->text(); +} + +QString ExportGraphToImageDialog::selectedSignal() const +{ + return ui->qcbox_signal->currentText(); +} + +double ExportGraphToImageDialog::toX() const +{ + try { + return std::get<2>(m_bounds.at(m_curKey)); + } catch (std::out_of_range&) { + return 0; + } +} + +double ExportGraphToImageDialog::toY() const +{ + try { + return std::get<3>(m_bounds.at(m_curKey)); + } catch (std::out_of_range&) { + return 0; + } +} + +int ExportGraphToImageDialog::imageWidth() const +{ + bool ok; + double w = ui->qle_width->text().toInt(&ok); + + if (!ok) return 0; + return w; +} + +/* Private slots */ + +void ExportGraphToImageDialog::changeBounds(const QString& key) +{ + QLocale l = QLocale::system(); + Bounds oldBounds, newBounds; + std::string skey = key.toStdString(); + double fX, fY, tX, tY; + bool ok; + + try { + oldBounds = m_bounds.at(m_curKey); + + fX = ui->qle_fromX->text().toDouble(&ok); + if (ok) std::get<0>(oldBounds) = fX; + fY = ui->qle_fromY->text().toDouble(&ok); + if (ok) std::get<1>(oldBounds) = fY; + tX = ui->qle_toX->text().toDouble(&ok); + if (ok) std::get<2>(oldBounds) = tX; + tY = ui->qle_toY->text().toDouble(&ok); + if (ok) std::get<3>(oldBounds) = tY; + + m_bounds[m_curKey] = oldBounds; + } catch (std::out_of_range&) { + } + + try { + newBounds = m_bounds.at(skey); + + ui->qle_fromX->setText(l.toString(std::get<0>(newBounds))); + ui->qle_fromY->setText(l.toString(std::get<1>(newBounds))); + ui->qle_toX->setText(l.toString(std::get<2>(newBounds))); + ui->qle_toY->setText(l.toString(std::get<3>(newBounds))); + m_curKey = skey; + } catch (std::out_of_range&) { + } +} + +void ExportGraphToImageDialog::checkAndAccept() +{ + try { + double fX, fY, tX, tY; + bool ok; + const std::string key = ui->qcbox_signal->currentText().toStdString(); + Bounds b = m_bounds.at(key); + + fX = ui->qle_fromX->text().toDouble(&ok); + if (ok) std::get<0>(b) = fX; + fY = ui->qle_fromY->text().toDouble(&ok); + if (ok) std::get<1>(b) = fY; + tX = ui->qle_toX->text().toDouble(&ok); + if (ok) std::get<2>(b) = tX; + tY = ui->qle_toY->text().toDouble(&ok); + if (ok) std::get<3>(b) = tY; + + m_bounds[key] = b; + } catch (std::out_of_range&) { + } + + accept(); +} + +void ExportGraphToImageDialog::onBrowseClicked() +{ + QFileDialog fdlg(this, "Pick destination", QString()); + if (fdlg.exec() == QDialog::Accepted) + ui->qle_destPath->setText(fdlg.selectedFiles()[0]); +} + +ExportGraphToImageDialog::~ExportGraphToImageDialog() +{ + delete ui; +} diff --git a/gui/exportgraphtoimagedialog.h b/gui/exportgraphtoimagedialog.h new file mode 100644 index 0000000..ff9d087 --- /dev/null +++ b/gui/exportgraphtoimagedialog.h @@ -0,0 +1,45 @@ +#ifndef EXPORTGRAPHTOIMAGEDIALOG_H +#define EXPORTGRAPHTOIMAGEDIALOG_H + +#include +#include + +namespace Ui { + class ExportGraphToImageDialog; +} + +class ExportGraphToImageDialog : public QDialog +{ + Q_OBJECT + + typedef std::tuple Bounds; + +public: + explicit ExportGraphToImageDialog(QList formats, QWidget* parent = nullptr); + ~ExportGraphToImageDialog(); + void addSignal(const double fromX, const double fromY, const double toX, const double toY, const std::string& key); + double fromX() const; + double fromY() const; + QString imageFormat() const; + int imageHeight() const; + QString path() const; + QString selectedSignal() const; + double toX() const; + double toY() const; + int imageWidth() const; + +private: + std::unordered_map m_bounds; + std::string m_curKey; + std::vector m_signalKeys; + + Ui::ExportGraphToImageDialog *ui; + +private slots: + void checkAndAccept(); + void changeBounds(const QString& key); + void onBrowseClicked(); + +}; + +#endif // EXPORTGRAPHTOIMAGEDIALOG_H diff --git a/gui/exportgraphtoimagedialog.ui b/gui/exportgraphtoimagedialog.ui new file mode 100644 index 0000000..4540eb2 --- /dev/null +++ b/gui/exportgraphtoimagedialog.ui @@ -0,0 +1,220 @@ + + + ExportGraphToImageDialog + + + + 0 + 0 + 400 + 300 + + + + Export graph to image + + + + 3 + + + + + + + + 0 + 0 + + + + + + + + Destination: + + + + + + + + 0 + 0 + + + + Signal: + + + + + + + + + + Browse + + + + + + + Output format: + + + + + + + + + + + + + + Width: + + + + + + + 1000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + Height: + + + + + + + 1000 + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + Qt::Horizontal + + + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + From X: + + + + + + + To X: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + From Y: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + To Y: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + 6 + + + QLayout::SetMinimumSize + + + + + Qt::Horizontal + + + + 40 + 1 + + + + + + + + OK + + + + + + + Cancel + + + + + + + + + + diff --git a/gui/mainwindow.cpp b/gui/mainwindow.cpp index 53210bb..43fd34f 100644 --- a/gui/mainwindow.cpp +++ b/gui/mainwindow.cpp @@ -73,6 +73,7 @@ void MainWindow::connectActions() /* EXPORT menu */ connect(ui->actionRaw_values, SIGNAL(triggered()), this, SLOT(onExportRawData())); connect(ui->actionIntegration_results, SIGNAL(triggered()), this, SLOT(onExportPeaks())); + connect(ui->actionGraph_as_image, SIGNAL(triggered()), this, SLOT(onExportGraphToImage())); /* HELP menu */ connect(ui->actionAbout, SIGNAL(triggered()), this, SLOT(onAboutAnyanka())); /* RUN/SEQ cboxes*/ diff --git a/gui/mainwindow.h b/gui/mainwindow.h index e3c42a4..663416b 100644 --- a/gui/mainwindow.h +++ b/gui/mainwindow.h @@ -69,6 +69,7 @@ public slots: private slots: void onAboutAnyanka(); + void onExportGraphToImage() { emit exportGraphToImage(); } void onExportPeaks() { emit exportPeaks(); } void onExportRawData() { emit exportRawData(); } void onIntegrateSelected(); @@ -81,6 +82,7 @@ private slots: signals: void controlModeChanged(GraphControlModes mode); + void exportGraphToImage(); void exportPeaks(); void exportRawData(); void loadSequence(const QString& dir); diff --git a/main.cpp b/main.cpp index 446ddf5..3d0bfaa 100644 --- a/main.cpp +++ b/main.cpp @@ -69,6 +69,7 @@ int main(int argc, char *argv[]) QObject::connect(dMgr.get(), SIGNAL(setActiveSequenceIndex(int)), mWin.get(), SLOT(onSetActiveSequenceIndex(int))); QObject::connect(dMgr.get(), SIGNAL(setActiveSingleRunKey(std::string)), mWin.get(), SLOT(onSetActiveSingleRunKey(std::string))); QObject::connect(dMgr.get(), SIGNAL(setSingleRunInfo(QString,QString,QString,QString)), mWin.get(), SLOT(onSetSingleRunInfo(QString,QString,QString,QString))); + QObject::connect(mWin.get(), SIGNAL(exportGraphToImage()),dMgr.get(), SLOT(onExportGraphToImage())); QObject::connect(mWin.get(), SIGNAL(exportPeaks()), dMgr.get(), SLOT(onExportPeaks())); QObject::connect(mWin.get(), SIGNAL(exportRawData()), dMgr.get(), SLOT(onExportRawData())); QObject::connect(mWin.get(), SIGNAL(loadSingleRun(QString)), dMgr.get(), SLOT(onLoadSingleRun(QString))); diff --git a/signal.h b/signal.h index 09a8d4f..f9bb4b9 100644 --- a/signal.h +++ b/signal.h @@ -65,7 +65,6 @@ public: std::string resourceToString() const; double timeAt(const size_t idx) const; double samplingRate() const { return m_samplingRate; } - //QString uidString() const; double valueAt(const size_t idx) const; const std::vector& values() const { return m_values; } std::vector::const_iterator valuesBegin() const { return m_values.begin(); } diff --git a/signalcontroller.cpp b/signalcontroller.cpp index 44e62cb..2714860 100644 --- a/signalcontroller.cpp +++ b/signalcontroller.cpp @@ -44,6 +44,26 @@ SignalController::SignalController(std::shared_ptr signal, QObject* pare connect(m_ctxMenu.m_acDeletePeak, SIGNAL(triggered()), this, SLOT(onCtxMenuDeletePeak())); } +double SignalController::currentStartX() const +{ + return m_signal->pairAt(m_fromIdx).first; +} + +double SignalController::currentStartY() const +{ + return m_yMin; +} + +double SignalController::currentStopX() const +{ + return m_signal->pairAt(m_toIdx).first; +} + +double SignalController::currentStopY() const +{ + return m_yMax; +} + /* Public methods */ void SignalController::draw() { @@ -75,26 +95,10 @@ void SignalController::drawFullGraph() void SignalController::drawGraph() { - double* arr; - size_t len; - - if (m_fromIdx > m_toIdx) { - Logger::log(Logger::Level::CRITICAL, ME_SENDER_STR, "From index lower than to index"); - return; - } - if (m_fromIdx == m_toIdx) - return; //Nothing to zoom to - if (m_toIdx >= m_signal->valuesCount() || m_fromIdx >= m_signal->valuesCount()) { - Logger::log(Logger::Level::CRITICAL, ME_SENDER_STR, "Invalid value index"); - return; - } - len = m_toIdx - m_fromIdx; - - arr = new double[len]; - for (uint idx = 0; idx < len; idx++) - arr[idx] = m_signal->valueAt(idx + m_fromIdx); - - emit viewDrawGraph(arr, len, m_yMin, m_yMax); + size_t len = m_toIdx - m_fromIdx; + double* arr = generateDrawData(m_fromIdx, m_toIdx); + if (arr != nullptr) + emit viewDrawGraph(arr, len, m_yMin, m_yMax); } void SignalController::drawIntegratedPeak(const std::shared_ptr peak) @@ -142,6 +146,30 @@ void SignalController::drawIntegratedPeak(const std::shared_ptr emit viewDrawIntegration(fromX, fromY, toX, toY, peak->peakIdx() - m_fromIdx, m_signal->valueAt(peak->peakIdx()), peakTime, peakArea, valley); } +double* SignalController::generateDrawData(size_t from, size_t to) +{ + double* arr; + size_t len; + + if (from > to) { + Logger::log(Logger::Level::CRITICAL, ME_SENDER_STR, "From index lower than to index"); + return nullptr; + } + if (from == to) + return nullptr; + if (to >= m_signal->valuesCount() || from >= m_signal->valuesCount()) { + Logger::log(Logger::Level::CRITICAL, ME_SENDER_STR, "Invalid value index"); + return nullptr; + } + len = to - from; + + arr = new double[len]; + for (uint idx = 0; idx < len; idx++) + arr[idx] = m_signal->valueAt(idx + from); + + return arr; +} + bool SignalController::updateConstraints(const int fromX, const int fromY, const int toX, const int toY) { size_t xRange = m_toIdx - m_fromIdx; diff --git a/signalcontroller.h b/signalcontroller.h index b11e758..1fae1a2 100644 --- a/signalcontroller.h +++ b/signalcontroller.h @@ -37,8 +37,15 @@ class SignalController : public QObject public: explicit SignalController(std::shared_ptr signal, QObject* parent = nullptr); ~SignalController(); + double currentYMax() const { return m_yMax; } + double currentYMin() const { return m_yMin; } + double currentStartX() const; + double currentStartY() const; + double currentStopX() const; + double currentStopY() const; void draw(); SignalDataTableModel* dataTableModel() { return m_dtblModel; } + double* generateDrawData(size_t from, size_t to); IntegrationTableModel* integrationTableModel() { return m_integTblModel;} const std::shared_ptr signal() const { return m_signal; } const std::shared_ptr cIntegrator() const { return m_integrator; } diff --git a/singlerundata.h b/singlerundata.h index c099fa9..b58529d 100644 --- a/singlerundata.h +++ b/singlerundata.h @@ -37,7 +37,9 @@ public: explicit SingleRunData(const QString& methodName, const QString& operatorName, const QString& sampleInfo, const QDate date, const QTime time, std::unordered_map>& sigs, std::unordered_map>& ctrls, const QString& dirname, QObject* parent = nullptr); + const std::unordered_map>& allControllers() const { return m_ctrls; } std::vector allKeys() const; + const std::unordered_map>& allSignals() const { return m_signals; } std::shared_ptr controllerAt(const std::string& key); QDate date() const { return m_date; } QString dirName() const { return m_dirName; } @@ -45,7 +47,6 @@ public: QString operatorName() const { return m_operatorName; } QString sampleInfo() const { return m_sampleInfo; } std::shared_ptr signalAt(const std::string& key); - const std::unordered_map>& allSignals() const { return m_signals; } size_t signalCount() const { return m_signals.size(); } QTime time() const { return m_time; } -- 2.43.5