diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index 2ce87eb50..484b2b523 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2124 +1,2125 @@ /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" } #include #include #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYCurve(name, new XYFitCurvePrivate(this)) { init(); } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFitCurve::~XYFitCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFitCurve::init() { Q_D(XYFitCurve); //TODO: read from the saved settings for XYFitCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = nsl_fit_model_custom; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; unsigned int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int num = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; DEBUG("XYFitCurve::initFitData() for model category = " << modelCategory << ", model type = " << modelType << ", degree = " << num); if (modelCategory != nsl_fit_model_custom) paramNames.clear(); paramNamesUtf8.clear(); - // indices used in multi peak parameter models - QStringList indices = {QString::fromUtf8("\u2081"), QString::fromUtf8("\u2082"), QString::fromUtf8("\u2083"), QString::fromUtf8("\u2084"), QString::fromUtf8("\u2085"), - QString::fromUtf8("\u2086"), QString::fromUtf8("\u2087"), QString::fromUtf8("\u2088"), QString::fromUtf8("\u2089")}; + // 10 indices used in multi degree models + QStringList indices = {QString::fromUtf8("\u2081"), QString::fromUtf8("\u2082"), QString::fromUtf8("\u2083"), + QString::fromUtf8("\u2084"), QString::fromUtf8("\u2085"), QString::fromUtf8("\u2086"), QString::fromUtf8("\u2087"), + QString::fromUtf8("\u2088"), QString::fromUtf8("\u2089"), QString::fromUtf8("\u2081") + QString::fromUtf8("\u2080")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << QString::fromUtf8("c\u2080") << QString::fromUtf8("c\u2081"); if (num == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << QString::fromUtf8("c\u2082"); } else if (num > 2) { QString numStr = QString::number(num); for (int i = 2; i <= num; ++i) { numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << "c" + numStr; paramNamesUtf8 << "c" + indices[i-1]; } } break; case nsl_fit_model_power: if (num == 1) paramNames << "a" << "b"; else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: switch (num) { case 1: paramNames << "a" << "b"; break; case 2: model = "a1*exp(b1*x) + a2*exp(b2*x)"; paramNames << "a1" << "b1" << "a2" << "b2"; paramNamesUtf8 << QString::fromUtf8("a\u2081") << QString::fromUtf8("b\u2081") << QString::fromUtf8("a\u2082") << QString::fromUtf8("b\u2082"); break; case 3: model = "a1*exp(b1*x) + a2*exp(b2*x) + a3*exp(b3*x)"; paramNames << "a1" << "b1" << "a2" << "b2" << "a3" << "b3"; paramNamesUtf8 << QString::fromUtf8("a\u2081") << QString::fromUtf8("b\u2081") << QString::fromUtf8("a\u2082") << QString::fromUtf8("b\u2082") << QString::fromUtf8("a\u2083") << QString::fromUtf8("b\u2083"); break; //TODO: up to 9 exponentials } break; case nsl_fit_model_inverse_exponential: num = 1; paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << QString::fromUtf8("\u03c9") << QString::fromUtf8("a\u2080") << QString::fromUtf8("a\u2081") << QString::fromUtf8("b\u2081"); if (num > 1) { for (int i = 1; i <= num; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << "a"+numStr << "b"+numStr; paramNamesUtf8 << "a" + indices[i-1] << "b" + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch ((nsl_fit_model_type_peak)modelType) { case nsl_fit_model_gaussian: switch (num) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2) + a3/s3 * exp(-((x-mu3)/s3)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= num; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_lorentz: switch (num) { case 1: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2) + a3 * g3/(g3^2+(x-mu3)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2" << "g3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03b3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: QString numStr = QString::number(num); model = "1./pi * ("; for (int i = 1; i <= num; ++i) { numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << "g" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03b3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_sech: switch (num) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2) + a3/s3 * sech((x-mu3)/s3))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: QString numStr = QString::number(num); model = "1/pi * ("; for (int i = 1; i <= num; ++i) { numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ")"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_logistic: switch (num) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2 + a3/s3 * sech((x-mu3)/2/s3)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); break; default: QString numStr = QString::number(num); model = "1/4 * ("; for (int i = 1; i <= num; ++i) { numStr = QString::number(i); if (i > 1) model += " + "; model += "a" + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch ((nsl_fit_model_type_growth)modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_sigmoid: paramNames << "k" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_hill: paramNames << "s" << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "n" << "A"; break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch ((nsl_sf_stats_distribution)modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gaussian_tail: paramNames << "s" << "mu" << "A" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A" << "a"; break; case nsl_sf_stats_exponential: paramNames << "l" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_exponential_power: paramNames << "s" << "mu" << "b" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "b" << "A"; break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_rayleigh: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "t" << "k" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b8") << "k" << "A"; break; case nsl_sf_stats_flat: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_chi_squared: paramNames << "n" << "a"; paramNamesUtf8 << "n" << "A"; break; case nsl_sf_stats_fdist: paramNames << "n1" << "n2" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2081") << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2082") << "A"; break; case nsl_sf_stats_tdist: paramNames << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") << "A"; break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_weibull: paramNames << "k" << "l" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel1: paramNames << "s" << "b" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03b2") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel2: paramNames << "a" << "b" << "mu" << "A"; paramNamesUtf8 << "a" << "b" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_poisson: paramNames << "l" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << "A"; break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "p" << "n" << "a"; paramNamesUtf8 << "p" << "n" << "A"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "p" << "a"; paramNamesUtf8 << "p" << "A"; break; case nsl_sf_stats_hypergeometric: paramNames << "n1" << "n2" << "t" << "a"; paramNamesUtf8 << "n" + QString::fromUtf8("\u2081") << "n" + QString::fromUtf8("\u2082") << "t" << "A"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; break; case nsl_sf_stats_frechet: paramNames << "g" << "mu" << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << QString::fromUtf8("\u03c3") << "A"; break; } break; case nsl_fit_model_custom: break; } if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -DBL_MAX; paramUpperLimits[i] = DBL_MAX; } // set some model-dependent start values if (modelCategory == nsl_fit_model_distribution) { nsl_sf_stats_distribution type = (nsl_sf_stats_distribution)modelType; if (type == nsl_sf_stats_flat) paramStartValues[0] = -1.0; else if (type == nsl_sf_stats_frechet || type == nsl_sf_stats_levy || type == nsl_sf_stats_exponential_power) paramStartValues[1] = 0.0; else if (type == nsl_sf_stats_weibull || type == nsl_sf_stats_gumbel2) paramStartValues[2] = 0.0; else if (type == nsl_sf_stats_binomial || type == nsl_sf_stats_negative_binomial || type == nsl_sf_stats_pascal || type == nsl_sf_stats_geometric || type == nsl_sf_stats_logarithmic) paramStartValues[0] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yDataColumn, yDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xDataColumnPath() const { Q_D(const XYFitCurve); return d->xDataColumnPath; } const QString& XYFitCurve::yDataColumnPath() const { Q_D(const XYFitCurve); return d->yDataColumnPath; } const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve);return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve);return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFitCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xDataColumn) { exec(new XYFitCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFitCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yDataColumn) { exec(new XYFitCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { exec(new XYFitCurveSetXErrorColumnCmd(d, column, i18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { exec(new XYFitCurveSetYErrorColumnCmd(d, column, i18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate); void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); exec(new XYFitCurveSetFitDataCmd(d, fitData, i18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xErrorColumn(0), yErrorColumn(0), xColumn(0), yColumn(0), residualsColumn(0), xVector(0), yVector(0), residualsVector(0), q(owner) { } XYFitCurvePrivate::~XYFitCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; unsigned int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double x = gsl_vector_get(paramValues, i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(x, min[i], max[i])); QDEBUG("Parameter"<at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); const double eps = 1.e-9 * fabs(f_p); // adapt step size to value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // qDebug()<<"evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); residualsVector->clear(); } // clear the previous result fitResult = XYFitCurve::FitResult(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Number of x and y data points must be equal."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < xDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.xRange.first(); xmax = fitData.xRange.last(); } for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y and errors, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { if (dataSourceType == XYCurve::DataSourceCurve || (!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } else if (!xErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } else { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG("number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG("x errors: " << xerrorVector.size()); DEBUG("y errors: " << yerrorVector.size()); double* weight = new double[n]; for (size_t i = 0; i < n; i++) weight[i] = 1.; switch (fitData.weightsType) { case nsl_fit_weight_no: break; case nsl_fit_weight_instrumental: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(yerror[i]); break; case nsl_fit_weight_direct: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = 1./yerror[i]; break; case nsl_fit_weight_statistical: for (size_t i = 0; i < n; i++) weight[i] = 1./ydata[i]; break; case nsl_fit_weight_relative: for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(ydata[i]); break; case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" for (unsigned int i = 0; i < np; i++) DEBUG("parameter " << i << " fixed: " << fitData.paramFixed.data()[i]); //function to fit gsl_multifit_function_fdf f; DEBUG("model = " << fitData.model.toStdString()); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; // initialize the derivative solver (using Levenberg-Marquardt robust solver) const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); // set start values double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); // scale start values if limits are set for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); gsl_vector_view x = gsl_vector_view_array(x_init, np); // initialize solver with function f and initial guess x gsl_multifit_fdfsolver_set(s, &f, &x.vector); // iterate int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; // update weights for Y-depending weights if (fitData.weightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i } else if (fitData.weightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i^2 } status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG("Rerun fit with x errors"); // y'(x) double *yd = new double[n]; for (size_t i = 0; i < n; i++) { size_t index = i; if (index == n-1) index = n-2; yd[i] = gsl_vector_get(s->f, index+1) + ydata[index+1] - gsl_vector_get(s->f, index) - ydata[index]; yd[i] /= (xdata[index+1] - xdata[index]); } switch (fitData.weightsType) { case nsl_fit_weight_no: break; case nsl_fit_weight_instrumental: for (size_t i = 0; i < n; i++) { double sigma; if (yerrorVector.size() > 0) // x- and y-error // sigma = sqrt(sigma_y^2 + (y'(x)*sigma_x)^2) sigma = sqrt(gsl_pow_2(yerror[i]) + gsl_pow_2(yd[i] * xerror[i])); else // only x-error sigma = yd[i] * xerror[i]; weight[i] = 1./gsl_pow_2(sigma); } break; // other weight types: y'(x) considered correctly? case nsl_fit_weight_direct: for (size_t i = 0; i < n; i++) { weight[i] = xerror[i]/yd[i]; if (yerrorVector.size() > 0) weight[i] += yerror[i]; } break; case nsl_fit_weight_inverse: for (size_t i = 0; i < n; i++) { weight[i] = yd[i]/xerror[i]; if (yerrorVector.size() > 0) weight[i] += 1./yerror[i]; } break; case nsl_fit_weight_statistical: case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i break; case nsl_fit_weight_relative_fit: for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i^2 break; } delete[] yd; do { iter++; status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) break; status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); } delete[] weight; // unscale start values for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = QString(gsl_strerror(status)); // i18n? GSL does not support translations fitResult.iterations = iter; fitResult.dof = n - np; //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; //needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); //parameter values const double c = GSL_MIN_DBL(1., sqrt(fitResult.rms)); //limit error for poor fit fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG("saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); if (fitData.evaluateFullRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*xVector)[i] = tmpXDataColumn->valueAt(i); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; if (!rc) residualsVector->clear(); } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) ExpressionParser* parser = ExpressionParser::getInstance(); if (fitData.evaluateFullRange) { // evaluate fit on full data range if selected xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } xVector->resize(fitData.evaluatedPoints); yVector->resize(fitData.evaluatedPoints); bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, fitResult.paramValues); if (!rc) { xVector->clear(); yVector->clear(); } fitResult.elapsedTime = timer.elapsed(); //redraw the curve emit (q->dataChanged()); sourceDataChangedSinceLastRecalc = false; } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write xy-curve information XYCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("xRangeMin", QString::number(d->fitData.xRange.first(), 'g', 15)); writer->writeAttribute("xRangeMax", QString::number(d->fitData.xRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("weightsType", QString::number(d->fitData.weightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("evaluateFullRange", QString::number(d->fitData.evaluateFullRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFitCurve); if (!reader->isStartElement() || reader->name() != "xyFitCurve") { reader->raiseError(i18n("no xy fit curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; if (preview) return true; } else if (reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", fitData.xRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("weightsType", fitData.weightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << d->fitData.model.toStdString()); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.evaluateFullRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); } else if (reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -DBL_MAX; } else if (reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << DBL_MAX; } else if (reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { d->fitData.modelType = 0; d->fitData.degree = 1; // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); } // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; } return true; } diff --git a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp index e67e864bb..9fb300a4c 100644 --- a/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp +++ b/src/kdefrontend/dockwidgets/XYFitCurveDock.cpp @@ -1,1128 +1,1128 @@ /*************************************************************************** File : XYFitCurveDock.cpp Project : LabPlot -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) Description : widget for editing properties of fit curves ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "XYFitCurveDock.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/lib/macros.h" #include "backend/gsl/ExpressionParser.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include "kdefrontend/widgets/ConstantsWidget.h" #include "kdefrontend/widgets/FunctionsWidget.h" #include "kdefrontend/widgets/FitOptionsWidget.h" #include "kdefrontend/widgets/FitParametersWidget.h" #include #include #include #include #include // DBL_MAX #include // fabs() extern "C" { #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } /*! \class XYFitCurveDock \brief Provides a widget for editing the properties of the XYFitCurves (2D-curves defined by a fit model) currently selected in the project explorer. If more then one curves are set, the properties of the first column are shown. The changes of the properties are applied to all curves. The exclusions are the name, the comment and the datasets (columns) of the curves - these properties can only be changed if there is only one single curve. \ingroup kdefrontend */ XYFitCurveDock::XYFitCurveDock(QWidget* parent) : XYCurveDock(parent), cbDataSourceCurve(0), cbXDataColumn(0), cbYDataColumn(0), cbXErrorColumn(0), cbYErrorColumn(0), m_fitCurve(0) { //remove the tab "Error bars" ui.tabWidget->removeTab(5); } /*! * // Tab "General" */ void XYFitCurveDock::setupGeneral() { QWidget* generalTab = new QWidget(ui.tabGeneral); uiGeneralTab.setupUi(generalTab); QGridLayout* gridLayout = qobject_cast(generalTab->layout()); if (gridLayout) { gridLayout->setContentsMargins(2, 2, 2, 2); gridLayout->setHorizontalSpacing(2); gridLayout->setVerticalSpacing(2); } uiGeneralTab.cbDataSourceType->addItem(i18n("Spreadsheet")); uiGeneralTab.cbDataSourceType->addItem(i18n("XY-Curve")); cbDataSourceCurve = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbDataSourceCurve, 6, 4, 1, 4); cbXDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXDataColumn, 7, 4, 1, 1); cbXErrorColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbXErrorColumn, 7, 5, 1, 4); cbYDataColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYDataColumn, 8, 4, 1, 1); cbYErrorColumn = new TreeViewComboBox(generalTab); gridLayout->addWidget(cbYErrorColumn, 8, 5, 1, 4); //Weight for(int i = 0; i < NSL_FIT_WEIGHT_TYPE_COUNT; i++) uiGeneralTab.cbWeight->addItem(nsl_fit_weight_type_name[i]); uiGeneralTab.cbWeight->setCurrentIndex(nsl_fit_weight_instrumental); for(int i = 0; i < NSL_FIT_MODEL_CATEGORY_COUNT; i++) uiGeneralTab.cbCategory->addItem(nsl_fit_model_category_name[i]); //show the fit-model category for the currently selected default (first) fit-model category categoryChanged(uiGeneralTab.cbCategory->currentIndex()); uiGeneralTab.teEquation->setMaximumHeight(uiGeneralTab.leName->sizeHint().height() * 2); //use white background in the preview label QPalette p; p.setColor(QPalette::Window, Qt::white); uiGeneralTab.lFuncPic->setAutoFillBackground(true); uiGeneralTab.lFuncPic->setPalette(p); uiGeneralTab.tbConstants->setIcon(QIcon::fromTheme("labplot-format-text-symbol")); uiGeneralTab.tbFunctions->setIcon(QIcon::fromTheme("preferences-desktop-font")); uiGeneralTab.pbRecalculate->setIcon(QIcon::fromTheme("run-build")); uiGeneralTab.twGeneral->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twParameters->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twGoodness->setEditTriggers(QAbstractItemView::NoEditTriggers); uiGeneralTab.twGeneral->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->horizontalHeader()->resizeSections(QHeaderView::ResizeToContents); uiGeneralTab.twGoodness->item(0, 1)->setText(QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(1, 1)->setText(i18n("reduced") + " " + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + " (" + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + "/dof)"); uiGeneralTab.twGoodness->item(3, 1)->setText("R" + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(4, 1)->setText("R" + QString::fromUtf8("\u0304") + QString::fromUtf8("\u00b2")); uiGeneralTab.twGoodness->item(5, 0)->setText(QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ' ' + i18n("test")); uiGeneralTab.twGoodness->item(5, 1)->setText("P > " + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2")); QHBoxLayout* layout = new QHBoxLayout(ui.tabGeneral); layout->setMargin(0); layout->addWidget(generalTab); //Slots connect(uiGeneralTab.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged())); connect(uiGeneralTab.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged())); connect(uiGeneralTab.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool))); connect(uiGeneralTab.cbDataSourceType, SIGNAL(currentIndexChanged(int)), this, SLOT(dataSourceTypeChanged(int))); connect(uiGeneralTab.cbAutoRange, SIGNAL(clicked(bool)), this, SLOT(autoRangeChanged())); connect(uiGeneralTab.sbMin, SIGNAL(valueChanged(double)), this, SLOT(xRangeMinChanged())); connect(uiGeneralTab.sbMax, SIGNAL(valueChanged(double)), this, SLOT(xRangeMaxChanged())); connect(uiGeneralTab.cbWeight, SIGNAL(currentIndexChanged(int)), this, SLOT(weightChanged(int))); connect(uiGeneralTab.cbCategory, SIGNAL(currentIndexChanged(int)), this, SLOT(categoryChanged(int))); connect(uiGeneralTab.cbModel, SIGNAL(currentIndexChanged(int)), this, SLOT(modelTypeChanged(int))); connect(uiGeneralTab.sbDegree, SIGNAL(valueChanged(int)), this, SLOT(updateModelEquation())); connect(uiGeneralTab.teEquation, SIGNAL(expressionChanged()), this, SLOT(enableRecalculate())); connect(uiGeneralTab.tbConstants, SIGNAL(clicked()), this, SLOT(showConstants())); connect(uiGeneralTab.tbFunctions, SIGNAL(clicked()), this, SLOT(showFunctions())); connect(uiGeneralTab.pbParameters, SIGNAL(clicked()), this, SLOT(showParameters())); connect(uiGeneralTab.pbOptions, SIGNAL(clicked()), this, SLOT(showOptions())); connect(uiGeneralTab.pbRecalculate, SIGNAL(clicked()), this, SLOT(recalculateClicked())); connect(cbDataSourceCurve, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(dataSourceCurveChanged(QModelIndex))); connect(cbXDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xDataColumnChanged(QModelIndex))); connect(cbYDataColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yDataColumnChanged(QModelIndex))); connect(cbXErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(xErrorColumnChanged(QModelIndex))); connect(cbYErrorColumn, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(yErrorColumnChanged(QModelIndex))); } void XYFitCurveDock::initGeneralTab() { //if there are more then one curve in the list, disable the tab "general" if (m_curvesList.size() == 1) { uiGeneralTab.lName->setEnabled(true); uiGeneralTab.leName->setEnabled(true); uiGeneralTab.lComment->setEnabled(true); uiGeneralTab.leComment->setEnabled(true); uiGeneralTab.leName->setText(m_curve->name()); uiGeneralTab.leComment->setText(m_curve->comment()); } else { uiGeneralTab.lName->setEnabled(false); uiGeneralTab.leName->setEnabled(false); uiGeneralTab.lComment->setEnabled(false); uiGeneralTab.leComment->setEnabled(false); uiGeneralTab.leName->setText(""); uiGeneralTab.leComment->setText(""); } //show the properties of the first curve m_fitCurve = dynamic_cast(m_curve); Q_ASSERT(m_fitCurve); uiGeneralTab.cbDataSourceType->setCurrentIndex(m_fitCurve->dataSourceType()); this->dataSourceTypeChanged(uiGeneralTab.cbDataSourceType->currentIndex()); XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, m_fitCurve->dataSourceCurve()); XYCurveDock::setModelIndexFromAspect(cbXDataColumn, m_fitCurve->xDataColumn()); XYCurveDock::setModelIndexFromAspect(cbYDataColumn, m_fitCurve->yDataColumn()); XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, m_fitCurve->xErrorColumn()); XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, m_fitCurve->yErrorColumn()); uiGeneralTab.cbAutoRange->setChecked(m_fitData.autoRange); uiGeneralTab.sbMin->setValue(m_fitData.xRange.first()); uiGeneralTab.sbMax->setValue(m_fitData.xRange.last()); this->autoRangeChanged(); unsigned int tmpModelType = m_fitData.modelType; // save type because it's reset when category changes if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.cbCategory->setCurrentIndex(uiGeneralTab.cbCategory->count() - 1); else uiGeneralTab.cbCategory->setCurrentIndex(m_fitData.modelCategory); m_fitData.modelType = tmpModelType; if (m_fitData.modelCategory != nsl_fit_model_custom) uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.cbWeight->setCurrentIndex(m_fitData.weightsType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); updateModelEquation(); this->showFitResult(); uiGeneralTab.chkVisible->setChecked(m_curve->isVisible()); //Slots connect(m_fitCurve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(curveDescriptionChanged(const AbstractAspect*))); connect(m_fitCurve, SIGNAL(dataSourceTypeChanged(XYCurve::DataSourceType)), this, SLOT(curveDataSourceTypeChanged(XYCurve::DataSourceType))); connect(m_fitCurve, SIGNAL(dataSourceCurveChanged(const XYCurve*)), this, SLOT(curveDataSourceCurveChanged(const XYCurve*))); connect(m_fitCurve, SIGNAL(xDataColumnChanged(const AbstractColumn*)), this, SLOT(curveXDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yDataColumnChanged(const AbstractColumn*)), this, SLOT(curveYDataColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(xErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveXErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(yErrorColumnChanged(const AbstractColumn*)), this, SLOT(curveYErrorColumnChanged(const AbstractColumn*))); connect(m_fitCurve, SIGNAL(fitDataChanged(XYFitCurve::FitData)), this, SLOT(curveFitDataChanged(XYFitCurve::FitData))); connect(m_fitCurve, SIGNAL(sourceDataChanged()), this, SLOT(enableRecalculate())); } void XYFitCurveDock::setModel() { QList list; list << "Folder" << "Datapicker" << "Worksheet" << "CartesianPlot" << "XYCurve"; cbDataSourceCurve->setTopLevelClasses(list); QList hiddenAspects; for (auto* curve: m_curvesList) hiddenAspects << curve; cbDataSourceCurve->setHiddenAspects(hiddenAspects); list.clear(); list << "Folder" << "Workbook" << "Spreadsheet" << "FileDataSource" << "Column" << "CantorWorksheet" << "Datapicker"; cbXDataColumn->setTopLevelClasses(list); cbYDataColumn->setTopLevelClasses(list); cbXErrorColumn->setTopLevelClasses(list); cbYErrorColumn->setTopLevelClasses(list); cbDataSourceCurve->setModel(m_aspectTreeModel); cbXDataColumn->setModel(m_aspectTreeModel); cbYDataColumn->setModel(m_aspectTreeModel); cbXErrorColumn->setModel(m_aspectTreeModel); cbYErrorColumn->setModel(m_aspectTreeModel); XYCurveDock::setModel(); } /*! sets the curves. The properties of the curves in the list \c list can be edited in this widget. */ void XYFitCurveDock::setCurves(QList list) { m_initializing = true; m_curvesList = list; m_curve = list.first(); m_fitCurve = dynamic_cast(m_curve); Q_ASSERT(m_fitCurve); m_aspectTreeModel = new AspectTreeModel(m_curve->project()); this->setModel(); m_fitData = m_fitCurve->fitData(); initGeneralTab(); initTabs(); m_initializing = false; } //************************************************************* //**** SLOTs for changes triggered in XYFitCurveDock ***** //************************************************************* void XYFitCurveDock::nameChanged() { if (m_initializing) return; m_curve->setName(uiGeneralTab.leName->text()); } void XYFitCurveDock::commentChanged() { if (m_initializing) return; m_curve->setComment(uiGeneralTab.leComment->text()); } void XYFitCurveDock::dataSourceTypeChanged(int index) { const XYCurve::DataSourceType type = (XYCurve::DataSourceType)index; if (type == XYCurve::DataSourceSpreadsheet) { uiGeneralTab.lDataSourceCurve->hide(); cbDataSourceCurve->hide(); uiGeneralTab.lXColumn->show(); cbXDataColumn->show(); uiGeneralTab.lYColumn->show(); cbYDataColumn->show(); cbXErrorColumn->show(); cbYErrorColumn->show(); } else { uiGeneralTab.lDataSourceCurve->show(); cbDataSourceCurve->show(); uiGeneralTab.lXColumn->hide(); cbXDataColumn->hide(); uiGeneralTab.lYColumn->hide(); cbYDataColumn->hide(); cbXErrorColumn->hide(); cbYErrorColumn->hide(); } if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceType(type); } void XYFitCurveDock::dataSourceCurveChanged(const QModelIndex& index) { AbstractAspect* aspect = static_cast(index.internalPointer()); XYCurve* dataSourceCurve = 0; if (aspect) { dataSourceCurve = dynamic_cast(aspect); Q_ASSERT(dataSourceCurve); } this->updateSettings(dataSourceCurve->xColumn()); if (m_initializing) return; for (auto* curve: m_curvesList) dynamic_cast(curve)->setDataSourceCurve(dataSourceCurve); } void XYFitCurveDock::xDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } this->updateSettings(column); for (auto* curve: m_curvesList) dynamic_cast(curve)->setXDataColumn(column); } void XYFitCurveDock::updateSettings(const AbstractColumn* column) { if (!column) return; if (uiGeneralTab.cbAutoRange->isChecked()) { uiGeneralTab.sbMin->setValue(column->minimum()); uiGeneralTab.sbMax->setValue(column->maximum()); } } void XYFitCurveDock::yDataColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setYDataColumn(column); } void XYFitCurveDock::autoRangeChanged() { const bool autoRange = uiGeneralTab.cbAutoRange->isChecked(); m_fitData.autoRange = autoRange; if (autoRange) { uiGeneralTab.sbMin->setEnabled(false); uiGeneralTab.lXRange2->setEnabled(false); uiGeneralTab.sbMax->setEnabled(false); const AbstractColumn* xDataColumn = 0; if (m_fitCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) xDataColumn = m_fitCurve->xDataColumn(); else { if (m_fitCurve->dataSourceCurve()) xDataColumn = m_fitCurve->dataSourceCurve()->xColumn(); } if (xDataColumn) { uiGeneralTab.sbMin->setValue(xDataColumn->minimum()); uiGeneralTab.sbMax->setValue(xDataColumn->maximum()); } } else { uiGeneralTab.sbMin->setEnabled(true); uiGeneralTab.lXRange2->setEnabled(true); uiGeneralTab.sbMax->setEnabled(true); } } void XYFitCurveDock::xRangeMinChanged() { const double xMin = uiGeneralTab.sbMin->value(); m_fitData.xRange.first() = xMin; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYFitCurveDock::xRangeMaxChanged() { const double xMax = uiGeneralTab.sbMax->value(); m_fitData.xRange.last() = xMax; uiGeneralTab.pbRecalculate->setEnabled(true); } void XYFitCurveDock::xErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setXErrorColumn(column); } void XYFitCurveDock::yErrorColumnChanged(const QModelIndex& index) { if (m_initializing) return; AbstractAspect* aspect = static_cast(index.internalPointer()); AbstractColumn* column = 0; if (aspect) { column = dynamic_cast(aspect); Q_ASSERT(column); } for (auto* curve: m_curvesList) dynamic_cast(curve)->setYErrorColumn(column); //y-error column was selected - in case no weighting is selected yet, automatically select instrumental weighting if ( uiGeneralTab.cbWeight->currentIndex() == 0 ) uiGeneralTab.cbWeight->setCurrentIndex((int)nsl_fit_weight_instrumental); } void XYFitCurveDock::weightChanged(int index) { DEBUG("weightChanged() weight = " << nsl_fit_weight_type_name[index]); m_fitData.weightsType = (nsl_fit_weight_type)index; enableRecalculate(); } /*! * called when the fit model category (basic functions, peak functions etc.) was changed. * In the combobox for the model type shows the model types for the current category \index and calls \c modelTypeChanged() * to update the model type dependent widgets in the general-tab. */ void XYFitCurveDock::categoryChanged(int index) { DEBUG("categoryChanged() category = \"" << nsl_fit_model_category_name[index] << "\""); if (uiGeneralTab.cbCategory->currentIndex() == uiGeneralTab.cbCategory->count() - 1) m_fitData.modelCategory = nsl_fit_model_custom; else m_fitData.modelCategory = (nsl_fit_model_category)index; m_initializing = true; uiGeneralTab.cbModel->clear(); uiGeneralTab.cbModel->show(); uiGeneralTab.lModel->show(); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: for(int i = 0; i < NSL_FIT_MODEL_BASIC_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_basic_name[i]); break; case nsl_fit_model_peak: for(int i = 0; i < NSL_FIT_MODEL_PEAK_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_peak_name[i]); break; case nsl_fit_model_growth: for(int i = 0; i < NSL_FIT_MODEL_GROWTH_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_fit_model_growth_name[i]); break; case nsl_fit_model_distribution: { for(int i = 0; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) uiGeneralTab.cbModel->addItem(nsl_sf_stats_distribution_name[i]); // not-used items are disabled here const QStandardItemModel* model = qobject_cast(uiGeneralTab.cbModel->model()); for(int i = 1; i < NSL_SF_STATS_DISTRIBUTION_COUNT; i++) { // unused distributions if (i == nsl_sf_stats_levy_alpha_stable || i == nsl_sf_stats_levy_skew_alpha_stable || i == nsl_sf_stats_bernoulli) { QStandardItem* item = model->item(i); item->setFlags(item->flags() & ~(Qt::ItemIsSelectable|Qt::ItemIsEnabled)); } } break; } case nsl_fit_model_custom: uiGeneralTab.cbModel->addItem(i18n("Custom")); uiGeneralTab.cbModel->hide(); uiGeneralTab.lModel->hide(); } //show the fit-model for the currently selected default (first) fit-model m_fitData.modelType = 0; uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); modelTypeChanged(m_fitData.modelType); m_initializing = false; } /*! * called when the fit model type (polynomial, power, etc.) was changed. * Updates the model type dependent widgets in the general-tab and calls \c updateModelEquation() to update the preview pixmap. */ void XYFitCurveDock::modelTypeChanged(int index) { DEBUG("modelTypeChanged() type = " << index << ", initializing = " << m_initializing); // leave if there is no selection if(index == -1) return; unsigned int type = 0; bool custom = false; if (m_fitData.modelCategory == nsl_fit_model_custom) custom = true; else type = (unsigned int)index; m_fitData.modelType = type; uiGeneralTab.teEquation->setReadOnly(!custom); uiGeneralTab.tbFunctions->setVisible(custom); uiGeneralTab.tbConstants->setVisible(custom); // default settings uiGeneralTab.lDegree->setText(i18n("Degree")); switch (m_fitData.modelCategory) { case nsl_fit_model_basic: switch (type) { case nsl_fit_model_polynomial: case nsl_fit_model_fourier: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); - uiGeneralTab.sbDegree->setMaximum(9); + uiGeneralTab.sbDegree->setMaximum(10); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_power: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(2); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_exponential: uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(3); uiGeneralTab.sbDegree->setValue(1); break; default: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } break; case nsl_fit_model_peak: // all models support multiple peaks uiGeneralTab.lDegree->setText(i18n("Number of peaks")); uiGeneralTab.lDegree->setVisible(true); uiGeneralTab.sbDegree->setVisible(true); uiGeneralTab.sbDegree->setMaximum(9); uiGeneralTab.sbDegree->setValue(1); break; case nsl_fit_model_growth: case nsl_fit_model_distribution: case nsl_fit_model_custom: uiGeneralTab.lDegree->setVisible(false); uiGeneralTab.sbDegree->setVisible(false); } this->updateModelEquation(); } /*! * Show the preview pixmap of the fit model expression for the current model category and type. * Called when the model type or the degree of the model were changed. */ void XYFitCurveDock::updateModelEquation() { DEBUG("updateModelEquation() category = " << m_fitData.modelCategory << ", type = " << m_fitData.modelType); //this function can also be called when the value for the degree was changed -> update the fit data structure int degree = uiGeneralTab.sbDegree->value(); m_fitData.degree = degree; XYFitCurve::initFitData(m_fitData); // variables/parameter that are known QStringList vars = {"x"}; vars << m_fitData.paramNames; uiGeneralTab.teEquation->setVariables(vars); // set formula picture uiGeneralTab.lEquation->setText(QLatin1String("f(x) =")); QString file; switch (m_fitData.modelCategory) { case nsl_fit_model_basic: { // formula pic depends on degree QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = "4"; if ((nsl_fit_model_type_basic)m_fitData.modelType == nsl_fit_model_power && degree > 2) numSuffix = "2"; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_basic_pic_name[m_fitData.modelType]) + numSuffix + ".jpg"); break; } case nsl_fit_model_peak: { // formula pic depends on number of peaks QString numSuffix = QString::number(degree); if (degree > 4) numSuffix = "4"; file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_peak_pic_name[m_fitData.modelType]) + numSuffix + ".jpg"); break; } case nsl_fit_model_growth: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/fit_models/" + QString(nsl_fit_model_growth_pic_name[m_fitData.modelType]) + ".jpg"); break; case nsl_fit_model_distribution: file = QStandardPaths::locate(QStandardPaths::AppDataLocation, "pics/gsl_distributions/" + QString(nsl_sf_stats_distribution_pic_name[m_fitData.modelType]) + ".jpg"); // change label if (m_fitData.modelType == nsl_sf_stats_poisson) uiGeneralTab.lEquation->setText(QLatin1String("f(k)/A =")); else uiGeneralTab.lEquation->setText(QLatin1String("f(x)/A =")); break; case nsl_fit_model_custom: uiGeneralTab.teEquation->show(); uiGeneralTab.teEquation->clear(); uiGeneralTab.teEquation->insertPlainText(m_fitData.model); uiGeneralTab.lFuncPic->hide(); } if (m_fitData.modelCategory != nsl_fit_model_custom) { uiGeneralTab.lFuncPic->setPixmap(file); uiGeneralTab.lFuncPic->show(); uiGeneralTab.teEquation->hide(); } } void XYFitCurveDock::showConstants() { QMenu menu; ConstantsWidget constants(&menu); connect(&constants, SIGNAL(constantSelected(QString)), this, SLOT(insertConstant(QString))); connect(&constants, SIGNAL(constantSelected(QString)), &menu, SLOT(close())); connect(&constants, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&constants); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbConstants->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbConstants->mapToGlobal(pos)); } void XYFitCurveDock::showFunctions() { QMenu menu; FunctionsWidget functions(&menu); connect(&functions, SIGNAL(functionSelected(QString)), this, SLOT(insertFunction(QString))); connect(&functions, SIGNAL(functionSelected(QString)), &menu, SLOT(close())); connect(&functions, SIGNAL(canceled()), &menu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&functions); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.tbFunctions->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.tbFunctions->mapToGlobal(pos)); } void XYFitCurveDock::updateParameterList() { // use current model function m_fitData.model = uiGeneralTab.teEquation->toPlainText(); ExpressionParser* parser = ExpressionParser::getInstance(); QStringList vars; // variables that are known vars << "x"; //TODO: others? m_fitData.paramNames = m_fitData.paramNamesUtf8 = parser->getParameter(m_fitData.model, vars); // if number of parameter changed bool moreParameter = false; if (m_fitData.paramNames.size() > m_fitData.paramStartValues.size()) moreParameter = true; if (m_fitData.paramNames.size() != m_fitData.paramStartValues.size()) { m_fitData.paramStartValues.resize(m_fitData.paramNames.size()); m_fitData.paramFixed.resize(m_fitData.paramNames.size()); m_fitData.paramLowerLimits.resize(m_fitData.paramNames.size()); m_fitData.paramUpperLimits.resize(m_fitData.paramNames.size()); } if (moreParameter) { for (int i = m_fitData.paramStartValues.size() - 1; i < m_fitData.paramNames.size(); ++i) { m_fitData.paramStartValues[i] = 1.0; m_fitData.paramFixed[i] = false; m_fitData.paramLowerLimits[i] = -DBL_MAX; m_fitData.paramUpperLimits[i] = DBL_MAX; } } parametersChanged(); } void XYFitCurveDock::showParameters() { if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); QMenu menu; FitParametersWidget w(&menu, &m_fitData); connect(&w, SIGNAL(finished()), &menu, SLOT(close())); connect(&w, SIGNAL(parametersChanged()), this, SLOT(parametersChanged())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); menu.setMinimumWidth(w.width()); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbParameters->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.pbParameters->mapToGlobal(pos)); } /*! * called when parameter names and/or start values for the custom model were changed */ void XYFitCurveDock::parametersChanged() { //parameter names were (probably) changed -> set the new names in EquationTextEdit uiGeneralTab.teEquation->setVariables(m_fitData.paramNames); enableRecalculate(); } void XYFitCurveDock::showOptions() { QMenu menu; FitOptionsWidget w(&menu, &m_fitData); connect(&w, SIGNAL(finished()), &menu, SLOT(close())); connect(&w, SIGNAL(optionsChanged()), this, SLOT(enableRecalculate())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(&w); menu.addAction(widgetAction); QPoint pos(-menu.sizeHint().width() + uiGeneralTab.pbParameters->width(), -menu.sizeHint().height()); menu.exec(uiGeneralTab.pbOptions->mapToGlobal(pos)); } void XYFitCurveDock::insertFunction(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str + "(x)"); } void XYFitCurveDock::insertConstant(const QString& str) const { uiGeneralTab.teEquation->insertPlainText(str); } void XYFitCurveDock::recalculateClicked() { QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)); m_fitData.degree = uiGeneralTab.sbDegree->value(); if (m_fitData.modelCategory == nsl_fit_model_custom) updateParameterList(); for (XYCurve* curve: m_curvesList) dynamic_cast(curve)->setFitData(m_fitData); this->showFitResult(); uiGeneralTab.pbRecalculate->setEnabled(false); QApplication::restoreOverrideCursor(); } void XYFitCurveDock::enableRecalculate() const { if (m_initializing) return; //no fitting possible without the x- and y-data bool hasSourceData = false; if (m_fitCurve->dataSourceType() == XYCurve::DataSourceSpreadsheet) { AbstractAspect* aspectX = static_cast(cbXDataColumn->currentModelIndex().internalPointer()); AbstractAspect* aspectY = static_cast(cbYDataColumn->currentModelIndex().internalPointer()); hasSourceData = (aspectX != 0 && aspectY != 0); } else { hasSourceData = (m_fitCurve->dataSourceCurve() != NULL); } uiGeneralTab.pbRecalculate->setEnabled(hasSourceData); } /*! * show the fit result summary (with HTML tables) */ void XYFitCurveDock::showFitResultSummary(const XYFitCurve::FitResult& fitResult) { DEBUG("XYFitCurveDock::showFitResultSummary()"); QString str = ""; str += ""; str += ""; //str += i18n("iterations:") + ' ' + QString::number(fitResult.iterations) + "
"; //if (fitResult.elapsedTime > 1000) // str += i18n("calculation time: %1 s", fitResult.elapsedTime/1000) + "
"; // else //str += i18n("calculation time: %1 ms", fitResult.elapsedTime) + "
"; str += "
" + i18n("status:") + " " + fitResult.status + "
" + i18n("degrees of freedom:") + " " + QString::number(fitResult.dof) + "
"; if (!fitResult.valid) { uiGeneralTab.teResult->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } const int np = fitResult.paramValues.size(); const double rsquare = nsl_stats_rsquare(fitResult.sse,fitResult.sst); const double rsquareAdj = nsl_stats_rsquareAdj(rsquare, np, fitResult.dof); str += "

