File indexing completed on 2025-01-26 03:34:13

0001 /*
0002     File                 : QQPlot.cpp
0003     Project              : LabPlot
0004     Description          : QQPlot
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2023 Alexander Semke <alexander.semke@web.de>
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 /*!
0011   \class QQPlot
0012   \brief
0013 
0014   \ingroup worksheet
0015   */
0016 #include "QQPlot.h"
0017 #include "QQPlotPrivate.h"
0018 #include "backend/core/column/Column.h"
0019 #include "backend/lib/XmlStreamReader.h"
0020 #include "backend/lib/commandtemplates.h"
0021 #include "backend/lib/macrosCurve.h"
0022 #include "backend/lib/trace.h"
0023 #include "backend/worksheet/Background.h"
0024 #include "backend/worksheet/Line.h"
0025 #include "backend/worksheet/plots/cartesian/Symbol.h"
0026 
0027 #include <gsl/gsl_cdf.h>
0028 #include <gsl/gsl_statistics.h>
0029 
0030 #include <QMenu>
0031 #include <QPainter>
0032 
0033 #include <KConfig>
0034 #include <KConfigGroup>
0035 #include <KLocalizedString>
0036 #include <KSharedConfig>
0037 
0038 CURVE_COLUMN_CONNECT(QQPlot, Data, data, recalc)
0039 
0040 QQPlot::QQPlot(const QString& name)
0041     : Plot(name, new QQPlotPrivate(this), AspectType::QQPlot) {
0042     init();
0043 }
0044 
0045 QQPlot::QQPlot(const QString& name, QQPlotPrivate* dd)
0046     : Plot(name, dd, AspectType::QQPlot) {
0047     init();
0048 }
0049 
0050 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0051 // and is deleted during the cleanup in QGraphicsScene
0052 QQPlot::~QQPlot() = default;
0053 
0054 void QQPlot::init() {
0055     Q_D(QQPlot);
0056 
0057     // setUndoAware(false);
0058 
0059     KConfig config;
0060     KConfigGroup group = config.group(QStringLiteral("QQPlot"));
0061     // reference curve - line conneting two central quantiles Q1 and Q3
0062     d->referenceCurve = new XYCurve(QString());
0063     d->referenceCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0064     d->referenceCurve->setHidden(true);
0065     d->referenceCurve->graphicsItem()->setParentItem(d);
0066     d->referenceCurve->line()->init(group);
0067     d->referenceCurve->line()->setStyle(Qt::SolidLine);
0068     d->referenceCurve->symbol()->setStyle(Symbol::Style::NoSymbols);
0069     d->referenceCurve->background()->setPosition(Background::Position::No);
0070 
0071     // columns holding the data for the reference curve
0072     d->xReferenceColumn = new Column(QStringLiteral("xReference"));
0073     d->xReferenceColumn->setHidden(true);
0074     d->xReferenceColumn->setUndoAware(false);
0075     addChildFast(d->xReferenceColumn);
0076     d->referenceCurve->setXColumn(d->xReferenceColumn);
0077 
0078     d->yReferenceColumn = new Column(QStringLiteral("yReference"));
0079     d->yReferenceColumn->setHidden(true);
0080     d->yReferenceColumn->setUndoAware(false);
0081     addChildFast(d->yReferenceColumn);
0082     d->referenceCurve->setYColumn(d->yReferenceColumn);
0083 
0084     // percentiles curve
0085     d->percentilesCurve = new XYCurve(QString());
0086     d->percentilesCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0087     d->percentilesCurve->setHidden(true);
0088     d->percentilesCurve->graphicsItem()->setParentItem(d);
0089     d->percentilesCurve->symbol()->init(group);
0090     d->percentilesCurve->symbol()->setStyle(Symbol::Style::Circle);
0091     d->percentilesCurve->line()->setStyle(Qt::NoPen);
0092     d->percentilesCurve->background()->setPosition(Background::Position::No);
0093 
0094     // columns holding the data for the percentiles curve
0095     d->xPercentilesColumn = new Column(QStringLiteral("xPercentiles"));
0096     d->xPercentilesColumn->setHidden(true);
0097     d->xPercentilesColumn->setUndoAware(false);
0098     addChildFast(d->xPercentilesColumn);
0099     d->percentilesCurve->setXColumn(d->xPercentilesColumn);
0100 
0101     d->yPercentilesColumn = new Column(QStringLiteral("yPercentiles"));
0102     d->yPercentilesColumn->setHidden(true);
0103     d->yPercentilesColumn->setUndoAware(false);
0104     addChildFast(d->yPercentilesColumn);
0105     d->percentilesCurve->setYColumn(d->yPercentilesColumn);
0106 
0107     d->updateDistribution();
0108 
0109     // synchronize the names of the internal XYCurves with the name of the current q-q plot
0110     // so we have the same name shown on the undo stack
0111     connect(this, &AbstractAspect::aspectDescriptionChanged, [this] {
0112         Q_D(QQPlot);
0113         d->referenceCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0114         d->percentilesCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0115     });
0116 }
0117 
0118 void QQPlot::finalizeAdd() {
0119     Q_D(QQPlot);
0120     WorksheetElement::finalizeAdd();
0121     addChildFast(d->referenceCurve);
0122     addChildFast(d->percentilesCurve);
0123 }
0124 
0125 /*!
0126   Returns an icon to be used in the project explorer.
0127   */
0128 QIcon QQPlot::icon() const {
0129     return QIcon::fromTheme(QStringLiteral("view-object-histogram-linear"));
0130 }
0131 
0132 void QQPlot::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) {
0133     // TODO
0134 }
0135 
0136 void QQPlot::setVisible(bool on) {
0137     Q_D(QQPlot);
0138     beginMacro(on ? i18n("%1: set visible", name()) : i18n("%1: set invisible", name()));
0139     d->referenceCurve->setVisible(on);
0140     d->percentilesCurve->setVisible(on);
0141     WorksheetElement::setVisible(on);
0142     endMacro();
0143 }
0144 
0145 // ##############################################################################
0146 // ##########################  getter methods  ##################################
0147 // ##############################################################################
0148 //  general
0149 BASIC_SHARED_D_READER_IMPL(QQPlot, const AbstractColumn*, dataColumn, dataColumn)
0150 BASIC_SHARED_D_READER_IMPL(QQPlot, QString, dataColumnPath, dataColumnPath)
0151 BASIC_SHARED_D_READER_IMPL(QQPlot, nsl_sf_stats_distribution, distribution, distribution)
0152 
0153 // line
0154 Line* QQPlot::line() const {
0155     Q_D(const QQPlot);
0156     return d->referenceCurve->line();
0157 }
0158 
0159 // symbols
0160 Symbol* QQPlot::symbol() const {
0161     Q_D(const QQPlot);
0162     return d->percentilesCurve->symbol();
0163 }
0164 
0165 bool QQPlot::minMax(const Dimension dim, const Range<int>& indexRange, Range<double>& r, bool /* includeErrorBars */) const {
0166     Q_D(const QQPlot);
0167 
0168     switch (dim) {
0169     case Dimension::X:
0170         return d->referenceCurve->minMax(dim, indexRange, r, false);
0171     case Dimension::Y: {
0172         Range referenceRange(r);
0173         Range percentilesRange(r);
0174         bool rc = true;
0175         rc = d->referenceCurve->minMax(dim, indexRange, referenceRange, false);
0176         if (!rc)
0177             return false;
0178 
0179         rc = d->percentilesCurve->minMax(dim, indexRange, percentilesRange, false);
0180         if (!rc)
0181             return false;
0182 
0183         r.setStart(std::min(referenceRange.start(), percentilesRange.start()));
0184         r.setEnd(std::max(referenceRange.end(), percentilesRange.end()));
0185         return true;
0186     }
0187     }
0188     return false;
0189 }
0190 
0191 double QQPlot::minimum(const Dimension dim) const {
0192     Q_D(const QQPlot);
0193     switch (dim) {
0194     case Dimension::X:
0195         return d->referenceCurve->minimum(dim);
0196     case Dimension::Y:
0197         return std::min(d->referenceCurve->minimum(dim), d->percentilesCurve->minimum(dim));
0198     }
0199     return NAN;
0200 }
0201 
0202 double QQPlot::maximum(const Dimension dim) const {
0203     Q_D(const QQPlot);
0204     switch (dim) {
0205     case Dimension::X:
0206         return d->referenceCurve->maximum(dim);
0207     case Dimension::Y:
0208         return std::max(d->referenceCurve->maximum(dim), d->percentilesCurve->maximum(dim));
0209     }
0210     return NAN;
0211 }
0212 
0213 bool QQPlot::hasData() const {
0214     Q_D(const QQPlot);
0215     return (d->dataColumn != nullptr);
0216 }
0217 
0218 bool QQPlot::usingColumn(const Column* column) const {
0219     Q_D(const QQPlot);
0220     return (d->dataColumn == column);
0221 }
0222 
0223 void QQPlot::updateColumnDependencies(const AbstractColumn* column) {
0224     Q_D(QQPlot);
0225     const QString& columnPath = column->path();
0226 
0227     if (d->dataColumn == column) // the column is the same and was just renamed -> update the column path
0228         d->dataColumnPath = columnPath;
0229     else if (d->dataColumnPath == columnPath) { // another column was renamed to the current path -> set and connect to the new column
0230         setUndoAware(false);
0231         setDataColumn(column);
0232         setUndoAware(true);
0233     }
0234 }
0235 
0236 QColor QQPlot::color() const {
0237     Q_D(const QQPlot);
0238     return d->percentilesCurve->color();
0239 }
0240 
0241 // ##############################################################################
0242 // #################  setter methods and undo commands ##########################
0243 // ##############################################################################
0244 
0245 // General
0246 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(QQPlot, Data, data, recalc)
0247 void QQPlot::setDataColumn(const AbstractColumn* column) {
0248     Q_D(QQPlot);
0249     if (column != d->dataColumn)
0250         exec(new QQPlotSetDataColumnCmd(d, column, ki18n("%1: set data column")));
0251 }
0252 
0253 void QQPlot::setDataColumnPath(const QString& path) {
0254     Q_D(QQPlot);
0255     d->dataColumnPath = path;
0256 }
0257 
0258 STD_SETTER_CMD_IMPL_F_S(QQPlot, SetDistribution, nsl_sf_stats_distribution, distribution, updateDistribution)
0259 void QQPlot::setDistribution(nsl_sf_stats_distribution distribution) {
0260     Q_D(QQPlot);
0261     if (distribution != d->distribution)
0262         exec(new QQPlotSetDistributionCmd(d, distribution, ki18n("%1: set distribution")));
0263 }
0264 
0265 // ##############################################################################
0266 // #################################  SLOTS  ####################################
0267 // ##############################################################################
0268 void QQPlot::retransform() {
0269     D(QQPlot);
0270     d->retransform();
0271 }
0272 
0273 void QQPlot::recalc() {
0274     D(QQPlot);
0275     d->recalc();
0276 }
0277 
0278 void QQPlot::dataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0279     Q_D(QQPlot);
0280     if (aspect == d->dataColumn) {
0281         d->dataColumn = nullptr;
0282         d->retransform();
0283     }
0284 }
0285 
0286 // ##############################################################################
0287 // ######################### Private implementation #############################
0288 // ##############################################################################
0289 QQPlotPrivate::QQPlotPrivate(QQPlot* owner)
0290     : PlotPrivate(owner)
0291     , q(owner) {
0292     setFlag(QGraphicsItem::ItemIsSelectable, true);
0293     setAcceptHoverEvents(false);
0294 }
0295 
0296 QQPlotPrivate::~QQPlotPrivate() {
0297 }
0298 
0299 /*!
0300   called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed.
0301   recalculates the position of the scene points to be drawn.
0302   triggers the update of lines, drop lines, symbols etc.
0303 */
0304 void QQPlotPrivate::retransform() {
0305     const bool suppressed = suppressRetransform || q->isLoading();
0306     if (suppressed)
0307         return;
0308 
0309     if (!isVisible())
0310         return;
0311 
0312     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0313     referenceCurve->retransform();
0314     percentilesCurve->retransform();
0315     recalcShapeAndBoundingRect();
0316 }
0317 
0318 /*!
0319  * called when the source data was changed, recalculates the plot.
0320  */
0321 void QQPlotPrivate::recalc() {
0322     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0323 
0324     if (!dataColumn) {
0325         yPercentilesColumn->clear();
0326         Q_EMIT q->dataChanged();
0327         return;
0328     }
0329 
0330     // copy the non-nan and not masked values into a new vector
0331     QVector<double> rawData;
0332     copyValidData(rawData);
0333     size_t n = rawData.count();
0334 
0335     // sort the data to calculate the percentiles
0336     std::sort(rawData.begin(), rawData.end());
0337 
0338     // calculate y-values - the percentiles for the column data
0339     QVector<double> yData;
0340     for (int i = 1; i < 100; ++i)
0341         yData << gsl_stats_quantile_from_sorted_data(rawData.data(), 1, n, double(i) / 100.);
0342 
0343     yPercentilesColumn->replaceValues(0, yData);
0344 
0345     double y1 = gsl_stats_quantile_from_sorted_data(rawData.data(), 1, n, 0.01);
0346     double y2 = gsl_stats_quantile_from_sorted_data(rawData.data(), 1, n, 0.99);
0347     yReferenceColumn->setValueAt(0, y1);
0348     yReferenceColumn->setValueAt(1, y2);
0349 
0350     // Q_EMIT dataChanged() in order to retransform everything with the new size/shape of the plot
0351     Q_EMIT q->dataChanged();
0352 }
0353 
0354 /*!
0355  * called when the distribution was changed, recalculates everything that depends on
0356  * the distribution only and doesn't dependent on the source data
0357  */
0358 void QQPlotPrivate::updateDistribution() {
0359     QVector<double> xData;
0360     double x1 = 0.;
0361     double x2 = 0.;
0362 
0363     // handle all distributions where the inverse of the CDF is available in gsl_cdf.h
0364     switch (distribution) {
0365     case nsl_sf_stats_gaussian: {
0366         x1 = gsl_cdf_gaussian_Pinv(0.01, 1.0);
0367         x2 = gsl_cdf_gaussian_Pinv(0.99, 1.0);
0368         for (int i = 1; i < 100; ++i)
0369             xData << gsl_cdf_gaussian_Pinv(double(i) / 100., 1.0);
0370         break;
0371     }
0372     case nsl_sf_stats_exponential: {
0373         x1 = gsl_cdf_exponential_Pinv(0.01, 1.0);
0374         x2 = gsl_cdf_exponential_Pinv(0.99, 1.0);
0375         for (int i = 1; i < 100; ++i)
0376             xData << gsl_cdf_exponential_Pinv(double(i) / 100., 1.0);
0377         break;
0378     }
0379     case nsl_sf_stats_laplace: {
0380         x1 = gsl_cdf_laplace_Pinv(0.01, 1.0);
0381         x2 = gsl_cdf_laplace_Pinv(0.99, 1.0);
0382         for (int i = 1; i < 100; ++i)
0383             xData << gsl_cdf_laplace_Pinv(double(i) / 100., 1.0);
0384         break;
0385     }
0386     case nsl_sf_stats_cauchy_lorentz: {
0387         x1 = gsl_cdf_cauchy_Pinv(0.01, 1.0);
0388         x2 = gsl_cdf_cauchy_Pinv(0.99, 1.0);
0389         for (int i = 1; i < 100; ++i)
0390             xData << gsl_cdf_cauchy_Pinv(double(i) / 100., 1.0);
0391         break;
0392     }
0393     case nsl_sf_stats_rayleigh: {
0394         x1 = gsl_cdf_rayleigh_Pinv(0.01, 1.0);
0395         x2 = gsl_cdf_rayleigh_Pinv(0.99, 1.0);
0396         for (int i = 1; i < 100; ++i)
0397             xData << gsl_cdf_rayleigh_Pinv(double(i) / 100., 1.0);
0398         break;
0399     }
0400     case nsl_sf_stats_gamma: {
0401         x1 = gsl_cdf_gamma_Pinv(0.01, 1.0, 1.0);
0402         x2 = gsl_cdf_gamma_Pinv(0.99, 1.0, 1.0);
0403         for (int i = 1; i < 100; ++i)
0404             xData << gsl_cdf_gamma_Pinv(double(i) / 100., 1.0, 1.0);
0405         break;
0406     }
0407     case nsl_sf_stats_flat: {
0408         x1 = gsl_cdf_flat_Pinv(0.01, 0.0, 1.0);
0409         x2 = gsl_cdf_flat_Pinv(0.99, 0.0, 1.0);
0410         for (int i = 1; i < 100; ++i)
0411             xData << gsl_cdf_flat_Pinv(double(i) / 100., 0.0, 1.0);
0412         break;
0413     }
0414     case nsl_sf_stats_lognormal: {
0415         x1 = gsl_cdf_lognormal_Pinv(0.01, 1.0, 1.0);
0416         x2 = gsl_cdf_lognormal_Pinv(0.99, 1.0, 1.0);
0417         for (int i = 1; i < 100; ++i)
0418             xData << gsl_cdf_lognormal_Pinv(double(i) / 100., 1.0, 1.0);
0419         break;
0420     }
0421     case nsl_sf_stats_chi_squared: {
0422         x1 = gsl_cdf_chisq_Pinv(0.01, 1.0);
0423         x2 = gsl_cdf_chisq_Pinv(0.99, 1.0);
0424         for (int i = 1; i < 100; ++i)
0425             xData << gsl_cdf_chisq_Pinv(double(i) / 100., 1.0);
0426         break;
0427     }
0428     case nsl_sf_stats_fdist: {
0429         x1 = gsl_cdf_fdist_Pinv(0.01, 1.0, 1.0);
0430         x2 = gsl_cdf_fdist_Pinv(0.99, 1.0, 1.0);
0431         for (int i = 1; i < 100; ++i)
0432             xData << gsl_cdf_fdist_Pinv(double(i) / 100., 1.0, 1.0);
0433         break;
0434     }
0435     case nsl_sf_stats_tdist: {
0436         x1 = gsl_cdf_tdist_Pinv(0.01, 1.0);
0437         x2 = gsl_cdf_tdist_Pinv(0.99, 1.0);
0438         for (int i = 1; i < 100; ++i)
0439             xData << gsl_cdf_tdist_Pinv(double(i) / 100., 1.0);
0440         break;
0441     }
0442     case nsl_sf_stats_beta: {
0443         x1 = gsl_cdf_beta_Pinv(0.01, 1.0, 1.0);
0444         x2 = gsl_cdf_beta_Pinv(0.99, 1.0, 1.0);
0445         for (int i = 1; i < 100; ++i)
0446             xData << gsl_cdf_beta_Pinv(double(i) / 100., 1.0, 1.0);
0447         break;
0448     }
0449     case nsl_sf_stats_logistic: {
0450         x1 = gsl_cdf_logistic_Pinv(0.01, 1.0);
0451         x2 = gsl_cdf_logistic_Pinv(0.99, 1.0);
0452         for (int i = 1; i < 100; ++i)
0453             xData << gsl_cdf_logistic_Pinv(double(i) / 100., 1.0);
0454         break;
0455     }
0456     case nsl_sf_stats_pareto: {
0457         x1 = gsl_cdf_pareto_Pinv(0.01, 1.0, 1.0);
0458         x2 = gsl_cdf_pareto_Pinv(0.99, 1.0, 1.0);
0459         for (int i = 1; i < 100; ++i)
0460             xData << gsl_cdf_pareto_Pinv(double(i) / 100., 1.0, 1.0);
0461         break;
0462     }
0463     case nsl_sf_stats_weibull: {
0464         x1 = gsl_cdf_weibull_Pinv(0.01, 1.0, 1.0);
0465         x2 = gsl_cdf_weibull_Pinv(0.99, 1.0, 1.0);
0466         for (int i = 1; i < 100; ++i)
0467             xData << gsl_cdf_weibull_Pinv(double(i) / 100., 1.0, 1.0);
0468         break;
0469     }
0470     case nsl_sf_stats_gumbel1: {
0471         x1 = gsl_cdf_gumbel1_Pinv(0.01, 1.0, 1.0);
0472         x2 = gsl_cdf_gumbel1_Pinv(0.99, 1.0, 1.0);
0473         for (int i = 1; i < 100; ++i)
0474             xData << gsl_cdf_gumbel1_Pinv(double(i) / 100., 1.0, 1.0);
0475         break;
0476     }
0477     case nsl_sf_stats_gumbel2: {
0478         x1 = gsl_cdf_gumbel2_Pinv(0.01, 1.0, 1.0);
0479         x2 = gsl_cdf_gumbel2_Pinv(0.99, 1.0, 1.0);
0480         for (int i = 1; i < 100; ++i)
0481             xData << gsl_cdf_gumbel2_Pinv(double(i) / 100., 1.0, 1.0);
0482         break;
0483     }
0484     case nsl_sf_stats_gaussian_tail:
0485     case nsl_sf_stats_exponential_power:
0486     case nsl_sf_stats_rayleigh_tail:
0487     case nsl_sf_stats_landau:
0488     case nsl_sf_stats_levy_alpha_stable:
0489     case nsl_sf_stats_levy_skew_alpha_stable:
0490     case nsl_sf_stats_poisson:
0491     case nsl_sf_stats_bernoulli:
0492     case nsl_sf_stats_binomial:
0493     case nsl_sf_stats_negative_binomial:
0494     case nsl_sf_stats_pascal:
0495     case nsl_sf_stats_geometric:
0496     case nsl_sf_stats_hypergeometric:
0497     case nsl_sf_stats_logarithmic:
0498     case nsl_sf_stats_maxwell_boltzmann:
0499     case nsl_sf_stats_sech:
0500     case nsl_sf_stats_levy:
0501     case nsl_sf_stats_frechet:
0502         break;
0503     }
0504 
0505     xReferenceColumn->setValueAt(0, x1);
0506     xReferenceColumn->setValueAt(1, x2);
0507 
0508     xPercentilesColumn->replaceValues(0, xData);
0509 
0510     // Q_EMIT dataChanged() in order to retransform everything with the new size/shape of the plot
0511     Q_EMIT q->dataChanged();
0512 }
0513 
0514 /*!
0515  * copy the non-nan and not masked values of the current column
0516  * into the vector \c data.
0517  */
0518 void QQPlotPrivate::copyValidData(QVector<double>& data) const {
0519     const int rowCount = dataColumn->rowCount();
0520     data.reserve(rowCount);
0521     double val;
0522     if (dataColumn->columnMode() == AbstractColumn::ColumnMode::Double) {
0523         auto* rowValues = reinterpret_cast<QVector<double>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0524         for (int row = 0; row < rowCount; ++row) {
0525             val = rowValues->value(row);
0526             if (std::isnan(val) || dataColumn->isMasked(row))
0527                 continue;
0528 
0529             data.push_back(val);
0530         }
0531     } else if (dataColumn->columnMode() == AbstractColumn::ColumnMode::Integer) {
0532         auto* rowValues = reinterpret_cast<QVector<int>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0533         for (int row = 0; row < rowCount; ++row) {
0534             val = rowValues->value(row);
0535             if (std::isnan(val) || dataColumn->isMasked(row))
0536                 continue;
0537 
0538             data.push_back(val);
0539         }
0540     } else if (dataColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) {
0541         auto* rowValues = reinterpret_cast<QVector<qint64>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0542         for (int row = 0; row < rowCount; ++row) {
0543             val = rowValues->value(row);
0544             if (std::isnan(val) || dataColumn->isMasked(row))
0545                 continue;
0546 
0547             data.push_back(val);
0548         }
0549     }
0550 
0551     if (data.size() < rowCount)
0552         data.squeeze();
0553 }
0554 
0555 /*!
0556   recalculates the outer bounds and the shape of the curve.
0557   */
0558 void QQPlotPrivate::recalcShapeAndBoundingRect() {
0559     if (suppressRecalc)
0560         return;
0561 
0562     prepareGeometryChange();
0563     m_shape = QPainterPath();
0564     m_shape.addPath(referenceCurve->graphicsItem()->shape());
0565     m_shape.addPath(percentilesCurve->graphicsItem()->shape());
0566 
0567     m_boundingRectangle = m_shape.boundingRect();
0568 }
0569 
0570 // ##############################################################################
0571 // ##################  Serialization/Deserialization  ###########################
0572 // ##############################################################################
0573 //! Save as XML
0574 void QQPlot::save(QXmlStreamWriter* writer) const {
0575     Q_D(const QQPlot);
0576 
0577     writer->writeStartElement(QStringLiteral("QQPlot"));
0578     writeBasicAttributes(writer);
0579     writeCommentElement(writer);
0580 
0581     // general
0582     writer->writeStartElement(QStringLiteral("general"));
0583     WRITE_COLUMN(d->dataColumn, dataColumn);
0584     WRITE_COLUMN(d->xReferenceColumn, xReferenceColumn);
0585     WRITE_COLUMN(d->yReferenceColumn, yReferenceColumn);
0586     WRITE_COLUMN(d->xPercentilesColumn, xPercentilesColumn);
0587     WRITE_COLUMN(d->yPercentilesColumn, yPercentilesColumn);
0588     writer->writeAttribute(QStringLiteral("distribution"), QString::number(static_cast<int>(d->distribution)));
0589     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
0590     writer->writeAttribute(QStringLiteral("legendVisible"), QString::number(d->legendVisible));
0591     writer->writeEndElement();
0592 
0593     // save the internal columns, above only the references to them were saved
0594     d->xReferenceColumn->save(writer);
0595     d->yReferenceColumn->save(writer);
0596     d->xPercentilesColumn->save(writer);
0597     d->yPercentilesColumn->save(writer);
0598 
0599     // save the internal curves
0600     d->referenceCurve->save(writer);
0601     d->percentilesCurve->save(writer);
0602 
0603     writer->writeEndElement(); // close "QQPlot" section
0604 }
0605 
0606 //! Load from XML
0607 bool QQPlot::load(XmlStreamReader* reader, bool preview) {
0608     Q_D(QQPlot);
0609 
0610     if (!readBasicAttributes(reader))
0611         return false;
0612 
0613     QXmlStreamAttributes attribs;
0614     QString str;
0615 
0616     while (!reader->atEnd()) {
0617         reader->readNext();
0618         if (reader->isEndElement() && reader->name() == QLatin1String("QQPlot"))
0619             break;
0620 
0621         if (!reader->isStartElement())
0622             continue;
0623 
0624         if (reader->name() == QLatin1String("comment")) {
0625             if (!readCommentElement(reader))
0626                 return false;
0627         } else if (!preview && reader->name() == QLatin1String("general")) {
0628             attribs = reader->attributes();
0629             READ_COLUMN(dataColumn);
0630             READ_COLUMN(xReferenceColumn);
0631             READ_COLUMN(yReferenceColumn);
0632             READ_COLUMN(xPercentilesColumn);
0633             READ_COLUMN(yPercentilesColumn);
0634             READ_INT_VALUE("distribution", distribution, nsl_sf_stats_distribution);
0635             READ_INT_VALUE("legendVisible", legendVisible, bool);
0636 
0637             str = attribs.value(QStringLiteral("visible")).toString();
0638             if (str.isEmpty())
0639                 reader->raiseMissingAttributeWarning(QStringLiteral("visible"));
0640             else
0641                 d->setVisible(str.toInt());
0642         } else if (reader->name() == QLatin1String("column")) {
0643             attribs = reader->attributes();
0644             bool rc = true;
0645             const auto& name = attribs.value(QStringLiteral("name"));
0646             if (name == QLatin1String("xReference"))
0647                 rc = d->xReferenceColumn->load(reader, preview);
0648             else if (name == QLatin1String("yReference"))
0649                 rc = d->yReferenceColumn->load(reader, preview);
0650             else if (name == QLatin1String("xPercentiles"))
0651                 rc = d->xPercentilesColumn->load(reader, preview);
0652             else if (name == QLatin1String("yPercentiles"))
0653                 rc = d->yPercentilesColumn->load(reader, preview);
0654 
0655             if (!rc)
0656                 return false;
0657         } else if (reader->name() == QLatin1String("xyCurve")) {
0658             attribs = reader->attributes();
0659             bool rc = true;
0660             if (attribs.value(QStringLiteral("name")) == QLatin1String("reference"))
0661                 rc = d->referenceCurve->load(reader, preview);
0662             else
0663                 rc = d->percentilesCurve->load(reader, preview);
0664 
0665             if (!rc)
0666                 return false;
0667         } else { // unknown element
0668             reader->raiseUnknownElementWarning();
0669             if (!reader->skipToEndElement())
0670                 return false;
0671         }
0672     }
0673     return true;
0674 }
0675 
0676 // ##############################################################################
0677 // #########################  Theme management ##################################
0678 // ##############################################################################
0679 void QQPlot::loadThemeConfig(const KConfig& config) {
0680     KConfigGroup group;
0681     if (config.hasGroup(QStringLiteral("Theme")))
0682         group = config.group(QStringLiteral("XYCurve")); // when loading from the theme config, use the same properties as for XYCurve
0683     else
0684         group = config.group(QStringLiteral("QQPlot"));
0685 
0686     const auto* plot = static_cast<const CartesianPlot*>(parentAspect());
0687     int index = plot->curveChildIndex(this);
0688     const QColor themeColor = plot->themeColorPalette(index);
0689 
0690     Q_D(QQPlot);
0691     d->suppressRecalc = true;
0692 
0693     d->referenceCurve->line()->loadThemeConfig(group, themeColor);
0694     d->percentilesCurve->line()->setStyle(Qt::NoPen);
0695     d->percentilesCurve->symbol()->loadThemeConfig(group, themeColor);
0696 
0697     d->suppressRecalc = false;
0698     d->recalcShapeAndBoundingRect();
0699 }
0700 
0701 void QQPlot::saveThemeConfig(const KConfig& config) {
0702     Q_D(const QQPlot);
0703     KConfigGroup group = config.group(QStringLiteral("QQPlot"));
0704     d->referenceCurve->line()->saveThemeConfig(group);
0705     d->percentilesCurve->symbol()->saveThemeConfig(group);
0706 }