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

0001 /*
0002     File                 : KDEPlot.cpp
0003     Project              : LabPlot
0004     Description          : KDE Plot
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 KDEPlot
0012   \brief
0013 
0014   \ingroup worksheet
0015   */
0016 #include "KDEPlot.h"
0017 #include "KDEPlotPrivate.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 "backend/nsl/nsl_kde.h"
0028 #include "backend/nsl/nsl_sf_kernel.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(KDEPlot, Data, data, recalc)
0039 
0040 KDEPlot::KDEPlot(const QString& name)
0041     : Plot(name, new KDEPlotPrivate(this), AspectType::KDEPlot) {
0042     init();
0043 }
0044 
0045 KDEPlot::KDEPlot(const QString& name, KDEPlotPrivate* dd)
0046     : Plot(name, dd, AspectType::KDEPlot) {
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 KDEPlot::~KDEPlot() = default;
0053 
0054 void KDEPlot::init() {
0055     Q_D(KDEPlot);
0056 
0057     KConfig config;
0058     KConfigGroup group = config.group(QStringLiteral("KDEPlot"));
0059 
0060     // general
0061     d->kernelType = static_cast<nsl_kernel_type>(group.readEntry(QStringLiteral("kernelType"), static_cast<int>(nsl_kernel_gauss)));
0062     d->bandwidthType = static_cast<nsl_kde_bandwidth_type>(group.readEntry(QStringLiteral("bandwidthType"), static_cast<int>(nsl_kde_bandwidth_silverman)));
0063     d->bandwidth = group.readEntry(QStringLiteral("bandwidth"), 0.1);
0064 
0065     // estimation curve
0066     d->estimationCurve = new XYCurve(QString());
0067     d->estimationCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0068     d->estimationCurve->setHidden(true);
0069     d->estimationCurve->graphicsItem()->setParentItem(d);
0070     d->estimationCurve->line()->init(group);
0071     d->estimationCurve->line()->setStyle(Qt::SolidLine);
0072     d->estimationCurve->symbol()->setStyle(Symbol::Style::NoSymbols);
0073     d->estimationCurve->background()->setPosition(Background::Position::No);
0074 
0075     // columns holding the data for the reference curve
0076     d->xEstimationColumn = new Column(QStringLiteral("xReference"));
0077     d->xEstimationColumn->setHidden(true);
0078     d->xEstimationColumn->setUndoAware(false);
0079     addChildFast(d->xEstimationColumn);
0080     d->estimationCurve->setXColumn(d->xEstimationColumn);
0081 
0082     d->yEstimationColumn = new Column(QStringLiteral("yReference"));
0083     d->yEstimationColumn->setHidden(true);
0084     d->yEstimationColumn->setUndoAware(false);
0085     addChildFast(d->yEstimationColumn);
0086     d->estimationCurve->setYColumn(d->yEstimationColumn);
0087 
0088     // xy-curve for the rug plot
0089     d->rugCurve = new XYCurve(QString());
0090     d->rugCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0091     d->rugCurve->setHidden(true);
0092     d->rugCurve->graphicsItem()->setParentItem(d);
0093     d->rugCurve->line()->setStyle(Qt::NoPen);
0094     d->rugCurve->symbol()->setStyle(Symbol::Style::NoSymbols);
0095     d->rugCurve->setRugOrientation(WorksheetElement::Orientation::Horizontal);
0096 
0097     // synchronize the names of the internal XYCurves with the name of the current q-q plot
0098     // so we have the same name shown on the undo stack
0099     connect(this, &AbstractAspect::aspectDescriptionChanged, [this] {
0100         Q_D(KDEPlot);
0101         d->estimationCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0102         d->rugCurve->setName(name(), AbstractAspect::NameHandling::UniqueNotRequired);
0103     });
0104 }
0105 
0106 void KDEPlot::finalizeAdd() {
0107     Q_D(KDEPlot);
0108     WorksheetElement::finalizeAdd();
0109     addChildFast(d->estimationCurve);
0110     addChildFast(d->rugCurve);
0111 }
0112 
0113 /*!
0114   Returns an icon to be used in the project explorer.
0115   */
0116 QIcon KDEPlot::icon() const {
0117     return QIcon::fromTheme(QStringLiteral("view-object-histogram-linear"));
0118 }
0119 
0120 void KDEPlot::handleResize(double /*horizontalRatio*/, double /*verticalRatio*/, bool /*pageResize*/) {
0121     // TODO
0122 }
0123 
0124 void KDEPlot::setVisible(bool on) {
0125     Q_D(KDEPlot);
0126     beginMacro(on ? i18n("%1: set visible", name()) : i18n("%1: set invisible", name()));
0127     d->estimationCurve->setVisible(on);
0128     d->rugCurve->setVisible(on);
0129     WorksheetElement::setVisible(on);
0130     endMacro();
0131 }
0132 
0133 // ##############################################################################
0134 // ##########################  getter methods  ##################################
0135 // ##############################################################################
0136 //  general
0137 BASIC_SHARED_D_READER_IMPL(KDEPlot, const AbstractColumn*, dataColumn, dataColumn)
0138 BASIC_SHARED_D_READER_IMPL(KDEPlot, QString, dataColumnPath, dataColumnPath)
0139 BASIC_SHARED_D_READER_IMPL(KDEPlot, nsl_kernel_type, kernelType, kernelType)
0140 BASIC_SHARED_D_READER_IMPL(KDEPlot, nsl_kde_bandwidth_type, bandwidthType, bandwidthType)
0141 BASIC_SHARED_D_READER_IMPL(KDEPlot, double, bandwidth, bandwidth)
0142 
0143 XYCurve* KDEPlot::estimationCurve() const {
0144     Q_D(const KDEPlot);
0145     return d->estimationCurve;
0146 }
0147 
0148 XYCurve* KDEPlot::rugCurve() const {
0149     Q_D(const KDEPlot);
0150     return d->rugCurve;
0151 }
0152 
0153 bool KDEPlot::minMax(const Dimension dim, const Range<int>& indexRange, Range<double>& r, bool /* includeErrorBars */) const {
0154     Q_D(const KDEPlot);
0155     return d->estimationCurve->minMax(dim, indexRange, r);
0156 }
0157 
0158 double KDEPlot::minimum(const Dimension dim) const {
0159     Q_D(const KDEPlot);
0160     switch (dim) {
0161     case Dimension::X:
0162         return d->estimationCurve->minimum(dim);
0163     case Dimension::Y:
0164         return d->estimationCurve->minimum(dim);
0165     }
0166     return NAN;
0167 }
0168 
0169 double KDEPlot::maximum(const Dimension dim) const {
0170     Q_D(const KDEPlot);
0171     switch (dim) {
0172     case Dimension::X:
0173         return d->estimationCurve->maximum(dim);
0174     case Dimension::Y:
0175         return d->estimationCurve->maximum(dim);
0176     }
0177     return NAN;
0178 }
0179 
0180 bool KDEPlot::hasData() const {
0181     Q_D(const KDEPlot);
0182     return (d->dataColumn != nullptr);
0183 }
0184 
0185 bool KDEPlot::usingColumn(const Column* column) const {
0186     Q_D(const KDEPlot);
0187     return (d->dataColumn == column);
0188 }
0189 
0190 void KDEPlot::updateColumnDependencies(const AbstractColumn* column) {
0191     Q_D(KDEPlot);
0192     const QString& columnPath = column->path();
0193 
0194     if (d->dataColumn == column) // the column is the same and was just renamed -> update the column path
0195         d->dataColumnPath = columnPath;
0196     else if (d->dataColumnPath == columnPath) { // another column was renamed to the current path -> set and connect to the new column
0197         setUndoAware(false);
0198         setDataColumn(column);
0199         setUndoAware(true);
0200     }
0201 }
0202 
0203 QColor KDEPlot::color() const {
0204     Q_D(const KDEPlot);
0205     return d->estimationCurve->color();
0206 }
0207 /*!
0208  * returns the the number of equaly spaced points at which the density is to be evaluated,
0209  * which also corresponds to the number of data points in the xy-curve used internally.
0210  */
0211 int KDEPlot::gridPointsCount() const {
0212     Q_D(const KDEPlot);
0213     return d->gridPointsCount;
0214 }
0215 
0216 // ##############################################################################
0217 // #################  setter methods and undo commands ##########################
0218 // ##############################################################################
0219 
0220 // General
0221 CURVE_COLUMN_SETTER_CMD_IMPL_F_S(KDEPlot, Data, data, recalc)
0222 void KDEPlot::setDataColumn(const AbstractColumn* column) {
0223     Q_D(KDEPlot);
0224     if (column != d->dataColumn)
0225         exec(new KDEPlotSetDataColumnCmd(d, column, ki18n("%1: set data column")));
0226 }
0227 
0228 void KDEPlot::setDataColumnPath(const QString& path) {
0229     Q_D(KDEPlot);
0230     d->dataColumnPath = path;
0231 }
0232 
0233 STD_SETTER_CMD_IMPL_F_S(KDEPlot, SetKernelType, nsl_kernel_type, kernelType, recalc)
0234 void KDEPlot::setKernelType(nsl_kernel_type kernelType) {
0235     Q_D(KDEPlot);
0236     if (kernelType != d->kernelType)
0237         exec(new KDEPlotSetKernelTypeCmd(d, kernelType, ki18n("%1: set kernel type")));
0238 }
0239 
0240 STD_SETTER_CMD_IMPL_F_S(KDEPlot, SetBandwidthType, nsl_kde_bandwidth_type, bandwidthType, recalc)
0241 void KDEPlot::setBandwidthType(nsl_kde_bandwidth_type bandwidthType) {
0242     Q_D(KDEPlot);
0243     if (bandwidthType != d->bandwidthType)
0244         exec(new KDEPlotSetBandwidthTypeCmd(d, bandwidthType, ki18n("%1: set bandwidth type")));
0245 }
0246 
0247 STD_SETTER_CMD_IMPL_F_S(KDEPlot, SetBandwidth, double, bandwidth, recalc)
0248 void KDEPlot::setBandwidth(double bandwidth) {
0249     Q_D(KDEPlot);
0250     if (bandwidth != d->bandwidth)
0251         exec(new KDEPlotSetBandwidthCmd(d, bandwidth, ki18n("%1: set bandwidth")));
0252 }
0253 
0254 // ##############################################################################
0255 // #################################  SLOTS  ####################################
0256 // ##############################################################################
0257 void KDEPlot::retransform() {
0258     d_ptr->retransform();
0259 }
0260 
0261 void KDEPlot::recalc() {
0262     D(KDEPlot);
0263     d->recalc();
0264 }
0265 
0266 void KDEPlot::dataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0267     Q_D(KDEPlot);
0268     if (aspect == d->dataColumn) {
0269         d->dataColumn = nullptr;
0270         d->retransform();
0271     }
0272 }
0273 
0274 // ##############################################################################
0275 // ######################### Private implementation #############################
0276 // ##############################################################################
0277 KDEPlotPrivate::KDEPlotPrivate(KDEPlot* owner)
0278     : PlotPrivate(owner)
0279     , q(owner) {
0280     setFlag(QGraphicsItem::ItemIsSelectable, true);
0281     setAcceptHoverEvents(false);
0282 }
0283 
0284 KDEPlotPrivate::~KDEPlotPrivate() {
0285 }
0286 
0287 /*!
0288   called when the size of the plot or its data ranges (manual changes, zooming, etc.) were changed.
0289   recalculates the position of the scene points to be drawn.
0290   triggers the update of lines, drop lines, symbols etc.
0291 */
0292 void KDEPlotPrivate::retransform() {
0293     const bool suppressed = suppressRetransform || q->isLoading();
0294     if (suppressed)
0295         return;
0296 
0297     if (!isVisible())
0298         return;
0299 
0300     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0301     estimationCurve->retransform();
0302     rugCurve->retransform();
0303     recalcShapeAndBoundingRect();
0304 }
0305 
0306 /*!
0307  * called when the source data was changed, recalculates the plot.
0308  */
0309 void KDEPlotPrivate::recalc() {
0310     PERFTRACE(name() + QLatin1String(Q_FUNC_INFO));
0311 
0312     rugCurve->setXColumn(dataColumn);
0313     rugCurve->setYColumn(dataColumn);
0314 
0315     if (!dataColumn) {
0316         xEstimationColumn->clear();
0317         yEstimationColumn->clear();
0318 
0319         Q_EMIT q->dataChanged();
0320         return;
0321     }
0322 
0323     // copy the non-nan and not masked values into a new vector
0324     QVector<double> data;
0325     copyValidData(data);
0326 
0327     // calculate the estimation curve for the number
0328     // of equaly spaced points determined by gridPoints
0329     QVector<double> xData;
0330     QVector<double> yData;
0331     xData.resize(gridPointsCount);
0332     yData.resize(gridPointsCount);
0333     int n = data.count();
0334     const auto& statistics = static_cast<const Column*>(dataColumn)->statistics();
0335     const double sigma = statistics.standardDeviation;
0336     const double iqr = statistics.iqr;
0337 
0338     // bandwidth
0339     double h;
0340     if (bandwidthType == nsl_kde_bandwidth_custom) {
0341         if (bandwidth != 0)
0342             h = bandwidth;
0343         else {
0344             // invalid smoothing bandwidth parameter
0345             xEstimationColumn->setValues(xData);
0346             yEstimationColumn->setValues(yData);
0347             Q_EMIT q->dataChanged();
0348             return;
0349         }
0350     } else
0351         h = nsl_kde_bandwidth(n, sigma, iqr, bandwidthType);
0352 
0353     // calculate KDE for the grid points from min-3*sigma to max+3*sigma
0354     const double min = statistics.minimum - 3 * h;
0355     const double max = statistics.maximum + 3 * h;
0356     const double step = (max - min) / gridPointsCount;
0357 
0358     for (int i = 0; i < gridPointsCount; ++i) {
0359         double x = min + i * step;
0360         xData[i] = x;
0361         yData[i] = nsl_kde(data.data(), x, kernelType, h, n);
0362     }
0363 
0364     xEstimationColumn->setValues(xData);
0365     yEstimationColumn->setValues(yData);
0366 
0367     // Q_EMIT dataChanged() in order to retransform everything with the new size/shape of the plot
0368     Q_EMIT q->dataChanged();
0369 }
0370 
0371 /*!
0372  * copy the non-nan and not masked values of the current column
0373  * into the vector \c data.
0374  */
0375 void KDEPlotPrivate::copyValidData(QVector<double>& data) const {
0376     const int rowCount = dataColumn->rowCount();
0377     data.reserve(rowCount);
0378     double val;
0379     if (dataColumn->columnMode() == AbstractColumn::ColumnMode::Double) {
0380         auto* rowValues = reinterpret_cast<QVector<double>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0381         for (int row = 0; row < rowCount; ++row) {
0382             val = rowValues->value(row);
0383             if (std::isnan(val) || dataColumn->isMasked(row))
0384                 continue;
0385 
0386             data.push_back(val);
0387         }
0388     } else if (dataColumn->columnMode() == AbstractColumn::ColumnMode::Integer) {
0389         auto* rowValues = reinterpret_cast<QVector<int>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0390         for (int row = 0; row < rowCount; ++row) {
0391             val = rowValues->value(row);
0392             if (std::isnan(val) || dataColumn->isMasked(row))
0393                 continue;
0394 
0395             data.push_back(val);
0396         }
0397     } else if (dataColumn->columnMode() == AbstractColumn::ColumnMode::BigInt) {
0398         auto* rowValues = reinterpret_cast<QVector<qint64>*>(reinterpret_cast<const Column*>(dataColumn)->data());
0399         for (int row = 0; row < rowCount; ++row) {
0400             val = rowValues->value(row);
0401             if (std::isnan(val) || dataColumn->isMasked(row))
0402                 continue;
0403 
0404             data.push_back(val);
0405         }
0406     }
0407 
0408     if (data.size() < rowCount)
0409         data.squeeze();
0410 }
0411 
0412 /*!
0413   recalculates the outer bounds and the shape of the curve.
0414   */
0415 void KDEPlotPrivate::recalcShapeAndBoundingRect() {
0416     if (suppressRecalc)
0417         return;
0418 
0419     prepareGeometryChange();
0420     m_shape = QPainterPath();
0421     m_shape.addPath(estimationCurve->graphicsItem()->shape());
0422     m_shape.addPath(rugCurve->graphicsItem()->shape());
0423     m_boundingRectangle = m_shape.boundingRect();
0424 }
0425 
0426 // ##############################################################################
0427 // ##################  Serialization/Deserialization  ###########################
0428 // ##############################################################################
0429 //! Save as XML
0430 void KDEPlot::save(QXmlStreamWriter* writer) const {
0431     Q_D(const KDEPlot);
0432 
0433     writer->writeStartElement(QStringLiteral("KDEPlot"));
0434     writeBasicAttributes(writer);
0435     writeCommentElement(writer);
0436 
0437     // general
0438     writer->writeStartElement(QStringLiteral("general"));
0439     WRITE_COLUMN(d->dataColumn, dataColumn);
0440     writer->writeAttribute(QStringLiteral("kernelType"), QString::number(d->kernelType));
0441     writer->writeAttribute(QStringLiteral("bandwidthType"), QString::number(d->bandwidthType));
0442     writer->writeAttribute(QStringLiteral("bandwidth"), QString::number(d->bandwidth));
0443     writer->writeAttribute(QStringLiteral("visible"), QString::number(d->isVisible()));
0444     writer->writeAttribute(QStringLiteral("legendVisible"), QString::number(d->legendVisible));
0445     writer->writeEndElement();
0446 
0447     // save the internal columns, above only the references to them were saved
0448     d->xEstimationColumn->save(writer);
0449     d->yEstimationColumn->save(writer);
0450 
0451     // save the internal curves
0452     d->estimationCurve->save(writer);
0453     d->rugCurve->save(writer);
0454 
0455     writer->writeEndElement(); // close "KDEPlot" section
0456 }
0457 
0458 //! Load from XML
0459 bool KDEPlot::load(XmlStreamReader* reader, bool preview) {
0460     Q_D(KDEPlot);
0461 
0462     if (!readBasicAttributes(reader))
0463         return false;
0464 
0465     KLocalizedString attributeWarning = ki18n("Attribute '%1' missing or empty, default value is used");
0466     QXmlStreamAttributes attribs;
0467     QString str;
0468     bool estimationCurveInitialized = false;
0469     bool rugCurveInitialized = false;
0470 
0471     while (!reader->atEnd()) {
0472         reader->readNext();
0473         if (reader->isEndElement() && reader->name() == QLatin1String("KDEPlot"))
0474             break;
0475 
0476         if (!reader->isStartElement())
0477             continue;
0478 
0479         if (reader->name() == QLatin1String("comment")) {
0480             if (!readCommentElement(reader))
0481                 return false;
0482         } else if (!preview && reader->name() == QLatin1String("general")) {
0483             attribs = reader->attributes();
0484             READ_COLUMN(dataColumn);
0485             READ_INT_VALUE("kernelType", kernelType, nsl_kernel_type);
0486             READ_INT_VALUE("bandwidthType", bandwidthType, nsl_kde_bandwidth_type);
0487             READ_DOUBLE_VALUE("bandwidth", bandwidth);
0488             READ_INT_VALUE("legendVisible", legendVisible, bool);
0489 
0490             str = attribs.value(QStringLiteral("visible")).toString();
0491             if (str.isEmpty())
0492                 reader->raiseWarning(attributeWarning.subs(QStringLiteral("visible")).toString());
0493             else
0494                 d->setVisible(str.toInt());
0495         } else if (reader->name() == QLatin1String("column")) {
0496             attribs = reader->attributes();
0497             bool rc = true;
0498             const auto& name = attribs.value(QStringLiteral("name"));
0499             if (name == QLatin1String("xReference"))
0500                 rc = d->xEstimationColumn->load(reader, preview);
0501             else if (name == QLatin1String("yReference"))
0502                 rc = d->yEstimationColumn->load(reader, preview);
0503 
0504             if (!rc)
0505                 return false;
0506         } else if (reader->name() == QLatin1String("xyCurve")) {
0507             if (!estimationCurveInitialized) {
0508                 if (d->estimationCurve->load(reader, preview)) {
0509                     estimationCurveInitialized = true;
0510                     continue;
0511                 } else
0512                     return false;
0513             }
0514 
0515             if (!rugCurveInitialized) {
0516                 if (d->rugCurve->load(reader, preview)) {
0517                     rugCurveInitialized = true;
0518                     continue;
0519                 } else
0520                     return false;
0521             }
0522         } else { // unknown element
0523             reader->raiseUnknownElementWarning();
0524             if (!reader->skipToEndElement())
0525                 return false;
0526         }
0527     }
0528     return true;
0529 }
0530 
0531 // ##############################################################################
0532 // #########################  Theme management ##################################
0533 // ##############################################################################
0534 void KDEPlot::loadThemeConfig(const KConfig& config) {
0535     KConfigGroup group;
0536     if (config.hasGroup(QStringLiteral("Theme")))
0537         group = config.group(QStringLiteral("XYCurve")); // when loading from the theme config, use the same properties as for XYCurve
0538     else
0539         group = config.group(QStringLiteral("KDEPlot"));
0540 
0541     const auto* plot = static_cast<const CartesianPlot*>(parentAspect());
0542     int index = plot->curveChildIndex(this);
0543     const QColor themeColor = plot->themeColorPalette(index);
0544 
0545     Q_D(KDEPlot);
0546     d->suppressRecalc = true;
0547 
0548     d->estimationCurve->line()->loadThemeConfig(group, themeColor);
0549     d->estimationCurve->background()->loadThemeConfig(group, themeColor);
0550     d->rugCurve->symbol()->loadThemeConfig(group, themeColor);
0551 
0552     d->suppressRecalc = false;
0553     d->recalcShapeAndBoundingRect();
0554 }
0555 
0556 void KDEPlot::saveThemeConfig(const KConfig& config) {
0557     // Q_D(const KDEPlot);
0558     KConfigGroup group = config.group(QStringLiteral("KDEPlot"));
0559     // TODO
0560 }