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