File indexing completed on 2024-06-02 03:49:27
0001 /* 0002 File : XYFitCurveDock.cpp 0003 Project : LabPlot 0004 Description : widget for editing properties of fit curves 0005 -------------------------------------------------------------------- 0006 SPDX-FileCopyrightText: 2014-2021 Alexander Semke <alexander.semke@web.de> 0007 SPDX-FileCopyrightText: 2016-2022 Stefan Gerlach <stefan.gerlach@uni.kn> 0008 0009 SPDX-License-Identifier: GPL-2.0-or-later 0010 */ 0011 0012 #include "XYFitCurveDock.h" 0013 #include "backend/core/Project.h" 0014 #include "backend/gsl/ExpressionParser.h" 0015 #include "backend/worksheet/plots/cartesian/CartesianPlot.h" 0016 #include "backend/worksheet/plots/cartesian/Histogram.h" 0017 #include "commonfrontend/widgets/TreeViewComboBox.h" 0018 #include "kdefrontend/GuiTools.h" 0019 #include "kdefrontend/widgets/ConstantsWidget.h" 0020 #include "kdefrontend/widgets/FitOptionsWidget.h" 0021 #include "kdefrontend/widgets/FitParametersWidget.h" 0022 #include "kdefrontend/widgets/FunctionsWidget.h" 0023 0024 #include "backend/nsl/nsl_sf_stats.h" 0025 0026 #include <KMessageWidget> 0027 0028 #include <QClipboard> 0029 #include <QMenu> 0030 #include <QStandardItemModel> 0031 #include <QStandardPaths> 0032 #include <QWidgetAction> 0033 0034 #ifdef HAVE_POPPLER 0035 #include <poppler-qt5.h> 0036 #endif 0037 0038 /*! 0039 \class XYFitCurveDock 0040 \brief Provides a widget for editing the properties of the XYFitCurves 0041 (2D-curves defined by a fit model) currently selected in 0042 the project explorer. 0043 0044 If more than one curves are set, the properties of the first column are shown. 0045 The changes of the properties are applied to all curves. 0046 The exclusions are the name, the comment and the datasets (columns) of 0047 the curves - these properties can only be changed if there is only one single curve. 0048 0049 \ingroup kdefrontend 0050 */ 0051 0052 XYFitCurveDock::XYFitCurveDock(QWidget* parent) 0053 : XYCurveDock(parent) { 0054 } 0055 0056 XYFitCurveDock::~XYFitCurveDock() { 0057 delete m_dataSourceModel; 0058 } 0059 0060 /*! 0061 * set up "General" tab 0062 */ 0063 void XYFitCurveDock::setupGeneral() { 0064 auto* generalTab = new QWidget(ui.tabGeneral); 0065 uiGeneralTab.setupUi(generalTab); 0066 setPlotRangeCombobox(uiGeneralTab.cbPlotRanges); 0067 setBaseWidgets(uiGeneralTab.leName, uiGeneralTab.teComment); 0068 setVisibilityWidgets(uiGeneralTab.chkVisible, uiGeneralTab.chkLegendVisible); 0069 0070 auto* gridLayout = static_cast<QGridLayout*>(generalTab->layout()); 0071 gridLayout->setContentsMargins(2, 2, 2, 2); 0072 gridLayout->setHorizontalSpacing(2); 0073 gridLayout->setVerticalSpacing(2); 0074 0075 uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); 0076 uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); 0077 uiGeneralTab.cbDataSourceType->addItem(i18n("Histogram")); 0078 0079 cbDataSourceCurve = new TreeViewComboBox(generalTab); 0080 gridLayout->addWidget(cbDataSourceCurve, 5, 3, 1, 4); 0081 0082 cbXDataColumn = new TreeViewComboBox(generalTab); 0083 gridLayout->addWidget(cbXDataColumn, 6, 3, 1, 4); 0084 0085 cbXErrorColumn = new TreeViewComboBox(generalTab); 0086 cbXErrorColumn->setEnabled(false); 0087 uiGeneralTab.hlXError->addWidget(cbXErrorColumn); 0088 0089 cbYDataColumn = new TreeViewComboBox(generalTab); 0090 gridLayout->addWidget(cbYDataColumn, 7, 3, 1, 4); 0091 0092 cbYErrorColumn = new TreeViewComboBox(generalTab); 0093 cbYErrorColumn->setEnabled(false); 0094 uiGeneralTab.hlYWeight->addWidget(cbYErrorColumn); 0095 0096 // X/Y-Weight 0097 for (int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) { 0098 uiGeneralTab.cbXWeight->addItem(QLatin1String(nsl_fit_weight_type_name[i])); 0099 uiGeneralTab.cbYWeight->addItem(QLatin1String(nsl_fit_weight_type_name[i])); 0100 } 0101 uiGeneralTab.cbXWeight->setCurrentIndex(nsl_fit_weight_no); 0102 uiGeneralTab.cbYWeight->setCurrentIndex(nsl_fit_weight_no); 0103 0104 for (int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++) 0105 uiGeneralTab.cbCategory->addItem(QLatin1String(nsl_fit_model_category_name[i])); 0106 0107 uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2); 0108 0109 fitParametersWidget = new FitParametersWidget(uiGeneralTab.frameParameters); 0110 auto* l = new QVBoxLayout(); 0111 l->setContentsMargins(0, 0, 0, 0); 0112 l->addWidget(fitParametersWidget); 0113 uiGeneralTab.frameParameters->setLayout(l); 0114 0115 uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme(QStringLiteral("labplot-format-text-symbol"))); 0116 uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme(QStringLiteral("preferences-desktop-font"))); 0117 uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme(QStringLiteral("run-build"))); 0118 0119 for (int i = 0; i < NSL_FIT_ALGORITHM_COUNT; i++) 0120 uiGeneralTab.cbAlgorithm->addItem(QLatin1String(nsl_fit_algorithm_name[i])); 0121 0122 if (m_fitData.modelCategory != nsl_fit_model_distribution) { // disable ML 0123 const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbAlgorithm->model()); 0124 auto* item = model->item(nsl_fit_algorithm_ml); 0125 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0126 } 0127 0128 // TODO: setting checked background color to unchecked color 0129 // p = uiGeneralTab.lData->palette(); 0130 // QWidget::palette().color(QWidget::backgroundRole()) 0131 // not working with 'transparent' 0132 // p.setColor(QPalette::Base, Qt::transparent); 0133 // uiGeneralTab.lData->setPalette(p); 0134 // see https://forum.qt.io/topic/41325/solved-background-of-checked-qpushbutton-with-stylesheet/2 0135 // Styles not usable (here: text color not theme dependent). see https://forum.qt.io/topic/60546/qpushbutton-default-windows-style-sheet/9 0136 // uiGeneralTab.lData->setStyleSheet(QStringLiteral("QToolButton:checked{background-color: transparent;border: 3px transparent;padding: 3px;}")); 0137 0138 // uiGeneralTab.lData->setAutoFillBackground(true); 0139 0140 uiGeneralTab.twLog->setEditTriggers(QAbstractItemView::NoEditTriggers); 0141 uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers); 0142 uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers); 0143 0144 // don't allow word wrapping in the log-table for the multi-line iterations string 0145 uiGeneralTab.twLog->setWordWrap(false); 0146 0147 // header labels 0148 QStringList headerLabels; 0149 headerLabels << QString() << i18n("Value") << i18n("Uncertainty") << i18n("Uncertainty, %") << i18n("t statistic") << QLatin1String("P > |t|") 0150 << i18n("Lower") << i18n("Upper"); 0151 uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels); 0152 0153 // show all options per default 0154 showDataOptions(true); 0155 showFitOptions(true); 0156 showWeightsOptions(true); 0157 showParameters(true); 0158 showResults(true); 0159 0160 // CTRL+C copies only the last cell in the selection, we want to copy the whole selection. 0161 // install event filters to handle CTRL+C key events. 0162 uiGeneralTab.twParameters->installEventFilter(this); 0163 uiGeneralTab.twGoodness->installEventFilter(this); 0164 uiGeneralTab.twLog->installEventFilter(this); 0165 0166 // context menus 0167 uiGeneralTab.twParameters->setContextMenuPolicy(Qt::CustomContextMenu); 0168 uiGeneralTab.twGoodness->setContextMenuPolicy(Qt::CustomContextMenu); 0169 uiGeneralTab.twLog->setContextMenuPolicy(Qt::CustomContextMenu); 0170 connect(uiGeneralTab.twParameters, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultParametersContextMenuRequest); 0171 connect(uiGeneralTab.twGoodness, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultGoodnessContextMenuRequest); 0172 connect(uiGeneralTab.twLog, &QTableWidget::customContextMenuRequested, this, &XYFitCurveDock::resultLogContextMenuRequest); 0173 0174 uiGeneralTab.twLog->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); 0175 uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); 0176 // append symbols 0177 uiGeneralTab.twGoodness->item(0, 0)->setText(uiGeneralTab.twGoodness->item(0, 0)->text() + UTF8_QSTRING(" (χ²)")); 0178 uiGeneralTab.twGoodness->item(1, 0)->setText(uiGeneralTab.twGoodness->item(1, 0)->text() + UTF8_QSTRING(" (χ²/dof)")); 0179 uiGeneralTab.twGoodness->item(3, 0)->setText(uiGeneralTab.twGoodness->item(3, 0)->text() + UTF8_QSTRING(" (R²)")); 0180 uiGeneralTab.twGoodness->item(4, 0)->setText(uiGeneralTab.twGoodness->item(4, 0)->text() + UTF8_QSTRING(" (R̄²)")); 0181 uiGeneralTab.twGoodness->item(5, 0)->setText(UTF8_QSTRING("χ²-") + i18n("test") + UTF8_QSTRING(" (P > χ²)")); 0182 0183 auto* layout = new QHBoxLayout(ui.tabGeneral); 0184 layout->setContentsMargins(0, 0, 0, 0); 0185 layout->addWidget(generalTab); 0186 0187 // Slots 0188 connect(uiGeneralTab.cbDataSourceType, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::dataSourceTypeChanged); 0189 connect(uiGeneralTab.lWeights, &QPushButton::clicked, this, &XYFitCurveDock::showWeightsOptions); 0190 connect(uiGeneralTab.cbXWeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::xWeightChanged); 0191 connect(uiGeneralTab.cbYWeight, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::yWeightChanged); 0192 connect(uiGeneralTab.cbCategory, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::categoryChanged); 0193 connect(uiGeneralTab.cbModel, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::modelTypeChanged); 0194 connect(uiGeneralTab.sbDegree, QOverload<int>::of(&QSpinBox::valueChanged), this, &XYFitCurveDock::updateModelEquation); 0195 connect(uiGeneralTab.teEquation, &ExpressionTextEdit::expressionChanged, this, &XYFitCurveDock::expressionChanged); 0196 connect(uiGeneralTab.tbConstants, &QToolButton::clicked, this, &XYFitCurveDock::showConstants); 0197 connect(uiGeneralTab.tbFunctions, &QToolButton::clicked, this, &XYFitCurveDock::showFunctions); 0198 connect(uiGeneralTab.pbOptions, &QPushButton::clicked, this, &XYFitCurveDock::showOptions); 0199 connect(uiGeneralTab.cbAlgorithm, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::algorithmChanged); 0200 connect(uiGeneralTab.pbRecalculate, &QPushButton::clicked, this, &XYFitCurveDock::recalculateClicked); 0201 connect(uiGeneralTab.lData, &QPushButton::clicked, this, &XYFitCurveDock::showDataOptions); 0202 connect(uiGeneralTab.lFit, &QPushButton::clicked, this, &XYFitCurveDock::showFitOptions); 0203 connect(uiGeneralTab.lParameters, &QPushButton::clicked, this, &XYFitCurveDock::showParameters); 0204 connect(uiGeneralTab.lResults, &QPushButton::clicked, this, &XYFitCurveDock::showResults); 0205 connect(uiGeneralTab.cbPlotRanges, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &XYFitCurveDock::plotRangeChanged); 0206 0207 connect(cbDataSourceCurve, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::dataSourceCurveChanged); 0208 connect(cbXDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xDataColumnChanged); 0209 connect(cbYDataColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yDataColumnChanged); 0210 connect(cbXErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::xErrorColumnChanged); 0211 connect(cbYErrorColumn, &TreeViewComboBox::currentModelIndexChanged, this, &XYFitCurveDock::yErrorColumnChanged); 0212 } 0213 0214 /* 0215 * load curve settings 0216 */ 0217 void XYFitCurveDock::initGeneralTab() { 0218 uiGeneralTab.cbDataSourceType->setCurrentIndex(static_cast<int>(m_fitCurve->dataSourceType())); 0219 this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); 0220 0221 switch (m_fitCurve->dataSourceType()) { 0222 case XYAnalysisCurve::DataSourceType::Curve: 0223 cbDataSourceCurve->setAspect(m_fitCurve->dataSourceCurve()); 0224 break; 0225 case XYAnalysisCurve::DataSourceType::Histogram: 0226 cbDataSourceCurve->setAspect(m_fitCurve->dataSourceHistogram()); 0227 break; 0228 case XYAnalysisCurve::DataSourceType::Spreadsheet: 0229 cbDataSourceCurve->setAspect(nullptr); 0230 } 0231 0232 cbXDataColumn->setColumn(m_fitCurve->xDataColumn(), m_fitCurve->xDataColumnPath()); 0233 cbYDataColumn->setColumn(m_fitCurve->yDataColumn(), m_fitCurve->yDataColumnPath()); 0234 cbXErrorColumn->setColumn(m_fitCurve->xErrorColumn(), m_fitCurve->xErrorColumnPath()); 0235 cbYErrorColumn->setColumn(m_fitCurve->yErrorColumn(), m_fitCurve->yErrorColumnPath()); 0236 0237 int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes 0238 if (m_fitData.modelCategory == nsl_fit_model_custom) 0239 uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1); 0240 else 0241 uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory); 0242 categoryChanged(m_fitData.modelCategory); // fill model types 0243 0244 m_fitData.modelType = tmpModelType; 0245 if (m_fitData.modelCategory != nsl_fit_model_custom) 0246 uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); 0247 0248 uiGeneralTab.cbXWeight->setCurrentIndex(m_fitData.xWeightsType); 0249 uiGeneralTab.cbYWeight->setCurrentIndex(m_fitData.yWeightsType); 0250 uiGeneralTab.sbDegree->setValue(m_fitData.degree); 0251 DEBUG(Q_FUNC_INFO << ", model degree = " << m_fitData.degree); 0252 0253 if (m_fitData.paramStartValues.size() > 0) 0254 DEBUG(Q_FUNC_INFO << ", start value 1 = " << m_fitData.paramStartValues.at(0)); 0255 0256 uiGeneralTab.cbAlgorithm->setCurrentIndex(m_fitData.algorithm); 0257 0258 uiGeneralTab.chkLegendVisible->setChecked(m_curve->legendVisible()); 0259 uiGeneralTab.chkVisible->setChecked(m_curve->isVisible()); 0260 0261 // Slots 0262 connect(m_fitCurve, &XYFitCurve::dataSourceTypeChanged, this, &XYFitCurveDock::curveDataSourceTypeChanged); 0263 connect(m_fitCurve, &XYFitCurve::dataSourceCurveChanged, this, &XYFitCurveDock::curveDataSourceCurveChanged); 0264 connect(m_fitCurve, &XYFitCurve::dataSourceHistogramChanged, this, &XYFitCurveDock::curveDataSourceHistogramChanged); 0265 connect(m_fitCurve, &XYFitCurve::xDataColumnChanged, this, &XYFitCurveDock::curveXDataColumnChanged); 0266 connect(m_fitCurve, &XYFitCurve::yDataColumnChanged, this, &XYFitCurveDock::curveYDataColumnChanged); 0267 connect(m_fitCurve, &XYFitCurve::xErrorColumnChanged, this, &XYFitCurveDock::curveXErrorColumnChanged); 0268 connect(m_fitCurve, &XYFitCurve::yErrorColumnChanged, this, &XYFitCurveDock::curveYErrorColumnChanged); 0269 connect(m_fitCurve, &XYFitCurve::fitDataChanged, this, &XYFitCurveDock::curveFitDataChanged); 0270 connect(m_fitCurve, &XYFitCurve::sourceDataChanged, this, &XYFitCurveDock::enableRecalculate); 0271 0272 connect(fitParametersWidget, &FitParametersWidget::parametersChanged, this, &XYFitCurveDock::parametersChanged); 0273 connect(fitParametersWidget, &FitParametersWidget::parametersValid, this, &XYFitCurveDock::parametersValid); 0274 } 0275 0276 void XYFitCurveDock::setModel() { 0277 QList<const AbstractAspect*> hiddenAspects; 0278 for (auto* curve : m_curvesList) 0279 hiddenAspects << curve; 0280 cbDataSourceCurve->setHiddenAspects(hiddenAspects); 0281 0282 QList<AspectType> list = {AspectType::Folder, 0283 AspectType::Workbook, 0284 AspectType::Spreadsheet, 0285 AspectType::LiveDataSource, 0286 AspectType::CantorWorksheet, 0287 AspectType::Datapicker, 0288 AspectType::Column}; 0289 cbXDataColumn->setTopLevelClasses(list); 0290 cbYDataColumn->setTopLevelClasses(list); 0291 cbXErrorColumn->setTopLevelClasses(list); 0292 cbYErrorColumn->setTopLevelClasses(list); 0293 0294 list = {AspectType::Column}; 0295 auto* model = aspectModel(); 0296 model->setSelectableAspects(list); 0297 0298 cbXDataColumn->setModel(model); 0299 cbYDataColumn->setModel(model); 0300 cbXErrorColumn->setModel(model); 0301 cbYErrorColumn->setModel(model); 0302 0303 XYCurveDock::setModel(); 0304 } 0305 0306 /*! 0307 sets the curves. The properties of the curves in the list \c list can be edited in this widget. 0308 */ 0309 void XYFitCurveDock::setCurves(QList<XYCurve*> list) { 0310 CONDITIONAL_LOCK_RETURN; 0311 m_curvesList = list; 0312 m_curve = list.first(); 0313 setAspects(list); 0314 m_fitCurve = static_cast<XYFitCurve*>(m_curve); 0315 0316 // we need a second model for data source comboboxes which will be dynamically 0317 // updated in the slot depending on the current type (spreadsheet, curve or histogram) 0318 // to allow to select the relevant aspects only 0319 m_dataSourceModel = new AspectTreeModel(m_curve->project()); 0320 0321 this->setModel(); 0322 m_fitData = m_fitCurve->fitData(); 0323 0324 DEBUG(Q_FUNC_INFO << ", model type = " << m_fitData.modelType); 0325 DEBUG(Q_FUNC_INFO << ", model = " << STDSTRING(m_fitData.model)); 0326 DEBUG(Q_FUNC_INFO << ", model degree = " << m_fitData.degree); 0327 DEBUG(Q_FUNC_INFO << ", # params = " << m_fitData.paramNames.size()); 0328 DEBUG(Q_FUNC_INFO << ", # start values = " << m_fitData.paramStartValues.size()); 0329 // for (auto startValue: m_fitData.paramStartValues) 0330 // DEBUG("XYFitCurveDock::setCurves() start value = " << startValue); 0331 0332 fitParametersWidget->setFitData(&m_fitData); 0333 0334 initGeneralTab(); 0335 initTabs(); 0336 setSymbols(list); 0337 0338 if (m_messageWidget && m_messageWidget->isVisible()) 0339 m_messageWidget->close(); 0340 0341 showFitResult(); 0342 enableRecalculate(); 0343 0344 updatePlotRangeList(); 0345 0346 // init parameter list when not available 0347 if (m_fitData.paramStartValues.size() == 0) 0348 updateModelEquation(); 0349 } 0350 0351 bool XYFitCurveDock::eventFilter(QObject* obj, QEvent* event) { 0352 if (event->type() == QEvent::KeyPress && (obj == uiGeneralTab.twParameters || obj == uiGeneralTab.twGoodness || obj == uiGeneralTab.twLog)) { 0353 auto* key_event = static_cast<QKeyEvent*>(event); 0354 if (key_event->matches(QKeySequence::Copy)) { 0355 resultCopy(); 0356 return true; 0357 } 0358 } 0359 return QWidget::eventFilter(obj, event); 0360 } 0361 0362 //************************************************************* 0363 //**** SLOTs for changes triggered in XYFitCurveDock ***** 0364 //************************************************************* 0365 void XYFitCurveDock::dataSourceTypeChanged(int index) { 0366 DEBUG(Q_FUNC_INFO << ", m_initializing = " << m_initializing) 0367 const auto type = (XYAnalysisCurve::DataSourceType)index; 0368 DEBUG(Q_FUNC_INFO << ", source type = " << ENUM_TO_STRING(XYAnalysisCurve, DataSourceType, type)) 0369 if (type == XYAnalysisCurve::DataSourceType::Spreadsheet) { 0370 uiGeneralTab.cbCategory->setEnabled(true); 0371 uiGeneralTab.lDataSourceCurve->hide(); 0372 cbDataSourceCurve->hide(); 0373 uiGeneralTab.lXColumn->show(); 0374 cbXDataColumn->show(); 0375 uiGeneralTab.lYColumn->show(); 0376 cbYDataColumn->show(); 0377 0378 QList<AspectType> list{AspectType::Folder, AspectType::Workbook, AspectType::Spreadsheet, AspectType::Datapicker}; 0379 cbDataSourceCurve->setTopLevelClasses(list); 0380 0381 // when the dock is initialized, this functions is called before setModel(), 0382 // we need this nullptr check 0383 if (m_dataSourceModel) { 0384 list = {AspectType::Column}; 0385 m_dataSourceModel->setSelectableAspects(list); 0386 0387 // TODO: why do we need to reset the model here and below again to get the combobox updated? 0388 cbDataSourceCurve->setModel(m_dataSourceModel); 0389 } 0390 } else { // curve or histogram 0391 uiGeneralTab.lDataSourceCurve->show(); 0392 cbDataSourceCurve->show(); 0393 uiGeneralTab.lXColumn->hide(); 0394 cbXDataColumn->hide(); 0395 uiGeneralTab.lYColumn->hide(); 0396 cbYDataColumn->hide(); 0397 0398 if (type == XYAnalysisCurve::DataSourceType::Curve) { 0399 uiGeneralTab.cbCategory->setEnabled(true); 0400 uiGeneralTab.lDataSourceCurve->setText(i18n("Curve:")); 0401 0402 QList<AspectType> list{AspectType::Folder, 0403 AspectType::Datapicker, 0404 AspectType::Worksheet, 0405 AspectType::CartesianPlot, 0406 AspectType::XYCurve, 0407 AspectType::XYAnalysisCurve, 0408 AspectType::XYEquationCurve}; 0409 cbDataSourceCurve->setTopLevelClasses(list); 0410 0411 if (m_dataSourceModel) { 0412 list = {AspectType::XYCurve, AspectType::XYAnalysisCurve, AspectType::XYEquationCurve}; 0413 m_dataSourceModel->setSelectableAspects(list); 0414 cbDataSourceCurve->setModel(m_dataSourceModel); 0415 cbDataSourceCurve->setAspect(m_fitCurve->dataSourceCurve()); 0416 } 0417 } else { // histogram 0418 uiGeneralTab.cbCategory->setEnabled(false); 0419 uiGeneralTab.cbCategory->setCurrentIndex(3); // select "statistics (distributions); 0420 uiGeneralTab.lDataSourceCurve->setText(i18n("Histogram:")); 0421 0422 QList<AspectType> list{AspectType::Folder, AspectType::Worksheet, AspectType::CartesianPlot, AspectType::Histogram}; 0423 cbDataSourceCurve->setTopLevelClasses(list); 0424 0425 if (m_dataSourceModel) { 0426 list = {AspectType::Histogram}; 0427 m_dataSourceModel->setSelectableAspects(list); 0428 cbDataSourceCurve->setModel(m_dataSourceModel); 0429 cbDataSourceCurve->setAspect(m_fitCurve->dataSourceHistogram()); 0430 } 0431 } 0432 } 0433 0434 CONDITIONAL_LOCK_RETURN; 0435 0436 for (auto* curve : m_curvesList) 0437 static_cast<XYFitCurve*>(curve)->setDataSourceType(type); 0438 } 0439 0440 void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) { 0441 DEBUG(Q_FUNC_INFO << ", m_initializing = " << m_initializing) 0442 CONDITIONAL_RETURN_NO_LOCK; 0443 0444 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0445 0446 const auto type = (XYAnalysisCurve::DataSourceType)uiGeneralTab.cbDataSourceType->currentIndex(); 0447 if (type == XYAnalysisCurve::DataSourceType::Curve) { 0448 auto* dataSourceCurve = static_cast<XYCurve*>(aspect); 0449 for (auto* curve : m_curvesList) 0450 static_cast<XYFitCurve*>(curve)->setDataSourceCurve(dataSourceCurve); 0451 } else { 0452 auto* dataSourceHist = static_cast<Histogram*>(aspect); 0453 for (auto* curve : m_curvesList) 0454 static_cast<XYFitCurve*>(curve)->setDataSourceHistogram(dataSourceHist); 0455 } 0456 } 0457 0458 void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) { 0459 CONDITIONAL_LOCK_RETURN; 0460 0461 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0462 auto* column = dynamic_cast<AbstractColumn*>(aspect); 0463 0464 for (auto* curve : m_curvesList) 0465 static_cast<XYFitCurve*>(curve)->setXDataColumn(column); 0466 0467 // set model dependent start values from new data 0468 DEBUG(Q_FUNC_INFO) 0469 static_cast<XYFitCurve*>(m_curve)->initStartValues(m_fitData, m_curve); 0470 // udpate parameter widget 0471 fitParametersWidget->setFitData(&m_fitData); 0472 enableRecalculate(); // update preview 0473 showFitResult(); // show result of preview 0474 0475 // update model limits depending on number of points 0476 modelTypeChanged(uiGeneralTab.cbModel->currentIndex()); 0477 0478 cbXDataColumn->useCurrentIndexText(true); 0479 cbXDataColumn->setInvalid(false); 0480 } 0481 0482 void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) { 0483 CONDITIONAL_LOCK_RETURN; 0484 0485 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0486 auto* column = dynamic_cast<AbstractColumn*>(aspect); 0487 0488 for (auto* curve : m_curvesList) 0489 static_cast<XYFitCurve*>(curve)->setYDataColumn(column); 0490 0491 // set model dependent start values from new data 0492 DEBUG(Q_FUNC_INFO) 0493 static_cast<XYFitCurve*>(m_curve)->initStartValues(m_fitData, m_curve); 0494 // update parameter widget 0495 fitParametersWidget->setFitData(&m_fitData); 0496 enableRecalculate(); // update preview 0497 showFitResult(); // show result of preview 0498 0499 cbYDataColumn->useCurrentIndexText(true); 0500 cbYDataColumn->setInvalid(false); 0501 } 0502 0503 void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) { 0504 CONDITIONAL_LOCK_RETURN; 0505 0506 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0507 auto* column = dynamic_cast<AbstractColumn*>(aspect); 0508 0509 for (auto* curve : m_curvesList) 0510 static_cast<XYFitCurve*>(curve)->setXErrorColumn(column); 0511 0512 cbXErrorColumn->useCurrentIndexText(true); 0513 cbXErrorColumn->setInvalid(false); 0514 } 0515 0516 void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) { 0517 CONDITIONAL_LOCK_RETURN; 0518 0519 auto* aspect = static_cast<AbstractAspect*>(index.internalPointer()); 0520 auto* column = dynamic_cast<AbstractColumn*>(aspect); 0521 0522 for (auto* curve : m_curvesList) 0523 static_cast<XYFitCurve*>(curve)->setYErrorColumn(column); 0524 0525 cbYErrorColumn->useCurrentIndexText(true); 0526 cbYErrorColumn->setInvalid(false); 0527 } 0528 0529 ///////////////////////// fold/unfold options ////////////////////////////////////////////////// 0530 0531 void XYFitCurveDock::showDataOptions(bool checked) { 0532 if (checked) { 0533 uiGeneralTab.lData->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0534 uiGeneralTab.lDataSourceType->show(); 0535 uiGeneralTab.cbDataSourceType->show(); 0536 // select options for current source type 0537 dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); 0538 } else { 0539 uiGeneralTab.lData->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0540 uiGeneralTab.lDataSourceType->hide(); 0541 uiGeneralTab.cbDataSourceType->hide(); 0542 uiGeneralTab.lXColumn->hide(); 0543 cbXDataColumn->hide(); 0544 uiGeneralTab.lYColumn->hide(); 0545 cbYDataColumn->hide(); 0546 uiGeneralTab.lDataSourceCurve->hide(); 0547 cbDataSourceCurve->hide(); 0548 } 0549 } 0550 0551 void XYFitCurveDock::showWeightsOptions(bool checked) { 0552 if (checked) { 0553 uiGeneralTab.lWeights->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0554 uiGeneralTab.lXWeight->show(); 0555 uiGeneralTab.cbXWeight->show(); 0556 uiGeneralTab.lXErrorCol->show(); 0557 cbXErrorColumn->show(); 0558 uiGeneralTab.lYWeight->show(); 0559 uiGeneralTab.cbYWeight->show(); 0560 uiGeneralTab.lYErrorCol->show(); 0561 cbYErrorColumn->show(); 0562 } else { 0563 uiGeneralTab.lWeights->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0564 uiGeneralTab.lXWeight->hide(); 0565 uiGeneralTab.cbXWeight->hide(); 0566 uiGeneralTab.lXErrorCol->hide(); 0567 cbXErrorColumn->hide(); 0568 uiGeneralTab.lYWeight->hide(); 0569 uiGeneralTab.cbYWeight->hide(); 0570 uiGeneralTab.lYErrorCol->hide(); 0571 cbYErrorColumn->hide(); 0572 } 0573 } 0574 0575 void XYFitCurveDock::showFitOptions(bool checked) { 0576 if (checked) { 0577 uiGeneralTab.lFit->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0578 uiGeneralTab.lCategory->show(); 0579 uiGeneralTab.cbCategory->show(); 0580 uiGeneralTab.lModel->show(); 0581 uiGeneralTab.cbModel->show(); 0582 uiGeneralTab.lEquation->show(); 0583 0584 CONDITIONAL_LOCK_RETURN; // do not change start parameter 0585 modelTypeChanged(uiGeneralTab.cbModel->currentIndex()); 0586 } else { 0587 uiGeneralTab.lFit->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0588 uiGeneralTab.lCategory->hide(); 0589 uiGeneralTab.cbCategory->hide(); 0590 uiGeneralTab.lModel->hide(); 0591 uiGeneralTab.cbModel->hide(); 0592 uiGeneralTab.lDegree->hide(); 0593 uiGeneralTab.sbDegree->hide(); 0594 uiGeneralTab.lEquation->hide(); 0595 uiGeneralTab.lFuncPic->hide(); 0596 uiGeneralTab.teEquation->hide(); 0597 uiGeneralTab.tbFunctions->hide(); 0598 uiGeneralTab.tbConstants->hide(); 0599 } 0600 } 0601 0602 void XYFitCurveDock::showParameters(bool checked) { 0603 if (checked) { 0604 uiGeneralTab.lParameters->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0605 uiGeneralTab.frameParameters->show(); 0606 } else { 0607 uiGeneralTab.lParameters->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0608 uiGeneralTab.frameParameters->hide(); 0609 } 0610 } 0611 0612 void XYFitCurveDock::showResults(bool checked) { 0613 if (checked) { 0614 uiGeneralTab.lResults->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); 0615 uiGeneralTab.twResults->show(); 0616 } else { 0617 uiGeneralTab.lResults->setIcon(QIcon::fromTheme(QStringLiteral("arrow-right"))); 0618 uiGeneralTab.twResults->hide(); 0619 } 0620 } 0621 0622 /////////////////////////////////////////////////////////////////////////// 0623 0624 void XYFitCurveDock::xWeightChanged(int index) { 0625 DEBUG(Q_FUNC_INFO << ", weight = " << nsl_fit_weight_type_name[index]); 0626 0627 m_fitData.xWeightsType = (nsl_fit_weight_type)index; 0628 0629 // enable/disable weight column 0630 switch ((nsl_fit_weight_type)index) { 0631 case nsl_fit_weight_no: 0632 case nsl_fit_weight_statistical: 0633 case nsl_fit_weight_statistical_fit: 0634 case nsl_fit_weight_relative: 0635 case nsl_fit_weight_relative_fit: 0636 cbXErrorColumn->setEnabled(false); 0637 uiGeneralTab.lXErrorCol->setEnabled(false); 0638 break; 0639 case nsl_fit_weight_instrumental: 0640 case nsl_fit_weight_direct: 0641 case nsl_fit_weight_inverse: 0642 cbXErrorColumn->setEnabled(true); 0643 uiGeneralTab.lXErrorCol->setEnabled(true); 0644 break; 0645 } 0646 enableRecalculate(); 0647 } 0648 0649 void XYFitCurveDock::yWeightChanged(int index) { 0650 DEBUG(Q_FUNC_INFO << ", weight = " << nsl_fit_weight_type_name[index]); 0651 0652 m_fitData.yWeightsType = (nsl_fit_weight_type)index; 0653 0654 // enable/disable weight column 0655 switch ((nsl_fit_weight_type)index) { 0656 case nsl_fit_weight_no: 0657 case nsl_fit_weight_statistical: 0658 case nsl_fit_weight_statistical_fit: 0659 case nsl_fit_weight_relative: 0660 case nsl_fit_weight_relative_fit: 0661 cbYErrorColumn->setEnabled(false); 0662 uiGeneralTab.lYErrorCol->setEnabled(false); 0663 break; 0664 case nsl_fit_weight_instrumental: 0665 case nsl_fit_weight_direct: 0666 case nsl_fit_weight_inverse: 0667 cbYErrorColumn->setEnabled(true); 0668 uiGeneralTab.lYErrorCol->setEnabled(true); 0669 break; 0670 } 0671 enableRecalculate(); 0672 } 0673 0674 /*! 0675 * called when the fit model category (basic functions, peak functions etc.) was changed. 0676 * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged() 0677 * to update the model type dependent widgets in the general-tab. 0678 */ 0679 void XYFitCurveDock::categoryChanged(int index) { 0680 if (index == nsl_fit_model_custom) { 0681 DEBUG(Q_FUNC_INFO << ", category = \"nsl_fit_model_custom\""); 0682 } else { 0683 DEBUG(Q_FUNC_INFO << ", category = \"" << nsl_fit_model_category_name[index] << "\""); 0684 } 0685 0686 bool hasChanged = true; 0687 // nothing has changed when ... 0688 if (m_fitData.modelCategory == (nsl_fit_model_category)index 0689 || (m_fitData.modelCategory == nsl_fit_model_custom && index == uiGeneralTab.cbCategory->count() - 1)) 0690 hasChanged = false; 0691 DEBUG("HAS CHANGED: " << hasChanged) 0692 0693 if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1) 0694 m_fitData.modelCategory = nsl_fit_model_custom; 0695 else 0696 m_fitData.modelCategory = (nsl_fit_model_category)index; 0697 uiGeneralTab.cbModel->clear(); 0698 uiGeneralTab.cbModel->show(); 0699 uiGeneralTab.lModel->show(); 0700 0701 // enable algorithm selection only for distributions 0702 if (m_fitData.modelCategory == nsl_fit_model_distribution) { 0703 uiGeneralTab.lAlgorithm->show(); 0704 uiGeneralTab.cbAlgorithm->show(); 0705 } else { 0706 uiGeneralTab.lAlgorithm->hide(); 0707 uiGeneralTab.cbAlgorithm->hide(); 0708 } 0709 0710 switch (m_fitData.modelCategory) { 0711 case nsl_fit_model_basic: 0712 for (int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++) 0713 uiGeneralTab.cbModel->addItem(QLatin1String(nsl_fit_model_basic_name[i])); 0714 break; 0715 case nsl_fit_model_peak: { 0716 for (int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++) 0717 uiGeneralTab.cbModel->addItem(QLatin1String(nsl_fit_model_peak_name[i])); 0718 #if defined(_MSC_VER) 0719 // disable voigt model 0720 const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbModel->model()); 0721 auto* item = model->item(nsl_fit_model_voigt); 0722 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0723 #endif 0724 break; 0725 } 0726 case nsl_fit_model_growth: 0727 for (int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++) 0728 uiGeneralTab.cbModel->addItem(QLatin1String(nsl_fit_model_growth_name[i])); 0729 break; 0730 case nsl_fit_model_distribution: { 0731 for (int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) 0732 uiGeneralTab.cbModel->addItem(QLatin1String(nsl_sf_stats_distribution_name[i])); 0733 0734 // not-used items are disabled here 0735 const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbModel->model()); 0736 0737 for (int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) { 0738 if (m_fitData.algorithm == nsl_fit_algorithm_ml) { 0739 if (!nsl_sf_stats_distribution_supports_ML((nsl_sf_stats_distribution)i)) { 0740 auto* item = model->item(i); 0741 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0742 } 0743 } else { // LM 0744 // unused distributions 0745 if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) { 0746 auto* item = model->item(i); 0747 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0748 } 0749 } 0750 } 0751 break; 0752 } 0753 case nsl_fit_model_custom: 0754 uiGeneralTab.cbModel->addItem(i18n("Custom")); 0755 uiGeneralTab.cbModel->hide(); 0756 uiGeneralTab.lModel->hide(); 0757 } 0758 0759 if (hasChanged) { 0760 DEBUG("HAS CHANGED! Resetting MODEL") 0761 // show the fit-model for the currently selected default (first) fit-model 0762 uiGeneralTab.cbModel->setCurrentIndex(0); 0763 uiGeneralTab.sbDegree->setValue(1); 0764 // when model type does not change, call it here 0765 updateModelEquation(); 0766 } 0767 0768 // update algorithm list 0769 const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbAlgorithm->model()); 0770 auto* item = model->item(nsl_fit_algorithm_ml); 0771 // enable ML item only for distributions 0772 if (m_fitData.modelCategory == nsl_fit_model_distribution) { 0773 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0774 } else { 0775 uiGeneralTab.cbAlgorithm->setCurrentIndex(nsl_fit_algorithm_lm); 0776 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0777 } 0778 0779 enableRecalculate(); 0780 } 0781 0782 /*! 0783 * called when the fit model type (depends on category) was changed. 0784 * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap. 0785 */ 0786 void XYFitCurveDock::modelTypeChanged(int index) { 0787 DEBUG(Q_FUNC_INFO << ", type = " << (unsigned int)index << ", initializing = " << m_initializing << ", current type = " << m_fitData.modelType); 0788 // leave if no selection 0789 if (index == -1) 0790 return; 0791 0792 bool custom = false; 0793 if (m_fitData.modelCategory == nsl_fit_model_custom) 0794 custom = true; 0795 uiGeneralTab.teEquation->setReadOnly(!custom); 0796 uiGeneralTab.lModel->setVisible(!custom); 0797 uiGeneralTab.cbModel->setVisible(!custom); 0798 uiGeneralTab.tbFunctions->setVisible(custom); 0799 uiGeneralTab.tbConstants->setVisible(custom); 0800 0801 // default settings 0802 uiGeneralTab.lDegree->setText(i18n("Degree:")); 0803 if (m_fitData.modelType != index) 0804 uiGeneralTab.sbDegree->setValue(1); 0805 0806 const AbstractColumn* xColumn = nullptr; 0807 if (m_fitCurve->dataSourceType() == XYAnalysisCurve::DataSourceType::Spreadsheet) { 0808 DEBUG(" data source: Spreadsheet") 0809 // auto* aspect = static_cast<AbstractAspect*>(cbXDataColumn->currentModelIndex().internalPointer()); 0810 // xColumn = dynamic_cast<AbstractColumn*>(aspect); 0811 xColumn = m_fitCurve->xDataColumn(); 0812 } else { 0813 DEBUG(" data source: Curve or Histogram") 0814 if (m_fitCurve->dataSourceCurve()) 0815 xColumn = m_fitCurve->dataSourceCurve()->xColumn(); 0816 } 0817 // with no xColumn: show all models (assume 100 data points) 0818 const int availableRowCount = xColumn ? xColumn->availableRowCount(100) : 100; 0819 DEBUG(" available row count = " << availableRowCount) 0820 0821 bool disableFit = false; 0822 switch (m_fitData.modelCategory) { 0823 case nsl_fit_model_basic: 0824 switch (index) { 0825 case nsl_fit_model_polynomial: 0826 uiGeneralTab.lDegree->setVisible(true); 0827 uiGeneralTab.sbDegree->setVisible(true); 0828 uiGeneralTab.sbDegree->setMaximum(std::min(availableRowCount - 1, 10)); 0829 break; 0830 case nsl_fit_model_fourier: 0831 if (availableRowCount < 4) { // too few data points 0832 uiGeneralTab.lDegree->setVisible(false); 0833 uiGeneralTab.sbDegree->setVisible(false); 0834 disableFit = true; 0835 } else { 0836 uiGeneralTab.lDegree->setVisible(true); 0837 uiGeneralTab.sbDegree->setVisible(true); 0838 uiGeneralTab.sbDegree->setMaximum(std::min(availableRowCount / 2 - 1, 10)); 0839 } 0840 break; 0841 case nsl_fit_model_power: 0842 uiGeneralTab.lDegree->setVisible(true); 0843 uiGeneralTab.sbDegree->setVisible(true); 0844 uiGeneralTab.sbDegree->setMaximum(2); 0845 // TODO: limit degree depending on availableRowCount 0846 break; 0847 case nsl_fit_model_exponential: 0848 uiGeneralTab.lDegree->setVisible(true); 0849 uiGeneralTab.sbDegree->setVisible(true); 0850 uiGeneralTab.sbDegree->setMaximum(10); 0851 // TODO: limit degree depending on availableRowCount 0852 break; 0853 default: 0854 uiGeneralTab.lDegree->setVisible(false); 0855 uiGeneralTab.sbDegree->setVisible(false); 0856 } 0857 break; 0858 case nsl_fit_model_peak: // all models support multiple peaks 0859 uiGeneralTab.lDegree->setText(i18n("Number of peaks:")); 0860 uiGeneralTab.lDegree->setVisible(true); 0861 uiGeneralTab.sbDegree->setVisible(true); 0862 uiGeneralTab.sbDegree->setMaximum(9); 0863 break; 0864 case nsl_fit_model_growth: 0865 case nsl_fit_model_distribution: 0866 case nsl_fit_model_custom: 0867 uiGeneralTab.lDegree->setVisible(false); 0868 uiGeneralTab.sbDegree->setVisible(false); 0869 } 0870 0871 if (m_fitData.modelCategory == nsl_fit_model_distribution) { 0872 // enable ML only for supported distros 0873 const auto* model = qobject_cast<const QStandardItemModel*>(uiGeneralTab.cbAlgorithm->model()); 0874 auto* item = model->item(nsl_fit_algorithm_ml); 0875 0876 if (nsl_sf_stats_distribution_supports_ML((nsl_sf_stats_distribution)index)) 0877 item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); 0878 else { // switch to LM 0879 item->setFlags(item->flags() & ~(Qt::ItemIsSelectable | Qt::ItemIsEnabled)); 0880 uiGeneralTab.cbAlgorithm->setCurrentIndex(nsl_fit_algorithm_lm); 0881 } 0882 } 0883 0884 if (!m_initializing) 0885 m_fitData.modelType = index; 0886 0887 updateModelEquation(); 0888 0889 if (disableFit) 0890 uiGeneralTab.pbRecalculate->setEnabled(false); 0891 } 0892 0893 /*! 0894 * Called when the model type or the degree of the model were changed. 0895 * Show the preview pixmap of the fit model expression for the current model category and type. 0896 */ 0897 void XYFitCurveDock::updateModelEquation() { 0898 if (m_fitData.modelCategory == nsl_fit_model_custom) { 0899 DEBUG(Q_FUNC_INFO << ", category = nsl_fit_model_custom, type = " << m_fitData.modelType); 0900 } else { 0901 DEBUG(Q_FUNC_INFO << ", category = " << nsl_fit_model_category_name[m_fitData.modelCategory] << ", type = " << m_fitData.modelType); 0902 } 0903 0904 // this function can also be called when the value for the degree was changed -> update the fit data structure 0905 int degree = uiGeneralTab.sbDegree->value(); 0906 if (!m_initializing) { 0907 m_fitData.degree = degree; 0908 XYFitCurve::initFitData(m_fitData); 0909 // set model dependent start values from curve data 0910 // invalidate result 0911 m_fitCurve->clearFitResult(); 0912 static_cast<XYFitCurve*>(m_curve)->initStartValues(m_fitData, m_curve); 0913 // udpate parameter widget 0914 fitParametersWidget->setFitData(&m_fitData); 0915 if (m_messageWidget) 0916 m_messageWidget->close(); 0917 showFitResult(); // show result of preview 0918 } 0919 0920 // variables/parameter that are known 0921 QStringList vars = {QStringLiteral("x")}; 0922 vars << m_fitData.paramNames; 0923 uiGeneralTab.teEquation->setVariables(vars); 0924 0925 // set formula picture 0926 uiGeneralTab.lEquation->setText(QStringLiteral("f(x) =")); 0927 QString file; 0928 switch (m_fitData.modelCategory) { 0929 case nsl_fit_model_basic: { 0930 // formula pic depends on degree 0931 QString numSuffix = QString::number(degree); 0932 if (degree > 4) 0933 numSuffix = QLatin1Char('4'); 0934 if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2) 0935 numSuffix = QLatin1Char('2'); 0936 file = QStandardPaths::locate(QStandardPaths::AppDataLocation, 0937 QStringLiteral("pics/fit_models/") + QLatin1String(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix 0938 + QStringLiteral(".pdf")); 0939 break; 0940 } 0941 case nsl_fit_model_peak: { 0942 // formula pic depends on number of peaks 0943 QString numSuffix = QString::number(degree); 0944 if (degree > 4) 0945 numSuffix = QLatin1Char('4'); 0946 file = QStandardPaths::locate(QStandardPaths::AppDataLocation, 0947 QStringLiteral("pics/fit_models/") + QLatin1String(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix 0948 + QStringLiteral(".pdf")); 0949 break; 0950 } 0951 case nsl_fit_model_growth: 0952 file = QStandardPaths::locate(QStandardPaths::AppDataLocation, 0953 QStringLiteral("pics/fit_models/") + QLatin1String(nsl_fit_model_growth_pic_name[m_fitData.modelType]) 0954 + QStringLiteral(".pdf")); 0955 break; 0956 case nsl_fit_model_distribution: 0957 file = QStandardPaths::locate(QStandardPaths::AppDataLocation, 0958 QStringLiteral("pics/gsl_distributions/") + QLatin1String(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) 0959 + QStringLiteral(".pdf")); 0960 // change label 0961 if (m_fitData.modelType == nsl_sf_stats_poisson) 0962 uiGeneralTab.lEquation->setText(QStringLiteral("f(k)/A =")); 0963 else 0964 uiGeneralTab.lEquation->setText(QStringLiteral("f(x)/A =")); 0965 break; 0966 case nsl_fit_model_custom: 0967 uiGeneralTab.lFuncPic->hide(); 0968 uiGeneralTab.teEquation->show(); 0969 uiGeneralTab.teEquation->setPlainText(m_fitData.model); 0970 } 0971 0972 if (m_fitData.modelCategory != nsl_fit_model_custom) { 0973 QImage image = GuiTools::importPDFFile(file); 0974 0975 // use system palette for background 0976 if (GuiTools::isDarkMode()) { 0977 // invert image if in dark mode 0978 image.invertPixels(); 0979 0980 for (int i = 0; i < image.size().width(); i++) 0981 for (int j = 0; j < image.size().height(); j++) 0982 if (qGray(image.pixel(i, j)) < 64) // 0-255: 0-64 covers all dark pixel 0983 image.setPixel(QPoint(i, j), palette().color(QPalette::Base).rgb()); 0984 } else { 0985 for (int i = 0; i < image.size().width(); i++) 0986 for (int j = 0; j < image.size().height(); j++) 0987 if (qGray(image.pixel(i, j)) > 192) // 0-255: 224-255 covers all light pixel 0988 image.setPixel(QPoint(i, j), palette().color(QPalette::Base).rgb()); 0989 } 0990 0991 if (image.isNull()) { 0992 uiGeneralTab.lEquation->hide(); 0993 uiGeneralTab.lFuncPic->hide(); 0994 } else { 0995 // use light/dark background in the preview label 0996 QPalette p; 0997 p.setColor(QPalette::Window, palette().color(QPalette::Base)); 0998 uiGeneralTab.lFuncPic->setAutoFillBackground(true); 0999 uiGeneralTab.lFuncPic->setPalette(p); 1000 1001 uiGeneralTab.lFuncPic->setPixmap(QPixmap::fromImage(image)); 1002 uiGeneralTab.lFuncPic->show(); 1003 } 1004 uiGeneralTab.teEquation->hide(); 1005 } 1006 1007 enableRecalculate(); 1008 } 1009 1010 void XYFitCurveDock::showConstants() { 1011 QMenu menu; 1012 ConstantsWidget constants(&menu); 1013 1014 connect(&constants, &ConstantsWidget::constantSelected, this, &XYFitCurveDock::insertConstant); 1015 connect(&constants, &ConstantsWidget::constantSelected, &menu, &QMenu::close); 1016 connect(&constants, &ConstantsWidget::canceled, &menu, &QMenu::close); 1017 1018 auto* widgetAction = new QWidgetAction(this); 1019 widgetAction->setDefaultWidget(&constants); 1020 menu.addAction(widgetAction); 1021 1022 QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height()); 1023 menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos)); 1024 } 1025 1026 void XYFitCurveDock::showFunctions() { 1027 QMenu menu; 1028 FunctionsWidget functions(&menu); 1029 connect(&functions, &FunctionsWidget::functionSelected, this, &XYFitCurveDock::insertFunction); 1030 connect(&functions, &FunctionsWidget::functionSelected, &menu, &QMenu::close); 1031 connect(&functions, &FunctionsWidget::canceled, &menu, &QMenu::close); 1032 1033 auto* widgetAction = new QWidgetAction(this); 1034 widgetAction->setDefaultWidget(&functions); 1035 menu.addAction(widgetAction); 1036 1037 QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height()); 1038 menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos)); 1039 } 1040 1041 void XYFitCurveDock::algorithmChanged(int index) { 1042 m_fitData.algorithm = (nsl_fit_algorithm)index; 1043 1044 enableRecalculate(); 1045 } 1046 1047 /*! 1048 * Update parameter by parsing expression 1049 * Only called for custom fit model 1050 */ 1051 void XYFitCurveDock::updateParameterList() { 1052 DEBUG(Q_FUNC_INFO); 1053 // use current model function 1054 m_fitData.model = uiGeneralTab.teEquation->toPlainText(); 1055 1056 ExpressionParser* parser = ExpressionParser::getInstance(); 1057 QStringList vars; // variables that are known 1058 vars << QStringLiteral("x"); // TODO: generalize when we support other XYEquationCurve::EquationType 1059 m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars); 1060 1061 // if number of parameter changed 1062 int oldNumberOfParameter = m_fitData.paramStartValues.size(); 1063 int numberOfParameter = m_fitData.paramNames.size(); 1064 DEBUG(" old number of parameter: " << oldNumberOfParameter << " new number of parameter: " << numberOfParameter); 1065 if (numberOfParameter != oldNumberOfParameter) { 1066 m_fitData.paramStartValues.resize(numberOfParameter); 1067 m_fitData.paramFixed.resize(numberOfParameter); 1068 m_fitData.paramLowerLimits.resize(numberOfParameter); 1069 m_fitData.paramUpperLimits.resize(numberOfParameter); 1070 } 1071 if (numberOfParameter > oldNumberOfParameter) { 1072 for (int i = oldNumberOfParameter; i < numberOfParameter; ++i) { 1073 m_fitData.paramStartValues[i] = 1.0; 1074 m_fitData.paramFixed[i] = false; 1075 m_fitData.paramLowerLimits[i] = -std::numeric_limits<double>::max(); 1076 m_fitData.paramUpperLimits[i] = std::numeric_limits<double>::max(); 1077 } 1078 } 1079 1080 parametersChanged(); 1081 } 1082 1083 /*! 1084 * called when parameter names and/or start values for the model were changed 1085 * also called from parameter widget 1086 */ 1087 void XYFitCurveDock::parametersChanged(bool updateParameterWidget) { 1088 DEBUG(Q_FUNC_INFO << ", m_initializing = " << m_initializing); 1089 1090 // parameter names were (probably) changed -> set the new vars in ExpressionTextEdit teEquation 1091 QStringList vars{m_fitData.paramNames}; 1092 vars << QStringLiteral("x"); // TODO: generalize when we support other XYEquationCurve::EquationType 1093 uiGeneralTab.teEquation->setVariables(vars); 1094 1095 CONDITIONAL_RETURN_NO_LOCK 1096 1097 if (updateParameterWidget) 1098 fitParametersWidget->setFitData(&m_fitData); 1099 1100 enableRecalculate(); 1101 } 1102 void XYFitCurveDock::parametersValid(bool valid) { 1103 DEBUG(Q_FUNC_INFO << ", valid = " << valid); 1104 m_parametersValid = valid; 1105 } 1106 1107 void XYFitCurveDock::showOptions() { 1108 QMenu menu; 1109 FitOptionsWidget w(&menu, &m_fitData, m_fitCurve); 1110 connect(&w, &FitOptionsWidget::finished, &menu, &QMenu::close); 1111 connect(&w, &FitOptionsWidget::optionsChanged, this, &XYFitCurveDock::enableRecalculate); 1112 1113 auto* widgetAction = new QWidgetAction(this); 1114 widgetAction->setDefaultWidget(&w); 1115 menu.addAction(widgetAction); 1116 menu.setTearOffEnabled(true); 1117 1118 // menu.setWindowFlags(menu.windowFlags() & Qt::MSWindowsFixedSizeDialogHint); 1119 1120 QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbOptions->width(), 0); 1121 menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos)); 1122 } 1123 1124 void XYFitCurveDock::insertFunction(const QString& functionName) const { 1125 uiGeneralTab.teEquation->insertPlainText(functionName + ExpressionParser::functionArgumentString(functionName, XYEquationCurve::EquationType::Cartesian)); 1126 } 1127 1128 void XYFitCurveDock::insertConstant(const QString& constantsName) const { 1129 uiGeneralTab.teEquation->insertPlainText(constantsName); 1130 } 1131 1132 /*! 1133 * When a custom evaluate range is specified, set the plot range too. 1134 */ 1135 /*void XYFitCurveDock::setPlotXRange() { 1136 if (m_fitData.autoEvalRange || !m_curve) 1137 return; 1138 1139 auto* plot = dynamic_cast<CartesianPlot*>(m_curve->parentAspect()); 1140 if (plot) { 1141 Range<double> range{ m_fitData.evalRange }; 1142 if (!range.isZero()) 1143 plot->setXRange(range); 1144 } 1145 }*/ 1146 1147 void XYFitCurveDock::recalculateClicked() { 1148 DEBUG(Q_FUNC_INFO); 1149 QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); 1150 m_fitData.degree = uiGeneralTab.sbDegree->value(); 1151 if (m_fitData.modelCategory == nsl_fit_model_custom) 1152 updateParameterList(); 1153 1154 for (XYCurve* curve : m_curvesList) 1155 static_cast<XYFitCurve*>(curve)->setFitData(m_fitData); 1156 1157 m_fitCurve->recalculate(); 1158 // setPlotXRange(); 1159 1160 // update fitParametersWidget 1161 if (m_fitData.useResults) { 1162 DEBUG(" nr of param names = " << m_fitData.paramNames.size()) 1163 DEBUG(" size of start values = " << m_fitData.paramStartValues.size()) 1164 DEBUG(" size of param values = " << m_fitCurve->fitResult().paramValues.size()) 1165 if (m_fitCurve->fitResult().paramValues.size() > 0) { // may be 0 if fit fails 1166 for (int i = 0; i < m_fitData.paramNames.size(); i++) 1167 m_fitData.paramStartValues[i] = m_fitCurve->fitResult().paramValues.at(i); 1168 fitParametersWidget->setFitData(&m_fitData); 1169 } else { 1170 DEBUG(" WARNING: no fit result available!") 1171 } 1172 } 1173 1174 this->showFitResult(); 1175 uiGeneralTab.pbRecalculate->setEnabled(false); 1176 1177 // show the warning/error message, if available 1178 const auto& fitResult = m_fitCurve->fitResult(); 1179 const QString& status = fitResult.status; 1180 if (status != i18n("Success")) { 1181 Q_EMIT info(i18n("Fit status: %1", fitResult.status)); 1182 if (!m_messageWidget) { 1183 m_messageWidget = new KMessageWidget(this); 1184 uiGeneralTab.gridLayout_2->addWidget(m_messageWidget, 25, 3, 1, 4); 1185 } 1186 1187 if (!fitResult.valid) 1188 m_messageWidget->setMessageType(KMessageWidget::Error); 1189 else 1190 m_messageWidget->setMessageType(KMessageWidget::Warning); 1191 m_messageWidget->setText(status); 1192 m_messageWidget->animatedShow(); 1193 } else { 1194 if (m_messageWidget && m_messageWidget->isVisible()) 1195 m_messageWidget->close(); 1196 } 1197 1198 QApplication::restoreOverrideCursor(); 1199 DEBUG(Q_FUNC_INFO << " DONE"); 1200 } 1201 1202 void XYFitCurveDock::expressionChanged() { 1203 DEBUG(Q_FUNC_INFO); 1204 CONDITIONAL_RETURN_NO_LOCK; 1205 1206 // update parameter list for custom model 1207 if (m_fitData.modelCategory == nsl_fit_model_custom) 1208 updateParameterList(); 1209 1210 enableRecalculate(); 1211 } 1212 1213 void XYFitCurveDock::enableRecalculate() { 1214 DEBUG(Q_FUNC_INFO << ", m_initializing = " << m_initializing); 1215 if (m_initializing || !m_fitCurve) 1216 return; 1217 1218 // no fitting possible without the x- and y-data 1219 bool hasSourceData = false; 1220 // auto type = m_fitCurve->dataSourceType(); 1221 switch (m_fitCurve->dataSourceType()) { 1222 case XYAnalysisCurve::DataSourceType::Spreadsheet: { 1223 auto* aspectX = static_cast<AbstractAspect*>(cbXDataColumn->currentModelIndex().internalPointer()); 1224 auto* aspectY = static_cast<AbstractAspect*>(cbYDataColumn->currentModelIndex().internalPointer()); 1225 hasSourceData = (aspectX && aspectY); 1226 if (aspectX) { 1227 cbXDataColumn->useCurrentIndexText(true); 1228 cbXDataColumn->setInvalid(false); 1229 } 1230 if (aspectY) { 1231 cbYDataColumn->useCurrentIndexText(true); 1232 cbYDataColumn->setInvalid(false); 1233 } 1234 break; 1235 } 1236 case XYAnalysisCurve::DataSourceType::Curve: 1237 hasSourceData = (m_fitCurve->dataSourceCurve() != nullptr); 1238 break; 1239 case XYAnalysisCurve::DataSourceType::Histogram: 1240 hasSourceData = (m_fitCurve->dataSourceHistogram() != nullptr); 1241 } 1242 1243 DEBUG(Q_FUNC_INFO << ", hasSourceData = " << hasSourceData << ", m_parametersValid = " << m_parametersValid) 1244 uiGeneralTab.pbRecalculate->setEnabled(hasSourceData && m_parametersValid); 1245 1246 // PREVIEW as soon as recalculate is enabled (does not need source data) 1247 if (m_parametersValid && m_fitData.previewEnabled) { 1248 DEBUG(" EVALUATE WITH PREVIEW ENABLED"); 1249 // use recent fit data 1250 m_fitCurve->setFitData(m_fitData); 1251 // calculate fit function 1252 m_fitCurve->evaluate(true); 1253 // setPlotXRange(); 1254 } else { 1255 DEBUG(" PREVIEW DISABLED"); 1256 } 1257 DEBUG(Q_FUNC_INFO << " DONE"); 1258 } 1259 1260 void XYFitCurveDock::resultCopy(bool copyAll) { 1261 QTableWidget* tw{nullptr}; 1262 int currentTab = uiGeneralTab.twResults->currentIndex(); 1263 if (currentTab == 0) 1264 tw = uiGeneralTab.twParameters; 1265 else if (currentTab == 1) 1266 tw = uiGeneralTab.twGoodness; 1267 else if (currentTab == 2) 1268 tw = uiGeneralTab.twLog; 1269 else 1270 return; 1271 1272 QString str; 1273 QString rowStr; 1274 1275 // copy the header of the parameters table if we copy everything in this table 1276 if (copyAll && tw == uiGeneralTab.twParameters) { 1277 for (int i = 1; i < tw->columnCount(); ++i) 1278 str += QLatin1Char('\t') + tw->horizontalHeaderItem(i)->text(); 1279 } 1280 1281 // copy the content of the table 1282 for (int i = 0; i < tw->rowCount(); ++i) { 1283 for (int j = 0; j < tw->columnCount(); ++j) { 1284 if (!tw->item(i, j)) 1285 continue; 1286 if (!copyAll && !tw->item(i, j)->isSelected()) 1287 continue; 1288 1289 if (!rowStr.isEmpty()) 1290 rowStr += QLatin1Char('\t'); 1291 1292 rowStr += tw->item(i, j)->text(); 1293 } 1294 if (!rowStr.isEmpty()) { 1295 if (!str.isEmpty()) 1296 str += QLatin1Char('\n'); 1297 str += rowStr; 1298 rowStr.clear(); 1299 } 1300 } 1301 1302 QApplication::clipboard()->setText(str); 1303 DEBUG(STDSTRING(QApplication::clipboard()->text())); 1304 } 1305 1306 void XYFitCurveDock::resultCopyAll() { 1307 resultCopy(true); 1308 } 1309 1310 void XYFitCurveDock::resultParametersContextMenuRequest(QPoint pos) { 1311 auto* contextMenu = new QMenu(this); 1312 contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy); 1313 contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); 1314 contextMenu->exec(uiGeneralTab.twParameters->mapToGlobal(pos)); 1315 } 1316 void XYFitCurveDock::resultGoodnessContextMenuRequest(QPoint pos) { 1317 auto* contextMenu = new QMenu(this); 1318 contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy); 1319 contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); 1320 contextMenu->exec(uiGeneralTab.twGoodness->mapToGlobal(pos)); 1321 } 1322 void XYFitCurveDock::resultLogContextMenuRequest(QPoint pos) { 1323 auto* contextMenu = new QMenu(this); 1324 contextMenu->addAction(i18n("Copy Selection"), this, &XYFitCurveDock::resultCopy, QKeySequence::Copy); 1325 contextMenu->addAction(i18n("Copy All"), this, &XYFitCurveDock::resultCopyAll); 1326 contextMenu->exec(uiGeneralTab.twLog->mapToGlobal(pos)); 1327 } 1328 1329 /*! 1330 * show the result and details of the fit 1331 */ 1332 void XYFitCurveDock::showFitResult() { 1333 DEBUG(Q_FUNC_INFO) 1334 // clear the previous result 1335 uiGeneralTab.twParameters->setRowCount(0); 1336 for (int row = 0; row < uiGeneralTab.twGoodness->rowCount(); ++row) 1337 uiGeneralTab.twGoodness->item(row, 1)->setText(QString()); 1338 for (int row = 0; row < uiGeneralTab.twLog->rowCount(); ++row) 1339 uiGeneralTab.twLog->item(row, 1)->setText(QString()); 1340 1341 const auto& fitResult = m_fitCurve->fitResult(); 1342 1343 if (!fitResult.available) { 1344 DEBUG(Q_FUNC_INFO << ", fit result not available"); 1345 return; 1346 } 1347 1348 // Log 1349 uiGeneralTab.twLog->item(0, 1)->setText(fitResult.status); 1350 1351 if (!fitResult.valid) { 1352 DEBUG(Q_FUNC_INFO << ", fit result not valid"); 1353 return; 1354 } 1355 1356 const auto numberLocale = QLocale(); 1357 // used confidence interval 1358 double confidenceInterval{m_fitData.confidenceInterval}; 1359 uiGeneralTab.twParameters->horizontalHeaderItem(6)->setToolTip(i18n("%1 % lower confidence level", numberLocale.toString(confidenceInterval, 'g', 7))); 1360 uiGeneralTab.twParameters->horizontalHeaderItem(7)->setToolTip(i18n("%1 % upper confidence level", numberLocale.toString(confidenceInterval, 'g', 7))); 1361 1362 // log 1363 uiGeneralTab.twLog->item(1, 1)->setText(numberLocale.toString(fitResult.iterations)); 1364 uiGeneralTab.twLog->item(2, 1)->setText(numberLocale.toString(m_fitData.eps)); 1365 if (fitResult.elapsedTime > 1000) 1366 uiGeneralTab.twLog->item(3, 1)->setText(numberLocale.toString(fitResult.elapsedTime / 1000) + QStringLiteral(" s")); 1367 else 1368 uiGeneralTab.twLog->item(3, 1)->setText(numberLocale.toString(fitResult.elapsedTime) + QStringLiteral(" ms")); 1369 1370 uiGeneralTab.twLog->item(4, 1)->setText(numberLocale.toString(fitResult.dof)); 1371 uiGeneralTab.twLog->item(5, 1)->setText(numberLocale.toString(fitResult.paramValues.size())); 1372 uiGeneralTab.twLog->item(6, 1)->setText(m_fitData.fitRange.toString()); 1373 1374 const int np = m_fitData.paramNames.size(); 1375 1376 // correlation matrix 1377 QString sCorr; 1378 for (const auto& s : m_fitData.paramNamesUtf8) 1379 sCorr += QLatin1Char('\t') + s; 1380 DEBUG(Q_FUNC_INFO << ", correlation matrix size = " << fitResult.correlationMatrix.size()) 1381 if (fitResult.correlationMatrix.size() < np * (np + 1) / 2) 1382 return; 1383 1384 int index = 0; 1385 for (int i = 0; i < np; i++) { 1386 sCorr += QLatin1Char('\n') + m_fitData.paramNamesUtf8.at(i); 1387 for (int j = 0; j <= i; j++) 1388 sCorr += QLatin1Char('\t') + numberLocale.toString(fitResult.correlationMatrix.at(index++), 'f'); 1389 } 1390 uiGeneralTab.twLog->item(7, 1)->setText(sCorr); 1391 1392 // iterations 1393 QString sIter; 1394 for (const auto& s : m_fitData.paramNamesUtf8) 1395 sIter += s + QLatin1Char('\t'); 1396 sIter += UTF8_QSTRING("χ²"); 1397 1398 const QStringList iterations = fitResult.solverOutput.split(QLatin1Char(';')); 1399 for (const auto& s : iterations) 1400 if (!s.isEmpty()) 1401 sIter += QLatin1Char('\n') + s; 1402 uiGeneralTab.twLog->item(8, 1)->setText(sIter); 1403 uiGeneralTab.twLog->resizeRowsToContents(); 1404 1405 // Parameters 1406 uiGeneralTab.twParameters->setRowCount(np); 1407 1408 for (int i = 0; i < np; i++) { 1409 const double paramValue = fitResult.paramValues.at(i); 1410 const double errorValue = fitResult.errorValues.at(i); 1411 1412 auto* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i)); 1413 item->setBackground(QApplication::palette().color(QPalette::Window)); 1414 uiGeneralTab.twParameters->setItem(i, 0, item); 1415 item = new QTableWidgetItem(numberLocale.toString(paramValue)); 1416 uiGeneralTab.twParameters->setItem(i, 1, item); 1417 1418 if (!m_fitData.paramFixed.at(i)) { 1419 if (!std::isnan(errorValue)) { 1420 item = new QTableWidgetItem(numberLocale.toString(errorValue)); 1421 uiGeneralTab.twParameters->setItem(i, 2, item); 1422 item = new QTableWidgetItem(numberLocale.toString(100. * errorValue / std::abs(paramValue), 'g', 3)); 1423 uiGeneralTab.twParameters->setItem(i, 3, item); 1424 } else { 1425 item = new QTableWidgetItem(UTF8_QSTRING("∞")); 1426 uiGeneralTab.twParameters->setItem(i, 2, item); 1427 item = new QTableWidgetItem(UTF8_QSTRING("∞")); 1428 uiGeneralTab.twParameters->setItem(i, 3, item); 1429 } 1430 1431 // t values 1432 QString tdistValueString; 1433 if (fitResult.tdist_tValues.at(i) < std::numeric_limits<double>::max()) 1434 tdistValueString = numberLocale.toString(fitResult.tdist_tValues.at(i), 'g', 3); 1435 else 1436 tdistValueString = UTF8_QSTRING("∞"); 1437 item = new QTableWidgetItem(tdistValueString); 1438 uiGeneralTab.twParameters->setItem(i, 4, item); 1439 1440 // p values 1441 const double p = fitResult.tdist_pValues.at(i); 1442 item = new QTableWidgetItem(numberLocale.toString(p, 'g', 3)); 1443 // color p values depending on value 1444 if (p > 0.05) 1445 item->setForeground(QBrush(QApplication::palette().color(QPalette::LinkVisited))); 1446 else if (p > 0.01) 1447 item->setForeground(QBrush(Qt::darkGreen)); 1448 else if (p > 0.001) 1449 item->setForeground(QBrush(Qt::darkCyan)); 1450 else if (p > 0.0001) 1451 item->setForeground(QBrush(QApplication::palette().color(QPalette::Link))); 1452 else 1453 item->setForeground(QBrush(QApplication::palette().color(QPalette::Highlight))); 1454 uiGeneralTab.twParameters->setItem(i, 5, item); 1455 1456 // Conf. interval 1457 if (!std::isnan(errorValue)) { 1458 const double marginLow = fitResult.marginValues.at(i); 1459 // TODO: if (fitResult.tdist_tValues.at(i) > 1.e6) 1460 // item = new QTableWidgetItem(i18n("too small")); 1461 double marginHigh = marginLow; 1462 if (fitResult.marginValues.size() >= i && fitResult.marginValues.at(i) != 0.) 1463 marginHigh = fitResult.marginValues.at(i); 1464 1465 item = new QTableWidgetItem(numberLocale.toString(paramValue - marginLow)); 1466 uiGeneralTab.twParameters->setItem(i, 6, item); 1467 item = new QTableWidgetItem(numberLocale.toString(paramValue + marginHigh)); 1468 uiGeneralTab.twParameters->setItem(i, 7, item); 1469 } 1470 } 1471 } 1472 1473 // Goodness of fit 1474 uiGeneralTab.twGoodness->item(0, 1)->setText(numberLocale.toString(fitResult.sse)); 1475 1476 if (fitResult.dof != 0) { 1477 uiGeneralTab.twGoodness->item(1, 1)->setText(numberLocale.toString(fitResult.rms)); 1478 uiGeneralTab.twGoodness->item(2, 1)->setText(numberLocale.toString(fitResult.rsd)); 1479 1480 uiGeneralTab.twGoodness->item(3, 1)->setText(numberLocale.toString(fitResult.rsquare)); 1481 uiGeneralTab.twGoodness->item(4, 1)->setText(numberLocale.toString(fitResult.rsquareAdj)); 1482 1483 // chi^2 and F test p-values 1484 uiGeneralTab.twGoodness->item(5, 1)->setText(numberLocale.toString(fitResult.chisq_p, 'g', 3)); 1485 uiGeneralTab.twGoodness->item(6, 1)->setText(numberLocale.toString(fitResult.fdist_F, 'g', 3)); 1486 uiGeneralTab.twGoodness->item(7, 1)->setText(numberLocale.toString(fitResult.fdist_p, 'g', 3)); 1487 uiGeneralTab.twGoodness->item(9, 1)->setText(numberLocale.toString(fitResult.aic, 'g', 3)); 1488 uiGeneralTab.twGoodness->item(10, 1)->setText(numberLocale.toString(fitResult.bic, 'g', 3)); 1489 } 1490 1491 uiGeneralTab.twGoodness->item(8, 1)->setText(numberLocale.toString(fitResult.mae)); 1492 1493 // resize the table headers to fit the new content 1494 uiGeneralTab.twLog->resizeColumnsToContents(); 1495 uiGeneralTab.twParameters->resizeColumnsToContents(); 1496 // twGoodness doesn't have any header -> resize sections 1497 uiGeneralTab.twGoodness->resizeColumnToContents(0); 1498 uiGeneralTab.twGoodness->resizeColumnToContents(1); 1499 1500 // enable the "recalculate"-button if the source data was changed since the last fit 1501 uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc()); 1502 } 1503 1504 //************************************************************* 1505 //*********** SLOTs for changes triggered in XYCurve ********** 1506 //************************************************************* 1507 // General-Tab 1508 void XYFitCurveDock::curveDataSourceTypeChanged(XYAnalysisCurve::DataSourceType type) { 1509 CONDITIONAL_LOCK_RETURN; 1510 uiGeneralTab.cbDataSourceType->setCurrentIndex(static_cast<int>(type)); 1511 } 1512 1513 void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { 1514 CONDITIONAL_LOCK_RETURN; 1515 cbDataSourceCurve->setAspect(curve); 1516 } 1517 1518 void XYFitCurveDock::curveDataSourceHistogramChanged(const Histogram* hist) { 1519 CONDITIONAL_LOCK_RETURN; 1520 cbDataSourceCurve->setAspect(hist); 1521 } 1522 1523 void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { 1524 CONDITIONAL_LOCK_RETURN; 1525 cbXDataColumn->setColumn(column, m_fitCurve->xDataColumnPath()); 1526 } 1527 1528 void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { 1529 CONDITIONAL_LOCK_RETURN; 1530 cbYDataColumn->setColumn(column, m_fitCurve->yDataColumnPath()); 1531 } 1532 1533 void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) { 1534 CONDITIONAL_LOCK_RETURN; 1535 cbXErrorColumn->setColumn(column, m_fitCurve->xErrorColumnPath()); 1536 } 1537 1538 void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) { 1539 CONDITIONAL_LOCK_RETURN; 1540 cbYErrorColumn->setColumn(column, m_fitCurve->yErrorColumnPath()); 1541 } 1542 1543 /*! 1544 * called when fit data of fit curve changes 1545 */ 1546 void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& fitData) { 1547 CONDITIONAL_LOCK_RETURN; 1548 m_fitData = fitData; 1549 1550 if (m_fitData.modelCategory != nsl_fit_model_custom) 1551 uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); 1552 1553 uiGeneralTab.sbDegree->setValue(m_fitData.degree); 1554 } 1555 1556 void XYFitCurveDock::dataChanged() { 1557 this->enableRecalculate(); 1558 }