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

0001 /*
0002     File                 : XYAnalysisCurve.h
0003     Project              : LabPlot
0004     Description          : Base class for all analysis curves
0005     --------------------------------------------------------------------
0006     SPDX-FileCopyrightText: 2017-2022 Alexander Semke <alexander.semke@web.de>
0007     SPDX-FileCopyrightText: 2018-2022 Stefan Gerlach <stefan.gerlach@uni.kn>
0008     SPDX-License-Identifier: GPL-2.0-or-later
0009 */
0010 
0011 /*!
0012   \class XYAnalysisCurve
0013   \brief Base class for all analysis curves
0014 
0015   \ingroup worksheet
0016 */
0017 
0018 #include "XYAnalysisCurve.h"
0019 #include "XYAnalysisCurvePrivate.h"
0020 #include "backend/core/Project.h"
0021 #include "backend/core/column/Column.h"
0022 #include "backend/lib/XmlStreamReader.h"
0023 #include "backend/lib/commandtemplates.h"
0024 #include "backend/lib/macros.h"
0025 #include "backend/spreadsheet/Spreadsheet.h"
0026 #include "backend/worksheet/plots/cartesian/Symbol.h"
0027 #include "backend/worksheet/plots/cartesian/XYFitCurve.h"
0028 #include "backend/worksheet/plots/cartesian/XYSmoothCurve.h"
0029 
0030 #include <KLocalizedString>
0031 #include <QDateTime>
0032 
0033 XYAnalysisCurve::XYAnalysisCurve(const QString& name, XYAnalysisCurvePrivate* dd, AspectType type)
0034     : XYCurve(name, dd, type) {
0035     init();
0036 }
0037 
0038 // no need to delete the d-pointer here - it inherits from QGraphicsItem
0039 // and is deleted during the cleanup in QGraphicsScene
0040 XYAnalysisCurve::~XYAnalysisCurve() = default;
0041 
0042 void XYAnalysisCurve::init() {
0043     Q_D(XYAnalysisCurve);
0044     d->lineType = XYCurve::LineType::Line;
0045     d->symbol->setStyle(Symbol::Style::NoSymbols);
0046 }
0047 
0048 bool XYAnalysisCurve::resultAvailable() const {
0049     return result().available;
0050 }
0051 
0052 bool XYAnalysisCurve::usingColumn(const Column* column) const {
0053     Q_D(const XYAnalysisCurve);
0054 
0055     if (d->dataSourceType == DataSourceType::Spreadsheet)
0056         return (d->xDataColumn == column || d->yDataColumn == column || d->y2DataColumn == column);
0057     else
0058         return (d->dataSourceCurve->xColumn() == column || d->dataSourceCurve->yColumn() == column);
0059 }
0060 
0061 void XYAnalysisCurve::updateColumnDependencies(const AbstractColumn* column) {
0062     D(const XYAnalysisCurve);
0063     const QString& columnPath = column->path();
0064     setUndoAware(false);
0065 
0066     if (d->xDataColumnPath == columnPath)
0067         setXDataColumn(column);
0068     if (d->yDataColumnPath == columnPath)
0069         setYDataColumn(column);
0070     if (d->y2DataColumnPath == columnPath)
0071         setY2DataColumn(column);
0072 
0073     auto* fitCurve = dynamic_cast<XYFitCurve*>(this);
0074     if (fitCurve) {
0075         if (fitCurve->xErrorColumnPath() == columnPath)
0076             fitCurve->setXErrorColumn(column);
0077         if (fitCurve->yErrorColumnPath() == columnPath)
0078             fitCurve->setYErrorColumn(column);
0079     }
0080 
0081     if (d->valuesColumnPath == columnPath)
0082         setValuesColumn(column);
0083 
0084     setUndoAware(true);
0085 }
0086 
0087 // copy valid data from x/y data columns to x/y data vectors
0088 // for analysis functions
0089 // avgUniqueX: average y values for duplicate x values
0090 void XYAnalysisCurve::copyData(QVector<double>& xData,
0091                                QVector<double>& yData,
0092                                const AbstractColumn* xDataColumn,
0093                                const AbstractColumn* yDataColumn,
0094                                double xMin,
0095                                double xMax,
0096                                bool avgUniqueX) {
0097     const int rowCount = std::min(xDataColumn->rowCount(), yDataColumn->rowCount());
0098     bool uniqueX = true;
0099     for (int row = 0; row < rowCount; ++row) {
0100         if (!xDataColumn->isValid(row) || xDataColumn->isMasked(row) || !yDataColumn->isValid(row) || yDataColumn->isMasked(row))
0101             continue;
0102 
0103         double x = NAN;
0104         switch (xDataColumn->columnMode()) {
0105         case AbstractColumn::ColumnMode::Double:
0106             x = xDataColumn->valueAt(row);
0107             break;
0108         case AbstractColumn::ColumnMode::Integer:
0109             x = xDataColumn->integerAt(row);
0110             break;
0111         case AbstractColumn::ColumnMode::BigInt:
0112             x = xDataColumn->bigIntAt(row);
0113             break;
0114         case AbstractColumn::ColumnMode::Text: // invalid
0115             break;
0116         case AbstractColumn::ColumnMode::DateTime:
0117         case AbstractColumn::ColumnMode::Day:
0118         case AbstractColumn::ColumnMode::Month:
0119             x = xDataColumn->dateTimeAt(row).toMSecsSinceEpoch();
0120         }
0121 
0122         double y = NAN;
0123         switch (yDataColumn->columnMode()) {
0124         case AbstractColumn::ColumnMode::Double:
0125             y = yDataColumn->valueAt(row);
0126             break;
0127         case AbstractColumn::ColumnMode::Integer:
0128             y = yDataColumn->integerAt(row);
0129             break;
0130         case AbstractColumn::ColumnMode::BigInt:
0131             y = yDataColumn->bigIntAt(row);
0132             break;
0133         case AbstractColumn::ColumnMode::Text: // invalid
0134             break;
0135         case AbstractColumn::ColumnMode::DateTime:
0136         case AbstractColumn::ColumnMode::Day:
0137         case AbstractColumn::ColumnMode::Month:
0138             y = yDataColumn->dateTimeAt(row).toMSecsSinceEpoch();
0139         }
0140 
0141         // only when inside given range
0142         if (x >= xMin && x <= xMax) {
0143             if (xData.contains(x))
0144                 uniqueX = false;
0145             xData.append(x);
0146             yData.append(y);
0147         }
0148     }
0149 
0150     if (uniqueX || !avgUniqueX)
0151         return;
0152 
0153     // for (int i = 0; i < xData.size(); i++)
0154     //  WARN( xData.at(i) << " " << yData.at(i))
0155 
0156     // average values for consecutive same x value
0157     double oldX = NAN, sum = 0.;
0158     int count = 1;
0159     for (int i = 0; i < xData.size(); i++) {
0160         // WARN(" i = " << i)
0161         const double x = xData.at(i);
0162         const double y = yData.at(i);
0163         // WARN(x << " / " << y << ": " << sum << " " << oldX  << " " << count)
0164         if (x == oldX) { // same x, but not last
0165             // DEBUG(" same x")
0166             sum += y;
0167             count++;
0168             if (i < xData.size() - 1) {
0169                 continue;
0170             }
0171         }
0172 
0173         // WARN(" next/last x")
0174         if (count > 1) { // average and remove duplicate
0175             // WARN("average: " << sum/count)
0176             const int index = i - count + 1;
0177             yData[index - 1] = sum / count;
0178             // WARN("remove at " << index << ", count = " << count-1)
0179             xData.remove(index, count - 1);
0180             yData.remove(index, count - 1);
0181 
0182             i -= count - 1;
0183             count = 1;
0184         }
0185         sum = y;
0186         oldX = x;
0187     }
0188 }
0189 
0190 // ##############################################################################
0191 // ##########################  getter methods  ##################################
0192 // ##############################################################################
0193 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, XYAnalysisCurve::DataSourceType, dataSourceType, dataSourceType)
0194 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const XYCurve*, dataSourceCurve, dataSourceCurve)
0195 const QString& XYAnalysisCurve::dataSourceCurvePath() const {
0196     D(XYAnalysisCurve);
0197     return d->dataSourceCurvePath;
0198 }
0199 
0200 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, xDataColumn, xDataColumn)
0201 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, yDataColumn, yDataColumn)
0202 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, y2DataColumn, y2DataColumn)
0203 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, xDataColumnPath, xDataColumnPath)
0204 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, yDataColumnPath, yDataColumnPath)
0205 BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, QString, y2DataColumnPath, y2DataColumnPath)
0206 
0207 bool XYAnalysisCurve::saveCalculations() const {
0208     return const_cast<XYAnalysisCurve*>(this)->project()->saveCalculations();
0209 }
0210 
0211 // ##############################################################################
0212 // #################  setter methods and undo commands ##########################
0213 // ##############################################################################
0214 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetDataSourceType, XYAnalysisCurve::DataSourceType, dataSourceType)
0215 void XYAnalysisCurve::setDataSourceType(DataSourceType type) {
0216     Q_D(XYAnalysisCurve);
0217     if (type != d->dataSourceType)
0218         exec(new XYAnalysisCurveSetDataSourceTypeCmd(d, type, ki18n("%1: data source type changed")));
0219 }
0220 
0221 STD_SETTER_CMD_IMPL_F_S(XYAnalysisCurve, SetDataSourceCurve, const XYCurve*, dataSourceCurve, retransform)
0222 void XYAnalysisCurve::setDataSourceCurve(const XYCurve* curve) {
0223     Q_D(XYAnalysisCurve);
0224     if (curve != d->dataSourceCurve) {
0225         exec(new XYAnalysisCurveSetDataSourceCurveCmd(d, curve, ki18n("%1: data source curve changed")));
0226         handleSourceDataChanged();
0227 
0228         // handle the changes when different columns were provided for the source curve
0229         connect(curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0230         connect(curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0231         // TODO? connect(curve, SIGNAL(y2ColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0232 
0233         // handle the changes when the data inside of the source curve columns
0234         connect(curve, &XYCurve::xDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged);
0235         connect(curve, &XYCurve::yDataChanged, this, &XYAnalysisCurve::handleSourceDataChanged);
0236 
0237         // TODO: add disconnect in the undo-function
0238     }
0239 }
0240 
0241 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetXDataColumn, const AbstractColumn*, xDataColumn)
0242 void XYAnalysisCurve::setXDataColumn(const AbstractColumn* column) {
0243     DEBUG(Q_FUNC_INFO);
0244     Q_D(XYAnalysisCurve);
0245     if (column != d->xDataColumn) {
0246         exec(new XYAnalysisCurveSetXDataColumnCmd(d, column, ki18n("%1: assign x-data")));
0247         handleSourceDataChanged();
0248         if (column) {
0249             setXDataColumnPath(column->path());
0250             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::xDataColumnAboutToBeRemoved);
0251             connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0252             connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::xDataColumnNameChanged);
0253             // TODO disconnect on undo
0254         } else
0255             setXDataColumnPath(QString());
0256     }
0257 }
0258 
0259 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetYDataColumn, const AbstractColumn*, yDataColumn)
0260 void XYAnalysisCurve::setYDataColumn(const AbstractColumn* column) {
0261     DEBUG(Q_FUNC_INFO);
0262     Q_D(XYAnalysisCurve);
0263     if (column != d->yDataColumn) {
0264         exec(new XYAnalysisCurveSetYDataColumnCmd(d, column, ki18n("%1: assign y-data")));
0265         handleSourceDataChanged();
0266         if (column) {
0267             setYDataColumnPath(column->path());
0268             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::yDataColumnAboutToBeRemoved);
0269             connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0270             connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::yDataColumnNameChanged);
0271             // TODO disconnect on undo
0272         } else
0273             setYDataColumnPath(QString());
0274     }
0275 }
0276 
0277 STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetY2DataColumn, const AbstractColumn*, y2DataColumn)
0278 void XYAnalysisCurve::setY2DataColumn(const AbstractColumn* column) {
0279     DEBUG(Q_FUNC_INFO);
0280     Q_D(XYAnalysisCurve);
0281     if (column != d->y2DataColumn) {
0282         exec(new XYAnalysisCurveSetY2DataColumnCmd(d, column, ki18n("%1: assign second y-data")));
0283         handleSourceDataChanged();
0284         if (column) {
0285             setY2DataColumnPath(column->path());
0286             connect(column->parentAspect(), &AbstractAspect::childAspectAboutToBeRemoved, this, &XYAnalysisCurve::y2DataColumnAboutToBeRemoved);
0287             connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged()));
0288             connect(column, &AbstractAspect::aspectDescriptionChanged, this, &XYAnalysisCurve::y2DataColumnNameChanged);
0289             // TODO disconnect on undo
0290         } else
0291             setY2DataColumnPath(QString());
0292     }
0293 }
0294 
0295 void XYAnalysisCurve::setXDataColumnPath(const QString& path) {
0296     Q_D(XYAnalysisCurve);
0297     d->xDataColumnPath = path;
0298 }
0299 
0300 void XYAnalysisCurve::setYDataColumnPath(const QString& path) {
0301     Q_D(XYAnalysisCurve);
0302     d->yDataColumnPath = path;
0303 }
0304 
0305 void XYAnalysisCurve::setY2DataColumnPath(const QString& path) {
0306     Q_D(XYAnalysisCurve);
0307     d->y2DataColumnPath = path;
0308 }
0309 
0310 // ##############################################################################
0311 // #################################  SLOTS  ####################################
0312 // ##############################################################################
0313 void XYAnalysisCurve::handleSourceDataChanged() {
0314     Q_D(XYAnalysisCurve);
0315     d->sourceDataChangedSinceLastRecalc = true;
0316     Q_EMIT sourceDataChanged();
0317 }
0318 
0319 void XYAnalysisCurve::xDataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0320     Q_D(XYAnalysisCurve);
0321     if (aspect == d->xDataColumn) {
0322         d->xDataColumn = nullptr;
0323         d->retransform();
0324     }
0325 }
0326 
0327 void XYAnalysisCurve::yDataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0328     Q_D(XYAnalysisCurve);
0329     if (aspect == d->yDataColumn) {
0330         d->yDataColumn = nullptr;
0331         d->retransform();
0332     }
0333 }
0334 
0335 void XYAnalysisCurve::y2DataColumnAboutToBeRemoved(const AbstractAspect* aspect) {
0336     Q_D(XYAnalysisCurve);
0337     if (aspect == d->y2DataColumn) {
0338         d->y2DataColumn = nullptr;
0339         d->retransform();
0340     }
0341 }
0342 
0343 void XYAnalysisCurve::xDataColumnNameChanged() {
0344     Q_D(XYAnalysisCurve);
0345     setXDataColumnPath(d->xDataColumn->path());
0346 }
0347 
0348 void XYAnalysisCurve::yDataColumnNameChanged() {
0349     Q_D(XYAnalysisCurve);
0350     setYDataColumnPath(d->yDataColumn->path());
0351 }
0352 
0353 void XYAnalysisCurve::y2DataColumnNameChanged() {
0354     Q_D(XYAnalysisCurve);
0355     setYDataColumnPath(d->y2DataColumn->path());
0356 }
0357 
0358 /*!
0359  * creates a new spreadsheet having the data with the results of the calculation.
0360  * the new spreadsheet is added to the current folder.
0361  */
0362 void XYAnalysisCurve::createDataSpreadsheet() {
0363     if (!xColumn() || !yColumn())
0364         return;
0365 
0366     auto* spreadsheet = new Spreadsheet(i18n("%1 - Data", name()));
0367     spreadsheet->removeColumns(0, spreadsheet->columnCount()); // remove default columns
0368     spreadsheet->setRowCount(xColumn()->rowCount());
0369 
0370     // x values
0371     auto* data = static_cast<const Column*>(xColumn())->data();
0372     auto* xColumn = new Column(QLatin1String("x"), *static_cast<QVector<double>*>(data));
0373     xColumn->setPlotDesignation(AbstractColumn::PlotDesignation::X);
0374     spreadsheet->addChild(xColumn);
0375 
0376     // y values
0377     data = static_cast<const Column*>(yColumn())->data();
0378     auto* yColumn = new Column(QLatin1String("y"), *static_cast<QVector<double>*>(data));
0379     yColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0380     spreadsheet->addChild(yColumn);
0381 
0382     // residual values for fit curves
0383     if (type() == AspectType::XYFitCurve) {
0384         data = static_cast<const Column*>(static_cast<XYFitCurve*>(this)->residualsColumn())->data();
0385         auto* residualsColumn = new Column(QLatin1String("residuals"), *static_cast<QVector<double>*>(data));
0386         residualsColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0387         spreadsheet->addChild(residualsColumn);
0388     } else if (type() == AspectType::XYSmoothCurve) {
0389         // rough values for smooth curves
0390         data = static_cast<const Column*>(static_cast<XYSmoothCurve*>(this)->roughsColumn())->data();
0391         auto* roughsColumn = new Column(QLatin1String("rough values"), *static_cast<QVector<double>*>(data));
0392         roughsColumn->setPlotDesignation(AbstractColumn::PlotDesignation::Y);
0393         spreadsheet->addChild(roughsColumn);
0394     }
0395 
0396     // add the new spreadsheet to the current folder
0397     folder()->addChild(spreadsheet);
0398 }
0399 
0400 // ##############################################################################
0401 // ######################### Private implementation #############################
0402 // ##############################################################################
0403 XYAnalysisCurvePrivate::XYAnalysisCurvePrivate(XYAnalysisCurve* owner)
0404     : XYCurvePrivate(owner)
0405     , q(owner) {
0406 }
0407 
0408 // no need to delete xColumn and yColumn, they are deleted
0409 // when the parent aspect is removed
0410 XYAnalysisCurvePrivate::~XYAnalysisCurvePrivate() = default;
0411 
0412 void XYAnalysisCurvePrivate::prepareTmpDataColumn(const AbstractColumn** tmpXDataColumn, const AbstractColumn** tmpYDataColumn) {
0413     if (dataSourceType == XYAnalysisCurve::DataSourceType::Spreadsheet) {
0414         // spreadsheet columns as data source
0415         *tmpXDataColumn = xDataColumn;
0416         *tmpYDataColumn = yDataColumn;
0417     } else {
0418         // curve columns as data source
0419         *tmpXDataColumn = dataSourceCurve->xColumn();
0420         *tmpYDataColumn = dataSourceCurve->yColumn();
0421     }
0422 }
0423 
0424 void XYAnalysisCurvePrivate::recalculate() {
0425     // create filter result columns if not available yet, clear them otherwise
0426     if (!xColumn) {
0427         xColumn = new Column(QStringLiteral("x"), AbstractColumn::ColumnMode::Double);
0428         yColumn = new Column(QStringLiteral("y"), AbstractColumn::ColumnMode::Double);
0429         xVector = static_cast<QVector<double>*>(xColumn->data());
0430         yVector = static_cast<QVector<double>*>(yColumn->data());
0431 
0432         xColumn->setHidden(true);
0433         q->addChild(xColumn);
0434         yColumn->setHidden(true);
0435         q->addChild(yColumn);
0436 
0437         q->setUndoAware(false);
0438         q->setXColumn(xColumn);
0439         q->setYColumn(yColumn);
0440         q->setUndoAware(true);
0441     } else {
0442         xColumn->invalidateProperties();
0443         yColumn->invalidateProperties();
0444         xVector->clear();
0445         yVector->clear();
0446     }
0447 
0448     resetResults();
0449 
0450     const AbstractColumn* tmpXDataColumn = nullptr;
0451     const AbstractColumn* tmpYDataColumn = nullptr;
0452     prepareTmpDataColumn(&tmpXDataColumn, &tmpYDataColumn);
0453 
0454     if (!preparationValid(tmpXDataColumn, tmpYDataColumn)) {
0455         sourceDataChangedSinceLastRecalc = false;
0456         // recalcLogicalPoints(); TODO: needed?
0457     } else {
0458         bool result = recalculateSpecific(tmpXDataColumn, tmpYDataColumn);
0459         sourceDataChangedSinceLastRecalc = false;
0460 
0461         if (result) {
0462             // redraw the curve
0463             recalcLogicalPoints();
0464         }
0465     }
0466     Q_EMIT q->dataChanged();
0467 }
0468 
0469 bool XYAnalysisCurvePrivate::preparationValid(const AbstractColumn* tmpXDataColumn, const AbstractColumn* tmpYDataColumn) {
0470     return tmpXDataColumn && tmpYDataColumn;
0471 }
0472 
0473 // ##############################################################################
0474 // ##################  Serialization/Deserialization  ###########################
0475 // ##############################################################################
0476 //! Save as XML
0477 void XYAnalysisCurve::save(QXmlStreamWriter* writer) const {
0478     Q_D(const XYAnalysisCurve);
0479 
0480     writer->writeStartElement(QStringLiteral("xyAnalysisCurve"));
0481 
0482     // write xy-curve information
0483     XYCurve::save(writer);
0484 
0485     // write data source specific information
0486     writer->writeStartElement(QStringLiteral("dataSource"));
0487     writer->writeAttribute(QStringLiteral("type"), QString::number(static_cast<int>(d->dataSourceType)));
0488     WRITE_PATH(d->dataSourceCurve, dataSourceCurve);
0489     WRITE_COLUMN(d->xDataColumn, xDataColumn);
0490     WRITE_COLUMN(d->yDataColumn, yDataColumn);
0491     WRITE_COLUMN(d->y2DataColumn, y2DataColumn);
0492     writer->writeEndElement();
0493 
0494     writer->writeEndElement(); //"xyAnalysisCurve"
0495 }
0496 
0497 //! Load from XML
0498 bool XYAnalysisCurve::load(XmlStreamReader* reader, bool preview) {
0499     Q_D(XYAnalysisCurve);
0500 
0501     QXmlStreamAttributes attribs;
0502     QString str;
0503 
0504     while (!reader->atEnd()) {
0505         reader->readNext();
0506         if (reader->isEndElement() && reader->name() == QLatin1String("xyAnalysisCurve"))
0507             break;
0508 
0509         if (!reader->isStartElement())
0510             continue;
0511 
0512         if (reader->name() == QLatin1String("xyCurve")) {
0513             if (!XYCurve::load(reader, preview))
0514                 return false;
0515         } else if (reader->name() == QLatin1String("dataSource")) {
0516             attribs = reader->attributes();
0517             READ_INT_VALUE("type", dataSourceType, XYAnalysisCurve::DataSourceType);
0518             READ_PATH(dataSourceCurve);
0519             READ_COLUMN(xDataColumn);
0520             READ_COLUMN(yDataColumn);
0521             READ_COLUMN(y2DataColumn);
0522         } else { // unknown element
0523             reader->raiseUnknownElementWarning();
0524             if (!reader->skipToEndElement())
0525                 return false;
0526         }
0527     }
0528 
0529     return true;
0530 }