" + i18n("Parameters:") + ""; str += ""; str += ""; for (int i = 0; i < np; i++) { if (m_fitData.paramFixed.at(i)) str += ""; else str += ""; } str += "
" + i18n("Name") + " " + i18n("Value") + " " + i18n("Error") + " " + i18n("Error, %") + "
" + m_fitData.paramNamesUtf8.at(i) + " " + QString::number(fitResult.paramValues.at(i)) + "
" + m_fitData.paramNamesUtf8.at(i) + " " + QString::number(fitResult.paramValues.at(i)) + " " + QString::fromUtf8("\u00b1") + QString::number(fitResult.errorValues.at(i)) + " " + QString::number(100.*fitResult.errorValues.at(i)/fabs(fitResult.paramValues.at(i)), 'g', 3) + " %" + "
"; str += "

" + i18n("Goodness of fit:") + ""; str += ""; if (fitResult.dof != 0) { str += ""; str += ""; } str += "
" + i18n("reduced") + ' ' + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + " " + QString::number(fitResult.rms) + "
" + i18n("adj. coefficient of determination")+ " (R" + QString::fromUtf8("\u0304") + QString::fromUtf8("\u00b2") + ')' + " " + QString::number(rsquareAdj, 'g', 15) + "
"; uiGeneralTab.teResult->setText(str); } /*! * show the fit result log (plain text) */ void XYFitCurveDock::showFitResultLog(const XYFitCurve::FitResult& fitResult) { DEBUG("XYFitCurveDock::showFitResultLog()"); QString str = i18n("status:") + ' ' + fitResult.status + "
"; str += i18n("iterations:") + ' ' + QString::number(fitResult.iterations) + "
"; str += i18n("tolerance:") + ' ' + QString::number(m_fitData.eps) + "
"; if (fitResult.elapsedTime > 1000) str += i18n("calculation time: %1 s", fitResult.elapsedTime/1000) + "
"; else str += i18n("calculation time: %1 ms", fitResult.elapsedTime) + "
"; str += i18n("degrees of freedom:") + ' ' + QString::number(fitResult.dof) + "
"; str += i18n("number of parameters:") + ' ' + QString::number(fitResult.paramValues.size()) + "
"; str += i18n("X range:") + ' ' + QString::number(uiGeneralTab.sbMin->value()) + " .. " + QString::number(uiGeneralTab.sbMax->value()) + "
"; if (!fitResult.valid) { uiGeneralTab.teLog->setText(str); return; //result is not valid, there was an error which is shown in the status-string, nothing to show more. } const int np = fitResult.paramValues.size(); const double rsquare = nsl_stats_rsquare(fitResult.sse,fitResult.sst); const double rsquareAdj = nsl_stats_rsquareAdj(rsquare, np, fitResult.dof); // Parameter str += "
" + i18n("Parameters:") + "
"; for (int i = 0; i < np; i++) { if (m_fitData.paramFixed.at(i)) str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + "
"; else { str += m_fitData.paramNamesUtf8.at(i) + QString(" = ") + QString::number(fitResult.paramValues.at(i)) + QString::fromUtf8("\u00b1") + QString::number(fitResult.errorValues.at(i)) + " (" + QString::number(100.*fitResult.errorValues.at(i)/fabs(fitResult.paramValues.at(i)), 'g', 3) + " %)
"; const double t = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); const double p = nsl_stats_tdist_p(t, fitResult.dof); const double margin = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); str += " (" + i18n("t statistic:") + ' ' + QString::number(t, 'g', 3) + ", " + i18n("p value:") + ' ' + QString::number(p, 'g', 3) + ", " + i18n("conf. interval:") + ' ' + QString::number(fitResult.paramValues.at(i) - margin) + " .. " + QString::number(fitResult.paramValues.at(i) + margin) + ")
"; } } // Goodness of fit str += "
" + i18n("Goodness of fit:") + "
"; str += i18n("sum of squared residuals") + " (" + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + "): " + QString::number(fitResult.sse) + "
"; if (fitResult.dof != 0) { str += i18n("reduced") + ' ' + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ": " + QString::number(fitResult.rms) + "
"; str += i18n("root mean square error") + " (RMSE): " + QString::number(fitResult.rsd) + "
"; str += i18n("coefficient of determination") + " (R" + QString::fromUtf8("\u00b2") + "): " + QString::number(rsquare, 'g', 15) + "
"; str += i18n("adj. coefficient of determination")+ " (R" + QString::fromUtf8("\u0304") + QString::fromUtf8("\u00b2") + "): " + QString::number(rsquareAdj, 'g', 15) + "

"; double p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); str += i18n("P > ") + QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2") + ": " + QString::number(p, 'g', 3) + "
"; const double F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms); str += i18n("F statistic") + ": " + QString::number(F, 'g', 3) + "
"; p = nsl_stats_fdist_p(F, np, fitResult.dof); str += i18n("P > F") + ": " + QString::number(p, 'g', 3) + "
"; } str += i18n("mean absolute error:") + ' ' + QString::number(fitResult.mae) + "

