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

0001 /*
0002     File                 : BoxPlotDock.cpp
0003     Project              : LabPlot
0004     Description          : Dock widget for the reference line on the plot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2020-2024 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include "BoxPlotDock.h"
0011 #include "backend/core/AbstractColumn.h"
0012 #include "backend/lib/macros.h"
0013 #include "backend/worksheet/Worksheet.h"
0014 #include "commonfrontend/widgets/TreeViewComboBox.h"
0015 #include "kdefrontend/GuiTools.h"
0016 #include "kdefrontend/TemplateHandler.h"
0017 #include "kdefrontend/widgets/BackgroundWidget.h"
0018 #include "kdefrontend/widgets/LineWidget.h"
0019 #include "kdefrontend/widgets/SymbolWidget.h"
0020 
0021 #include <QPushButton>
0022 
0023 #include <KConfig>
0024 #include <KLocalizedString>
0025 
0026 BoxPlotDock::BoxPlotDock(QWidget* parent)
0027     : BaseDock(parent) {
0028     ui.setupUi(this);
0029     setPlotRangeCombobox(ui.cbPlotRanges);
0030     setBaseWidgets(ui.leName, ui.teComment);
0031     setVisibilityWidgets(ui.chkVisible, ui.chkLegendVisible);
0032 
0033     // Tab "General"
0034     m_buttonNew = new QPushButton();
0035     m_buttonNew->setIcon(QIcon::fromTheme(QStringLiteral("list-add")));
0036     connect(m_buttonNew, &QPushButton::clicked, this, &BoxPlotDock::addDataColumn);
0037 
0038     m_gridLayout = new QGridLayout(ui.frameDataColumns);
0039     m_gridLayout->setContentsMargins(0, 0, 0, 0);
0040     m_gridLayout->setHorizontalSpacing(2);
0041     m_gridLayout->setVerticalSpacing(2);
0042     ui.frameDataColumns->setLayout(m_gridLayout);
0043 
0044     ui.cbWhiskersType->addItem(QStringLiteral("min/max"));
0045     ui.cbWhiskersType->addItem(QStringLiteral("Tukey"));
0046     ui.cbWhiskersType->addItem(QStringLiteral("mean ∓ k*SD"));
0047     ui.cbWhiskersType->addItem(QStringLiteral("median ∓ k*MAD"));
0048     ui.cbWhiskersType->addItem(i18n("10/90 percentiles"));
0049     ui.cbWhiskersType->addItem(i18n("5/95 percentiles"));
0050     ui.cbWhiskersType->addItem(i18n("1/99 percentiles"));
0051 
0052     ui.cbOrientation->addItem(i18n("Horizontal"));
0053     ui.cbOrientation->addItem(i18n("Vertical"));
0054 
0055     ui.cbOrdering->addItem(i18n("None"));
0056     ui.cbOrdering->addItem(i18n("By Median, Ascending"));
0057     ui.cbOrdering->addItem(i18n("By Median, Descending"));
0058     ui.cbOrdering->addItem(i18n("By Mean, Ascending"));
0059     ui.cbOrdering->addItem(i18n("By Mean, Descending"));
0060 
0061     QString msg = i18n("If multiple data sets are provided, define how they should be ordered or use 'None' to keep the original order.");
0062     ui.lOrdering->setToolTip(msg);
0063     ui.cbOrdering->setToolTip(msg);
0064 
0065     msg = i18n("If checked, the box width is made proportional to the square root of the number of data points.");
0066     ui.lVariableWidth->setToolTip(msg);
0067     ui.chkVariableWidth->setToolTip(msg);
0068 
0069     msg = i18n("Parameter controlling the range of the inner fences of the box plot.");
0070     ui.lWhiskersRangeParameter->setToolTip(msg);
0071     ui.leWhiskersRangeParameter->setToolTip(msg);
0072 
0073     // Tab "Box"
0074     msg = i18n("Select the data column for which the properties should be shown and edited");
0075     ui.lNumber->setToolTip(msg);
0076     ui.cbNumber->setToolTip(msg);
0077 
0078     msg = i18n("Specify the factor in percent to control the width of the box relative to its default value.");
0079     ui.lWidthFactor->setToolTip(msg);
0080     ui.sbWidthFactor->setToolTip(msg);
0081 
0082     // Tab "Box"
0083     auto* gridLayout = static_cast<QGridLayout*>(ui.tabBox->layout());
0084     backgroundWidget = new BackgroundWidget(ui.tabBox);
0085     gridLayout->addWidget(backgroundWidget, 5, 0, 1, 3);
0086 
0087     // lines
0088     borderLineWidget = new LineWidget(ui.tabBox);
0089     gridLayout->addWidget(borderLineWidget, 8, 0, 1, 3);
0090 
0091     medianLineWidget = new LineWidget(ui.tabBox);
0092     gridLayout->addWidget(medianLineWidget, 11, 0, 1, 3);
0093 
0094     // Tab "Markers"
0095     gridLayout = static_cast<QGridLayout*>(ui.tabSymbol->layout());
0096     symbolWidget = new SymbolWidget(ui.tabSymbol);
0097     gridLayout->addWidget(symbolWidget, 2, 0, 1, 3);
0098 
0099     // Tab "Whiskers"
0100     gridLayout = static_cast<QGridLayout*>(ui.tabWhiskers->layout());
0101     whiskersLineWidget = new LineWidget(ui.tabBox);
0102     gridLayout->addWidget(whiskersLineWidget, 1, 0, 1, 3);
0103 
0104     whiskersCapLineWidget = new LineWidget(ui.tabBox);
0105     gridLayout->addWidget(whiskersCapLineWidget, 5, 0, 1, 3);
0106 
0107     // adjust layouts in the tabs
0108     for (int i = 0; i < ui.tabWidget->count(); ++i) {
0109         auto* layout = dynamic_cast<QGridLayout*>(ui.tabWidget->widget(i)->layout());
0110         if (!layout)
0111             continue;
0112 
0113         layout->setContentsMargins(2, 2, 2, 2);
0114         layout->setHorizontalSpacing(2);
0115         layout->setVerticalSpacing(2);
0116     }
0117 
0118     // Validators
0119     ui.leWhiskersRangeParameter->setValidator(new QDoubleValidator(ui.leWhiskersRangeParameter));
0120 
0121     // SLOTS
0122     // Tab "General"
0123     connect(ui.cbOrdering, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BoxPlotDock::orderingChanged);
0124     connect(ui.cbOrientation, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BoxPlotDock::orientationChanged);
0125     connect(ui.chkVariableWidth, &QCheckBox::toggled, this, &BoxPlotDock::variableWidthChanged);
0126     connect(ui.chkNotches, &QCheckBox::toggled, this, &BoxPlotDock::notchesEnabledChanged);
0127 
0128     // Tab "Box"
0129     connect(ui.cbNumber, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BoxPlotDock::currentBoxChanged);
0130     connect(ui.sbWidthFactor, QOverload<int>::of(&QSpinBox::valueChanged), this, &BoxPlotDock::widthFactorChanged);
0131 
0132     // Tab "Markers"
0133     connect(ui.rbMean, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0134     connect(ui.rbMedian, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0135     connect(ui.rbOutlier, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0136     connect(ui.rbFarOut, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0137     connect(ui.rbJitter, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0138     connect(ui.rbWhiskerEnd, &QRadioButton::toggled, this, &BoxPlotDock::symbolCategoryChanged);
0139     connect(ui.chkJitteringEnabled, &QCheckBox::toggled, this, &BoxPlotDock::jitteringEnabledChanged);
0140 
0141     // Tab "Whiskers"
0142     connect(ui.cbWhiskersType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &BoxPlotDock::whiskersTypeChanged);
0143     connect(ui.leWhiskersRangeParameter, &QLineEdit::textChanged, this, &BoxPlotDock::whiskersRangeParameterChanged);
0144     connect(ui.sbWhiskersCapSize, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &BoxPlotDock::whiskersCapSizeChanged);
0145 
0146     // Margin Plots
0147     connect(ui.chkRugEnabled, &QCheckBox::toggled, this, &BoxPlotDock::rugEnabledChanged);
0148     connect(ui.sbRugLength, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &BoxPlotDock::rugLengthChanged);
0149     connect(ui.sbRugWidth, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &BoxPlotDock::rugWidthChanged);
0150     connect(ui.sbRugOffset, QOverload<double>::of(&NumberSpinBox::valueChanged), this, &BoxPlotDock::rugOffsetChanged);
0151 
0152     // template handler
0153     auto* frame = new QFrame(this);
0154     auto* layout = new QHBoxLayout(frame);
0155     layout->setContentsMargins(0, 11, 0, 11);
0156 
0157     auto* templateHandler = new TemplateHandler(this, QLatin1String("BoxPlot"));
0158     layout->addWidget(templateHandler);
0159     connect(templateHandler, &TemplateHandler::loadConfigRequested, this, &BoxPlotDock::loadConfigFromTemplate);
0160     connect(templateHandler, &TemplateHandler::saveConfigRequested, this, &BoxPlotDock::saveConfigAsTemplate);
0161     connect(templateHandler, &TemplateHandler::info, this, &BoxPlotDock::info);
0162 
0163     ui.verticalLayout->addWidget(frame);
0164 }
0165 
0166 void BoxPlotDock::setBoxPlots(QList<BoxPlot*> list) {
0167     CONDITIONAL_LOCK_RETURN;
0168     m_boxPlots = list;
0169     m_boxPlot = list.first();
0170     setAspects(list);
0171     Q_ASSERT(m_boxPlot);
0172     setModel();
0173 
0174     QList<Background*> backgrounds;
0175     QList<Line*> borderLines;
0176     QList<Line*> medianLines;
0177     QList<Line*> whiskersLines;
0178     QList<Line*> whiskersCapLines;
0179     for (auto* plot : m_boxPlots) {
0180         backgrounds << plot->backgroundAt(0);
0181         borderLines << plot->borderLineAt(0);
0182         medianLines << plot->medianLineAt(0);
0183         whiskersLines << plot->whiskersLine();
0184         whiskersCapLines << plot->whiskersCapLine();
0185     }
0186     backgroundWidget->setBackgrounds(backgrounds);
0187     borderLineWidget->setLines(borderLines);
0188     medianLineWidget->setLines(medianLines);
0189     whiskersLineWidget->setLines(whiskersLines);
0190     whiskersCapLineWidget->setLines(whiskersCapLines);
0191 
0192     // show the properties of the first box plot
0193     ui.chkLegendVisible->setChecked(m_boxPlot->legendVisible());
0194     ui.chkVisible->setChecked(m_boxPlot->isVisible());
0195     load();
0196     loadDataColumns();
0197 
0198     updatePlotRangeList();
0199 
0200     // set the current locale
0201     updateLocale();
0202 
0203     // SIGNALs/SLOTs
0204     // general
0205     connect(m_boxPlot, &BoxPlot::orientationChanged, this, &BoxPlotDock::plotOrientationChanged);
0206     connect(m_boxPlot, &BoxPlot::variableWidthChanged, this, &BoxPlotDock::plotVariableWidthChanged);
0207     connect(m_boxPlot, &BoxPlot::notchesEnabledChanged, this, &BoxPlotDock::plotNotchesEnabledChanged);
0208     connect(m_boxPlot, &BoxPlot::dataColumnsChanged, this, &BoxPlotDock::plotDataColumnsChanged);
0209     connect(m_boxPlot, &BoxPlot::widthFactorChanged, this, &BoxPlotDock::plotWidthFactorChanged);
0210 
0211     // symbols
0212     connect(m_boxPlot, &BoxPlot::jitteringEnabledChanged, this, &BoxPlotDock::plotJitteringEnabledChanged);
0213 
0214     // whiskers
0215     connect(m_boxPlot, &BoxPlot::whiskersTypeChanged, this, &BoxPlotDock::plotWhiskersTypeChanged);
0216     connect(m_boxPlot, &BoxPlot::whiskersRangeParameterChanged, this, &BoxPlotDock::plotWhiskersRangeParameterChanged);
0217     connect(m_boxPlot, &BoxPlot::whiskersCapSizeChanged, this, &BoxPlotDock::plotWhiskersCapSizeChanged);
0218 
0219     //"Margin Plots"-Tab
0220     connect(m_boxPlot, &BoxPlot::rugEnabledChanged, this, &BoxPlotDock::plotRugEnabledChanged);
0221     connect(m_boxPlot, &BoxPlot::rugLengthChanged, this, &BoxPlotDock::plotRugLengthChanged);
0222     connect(m_boxPlot, &BoxPlot::rugWidthChanged, this, &BoxPlotDock::plotRugWidthChanged);
0223     connect(m_boxPlot, &BoxPlot::rugOffsetChanged, this, &BoxPlotDock::plotRugOffsetChanged);
0224 }
0225 
0226 void BoxPlotDock::setModel() {
0227     auto* model = aspectModel();
0228     model->enablePlottableColumnsOnly(true);
0229     model->enableShowPlotDesignation(true);
0230 
0231     QList<AspectType> list{AspectType::Column};
0232     model->setSelectableAspects(list);
0233 }
0234 
0235 /*
0236  * updates the locale in the widgets. called when the application settins are changed.
0237  */
0238 void BoxPlotDock::updateLocale() {
0239     ui.leWhiskersRangeParameter->setLocale(QLocale());
0240     borderLineWidget->updateLocale();
0241     medianLineWidget->updateLocale();
0242     whiskersLineWidget->updateLocale();
0243     whiskersCapLineWidget->updateLocale();
0244 }
0245 
0246 void BoxPlotDock::loadDataColumns() {
0247     // add the combobox for the first column, is always present
0248     if (m_dataComboBoxes.count() == 0)
0249         addDataColumn();
0250 
0251     int count = m_boxPlot->dataColumns().count();
0252     ui.cbNumber->clear();
0253 
0254     if (count != 0) {
0255         // box plot has already data columns, make sure we have the proper number of comboboxes
0256         int diff = count - m_dataComboBoxes.count();
0257         if (diff > 0) {
0258             for (int i = 0; i < diff; ++i)
0259                 addDataColumn();
0260         } else if (diff < 0) {
0261             for (int i = diff; i != 0; ++i)
0262                 removeDataColumn();
0263         }
0264 
0265         // show the columns in the comboboxes
0266         auto* model = aspectModel();
0267         for (int i = 0; i < count; ++i) {
0268             m_dataComboBoxes.at(i)->setModel(model); // the model might have changed in-between, reset the current model
0269             m_dataComboBoxes.at(i)->setAspect(m_boxPlot->dataColumns().at(i));
0270         }
0271 
0272         // show columns names in the combobox for the selection of the box to be modified
0273         for (int i = 0; i < count; ++i)
0274             if (m_boxPlot->dataColumns().at(i))
0275                 ui.cbNumber->addItem(m_boxPlot->dataColumns().at(i)->name());
0276     } else {
0277         // no data columns set in the box plot yet, we show the first combo box only
0278         m_dataComboBoxes.first()->setAspect(nullptr);
0279         for (int i = 1; i < m_dataComboBoxes.count(); ++i)
0280             removeDataColumn();
0281     }
0282 
0283     // disable data column widgets if we're modifying more than one box plot at the same time
0284     bool enabled = (m_boxPlots.count() == 1);
0285     m_buttonNew->setVisible(enabled);
0286     for (auto* cb : m_dataComboBoxes)
0287         cb->setEnabled(enabled);
0288     for (auto* b : m_removeButtons)
0289         b->setVisible(enabled);
0290 
0291     // select the first column after all of them were added to the combobox
0292     ui.cbNumber->setCurrentIndex(0);
0293 }
0294 
0295 void BoxPlotDock::setDataColumns() const {
0296     QVector<const AbstractColumn*> columns;
0297 
0298     for (auto* cb : m_dataComboBoxes) {
0299         auto* aspect = cb->currentAspect();
0300         if (aspect && aspect->type() == AspectType::Column)
0301             columns << static_cast<AbstractColumn*>(aspect);
0302     }
0303 
0304     m_boxPlot->setDataColumns(columns);
0305 }
0306 
0307 //**********************************************************
0308 //*** SLOTs for changes triggered in BoxPlotDock *****
0309 //**********************************************************
0310 void BoxPlotDock::addDataColumn() {
0311     auto* cb = new TreeViewComboBox(this);
0312 
0313     static const QList<AspectType> list{AspectType::Folder,
0314                                         AspectType::Workbook,
0315                                         AspectType::Datapicker,
0316                                         AspectType::DatapickerCurve,
0317                                         AspectType::Spreadsheet,
0318                                         AspectType::LiveDataSource,
0319                                         AspectType::Column,
0320                                         AspectType::Worksheet,
0321                                         AspectType::CartesianPlot,
0322                                         AspectType::XYFitCurve,
0323                                         AspectType::XYSmoothCurve,
0324                                         AspectType::CantorWorksheet};
0325     cb->setTopLevelClasses(list);
0326     cb->setModel(aspectModel());
0327     connect(cb, &TreeViewComboBox::currentModelIndexChanged, this, &BoxPlotDock::dataColumnChanged);
0328 
0329     int index = m_dataComboBoxes.size();
0330 
0331     if (index == 0) {
0332         QSizePolicy sizePolicy1(QSizePolicy::Expanding, QSizePolicy::Preferred);
0333         sizePolicy1.setHorizontalStretch(0);
0334         sizePolicy1.setVerticalStretch(0);
0335         sizePolicy1.setHeightForWidth(cb->sizePolicy().hasHeightForWidth());
0336         cb->setSizePolicy(sizePolicy1);
0337     } else {
0338         auto* button = new QPushButton();
0339         button->setIcon(QIcon::fromTheme(QStringLiteral("list-remove")));
0340         connect(button, &QPushButton::clicked, this, &BoxPlotDock::removeDataColumn);
0341         m_gridLayout->addWidget(button, index, 1, 1, 1);
0342         m_removeButtons << button;
0343 
0344         ui.lOrdering->setEnabled(true);
0345         ui.cbOrdering->setEnabled(true);
0346     }
0347 
0348     m_gridLayout->addWidget(cb, index, 0, 1, 1);
0349     m_gridLayout->addWidget(m_buttonNew, index + 1, 1, 1, 1);
0350 
0351     m_dataComboBoxes << cb;
0352     ui.lDataColumn->setText(i18n("Columns:"));
0353 }
0354 
0355 void BoxPlotDock::removeDataColumn() {
0356     auto* sender = static_cast<QPushButton*>(QObject::sender());
0357     if (sender) {
0358         // remove button was clicked, determin which one and
0359         // delete it together with the corresponding combobox
0360         for (int i = 0; i < m_removeButtons.count(); ++i) {
0361             if (sender == m_removeButtons.at(i)) {
0362                 delete m_dataComboBoxes.takeAt(i + 1);
0363                 delete m_removeButtons.takeAt(i);
0364             }
0365         }
0366     } else {
0367         // no sender is available, the function is being called directly in loadDataColumns().
0368         // delete the last remove button together with the corresponding combobox
0369         int index = m_removeButtons.count() - 1;
0370         if (index >= 0) {
0371             delete m_dataComboBoxes.takeAt(index + 1);
0372             delete m_removeButtons.takeAt(index);
0373         }
0374     }
0375 
0376     if (!m_removeButtons.isEmpty()) {
0377         ui.lDataColumn->setText(i18n("Columns:"));
0378         ui.lOrdering->setEnabled(true);
0379         ui.cbOrdering->setEnabled(true);
0380     } else {
0381         ui.lDataColumn->setText(i18n("Column:"));
0382         ui.lOrdering->setEnabled(false);
0383         ui.cbOrdering->setEnabled(false);
0384     }
0385 
0386     if (!m_initializing)
0387         setDataColumns();
0388 }
0389 
0390 void BoxPlotDock::dataColumnChanged(const QModelIndex&) {
0391     CONDITIONAL_LOCK_RETURN;
0392 
0393     setDataColumns();
0394 }
0395 
0396 void BoxPlotDock::orderingChanged(int index) {
0397     CONDITIONAL_LOCK_RETURN;
0398 
0399     auto ordering = static_cast<BoxPlot::Ordering>(index);
0400     for (auto* boxPlot : m_boxPlots)
0401         boxPlot->setOrdering(ordering);
0402 }
0403 
0404 void BoxPlotDock::orientationChanged(int index) {
0405     CONDITIONAL_LOCK_RETURN;
0406 
0407     auto orientation = BoxPlot::Orientation(index);
0408     for (auto* boxPlot : m_boxPlots)
0409         boxPlot->setOrientation(orientation);
0410 }
0411 
0412 void BoxPlotDock::variableWidthChanged(bool state) {
0413     CONDITIONAL_LOCK_RETURN;
0414 
0415     for (auto* boxPlot : m_boxPlots)
0416         boxPlot->setVariableWidth(state);
0417 }
0418 
0419 void BoxPlotDock::notchesEnabledChanged(bool state) {
0420     CONDITIONAL_LOCK_RETURN;
0421 
0422     for (auto* boxPlot : m_boxPlots)
0423         boxPlot->setNotchesEnabled(state);
0424 }
0425 
0426 //"Box"-tab
0427 /*!
0428  * called when the current box number was changed, shows the box properties for the selected box.
0429  */
0430 void BoxPlotDock::currentBoxChanged(int index) {
0431     if (index == -1)
0432         return;
0433 
0434     CONDITIONAL_LOCK_RETURN;
0435 
0436     QList<Background*> backgrounds;
0437     QList<Line*> borderLines;
0438     QList<Line*> medianLines;
0439     for (auto* plot : m_boxPlots) {
0440         auto* background = plot->backgroundAt(index);
0441         if (background)
0442             backgrounds << background;
0443 
0444         auto* line = plot->borderLineAt(index);
0445         if (line)
0446             borderLines << line;
0447 
0448         line = plot->medianLineAt(index);
0449         if (line)
0450             medianLines << line;
0451     }
0452 
0453     backgroundWidget->setBackgrounds(backgrounds);
0454     borderLineWidget->setLines(borderLines);
0455     medianLineWidget->setLines(medianLines);
0456 }
0457 
0458 void BoxPlotDock::widthFactorChanged(int value) {
0459     CONDITIONAL_LOCK_RETURN;
0460 
0461     double factor = (double)value / 100.;
0462     for (auto* boxPlot : m_boxPlots)
0463         boxPlot->setWidthFactor(factor);
0464 }
0465 
0466 // markers
0467 void BoxPlotDock::symbolCategoryChanged() {
0468     QList<Symbol*> symbols;
0469 
0470     for (auto* plot : m_boxPlots) {
0471         if (ui.rbMean->isChecked())
0472             symbols << plot->symbolMean();
0473         else if (ui.rbMedian->isChecked())
0474             symbols << plot->symbolMedian();
0475         else if (ui.rbOutlier->isChecked())
0476             symbols << plot->symbolOutlier();
0477         else if (ui.rbFarOut->isChecked())
0478             symbols << plot->symbolFarOut();
0479         else if (ui.rbJitter->isChecked())
0480             symbols << plot->symbolData();
0481         else if (ui.rbWhiskerEnd->isChecked())
0482             symbols << plot->symbolWhiskerEnd();
0483     }
0484 
0485     symbolWidget->setSymbols(symbols);
0486 }
0487 
0488 void BoxPlotDock::jitteringEnabledChanged(bool state) {
0489     CONDITIONAL_LOCK_RETURN;
0490 
0491     for (auto* boxPlot : m_boxPlots)
0492         boxPlot->setJitteringEnabled(state);
0493 }
0494 
0495 // whiskers
0496 void BoxPlotDock::whiskersTypeChanged(int index) {
0497     auto type = BoxPlot::WhiskersType(index);
0498     ui.rbOutlier->setEnabled(type != BoxPlot::WhiskersType::MinMax);
0499     ui.rbFarOut->setEnabled(type == BoxPlot::WhiskersType::IQR);
0500 
0501     // range parameter 'k' only available for IQR(=Tukey), SD and MAD
0502     bool visible = (type == BoxPlot::WhiskersType::IQR) || (type == BoxPlot::WhiskersType::SD) || (type == BoxPlot::WhiskersType::MAD);
0503     ui.lWhiskersRangeParameter->setVisible(visible);
0504     ui.leWhiskersRangeParameter->setVisible(visible);
0505 
0506     CONDITIONAL_LOCK_RETURN;
0507 
0508     for (auto* boxPlot : m_boxPlots)
0509         boxPlot->setWhiskersType(type);
0510 }
0511 
0512 void BoxPlotDock::whiskersRangeParameterChanged(const QString& text) {
0513     CONDITIONAL_LOCK_RETURN;
0514 
0515     bool ok;
0516     double value{QLocale().toDouble(text, &ok)};
0517     if (!ok)
0518         return;
0519 
0520     for (auto* boxPlot : m_boxPlots)
0521         boxPlot->setWhiskersRangeParameter(value);
0522 }
0523 
0524 // whiskers cap
0525 void BoxPlotDock::whiskersCapSizeChanged(double value) const {
0526     CONDITIONAL_RETURN_NO_LOCK;
0527 
0528     float size = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point);
0529     for (auto* boxPlot : m_boxPlots)
0530         boxPlot->setWhiskersCapSize(size);
0531 }
0532 
0533 //"Margin Plots"-Tab
0534 void BoxPlotDock::rugEnabledChanged(bool state) {
0535     CONDITIONAL_LOCK_RETURN;
0536 
0537     for (auto* curve : qAsConst(m_boxPlots))
0538         curve->setRugEnabled(state);
0539 }
0540 
0541 void BoxPlotDock::rugLengthChanged(double value) const {
0542     CONDITIONAL_RETURN_NO_LOCK;
0543 
0544     const double length = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point);
0545     for (auto* curve : qAsConst(m_boxPlots))
0546         curve->setRugLength(length);
0547 }
0548 
0549 void BoxPlotDock::rugWidthChanged(double value) const {
0550     CONDITIONAL_RETURN_NO_LOCK;
0551 
0552     const double width = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point);
0553     for (auto* curve : qAsConst(m_boxPlots))
0554         curve->setRugWidth(width);
0555 }
0556 
0557 void BoxPlotDock::rugOffsetChanged(double value) const {
0558     CONDITIONAL_RETURN_NO_LOCK;
0559 
0560     const double offset = Worksheet::convertToSceneUnits(value, Worksheet::Unit::Point);
0561     for (auto* curve : qAsConst(m_boxPlots))
0562         curve->setRugOffset(offset);
0563 }
0564 
0565 //*************************************************************
0566 //******* SLOTs for changes triggered in BoxPlot ********
0567 //*************************************************************
0568 // general
0569 void BoxPlotDock::plotDescriptionChanged(const AbstractAspect* aspect) {
0570     if (m_boxPlot != aspect)
0571         return;
0572 
0573     CONDITIONAL_LOCK_RETURN;
0574     if (aspect->name() != ui.leName->text())
0575         ui.leName->setText(aspect->name());
0576     else if (aspect->comment() != ui.teComment->text())
0577         ui.teComment->setText(aspect->comment());
0578 }
0579 void BoxPlotDock::plotDataColumnsChanged(const QVector<const AbstractColumn*>&) {
0580     CONDITIONAL_LOCK_RETURN;
0581     loadDataColumns();
0582 }
0583 void BoxPlotDock::plotOrderingChanged(BoxPlot::Ordering ordering) {
0584     CONDITIONAL_LOCK_RETURN;
0585     ui.cbOrdering->setCurrentIndex((int)ordering);
0586 }
0587 void BoxPlotDock::plotOrientationChanged(BoxPlot::Orientation orientation) {
0588     CONDITIONAL_LOCK_RETURN;
0589     ui.cbOrientation->setCurrentIndex((int)orientation);
0590 }
0591 void BoxPlotDock::plotVariableWidthChanged(bool on) {
0592     CONDITIONAL_LOCK_RETURN;
0593     ui.chkVariableWidth->setChecked(on);
0594 }
0595 void BoxPlotDock::plotNotchesEnabledChanged(bool on) {
0596     CONDITIONAL_LOCK_RETURN;
0597     ui.chkNotches->setChecked(on);
0598 }
0599 
0600 // box
0601 void BoxPlotDock::plotWidthFactorChanged(double factor) {
0602     CONDITIONAL_LOCK_RETURN;
0603     ui.sbWidthFactor->setValue(round(factor * 100));
0604 }
0605 
0606 // symbols
0607 void BoxPlotDock::plotJitteringEnabledChanged(bool status) {
0608     CONDITIONAL_LOCK_RETURN;
0609     ui.chkJitteringEnabled->setChecked(status);
0610 }
0611 
0612 // whiskers
0613 void BoxPlotDock::plotWhiskersTypeChanged(BoxPlot::WhiskersType type) {
0614     CONDITIONAL_LOCK_RETURN;
0615     ui.cbWhiskersType->setCurrentIndex((int)type);
0616 }
0617 void BoxPlotDock::plotWhiskersRangeParameterChanged(double value) {
0618     CONDITIONAL_LOCK_RETURN;
0619     ui.leWhiskersRangeParameter->setText(QLocale().toString(value));
0620 }
0621 
0622 // whiskers cap
0623 void BoxPlotDock::plotWhiskersCapSizeChanged(double size) {
0624     CONDITIONAL_LOCK_RETURN;
0625     ui.sbWhiskersCapSize->setValue(Worksheet::convertFromSceneUnits(size, Worksheet::Unit::Point));
0626 }
0627 
0628 //"Margin Plot"-Tab
0629 void BoxPlotDock::plotRugEnabledChanged(bool status) {
0630     CONDITIONAL_LOCK_RETURN;
0631     ui.chkRugEnabled->setChecked(status);
0632 }
0633 void BoxPlotDock::plotRugLengthChanged(double value) {
0634     CONDITIONAL_LOCK_RETURN;
0635     ui.sbRugLength->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Unit::Point));
0636 }
0637 void BoxPlotDock::plotRugWidthChanged(double value) {
0638     CONDITIONAL_LOCK_RETURN;
0639     ui.sbRugWidth->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Unit::Point));
0640 }
0641 void BoxPlotDock::plotRugOffsetChanged(double value) {
0642     CONDITIONAL_LOCK_RETURN;
0643     ui.sbRugOffset->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Unit::Point));
0644 }
0645 
0646 //**********************************************************
0647 //******************** SETTINGS ****************************
0648 //**********************************************************
0649 void BoxPlotDock::load() {
0650     // general
0651     ui.cbOrdering->setCurrentIndex((int)m_boxPlot->ordering());
0652     ui.cbOrientation->setCurrentIndex((int)m_boxPlot->orientation());
0653     ui.chkVariableWidth->setChecked(m_boxPlot->variableWidth());
0654     ui.chkNotches->setChecked(m_boxPlot->notchesEnabled());
0655 
0656     // box
0657     ui.sbWidthFactor->setValue(round(m_boxPlot->widthFactor()) * 100);
0658 
0659     // symbols
0660     symbolCategoryChanged();
0661     ui.chkJitteringEnabled->setChecked(m_boxPlot->jitteringEnabled());
0662 
0663     // whiskers
0664     ui.cbWhiskersType->setCurrentIndex((int)m_boxPlot->whiskersType());
0665     ui.leWhiskersRangeParameter->setText(QLocale().toString(m_boxPlot->whiskersRangeParameter()));
0666 
0667     // whiskers cap
0668     ui.sbWhiskersCapSize->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->whiskersCapSize(), Worksheet::Unit::Point));
0669 
0670     // Margin plots
0671     ui.chkRugEnabled->setChecked(m_boxPlot->rugEnabled());
0672     ui.sbRugWidth->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugWidth(), Worksheet::Unit::Point));
0673     ui.sbRugLength->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugLength(), Worksheet::Unit::Point));
0674     ui.sbRugOffset->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugOffset(), Worksheet::Unit::Point));
0675 }
0676 
0677 void BoxPlotDock::loadConfig(KConfig& config) {
0678     KConfigGroup group = config.group(QStringLiteral("BoxPlot"));
0679 
0680     // general
0681     ui.cbOrdering->setCurrentIndex(group.readEntry(QStringLiteral("Ordering"), (int)m_boxPlot->ordering()));
0682     ui.cbOrientation->setCurrentIndex(group.readEntry(QStringLiteral("Orientation"), (int)m_boxPlot->orientation()));
0683     ui.chkVariableWidth->setChecked(group.readEntry(QStringLiteral("QStringLiteral(VariableWidth"), m_boxPlot->variableWidth()));
0684     ui.chkNotches->setChecked(group.readEntry(QStringLiteral("NotchesEnabled"), m_boxPlot->notchesEnabled()));
0685 
0686     // box
0687     ui.sbWidthFactor->setValue(round(group.readEntry(QStringLiteral("WidthFactor"), m_boxPlot->widthFactor()) * 100));
0688     backgroundWidget->loadConfig(group);
0689     borderLineWidget->loadConfig(group);
0690     medianLineWidget->loadConfig(group);
0691 
0692     // symbols
0693     symbolCategoryChanged();
0694     ui.chkJitteringEnabled->setChecked(group.readEntry(QStringLiteral("JitteringEnabled"), m_boxPlot->jitteringEnabled()));
0695 
0696     // whiskers
0697     ui.cbWhiskersType->setCurrentIndex(group.readEntry(QStringLiteral("WhiskersType"), (int)m_boxPlot->whiskersType()));
0698     ui.leWhiskersRangeParameter->setText(QLocale().toString(m_boxPlot->whiskersRangeParameter()));
0699     whiskersLineWidget->loadConfig(group);
0700 
0701     // whiskers cap
0702     ui.sbWhiskersCapSize->setValue(
0703         Worksheet::convertFromSceneUnits(group.readEntry(QStringLiteral("WhiskersCapSize"), m_boxPlot->whiskersCapSize()), Worksheet::Unit::Point));
0704     whiskersCapLineWidget->loadConfig(group);
0705 
0706     // Margin plots
0707     ui.chkRugEnabled->setChecked(m_boxPlot->rugEnabled());
0708     ui.sbRugWidth->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugWidth(), Worksheet::Unit::Point));
0709     ui.sbRugLength->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugLength(), Worksheet::Unit::Point));
0710     ui.sbRugOffset->setValue(Worksheet::convertFromSceneUnits(m_boxPlot->rugOffset(), Worksheet::Unit::Point));
0711 }
0712 
0713 void BoxPlotDock::loadConfigFromTemplate(KConfig& config) {
0714     auto name = TemplateHandler::templateName(config);
0715     int size = m_boxPlots.size();
0716     if (size > 1)
0717         m_boxPlot->beginMacro(i18n("%1 box plots: template \"%2\" loaded", size, name));
0718     else
0719         m_boxPlot->beginMacro(i18n("%1: template \"%2\" loaded", m_boxPlot->name(), name));
0720 
0721     this->loadConfig(config);
0722 
0723     m_boxPlot->endMacro();
0724 }
0725 
0726 void BoxPlotDock::saveConfigAsTemplate(KConfig& config) {
0727     KConfigGroup group = config.group(QStringLiteral("BoxPlot"));
0728 
0729     // general
0730     group.writeEntry(QStringLiteral("Ordering"), ui.cbOrdering->currentIndex());
0731     group.writeEntry(QStringLiteral("Orientation"), ui.cbOrientation->currentIndex());
0732     group.writeEntry(QStringLiteral("VariableWidth"), ui.chkVariableWidth->isChecked());
0733     group.writeEntry(QStringLiteral("NotchesEnabled"), ui.chkNotches->isChecked());
0734 
0735     // box
0736     group.writeEntry(QStringLiteral("WidthFactor"), ui.sbWidthFactor->value() / 100.0);
0737     backgroundWidget->saveConfig(group);
0738     borderLineWidget->saveConfig(group);
0739     medianLineWidget->saveConfig(group);
0740 
0741     // symbols
0742     // TODO: save symbol properties for outliers, etc.?
0743     group.writeEntry(QStringLiteral("JitteringEnabled"), ui.chkJitteringEnabled->isChecked());
0744 
0745     // whiskers
0746     group.writeEntry(QStringLiteral("WhiskersType"), ui.cbWhiskersType->currentIndex());
0747     group.writeEntry(QStringLiteral("WhiskersRangeParameter"), QLocale().toDouble(ui.leWhiskersRangeParameter->text()));
0748     whiskersLineWidget->saveConfig(group);
0749 
0750     // whiskers cap
0751     group.writeEntry(QStringLiteral("WhiskersCapSize"), Worksheet::convertToSceneUnits(ui.sbWhiskersCapSize->value(), Worksheet::Unit::Point));
0752     whiskersCapLineWidget->saveConfig(group);
0753 
0754     config.sync();
0755 }