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

0001 /*
0002     File                 : PlotDataDialog.cpp
0003     Project              : LabPlot
0004     Description          : Dialog for generating plots for the spreadsheet data
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2017-2023 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 #include "PlotDataDialog.h"
0012 #include "backend/core/AspectTreeModel.h"
0013 #include "backend/core/Project.h"
0014 #include "backend/core/Settings.h"
0015 #include "backend/core/column/Column.h"
0016 #include "backend/lib/Range.h"
0017 #include "backend/spreadsheet/Spreadsheet.h"
0018 #include "backend/worksheet/TextLabel.h"
0019 #include "backend/worksheet/Worksheet.h"
0020 #include "backend/worksheet/plots/cartesian/Axis.h"
0021 #include "backend/worksheet/plots/cartesian/BarPlot.h"
0022 #include "backend/worksheet/plots/cartesian/BoxPlot.h"
0023 #include "backend/worksheet/plots/cartesian/CartesianPlot.h"
0024 #include "backend/worksheet/plots/cartesian/Histogram.h"
0025 #include "backend/worksheet/plots/cartesian/KDEPlot.h"
0026 #include "backend/worksheet/plots/cartesian/LollipopPlot.h"
0027 #include "backend/worksheet/plots/cartesian/QQPlot.h"
0028 #include "backend/worksheet/plots/cartesian/XYAnalysisCurve.h"
0029 #include "backend/worksheet/plots/cartesian/XYCurve.h"
0030 #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h"
0031 #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h"
0032 #include "backend/worksheet/plots/cartesian/XYFitCurve.h"
0033 #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h"
0034 #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h"
0035 #include "backend/worksheet/plots/cartesian/XYInterpolationCurve.h"
0036 #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h"
0037 #include "commonfrontend/widgets/TreeViewComboBox.h"
0038 
0039 #ifdef HAVE_MQTT
0040 #include "backend/datasources/MQTTTopic.h"
0041 #endif
0042 
0043 #include <QAction>
0044 #include <QActionGroup>
0045 #include <QDialogButtonBox>
0046 #include <QMenu>
0047 #include <QPushButton>
0048 #include <QWindow>
0049 
0050 #include <KConfigGroup>
0051 
0052 #include <KWindowConfig>
0053 
0054 #include "ui_plotdatawidget.h"
0055 
0056 /*!
0057     \class PlotDataDialog
0058     \brief Dialog for generating plots for the spreadsheet data.
0059 
0060     \ingroup kdefrontend
0061  */
0062 PlotDataDialog::PlotDataDialog(AbstractAspect* parentAspect, PlotType type, QWidget* parent)
0063     : QDialog(parent)
0064     , ui(new Ui::PlotDataWidget())
0065     , m_parentAspect(parentAspect)
0066     , m_plotsModel(new AspectTreeModel(m_parentAspect->project()))
0067     , m_worksheetsModel(new AspectTreeModel(m_parentAspect->project()))
0068     , m_plotType(type) {
0069     setAttribute(Qt::WA_DeleteOnClose);
0070     setWindowTitle(i18nc("@title:window", "Plot Spreadsheet Data"));
0071     setWindowIcon(QIcon::fromTheme(QStringLiteral("office-chart-line")));
0072 
0073     auto* mainWidget = new QWidget(this);
0074     ui->setupUi(mainWidget);
0075 
0076     auto* buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel);
0077     m_okButton = buttonBox->button(QDialogButtonBox::Ok);
0078     m_okButton->setDefault(true);
0079     m_okButton->setToolTip(i18n("Plot the selected data"));
0080     m_okButton->setText(i18n("&Plot"));
0081 
0082     auto* layout = new QVBoxLayout(this);
0083     layout->addWidget(mainWidget);
0084     layout->addWidget(buttonBox);
0085     setLayout(layout);
0086 
0087     // create combox boxes for the existing plots and worksheets
0088     auto* gridLayout = dynamic_cast<QGridLayout*>(ui->gbPlotPlacement->layout());
0089     cbExistingPlots = new TreeViewComboBox(ui->gbPlotPlacement);
0090     cbExistingPlots->setMinimumWidth(250); // TODO: use proper sizeHint in TreeViewComboBox
0091     gridLayout->addWidget(cbExistingPlots, 0, 1, 1, 1);
0092 
0093     cbExistingWorksheets = new TreeViewComboBox(ui->gbPlotPlacement);
0094     cbExistingWorksheets->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred));
0095     gridLayout->addWidget(cbExistingWorksheets, 1, 1, 1, 1);
0096 
0097     QList<AspectType> list{AspectType::Folder, AspectType::Worksheet, AspectType::CartesianPlot};
0098     cbExistingPlots->setTopLevelClasses(list);
0099     list = {AspectType::CartesianPlot};
0100     m_plotsModel->setSelectableAspects(list);
0101     cbExistingPlots->setModel(m_plotsModel);
0102 
0103     // select the first available plot, if available
0104     auto plots = m_parentAspect->project()->children<CartesianPlot>(AbstractAspect::ChildIndexFlag::Recursive);
0105     if (!plots.isEmpty()) {
0106         const auto* plot = plots.first();
0107         cbExistingPlots->setCurrentModelIndex(m_plotsModel->modelIndexOfAspect(plot));
0108     }
0109 
0110     list = {AspectType::Folder, AspectType::Worksheet};
0111     cbExistingWorksheets->setTopLevelClasses(list);
0112     list = {AspectType::Worksheet};
0113     m_worksheetsModel->setSelectableAspects(list);
0114     cbExistingWorksheets->setModel(m_worksheetsModel);
0115 
0116     // select the first available worksheet, if available
0117     auto worksheets = m_parentAspect->project()->children<Worksheet>(AbstractAspect::ChildIndexFlag::Recursive);
0118     if (!worksheets.isEmpty()) {
0119         const auto* worksheet = worksheets.first();
0120         cbExistingWorksheets->setCurrentModelIndex(m_worksheetsModel->modelIndexOfAspect(worksheet));
0121     }
0122 
0123     // in the grid layout of the scroll area we have on default one row for the x-column,
0124     // one row for the separating line and one line for the y-column.
0125     // set the height of this default content as the minimal size of the scroll area.
0126     gridLayout = dynamic_cast<QGridLayout*>(ui->scrollAreaColumns->widget()->layout());
0127     int height = 2 * ui->cbXColumn->height() + ui->line->height() + 2 * gridLayout->verticalSpacing() + gridLayout->contentsMargins().top()
0128         + gridLayout->contentsMargins().bottom();
0129     ui->scrollAreaColumns->setMinimumSize(0, height);
0130 
0131     // hide the check box for creation of original data, only shown if analysis curves are to be created
0132     ui->spacer->changeSize(0, 0);
0133     ui->chkCreateDataCurve->hide();
0134 
0135     // SIGNALs/SLOTs
0136     connect(buttonBox, &QDialogButtonBox::accepted, this, [=]() {
0137         hide();
0138         plot();
0139     });
0140     connect(buttonBox, &QDialogButtonBox::rejected, this, &PlotDataDialog::reject);
0141     connect(buttonBox, &QDialogButtonBox::accepted, this, &PlotDataDialog::accept);
0142     connect(ui->rbCurvePlacementAllInOnePlotArea, &QRadioButton::toggled, this, &PlotDataDialog::curvePlacementChanged);
0143     connect(ui->rbCurvePlacementAllInOwnPlotArea, &QRadioButton::toggled, this, &PlotDataDialog::curvePlacementChanged);
0144     connect(ui->rbPlotPlacementExistingPlotArea, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged);
0145     connect(ui->rbPlotPlacementExistingWorksheet, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged);
0146     connect(ui->rbPlotPlacementNewWorksheet, &QRadioButton::toggled, this, &PlotDataDialog::plotPlacementChanged);
0147     connect(cbExistingPlots, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton);
0148     connect(cbExistingWorksheets, &TreeViewComboBox::currentModelIndexChanged, this, &PlotDataDialog::checkOkButton);
0149 
0150     // restore saved settings if available
0151     create(); // ensure there's a window created
0152     KConfigGroup conf = Settings::group(QStringLiteral("PlotDataDialog"));
0153     if (conf.exists()) {
0154         int index = conf.readEntry("CurvePlacement", 0);
0155         if (index == 2)
0156             ui->rbCurvePlacementAllInOwnPlotArea->setChecked(true);
0157 
0158         index = conf.readEntry("PlotPlacement", 0);
0159         if (index == 2)
0160             ui->rbPlotPlacementExistingWorksheet->setChecked(true);
0161         if (index == 3)
0162             ui->rbPlotPlacementNewWorksheet->setChecked(true);
0163 
0164         KWindowConfig::restoreWindowSize(windowHandle(), conf);
0165         resize(windowHandle()->size()); // workaround for QTBUG-40584
0166     } else
0167         resize(QSize(0, 0).expandedTo(minimumSize()));
0168 }
0169 
0170 PlotDataDialog::~PlotDataDialog() {
0171     // save current settings
0172     KConfigGroup conf = Settings::group(QStringLiteral("PlotDataDialog"));
0173     int index = 0;
0174     if (ui->rbCurvePlacementAllInOnePlotArea->isChecked())
0175         index = 1;
0176     if (ui->rbCurvePlacementAllInOwnPlotArea->isChecked())
0177         index = 2;
0178     conf.writeEntry("CurvePlacement", index);
0179 
0180     if (ui->rbPlotPlacementExistingPlotArea->isChecked())
0181         index = 1;
0182     if (ui->rbPlotPlacementExistingWorksheet->isChecked())
0183         index = 2;
0184     if (ui->rbPlotPlacementNewWorksheet->isChecked())
0185         index = 3;
0186     conf.writeEntry("PlotPlacement", index);
0187 
0188     KWindowConfig::saveWindowSize(windowHandle(), conf);
0189 
0190     delete m_plotsModel;
0191     delete m_worksheetsModel;
0192     delete ui;
0193 }
0194 
0195 void PlotDataDialog::setAnalysisAction(XYAnalysisCurve::AnalysisAction action) {
0196     m_analysisAction = action;
0197     m_analysisMode = true;
0198     ui->spacer->changeSize(0, 40);
0199     ui->chkCreateDataCurve->show();
0200 }
0201 
0202 void PlotDataDialog::fillMenu(QMenu* menu, QActionGroup* group) {
0203     // synchronize with the menu in CartesianPlot
0204 
0205     auto* action = new QAction(QIcon::fromTheme(QStringLiteral("labplot-xy-curve")), i18n("xy-curve"), group);
0206     action->setData(static_cast<int>(PlotDataDialog::PlotType::XYCurve));
0207     menu->addAction(action);
0208 
0209     auto* addNewStatisticalPlotsMenu = new QMenu(i18n("Statistical Plots"), menu);
0210     action = new QAction(QIcon::fromTheme(QStringLiteral("view-object-histogram-linear")), i18n("Histogram"), group);
0211     action->setData(static_cast<int>(PlotDataDialog::PlotType::Histogram));
0212     addNewStatisticalPlotsMenu->addAction(action);
0213 
0214     action = new QAction(BoxPlot::staticIcon(), i18n("Box Plot"), group);
0215     action->setData(static_cast<int>(PlotDataDialog::PlotType::BoxPlot));
0216     addNewStatisticalPlotsMenu->addAction(action);
0217 
0218     action = new QAction(i18n("KDE Plot"), group);
0219     action->setData(static_cast<int>(PlotDataDialog::PlotType::KDEPlot));
0220     addNewStatisticalPlotsMenu->addAction(action);
0221 
0222     action = new QAction(i18n("Q-Q Plot"), group);
0223     action->setData(static_cast<int>(PlotDataDialog::PlotType::QQPlot));
0224     addNewStatisticalPlotsMenu->addAction(action);
0225     menu->addMenu(addNewStatisticalPlotsMenu);
0226 
0227     auto* addNewBarPlotsMenu = new QMenu(i18n("Bar Plots"), menu);
0228     action = new QAction(QIcon::fromTheme(QStringLiteral("office-chart-bar")), i18n("Bar Plot"), group);
0229     action->setData(static_cast<int>(PlotDataDialog::PlotType::BarPlot));
0230     addNewBarPlotsMenu->addAction(action);
0231 
0232     action = new QAction(LollipopPlot::staticIcon(), i18n("Lollipop Plot"), group);
0233     action->setData(static_cast<int>(PlotDataDialog::PlotType::LollipopPlot));
0234     addNewBarPlotsMenu->addAction(action);
0235     menu->addMenu(addNewBarPlotsMenu);
0236 }
0237 
0238 void PlotDataDialog::setSelectedColumns(QVector<Column*> selectedColumns) {
0239     // skip error and non-plottable columns
0240     for (Column* col : selectedColumns) {
0241         if ((col->plotDesignation() == AbstractColumn::PlotDesignation::X || col->plotDesignation() == AbstractColumn::PlotDesignation::Y
0242              || col->plotDesignation() == AbstractColumn::PlotDesignation::NoDesignation)
0243             && col->isPlottable())
0244             m_columns << col;
0245     }
0246 
0247     // disable everything if the spreadsheet doesn't have any columns to plot
0248     if (m_columns.isEmpty()) {
0249         ui->gbCurvePlacement->setEnabled(false);
0250         ui->gbPlotPlacement->setEnabled(false);
0251         return;
0252     }
0253 
0254     // determine the column names
0255     // and the name of the first column having "X" as the plot designation (relevant for xy-curves only)
0256     QStringList columnNames;
0257     QString xColumnName;
0258     for (const Column* column : m_columns) {
0259         columnNames << column->name();
0260         if (m_plotType == PlotType::XYCurve && xColumnName.isEmpty() && column->plotDesignation() == AbstractColumn::PlotDesignation::X)
0261             xColumnName = column->name();
0262     }
0263 
0264     if (m_plotType == PlotType::XYCurve && xColumnName.isEmpty()) {
0265         // no X-column was selected -> look for the first non-selected X-column left to the first selected column
0266         const auto& columns = m_parentAspect->children<Column>();
0267         const int index = columns.indexOf(selectedColumns.first()) - 1;
0268         for (int i = index; i >= 0; --i) {
0269             auto* column = columns.at(i);
0270             if (column->plotDesignation() == AbstractColumn::PlotDesignation::X && column->isPlottable()) {
0271                 xColumnName = column->name();
0272                 m_columns.prepend(column);
0273                 columnNames.prepend(xColumnName);
0274                 break;
0275             }
0276         }
0277 
0278         if (xColumnName.isEmpty()) {
0279             // no X-column was found left to the first selected column -> look right to the last selected column
0280             const int index = columns.indexOf(selectedColumns.last()) + 1;
0281             for (int i = index; i < columns.count(); ++i) {
0282                 auto* column = columns.at(i);
0283                 if (column->plotDesignation() == AbstractColumn::PlotDesignation::X && column->isPlottable()) {
0284                     xColumnName = column->name();
0285                     m_columns.prepend(column);
0286                     columnNames.prepend(xColumnName);
0287                     break;
0288                 }
0289             }
0290         }
0291     }
0292 
0293     switch (m_plotType) {
0294     case PlotType::XYCurve:
0295         processColumnsForXYCurve(columnNames, xColumnName);
0296         break;
0297     case PlotType::Histogram:
0298     case PlotType::KDEPlot:
0299     case PlotType::QQPlot:
0300     case PlotType::BoxPlot:
0301     case PlotType::BarPlot:
0302     case PlotType::LollipopPlot:
0303         processColumnsForHistogram(columnNames);
0304         break;
0305     }
0306 
0307     // resize the scroll area to show five ComboBoxes at maximum without showing the scroll bars
0308     int size = m_columnComboBoxes.size() >= 5 ? 5 : m_columnComboBoxes.size();
0309     int height = size * ui->cbXColumn->height();
0310     auto* layout = dynamic_cast<QGridLayout*>(ui->scrollAreaColumns->widget()->layout());
0311     if (layout) {
0312         height += (size + 1) * layout->verticalSpacing();
0313         if (m_plotType == PlotType::XYCurve)
0314             height += layout->verticalSpacing(); // one more spacing for the separating line
0315     }
0316     ui->scrollAreaColumns->setMinimumSize(ui->scrollAreaColumns->width(), height);
0317 
0318     plotPlacementChanged();
0319 }
0320 
0321 void PlotDataDialog::processColumnsForXYCurve(const QStringList& columnNames, const QString& xColumnName) {
0322     m_columnComboBoxes << ui->cbXColumn;
0323     m_columnComboBoxes << ui->cbYColumn;
0324 
0325     // ui-widget only has one combobox for the y-data -> add additional comboboxes dynamically if required
0326     if (m_columns.size() > 2) {
0327         auto* gridLayout = dynamic_cast<QGridLayout*>(ui->scrollAreaColumns->widget()->layout());
0328         for (int i = 2; i < m_columns.size(); ++i) {
0329             QLabel* label = new QLabel(i18n("Y-data"));
0330             auto* comboBox = new QComboBox();
0331             gridLayout->addWidget(label, i + 1, 0, 1, 1);
0332             gridLayout->addWidget(comboBox, i + 1, 2, 1, 1);
0333             m_columnComboBoxes << comboBox;
0334         }
0335     } else {
0336         // two columns provided, only one curve is possible -> hide the curve placement options
0337         ui->rbCurvePlacementAllInOnePlotArea->setChecked(true);
0338         ui->gbCurvePlacement->hide();
0339         ui->gbPlotPlacement->setTitle(i18n("Add Plot to"));
0340     }
0341 
0342     // show all selected/available column names in the data comboboxes
0343     for (auto* const comboBox : m_columnComboBoxes)
0344         comboBox->addItems(columnNames);
0345 
0346     if (!xColumnName.isEmpty()) {
0347         // show in the X-data combobox the first column having X as the plot designation
0348         ui->cbXColumn->setCurrentIndex(ui->cbXColumn->findText(xColumnName));
0349 
0350         // for the remaining columns, show the names in the comboboxes for the Y-data
0351         // TODO: handle columns with error-designations
0352         int yColumnIndex = 1; // the index of the first Y-data comboBox in m_columnComboBoxes
0353         for (const QString& name : columnNames) {
0354             if (name != xColumnName) {
0355                 QComboBox* comboBox = m_columnComboBoxes[yColumnIndex];
0356                 comboBox->setCurrentIndex(comboBox->findText(name));
0357                 yColumnIndex++;
0358             }
0359         }
0360     } else {
0361         // no column with "x plot designation" is selected, simply show all columns in the order they were selected.
0362         // first selected column will serve as the x-column.
0363         int yColumnIndex = 0;
0364         for (const QString& name : columnNames) {
0365             QComboBox* comboBox = m_columnComboBoxes[yColumnIndex];
0366             comboBox->setCurrentIndex(comboBox->findText(name));
0367             yColumnIndex++;
0368         }
0369     }
0370 }
0371 
0372 /*!
0373  * processes columns for cases where one single column
0374  * is required per "plot" (histogram, boxplot, etc.)
0375  * */
0376 void PlotDataDialog::processColumnsForHistogram(const QStringList& columnNames) {
0377     ui->line->hide();
0378     ui->spacer->changeSize(0, 0);
0379     ui->chkCreateDataCurve->hide();
0380 
0381     // use the already available cbXColumn combo box
0382     ui->lXColumn->setText(i18n("Data"));
0383     m_columnComboBoxes << ui->cbXColumn;
0384     ui->cbXColumn->addItems(columnNames);
0385     ui->cbXColumn->setCurrentIndex(0);
0386 
0387     if (m_columns.size() == 1) {
0388         // one column provided, only one histogram is possible
0389         //-> hide the curve placement options and the scroll areas for further columns
0390         ui->lYColumn->hide();
0391         ui->cbYColumn->hide();
0392         ui->rbCurvePlacementAllInOnePlotArea->setChecked(true);
0393         ui->gbCurvePlacement->hide();
0394         ui->gbPlotPlacement->setTitle(i18n("Add Plot to"));
0395         ui->scrollAreaColumns->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
0396     } else {
0397         ui->gbPlotPlacement->setTitle(i18n("Add Plots to"));
0398         ui->scrollAreaColumns->setVerticalScrollBarPolicy(Qt::ScrollBarAsNeeded);
0399 
0400         // use the already available cbYColumn combo box
0401         ui->lYColumn->setText(i18n("Data"));
0402         m_columnComboBoxes << ui->cbYColumn;
0403         ui->cbYColumn->addItems(columnNames);
0404         ui->cbYColumn->setCurrentIndex(1);
0405 
0406         // add a ComboBox for every further column to be plotted
0407         auto* gridLayout = dynamic_cast<QGridLayout*>(ui->scrollAreaColumns->widget()->layout());
0408         for (int i = 2; i < m_columns.size(); ++i) {
0409             auto* label = new QLabel(i18n("Data"));
0410             auto* comboBox = new QComboBox();
0411             gridLayout->addWidget(label, i + 1, 0, 1, 1);
0412             gridLayout->addWidget(comboBox, i + 1, 2, 1, 1);
0413             comboBox->addItems(columnNames);
0414             comboBox->setCurrentIndex(i);
0415             m_columnComboBoxes << comboBox;
0416         }
0417     }
0418 }
0419 
0420 void PlotDataDialog::plot() {
0421     WAIT_CURSOR;
0422     m_parentAspect->project()->setSuppressAspectAddedSignal(true);
0423     m_lastAddedCurve = nullptr;
0424 
0425     if (ui->rbPlotPlacementExistingPlotArea->isChecked()) {
0426         // add curves to an existing plot
0427         auto* aspect = static_cast<AbstractAspect*>(cbExistingPlots->currentModelIndex().internalPointer());
0428         auto* plot = static_cast<CartesianPlot*>(aspect);
0429         plot->beginMacro(i18n("Plot Area - %1", m_parentAspect->name()));
0430         addCurvesToPlot(plot);
0431         plot->endMacro();
0432     } else if (ui->rbPlotPlacementExistingWorksheet->isChecked()) {
0433         // add curves to a new plot in an existing worksheet
0434         auto* aspect = static_cast<AbstractAspect*>(cbExistingWorksheets->currentModelIndex().internalPointer());
0435         auto* worksheet = static_cast<Worksheet*>(aspect);
0436         worksheet->beginMacro(i18n("Worksheet - %1", m_parentAspect->name()));
0437 
0438         if (ui->rbCurvePlacementAllInOnePlotArea->isChecked()) {
0439             // all curves in one plot
0440             auto* plot = new CartesianPlot(i18n("Plot Area - %1", m_parentAspect->name()));
0441             plot->setType(CartesianPlot::Type::FourAxes);
0442             worksheet->addChild(plot);
0443             if (m_columnComboBoxes.count() == 2)
0444                 setAxesColumnLabels(plot, m_columnComboBoxes.at(1)->currentText());
0445 
0446             addCurvesToPlot(plot);
0447             setAxesTitles(plot);
0448         } else {
0449             // one plot per curve
0450             addCurvesToPlots(worksheet);
0451         }
0452         worksheet->endMacro();
0453     } else {
0454         // add curves to a new plot(s) in a new worksheet
0455         auto* parent = m_parentAspect->parentAspect();
0456         if (parent->type() == AspectType::DatapickerCurve)
0457             parent = parent->parentAspect()->parentAspect();
0458         else if (parent->type() == AspectType::Workbook)
0459             parent = parent->parentAspect();
0460 #ifdef HAVE_MQTT
0461         else if (dynamic_cast<MQTTTopic*>(m_parentAspect))
0462             parent = m_parentAspect->project();
0463 #endif
0464         parent->beginMacro(i18n("Plot data from %1", m_parentAspect->name()));
0465         auto* worksheet = new Worksheet(i18n("Worksheet - %1", m_parentAspect->name()));
0466         parent->addChild(worksheet);
0467 
0468         if (ui->rbCurvePlacementAllInOnePlotArea->isChecked()) {
0469             // all curves in one plot
0470             auto* plot = new CartesianPlot(i18n("Plot Area - %1", m_parentAspect->name()));
0471             plot->setType(CartesianPlot::Type::FourAxes);
0472             worksheet->addChild(plot);
0473             if (m_columnComboBoxes.count() == 2)
0474                 setAxesColumnLabels(plot, m_columnComboBoxes.at(1)->currentText());
0475             addCurvesToPlot(plot);
0476             setAxesTitles(plot);
0477         } else {
0478             // one plot per curve
0479             addCurvesToPlots(worksheet);
0480         }
0481 
0482         // when creating a new worksheet with many plots it can happen the place available for the data in the plot
0483         // is small and the result created here doesn't look nice. To avoid this and as a quick a dirty workaround,
0484         // we increase the size of the worksheet so the plots have a certain minimal size.
0485         // TODO:
0486         // A more sophisticated and better logic would require further adjustments for properties like plot area
0487         // paddings, the font size of axis ticks and title labels, etc. Also, this logic should be applied maybe if
0488         // we add plots to an already created worksheet.
0489         adjustWorksheetSize(worksheet);
0490 
0491         parent->endMacro();
0492     }
0493 
0494     // if new curves are created via this dialog, select the parent plot of the last added curve in the project explorer.
0495     // if a custom fit is being done, select the last created fit curve independent of the number of created curves.
0496     m_parentAspect->project()->setSuppressAspectAddedSignal(false);
0497     if (m_lastAddedCurve) {
0498         QString path;
0499         if (!m_analysisMode) {
0500             path = m_lastAddedCurve->parentAspect()->path();
0501         } else {
0502             if (m_analysisAction == XYAnalysisCurve::AnalysisAction::FitCustom)
0503                 path = m_lastAddedCurve->path();
0504             else
0505                 path = m_lastAddedCurve->parentAspect()->path();
0506         }
0507 
0508         Q_EMIT m_parentAspect->project()->requestNavigateTo(path);
0509     }
0510     RESET_CURSOR;
0511 }
0512 
0513 Column* PlotDataDialog::columnFromName(const QString& name) const {
0514     for (auto* column : m_columns) {
0515         if (column->name() == name)
0516             return column;
0517     }
0518     return nullptr;
0519 }
0520 
0521 /*!
0522  * * for the selected columns in this dialog, creates a curve in the already existing plot \c plot.
0523  */
0524 void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) {
0525     QApplication::processEvents(QEventLoop::AllEvents, 100);
0526     switch (m_plotType) {
0527     case PlotType::XYCurve: {
0528         Column* xColumn = columnFromName(ui->cbXColumn->currentText());
0529         for (auto* comboBox : m_columnComboBoxes) {
0530             const QString& name = comboBox->currentText();
0531             Column* yColumn = columnFromName(name);
0532 
0533             // if only one column was selected, allow to use this column for x and for y.
0534             // otherwise, don't assign xColumn to y
0535             if (yColumn == xColumn) {
0536                 if (m_columns.size() == 1) {
0537                     addCurve(name, xColumn, yColumn, plot);
0538                     break;
0539                 } else
0540                     continue;
0541             }
0542 
0543             addCurve(name, xColumn, yColumn, plot);
0544         }
0545         break;
0546     }
0547     case PlotType::Histogram:
0548     case PlotType::KDEPlot:
0549     case PlotType::QQPlot: {
0550         for (auto* comboBox : m_columnComboBoxes) {
0551             const QString& name = comboBox->currentText();
0552             Column* column = columnFromName(name);
0553             addSingleSourceColumnPlot(column, plot);
0554         }
0555         break;
0556     }
0557     case PlotType::BoxPlot:
0558     case PlotType::BarPlot:
0559     case PlotType::LollipopPlot: {
0560         QVector<const AbstractColumn*> columns;
0561         for (auto* comboBox : m_columnComboBoxes)
0562             columns << columnFromName(comboBox->currentText());
0563 
0564         addMultiSourceColumnsPlot(columns, plot);
0565         break;
0566     }
0567     }
0568 
0569     // TODO: check if it really needs to update all ranges
0570     plot->dataChanged(-1, -1);
0571 }
0572 
0573 /*!
0574  * for the selected columns in this dialog, creates a plot and a curve in the already existing worksheet \c worksheet.
0575  */
0576 void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) {
0577     QApplication::processEvents(QEventLoop::AllEvents, 100);
0578     worksheet->setSuppressLayoutUpdate(true);
0579 
0580     switch (m_plotType) {
0581     case PlotType::XYCurve: {
0582         const QString& xColumnName = ui->cbXColumn->currentText();
0583         Column* xColumn = columnFromName(xColumnName);
0584         for (auto* comboBox : m_columnComboBoxes) {
0585             const QString& name = comboBox->currentText();
0586             Column* yColumn = columnFromName(name);
0587             if (yColumn == xColumn)
0588                 continue;
0589 
0590             auto* plot = new CartesianPlot(i18n("Plot Area %1", name));
0591             plot->setType(CartesianPlot::Type::FourAxes);
0592             worksheet->addChild(plot);
0593             setAxesColumnLabels(plot, yColumn);
0594             addCurve(name, xColumn, yColumn, plot);
0595             plot->scaleAuto(-1, -1);
0596             plot->retransform();
0597             setAxesTitles(plot, name);
0598         }
0599         break;
0600     }
0601     case PlotType::Histogram:
0602     case PlotType::KDEPlot:
0603     case PlotType::QQPlot: {
0604         for (auto* comboBox : m_columnComboBoxes) {
0605             const QString& name = comboBox->currentText();
0606             Column* column = columnFromName(name);
0607 
0608             auto* plot = new CartesianPlot(i18n("Plot Area %1", name));
0609             plot->setType(CartesianPlot::Type::FourAxes);
0610             setAxesTitles(plot, name);
0611             worksheet->addChild(plot);
0612             addSingleSourceColumnPlot(column, plot);
0613             plot->scaleAuto(-1, -1);
0614             plot->retransform();
0615         }
0616         break;
0617     }
0618     case PlotType::BoxPlot:
0619     case PlotType::BarPlot:
0620     case PlotType::LollipopPlot: {
0621         for (auto* comboBox : m_columnComboBoxes) {
0622             const QString& name = comboBox->currentText();
0623             Column* column = columnFromName(name);
0624 
0625             auto* plot = new CartesianPlot(i18n("Plot Area %1", name));
0626             plot->setType(CartesianPlot::Type::FourAxes);
0627             worksheet->addChild(plot);
0628             addMultiSourceColumnsPlot(QVector<const AbstractColumn*>{column}, plot);
0629             plot->scaleAuto(-1, -1);
0630             plot->retransform();
0631             setAxesTitles(plot, name);
0632         }
0633         break;
0634     }
0635     }
0636 
0637     worksheet->setSuppressLayoutUpdate(false);
0638     worksheet->updateLayout();
0639 }
0640 
0641 /*!
0642  * helper function that does the actual creation of the curve and adding it as child to the \c plot.
0643  */
0644 void PlotDataDialog::addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot* plot) {
0645     if (!m_analysisMode) {
0646         auto* curve = new XYCurve(name);
0647         curve->setSuppressRetransform(true);
0648         curve->setXColumn(xColumn);
0649         curve->setYColumn(yColumn);
0650         curve->setSuppressRetransform(false);
0651         plot->addChild(curve);
0652         m_lastAddedCurve = curve;
0653     } else {
0654         bool createDataCurve = ui->chkCreateDataCurve->isChecked();
0655         XYCurve* curve = nullptr;
0656         if (createDataCurve) {
0657             curve = new XYCurve(name);
0658             curve->setSuppressRetransform(true);
0659             curve->setXColumn(xColumn);
0660             curve->setYColumn(yColumn);
0661             curve->setSuppressRetransform(false);
0662             plot->addChild(curve);
0663             m_lastAddedCurve = curve;
0664         }
0665 
0666         XYAnalysisCurve* analysisCurve = nullptr;
0667         switch (m_analysisAction) {
0668         case XYAnalysisCurve::AnalysisAction::DataReduction:
0669             analysisCurve = new XYDataReductionCurve(i18n("Reduction of '%1'", name));
0670             break;
0671         case XYAnalysisCurve::AnalysisAction::Differentiation:
0672             analysisCurve = new XYDifferentiationCurve(i18n("Derivative of '%1'", name));
0673             break;
0674         case XYAnalysisCurve::AnalysisAction::Integration:
0675             analysisCurve = new XYIntegrationCurve(i18n("Integral of '%1'", name));
0676             break;
0677         case XYAnalysisCurve::AnalysisAction::Interpolation:
0678             analysisCurve = new XYInterpolationCurve(i18n("Interpolation of '%1'", name));
0679             break;
0680         case XYAnalysisCurve::AnalysisAction::Smoothing:
0681             analysisCurve = new XYSmoothCurve(i18n("Smoothing of '%1'", name));
0682             break;
0683         case XYAnalysisCurve::AnalysisAction::FitLinear:
0684         case XYAnalysisCurve::AnalysisAction::FitPower:
0685         case XYAnalysisCurve::AnalysisAction::FitExp1:
0686         case XYAnalysisCurve::AnalysisAction::FitExp2:
0687         case XYAnalysisCurve::AnalysisAction::FitInvExp:
0688         case XYAnalysisCurve::AnalysisAction::FitGauss:
0689         case XYAnalysisCurve::AnalysisAction::FitCauchyLorentz:
0690         case XYAnalysisCurve::AnalysisAction::FitTan:
0691         case XYAnalysisCurve::AnalysisAction::FitTanh:
0692         case XYAnalysisCurve::AnalysisAction::FitErrFunc:
0693         case XYAnalysisCurve::AnalysisAction::FitCustom:
0694             analysisCurve = new XYFitCurve(i18n("Fit to '%1'", name));
0695             static_cast<XYFitCurve*>(analysisCurve)->initFitData(m_analysisAction);
0696             static_cast<XYFitCurve*>(analysisCurve)->initStartValues(curve);
0697             break;
0698         case XYAnalysisCurve::AnalysisAction::FourierFilter:
0699             analysisCurve = new XYFourierFilterCurve(i18n("Fourier Filter of '%1'", name));
0700             break;
0701         }
0702 
0703         if (analysisCurve != nullptr) {
0704             analysisCurve->setSuppressRetransform(true);
0705             analysisCurve->setXDataColumn(xColumn);
0706             analysisCurve->setYDataColumn(yColumn);
0707             if (m_analysisAction != XYAnalysisCurve::AnalysisAction::FitCustom) // no custom fit-model set yet, no need to recalculate
0708                 analysisCurve->recalculate();
0709             analysisCurve->setSuppressRetransform(false);
0710             plot->addChild(analysisCurve);
0711             m_lastAddedCurve = analysisCurve;
0712         }
0713     }
0714 }
0715 
0716 void PlotDataDialog::addSingleSourceColumnPlot(const Column* column, CartesianPlot* plotArea) {
0717     const QString& name = column->name();
0718     QApplication::processEvents(QEventLoop::AllEvents, 100);
0719     Plot* plot{nullptr};
0720     if (m_plotType == PlotType::Histogram) {
0721         auto* hist = new Histogram(name);
0722         hist->setDataColumn(column);
0723         plot = hist;
0724     } else if (m_plotType == PlotType::KDEPlot) {
0725         auto* kdePlot = new KDEPlot(name);
0726         kdePlot->setDataColumn(column);
0727         plot = kdePlot;
0728     } else if (m_plotType == PlotType::QQPlot) {
0729         auto* qqPlot = new QQPlot(name);
0730         qqPlot->setDataColumn(column);
0731         plot = qqPlot;
0732     }
0733 
0734     if (plot) {
0735         plotArea->addChild(plot);
0736         m_lastAddedCurve = plot;
0737     }
0738 }
0739 
0740 void PlotDataDialog::addMultiSourceColumnsPlot(const QVector<const AbstractColumn*>& columns, CartesianPlot* plotArea) {
0741     QString name;
0742     if (columns.size() > 1) {
0743         if (m_plotType == PlotType::BoxPlot)
0744             name = i18n("Box Plot");
0745         else if (m_plotType == PlotType::BarPlot)
0746             name = i18n("Bar Plot");
0747         else if (m_plotType == PlotType::LollipopPlot)
0748             name = i18n("Bar Plot");
0749     } else
0750         name = columns.constFirst()->name();
0751 
0752     QApplication::processEvents(QEventLoop::AllEvents, 100);
0753     Plot* plot{nullptr};
0754     if (m_plotType == PlotType::BoxPlot) {
0755         auto* boxPlot = new BoxPlot(name);
0756         boxPlot->setDataColumns(columns);
0757         plot = boxPlot;
0758     } else if (m_plotType == PlotType::BarPlot) {
0759         auto* barPlot = new BarPlot(name);
0760         barPlot->setDataColumns(columns);
0761         plot = barPlot;
0762     } else if (m_plotType == PlotType::LollipopPlot) {
0763         auto* lollipopPlot = new LollipopPlot(name);
0764         lollipopPlot->setDataColumns(columns);
0765         plot = lollipopPlot;
0766     }
0767 
0768     if (plot) {
0769         plotArea->addChild(plot);
0770         m_lastAddedCurve = plot;
0771     }
0772 }
0773 
0774 void PlotDataDialog::adjustWorksheetSize(Worksheet* worksheet) const {
0775     // adjust the sizes
0776     const auto layout = worksheet->layout();
0777     const auto plots = worksheet->children<CartesianPlot>();
0778     const int count = plots.size();
0779     const double minSize = 4.0;
0780     switch (layout) {
0781     case Worksheet::Layout::NoLayout:
0782     case Worksheet::Layout::VerticalLayout: {
0783         if (layout == Worksheet::Layout::NoLayout)
0784             worksheet->setLayout(Worksheet::Layout::VerticalLayout);
0785 
0786         const auto plot = plots.constFirst();
0787         double height = Worksheet::convertFromSceneUnits(plot->rect().height(), Worksheet::Unit::Centimeter);
0788         if (height < 4.) {
0789             double newHeight = worksheet->layoutTopMargin() + worksheet->layoutBottomMargin() + (count - 1) * worksheet->layoutHorizontalSpacing()
0790                 + count * Worksheet::convertToSceneUnits(minSize, Worksheet::Unit::Centimeter);
0791             QRectF newRect = worksheet->pageRect();
0792             newRect.setHeight(round(newHeight));
0793             worksheet->setPageRect(newRect);
0794         }
0795         break;
0796     }
0797     case Worksheet::Layout::HorizontalLayout: {
0798         const auto plot = plots.constFirst();
0799         double width = Worksheet::convertFromSceneUnits(plot->rect().width(), Worksheet::Unit::Centimeter);
0800         if (width < 4.) {
0801             double newWidth = worksheet->layoutLeftMargin() + worksheet->layoutRightMargin() + (count - 1) * worksheet->layoutVerticalSpacing()
0802                 + count * Worksheet::convertToSceneUnits(minSize, Worksheet::Unit::Centimeter);
0803             QRectF newRect = worksheet->pageRect();
0804             newRect.setWidth(round(newWidth));
0805             worksheet->setPageRect(newRect);
0806         }
0807         break;
0808     }
0809     case Worksheet::Layout::GridLayout: {
0810         const auto plot = plots.constFirst();
0811         double width = Worksheet::convertFromSceneUnits(plot->rect().width(), Worksheet::Unit::Centimeter);
0812         double height = Worksheet::convertFromSceneUnits(plot->rect().height(), Worksheet::Unit::Centimeter);
0813         if (width < 4. || height < 4.) {
0814             QRectF newRect = worksheet->pageRect();
0815             if (height < 4.) {
0816                 double newHeight = worksheet->layoutTopMargin() + worksheet->layoutBottomMargin() + (count - 1) * worksheet->layoutHorizontalSpacing()
0817                     + count * Worksheet::convertToSceneUnits(minSize, Worksheet::Unit::Centimeter);
0818                 newRect.setHeight(round(newHeight));
0819             } else {
0820                 double newWidth = worksheet->layoutLeftMargin() + worksheet->layoutRightMargin() + (count - 1) * worksheet->layoutVerticalSpacing()
0821                     + count * Worksheet::convertToSceneUnits(minSize, Worksheet::Unit::Centimeter);
0822                 newRect.setWidth(round(newWidth));
0823             }
0824             worksheet->setPageRect(newRect);
0825         }
0826         break;
0827     }
0828     }
0829 }
0830 
0831 void PlotDataDialog::setAxesColumnLabels(CartesianPlot* plot, const Column* column) {
0832     // Use value labels if possible
0833     if (column && column->valueLabelsInitialized()) {
0834         auto axes = plot->children(AspectType::Axis);
0835         for (auto a : axes) {
0836             auto axis = static_cast<Axis*>(a);
0837             if (axis->orientation() == Axis::Orientation::Vertical) {
0838                 // Use first vertical axis
0839                 axis->setMajorTicksType(Axis::TicksType::ColumnLabels);
0840                 axis->setMajorTicksColumn(column);
0841                 break;
0842             }
0843         }
0844     }
0845 }
0846 
0847 void PlotDataDialog::setAxesColumnLabels(CartesianPlot* plot, const QString& columnName) {
0848     Column* column = columnFromName(columnName);
0849     setAxesColumnLabels(plot, column);
0850 }
0851 
0852 void PlotDataDialog::setAxesTitles(CartesianPlot* plot, const QString& name) const {
0853     DEBUG(Q_FUNC_INFO)
0854     const auto& axes = plot->children<Axis>();
0855     switch (m_plotType) {
0856     case PlotType::XYCurve: {
0857         // x-axis title
0858         const QString& xColumnName = ui->cbXColumn->currentText();
0859         // DEBUG(Q_FUNC_INFO << ", x column name = " << STDSTRING(xColumnName))
0860 
0861         for (auto* axis : axes) {
0862             if (axis->orientation() == Axis::Orientation::Horizontal) {
0863                 axis->title()->setText(xColumnName);
0864                 break;
0865             }
0866         }
0867 
0868         // y-axis title
0869         for (auto* axis : axes) {
0870             if (axis->orientation() == Axis::Orientation::Vertical) {
0871                 if (!name.isEmpty()) {
0872                     // multiple columns are plotted with "one curve per plot",
0873                     // the function is called with the column name.
0874                     // use it for the x-axis title
0875                     axis->title()->setText(name);
0876                 } else if (m_columnComboBoxes.size() == 2) {
0877                     // if we only have one single y-column to plot, we can set the title of the y-axes
0878                     const QString& yColumnName = m_columnComboBoxes[1]->currentText();
0879                     axis->title()->setText(yColumnName);
0880                 }
0881 
0882                 break;
0883             }
0884         }
0885         break;
0886     }
0887     case PlotType::Histogram:
0888     case PlotType::KDEPlot: {
0889         // x-axis title
0890         for (auto* axis : axes) {
0891             if (axis->orientation() == Axis::Orientation::Horizontal) {
0892                 if (!name.isEmpty()) {
0893                     // multiple columns are plotted with "one curve per plot",
0894                     // the function is called with the column name.
0895                     // use it for the x-axis title
0896                     axis->title()->setText(name);
0897                 } else if (m_columnComboBoxes.size() == 1) {
0898                     const QString& yColumnName = m_columnComboBoxes.constFirst()->currentText();
0899                     axis->title()->setText(yColumnName);
0900                 }
0901 
0902                 break;
0903             }
0904         }
0905 
0906         // y-axis title
0907         for (auto* axis : axes) {
0908             if (axis->orientation() == Axis::Orientation::Vertical) {
0909                 if (m_plotType == PlotType::Histogram)
0910                     axis->title()->setText(i18n("Frequency"));
0911                 else
0912                     axis->title()->setText(i18n("Density"));
0913                 break;
0914             }
0915         }
0916         break;
0917     }
0918     case PlotType::QQPlot: {
0919         // x-axis title
0920         for (auto* axis : axes) {
0921             if (axis->orientation() == Axis::Orientation::Horizontal) {
0922                 axis->title()->setText(i18n("Theoretical Quantiles"));
0923                 break;
0924             }
0925         }
0926 
0927         // y-axis title
0928         for (auto* axis : axes) {
0929             if (axis->orientation() == Axis::Orientation::Vertical) {
0930                 axis->title()->setText(i18n("Sample Quantiles"));
0931                 break;
0932             }
0933         }
0934         break;
0935     }
0936     case PlotType::BoxPlot: {
0937         auto* boxPlot = static_cast<BoxPlot*>(m_lastAddedCurve);
0938         auto orientation = boxPlot->orientation();
0939 
0940         // number of box plots per plot:
0941         int count = 1; // 1 if we create a separate plot for every box plot
0942         if (ui->rbCurvePlacementAllInOnePlotArea->isChecked())
0943             count = m_columnComboBoxes.count(); // all box plots in one single plot
0944 
0945         // set the range of the plot and the number of ticks manually.
0946         // the n-th box plot is positioned at x=n and has the width=0.5 in logical coordinatates.
0947         // manually set the range to (0.5, n+0.5) and ajdust the number of ticks starting at 0.5
0948         // to make sure we have every tick precisely under the middle of the box plot
0949         const auto xIndex = plot->coordinateSystem(boxPlot->coordinateSystemIndex())->index(Dimension::X);
0950         const auto yIndex = plot->coordinateSystem(boxPlot->coordinateSystemIndex())->index(Dimension::Y);
0951         Range<double> range(0.5, count + 0.5);
0952         if (orientation == WorksheetElement::Orientation::Vertical)
0953             plot->setRange(Dimension::X, xIndex, range);
0954         else
0955             plot->setRange(Dimension::Y, yIndex, range);
0956 
0957         for (auto* axis : axes) {
0958             if (axis->orientation() != orientation) {
0959                 axis->setMajorTicksType(Axis::TicksType::Spacing);
0960                 axis->setMajorTicksSpacing(1.);
0961                 axis->setMajorTickStartOffset(0.5);
0962                 axis->setMinorTicksNumber(0);
0963             }
0964             axis->title()->setText(QString()); // no title
0965         }
0966 
0967         // y-axis title
0968         for (auto* axis : axes) {
0969             if (axis->orientation() == Axis::Orientation::Vertical) {
0970                 if (!name.isEmpty()) {
0971                     // multiple columns are plotted with "one curve per plot",
0972                     // the function is called with the column name.
0973                     // use it for the x-axis title
0974                     axis->title()->setText(name);
0975                 } else if (count == 1) {
0976                     const QString& yColumnName = m_columnComboBoxes.constFirst()->currentText();
0977                     axis->title()->setText(yColumnName);
0978                 }
0979                 break;
0980             }
0981         }
0982         break;
0983     }
0984     case PlotType::BarPlot:
0985     case PlotType::LollipopPlot: {
0986         WorksheetElement::Orientation orientation;
0987         if (m_plotType == PlotType::BarPlot) {
0988             auto* barPlot = static_cast<BarPlot*>(m_lastAddedCurve);
0989             orientation = barPlot->orientation();
0990         } else {
0991             auto* lollipopPlot = static_cast<LollipopPlot*>(m_lastAddedCurve);
0992             orientation = lollipopPlot->orientation();
0993         }
0994 
0995         plot->setNiceExtend(false);
0996 
0997         for (auto* axis : axes) {
0998             if (axis->orientation() != orientation) {
0999                 axis->setMajorTicksType(Axis::TicksType::Spacing);
1000                 axis->setMajorTickStartOffset(0.5);
1001                 axis->setMajorTicksSpacing(1.);
1002                 axis->setLabelsPosition(Axis::LabelsPosition::NoLabels);
1003                 axis->setMinorTicksDirection(Axis::noTicks);
1004             }
1005             axis->title()->setText(QString()); // no title
1006         }
1007     }
1008     }
1009 }
1010 
1011 // ################################################################
1012 // ########################## Slots ###############################
1013 // ################################################################
1014 void PlotDataDialog::curvePlacementChanged() {
1015     if (ui->rbCurvePlacementAllInOnePlotArea->isChecked()) {
1016         ui->rbPlotPlacementExistingPlotArea->setEnabled(true);
1017     } else {
1018         ui->rbPlotPlacementExistingPlotArea->setEnabled(false);
1019         if (ui->rbPlotPlacementExistingPlotArea->isChecked())
1020             ui->rbPlotPlacementExistingWorksheet->setChecked(true);
1021     }
1022 }
1023 
1024 void PlotDataDialog::plotPlacementChanged() {
1025     if (ui->rbPlotPlacementExistingPlotArea->isChecked()) {
1026         cbExistingPlots->setEnabled(true);
1027         cbExistingWorksheets->setEnabled(false);
1028     } else if (ui->rbPlotPlacementExistingWorksheet->isChecked()) {
1029         cbExistingPlots->setEnabled(false);
1030         cbExistingWorksheets->setEnabled(true);
1031     } else {
1032         cbExistingPlots->setEnabled(false);
1033         cbExistingWorksheets->setEnabled(false);
1034     }
1035 
1036     checkOkButton();
1037 }
1038 
1039 void PlotDataDialog::checkOkButton() {
1040     bool enable = false;
1041     QString msg;
1042     if ((m_plotType == PlotType::XYCurve && (ui->cbXColumn->currentIndex() == -1 || ui->cbYColumn->currentIndex() == -1))
1043         || (m_plotType == PlotType::Histogram && ui->cbXColumn->currentIndex() == -1))
1044         msg = i18n("No data selected to plot.");
1045     else if (ui->rbPlotPlacementExistingPlotArea->isChecked()) {
1046         AbstractAspect* aspect = static_cast<AbstractAspect*>(cbExistingPlots->currentModelIndex().internalPointer());
1047         enable = (aspect != nullptr);
1048         if (!enable)
1049             msg = i18n("An already existing plot area has to be selected.");
1050     } else if (ui->rbPlotPlacementExistingWorksheet->isChecked()) {
1051         AbstractAspect* aspect = static_cast<AbstractAspect*>(cbExistingWorksheets->currentModelIndex().internalPointer());
1052         enable = (aspect != nullptr);
1053         if (!enable)
1054             msg = i18n("An already existing worksheet has to be selected.");
1055     } else
1056         enable = true;
1057 
1058     m_okButton->setEnabled(enable);
1059     if (enable)
1060         m_okButton->setToolTip(i18n("Close the dialog and plot the data."));
1061     else
1062         m_okButton->setToolTip(msg);
1063 }