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 }