"; // show all iterations str += "" + i18n("Iterations:") + "
"; for (const auto &s: m_fitData.paramNamesUtf8) str += s + ' '; str += QString::fromUtf8("\u03c7") + QString::fromUtf8("\u00b2"); const QStringList iterations = fitResult.solverOutput.split(';'); for (const auto &s: iterations) str += "
" + s; uiGeneralTab.teLog->setText(str); } /*! * show the result and details of fit */ void XYFitCurveDock::showFitResult() { DEBUG("XYFitCurveDock::showFitResult()"); const XYFitCurve::FitResult& fitResult = m_fitCurve->fitResult(); showFitResultSummary(fitResult); showFitResultLog(fitResult); if (!fitResult.available) { DEBUG("fit result not available"); uiGeneralTab.teResult->clear(); uiGeneralTab.teLog->clear(); return; } // General uiGeneralTab.twGeneral->item(0, 1)->setText(fitResult.status); if (!fitResult.valid) { DEBUG("fit result not valid"); return; } uiGeneralTab.twGeneral->item(1, 1)->setText(QString::number(fitResult.iterations)); uiGeneralTab.twGeneral->item(2, 1)->setText(QString::number(m_fitData.eps)); if (fitResult.elapsedTime > 1000) uiGeneralTab.twGeneral->item(3, 1)->setText(QString::number(fitResult.elapsedTime/1000) + " s"); else uiGeneralTab.twGeneral->item(3, 1)->setText(QString::number(fitResult.elapsedTime) + " ms"); uiGeneralTab.twGeneral->item(4, 1)->setText(QString::number(fitResult.dof)); uiGeneralTab.twGeneral->item(5, 1)->setText(QString::number(fitResult.paramValues.size())); uiGeneralTab.twGeneral->item(6, 1)->setText(QString::number(uiGeneralTab.sbMin->value()) + " .. " + QString::number(uiGeneralTab.sbMax->value()) ); // Parameters const int np = m_fitData.paramNames.size(); uiGeneralTab.twParameters->setRowCount(np); QStringList headerLabels; headerLabels << i18n("Name") << i18n("Value") << i18n("Error") << i18n("Error, %") << i18n("t statistic") << QLatin1String("P > |t|") << i18n("Conf. Interval"); uiGeneralTab.twParameters->setHorizontalHeaderLabels(headerLabels); for (int i = 0; i < np; i++) { const double paramValue = fitResult.paramValues.at(i); const double errorValue = fitResult.errorValues.at(i); QTableWidgetItem* item = new QTableWidgetItem(m_fitData.paramNamesUtf8.at(i)); uiGeneralTab.twParameters->setItem(i, 0, item); item = new QTableWidgetItem(QString::number(paramValue)); uiGeneralTab.twParameters->setItem(i, 1, item); if (!m_fitData.paramFixed.at(i)) { item = new QTableWidgetItem(QString::number(errorValue, 'g', 6)); uiGeneralTab.twParameters->setItem(i, 2, item); item = new QTableWidgetItem(QString::number(100.*errorValue/fabs(paramValue), 'g', 3)); uiGeneralTab.twParameters->setItem(i, 3, item); // t values const double t = nsl_stats_tdist_t(paramValue, errorValue); item = new QTableWidgetItem(QString::number(t, 'g', 3)); uiGeneralTab.twParameters->setItem(i, 4, item); // p values const double p = nsl_stats_tdist_p(t, fitResult.dof); item = new QTableWidgetItem(QString::number(p, 'g', 3)); // color p values depending on value //TODO: these hard coded colors don't always look well on dark themes (blue on black, etc. is hard to read) if (p > 0.05) item->setTextColor(Qt::red); else if (p > 0.01) item->setTextColor(Qt::darkGreen); else if (p > 0.001) item->setTextColor(Qt::darkCyan); else if (p > 0.0001) item->setTextColor(Qt::blue); else item->setTextColor(Qt::darkBlue); uiGeneralTab.twParameters->setItem(i, 5, item); // Conf. interval const double margin = nsl_stats_tdist_margin(0.05, fitResult.dof, errorValue); item = new QTableWidgetItem(QString::number(paramValue - margin) + QLatin1String(" .. ") + QString::number(paramValue + margin)); uiGeneralTab.twParameters->setItem(i, 6, item); } } // Goodness of fit uiGeneralTab.twGoodness->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); uiGeneralTab.twGoodness->item(0, 2)->setText(QString::number(fitResult.sse)); if (fitResult.dof != 0) { uiGeneralTab.twGoodness->item(1, 2)->setText(QString::number(fitResult.rms)); uiGeneralTab.twGoodness->item(2, 2)->setText(QString::number(fitResult.rsd)); const double rsquare = nsl_stats_rsquare(fitResult.sse,fitResult.sst); const double rsquareAdj = nsl_stats_rsquareAdj(rsquare, np, fitResult.dof); uiGeneralTab.twGoodness->item(3, 2)->setText(QString::number(rsquare, 'g', 15)); uiGeneralTab.twGoodness->item(4, 2)->setText(QString::number(rsquareAdj, 'g', 15)); // chi^2 and F test p-values double p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); uiGeneralTab.twGoodness->item(5, 2)->setText(QString::number(p, 'g', 3)); double F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms); uiGeneralTab.twGoodness->item(6, 2)->setText(QString::number(F, 'g', 3)); p = nsl_stats_fdist_p(F, np, fitResult.dof); uiGeneralTab.twGoodness->item(7, 2)->setText(QString::number(p, 'g', 3)); } uiGeneralTab.twGoodness->item(8, 2)->setText(QString::number(fitResult.mae)); //resize the table headers to fit the new content uiGeneralTab.twGeneral->resizeColumnsToContents(); uiGeneralTab.twParameters->resizeColumnsToContents(); //twGoodness doesn't have any header -> resize sections uiGeneralTab.twGoodness->resizeColumnToContents(0); uiGeneralTab.twGoodness->resizeColumnToContents(1); uiGeneralTab.twGoodness->resizeColumnToContents(2); //enable the "recalculate"-button if the source data was changed since the last fit uiGeneralTab.pbRecalculate->setEnabled(m_fitCurve->isSourceDataChangedSinceLastRecalc()); } //************************************************************* //*********** SLOTs for changes triggered in XYCurve ********** //************************************************************* //General-Tab void XYFitCurveDock::curveDescriptionChanged(const AbstractAspect* aspect) { if (m_curve != aspect) return; m_initializing = true; if (aspect->name() != uiGeneralTab.leName->text()) { uiGeneralTab.leName->setText(aspect->name()); } else if (aspect->comment() != uiGeneralTab.leComment->text()) { uiGeneralTab.leComment->setText(aspect->comment()); } m_initializing = false; } void XYFitCurveDock::curveDataSourceTypeChanged(XYCurve::DataSourceType type) { m_initializing = true; uiGeneralTab.cbDataSourceType->setCurrentIndex(type); m_initializing = false; } void XYFitCurveDock::curveDataSourceCurveChanged(const XYCurve* curve) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbDataSourceCurve, curve); m_initializing = false; } void XYFitCurveDock::curveXDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveYDataColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYDataColumn, column); m_initializing = false; } void XYFitCurveDock::curveXErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbXErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveYErrorColumnChanged(const AbstractColumn* column) { m_initializing = true; XYCurveDock::setModelIndexFromAspect(cbYErrorColumn, column); m_initializing = false; } void XYFitCurveDock::curveFitDataChanged(const XYFitCurve::FitData& data) { m_initializing = true; m_fitData = data; if (m_fitData.modelCategory == nsl_fit_model_custom) uiGeneralTab.teEquation->setPlainText(m_fitData.model); else uiGeneralTab.cbModel->setCurrentIndex(m_fitData.modelType); uiGeneralTab.sbDegree->setValue(m_fitData.degree); this->showFitResult(); m_initializing = false; } void XYFitCurveDock::dataChanged() { this->enableRecalculate(); } diff --git a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp index e29815c5c..4fc4376a2 100644 --- a/src/kdefrontend/spreadsheet/PlotDataDialog.cpp +++ b/src/kdefrontend/spreadsheet/PlotDataDialog.cpp @@ -1,518 +1,519 @@ /*************************************************************************** File : PlotDataDialog.cpp Project : LabPlot Description : Dialog for generating plots for the spreadsheet data -------------------------------------------------------------------- Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "PlotDataDialog.h" #include "backend/core/AspectTreeModel.h" #include "backend/core/Project.h" #include "backend/core/column/Column.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/plots/cartesian/XYDataReductionCurve.h" #include "backend/worksheet/plots/cartesian/XYDifferentiationCurve.h" #include "backend/worksheet/plots/cartesian/XYIntegrationCurve.h" #include "backend/worksheet/plots/cartesian/XYInterpolationCurve.h" #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h" #include "backend/worksheet/plots/cartesian/XYFitCurve.h" #include "backend/worksheet/plots/cartesian/XYFourierFilterCurve.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "commonfrontend/spreadsheet/SpreadsheetView.h" #include "commonfrontend/widgets/TreeViewComboBox.h" #include #include #include #include #include #include #include "ui_plotdatawidget.h" /*! \class PlotDataDialog \brief Dialog for generating plots for the spreadsheet data. \ingroup kdefrontend */ PlotDataDialog::PlotDataDialog(Spreadsheet* s, QWidget* parent, Qt::WFlags fl) : QDialog(parent, fl), ui(new Ui::PlotDataWidget()), m_spreadsheet(s), m_plotsModel(new AspectTreeModel(m_spreadsheet->project())), m_worksheetsModel(new AspectTreeModel(m_spreadsheet->project())), m_analysisMode(false) { setAttribute(Qt::WA_DeleteOnClose); setWindowTitle(i18n("Plot spreadsheet data")); setWindowIcon(QIcon::fromTheme("office-chart-line")); QWidget* mainWidget = new QWidget(this); ui->setupUi(mainWidget); QDialogButtonBox *buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); m_okButton = buttonBox->button(QDialogButtonBox::Ok); m_okButton->setDefault(true); m_okButton->setToolTip(i18n("Plot the selected data")); m_okButton->setText(i18n("&Plot")); QVBoxLayout* layout = new QVBoxLayout(this); layout->addWidget(mainWidget); layout->addWidget(buttonBox); setLayout(layout); //create combox boxes for the existing plots and worksheets QGridLayout* gridLayout = dynamic_cast(ui->gbPlotPlacement->layout()); cbExistingPlots = new TreeViewComboBox(ui->gbPlotPlacement); cbExistingPlots->setMinimumWidth(250);//TODO: use proper sizeHint in TreeViewComboBox gridLayout->addWidget(cbExistingPlots, 0, 1, 1, 1); cbExistingWorksheets = new TreeViewComboBox(ui->gbPlotPlacement); cbExistingWorksheets->setSizePolicy(QSizePolicy(QSizePolicy::Expanding, QSizePolicy::Preferred)); gridLayout->addWidget(cbExistingWorksheets, 1, 1, 1, 1); QList list; list<<"Folder"<<"Worksheet"<<"CartesianPlot"; cbExistingPlots->setTopLevelClasses(list); list.clear(); list<<"CartesianPlot"; m_plotsModel->setSelectableAspects(list); cbExistingPlots->setModel(m_plotsModel); list.clear(); list<<"Folder"<<"Worksheet"; cbExistingWorksheets->setTopLevelClasses(list); list.clear(); list<<"Worksheet"; m_worksheetsModel->setSelectableAspects(list); cbExistingWorksheets->setModel(m_worksheetsModel); //hide the check box for creation of original data, only shown if analysis curves are to be created ui->chkCreateDataCurve->setVisible(false); //SIGNALs/SLOTs connect(buttonBox, SIGNAL(accepted()), this, SLOT(plot())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->rbCurvePlacement1, SIGNAL(toggled(bool)), this, SLOT(curvePlacementChanged())); connect(ui->rbCurvePlacement2, SIGNAL(toggled(bool)), this, SLOT(curvePlacementChanged())); connect(ui->rbPlotPlacement1, SIGNAL(toggled(bool)), this, SLOT(plotPlacementChanged())); connect(ui->rbPlotPlacement2, SIGNAL(toggled(bool)), this, SLOT(plotPlacementChanged())); connect(ui->rbPlotPlacement3, SIGNAL(toggled(bool)), this, SLOT(plotPlacementChanged())); connect(cbExistingPlots, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(checkOkButton())); connect(cbExistingWorksheets, SIGNAL(currentModelIndexChanged(QModelIndex)), this, SLOT(checkOkButton())); QTimer::singleShot(0, this, &PlotDataDialog::loadSettings); } void PlotDataDialog::loadSettings() { //restore saved settings if available QApplication::processEvents(QEventLoop::AllEvents, 0); const KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); if (conf.exists()) { int index = conf.readEntry("CurvePlacement", 0); if (index == 2) ui->rbCurvePlacement2->setChecked(true); index = conf.readEntry("PlotPlacement", 0); if (index == 2) ui->rbPlotPlacement2->setChecked(true); if (index == 3) ui->rbPlotPlacement3->setChecked(true); plotPlacementChanged(); KWindowConfig::restoreWindowSize(windowHandle(), conf); } else resize( QSize(500,0).expandedTo(minimumSize()) ); this->processColumns(); } PlotDataDialog::~PlotDataDialog() { //save current settings KConfigGroup conf(KSharedConfig::openConfig(), "PlotDataDialog"); int index = 0; if (ui->rbCurvePlacement1->isChecked()) index = 1; if (ui->rbCurvePlacement2->isChecked()) index = 2; conf.writeEntry("CurvePlacement", index); if (ui->rbPlotPlacement1->isChecked()) index = 1; if (ui->rbPlotPlacement2->isChecked()) index = 2; if (ui->rbPlotPlacement3->isChecked()) index = 3; conf.writeEntry("PlotPlacement", index); KWindowConfig::saveWindowSize(windowHandle(), conf); delete m_plotsModel; delete m_worksheetsModel; } void PlotDataDialog::setAnalysisAction(AnalysisAction action) { m_analysisAction = action; m_analysisMode = true; ui->chkCreateDataCurve->setVisible(true); } void PlotDataDialog::processColumns() { //columns to plot SpreadsheetView* view = reinterpret_cast(m_spreadsheet->view()); m_columns = view->selectedColumns(true); //use all spreadsheet columns if no columns are selected if (!m_columns.size()) { m_columns = m_spreadsheet->children(); //disable everything if the spreadsheet doesn't have any columns if (!m_columns.size()) { ui->gbData->setEnabled(false); ui->gbCurvePlacement->setEnabled(false); ui->gbPlotPlacement->setEnabled(false); return; } } m_columnComboBoxes << ui->cbXColumn; m_columnComboBoxes << ui->cbYColumn; //ui-widget only has one combobox for the y-data -> add additional comboboxes dynamically if required if (m_columns.size()>2) { QGridLayout* gridLayout = dynamic_cast(ui->scrollAreaYColumns->widget()->layout()); for (int i = 2; i < m_columns.size(); ++i) { QLabel* label = new QLabel(i18n("Y-data")); QComboBox* comboBox = new QComboBox(); gridLayout->addWidget(label, i+1, 0, 1, 1); gridLayout->addWidget(comboBox, i+1, 2, 1, 1); m_columnComboBoxes << comboBox; } } else { //two columns provided, only one curve is possible -> hide the curve placement options ui->rbCurvePlacement1->setChecked(true); ui->gbCurvePlacement->hide(); ui->gbPlotPlacement->setTitle(i18n("Add curve to")); } //determine the column names and the name of the first column having "X" as the plot designation QList columnNames; QString xColumnName; for(const Column* column : m_columns) { columnNames << column->name(); if (xColumnName.isEmpty() && column->plotDesignation() == AbstractColumn::X) xColumnName = column->name(); } //show all selected/available column names in the data comboboxes for(QComboBox* const comboBox : m_columnComboBoxes) comboBox->addItems(columnNames); if (!xColumnName.isEmpty()) { //show in the X-data combobox the first column having X as the plot designation ui->cbXColumn->setCurrentIndex(ui->cbXColumn->findText(xColumnName)); //for the remaining columns, show the names in the comboboxes for the Y-data //TODO: handle columns with error-designations int yColumnIndex = 1; //the index of the first Y-data comboBox in m_columnComboBoxes for(const QString& name : columnNames) { if (name != xColumnName) { QComboBox* comboBox = m_columnComboBoxes[yColumnIndex]; comboBox->setCurrentIndex(comboBox->findText(name)); yColumnIndex++; } } } else { //no column with "x plot designation" is selected, simply show all columns in the order they were selected. //first selected column will serve as the x-column. int yColumnIndex = 0; for(const QString& name : columnNames) { QComboBox* comboBox = m_columnComboBoxes[yColumnIndex]; comboBox->setCurrentIndex(comboBox->findText(name)); yColumnIndex++; } } } void PlotDataDialog::plot() { DEBUG("PlotDataDialog::plot()"); WAIT_CURSOR; if (ui->rbPlotPlacement1->isChecked()) { //add curves to an existing plot AbstractAspect* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); CartesianPlot* plot = dynamic_cast(aspect); plot->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); addCurvesToPlot(plot); plot->endMacro(); } else if (ui->rbPlotPlacement2->isChecked()) { //add curves to a new plot in an existing worksheet AbstractAspect* aspect = static_cast(cbExistingWorksheets->currentModelIndex().internalPointer()); Worksheet* worksheet = dynamic_cast(aspect); worksheet->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); if (ui->rbCurvePlacement1->isChecked()) { //all curves in one plot CartesianPlot* plot = new CartesianPlot( i18n("Plot data from %1", m_spreadsheet->name()) ); plot->initDefault(CartesianPlot::FourAxes); //set the x-axis names const QString& xColumnName = ui->cbXColumn->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal) { axis->title()->setText(xColumnName); break; } } worksheet->addChild(plot); addCurvesToPlot(plot); } else { //one plot per curve addCurvesToPlots(worksheet); } worksheet->endMacro(); } else { //add curves to a new plot(s) in a new worksheet Project* project = m_spreadsheet->project(); project->beginMacro( i18n("Plot data from %1", m_spreadsheet->name()) ); Worksheet* worksheet = new Worksheet(0, i18n("Plot data from %1", m_spreadsheet->name())); project->addChild(worksheet); if (ui->rbCurvePlacement1->isChecked()) { //all curves in one plot CartesianPlot* plot = new CartesianPlot( i18n("Plot data from %1", m_spreadsheet->name()) ); plot->initDefault(CartesianPlot::FourAxes); //set the x-axis names const QString& xColumnName = ui->cbXColumn->currentText(); for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal) { axis->title()->setText(xColumnName); break; } } worksheet->addChild(plot); addCurvesToPlot(plot); } else { //one plot per curve addCurvesToPlots(worksheet); } project->endMacro(); } RESET_CURSOR; } Column* PlotDataDialog::columnFromName(const QString& name) const { for(auto* column : m_columns) { if (column->name() == name) return column; } return 0; } void PlotDataDialog::addCurvesToPlot(CartesianPlot* plot) const { QApplication::processEvents(QEventLoop::AllEvents, 100); Column* xColumn = columnFromName(ui->cbXColumn->currentText()); for (int i = 1; i < m_columnComboBoxes.size(); ++i) { QComboBox* comboBox = m_columnComboBoxes[i]; const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); addCurve(name, xColumn, yColumn, plot); } plot->scaleAuto(); } void PlotDataDialog::addCurvesToPlots(Worksheet* worksheet) const { QApplication::processEvents(QEventLoop::AllEvents, 100); worksheet->setSuppressLayoutUpdate(true); const QString& xColumnName = ui->cbXColumn->currentText(); Column* xColumn = columnFromName(xColumnName); for (int i = 1; i < m_columnComboBoxes.size(); ++i) { QComboBox* comboBox = m_columnComboBoxes[i]; const QString& name = comboBox->currentText(); Column* yColumn = columnFromName(name); CartesianPlot* plot = new CartesianPlot(i18n("Plot %1", name)); plot->initDefault(CartesianPlot::FourAxes); //set the axis names bool xSet = false; bool ySet = false; for (auto axis : plot->children()) { if (axis->orientation() == Axis::AxisHorizontal && !xSet) { axis->title()->setText(xColumnName); xSet = true; } else if (axis->orientation() == Axis::AxisVertical && !ySet) { axis->title()->setText(name); ySet = true; } } worksheet->addChild(plot); addCurve(name, xColumn, yColumn, plot); plot->scaleAuto(); } worksheet->setSuppressLayoutUpdate(false); worksheet->updateLayout(); } + /*! * helper function that does the actual creation of the curve and adding it as child to the \c plot. */ void PlotDataDialog::addCurve(const QString& name, Column* xColumn, Column* yColumn, CartesianPlot* plot) const { DEBUG("PlotDataDialog::addCurve()"); if (!m_analysisMode) { XYCurve* curve = new XYCurve(name); curve->setXColumn(xColumn); curve->setYColumn(yColumn); plot->addChild(curve); } else { bool createDataCurve = ui->chkCreateDataCurve->isChecked(); if (createDataCurve) { XYCurve* curve = new XYCurve(name); curve->setXColumn(xColumn); curve->setYColumn(yColumn); plot->addChild(curve); } //TODO: introduce a base class for all analysis curves and refactor this part switch (m_analysisAction) { case DataReduction: { XYDataReductionCurve* analysisCurve = new XYDataReductionCurve(i18n("Reduction of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Differentiation: { XYDifferentiationCurve* analysisCurve = new XYDifferentiationCurve(i18n("Derivative of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Integration: { XYIntegrationCurve* analysisCurve = new XYIntegrationCurve(i18n("Integral of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Interpolation: { XYInterpolationCurve* analysisCurve = new XYInterpolationCurve(i18n("Interpolation of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case Smoothing: { XYSmoothCurve* analysisCurve = new XYSmoothCurve(i18n("Smoothing of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case FitLinear: case FitPower: case FitExp1: case FitExp2: case FitInvExp: case FitGauss: case FitCauchyLorentz: case FitTan: case FitTanh: case FitErrFunc: case FitCustom: { XYFitCurve* analysisCurve = new XYFitCurve(i18n("Fit to '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->initFitData(m_analysisAction); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } case FourierFilter: { XYFourierFilterCurve* analysisCurve = new XYFourierFilterCurve(i18n("Fourier Filter of '%1'", name)); analysisCurve->setXDataColumn(xColumn); analysisCurve->setYDataColumn(yColumn); analysisCurve->recalculate(); plot->addChild(analysisCurve); break; } } } } //################################################################ //########################## Slots ############################### //################################################################ void PlotDataDialog::curvePlacementChanged() { if (ui->rbCurvePlacement1->isChecked()) { ui->rbPlotPlacement1->setEnabled(true); ui->rbPlotPlacement2->setText(i18n("new plot in an existing worksheet")); ui->rbPlotPlacement3->setText(i18n("new plot in a new worksheet")); } else { ui->rbPlotPlacement1->setEnabled(false); if (ui->rbPlotPlacement1->isChecked()) ui->rbPlotPlacement2->setChecked(true); ui->rbPlotPlacement2->setText(i18n("new plots in an existing worksheet")); ui->rbPlotPlacement3->setText(i18n("new plots in a new worksheet")); } } void PlotDataDialog::plotPlacementChanged() { if (ui->rbPlotPlacement1->isChecked()) { cbExistingPlots->setEnabled(true); cbExistingWorksheets->setEnabled(false); } else if (ui->rbPlotPlacement2->isChecked()) { cbExistingPlots->setEnabled(false); cbExistingWorksheets->setEnabled(true); } else { cbExistingPlots->setEnabled(false); cbExistingWorksheets->setEnabled(false); } checkOkButton(); } void PlotDataDialog::checkOkButton() { bool enable = false; if (ui->rbPlotPlacement1->isChecked()) { AbstractAspect* aspect = static_cast(cbExistingPlots->currentModelIndex().internalPointer()); enable = (aspect!=NULL); } else if (ui->rbPlotPlacement2->isChecked()) { AbstractAspect* aspect = static_cast(cbExistingWorksheets->currentModelIndex().internalPointer()); enable = (aspect!=NULL); } else enable = true; m_okButton->setEnabled(enable); }