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 }