File indexing completed on 2025-01-05 03:35:42

0001 /*
0002     File                 : SampleValuesDialog.cpp
0003     Project              : LabPlot
0004     Description          : Dialog for sampling values in columns
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2022 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "SampleValuesDialog.h"
0011 #include "backend/core/Settings.h"
0012 #include "backend/core/column/Column.h"
0013 #include "backend/lib/macros.h"
0014 #include "backend/spreadsheet/Spreadsheet.h"
0015 
0016 #include <QDialogButtonBox>
0017 #include <QPushButton>
0018 #include <QThreadPool>
0019 #include <QWindow>
0020 
0021 #include <KLocalizedString>
0022 #include <KMessageBox>
0023 #include <KWindowConfig>
0024 
0025 #include <cmath>
0026 
0027 #include <gsl/gsl_randist.h>
0028 #include <gsl/gsl_rng.h>
0029 
0030 enum class Method { Periodic, Random };
0031 
0032 /*!
0033     \class SampleValuesDialog
0034     \brief Dialog for generating values from a mathematical function.
0035 
0036     \ingroup kdefrontend
0037  */
0038 SampleValuesDialog::SampleValuesDialog(Spreadsheet* s, QWidget* parent)
0039     : QDialog(parent)
0040     , m_spreadsheet(s) {
0041     ui.setupUi(this);
0042     setAttribute(Qt::WA_DeleteOnClose);
0043 
0044     ui.cbMethod->addItem(i18n("Periodic"));
0045     ui.cbMethod->addItem(i18n("Random"));
0046 
0047     QString info = i18n(
0048         "Sampling method:"
0049         "<ul>"
0050         "<li>Periodic - samples are created according to the specified interval.</li>"
0051         "<li>Random - samples are created randomly based on the uniform distribution. </li>"
0052         "</ul>");
0053     ui.lMethod->setToolTip(info);
0054     ui.cbMethod->setToolTip(info);
0055 
0056     auto* btnBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0057     ui.gridLayout->addWidget(btnBox, 2, 1, 1, 2);
0058     m_okButton = btnBox->button(QDialogButtonBox::Ok);
0059 
0060     connect(btnBox->button(QDialogButtonBox::Cancel), &QPushButton::clicked, this, &SampleValuesDialog::close);
0061 
0062     m_okButton->setText(i18n("&Sample"));
0063     m_okButton->setToolTip(i18n("Sample values in the selected spreadsheet columns"));
0064     setWindowTitle(i18nc("@title:window", "Sample Values"));
0065 
0066     connect(ui.cbMethod, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &SampleValuesDialog::methodChanged);
0067     connect(m_okButton, &QPushButton::clicked, this, &SampleValuesDialog::sampleValues);
0068     connect(btnBox, &QDialogButtonBox::accepted, this, &SampleValuesDialog::accept);
0069     connect(btnBox, &QDialogButtonBox::rejected, this, &SampleValuesDialog::reject);
0070 
0071     // restore saved settings if available
0072     KConfigGroup conf = Settings::group(QLatin1String("SampleValuesDialog"));
0073     ui.cbMethod->setCurrentIndex(conf.readEntry("Method", 0));
0074     ui.sbValue->setValue(conf.readEntry("Value", 1));
0075     methodChanged(ui.cbMethod->currentIndex());
0076 
0077     create(); // ensure there's a window created
0078     if (conf.exists()) {
0079         KWindowConfig::restoreWindowSize(windowHandle(), conf);
0080         resize(windowHandle()->size()); // workaround for QTBUG-40584
0081     } else
0082         resize(QSize(400, 0).expandedTo(minimumSize()));
0083 }
0084 
0085 SampleValuesDialog::~SampleValuesDialog() {
0086     // save the current settings
0087     KConfigGroup conf = Settings::group(QLatin1String("SampleValuesDialog"));
0088     conf.writeEntry("Method", ui.cbMethod->currentIndex());
0089     conf.writeEntry("Value", ui.sbValue->value());
0090     KWindowConfig::saveWindowSize(windowHandle(), conf);
0091 }
0092 
0093 void SampleValuesDialog::setColumns(const QVector<Column*>& columns) {
0094     m_columns = columns;
0095 
0096     // resize the dialog to have the minimum height
0097     layout()->activate();
0098     resize(QSize(this->width(), 0).expandedTo(minimumSize()));
0099 }
0100 
0101 void SampleValuesDialog::methodChanged(int index) const {
0102     if (index == 0)
0103         ui.lValue->setText(i18n("Period:"));
0104     else
0105         ui.lValue->setText(i18n("Sample Size:"));
0106 }
0107 
0108 class SampleValuesTask : public QRunnable {
0109 public:
0110     SampleValuesTask(Column* source, Column* target, const QVector<int>& rows)
0111         : m_rows(rows) {
0112         m_source = source;
0113         m_target = target;
0114     }
0115 
0116     void run() override {
0117         auto mode = m_source->columnMode();
0118         int size = m_rows.size();
0119         switch (mode) {
0120         case AbstractColumn::ColumnMode::Double: {
0121             auto* dataSource = static_cast<QVector<double>*>(m_source->data());
0122             QVector<double> dataTarget(size);
0123             for (int i = 0; i < size; ++i)
0124                 dataTarget[i] = dataSource->at(m_rows.at(i));
0125 
0126             m_target->setValues(dataTarget);
0127             break;
0128         }
0129         case AbstractColumn::ColumnMode::Text: {
0130             auto* dataSource = static_cast<QVector<QString>*>(m_source->data());
0131             QVector<QString> dataTarget(size);
0132             for (int i = 0; i < size; ++i)
0133                 dataTarget[i] = dataSource->at(m_rows.at(i));
0134 
0135             m_target->setText(dataTarget);
0136             break;
0137         }
0138         case AbstractColumn::ColumnMode::Month:
0139         case AbstractColumn::ColumnMode::Day:
0140         case AbstractColumn::ColumnMode::DateTime: {
0141             auto* dataSource = static_cast<QVector<QDateTime>*>(m_source->data());
0142             QVector<QDateTime> dataTarget(size);
0143             for (int i = 0; i < size; ++i)
0144                 dataTarget[i] = dataSource->at(m_rows.at(i));
0145 
0146             m_target->setDateTimes(dataTarget);
0147             break;
0148         }
0149         case AbstractColumn::ColumnMode::Integer: {
0150             auto* dataSource = static_cast<QVector<int>*>(m_source->data());
0151             QVector<int> dataTarget(size);
0152             for (int i = 0; i < size; ++i)
0153                 dataTarget[i] = dataSource->at(m_rows.at(i));
0154 
0155             m_target->setIntegers(dataTarget);
0156             break;
0157         }
0158         case AbstractColumn::ColumnMode::BigInt: {
0159             auto* dataSource = static_cast<QVector<qint64>*>(m_source->data());
0160             QVector<qint64> dataTarget(size);
0161             for (int i = 0; i < size; ++i)
0162                 dataTarget[i] = dataSource->at(m_rows.at(i));
0163 
0164             m_target->setBigInts(dataTarget);
0165             break;
0166         }
0167         }
0168     }
0169 
0170 private:
0171     Column* m_source;
0172     Column* m_target;
0173     QVector<int> m_rows;
0174 };
0175 
0176 void SampleValuesDialog::sampleValues() const {
0177     WAIT_CURSOR;
0178     const auto method = static_cast<Method>(ui.cbMethod->currentIndex());
0179 
0180     QVector<int> rows;
0181     switch (method) {
0182     case Method::Periodic: {
0183         int period = ui.sbValue->value();
0184         int count = std::floor(m_spreadsheet->rowCount() / period);
0185         for (int i = 0; i < count; ++i)
0186             rows << period * (i + 1) - 1;
0187         break;
0188     }
0189     case Method::Random: {
0190         // create a generator chosen by the environment variable GSL_RNG_TYPE
0191         gsl_rng_env_setup();
0192         const gsl_rng_type* T = gsl_rng_default;
0193         gsl_rng* r = gsl_rng_alloc(T);
0194         gsl_rng_set(r, QDateTime::currentMSecsSinceEpoch());
0195 
0196         int sampleSize = ui.sbValue->value();
0197         int a = 0;
0198         int b = m_spreadsheet->rowCount() - 1;
0199         for (int i = 0; i < sampleSize; ++i)
0200             rows << (int)round(gsl_ran_flat(r, a, b));
0201         break;
0202     }
0203     }
0204 
0205     m_spreadsheet->beginMacro(i18n("%1: sample values", m_spreadsheet->name()));
0206 
0207     // create target spreadsheet
0208     auto* targetSpreadsheet = new Spreadsheet(i18n("Sample of %1", m_spreadsheet->name()));
0209     targetSpreadsheet->setColumnCount(m_columns.count());
0210     targetSpreadsheet->setRowCount(rows.count());
0211 
0212     int index = 0;
0213     const auto& targetColumns = targetSpreadsheet->children<Column>();
0214     for (auto* source : m_columns) {
0215         auto* target = targetColumns.at(index);
0216         target->setName(source->name());
0217         target->setColumnMode(source->columnMode());
0218         ++index;
0219     }
0220 
0221     for (int i = 0; i < m_columns.count(); ++i) {
0222         auto* task = new SampleValuesTask(m_columns.at(i), targetColumns.at(i), rows);
0223         QThreadPool::globalInstance()->start(task);
0224     }
0225 
0226     // wait until all columns were processed
0227     QThreadPool::globalInstance()->waitForDone();
0228 
0229     m_spreadsheet->parentAspect()->addChild(targetSpreadsheet);
0230 
0231     m_spreadsheet->endMacro();
0232     RESET_CURSOR;
0233 }