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 }