diff --git a/src/backend/datapicker/DatapickerCurve.cpp b/src/backend/datapicker/DatapickerCurve.cpp index 6261321a5..9153ac4b3 100644 --- a/src/backend/datapicker/DatapickerCurve.cpp +++ b/src/backend/datapicker/DatapickerCurve.cpp @@ -1,637 +1,637 @@ /*************************************************************************** File : DatapickerCurve.cpp Project : LabPlot Description : container for Curve-Point and Datasheet/Spreadsheet of datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatapickerCurve.h" #include "backend/datapicker/DatapickerCurvePrivate.h" #include "backend/datapicker/Datapicker.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/datapicker/DatapickerPoint.h" #include #include #include #include /** * \class DatapickerCurve * \brief Top-level container for Curve-Point and Datasheet/Spreadsheet of datapicker. * \ingroup backend */ DatapickerCurve::DatapickerCurve(const QString &name) : AbstractAspect(name), d_ptr(new DatapickerCurvePrivate(this)) { init(); } DatapickerCurve::DatapickerCurve(const QString &name, DatapickerCurvePrivate *dd) : AbstractAspect(name), d_ptr(dd) { init(); } DatapickerCurve::~DatapickerCurve() { delete d_ptr; } void DatapickerCurve::init() { Q_D(DatapickerCurve); KConfig config; KConfigGroup group; group = config.group("DatapickerCurve"); d->posXColumn = NULL; d->posYColumn = NULL; d->posZColumn = NULL; d->plusDeltaXColumn = NULL; d->minusDeltaXColumn = NULL; d->plusDeltaYColumn = NULL; d->minusDeltaYColumn = NULL; d->curveErrorTypes.x = (ErrorType) group.readEntry("CurveErrorType_X", (int) NoError); d->curveErrorTypes.y = (ErrorType) group.readEntry("CurveErrorType_X", (int) NoError); // point properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); d->pointRotationAngle = group.readEntry("Rotation", 0.0); d->pointOpacity = group.readEntry("Opacity", 1.0); d->pointBrush.setStyle( (Qt::BrushStyle)group.readEntry("FillingStyle", (int)Qt::NoBrush) ); d->pointBrush.setColor( group.readEntry("FillingColor", QColor(Qt::black)) ); d->pointPen.setStyle( (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine) ); d->pointPen.setColor( group.readEntry("BorderColor", QColor(Qt::red)) ); d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointErrorBarSize = group.readEntry("ErrorBarSize", Worksheet::convertToSceneUnits(8, Worksheet::Point)); d->pointErrorBarBrush.setStyle( (Qt::BrushStyle)group.readEntry("ErrorBarFillingStyle", (int)Qt::NoBrush) ); d->pointErrorBarBrush.setColor( group.readEntry("ErrorBarFillingColor", QColor(Qt::black)) ); d->pointErrorBarPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarBorderStyle", (int)Qt::SolidLine) ); d->pointErrorBarPen.setColor( group.readEntry("ErrorBarBorderColor", QColor(Qt::black)) ); d->pointErrorBarPen.setWidthF( group.readEntry("ErrorBarBorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointVisibility = group.readEntry("PointVisibility", true); this->initAction(); } void DatapickerCurve::initAction() { updateDatasheetAction = new QAction(QIcon::fromTheme("view-refresh"), i18n("Update Spreadsheet"), this); connect(updateDatasheetAction, &QAction::triggered, this, &DatapickerCurve::updateDatasheet); } /*! Returns an icon to be used in the project explorer. */ QIcon DatapickerCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } /*! Return a new context menu */ QMenu* DatapickerCurve::createContextMenu() { QMenu *menu = AbstractAspect::createContextMenu(); Q_ASSERT(menu); QAction* firstAction = 0; if (menu->actions().size()>1) firstAction = menu->actions().at(1); menu->insertAction(firstAction, updateDatasheetAction); return menu; } Column* DatapickerCurve::appendColumn(const QString& name) { Column* col = new Column(i18n("Column"), AbstractColumn::Numeric); col->insertRows(0, m_datasheet->rowCount()); col->setName(name); m_datasheet->addChild(col); return col; } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(DatapickerCurve, DatapickerCurve::Errors, curveErrorTypes, curveErrorTypes) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, Symbol::Style, pointStyle, pointStyle) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointOpacity, pointOpacity) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointRotationAngle, pointRotationAngle) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointSize, pointSize) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QBrush, pointBrush, pointBrush) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QPen, pointPen, pointPen) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, qreal, pointErrorBarSize, pointErrorBarSize) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QBrush, pointErrorBarBrush, pointErrorBarBrush) CLASS_SHARED_D_READER_IMPL(DatapickerCurve, QPen, pointErrorBarPen, pointErrorBarPen) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, bool, pointVisibility, pointVisibility) BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posXColumn, posXColumn) QString& DatapickerCurve::posXColumnPath() const { return d_ptr->posXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posYColumn, posYColumn) QString& DatapickerCurve::posYColumnPath() const { return d_ptr->posYColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, posZColumn, posZColumn) QString& DatapickerCurve::posZColumnPath() const { return d_ptr->posZColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaXColumn, plusDeltaXColumn) QString& DatapickerCurve::plusDeltaXColumnPath() const { return d_ptr->plusDeltaXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaXColumn, minusDeltaXColumn) QString& DatapickerCurve::minusDeltaXColumnPath() const { return d_ptr->minusDeltaXColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, plusDeltaYColumn, plusDeltaYColumn) QString& DatapickerCurve::plusDeltaYColumnPath() const { return d_ptr->plusDeltaYColumnPath; } BASIC_SHARED_D_READER_IMPL(DatapickerCurve, AbstractColumn*, minusDeltaYColumn, minusDeltaYColumn) QString& DatapickerCurve::minusDeltaYColumnPath() const { return d_ptr->minusDeltaYColumnPath; } //############################################################################## //######################### setter methods ################################### //############################################################################## void DatapickerCurve::addDatasheet(DatapickerImage::GraphType type) { Q_D(DatapickerCurve); m_datasheet = new Spreadsheet(0, i18n("Data")); addChild(m_datasheet); QString xLabel('x'); QString yLabel('y'); if (type == DatapickerImage::PolarInDegree) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(deg)"); } else if (type == DatapickerImage::PolarInRadians) { xLabel = QLatin1String("r"); yLabel = QLatin1String("y(rad)"); } else if (type == DatapickerImage::LogarithmicX) { xLabel = QLatin1String("log(x)"); yLabel = QLatin1String("y"); } else if (type == DatapickerImage::LogarithmicY) { xLabel = QLatin1String("x"); yLabel = QLatin1String("log(y)"); } if (type == DatapickerImage::Ternary) d->posZColumn = appendColumn(i18n("c")); d->posXColumn = m_datasheet->column(0); d->posXColumn->setName(xLabel); d->posYColumn = m_datasheet->column(1); d->posYColumn->setName(yLabel); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetCurveErrorTypes, DatapickerCurve::Errors, curveErrorTypes) void DatapickerCurve::setCurveErrorTypes(const DatapickerCurve::Errors errors) { Q_D(DatapickerCurve); if (d->curveErrorTypes.x != errors.x || d->curveErrorTypes.y != errors.y) { beginMacro(i18n("%1: set xy-error type", name())); - exec(new DatapickerCurveSetCurveErrorTypesCmd(d, errors, i18n("%1: set xy-error type"))); + exec(new DatapickerCurveSetCurveErrorTypesCmd(d, errors, ki18n("%1: set xy-error type"))); if ( errors.x != NoError && !d->plusDeltaXColumn ) setPlusDeltaXColumn(appendColumn(QLatin1String("+delta_x"))); else if ( d->plusDeltaXColumn && errors.x == NoError ) { d->plusDeltaXColumn->remove(); d->plusDeltaXColumn = 0; } if ( errors.x == AsymmetricError && !d->minusDeltaXColumn ) setMinusDeltaXColumn(appendColumn(QLatin1String("-delta_x"))); else if ( d->minusDeltaXColumn && errors.x != AsymmetricError ) { d->minusDeltaXColumn->remove(); d->minusDeltaXColumn = 0; } if ( errors.y != NoError && !d->plusDeltaYColumn ) setPlusDeltaYColumn(appendColumn(QLatin1String("+delta_y"))); else if ( d->plusDeltaYColumn && errors.y == NoError ) { d->plusDeltaYColumn->remove(); d->plusDeltaYColumn = 0; } if ( errors.y == AsymmetricError && !d->minusDeltaYColumn ) setMinusDeltaYColumn(appendColumn(QLatin1String("-delta_y"))); else if ( d->minusDeltaYColumn && errors.y != AsymmetricError ) { d->minusDeltaYColumn->remove(); d->minusDeltaYColumn = 0; } endMacro(); } } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosXColumn, AbstractColumn*, posXColumn) void DatapickerCurve::setPosXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posXColumn != column) - exec(new DatapickerCurveSetPosXColumnCmd(d, column, i18n("%1: set position X column"))); + exec(new DatapickerCurveSetPosXColumnCmd(d, column, ki18n("%1: set position X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosYColumn, AbstractColumn*, posYColumn) void DatapickerCurve::setPosYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posYColumn != column) - exec(new DatapickerCurveSetPosYColumnCmd(d, column, i18n("%1: set position Y column"))); + exec(new DatapickerCurveSetPosYColumnCmd(d, column, ki18n("%1: set position Y column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPosZColumn, AbstractColumn*, posZColumn) void DatapickerCurve::setPosZColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->posZColumn != column) - exec(new DatapickerCurveSetPosZColumnCmd(d, column, i18n("%1: set position Z column"))); + exec(new DatapickerCurveSetPosZColumnCmd(d, column, ki18n("%1: set position Z column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaXColumn, AbstractColumn*, plusDeltaXColumn) void DatapickerCurve::setPlusDeltaXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->plusDeltaXColumn != column) - exec(new DatapickerCurveSetPlusDeltaXColumnCmd(d, column, i18n("%1: set +delta_X column"))); + exec(new DatapickerCurveSetPlusDeltaXColumnCmd(d, column, ki18n("%1: set +delta_X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaXColumn, AbstractColumn*, minusDeltaXColumn) void DatapickerCurve::setMinusDeltaXColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->minusDeltaXColumn != column) - exec(new DatapickerCurveSetMinusDeltaXColumnCmd(d, column, i18n("%1: set -delta_X column"))); + exec(new DatapickerCurveSetMinusDeltaXColumnCmd(d, column, ki18n("%1: set -delta_X column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetPlusDeltaYColumn, AbstractColumn*, plusDeltaYColumn) void DatapickerCurve::setPlusDeltaYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->plusDeltaYColumn != column) - exec(new DatapickerCurveSetPlusDeltaYColumnCmd(d, column, i18n("%1: set +delta_Y column"))); + exec(new DatapickerCurveSetPlusDeltaYColumnCmd(d, column, ki18n("%1: set +delta_Y column"))); } STD_SETTER_CMD_IMPL_S(DatapickerCurve, SetMinusDeltaYColumn, AbstractColumn*, minusDeltaYColumn) void DatapickerCurve::setMinusDeltaYColumn(AbstractColumn* column) { Q_D(DatapickerCurve); if (d->minusDeltaYColumn != column) - exec(new DatapickerCurveSetMinusDeltaYColumnCmd(d, column, i18n("%1: set -delta_Y column"))); + exec(new DatapickerCurveSetMinusDeltaYColumnCmd(d, column, ki18n("%1: set -delta_Y column"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointStyle, Symbol::Style, pointStyle, retransform) void DatapickerCurve::setPointStyle(Symbol::Style newStyle) { Q_D(DatapickerCurve); if (newStyle != d->pointStyle) - exec(new DatapickerCurveSetPointStyleCmd(d, newStyle, i18n("%1: set point's style"))); + exec(new DatapickerCurveSetPointStyleCmd(d, newStyle, ki18n("%1: set point's style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointSize, qreal, pointSize, retransform) void DatapickerCurve::setPointSize(qreal value) { Q_D(DatapickerCurve); if (!qFuzzyCompare(1 + value, 1 + d->pointSize)) - exec(new DatapickerCurveSetPointSizeCmd(d, value, i18n("%1: set point's size"))); + exec(new DatapickerCurveSetPointSizeCmd(d, value, ki18n("%1: set point's size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointRotationAngle, qreal, pointRotationAngle, retransform) void DatapickerCurve::setPointRotationAngle(qreal angle) { Q_D(DatapickerCurve); if (!qFuzzyCompare(1 + angle, 1 + d->pointRotationAngle)) - exec(new DatapickerCurveSetPointRotationAngleCmd(d, angle, i18n("%1: rotate point"))); + exec(new DatapickerCurveSetPointRotationAngleCmd(d, angle, ki18n("%1: rotate point"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointBrush, QBrush, pointBrush, retransform) void DatapickerCurve::setPointBrush(const QBrush& newBrush) { Q_D(DatapickerCurve); if (newBrush != d->pointBrush) - exec(new DatapickerCurveSetPointBrushCmd(d, newBrush, i18n("%1: set point's filling"))); + exec(new DatapickerCurveSetPointBrushCmd(d, newBrush, ki18n("%1: set point's filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointPen, QPen, pointPen, retransform) void DatapickerCurve::setPointPen(const QPen &newPen) { Q_D(DatapickerCurve); if (newPen != d->pointPen) - exec(new DatapickerCurveSetPointPenCmd(d, newPen, i18n("%1: set outline style"))); + exec(new DatapickerCurveSetPointPenCmd(d, newPen, ki18n("%1: set outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointOpacity, qreal, pointOpacity, retransform) void DatapickerCurve::setPointOpacity(qreal newOpacity) { Q_D(DatapickerCurve); if (newOpacity != d->pointOpacity) - exec(new DatapickerCurveSetPointOpacityCmd(d, newOpacity, i18n("%1: set point's opacity"))); + exec(new DatapickerCurveSetPointOpacityCmd(d, newOpacity, ki18n("%1: set point's opacity"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarSize, qreal, pointErrorBarSize, retransform) void DatapickerCurve::setPointErrorBarSize(qreal size) { Q_D(DatapickerCurve); if (size != d->pointErrorBarSize) - exec(new DatapickerCurveSetPointErrorBarSizeCmd(d, size, i18n("%1: set error bar size"))); + exec(new DatapickerCurveSetPointErrorBarSizeCmd(d, size, ki18n("%1: set error bar size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarBrush, QBrush, pointErrorBarBrush, retransform) void DatapickerCurve::setPointErrorBarBrush(const QBrush &brush) { Q_D(DatapickerCurve); if (brush != d->pointErrorBarBrush) - exec(new DatapickerCurveSetPointErrorBarBrushCmd(d, brush, i18n("%1: set error bar filling"))); + exec(new DatapickerCurveSetPointErrorBarBrushCmd(d, brush, ki18n("%1: set error bar filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointErrorBarPen, QPen, pointErrorBarPen, retransform) void DatapickerCurve::setPointErrorBarPen(const QPen &pen) { Q_D(DatapickerCurve); if (pen != d->pointErrorBarPen) - exec(new DatapickerCurveSetPointErrorBarPenCmd(d, pen, i18n("%1: set error bar outline style"))); + exec(new DatapickerCurveSetPointErrorBarPenCmd(d, pen, ki18n("%1: set error bar outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerCurve, SetPointVisibility, bool, pointVisibility, retransform) void DatapickerCurve::setPointVisibility(bool on) { Q_D(DatapickerCurve); if (on != d->pointVisibility) - exec(new DatapickerCurveSetPointVisibilityCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new DatapickerCurveSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } void DatapickerCurve::setPrinting(bool on) { for (auto* point : children(IncludeHidden)) point->setPrinting(on); } /*! Selects or deselects the Datapicker/Curve in the project explorer. This function is called in \c DatapickerImageView. */ void DatapickerCurve::setSelectedInView(bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void DatapickerCurve::updateDatasheet() { beginMacro(i18n("%1: update datasheet", name())); for (auto* point : children(IncludeHidden)) updateData(point); endMacro(); } /*! Update datasheet for corresponding curve-point, it is called every time whenever there is any change in position of curve-point or its error-bar so keep it undo unaware no need to create extra entry in undo stack */ void DatapickerCurve::updateData(const DatapickerPoint* point) { Q_D(DatapickerCurve); Datapicker* datapicker = dynamic_cast(parentAspect()); if (!datapicker) return; int row = indexOfChild(point, AbstractAspect::IncludeHidden); QVector3D data = datapicker->mapSceneToLogical(point->position()); if(d->posXColumn) d->posXColumn->setValueAt(row, data.x()); if(d->posYColumn) d->posYColumn->setValueAt(row, data.y()); if(d->posZColumn) d->posZColumn->setValueAt(row, data.y()); if (d->plusDeltaXColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(point->plusDeltaXPos().x(), 0)); d->plusDeltaXColumn->setValueAt(row, qAbs(data.x())); } if (d->minusDeltaXColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(point->minusDeltaXPos().x(), 0)); d->minusDeltaXColumn->setValueAt(row, qAbs(data.x())); } if (d->plusDeltaYColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(0, point->plusDeltaYPos().y())); d->plusDeltaYColumn->setValueAt(row, qAbs(data.y())); } if (d->minusDeltaYColumn) { data = datapicker->mapSceneLengthToLogical(QPointF(0, point->minusDeltaYPos().y())); d->minusDeltaYColumn->setValueAt(row, qAbs(data.y())); } } //############################################################################## //####################### Private implementation ############################### //############################################################################## DatapickerCurvePrivate::DatapickerCurvePrivate(DatapickerCurve *curve) : q(curve) { } QString DatapickerCurvePrivate::name() const { return q->name(); } void DatapickerCurvePrivate::retransform() { QVector childrenPoints = q->children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->retransform(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerCurve::save(QXmlStreamWriter* writer) const { Q_D(const DatapickerCurve); writer->writeStartElement( "datapickerCurve" ); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->posXColumn, posXColumn); WRITE_COLUMN(d->posYColumn, posYColumn); WRITE_COLUMN(d->posZColumn, posZColumn); WRITE_COLUMN(d->plusDeltaXColumn, plusDeltaXColumn); WRITE_COLUMN(d->minusDeltaXColumn, minusDeltaXColumn); WRITE_COLUMN(d->plusDeltaYColumn, plusDeltaYColumn); WRITE_COLUMN(d->minusDeltaYColumn, minusDeltaYColumn); writer->writeAttribute( "curveErrorType_X", QString::number(d->curveErrorTypes.x) ); writer->writeAttribute( "curveErrorType_Y", QString::number(d->curveErrorTypes.y) ); writer->writeEndElement(); //symbol properties writer->writeStartElement( "symbolProperties" ); writer->writeAttribute( "pointRotationAngle", QString::number(d->pointRotationAngle) ); writer->writeAttribute( "pointOpacity", QString::number(d->pointOpacity) ); writer->writeAttribute( "pointSize", QString::number(d->pointSize) ); writer->writeAttribute( "pointStyle", QString::number(d->pointStyle) ); writer->writeAttribute( "pointVisibility", QString::number(d->pointVisibility) ); WRITE_QBRUSH(d->pointBrush); WRITE_QPEN(d->pointPen); writer->writeEndElement(); //error bar properties writer->writeStartElement( "errorBarProperties" ); writer->writeAttribute( "pointErrorBarSize", QString::number(d->pointErrorBarSize) ); WRITE_QBRUSH(d->pointErrorBarBrush); WRITE_QPEN(d->pointErrorBarPen); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); // close section } //! Load from XML bool DatapickerCurve::load(XmlStreamReader* reader, bool preview) { Q_D(DatapickerCurve); if(!reader->isStartElement() || reader->name() != "datapickerCurve") { reader->raiseError(i18n("no dataPicker curve element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "datapickerCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("curveErrorType_X").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("curveErrorType_X")); else d->curveErrorTypes.x = ErrorType(str.toInt()); str = attribs.value("curveErrorType_Y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("curveErrorType_Y")); else d->curveErrorTypes.y = ErrorType(str.toInt()); READ_COLUMN(posXColumn); READ_COLUMN(posYColumn); READ_COLUMN(posZColumn); READ_COLUMN(plusDeltaXColumn); READ_COLUMN(minusDeltaXColumn); READ_COLUMN(plusDeltaYColumn); READ_COLUMN(minusDeltaYColumn); } else if(!preview && reader->name() == "symbolProperties") { attribs = reader->attributes(); str = attribs.value("pointRotationAngle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointRotationAngle")); else d->pointRotationAngle = str.toFloat(); str = attribs.value("pointOpacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointOpacity")); else d->pointOpacity = str.toFloat(); str = attribs.value("pointSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointSize")); else d->pointSize = str.toFloat(); str = attribs.value("pointStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointStyle")); else d->pointStyle = (Symbol::Style)str.toInt(); str = attribs.value("pointVisibility").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointVisibility")); else d->pointVisibility = (bool)str.toInt(); READ_QBRUSH(d->pointBrush); READ_QPEN(d->pointPen); } else if(!preview && reader->name() == "errorBarProperties") { attribs = reader->attributes(); str = attribs.value("pointErrorBarSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointErrorBarSize")); else d->pointErrorBarSize = str.toFloat(); READ_QBRUSH(d->pointErrorBarBrush); READ_QPEN(d->pointErrorBarPen); } else if (reader->name() == "datapickerPoint") { DatapickerPoint* curvePoint = new DatapickerPoint(""); curvePoint->setHidden(true); if (!curvePoint->load(reader, preview)) { delete curvePoint; return false; } else { addChild(curvePoint); curvePoint->initErrorBar(curveErrorTypes()); } } else if (reader->name() == "spreadsheet") { Spreadsheet* datasheet = new Spreadsheet(0, "spreadsheet", true); if (!datasheet->load(reader, preview)) { delete datasheet; return false; } else { addChild(datasheet); m_datasheet = datasheet; } } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->retransform(); return true; } diff --git a/src/backend/datapicker/DatapickerImage.cpp b/src/backend/datapicker/DatapickerImage.cpp index 5e9d04165..28b8557a5 100644 --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -1,823 +1,823 @@ /*************************************************************************** File : DatapickerImage.cpp Project : LabPlot Description : Worksheet for Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatapickerImage.h" #include "DatapickerImagePrivate.h" #include "backend/datapicker/ImageEditor.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datapicker/DatapickerPoint.h" #include "backend/datapicker/Segments.h" #include "backend/worksheet/Worksheet.h" #include "commonfrontend/datapicker/DatapickerImageView.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include #include #include #include #include #include #include #include /** * \class DatapickerImage * \brief container to open image/plot. * * Top-level container for DatapickerPoint. * * * \ingroup datapicker */ DatapickerImage::DatapickerImage(AbstractScriptingEngine* engine, const QString& name, bool loading) : AbstractPart(name), scripted(engine), isLoaded(false), foregroundBins( new int[ImageEditor::colorAttributeMax(Foreground) + 1]), hueBins( new int[ImageEditor::colorAttributeMax(Hue) + 1]), saturationBins( new int[ImageEditor::colorAttributeMax(Saturation) + 1]), valueBins( new int[ImageEditor::colorAttributeMax(Value) + 1]), intensityBins( new int[ImageEditor::colorAttributeMax(Intensity) + 1]), m_magnificationWindow(nullptr), d(new DatapickerImagePrivate(this)), m_view(nullptr), m_segments(new Segments(this)) { if (!loading) init(); } DatapickerImage::~DatapickerImage() { delete [] hueBins; delete [] saturationBins; delete [] valueBins; delete [] intensityBins; delete [] foregroundBins; delete m_segments; delete d; } void DatapickerImage::init() { KConfig config; KConfigGroup group = config.group( "DatapickerImage" ); d->fileName = group.readEntry("FileName", QString()); d->rotationAngle = group.readEntry("RotationAngle", 0.0); d->minSegmentLength = group.readEntry("MinSegmentLength", 30); d->pointSeparation = group.readEntry("PointSeparation", 30); d->axisPoints.type = (DatapickerImage::GraphType) group.readEntry("GraphType", (int) DatapickerImage::Cartesian); d->axisPoints.ternaryScale = group.readEntry("TernaryScale", 1); d->settings.foregroundThresholdHigh = group.readEntry("ForegroundThresholdHigh", 90); d->settings.foregroundThresholdLow = group.readEntry("ForegroundThresholdLow", 30); d->settings.hueThresholdHigh = group.readEntry("HueThresholdHigh", 360); d->settings.hueThresholdLow = group.readEntry("HueThresholdLow", 0); d->settings.intensityThresholdHigh = group.readEntry("IntensityThresholdHigh", 100); d->settings.intensityThresholdLow = group.readEntry("IntensityThresholdLow", 20); d->settings.saturationThresholdHigh = group.readEntry("SaturationThresholdHigh", 100); d->settings.saturationThresholdLow = group.readEntry("SaturationThresholdLow", 30); d->settings.valueThresholdHigh = group.readEntry("ValueThresholdHigh", 90); d->settings.valueThresholdLow = group.readEntry("ValueThresholdLow", 30); d->plotPointsType = (DatapickerImage::PointsType) group.readEntry("PlotPointsType", (int) DatapickerImage::AxisPoints); d->plotImageType = DatapickerImage::OriginalImage; // point properties d->pointStyle = (Symbol::Style)group.readEntry("PointStyle", (int)Symbol::Cross); d->pointSize = group.readEntry("Size", Worksheet::convertToSceneUnits(7, Worksheet::Point)); d->pointRotationAngle = group.readEntry("Rotation", 0.0); d->pointOpacity = group.readEntry("Opacity", 1.0); d->pointBrush.setStyle( (Qt::BrushStyle)group.readEntry("FillingStyle", (int)Qt::NoBrush) ); d->pointBrush.setColor( group.readEntry("FillingColor", QColor(Qt::black)) ); d->pointPen.setStyle( (Qt::PenStyle)group.readEntry("BorderStyle", (int)Qt::SolidLine) ); d->pointPen.setColor( group.readEntry("BorderColor", QColor(Qt::red)) ); d->pointPen.setWidthF( group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1, Worksheet::Point)) ); d->pointVisibility = group.readEntry("PointVisibility", true); } /*! Returns an icon to be used in the project explorer. */ QIcon DatapickerImage::icon() const { return QIcon::fromTheme("image-x-generic"); } /*! Return a new context menu */ QMenu* DatapickerImage::createContextMenu() { QMenu* menu = new QMenu(0); emit requestProjectContextMenu(menu); return menu; } void DatapickerImage::createContextMenu(QMenu* menu) { emit requestProjectContextMenu(menu); } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* DatapickerImage::view() const { if (!m_partView) { m_view = new DatapickerImageView(const_cast(this)); m_partView = m_view; connect(m_view, &DatapickerImageView::statusInfo, this, &DatapickerImage::statusInfo); } return m_partView; } bool DatapickerImage::exportView() const { ExportWorksheetDialog* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, resolution); RESET_CURSOR; } delete dlg; return ret; } bool DatapickerImage::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18n("Print Datapicker Image")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool DatapickerImage::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &DatapickerImageView::print); return dlg->exec(); } /*! Selects or deselects the Datapicker/DatapickerImage in the project explorer. This function is called in \c DatapickerImageView. The DatapickerImage gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void DatapickerImage::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void DatapickerImage::setSegmentsHoverEvent(const bool on) { m_segments->setAcceptHoverEvents(on); } QGraphicsScene* DatapickerImage::scene() const { return d->m_scene; } QRectF DatapickerImage::pageRect() const { return d->m_scene->sceneRect(); } void DatapickerImage::setPlotImageType(const DatapickerImage::PlotImageType type) { d->plotImageType = type; if (d->plotImageType == DatapickerImage::ProcessedImage) d->discretize(); emit requestUpdate(); } DatapickerImage::PlotImageType DatapickerImage::plotImageType() { return d->plotImageType; } void DatapickerImage::initSceneParameters() { setRotationAngle(0.0); setminSegmentLength(30); setPointSeparation(30); ReferencePoints axisPoints = d->axisPoints; axisPoints.ternaryScale = 1; axisPoints.type = DatapickerImage::Cartesian; setAxisPoints(axisPoints); EditorSettings settings; settings.foregroundThresholdHigh = 90; settings.foregroundThresholdLow = 30; settings.hueThresholdHigh = 360; settings.hueThresholdLow = 0; settings.intensityThresholdHigh = 100; settings.intensityThresholdLow = 20; settings.saturationThresholdHigh = 100; settings.saturationThresholdLow = 30; settings.valueThresholdHigh = 90; settings.valueThresholdLow = 30; setSettings(settings); DatapickerImage::PointsType plotPointsType = DatapickerImage::AxisPoints; setPlotPointsType(plotPointsType); } /* =============================== getter methods for background options ================================= */ CLASS_D_READER_IMPL(DatapickerImage, QString, fileName, fileName) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::ReferencePoints, axisPoints, axisPoints) CLASS_D_READER_IMPL(DatapickerImage, DatapickerImage::EditorSettings, settings, settings) BASIC_D_READER_IMPL(DatapickerImage, float, rotationAngle, rotationAngle) BASIC_D_READER_IMPL(DatapickerImage, DatapickerImage::PointsType, plotPointsType, plotPointsType) BASIC_D_READER_IMPL(DatapickerImage, int, pointSeparation, pointSeparation) BASIC_D_READER_IMPL(DatapickerImage, int, minSegmentLength, minSegmentLength) BASIC_D_READER_IMPL(DatapickerImage, Symbol::Style, pointStyle, pointStyle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointOpacity, pointOpacity) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointRotationAngle, pointRotationAngle) BASIC_D_READER_IMPL(DatapickerImage, qreal, pointSize, pointSize) CLASS_D_READER_IMPL(DatapickerImage, QBrush, pointBrush, pointBrush) CLASS_D_READER_IMPL(DatapickerImage, QPen, pointPen, pointPen) BASIC_D_READER_IMPL(DatapickerImage, bool, pointVisibility, pointVisibility) /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetFileName, QString, fileName, updateFileName) void DatapickerImage::setFileName(const QString& fileName) { if (fileName!= d->fileName) { beginMacro(i18n("%1: upload new image", name())); - exec(new DatapickerImageSetFileNameCmd(d, fileName, i18n("%1: upload image"))); + exec(new DatapickerImageSetFileNameCmd(d, fileName, ki18n("%1: upload image"))); endMacro(); } } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetRotationAngle, float, rotationAngle) void DatapickerImage::setRotationAngle(float angle) { if (angle != d->rotationAngle) - exec(new DatapickerImageSetRotationAngleCmd(d, angle, i18n("%1: set rotation angle"))); + exec(new DatapickerImageSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_S(DatapickerImage, SetAxisPoints, DatapickerImage::ReferencePoints, axisPoints) void DatapickerImage::setAxisPoints(const DatapickerImage::ReferencePoints& points) { if (memcmp(&points, &d->axisPoints, sizeof(points)) != 0) - exec(new DatapickerImageSetAxisPointsCmd(d, points, i18n("%1: set Axis points"))); + exec(new DatapickerImageSetAxisPointsCmd(d, points, ki18n("%1: set Axis points"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetSettings, DatapickerImage::EditorSettings, settings, discretize) void DatapickerImage::setSettings(const DatapickerImage::EditorSettings& editorSettings) { if (memcmp(&editorSettings, &d->settings, sizeof(editorSettings)) != 0) - exec(new DatapickerImageSetSettingsCmd(d, editorSettings, i18n("%1: set editor settings"))); + exec(new DatapickerImageSetSettingsCmd(d, editorSettings, ki18n("%1: set editor settings"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetMinSegmentLength, int, minSegmentLength, makeSegments) void DatapickerImage::setminSegmentLength(const int value) { if (d->minSegmentLength != value) - exec(new DatapickerImageSetMinSegmentLengthCmd(d, value, i18n("%1: set minimum segment length"))); ; + exec(new DatapickerImageSetMinSegmentLengthCmd(d, value, ki18n("%1: set minimum segment length"))); ; } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointStyle, Symbol::Style, pointStyle, retransform) void DatapickerImage::setPointStyle(Symbol::Style newStyle) { if (newStyle != d->pointStyle) - exec(new DatapickerImageSetPointStyleCmd(d, newStyle, i18n("%1: set point's style"))); + exec(new DatapickerImageSetPointStyleCmd(d, newStyle, ki18n("%1: set point's style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointSize, qreal, pointSize, retransform) void DatapickerImage::setPointSize(qreal value) { if (!qFuzzyCompare(1 + value, 1 + d->pointSize)) - exec(new DatapickerImageSetPointSizeCmd(d, value, i18n("%1: set point's size"))); + exec(new DatapickerImageSetPointSizeCmd(d, value, ki18n("%1: set point's size"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointRotationAngle, qreal, pointRotationAngle, retransform) void DatapickerImage::setPointRotationAngle(qreal angle) { if (!qFuzzyCompare(1 + angle, 1 + d->pointRotationAngle)) - exec(new DatapickerImageSetPointRotationAngleCmd(d, angle, i18n("%1: rotate point"))); + exec(new DatapickerImageSetPointRotationAngleCmd(d, angle, ki18n("%1: rotate point"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointBrush, QBrush, pointBrush, retransform) void DatapickerImage::setPointBrush(const QBrush& newBrush) { if (newBrush != d->pointBrush) - exec(new DatapickerImageSetPointBrushCmd(d, newBrush, i18n("%1: set point's filling"))); + exec(new DatapickerImageSetPointBrushCmd(d, newBrush, ki18n("%1: set point's filling"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointPen, QPen, pointPen, retransform) void DatapickerImage::setPointPen(const QPen &newPen) { if (newPen != d->pointPen) - exec(new DatapickerImageSetPointPenCmd(d, newPen, i18n("%1: set outline style"))); + exec(new DatapickerImageSetPointPenCmd(d, newPen, ki18n("%1: set outline style"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointOpacity, qreal, pointOpacity, retransform) void DatapickerImage::setPointOpacity(qreal newOpacity) { if (newOpacity != d->pointOpacity) - exec(new DatapickerImageSetPointOpacityCmd(d, newOpacity, i18n("%1: set point's opacity"))); + exec(new DatapickerImageSetPointOpacityCmd(d, newOpacity, ki18n("%1: set point's opacity"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerImage, SetPointVisibility, bool, pointVisibility, retransform) void DatapickerImage::setPointVisibility(const bool on) { if (on != d->pointVisibility) - exec(new DatapickerImageSetPointVisibilityCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new DatapickerImageSetPointVisibilityCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } void DatapickerImage::setPrinting(bool on) const { QVector childPoints = parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* point : childPoints) point->setPrinting(on); } void DatapickerImage::setPlotPointsType(const PointsType pointsType) { d->plotPointsType = pointsType; if (pointsType == DatapickerImage::AxisPoints) { //clear image int childCount = this->childCount(AbstractAspect::IncludeHidden); if (childCount) { beginMacro(i18n("%1: remove all axis points", name())); QVector childrenPoints = children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->remove(); endMacro(); } m_segments->setSegmentsVisible(false); } else if (pointsType==DatapickerImage::CurvePoints) m_segments->setSegmentsVisible(false); else if (pointsType==DatapickerImage::SegmentPoints) { d->makeSegments(); m_segments->setSegmentsVisible(true); } } void DatapickerImage::setPointSeparation(const int value) { d->pointSeparation = value; } //############################################################################## //###################### Private implementation ############################### //############################################################################## DatapickerImagePrivate::DatapickerImagePrivate(DatapickerImage *owner):q(owner), pageRect(0, 0, 1500, 1500), m_scene(new QGraphicsScene(pageRect)) { } QString DatapickerImagePrivate::name() const { return q->name(); } void DatapickerImagePrivate::retransform() { QVector childrenPoints = q->children(AbstractAspect::IncludeHidden); for (auto* point : childrenPoints) point->retransform(); } bool DatapickerImagePrivate::uploadImage(const QString& address) { bool rc = q->originalPlotImage.load(address); if (rc) { //convert the image to 32bit-format if this is not the case yet QImage::Format format = q->originalPlotImage.format(); if (format != QImage::Format_RGB32 && format != QImage::Format_ARGB32 && format != QImage::Format_ARGB32_Premultiplied) q->originalPlotImage = q->originalPlotImage.convertToFormat(QImage::Format_RGB32); q->processedPlotImage = q->originalPlotImage; q->background = ImageEditor::findBackgroundColor(&q->originalPlotImage); //upload Histogram ImageEditor::uploadHistogram(q->intensityBins, &q->originalPlotImage, q->background, DatapickerImage::Intensity); ImageEditor::uploadHistogram(q->foregroundBins, &q->originalPlotImage, q->background, DatapickerImage::Foreground); ImageEditor::uploadHistogram(q->hueBins, &q->originalPlotImage, q->background, DatapickerImage::Hue); ImageEditor::uploadHistogram(q->saturationBins, &q->originalPlotImage, q->background, DatapickerImage::Saturation); ImageEditor::uploadHistogram(q->valueBins, &q->originalPlotImage, q->background, DatapickerImage::Value); discretize(); //resize the screen double w = Worksheet::convertToSceneUnits(q->originalPlotImage.width(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); double h = Worksheet::convertToSceneUnits(q->originalPlotImage.height(), Worksheet::Inch)/QApplication::desktop()->physicalDpiX(); m_scene->setSceneRect(0, 0, w, h); q->isLoaded = true; } return rc; } void DatapickerImagePrivate::discretize() { if (plotImageType != DatapickerImage::ProcessedImage) return; ImageEditor::discretize(&q->processedPlotImage, &q->originalPlotImage, settings, q->background); if (plotPointsType != DatapickerImage::SegmentPoints) emit q->requestUpdate(); else makeSegments(); } void DatapickerImagePrivate::makeSegments() { if (plotPointsType != DatapickerImage::SegmentPoints) return; q->m_segments->makeSegments(q->processedPlotImage); q->m_segments->setSegmentsVisible(true); emit q->requestUpdate(); } DatapickerImagePrivate::~DatapickerImagePrivate() { delete m_scene; } void DatapickerImagePrivate::updateFileName() { WAIT_CURSOR; q->isLoaded = false; const QString& address = fileName.trimmed(); if ( !address.isEmpty() ) { if (uploadImage(address)) { q->initSceneParameters(); fileName = address; } } else { //hide segments if they are visible q->m_segments->setSegmentsVisible(false); } QVector childPoints = q->parentAspect()->children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); if (childPoints.count()) { for (auto* point : childPoints) point->remove(); } emit q->requestUpdate(); emit q->requestUpdateActions(); RESET_CURSOR; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerImage::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "datapickerImage" ); writeBasicAttributes(writer); writeCommentElement(writer); //general properties writer->writeStartElement( "general" ); writer->writeAttribute( "fileName", d->fileName ); writer->writeAttribute( "plotPointsType", QString::number(d->plotPointsType) ); writer->writeEndElement(); writer->writeStartElement( "axisPoint" ); writer->writeAttribute( "graphType", QString::number(d->axisPoints.type) ); writer->writeAttribute( "ternaryScale", QString::number(d->axisPoints.ternaryScale) ); writer->writeAttribute( "axisPointLogicalX1", QString::number(d->axisPoints.logicalPos[0].x()) ); writer->writeAttribute( "axisPointLogicalY1", QString::number(d->axisPoints.logicalPos[0].y()) ); writer->writeAttribute( "axisPointLogicalX2", QString::number(d->axisPoints.logicalPos[1].x()) ); writer->writeAttribute( "axisPointLogicalY2", QString::number(d->axisPoints.logicalPos[1].y()) ); writer->writeAttribute( "axisPointLogicalX3", QString::number(d->axisPoints.logicalPos[2].x()) ); writer->writeAttribute( "axisPointLogicalY3", QString::number(d->axisPoints.logicalPos[2].y()) ); writer->writeAttribute( "axisPointLogicalZ1", QString::number(d->axisPoints.logicalPos[0].z()) ); writer->writeAttribute( "axisPointLogicalZ2", QString::number(d->axisPoints.logicalPos[1].z()) ); writer->writeAttribute( "axisPointLogicalZ3", QString::number(d->axisPoints.logicalPos[2].z()) ); writer->writeAttribute( "axisPointSceneX1", QString::number(d->axisPoints.scenePos[0].x()) ); writer->writeAttribute( "axisPointSceneY1", QString::number(d->axisPoints.scenePos[0].y()) ); writer->writeAttribute( "axisPointSceneX2", QString::number(d->axisPoints.scenePos[1].x()) ); writer->writeAttribute( "axisPointSceneY2", QString::number(d->axisPoints.scenePos[1].y()) ); writer->writeAttribute( "axisPointSceneX3", QString::number(d->axisPoints.scenePos[2].x()) ); writer->writeAttribute( "axisPointSceneY3", QString::number(d->axisPoints.scenePos[2].y()) ); writer->writeEndElement(); //editor and segment settings writer->writeStartElement( "editorSettings" ); writer->writeAttribute( "plotImageType", QString::number(d->plotImageType) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "minSegmentLength", QString::number(d->minSegmentLength) ); writer->writeAttribute( "pointSeparation", QString::number(d->pointSeparation) ); writer->writeAttribute( "foregroundThresholdHigh", QString::number(d->settings.foregroundThresholdHigh) ); writer->writeAttribute( "foregroundThresholdLow", QString::number(d->settings.foregroundThresholdLow) ); writer->writeAttribute( "hueThresholdHigh", QString::number(d->settings.hueThresholdHigh) ); writer->writeAttribute( "hueThresholdLow", QString::number(d->settings.hueThresholdLow) ); writer->writeAttribute( "intensityThresholdHigh", QString::number(d->settings.intensityThresholdHigh) ); writer->writeAttribute( "intensityThresholdLow", QString::number(d->settings.intensityThresholdLow) ); writer->writeAttribute( "saturationThresholdHigh", QString::number(d->settings.saturationThresholdHigh) ); writer->writeAttribute( "saturationThresholdLow", QString::number(d->settings.saturationThresholdLow) ); writer->writeAttribute( "valueThresholdHigh", QString::number(d->settings.valueThresholdHigh) ); writer->writeAttribute( "valueThresholdLow", QString::number(d->settings.valueThresholdLow) ); writer->writeEndElement(); //symbol properties writer->writeStartElement( "symbolProperties" ); writer->writeAttribute( "pointRotationAngle", QString::number(d->pointRotationAngle) ); writer->writeAttribute( "pointOpacity", QString::number(d->pointOpacity) ); writer->writeAttribute( "pointSize", QString::number(d->pointSize) ); writer->writeAttribute( "pointStyle", QString::number(d->pointStyle) ); writer->writeAttribute( "pointVisibility", QString::number(d->pointVisibility) ); WRITE_QBRUSH(d->pointBrush); WRITE_QPEN(d->pointPen); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); } //! Load from XML bool DatapickerImage::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "datapickerImage") { reader->raiseError(i18n("no image element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "datapickerImage") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("fileName").toString(); d->fileName = str; str = attribs.value("plotPointsType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("plotPointsType")); else d->plotPointsType = DatapickerImage::PointsType(str.toInt()); } else if (!preview && reader->name() == "axisPoint") { attribs = reader->attributes(); str = attribs.value("graphType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("graphType")); else d->axisPoints.type = DatapickerImage::GraphType(str.toInt()); str = attribs.value("ternaryScale").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("ternaryScale")); else d->axisPoints.ternaryScale = str.toDouble(); str = attribs.value("axisPointLogicalX1").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalX1")); else d->axisPoints.logicalPos[0].setX(str.toDouble()); str = attribs.value("axisPointLogicalY1").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalY1")); else d->axisPoints.logicalPos[0].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ1").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalZ1")); else d->axisPoints.logicalPos[0].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX2").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalX2")); else d->axisPoints.logicalPos[1].setX(str.toDouble()); str = attribs.value("axisPointLogicalY2").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalY2")); else d->axisPoints.logicalPos[1].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ2").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalZ2")); else d->axisPoints.logicalPos[1].setZ(str.toDouble()); str = attribs.value("axisPointLogicalX3").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalX3")); else d->axisPoints.logicalPos[2].setX(str.toDouble()); str = attribs.value("axisPointLogicalY3").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalY3")); else d->axisPoints.logicalPos[2].setY(str.toDouble()); str = attribs.value("axisPointLogicalZ3").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointLogicalZ3")); else d->axisPoints.logicalPos[2].setZ(str.toDouble()); str = attribs.value("axisPointSceneX1").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneX1")); else d->axisPoints.scenePos[0].setX(str.toDouble()); str = attribs.value("axisPointSceneY1").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneY1")); else d->axisPoints.scenePos[0].setY(str.toDouble()); str = attribs.value("axisPointSceneX2").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneX2")); else d->axisPoints.scenePos[1].setX(str.toDouble()); str = attribs.value("axisPointSceneY2").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneY2")); else d->axisPoints.scenePos[1].setY(str.toDouble()); str = attribs.value("axisPointSceneX3").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneX3")); else d->axisPoints.scenePos[2].setX(str.toDouble()); str = attribs.value("axisPointSceneY3").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("axisPointSceneY3")); else d->axisPoints.scenePos[2].setY(str.toDouble()); } else if (!preview && reader->name() == "editorSettings") { attribs = reader->attributes(); str = attribs.value("plotImageType").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("plotImageType")); else d->plotImageType = DatapickerImage::PlotImageType(str.toInt()); str = attribs.value("rotationAngle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rotationAngle")); else d->rotationAngle = str.toFloat(); str = attribs.value("minSegmentLength").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("minSegmentLength")); else d->minSegmentLength = str.toInt(); str = attribs.value("pointSeparation").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointSeparation")); else d->pointSeparation = str.toInt(); str = attribs.value("foregroundThresholdHigh").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("foregroundThresholdHigh")); else d->settings.foregroundThresholdHigh = str.toInt(); str = attribs.value("foregroundThresholdLow").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("foregroundThresholdLow")); else d->settings.foregroundThresholdLow = str.toInt(); str = attribs.value("hueThresholdHigh").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("hueThresholdHigh")); else d->settings.hueThresholdHigh = str.toInt(); str = attribs.value("hueThresholdLow").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("hueThresholdLow")); else d->settings.hueThresholdLow = str.toInt(); str = attribs.value("intensityThresholdHigh").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("intensityThresholdHigh")); else d->settings.intensityThresholdHigh = str.toInt(); str = attribs.value("intensityThresholdLow").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("intensityThresholdLow")); else d->settings.intensityThresholdLow = str.toInt(); str = attribs.value("saturationThresholdHigh").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("saturationThresholdHigh")); else d->settings.saturationThresholdHigh = str.toInt(); str = attribs.value("saturationThresholdLow").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("saturationThresholdLow")); else d->settings.saturationThresholdLow = str.toInt(); str = attribs.value("valueThresholdHigh").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("valueThresholdHigh")); else d->settings.valueThresholdHigh = str.toInt(); str = attribs.value("valueThresholdLow").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("valueThresholdLow")); else d->settings.valueThresholdLow = str.toInt(); } else if(!preview && reader->name() == "symbolProperties") { attribs = reader->attributes(); str = attribs.value("pointRotationAngle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointRotationAngle")); else d->pointRotationAngle = str.toFloat(); str = attribs.value("pointOpacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointOpacity")); else d->pointOpacity = str.toFloat(); str = attribs.value("pointSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointSize")); else d->pointSize = str.toFloat(); str = attribs.value("pointStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointStyle")); else d->pointStyle = (Symbol::Style)str.toInt(); str = attribs.value("pointVisibility").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("pointVisibility")); else d->pointVisibility = (bool)str.toInt(); READ_QBRUSH(d->pointBrush); READ_QPEN(d->pointPen); } else if(reader->name() == "datapickerPoint") { DatapickerPoint* datapickerPoint = new DatapickerPoint(""); datapickerPoint->setHidden(true); if (!datapickerPoint->load(reader, preview)) { delete datapickerPoint; return false; } else addChild(datapickerPoint); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->uploadImage(d->fileName); d->retransform(); return true; } diff --git a/src/backend/datapicker/DatapickerPoint.cpp b/src/backend/datapicker/DatapickerPoint.cpp index 77b89ccc2..e10ebafd7 100644 --- a/src/backend/datapicker/DatapickerPoint.cpp +++ b/src/backend/datapicker/DatapickerPoint.cpp @@ -1,556 +1,556 @@ /*************************************************************************** File : DatapickerPoint.cpp Project : LabPlot Description : Graphic Item for coordinate points of Datapicker -------------------------------------------------------------------- Copyright : (C) 2015 by Ankit Wagadre (wagadre.ankit@gmail.com) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "DatapickerPoint.h" #include "backend/worksheet/Worksheet.h" #include "DatapickerPointPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/datapicker/DatapickerCurve.h" #include #include #include #include #include #include #include QPen DatapickerPoint::selectedPen = QPen(Qt::darkBlue, 3, Qt::SolidLine); float DatapickerPoint::selectedOpacity = 0.3f; /** * \class ErrorBarItem * \brief A customizable error-bar for DatapickerPoint. */ ErrorBarItem::ErrorBarItem(DatapickerPoint *parent, const ErrorBarType& type) : QGraphicsRectItem(parent->graphicsItem()), barLineItem(new QGraphicsLineItem(parent->graphicsItem())), m_type(type), m_parentItem(parent) { setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); initRect(); } void ErrorBarItem::initRect() { QRectF xBarRect(-0.15, -0.5, 0.3, 1); QRectF yBarRect(-0.5, -0.15, 1, 0.3); if (m_type == PlusDeltaX || m_type == MinusDeltaX) m_rect = xBarRect; else m_rect = yBarRect; } void ErrorBarItem::setPosition(const QPointF& position) { setPos(position); barLineItem->setLine(0, 0, position.x(), position.y()); } void ErrorBarItem::setRectSize(const qreal size) { QMatrix matrix; matrix.scale(size, size); setRect(matrix.mapRect(m_rect)); } void ErrorBarItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) { if (m_type == PlusDeltaX) m_parentItem->setPlusDeltaXPos(pos()); else if (m_type == MinusDeltaX) m_parentItem->setMinusDeltaXPos(pos()); else if (m_type == PlusDeltaY) m_parentItem->setPlusDeltaYPos(pos()); else if (m_type == MinusDeltaY) m_parentItem->setMinusDeltaYPos(pos()); QGraphicsItem::mouseReleaseEvent(event); } QVariant ErrorBarItem::itemChange(QGraphicsItem::GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { QPointF newPos = value.toPointF(); barLineItem->setLine(0, 0, newPos.x(), newPos.y()); } return QGraphicsRectItem::itemChange(change, value); } /** * \class Datapicker-Point * \brief A customizable symbol supports error-bars. * * The datapicker-Point is aligned relative to the specified position. * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system, or by specifying one * of the predefined position flags (\ca HorizontalPosition, \ca VerticalPosition). */ DatapickerPoint::DatapickerPoint(const QString& name):AbstractAspect(name), d_ptr(new DatapickerPointPrivate(this)) { init(); } DatapickerPoint::DatapickerPoint(const QString& name, DatapickerPointPrivate *dd):AbstractAspect(name), d_ptr(dd) { init(); } DatapickerPoint::~DatapickerPoint() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void DatapickerPoint::init() { Q_D(DatapickerPoint); KConfig config; KConfigGroup group; group = config.group("DatapickerPoint"); d->position.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->position.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->plusDeltaXPos = group.readEntry("PlusDeltaXPos", QPointF(30, 0)); d->minusDeltaXPos = group.readEntry("MinusDeltaXPos", QPointF(-30, 0)); d->plusDeltaYPos = group.readEntry("PlusDeltaYPos", QPointF(0, -30)); d->minusDeltaYPos = group.readEntry("MinusDeltaYPos", QPointF(0, 30)); } void DatapickerPoint::initErrorBar(const DatapickerCurve::Errors& errors) { m_errorBarItemList.clear(); if (errors.x != DatapickerCurve::NoError) { ErrorBarItem* plusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::PlusDeltaX); plusDeltaXItem->setPosition(plusDeltaXPos()); connect(this, &DatapickerPoint::plusDeltaXPosChanged, plusDeltaXItem, &ErrorBarItem::setPosition); ErrorBarItem* minusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaX); minusDeltaXItem->setPosition(minusDeltaXPos()); connect(this, &DatapickerPoint::minusDeltaXPosChanged, minusDeltaXItem, &ErrorBarItem::setPosition); m_errorBarItemList<setPosition(plusDeltaYPos()); connect(this, &DatapickerPoint::plusDeltaYPosChanged, plusDeltaYItem, &ErrorBarItem::setPosition); ErrorBarItem* minusDeltaYItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaY); minusDeltaYItem->setPosition(minusDeltaYPos()); connect(this, &DatapickerPoint::minusDeltaYPosChanged, minusDeltaYItem, &ErrorBarItem::setPosition); m_errorBarItemList<setParentItem(item); } void DatapickerPoint::retransform() { Q_D(DatapickerPoint); d->retransform(); } /* ============================ getter methods ================= */ //point CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, position, position) //error-bar CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, plusDeltaXPos, plusDeltaXPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, minusDeltaXPos, minusDeltaXPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, plusDeltaYPos, plusDeltaYPos) CLASS_SHARED_D_READER_IMPL(DatapickerPoint, QPointF, minusDeltaYPos, minusDeltaYPos) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPosition, QPointF, position, retransform) void DatapickerPoint::setPosition(const QPointF& pos) { Q_D(DatapickerPoint); if (pos!=d->position) - exec(new DatapickerPointSetPositionCmd(d, pos, i18n("%1: set position"))); + exec(new DatapickerPointSetPositionCmd(d, pos, ki18n("%1: set position"))); } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPlusDeltaXPos, QPointF, plusDeltaXPos, updateData) void DatapickerPoint::setPlusDeltaXPos(const QPointF& pos) { Q_D(DatapickerPoint); if ( pos != d->plusDeltaXPos ) { DatapickerCurve* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set +delta_X position", name())); if (curve->curveErrorTypes().x == DatapickerCurve::SymmetricError) { - exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, i18n("%1: set +delta X position"))); + exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, ki18n("%1: set +delta X position"))); setMinusDeltaXPos(QPointF(-qAbs(pos.x()), pos.y())); } else - exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, i18n("%1: set +delta X position"))); + exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, ki18n("%1: set +delta X position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetMinusDeltaXPos, QPointF, minusDeltaXPos, updateData) void DatapickerPoint::setMinusDeltaXPos(const QPointF& pos) { Q_D(DatapickerPoint); if ( pos != d->minusDeltaXPos ) { DatapickerCurve* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set -delta_X position", name())); if (curve->curveErrorTypes().x == DatapickerCurve::SymmetricError) { - exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, i18n("%1: set -delta_X position"))); + exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, ki18n("%1: set -delta_X position"))); setPlusDeltaXPos(QPointF(qAbs(pos.x()), pos.y())); } else - exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, i18n("%1: set -delta_X position"))); + exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, ki18n("%1: set -delta_X position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetPlusDeltaYPos, QPointF, plusDeltaYPos, updateData) void DatapickerPoint::setPlusDeltaYPos(const QPointF& pos) { Q_D(DatapickerPoint); if ( pos != d->plusDeltaYPos ) { DatapickerCurve* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set +delta_Y position", name())); if (curve->curveErrorTypes().y == DatapickerCurve::SymmetricError) { - exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, i18n("%1: set +delta_Y position"))); + exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, ki18n("%1: set +delta_Y position"))); setMinusDeltaYPos(QPointF(pos.x(), qAbs(pos.y()))); } else - exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, i18n("%1: set +delta_Y position"))); + exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, ki18n("%1: set +delta_Y position"))); endMacro(); } } STD_SETTER_CMD_IMPL_F_S(DatapickerPoint, SetMinusDeltaYPos, QPointF, minusDeltaYPos, updateData) void DatapickerPoint::setMinusDeltaYPos(const QPointF& pos) { Q_D(DatapickerPoint); if ( pos != d->minusDeltaYPos ) { DatapickerCurve* curve = dynamic_cast(parentAspect()); if (!curve) return; beginMacro(i18n("%1: set -delta_Y position", name())); if (curve->curveErrorTypes().y == DatapickerCurve::SymmetricError) { - exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, i18n("%1: set -delta_Y position"))); + exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, ki18n("%1: set -delta_Y position"))); setPlusDeltaYPos(QPointF(pos.x(), -qAbs(pos.y()))); } else - exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, i18n("%1: set -delta_Y position"))); + exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, ki18n("%1: set -delta_Y position"))); endMacro(); } } void DatapickerPoint::setPrinting(bool on) { Q_D(DatapickerPoint); d->m_printing = on; } //############################################################################## //####################### Private implementation ############################### //############################################################################## DatapickerPointPrivate::DatapickerPointPrivate(DatapickerPoint* owner) : m_printing(false), q(owner) { setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString DatapickerPointPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void DatapickerPointPrivate::retransform() { updatePropeties(); setPos(position); QPainterPath path = Symbol::pathFromStyle(pointStyle); boundingRectangle = path.boundingRect(); recalcShapeAndBoundingRect(); retransformErrorBar(); updateData(); } /*! update color and size of all error-bar. */ void DatapickerPointPrivate::retransformErrorBar() { for(auto* item : q->m_errorBarItemList) { if (item) { item->setBrush(errorBarBrush); item->setPen(errorBarPen); item->setRectSize(errorBarSize); } } } /*! update datasheet on any change in position of Datapicker-Point or it's error-bar. */ void DatapickerPointPrivate::updateData() { DatapickerCurve* curve = dynamic_cast(q->parentAspect()); if (curve) curve->updateData(q); } void DatapickerPointPrivate::updatePropeties() { DatapickerCurve* curve = dynamic_cast(q->parentAspect()); DatapickerImage* image = dynamic_cast(q->parentAspect()); if (image) { rotationAngle = image->pointRotationAngle(); pointStyle = image->pointStyle(); brush = image->pointBrush(); pen = image->pointPen(); opacity = image->pointOpacity(); size = image->pointSize(); setVisible(image->pointVisibility()); } else if (curve) { rotationAngle = curve->pointRotationAngle(); pointStyle = curve->pointStyle(); brush = curve->pointBrush(); pen = curve->pointPen(); opacity = curve->pointOpacity(); size = curve->pointSize(); errorBarBrush = curve->pointErrorBarBrush(); errorBarPen = curve->pointErrorBarPen(); errorBarSize = curve->pointErrorBarSize(); setVisible(curve->pointVisibility()); } } /*! Returns the outer bounds of the item as a rectangle. */ QRectF DatapickerPointPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath DatapickerPointPrivate::shape() const { return itemShape; } /*! recalculates the outer bounds and the shape of the item. */ void DatapickerPointPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.scale(size, size); matrix.rotate(-rotationAngle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); itemShape = QPainterPath(); itemShape.addRect(transformedBoundingRectangle); } void DatapickerPointPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) { Q_UNUSED(option) Q_UNUSED(widget) QPainterPath path = Symbol::pathFromStyle(pointStyle); QTransform trafo; trafo.scale(size, size); path = trafo.map(path); trafo.reset(); if (rotationAngle != 0) { trafo.rotate(-rotationAngle); path = trafo.map(path); } painter->save(); painter->setPen(pen); painter->setBrush(brush); painter->setOpacity(opacity); painter->drawPath(path); painter->restore(); if (isSelected() && !m_printing) { painter->setPen(q->selectedPen); painter->setOpacity(q->selectedOpacity); painter->drawPath(itemShape); } } void DatapickerPointPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void DatapickerPoint::save(QXmlStreamWriter* writer) const { Q_D(const DatapickerPoint); writer->writeStartElement( "datapickerPoint" ); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.x()) ); writer->writeAttribute( "y", QString::number(d->position.y()) ); writer->writeEndElement(); writer->writeStartElement( "errorBar" ); writer->writeAttribute( "plusDeltaXPos_x", QString::number(d->plusDeltaXPos.x()) ); writer->writeAttribute( "plusDeltaXPos_y", QString::number(d->plusDeltaXPos.y()) ); writer->writeAttribute( "minusDeltaXPos_x", QString::number(d->minusDeltaXPos.x()) ); writer->writeAttribute( "minusDeltaXPos_y", QString::number(d->minusDeltaXPos.y()) ); writer->writeAttribute( "plusDeltaYPos_x", QString::number(d->plusDeltaYPos.x()) ); writer->writeAttribute( "plusDeltaYPos_y", QString::number(d->plusDeltaYPos.y()) ); writer->writeAttribute( "minusDeltaYPos_x", QString::number(d->minusDeltaYPos.x()) ); writer->writeAttribute( "minusDeltaYPos_y", QString::number(d->minusDeltaYPos.y()) ); writer->writeEndElement(); writer->writeEndElement(); // close "DatapickerPoint" section } //! Load from XML bool DatapickerPoint::load(XmlStreamReader* reader, bool preview) { Q_D(DatapickerPoint); if(!reader->isStartElement() || reader->name() != "datapickerPoint") { reader->raiseError(i18n("no datapicker-Point element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "datapickerPoint") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.setX(str.toDouble()); str = attribs.value("y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.setY(str.toDouble()); } else if (!preview && reader->name() == "errorBar") { attribs = reader->attributes(); str = attribs.value("plusDeltaXPos_x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'plusDeltaXPos_x'")); else d->plusDeltaXPos.setX(str.toDouble()); str = attribs.value("plusDeltaXPos_y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'plusDeltaXPos_y'")); else d->plusDeltaXPos.setY(str.toDouble()); str = attribs.value("minusDeltaXPos_x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'minusDeltaXPos_x'")); else d->minusDeltaXPos.setX(str.toDouble()); str = attribs.value("minusDeltaXPos_y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'minusDeltaXPos_y'")); else d->minusDeltaXPos.setY(str.toDouble()); str = attribs.value("plusDeltaYPos_x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'plusDeltaYPos_x'")); else d->plusDeltaYPos.setX(str.toDouble()); str = attribs.value("plusDeltaYPos_y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'plusDeltaYPos_y'")); else d->plusDeltaYPos.setY(str.toDouble()); str = attribs.value("minusDeltaYPos_x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'minusDeltaYPos_x'")); else d->minusDeltaYPos.setX(str.toDouble()); str = attribs.value("minusDeltaYPos_y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'minusDeltaYPos_y'")); else d->minusDeltaYPos.setY(str.toDouble()); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } retransform(); return true; } diff --git a/src/backend/lib/commandtemplates.h b/src/backend/lib/commandtemplates.h index 26059b097..3f755a5e5 100644 --- a/src/backend/lib/commandtemplates.h +++ b/src/backend/lib/commandtemplates.h @@ -1,123 +1,125 @@ /*************************************************************************** File : commandtemplates.h Project : LabPlot Description : Undo/Redo command templates -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef COMMANDTEMPLATES_H #define COMMANDTEMPLATES_H #include +#include + template class StandardSetterCmd : public QUndoCommand { public: - StandardSetterCmd(target_class* target, value_type target_class::* field, value_type newValue, const QString& description) // use i18n("%1: ...") for last arg + StandardSetterCmd(target_class* target, value_type target_class::* field, value_type newValue, const KLocalizedString& description) // use ki18n("%1: ...") : m_target(target), m_field(field), m_otherValue(newValue) { - setText(description.arg(m_target->name())); + setText(description.subs(m_target->name()).toString()); } virtual void initialize() {}; virtual void finalize() {}; void redo() override { initialize(); value_type tmp = *m_target.*m_field; *m_target.*m_field = m_otherValue; m_otherValue = tmp; finalize(); } void undo() override { redo(); } protected: target_class* m_target; value_type target_class::*m_field; value_type m_otherValue; }; template class StandardMacroSetterCmd : public QUndoCommand { public: - StandardMacroSetterCmd(target_class* target, value_type target_class::*field, value_type newValue, const QString& description) // use i18n("%1: ...") for last arg + StandardMacroSetterCmd(target_class* target, value_type target_class::*field, value_type newValue, const KLocalizedString& description) // use ki18n("%1: ...") : m_target(target), m_field(field), m_otherValue(newValue) { - setText(description.arg(m_target->name())); + setText(description.subs(m_target->name()).toString()); } virtual void initialize() {}; virtual void finalize() {}; virtual void finalizeUndo() {}; void redo() override { initialize(); value_type tmp = *m_target.*m_field; *m_target.*m_field = m_otherValue; m_otherValue = tmp; finalize(); } //call finalizeUndo() at the end where only the signal is emmited //and no actual finalize-method is called that can potentially //cause new entries on the undo-stack void undo() override { initialize(); value_type tmp = *m_target.*m_field; *m_target.*m_field = m_otherValue; m_otherValue = tmp; finalizeUndo(); } protected: target_class* m_target; value_type target_class::*m_field; value_type m_otherValue; }; template class StandardSwapMethodSetterCmd : public QUndoCommand { public: - StandardSwapMethodSetterCmd(target_class* target, value_type (target_class::*method)(value_type), value_type newValue, const QString& description) // use i18n("%1: ...") for last arg + StandardSwapMethodSetterCmd(target_class* target, value_type (target_class::*method)(value_type), value_type newValue, const KLocalizedString& description) // use ki18n("%1: ...") : m_target(target), m_method(method), m_otherValue(newValue) { - setText(description.arg(m_target->name())); + setText(description.subs(m_target->name()).toString()); } virtual void initialize() {}; virtual void finalize() {}; void redo() override { initialize(); m_otherValue = (*m_target.*m_method)(m_otherValue); finalize(); } void undo() override { redo(); } protected: target_class* m_target; value_type (target_class::*m_method)(value_type); value_type m_otherValue; }; #endif diff --git a/src/backend/lib/macros.h b/src/backend/lib/macros.h index 480266169..b8b679f1d 100644 --- a/src/backend/lib/macros.h +++ b/src/backend/lib/macros.h @@ -1,497 +1,497 @@ /*************************************************************************** File : macros.h Project : LabPlot Description : Various preprocessor macros -------------------------------------------------------------------- Copyright : (C) 2008 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2013-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef MACROS_H #define MACROS_H #include #include // C++ style warning (works on Windows) #include #define WARN(x) std::cout << x << std::endl #ifndef NDEBUG #include #define QDEBUG(x) qDebug() << x // C++ style debugging (works on Windows) #include #define DEBUG(x) std::cout << x << std::endl #else #define QDEBUG(x) {} #define DEBUG(x) {} #endif #ifdef Q_OS_WIN #define UTF8_QSTRING(str) QString::fromWCharArray(L##str) #else #define UTF8_QSTRING(str) QString::fromUtf8(str) #endif #define ENUM_TO_STRING(class, enum, value) \ (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).valueToKey(value)) #define ENUM_COUNT(class, enum) \ (class::staticMetaObject.enumerator(class::staticMetaObject.indexOfEnumerator(#enum)).keyCount()) #define BASIC_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type value) { var = value; } #define CLASS_ACCESSOR(type, var, method, Method) \ type method() const { return var; }; \ void set ## Method(const type & value) { var = value; } #define BASIC_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type value); #define BASIC_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define BASIC_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define BASIC_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define CLASS_D_ACCESSOR_DECL(type, method, Method) \ type method() const; \ void set ## Method(const type & value); #define CLASS_D_ACCESSOR_IMPL(classname, type, method, Method, var) \ void classname::set ## Method(const type & value) \ { \ d->var = value; \ } \ type classname::method() const \ { \ return d->var; \ } #define CLASS_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ return d->var; \ } #define CLASS_SHARED_D_READER_IMPL(classname, type, method, var) \ type classname::method() const \ { \ Q_D(const classname); \ return d->var; \ } #define POINTER_D_ACCESSOR_DECL(type, method, Method) \ type *method() const; \ void set ## Method(type *ptr); #define FLAG_D_ACCESSOR_DECL(Method) \ bool is ## Method() const; \ bool has ## Method() const; \ void set ## Method(const bool value=true); \ void enable ## Method(const bool value=true); #define FLAG_D_ACCESSOR_IMPL(classname, Method, var) \ void classname::set ## Method(const bool value) \ { \ d->var = value; \ } \ void classname::enable ## Method(const bool value) \ { \ d->var = value; \ } \ bool classname::is ## Method() const \ { \ return d->var; \ } \ bool classname::has ## Method() const \ { \ return d->var; \ } #define WAIT_CURSOR QApplication::setOverrideCursor(QCursor(Qt::WaitCursor)) #define RESET_CURSOR QApplication::restoreOverrideCursor() #define STD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ }; #define STD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() { m_target->finalize_method(); } \ }; // setter class with finalize() and signal emmiting. #define STD_SETTER_CMD_IMPL_S(class_name, cmd_name, value_type, field_name) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; // setter class with finalize() and signal emmiting for changing several properties in one single step (embedded in beginMacro/endMacro) #define STD_SETTER_CMD_IMPL_M_F_S(class_name, cmd_name, value_type, field_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardMacroSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardMacroSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void finalize() { m_target->finalize_method(); emit m_target->q->field_name##Changed(m_target->*m_field); } \ virtual void finalizeUndo() { emit m_target->q->field_name##Changed(m_target->*m_field); } \ }; #define STD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, field_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, field_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSetterCmd(target, &class_name::Private::field_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL(class_name, cmd_name, value_type, method_name) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_F(class_name, cmd_name, value_type, method_name, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void finalize() { m_target->finalize_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_I(class_name, cmd_name, value_type, method_name, init_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ }; #define STD_SWAP_METHOD_SETTER_CMD_IMPL_IF(class_name, cmd_name, value_type, method_name, init_method, finalize_method) \ class class_name ## cmd_name ## Cmd: public StandardSwapMethodSetterCmd { \ public: \ - class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const QString &description) \ + class_name ## cmd_name ## Cmd(class_name::Private *target, value_type newValue, const KLocalizedString &description) \ : StandardSwapMethodSetterCmd(target, &class_name::Private::method_name, newValue, description) {} \ virtual void initialize() { m_target->init_method(); } \ virtual void finalize() { m_target->finalize_method(); } \ }; //xml-serialization/deserialization //QColor #define WRITE_QCOLOR(color) \ do { \ writer->writeAttribute( "color_r", QString::number(color.red()) ); \ writer->writeAttribute( "color_g", QString::number(color.green()) ); \ writer->writeAttribute( "color_b", QString::number(color.blue()) ); \ } while (0) #define READ_QCOLOR(color) \ do { \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_r'")); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_g'")); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_b'")); \ else \ color.setBlue( str.toInt() ); \ } while(0) //QPen #define WRITE_QPEN(pen) \ do { \ writer->writeAttribute( "style", QString::number(pen.style()) ); \ writer->writeAttribute( "color_r", QString::number(pen.color().red()) ); \ writer->writeAttribute( "color_g", QString::number(pen.color().green()) ); \ writer->writeAttribute( "color_b", QString::number(pen.color().blue()) ); \ writer->writeAttribute( "width", QString::number(pen.widthF()) ); \ } while (0) #define READ_QPEN(pen) \ do { \ str = attribs.value("style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'style'")); \ else \ pen.setStyle( (Qt::PenStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_r'")); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_g'")); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'color_b'")); \ else \ color.setBlue( str.toInt() ); \ \ pen.setColor(color); \ \ str = attribs.value("width").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'width'")); \ else \ pen.setWidthF( str.toDouble() ); \ } while(0) //QFont #define WRITE_QFONT(font) \ do { \ writer->writeAttribute( "fontFamily", font.family() ); \ writer->writeAttribute( "fontSize", QString::number(font.pixelSize()) ); \ writer->writeAttribute( "fontPointSize", QString::number(font.pointSize()));\ writer->writeAttribute( "fontWeight", QString::number(font.weight()) ); \ writer->writeAttribute( "fontItalic", QString::number(font.italic()) ); \ } while(0) #define READ_QFONT(font) \ do { \ str = attribs.value("fontFamily").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'fontFamily'")); \ else \ font.setFamily( str ); \ \ str = attribs.value("fontSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'fontSize'")); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPixelSize(size); \ } \ \ str = attribs.value("fontPointSize").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'fontPointSize'")); \ else { \ int size = str.toInt(); \ if (size != -1) \ font.setPointSize(size); \ } \ \ str = attribs.value("fontWeight").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'fontWeight'")); \ else \ font.setWeight( str.toInt() ); \ \ str = attribs.value("fontItalic").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'fontItalic'")); \ else \ font.setItalic( str.toInt() ); \ } while(0) //QBrush #define WRITE_QBRUSH(brush) \ do { \ writer->writeAttribute("brush_style", QString::number(brush.style()) ); \ writer->writeAttribute("brush_color_r", QString::number(brush.color().red())); \ writer->writeAttribute("brush_color_g", QString::number(brush.color().green()));\ writer->writeAttribute("brush_color_b", QString::number(brush.color().blue())); \ } while(0) #define READ_QBRUSH(brush) \ do { \ str = attribs.value("brush_style").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'brush_style'")); \ else \ brush.setStyle( (Qt::BrushStyle)str.toInt() ); \ \ QColor color; \ str = attribs.value("brush_color_r").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'brush_color_r'")); \ else \ color.setRed( str.toInt() ); \ \ str = attribs.value("brush_color_g").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'brush_color_g'")); \ else \ color.setGreen( str.toInt() ); \ \ str = attribs.value("brush_color_b").toString(); \ if(str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg("'brush_color_b'")); \ else \ color.setBlue( str.toInt() ); \ \ brush.setColor(color); \ } while(0) //Column #define WRITE_COLUMN(column, columnName) \ do { \ if (column){ \ writer->writeAttribute( #columnName, column->path() ); \ } else { \ writer->writeAttribute( #columnName, "" ); \ } \ } while(0) //column names can be empty in case no columns were used before save //the actual pointers to the x- and y-columns are restored in Project::load() #define READ_COLUMN(columnName) \ do { \ str = attribs.value(#columnName).toString(); \ d->columnName ##Path = str; \ } while(0) #define READ_INT_VALUE(name, var, type) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg(name)); \ else \ d->var = (type)str.toInt(); #define READ_DOUBLE_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg(name)); \ else \ d->var = str.toDouble(); #define READ_STRING_VALUE(name, var) \ str = attribs.value(name).toString(); \ if (str.isEmpty()) \ reader->raiseWarning(attributeWarning.arg(name)); \ else \ d->var = str; //used in Project::load() #define RESTORE_COLUMN_POINTER(obj, col, Col) \ do { \ if (!obj->col ##Path().isEmpty()) { \ for (Column* column : columns) { \ if (!column) continue; \ if (column->path() == obj->col ##Path()) { \ obj->set## Col(column); \ break; \ } \ } \ } \ } while(0) #define WRITE_PATH(obj, name) \ do { \ if (obj){ \ writer->writeAttribute( #name, obj->path() ); \ } else { \ writer->writeAttribute( #name, "" ); \ } \ } while(0) #define READ_PATH(name) \ do { \ str = attribs.value(#name).toString(); \ d->name ##Path = str; \ } while(0) #define RESTORE_POINTER(obj, name, Name, Type, list) \ do { \ if (!obj->name ##Path().isEmpty()) { \ for (AbstractAspect* aspect : list) { \ if (aspect->path() == obj->name ##Path()) { \ Type * a = dynamic_cast(aspect); \ if (!a) continue; \ obj->set## Name(a); \ break; \ } \ } \ } \ } while(0) #endif // MACROS_H diff --git a/src/backend/matrix/Matrix.cpp b/src/backend/matrix/Matrix.cpp index 27ae5f294..5508bfee5 100644 --- a/src/backend/matrix/Matrix.cpp +++ b/src/backend/matrix/Matrix.cpp @@ -1,1293 +1,1293 @@ /*************************************************************************** File : Matrix.cpp Project : Matrix Description : Spreadsheet with a MxN matrix data model -------------------------------------------------------------------- Copyright : (C) 2008-2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2015-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2017-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "Matrix.h" #include "MatrixPrivate.h" #include "matrixcommands.h" #include "backend/matrix/MatrixModel.h" #include "backend/core/Folder.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "commonfrontend/matrix/MatrixView.h" #include "kdefrontend/spreadsheet/ExportSpreadsheetDialog.h" #include #include #include #include #include #include /*! This class manages matrix based data (i.e., mathematically a MxN matrix with M rows, N columns). This data is typically used to for 3D plots. The values of the matrix are stored as generic values. Each column of the matrix is stored in a QVector objects. \ingroup backend */ Matrix::Matrix(AbstractScriptingEngine* engine, int rows, int cols, const QString& name, const AbstractColumn::ColumnMode mode) : AbstractDataSource(engine, name), d(new MatrixPrivate(this, mode)), m_model(nullptr), m_view(nullptr) { //set initial number of rows and columns appendColumns(cols); appendRows(rows); init(); } Matrix::Matrix(AbstractScriptingEngine* engine, const QString& name, bool loading, const AbstractColumn::ColumnMode mode) : AbstractDataSource(engine, name), d(new MatrixPrivate(this, mode)), m_model(nullptr), m_view(nullptr) { if (!loading) init(); } Matrix::~Matrix() { delete d; } void Matrix::init() { KConfig config; KConfigGroup group = config.group("Matrix"); //matrix dimension int rows = group.readEntry("RowCount", 10); int cols = group.readEntry("ColumnCount", 10); appendRows(rows); appendColumns(cols); //mapping to logical x- and y-coordinates d->xStart = group.readEntry("XStart", 0.0); d->xEnd = group.readEntry("XEnd", 1.0); d->yStart = group.readEntry("YStart", 0.0); d->yEnd = group.readEntry("YEnd", 1.0); //format QByteArray formatba = group.readEntry("NumericFormat", "f").toLatin1(); d->numericFormat = *formatba.data(); d->precision = group.readEntry("Precision", 3); d->headerFormat = (Matrix::HeaderFormat)group.readEntry("HeaderFormat", (int)Matrix::HeaderRowsColumns); } /*! Returns an icon to be used for decorating my views. */ QIcon Matrix::icon() const { return QIcon::fromTheme("labplot-matrix"); } /*! Returns a new context menu. The caller takes ownership of the menu. */ QMenu* Matrix::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); emit requestProjectContextMenu(menu); return menu; } QWidget* Matrix::view() const { if (!m_partView) { m_view= new MatrixView(const_cast(this)); m_partView = m_view; m_model = m_view->model(); } return m_partView; } bool Matrix::exportView() const { ExportSpreadsheetDialog* dlg = new ExportSpreadsheetDialog(m_view); dlg->setFileName(name()); dlg->setMatrixMode(true); //TODO FITS filter to decide if it can be exported to both dlg->setExportTo(QStringList() << i18n("FITS image") << i18n("FITS table")); if (m_view->selectedColumnCount() == 0) { dlg->setExportSelection(false); } bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); WAIT_CURSOR; if (dlg->format() == ExportSpreadsheetDialog::LaTeX) { const bool verticalHeader = dlg->matrixVerticalHeader(); const bool horizontalHeader = dlg->matrixHorizontalHeader(); const bool latexHeader = dlg->exportHeader(); const bool gridLines = dlg->gridLines(); const bool entire = dlg->entireSpreadheet(); const bool captions = dlg->captions(); m_view->exportToLaTeX(path, verticalHeader, horizontalHeader, latexHeader, gridLines, entire, captions); } else if (dlg->format() == ExportSpreadsheetDialog::FITS) { const int exportTo = dlg->exportToFits(); m_view->exportToFits(path, exportTo ); } else { const QString separator = dlg->separator(); m_view->exportToFile(path, separator); } RESET_CURSOR; } delete dlg; return ret; } bool Matrix::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); bool ret; dlg->setWindowTitle(i18n("Print Matrix")); if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Matrix::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &MatrixView::print); return dlg->exec(); } //############################################################################## //########################## getter methods ################################## //############################################################################## void* Matrix::data() const { return d->data; } BASIC_D_READER_IMPL(Matrix, AbstractColumn::ColumnMode, mode, mode) BASIC_D_READER_IMPL(Matrix, int, rowCount, rowCount) BASIC_D_READER_IMPL(Matrix, int, columnCount, columnCount) BASIC_D_READER_IMPL(Matrix, double, xStart, xStart) BASIC_D_READER_IMPL(Matrix, double, xEnd, xEnd) BASIC_D_READER_IMPL(Matrix, double, yStart, yStart) BASIC_D_READER_IMPL(Matrix, double, yEnd, yEnd) BASIC_D_READER_IMPL(Matrix, char, numericFormat, numericFormat) BASIC_D_READER_IMPL(Matrix, int, precision, precision) BASIC_D_READER_IMPL(Matrix, Matrix::HeaderFormat, headerFormat, headerFormat) CLASS_D_READER_IMPL(Matrix, QString, formula, formula) void Matrix::setSuppressDataChangedSignal(bool b) { if (m_model) m_model->setSuppressDataChangedSignal(b); } void Matrix::setChanged() { if (m_model) m_model->setChanged(); } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## void Matrix::setRowCount(int count) { if (count == d->rowCount) return; const int diff = count - d->rowCount; if (diff > 0) appendRows(diff); else if (diff < 0) removeRows(rowCount() + diff, -diff); } void Matrix::setColumnCount(int count) { if (count == d->columnCount) return; const int diff = count - columnCount(); if (diff > 0) appendColumns(diff); else if (diff < 0) removeColumns(columnCount() + diff, -diff); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXStart, double, xStart, updateViewHeader) void Matrix::setXStart(double xStart) { if (xStart != d->xStart) - exec(new MatrixSetXStartCmd(d, xStart, i18n("%1: x-start changed"))); + exec(new MatrixSetXStartCmd(d, xStart, ki18n("%1: x-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetXEnd, double, xEnd, updateViewHeader) void Matrix::setXEnd(double xEnd) { if (xEnd != d->xEnd) - exec(new MatrixSetXEndCmd(d, xEnd, i18n("%1: x-end changed"))); + exec(new MatrixSetXEndCmd(d, xEnd, ki18n("%1: x-end changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYStart, double, yStart, updateViewHeader) void Matrix::setYStart(double yStart) { if (yStart != d->yStart) - exec(new MatrixSetYStartCmd(d, yStart, i18n("%1: y-start changed"))); + exec(new MatrixSetYStartCmd(d, yStart, ki18n("%1: y-start changed"))); } STD_SETTER_CMD_IMPL_F_S(Matrix, SetYEnd, double, yEnd, updateViewHeader) void Matrix::setYEnd(double yEnd) { if (yEnd != d->yEnd) - exec(new MatrixSetYEndCmd(d, yEnd, i18n("%1: y-end changed"))); + exec(new MatrixSetYEndCmd(d, yEnd, ki18n("%1: y-end changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetNumericFormat, char, numericFormat) void Matrix::setNumericFormat(char format) { if (format != d->numericFormat) - exec(new MatrixSetNumericFormatCmd(d, format, i18n("%1: numeric format changed"))); + exec(new MatrixSetNumericFormatCmd(d, format, ki18n("%1: numeric format changed"))); } STD_SETTER_CMD_IMPL_S(Matrix, SetPrecision, int, precision) void Matrix::setPrecision(int precision) { if (precision != d->precision) - exec(new MatrixSetPrecisionCmd(d, precision, i18n("%1: precision changed"))); + exec(new MatrixSetPrecisionCmd(d, precision, ki18n("%1: precision changed"))); } //TODO: make this undoable? void Matrix::setHeaderFormat(Matrix::HeaderFormat format) { d->headerFormat = format; m_model->updateHeader(); if (m_view) m_view->resizeHeaders(); emit headerFormatChanged(format); } //columns void Matrix::insertColumns(int before, int count) { if (count < 1 || before < 0 || before > columnCount()) return; WAIT_CURSOR; exec(new MatrixInsertColumnsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendColumns(int count) { insertColumns(columnCount(), count); } void Matrix::removeColumns(int first, int count) { if (count < 1 || first < 0 || first+count > columnCount()) return; WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; case AbstractColumn::Text: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; case AbstractColumn::Integer: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixRemoveColumnsCmd(d, first, count)); break; } RESET_CURSOR; } void Matrix::clearColumn(int c) { WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixClearColumnCmd(d, c)); break; case AbstractColumn::Text: exec(new MatrixClearColumnCmd(d, c)); break; case AbstractColumn::Integer: exec(new MatrixClearColumnCmd(d, c)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixClearColumnCmd(d, c)); break; } RESET_CURSOR; } //rows void Matrix::insertRows(int before, int count) { if (count < 1 || before < 0 || before > rowCount()) return; WAIT_CURSOR; exec(new MatrixInsertRowsCmd(d, before, count)); RESET_CURSOR; } void Matrix::appendRows(int count) { insertRows(rowCount(), count); } void Matrix::removeRows(int first, int count) { if (count < 1 || first < 0 || first+count > rowCount()) return; WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixRemoveRowsCmd(d, first, count)); break; case AbstractColumn::Text: exec(new MatrixRemoveRowsCmd(d, first, count)); break; case AbstractColumn::Integer: exec(new MatrixRemoveRowsCmd(d, first, count)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixRemoveRowsCmd(d, first, count)); break; } RESET_CURSOR; } void Matrix::clearRow(int r) { switch (d->mode) { case AbstractColumn::Numeric: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0.0)); break; case AbstractColumn::Text: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QString())); break; case AbstractColumn::Integer: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, 0)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int c = 0; c < columnCount(); ++c) exec(new MatrixSetCellValueCmd(d, r, c, QDateTime())); break; } } //! Return the value in the given cell (needs explicit instantiation) template T Matrix::cell(int row, int col) const { return d->cell(row, col); } template double Matrix::cell(int row, int col) const; template int Matrix::cell(int row, int col) const; template QDateTime Matrix::cell(int row, int col) const; template QString Matrix::cell(int row, int col) const; //! Return the text displayed in the given cell (needs explicit instantiation) template QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col)); } // special cases template <> QString Matrix::text(int row, int col) { return QLocale().toString(cell(row,col), d->numericFormat, d->precision); } template <> QString Matrix::text(int row, int col) { return cell(row,col); } template QString Matrix::text(int row, int col); template QString Matrix::text(int row, int col); //! Set the value of the cell (needs explicit instantiation) template void Matrix::setCell(int row, int col, T value) { if(row < 0 || row >= rowCount()) return; if(col < 0 || col >= columnCount()) return; exec(new MatrixSetCellValueCmd(d, row, col, value)); } template void Matrix::setCell(int row, int col, double value); template void Matrix::setCell(int row, int col, int value); template void Matrix::setCell(int row, int col, QDateTime value); template void Matrix::setCell(int row, int col, QString value); void Matrix::clearCell(int row, int col) { switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixSetCellValueCmd(d, row, col, 0.0)); break; case AbstractColumn::Text: exec(new MatrixSetCellValueCmd(d, row, col, QString())); break; case AbstractColumn::Integer: exec(new MatrixSetCellValueCmd(d, row, col, 0)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixSetCellValueCmd(d, row, col, QDateTime())); break; } } void Matrix::setDimensions(int rows, int cols) { if( (rows < 0) || (cols < 0 ) || (rows == rowCount() && cols == columnCount()) ) return; WAIT_CURSOR; beginMacro(i18n("%1: set matrix size to %2x%3", name(), rows, cols)); int col_diff = cols - columnCount(); if (col_diff > 0) insertColumns(columnCount(), col_diff); else if (col_diff < 0) removeColumns(columnCount() + col_diff, -col_diff); int row_diff = rows - rowCount(); if(row_diff > 0) appendRows(row_diff); else if (row_diff < 0) removeRows(rowCount() + row_diff, -row_diff); endMacro(); RESET_CURSOR; } void Matrix::copy(Matrix* other) { WAIT_CURSOR; beginMacro(i18n("%1: copy %2", name(), other->name())); int rows = other->rowCount(); int columns = other->columnCount(); setDimensions(rows, columns); for (int i=0; irowHeight(i)); for (int i=0; icolumnWidth(i)); d->suppressDataChange = true; switch (d->mode) { case AbstractColumn::Numeric: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Text: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Integer: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int i = 0; i < columns; i++) setColumnCells(i, 0, rows-1, other->columnCells(i, 0, rows-1)); break; } setCoordinates(other->xStart(), other->xEnd(), other->yStart(), other->yEnd()); setNumericFormat(other->numericFormat()); setPrecision(other->precision()); d->formula = other->formula(); d->suppressDataChange = false; emit dataChanged(0, 0, rows-1, columns-1); if (m_view) m_view->adjustHeaders(); endMacro(); RESET_CURSOR; } //! Duplicate the matrix inside its folder void Matrix::duplicate() { Matrix* matrix = new Matrix(0, rowCount(), columnCount(), name()); matrix->copy(this); if (folder()) folder()->addChild(matrix); } void Matrix::addRows() { if (!m_view) return; WAIT_CURSOR; int count = m_view->selectedRowCount(false); beginMacro(i18np("%1: add %2 rows", "%1: add %2 rows", name(), count)); exec(new MatrixInsertRowsCmd(d, rowCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::addColumns() { if (!m_view) return; WAIT_CURSOR; int count = m_view->selectedRowCount(false); beginMacro(i18np("%1: add %2 column", "%1: add %2 columns", name(), count)); exec(new MatrixInsertColumnsCmd(d, columnCount(), count)); endMacro(); RESET_CURSOR; } void Matrix::setCoordinates(double x1, double x2, double y1, double y2) { exec(new MatrixSetCoordinatesCmd(d, x1, x2, y1, y2)); } void Matrix::setFormula(const QString& formula) { exec(new MatrixSetFormulaCmd(d, formula)); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setRowHeight(int row, int height) { d->setRowHeight(row, height); } //! This method should only be called by the view. /** This method does not change the view, it only changes the * values that are saved when the matrix is saved. The view * has to take care of reading and applying these values */ void Matrix::setColumnWidth(int col, int width) { d->setColumnWidth(col, width); } int Matrix::rowHeight(int row) const { return d->rowHeight(row); } int Matrix::columnWidth(int col) const { return d->columnWidth(col); } //! Return the values in the given cells as vector template QVector Matrix::columnCells(int col, int first_row, int last_row) { return d->columnCells(col, first_row, last_row); } //! Set the values in the given cells from a double vector template void Matrix::setColumnCells(int col, int first_row, int last_row, const QVector& values) { WAIT_CURSOR; exec(new MatrixSetColumnCellsCmd(d, col, first_row, last_row, values)); RESET_CURSOR; } //! Return the values in the given cells as vector (needs explicit instantiation) template QVector Matrix::rowCells(int row, int first_column, int last_column) { return d->rowCells(row, first_column, last_column); } template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); template QVector Matrix::rowCells(int row, int first_column, int last_column); //! Set the values in the given cells from a double vector template void Matrix::setRowCells(int row, int first_column, int last_column, const QVector& values) { WAIT_CURSOR; exec(new MatrixSetRowCellsCmd(d, row, first_column, last_column, values)); RESET_CURSOR; } void Matrix::setData(void* data) { bool isEmpty = false; switch (d->mode) { case AbstractColumn::Numeric: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Text: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Integer: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: if (static_cast>*>(data)->isEmpty()) isEmpty = true; break; } if (!isEmpty) exec(new MatrixReplaceValuesCmd(d, data)); } //############################################################################## //######################### Public slots ##################################### //############################################################################## //! Clear the whole matrix (i.e. reset all cells) void Matrix::clear() { WAIT_CURSOR; beginMacro(i18n("%1: clear", name())); switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixClearCmd(d)); break; case AbstractColumn::Text: exec(new MatrixClearCmd(d)); break; case AbstractColumn::Integer: exec(new MatrixClearCmd(d)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixClearCmd(d)); break; } endMacro(); RESET_CURSOR; } void Matrix::transpose() { WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixTransposeCmd(d)); break; case AbstractColumn::Text: exec(new MatrixTransposeCmd(d)); break; case AbstractColumn::Integer: exec(new MatrixTransposeCmd(d)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixTransposeCmd(d)); break; } RESET_CURSOR; } void Matrix::mirrorHorizontally() { WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixMirrorHorizontallyCmd(d)); break; case AbstractColumn::Text: exec(new MatrixMirrorHorizontallyCmd(d)); break; case AbstractColumn::Integer: exec(new MatrixMirrorHorizontallyCmd(d)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixMirrorHorizontallyCmd(d)); break; } RESET_CURSOR; } void Matrix::mirrorVertically() { WAIT_CURSOR; switch (d->mode) { case AbstractColumn::Numeric: exec(new MatrixMirrorVerticallyCmd(d)); break; case AbstractColumn::Text: exec(new MatrixMirrorVerticallyCmd(d)); break; case AbstractColumn::Integer: exec(new MatrixMirrorVerticallyCmd(d)); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: exec(new MatrixMirrorVerticallyCmd(d)); break; } RESET_CURSOR; } //############################################################################## //###################### Private implementation ############################### //############################################################################## MatrixPrivate::MatrixPrivate(Matrix* owner, const AbstractColumn::ColumnMode m) : q(owner), data(0), mode(m), rowCount(0), columnCount(0), suppressDataChange(false) { switch (mode) { case AbstractColumn::Numeric: data = new QVector>(); break; case AbstractColumn::Text: data = new QVector>(); break; case AbstractColumn::Month: case AbstractColumn::Day: case AbstractColumn::DateTime: data = new QVector>(); break; case AbstractColumn::Integer: data = new QVector>(); break; } } MatrixPrivate::~MatrixPrivate() { if (data) { switch (mode) { case AbstractColumn::Numeric: delete static_cast>*>(data); break; case AbstractColumn::Text: delete static_cast>*>(data); break; case AbstractColumn::Integer: delete static_cast>*>(data); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: delete static_cast>*>(data); break; } } } void MatrixPrivate::updateViewHeader() { q->m_view->model()->updateHeader(); } /*! Insert \count columns before column number \c before */ void MatrixPrivate::insertColumns(int before, int count) { Q_ASSERT(before >= 0); Q_ASSERT(before <= columnCount); emit q->columnsAboutToBeInserted(before, count); switch (mode) { case AbstractColumn::Numeric: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Text: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Integer: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int i = 0; i < count; i++) { static_cast>*>(data)->insert(before+i, QVector(rowCount)); columnWidths.insert(before+i, 0); } break; } columnCount += count; emit q->columnsInserted(before, count); } /*! Remove \c count columns starting with column index \c first */ void MatrixPrivate::removeColumns(int first, int count) { emit q->columnsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first + count <= columnCount); switch (mode) { case AbstractColumn::Numeric: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Text: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Integer: (static_cast>*>(data))->remove(first, count); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: (static_cast>*>(data))->remove(first, count); break; } for (int i = 0; i < count; i++) columnWidths.remove(first); columnCount -= count; emit q->columnsRemoved(first, count); } /*! Insert \c count rows before row with the index \c before */ void MatrixPrivate::insertRows(int before, int count) { emit q->rowsAboutToBeInserted(before, count); Q_ASSERT(before >= 0); Q_ASSERT(before <= rowCount); switch (mode) { case AbstractColumn::Numeric: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0.0); break; case AbstractColumn::Text: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QString()); break; case AbstractColumn::Integer: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, 0); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int col = 0; col < columnCount; col++) for (int i = 0; i < count; i++) (static_cast>*>(data))->operator[](col).insert(before+i, QDateTime()); } for(int i=0; irowsInserted(before, count); } /*! Remove \c count columns starting from the column with index \c first */ void MatrixPrivate::removeRows(int first, int count) { emit q->rowsAboutToBeRemoved(first, count); Q_ASSERT(first >= 0); Q_ASSERT(first+count <= rowCount); switch (mode) { case AbstractColumn::Numeric: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Text: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Integer: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int col = 0; col < columnCount; col++) (static_cast>*>(data))->operator[](col).remove(first, count); break; } for (int i = 0; i < count; i++) rowHeights.remove(first); rowCount -= count; emit q->rowsRemoved(first, count); } //! Fill column with zeroes void MatrixPrivate::clearColumn(int col) { switch (mode) { case AbstractColumn::Numeric: static_cast>*>(data)->operator[](col).fill(0.0); break; case AbstractColumn::Text: static_cast>*>(data)->operator[](col).fill(QString()); break; case AbstractColumn::Integer: static_cast>*>(data)->operator[](col).fill(0); break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: static_cast>*>(data)->operator[](col).fill(QDateTime()); break; } if (!suppressDataChange) emit q->dataChanged(0, col, rowCount-1, col); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## void Matrix::save(QXmlStreamWriter* writer) const { writer->writeStartElement("matrix"); writeBasicAttributes(writer); writeCommentElement(writer); //formula writer->writeStartElement("formula"); writer->writeCharacters(d->formula); writer->writeEndElement(); //format writer->writeStartElement("format"); writer->writeAttribute("mode", QString::number(d->mode)); writer->writeAttribute("headerFormat", QString::number(d->headerFormat)); writer->writeAttribute("numericFormat", QString(QChar(d->numericFormat))); writer->writeAttribute("precision", QString::number(d->precision)); writer->writeEndElement(); //dimensions writer->writeStartElement("dimension"); writer->writeAttribute("columns", QString::number(d->columnCount)); writer->writeAttribute("rows", QString::number(d->rowCount)); writer->writeAttribute("x_start", QString::number(d->xStart)); writer->writeAttribute("x_end", QString::number(d->xEnd)); writer->writeAttribute("y_start", QString::number(d->yStart)); writer->writeAttribute("y_end", QString::number(d->yEnd)); writer->writeEndElement(); //vector with row heights writer->writeStartElement("row_heights"); const char* data = reinterpret_cast(d->rowHeights.constData()); int size = d->rowHeights.size() * sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data,size).toBase64()); writer->writeEndElement(); //vector with column widths writer->writeStartElement("column_widths"); data = reinterpret_cast(d->columnWidths.constData()); size = d->columnWidths.size()*sizeof(int); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); //columns switch (d->mode) { case AbstractColumn::Numeric: size = d->rowCount*sizeof(double); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Text: size = d->rowCount*sizeof(QString); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Integer: size = d->rowCount*sizeof(int); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: size = d->rowCount*sizeof(QDateTime); for (int i = 0; i < d->columnCount; ++i) { data = reinterpret_cast(static_cast>*>(d->data)->at(i).constData()); writer->writeStartElement("column"); writer->writeCharacters(QByteArray::fromRawData(data, size).toBase64()); writer->writeEndElement(); } break; } writer->writeEndElement(); // "matrix" } bool Matrix::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "matrix") { reader->raiseError(i18n("no matrix element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; // read child elements while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "matrix") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if(!preview && reader->name() == "formula") { d->formula = reader->text().toString().trimmed(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); str = attribs.value("mode").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'mode'")); else d->mode = AbstractColumn::ColumnMode(str.toInt()); str = attribs.value("headerFormat").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'headerFormat'")); else d->headerFormat = Matrix::HeaderFormat(str.toInt()); str = attribs.value("numericFormat").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'numericFormat'")); else { QByteArray formatba = str.toLatin1(); d->numericFormat = *formatba.data(); } str = attribs.value("precision").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'precision'")); else d->precision = str.toInt(); } else if (!preview && reader->name() == "dimension") { attribs = reader->attributes(); str = attribs.value("columns").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'columns'")); else d->columnCount = str.toInt(); str = attribs.value("rows").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rows'")); else d->rowCount = str.toInt(); str = attribs.value("x_start").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x_start'")); else d->xStart = str.toDouble(); str = attribs.value("x_end").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x_end'")); else d->xEnd = str.toDouble(); str = attribs.value("y_start").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y_start'")); else d->yStart = str.toDouble(); str = attribs.value("y_end").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y_end'")); else d->yEnd = str.toDouble(); } else if (!preview && reader->name() == "row_heights") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); int count = bytes.size()/sizeof(int); d->rowHeights.resize(count); memcpy(d->rowHeights.data(), bytes.data(), count*sizeof(int)); } else if (!preview && reader->name() == "column_widths") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); int count = bytes.size()/sizeof(int); d->columnWidths.resize(count); memcpy(d->columnWidths.data(), bytes.data(), count*sizeof(int)); } else if (!preview && reader->name() == "column") { //TODO: parallelize reading of columns? reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray bytes = QByteArray::fromBase64(content.toAscii()); switch (d->mode) { case AbstractColumn::Numeric: { int count = bytes.size()/sizeof(double); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(double)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Text: { int count = bytes.size()/sizeof(QString); QVector column; column.resize(count); //TODO: warning (GCC8): writing to an object of type 'class QDateTime' with no trivial copy-assignment; use copy-assignment or copy-initialization instead memcpy(column.data(), bytes.data(), count*sizeof(QString)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Integer: { int count = bytes.size()/sizeof(int); QVector column; column.resize(count); memcpy(column.data(), bytes.data(), count*sizeof(int)); static_cast>*>(d->data)->append(column); break; } case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: { int count = bytes.size()/sizeof(QDateTime); QVector column; column.resize(count); //TODO: warning (GCC8): writing to an object of type 'class QDateTime' with no trivial copy-assignment; use copy-assignment or copy-initialization instead memcpy(column.data(), bytes.data(), count*sizeof(QDateTime)); static_cast>*>(d->data)->append(column); break; } } } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################## Data Import ####################################### //############################################################################## int Matrix::prepareImport(QVector& dataContainer, AbstractFileFilter::ImportMode mode, int actualRows, int actualCols, QStringList colNameList, QVector columnMode) { QDEBUG("prepareImport() rows =" << actualRows << " cols =" << actualCols); Q_UNUSED(colNameList); int columnOffset = 0; setUndoAware(false); setSuppressDataChangedSignal(true); // resize the matrix if (mode == AbstractFileFilter::Replace) { clear(); setDimensions(actualRows, actualCols); } else { if (rowCount() < actualRows) setDimensions(actualRows, actualCols); else setDimensions(rowCount(), actualCols); } // data() returns a void* which is a pointer to a matrix of any data type (see ColumnPrivate.cpp) dataContainer.resize(actualCols); switch (columnMode[0]) { // only columnMode[0] is used case AbstractColumn::Numeric: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } d->mode = AbstractColumn::Numeric; break; case AbstractColumn::Integer: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } d->mode = AbstractColumn::Integer; break; case AbstractColumn::Text: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } d->mode = AbstractColumn::Text; break; case AbstractColumn::Day: case AbstractColumn::Month: case AbstractColumn::DateTime: for (int n = 0; n < actualCols; n++) { QVector* vector = &(static_cast>*>(data())->operator[](n)); vector->reserve(actualRows); vector->resize(actualRows); dataContainer[n] = static_cast(vector); } d->mode = AbstractColumn::DateTime; break; } return columnOffset; } void Matrix::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) { DEBUG("Matrix::finalizeImport()"); Q_UNUSED(columnOffset); Q_UNUSED(startColumn); Q_UNUSED(endColumn); Q_UNUSED(dateTimeFormat); Q_UNUSED(importMode); setSuppressDataChangedSignal(false); setChanged(); setUndoAware(true); DEBUG("Matrix::finalizeImport() DONE"); } diff --git a/src/backend/worksheet/TextLabel.cpp b/src/backend/worksheet/TextLabel.cpp index 7a580c2d4..e92768861 100644 --- a/src/backend/worksheet/TextLabel.cpp +++ b/src/backend/worksheet/TextLabel.cpp @@ -1,872 +1,872 @@ /*************************************************************************** File : TextLabel.cpp Project : LabPlot Description : Text label supporting reach text and latex formatting -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "TextLabel.h" #include "Worksheet.h" #include "TextLabelPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /** * \class TextLabel * \brief A label supporting rendering of html- and tex-formated textes. * * The label is aligned relative to the specified position. * The position can be either specified by providing the x- and y- coordinates * in parent's coordinate system, or by specifying one of the predefined position * flags (\ca HorizontalPosition, \ca VerticalPosition). */ TextLabel::TextLabel(const QString& name, Type type):WorksheetElement(name), d_ptr(new TextLabelPrivate(this)), m_type(type), visibilityAction(nullptr) { init(); } TextLabel::TextLabel(const QString &name, TextLabelPrivate *dd, Type type):WorksheetElement(name), d_ptr(dd), m_type(type), visibilityAction(nullptr) { init(); } TextLabel::Type TextLabel::type() const { return m_type; } void TextLabel::init() { Q_D(TextLabel); KConfig config; KConfigGroup group; if (m_type == AxisTitle) group = config.group("AxisTitle"); else if (m_type == PlotTitle) group = config.group("PlotTitle"); else if (m_type == PlotLegendTitle) group = config.group("PlotLegendTitle"); else group = config.group("TextLabel"); //properties common to all types d->textWrapper.teXUsed = group.readEntry("TeXUsed", false); d->teXFont.setFamily(group.readEntry("TeXFontFamily", "Computer Modern")); d->teXFont.setPointSize(group.readEntry("TeXFontSize", 12)); d->teXFontColor = group.readEntry("TeXFontColor", QColor(Qt::black)); d->teXBackgroundColor = group.readEntry("TeXBackgroundColor", QColor(Qt::white)); d->rotationAngle = group.readEntry("Rotation", 0.0); d->staticText.setTextFormat(Qt::RichText); // explicitly set no wrap mode for text label to avoid unnecessary line breaks QTextOption textOption; textOption.setWrapMode(QTextOption::NoWrap); d->staticText.setTextOption(textOption); //position and alignment relevant properties, dependent on the actual type if (m_type == PlotTitle || m_type == PlotLegendTitle) { d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)TextLabel::hPositionCenter); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int) TextLabel::vPositionTop); d->position.point.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->position.point.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->horizontalAlignment= (TextLabel::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)TextLabel::hAlignCenter); d->verticalAlignment= (TextLabel::VerticalAlignment) group.readEntry("VerticalAlignment", (int)TextLabel::vAlignBottom); } else { d->position.horizontalPosition = (HorizontalPosition) group.readEntry("PositionX", (int)TextLabel::hPositionCustom); d->position.verticalPosition = (VerticalPosition) group.readEntry("PositionY", (int) TextLabel::vPositionCustom); d->position.point.setX( group.readEntry("PositionXValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->position.point.setY( group.readEntry("PositionYValue", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)) ); d->horizontalAlignment= (TextLabel::HorizontalAlignment) group.readEntry("HorizontalAlignment", (int)TextLabel::hAlignCenter); d->verticalAlignment= (TextLabel::VerticalAlignment) group.readEntry("VerticalAlignment", (int)TextLabel::vAlignCenter); } //scaling: //we need to scale from the font size specified in points to scene units. //furhermore, we create the tex-image in a higher resolution then usual desktop resolution // -> take this into account d->scaleFactor = Worksheet::convertToSceneUnits(1, Worksheet::Point); d->teXImageResolution = QApplication::desktop()->physicalDpiX(); d->teXImageScaleFactor = Worksheet::convertToSceneUnits(2.54/QApplication::desktop()->physicalDpiX(), Worksheet::Centimeter); connect(&d->teXImageFutureWatcher, &QFutureWatcher::finished, this, &TextLabel::updateTeXImage); } TextLabel::~TextLabel() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem* TextLabel::graphicsItem() const { return d_ptr; } void TextLabel::setParentGraphicsItem(QGraphicsItem* item) { Q_D(TextLabel); d->setParentItem(item); d->updatePosition(); } void TextLabel::retransform() { Q_D(TextLabel); d->retransform(); } void TextLabel::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("TextLabel::handleResize()"); Q_UNUSED(pageResize); Q_D(TextLabel); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); d->teXFont.setPointSizeF(d->teXFont.pointSizeF() * ratio); d->updateText(); //TODO: doesn't seem to work QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); QTextCharFormat fmt = cursor.charFormat(); QFont font = fmt.font(); font.setPointSizeF(font.pointSizeF() * ratio); fmt.setFont(font); cursor.setCharFormat(fmt); } /*! Returns an icon to be used in the project explorer. */ QIcon TextLabel::icon() const{ return QIcon::fromTheme("draw-text"); } QMenu* TextLabel::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" if (!visibilityAction) { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &TextLabel::visibilityChanged); } visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertSeparator(firstAction); return menu; } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::TextWrapper, text, textWrapper) CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, teXFontColor, teXFontColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QColor, teXBackgroundColor, teXBackgroundColor); CLASS_SHARED_D_READER_IMPL(TextLabel, QFont, teXFont, teXFont); CLASS_SHARED_D_READER_IMPL(TextLabel, TextLabel::PositionWrapper, position, position); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::HorizontalAlignment, horizontalAlignment, horizontalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, TextLabel::VerticalAlignment, verticalAlignment, verticalAlignment); BASIC_SHARED_D_READER_IMPL(TextLabel, qreal, rotationAngle, rotationAngle); /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(TextLabel, SetText, TextLabel::TextWrapper, textWrapper, updateText); void TextLabel::setText(const TextWrapper &textWrapper) { Q_D(TextLabel); if ( (textWrapper.text != d->textWrapper.text) || (textWrapper.teXUsed != d->textWrapper.teXUsed) ) - exec(new TextLabelSetTextCmd(d, textWrapper, i18n("%1: set label text"))); + exec(new TextLabelSetTextCmd(d, textWrapper, ki18n("%1: set label text"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFont, QFont, teXFont, updateText); void TextLabel::setTeXFont(const QFont& font) { Q_D(TextLabel); if (font != d->teXFont) - exec(new TextLabelSetTeXFontCmd(d, font, i18n("%1: set TeX main font"))); + exec(new TextLabelSetTeXFontCmd(d, font, ki18n("%1: set TeX main font"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXFontColor, QColor, teXFontColor, updateText); void TextLabel::setTeXFontColor(const QColor color) { Q_D(TextLabel); if (color != d->teXFontColor) - exec(new TextLabelSetTeXFontColorCmd(d, color, i18n("%1: set TeX font color"))); + exec(new TextLabelSetTeXFontColorCmd(d, color, ki18n("%1: set TeX font color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetTeXBackgroundColor, QColor, teXBackgroundColor, updateText); void TextLabel::setTeXBackgroundColor(const QColor color) { Q_D(TextLabel); if (color != d->teXBackgroundColor) - exec(new TextLabelSetTeXBackgroundColorCmd(d, color, i18n("%1: set TeX background color"))); + exec(new TextLabelSetTeXBackgroundColorCmd(d, color, ki18n("%1: set TeX background color"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetPosition, TextLabel::PositionWrapper, position, retransform); void TextLabel::setPosition(const PositionWrapper& pos) { Q_D(TextLabel); if (pos.point!=d->position.point || pos.horizontalPosition!=d->position.horizontalPosition || pos.verticalPosition!=d->position.verticalPosition) - exec(new TextLabelSetPositionCmd(d, pos, i18n("%1: set position"))); + exec(new TextLabelSetPositionCmd(d, pos, ki18n("%1: set position"))); } /*! sets the position without undo/redo-stuff */ void TextLabel::setPosition(QPointF point) { Q_D(TextLabel); if (point != d->position.point) { d->position.point = point; retransform(); } } /*! * position is set to invalid if the parent item is not drawn on the scene * (e.g. axis is not drawn because it's outside plot ranges -> don't draw axis' title label) */ void TextLabel::setPositionInvalid(bool invalid) { Q_D(TextLabel); if (invalid != d->positionInvalid) { d->positionInvalid = invalid; } } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetRotationAngle, qreal, rotationAngle, recalcShapeAndBoundingRect); void TextLabel::setRotationAngle(qreal angle) { Q_D(TextLabel); if (angle != d->rotationAngle) - exec(new TextLabelSetRotationAngleCmd(d, angle, i18n("%1: set rotation angle"))); + exec(new TextLabelSetRotationAngleCmd(d, angle, ki18n("%1: set rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetHorizontalAlignment, TextLabel::HorizontalAlignment, horizontalAlignment, retransform); void TextLabel::setHorizontalAlignment(const TextLabel::HorizontalAlignment hAlign) { Q_D(TextLabel); if (hAlign != d->horizontalAlignment) - exec(new TextLabelSetHorizontalAlignmentCmd(d, hAlign, i18n("%1: set horizontal alignment"))); + exec(new TextLabelSetHorizontalAlignmentCmd(d, hAlign, ki18n("%1: set horizontal alignment"))); } STD_SETTER_CMD_IMPL_F_S(TextLabel, SetVerticalAlignment, TextLabel::VerticalAlignment, verticalAlignment, retransform); void TextLabel::setVerticalAlignment(const TextLabel::VerticalAlignment vAlign) { Q_D(TextLabel); if (vAlign != d->verticalAlignment) - exec(new TextLabelSetVerticalAlignmentCmd(d, vAlign, i18n("%1: set vertical alignment"))); + exec(new TextLabelSetVerticalAlignmentCmd(d, vAlign, ki18n("%1: set vertical alignment"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(TextLabel, SetVisible, bool, swapVisible, retransform); void TextLabel::setVisible(bool on) { Q_D(TextLabel); - exec(new TextLabelSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new TextLabelSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool TextLabel::isVisible() const { Q_D(const TextLabel); return d->isVisible(); } void TextLabel::setPrinting(bool on) { Q_D(TextLabel); d->m_printing = on; } void TextLabel::updateTeXImage() { Q_D(TextLabel); d->updateTeXImage(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void TextLabel::visibilityChanged() { Q_D(const TextLabel); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## TextLabelPrivate::TextLabelPrivate(TextLabel* owner) : teXRenderSuccessful(false), positionInvalid(false), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemSendsGeometryChanges); setAcceptHoverEvents(true); } QString TextLabelPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the label. Called on geometry or text changes. */ void TextLabelPrivate::retransform() { if (suppressRetransform) return; if (position.horizontalPosition != TextLabel::hPositionCustom || position.verticalPosition != TextLabel::vPositionCustom) updatePosition(); float x = position.point.x(); float y = position.point.y(); //determine the size of the label in scene units. float w, h; if (textWrapper.teXUsed) { //image size is in pixel, convert to scene units w = teXImage.width()*teXImageScaleFactor; h = teXImage.height()*teXImageScaleFactor; } else { //size is in points, convert to scene units w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new GraphicsItem's position in parent's coordinate system QPointF itemPos; switch (horizontalAlignment) { case TextLabel::hAlignLeft: itemPos.setX(x - w/2); break; case TextLabel::hAlignCenter: itemPos.setX(x); break; case TextLabel::hAlignRight: itemPos.setX(x + w/2); break; } switch (verticalAlignment) { case TextLabel::vAlignTop: itemPos.setY(y - h/2); break; case TextLabel::vAlignCenter: itemPos.setY(y); break; case TextLabel::vAlignBottom: itemPos.setY(y + h/2); break; } suppressItemChangeEvent = true; setPos(itemPos); suppressItemChangeEvent = false; boundingRectangle.setX(-w/2); boundingRectangle.setY(-h/2); boundingRectangle.setWidth(w); boundingRectangle.setHeight(h); recalcShapeAndBoundingRect(); } /*! calculates the position of the label, when the position relative to the parent was specified (left, right, etc.) */ void TextLabelPrivate::updatePosition() { //determine the parent item QRectF parentRect; QGraphicsItem* parent = parentItem(); if (parent) { parentRect = parent->boundingRect(); } else { if (!scene()) return; parentRect = scene()->sceneRect(); } if (position.horizontalPosition != TextLabel::hPositionCustom) { if (position.horizontalPosition == TextLabel::hPositionLeft) position.point.setX( parentRect.x() ); else if (position.horizontalPosition == TextLabel::hPositionCenter) position.point.setX( parentRect.x() + parentRect.width()/2 ); else if (position.horizontalPosition == TextLabel::hPositionRight) position.point.setX( parentRect.x() + parentRect.width() ); } if (position.verticalPosition != TextLabel::vPositionCustom) { if (position.verticalPosition == TextLabel::vPositionTop) position.point.setY( parentRect.y() ); else if (position.verticalPosition == TextLabel::vPositionCenter) position.point.setY( parentRect.y() + parentRect.height()/2 ); else if (position.verticalPosition == TextLabel::vPositionBottom) position.point.setY( parentRect.y() + parentRect.height() ); } emit q->positionChanged(position); } /*! updates the static text. */ void TextLabelPrivate::updateText() { if (suppressRetransform) return; if (textWrapper.teXUsed) { TeXRenderer::Formatting format; format.fontColor = teXFontColor; format.backgroundColor = teXBackgroundColor; format.fontSize = teXFont.pointSize(); format.fontFamily = teXFont.family(); format.dpi = teXImageResolution; QFuture future = QtConcurrent::run(TeXRenderer::renderImageLaTeX, textWrapper.text, &teXRenderSuccessful, format); teXImageFutureWatcher.setFuture(future); //don't need to call retransorm() here since it is done in updateTeXImage //when the asynchronous rendering of the image is finished. } else { staticText.setText(textWrapper.text); //the size of the label was most probably changed. //call retransform() to recalculate the position and the bounding box of the label retransform(); } } void TextLabelPrivate::updateTeXImage() { teXImage = teXImageFutureWatcher.result(); retransform(); DEBUG("teXRenderSuccessful =" << teXRenderSuccessful); emit q->teXImageUpdated(teXRenderSuccessful); } bool TextLabelPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF TextLabelPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath TextLabelPrivate::shape() const { return labelShape; } /*! recalculates the outer bounds and the shape of the label. */ void TextLabelPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); QMatrix matrix; matrix.rotate(-rotationAngle); transformedBoundingRectangle = matrix.mapRect(boundingRectangle); labelShape = QPainterPath(); labelShape.addRect(boundingRectangle); labelShape = matrix.map(labelShape); emit q->changed(); } void TextLabelPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (positionInvalid) return; if (textWrapper.text.isEmpty()) return; painter->save(); painter->rotate(-rotationAngle); if (textWrapper.teXUsed) { if (boundingRect().width() != 0.0 && boundingRect().height() != 0.0) painter->drawImage(boundingRect(), teXImage); } else { painter->scale(scaleFactor, scaleFactor); float w = staticText.size().width(); float h = staticText.size().height(); painter->drawStaticText(QPoint(-w/2,-h/2), staticText); } painter->restore(); if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(labelShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(labelShape); } } QVariant TextLabelPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates TextLabel::PositionWrapper tempPosition; tempPosition.point = positionFromItemPosition(value.toPointF()); tempPosition.horizontalPosition = TextLabel::hPositionCustom; tempPosition.verticalPosition = TextLabel::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void TextLabelPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = positionFromItemPosition(pos()); if (qAbs(point.x()-position.point.x())>20 && qAbs(point.y()-position.point.y())>20 ) { //position was changed -> set the position related member variables suppressRetransform = true; TextLabel::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = TextLabel::hPositionCustom; tempPosition.verticalPosition = TextLabel::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } /*! * converts label's position to GraphicsItem's position. */ QPointF TextLabelPrivate::positionFromItemPosition(QPointF itemPos) { float x = itemPos.x(); float y = itemPos.y(); float w, h; QPointF tmpPosition; if (textWrapper.teXUsed) { w = teXImage.width()*scaleFactor; h = teXImage.height()*scaleFactor; } else { w = staticText.size().width()*scaleFactor; h = staticText.size().height()*scaleFactor; } //depending on the alignment, calculate the new position switch (horizontalAlignment) { case TextLabel::hAlignLeft: tmpPosition.setX(x + w/2); break; case TextLabel::hAlignCenter: tmpPosition.setX(x); break; case TextLabel::hAlignRight: tmpPosition.setX(x - w/2); break; } switch (verticalAlignment) { case TextLabel::vAlignTop: tmpPosition.setY(y + h/2); break; case TextLabel::vAlignCenter: tmpPosition.setY(y); break; case TextLabel::vAlignBottom: tmpPosition.setY(y - h/2); break; } return tmpPosition; } void TextLabelPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void TextLabelPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void TextLabelPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void TextLabel::save(QXmlStreamWriter* writer) const { Q_D(const TextLabel); writer->writeStartElement( "textLabel" ); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeAttribute( "horizontalAlignment", QString::number(d->horizontalAlignment) ); writer->writeAttribute( "verticalAlignment", QString::number(d->verticalAlignment) ); writer->writeAttribute( "rotationAngle", QString::number(d->rotationAngle) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); writer->writeStartElement( "text" ); writer->writeCharacters( d->textWrapper.text ); writer->writeEndElement(); writer->writeStartElement( "format" ); writer->writeAttribute( "teXUsed", QString::number(d->textWrapper.teXUsed) ); WRITE_QFONT(d->teXFont); writer->writeAttribute( "teXFontColor_r", QString::number(d->teXFontColor.red()) ); writer->writeAttribute( "teXFontColor_g", QString::number(d->teXFontColor.green()) ); writer->writeAttribute( "teXFontColor_b", QString::number(d->teXFontColor.blue()) ); writer->writeEndElement(); if (d->textWrapper.teXUsed) { writer->writeStartElement("teXImage"); QByteArray ba; QBuffer buffer(&ba); buffer.open(QIODevice::WriteOnly); d->teXImage.save(&buffer, "PNG"); writer->writeCharacters(ba.toBase64()); writer->writeEndElement(); } writer->writeEndElement(); // close "textLabel" section } //! Load from XML bool TextLabel::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "textLabel") { reader->raiseError(i18n("no textLabel element found")); return false; } if (!readBasicAttributes(reader)) return false; Q_D(TextLabel); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool teXImageFound = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "textLabel") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPosition'")); else d->position.horizontalPosition = (TextLabel::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPosition'")); else d->position.verticalPosition = (TextLabel::VerticalPosition)str.toInt(); str = attribs.value("horizontalAlignment").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalAlignment'")); else d->horizontalAlignment = (TextLabel::HorizontalAlignment)str.toInt(); str = attribs.value("verticalAlignment").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalAlignment'")); else d->verticalAlignment = (TextLabel::VerticalAlignment)str.toInt(); str = attribs.value("rotationAngle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotationAngle'")); else d->rotationAngle = str.toInt(); str = attribs.value("visible").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "text") { d->textWrapper.text = reader->readElementText(); } else if (!preview && reader->name() == "format") { attribs = reader->attributes(); str = attribs.value("teXUsed").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXUsed'")); else d->textWrapper.teXUsed = str.toInt(); READ_QFONT(d->teXFont); str = attribs.value("teXFontColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_r'")); else d->teXFontColor.setRed( str.toInt() ); str = attribs.value("teXFontColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_g'")); else d->teXFontColor.setGreen( str.toInt() ); str = attribs.value("teXFontColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'teXFontColor_b'")); else d->teXFontColor.setBlue( str.toInt() ); } else if (!preview && reader->name() == "teXImage") { reader->readNext(); QString content = reader->text().toString().trimmed(); QByteArray ba = QByteArray::fromBase64(content.toAscii()); teXImageFound = d->teXImage.loadFromData(ba); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; //in case we use latex and the image was stored (older versions of LabPlot didn't save the image)and loaded, //we just need to retransform. //otherwise, we set the static text and retransform in updateText() if ( !(d->textWrapper.teXUsed && teXImageFound) ) d->updateText(); else retransform(); return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void TextLabel::loadThemeConfig(const KConfig& config) { Q_D(TextLabel); KConfigGroup group = config.group("Label"); const QColor fontColor = group.readEntry("FontColor", QColor(Qt::white)); const QColor backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::black)); d->suppressRetransform = true; if (!d->textWrapper.teXUsed && !d->textWrapper.text.isEmpty()) { //replace colors in the html-formatted string QTextDocument doc; doc.setHtml(d->textWrapper.text); QTextCharFormat fmt; fmt.setForeground(QBrush(fontColor)); fmt.setBackground(QBrush(backgroundColor)); QTextCursor cursor(&doc); cursor.select(QTextCursor::Document); cursor.setCharFormat(fmt); TextLabel::TextWrapper wrapper(doc.toHtml(), d->textWrapper.teXUsed); this->setText(wrapper); } else { //replace colors in the TeX-string this->setTeXFontColor(fontColor); this->setTeXBackgroundColor(backgroundColor); } d->suppressRetransform = false; d->updateText(); } void TextLabel::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Label"); //TODO // group.writeEntry("TeXFontColor", (QColor) this->teXFontColor()); } diff --git a/src/backend/worksheet/Worksheet.cpp b/src/backend/worksheet/Worksheet.cpp index 73e4c6022..f793184ef 100644 --- a/src/backend/worksheet/Worksheet.cpp +++ b/src/backend/worksheet/Worksheet.cpp @@ -1,1048 +1,1048 @@ /*************************************************************************** File : Worksheet.cpp Project : LabPlot Description : Worksheet -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "Worksheet.h" #include "WorksheetPrivate.h" #include "WorksheetElement.h" #include "commonfrontend/worksheet/WorksheetView.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "kdefrontend/worksheet/ExportWorksheetDialog.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include /** * \class Worksheet * \brief Top-level container for worksheet elements like plot, labels, etc. * * The worksheet is, besides the data containers \c Spreadsheet and \c Matrix, another central part of the application * and provides an area for showing and grouping together different kinds of worksheet objects - plots, labels &etc; * * * \ingroup worksheet */ Worksheet::Worksheet(AbstractScriptingEngine* engine, const QString& name, bool loading) : AbstractPart(name), scripted(engine), d(new WorksheetPrivate(this)), m_view(nullptr) { connect(this, &Worksheet::aspectAdded, this, &Worksheet::handleAspectAdded); connect(this, &Worksheet::aspectAboutToBeRemoved, this, &Worksheet::handleAspectAboutToBeRemoved); connect(this, &Worksheet::aspectRemoved, this, &Worksheet::handleAspectRemoved); if (!loading) init(); } Worksheet::~Worksheet() { delete d; } void Worksheet::init() { KConfig config; KConfigGroup group = config.group( "Worksheet" ); //size d->scaleContent = group.readEntry("ScaleContent", false); d->useViewSize = group.readEntry("UseViewSize", false); d->pageRect.setX(0); d->pageRect.setY(0); d->pageRect.setWidth(group.readEntry("Width", 1500)); d->pageRect.setHeight(group.readEntry("Height",1500)); d->m_scene->setSceneRect(d->pageRect); //background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //layout d->layout = (Worksheet::Layout) group.readEntry("Layout", (int) Worksheet::VerticalLayout); d->layoutTopMargin = group.readEntry("LayoutTopMargin", convertToSceneUnits(1, Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", convertToSceneUnits(1, Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", convertToSceneUnits(1, Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", convertToSceneUnits(1, Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", convertToSceneUnits(1, Centimeter)); d->layoutRowCount = group.readEntry("LayoutRowCount", 2); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 2); //default theme KConfigGroup settings = KSharedConfig::openConfig()->group(QLatin1String("Settings_Worksheet")); d->theme = settings.readEntry(QLatin1String("Theme"), ""); if (!d->theme.isEmpty()) loadTheme(d->theme); } /*! converts from \c unit to the scene units. At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertToSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value*10.0; case Worksheet::Centimeter: return value*100.0; case Worksheet::Inch: return value*25.4*10.; case Worksheet::Point: return value*25.4/72.*10.; } return 0; } /*! converts from the scene units to \c unit . At the moment, 1 scene unit corresponds to 1/10 mm. */ float Worksheet::convertFromSceneUnits(const float value, const Worksheet::Unit unit) { switch (unit) { case Worksheet::Millimeter: return value/10.0; case Worksheet::Centimeter: return value/100.0; case Worksheet::Inch: return value/25.4/10.; case Worksheet::Point: return value/25.4/10.*72.; } return 0; } QIcon Worksheet::icon() const { return QIcon::fromTheme("labplot-worksheet"); } /** * Return a new context menu. The caller takes ownership of the menu. */ QMenu* Worksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } //! Construct a primary view on me. /** * This method may be called multiple times during the life time of an Aspect, or it might not get * called at all. Aspects must not depend on the existence of a view for their operation. */ QWidget* Worksheet::view() const { if (!m_partView) { m_view = new WorksheetView(const_cast(this)); m_partView = m_view; connect(m_view, &WorksheetView::statusInfo, this, &Worksheet::statusInfo); } return m_partView; } /*! * returns the list of all parent aspects (folders and sub-folders) * together with all the data containers required to plot the data in the worksheet */ QVector Worksheet::dependsOn() const { //add all parent aspects (folders and sub-folders) QVector aspects = AbstractAspect::dependsOn(); //traverse all plots and add all data containers they depend on for (const auto* plot : children()) aspects << plot->dependsOn(); return aspects; } bool Worksheet::exportView() const { ExportWorksheetDialog* dlg = new ExportWorksheetDialog(m_view); dlg->setFileName(name()); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { QString path = dlg->path(); const WorksheetView::ExportFormat format = dlg->exportFormat(); const WorksheetView::ExportArea area = dlg->exportArea(); const bool background = dlg->exportBackground(); const int resolution = dlg->exportResolution(); WAIT_CURSOR; m_view->exportToFile(path, format, area, background, resolution); RESET_CURSOR; } delete dlg; return ret; } bool Worksheet::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18n("Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) m_view->print(&printer); delete dlg; return ret; } bool Worksheet::printPreview() const { QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, &QPrintPreviewDialog::paintRequested, m_view, &WorksheetView::print); return dlg->exec(); } void Worksheet::handleAspectAdded(const AbstractAspect* aspect) { const WorksheetElement* addedElement = qobject_cast(aspect); if (!addedElement) return; if (aspect->parentAspect() != this) return; //add the GraphicsItem of the added child to the scene QGraphicsItem* item = addedElement->graphicsItem(); d->m_scene->addItem(item); qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->graphicsItem()->setZValue(zVal++); //if a theme was selected in the worksheet, apply this theme for newly added children if (!d->theme.isEmpty() && !isLoading()) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(addedElement)->loadThemeConfig(config); } //recalculated the layout if (!isLoading()) { if (d->layout != Worksheet::NoLayout) d->updateLayout(false); } } void Worksheet::handleAspectAboutToBeRemoved(const AbstractAspect* aspect) { const WorksheetElement* removedElement = qobject_cast(aspect); if (removedElement) { QGraphicsItem* item = removedElement->graphicsItem(); d->m_scene->removeItem(item); } } void Worksheet::handleAspectRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); Q_UNUSED(child); if (d->layout != Worksheet::NoLayout) d->updateLayout(false); } QGraphicsScene* Worksheet::scene() const { return d->m_scene; } QRectF Worksheet::pageRect() const { return d->m_scene->sceneRect(); } /*! this slot is called when a worksheet element is selected in the project explorer. emits \c itemSelected() which forwards this event to the \c WorksheetView in order to select the corresponding \c QGraphicsItem. */ void Worksheet::childSelected(const AbstractAspect* aspect) { WorksheetElement* element=qobject_cast(const_cast(aspect)); if (element) emit itemSelected(element->graphicsItem()); } /*! this slot is called when a worksheet element is deselected in the project explorer. emits \c itemDeselected() which forwards this event to \c WorksheetView in order to deselect the corresponding \c QGraphicsItem. */ void Worksheet::childDeselected(const AbstractAspect* aspect) { WorksheetElement* element=qobject_cast(const_cast(aspect)); if (element) emit itemDeselected(element->graphicsItem()); } /*! * Emits the signal to select or to deselect the aspect corresponding to \c QGraphicsItem \c item in the project explorer, * if \c selected=true or \c selected=false, respectively. * The signal is handled in \c AspectTreeModel and forwarded to the tree view in \c ProjectExplorer. * This function is called in \c WorksheetView upon selection changes. */ void Worksheet::setItemSelectedInView(const QGraphicsItem* item, const bool b) { //determine the corresponding aspect const AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; //forward selection/deselection to AbstractTreeModel if (b) emit childAspectSelectedInView(aspect); else emit childAspectDeselectedInView(aspect); } /*! * helper function: checks whether \c aspect or one of its children has the \c GraphicsItem \c item * Returns a pointer to \c WorksheetElement having this item. */ WorksheetElement* Worksheet::aspectFromGraphicsItem(const WorksheetElement* aspect, const QGraphicsItem* item) const { if ( aspect->graphicsItem() == item ) return const_cast(aspect); else { for (const auto* child : aspect->children(AbstractAspect::IncludeHidden) ) { WorksheetElement* a = this->aspectFromGraphicsItem(child, item); if (a) return a; } return nullptr; } } /*! Selects or deselects the worksheet in the project explorer. This function is called in \c WorksheetView. The worksheet gets deselected if there are selected items in the view, and selected if there are no selected items in the view. */ void Worksheet::setSelectedInView(const bool b) { if (b) emit childAspectSelectedInView(this); else emit childAspectDeselectedInView(this); } void Worksheet::deleteAspectFromGraphicsItem(const QGraphicsItem* item) { Q_ASSERT(item); //determine the corresponding aspect AbstractAspect* aspect(nullptr); for (const auto* child : children(IncludeHidden) ) { aspect = this->aspectFromGraphicsItem(child, item); if (aspect) break; } if (!aspect) return; if (aspect->parentAspect()) aspect->parentAspect()->removeChild(aspect); else this->removeChild(aspect); } void Worksheet::setIsClosing() { if (m_view) m_view->setIsClosing(); } void Worksheet::update() { emit requestUpdate(); } void Worksheet::setSuppressLayoutUpdate(bool value) { d->suppressLayoutUpdate = value; } void Worksheet::updateLayout() { d->updateLayout(); } /* =============================== getter methods for general options ==================================== */ BASIC_D_READER_IMPL(Worksheet, bool, scaleContent, scaleContent) BASIC_D_READER_IMPL(Worksheet, bool, useViewSize, useViewSize) /* =============================== getter methods for background options ================================= */ BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_D_READER_IMPL(Worksheet, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_D_READER_IMPL(Worksheet, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_D_READER_IMPL(Worksheet, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_D_READER_IMPL(Worksheet, QString, backgroundFileName, backgroundFileName) BASIC_D_READER_IMPL(Worksheet, float, backgroundOpacity, backgroundOpacity) /* =============================== getter methods for layout options ====================================== */ BASIC_D_READER_IMPL(Worksheet, Worksheet::Layout, layout, layout) BASIC_D_READER_IMPL(Worksheet, float, layoutTopMargin, layoutTopMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutBottomMargin, layoutBottomMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutLeftMargin, layoutLeftMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutRightMargin, layoutRightMargin) BASIC_D_READER_IMPL(Worksheet, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_D_READER_IMPL(Worksheet, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_D_READER_IMPL(Worksheet, int, layoutRowCount, layoutRowCount) BASIC_D_READER_IMPL(Worksheet, int, layoutColumnCount, layoutColumnCount) CLASS_D_READER_IMPL(Worksheet, QString, theme, theme) /* ============================ setter methods and undo commands for general options ===================== */ void Worksheet::setUseViewSize(bool useViewSize) { if (useViewSize != d->useViewSize) { d->useViewSize = useViewSize; emit useViewSizeRequested(); } } STD_SETTER_CMD_IMPL_S(Worksheet, SetScaleContent, bool, scaleContent) void Worksheet::setScaleContent(bool scaleContent) { if (scaleContent != d->scaleContent) - exec(new WorksheetSetScaleContentCmd(d, scaleContent, i18n("%1: change \"rescale the content\" property"))); + exec(new WorksheetSetScaleContentCmd(d, scaleContent, ki18n("%1: change \"rescale the content\" property"))); } /* ============================ setter methods and undo commands for background options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void Worksheet::setBackgroundType(PlotArea::BackgroundType type) { if (type != d->backgroundType) - exec(new WorksheetSetBackgroundTypeCmd(d, type, i18n("%1: background type changed"))); + exec(new WorksheetSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void Worksheet::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { if (style != d->backgroundColorStyle) - exec(new WorksheetSetBackgroundColorStyleCmd(d, style, i18n("%1: background color style changed"))); + exec(new WorksheetSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void Worksheet::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { if (style != d->backgroundImageStyle) - exec(new WorksheetSetBackgroundImageStyleCmd(d, style, i18n("%1: background image style changed"))); + exec(new WorksheetSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void Worksheet::setBackgroundBrushStyle(Qt::BrushStyle style) { if (style != d->backgroundBrushStyle) - exec(new WorksheetSetBackgroundBrushStyleCmd(d, style, i18n("%1: background brush style changed"))); + exec(new WorksheetSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void Worksheet::setBackgroundFirstColor(const QColor &color) { if (color!= d->backgroundFirstColor) - exec(new WorksheetSetBackgroundFirstColorCmd(d, color, i18n("%1: set background first color"))); + exec(new WorksheetSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void Worksheet::setBackgroundSecondColor(const QColor &color) { if (color!= d->backgroundSecondColor) - exec(new WorksheetSetBackgroundSecondColorCmd(d, color, i18n("%1: set background second color"))); + exec(new WorksheetSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundFileName, QString, backgroundFileName, update) void Worksheet::setBackgroundFileName(const QString& fileName) { if (fileName!= d->backgroundFileName) - exec(new WorksheetSetBackgroundFileNameCmd(d, fileName, i18n("%1: set background image"))); + exec(new WorksheetSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(Worksheet, SetBackgroundOpacity, float, backgroundOpacity, update) void Worksheet::setBackgroundOpacity(float opacity) { if (opacity != d->backgroundOpacity) - exec(new WorksheetSetBackgroundOpacityCmd(d, opacity, i18n("%1: set opacity"))); + exec(new WorksheetSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } /* ============================ setter methods and undo commands for layout options ================= */ STD_SETTER_CMD_IMPL_F_S(Worksheet, SetLayout, Worksheet::Layout, layout, updateLayout) void Worksheet::setLayout(Worksheet::Layout layout) { if (layout != d->layout) { beginMacro(i18n("%1: set layout", name())); - exec(new WorksheetSetLayoutCmd(d, layout, i18n("%1: set layout"))); + exec(new WorksheetSetLayoutCmd(d, layout, ki18n("%1: set layout"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutTopMargin, float, layoutTopMargin, updateLayout) void Worksheet::setLayoutTopMargin(float margin) { if (margin != d->layoutTopMargin) { beginMacro(i18n("%1: set layout top margin", name())); - exec(new WorksheetSetLayoutTopMarginCmd(d, margin, i18n("%1: set layout top margin"))); + exec(new WorksheetSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutBottomMargin, float, layoutBottomMargin, updateLayout) void Worksheet::setLayoutBottomMargin(float margin) { if (margin != d->layoutBottomMargin) { beginMacro(i18n("%1: set layout bottom margin", name())); - exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, i18n("%1: set layout bottom margin"))); + exec(new WorksheetSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutLeftMargin, float, layoutLeftMargin, updateLayout) void Worksheet::setLayoutLeftMargin(float margin) { if (margin != d->layoutLeftMargin) { beginMacro(i18n("%1: set layout left margin", name())); - exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, i18n("%1: set layout left margin"))); + exec(new WorksheetSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRightMargin, float, layoutRightMargin, updateLayout) void Worksheet::setLayoutRightMargin(float margin) { if (margin != d->layoutRightMargin) { beginMacro(i18n("%1: set layout right margin", name())); - exec(new WorksheetSetLayoutRightMarginCmd(d, margin, i18n("%1: set layout right margin"))); + exec(new WorksheetSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, updateLayout) void Worksheet::setLayoutVerticalSpacing(float spacing) { if (spacing != d->layoutVerticalSpacing) { beginMacro(i18n("%1: set layout vertical spacing", name())); - exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, i18n("%1: set layout vertical spacing"))); + exec(new WorksheetSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, updateLayout) void Worksheet::setLayoutHorizontalSpacing(float spacing) { if (spacing != d->layoutHorizontalSpacing) { beginMacro(i18n("%1: set layout horizontal spacing", name())); - exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, i18n("%1: set layout horizontal spacing"))); + exec(new WorksheetSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutRowCount, int, layoutRowCount, updateLayout) void Worksheet::setLayoutRowCount(int count) { if (count != d->layoutRowCount) { beginMacro(i18n("%1: set layout row count", name())); - exec(new WorksheetSetLayoutRowCountCmd(d, count, i18n("%1: set layout row count"))); + exec(new WorksheetSetLayoutRowCountCmd(d, count, ki18n("%1: set layout row count"))); endMacro(); } } STD_SETTER_CMD_IMPL_M_F_S(Worksheet, SetLayoutColumnCount, int, layoutColumnCount, updateLayout) void Worksheet::setLayoutColumnCount(int count) { if (count != d->layoutColumnCount) { beginMacro(i18n("%1: set layout column count", name())); - exec(new WorksheetSetLayoutColumnCountCmd(d, count, i18n("%1: set layout column count"))); + exec(new WorksheetSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); endMacro(); } } class WorksheetSetPageRectCmd : public StandardMacroSetterCmd { public: - WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const QString& description) + WorksheetSetPageRectCmd(Worksheet::Private* target, QRectF newValue, const KLocalizedString& description) : StandardMacroSetterCmd(target, &Worksheet::Private::pageRect, newValue, description) {} void finalize() override { m_target->updatePageRect(); emit m_target->q->pageRectChanged(m_target->*m_field); } void finalizeUndo() override { m_target->m_scene->setSceneRect(m_target->*m_field); emit m_target->q->pageRectChanged(m_target->*m_field); } }; void Worksheet::setPageRect(const QRectF& rect) { //don't allow any rectangulars of width/height equal to zero if (qFuzzyCompare(rect.width(), 0.) || qFuzzyCompare(rect.height(), 0.)) { emit pageRectChanged(d->pageRect); return; } if (rect != d->pageRect) { if (!d->useViewSize) { beginMacro(i18n("%1: set page size", name())); - exec(new WorksheetSetPageRectCmd(d, rect, i18n("%1: set page size"))); + exec(new WorksheetSetPageRectCmd(d, rect, ki18n("%1: set page size"))); endMacro(); } else { d->pageRect = rect; d->updatePageRect(); emit pageRectChanged(d->pageRect); } } } void Worksheet::setPrinting(bool on) const { QVector childElements = children(AbstractAspect::Recursive | AbstractAspect::IncludeHidden); for (auto* child : childElements) child->setPrinting(on); } STD_SETTER_CMD_IMPL_S(Worksheet, SetTheme, QString, theme) void Worksheet::setTheme(const QString& theme) { if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); - exec(new WorksheetSetThemeCmd(d, theme, i18n("%1: set theme"))); + exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else { - exec(new WorksheetSetThemeCmd(d, theme, i18n("%1: disable theming"))); + exec(new WorksheetSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } } //############################################################################## //###################### Private implementation ############################### //############################################################################## WorksheetPrivate::WorksheetPrivate(Worksheet* owner):q(owner), m_scene(new QGraphicsScene()), scaleContent(false), suppressLayoutUpdate(false) { } QString WorksheetPrivate::name() const { return q->name(); } void WorksheetPrivate::updatePageRect() { QRectF oldRect = m_scene->sceneRect(); m_scene->setSceneRect(pageRect); if (layout != Worksheet::NoLayout) updateLayout(); else { if (scaleContent) { qreal horizontalRatio = pageRect.width() / oldRect.width(); qreal verticalRatio = pageRect.height() / oldRect.height(); QVector childElements = q->children(AbstractAspect::IncludeHidden); if (useViewSize) { //don't make the change of the geometry undoable/redoable if the view size is used. for (auto* elem : childElements) { elem->setUndoAware(false); elem->handleResize(horizontalRatio, verticalRatio, true); elem->setUndoAware(true); } } else { for (auto* child : childElements) child->handleResize(horizontalRatio, verticalRatio, true); } } } } void WorksheetPrivate::update() { q->update(); } WorksheetPrivate::~WorksheetPrivate() { delete m_scene; } void WorksheetPrivate::updateLayout(bool undoable) { if (suppressLayoutUpdate) return; QVector list = q->children(); if (layout==Worksheet::NoLayout) { for(auto* elem : list) elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); return; } float x=layoutLeftMargin; float y=layoutTopMargin; float w, h; int count=list.count(); if (layout == Worksheet::VerticalLayout) { w= m_scene->sceneRect().width() - layoutLeftMargin - layoutRightMargin; h=(m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (count-1)*layoutVerticalSpacing)/count; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); y+=h + layoutVerticalSpacing; } } else if (layout == Worksheet::HorizontalLayout) { w=(m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (count-1)*layoutHorizontalSpacing)/count; h= m_scene->sceneRect().height() - layoutTopMargin-layoutBottomMargin; for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x+=w + layoutHorizontalSpacing; } } else { //GridLayout //add new rows, if not sufficient if (count>layoutRowCount*layoutColumnCount) { layoutRowCount = floor( (float)count/layoutColumnCount + 0.5); emit q->layoutRowCountChanged(layoutRowCount); } w=(m_scene->sceneRect().width()-layoutLeftMargin-layoutRightMargin- (layoutColumnCount-1)*layoutHorizontalSpacing)/layoutColumnCount; h=(m_scene->sceneRect().height()-layoutTopMargin-layoutBottomMargin- (layoutRowCount-1)*layoutVerticalSpacing)/layoutRowCount; int columnIndex=0; //counts the columns in a row for (auto* elem : list) { setContainerRect(elem, x, y, h, w, undoable); x+=w + layoutHorizontalSpacing; columnIndex++; if (columnIndex==layoutColumnCount) { columnIndex=0; x=layoutLeftMargin; y+=h + layoutVerticalSpacing; } } } } void WorksheetPrivate::setContainerRect(WorksheetElementContainer* elem, float x, float y, float h, float w, bool undoable) { if (useViewSize) { //when using the view size, no need to put rect changes onto the undo-stack elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else { //don't put rect changed onto the undo-stack if undoable-flag is set to true, //e.g. when new child is added or removed (the layout and the childrend rects will be updated anyway) if (!undoable) { elem->setUndoAware(false); elem->setRect(QRectF(x,y,w,h)); elem->setUndoAware(true); } else elem->setRect(QRectF(x,y,w,h)); } elem->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Worksheet::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "worksheet" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()){ writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); QRectF rect = d->m_scene->sceneRect(); writer->writeAttribute( "x", QString::number(rect.x()) ); writer->writeAttribute( "y", QString::number(rect.y()) ); writer->writeAttribute( "width", QString::number(rect.width()) ); writer->writeAttribute( "height", QString::number(rect.height()) ); writer->writeAttribute( "useViewSize", QString::number(d->useViewSize) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "layout", QString::number(d->layout) ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeAttribute( "rowCount", QString::number(d->layoutRowCount) ); writer->writeEndElement(); //background properties writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //serialize all children for (auto* child : children(IncludeHidden)) child->save(writer); writer->writeEndElement(); // close "worksheet" section } //! Load from XML bool Worksheet::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "worksheet") { reader->raiseError(i18n("no worksheet element found")); return false; } if (!readBasicAttributes(reader)) return false; //clear the theme that was potentially set in init() in order to correctly load here the worksheets without any theme used d->theme = ""; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; QRectF rect; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "worksheet") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else rect.setX(str.toDouble()); str = attribs.value("y").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else rect.setY(str.toDouble()); str = attribs.value("width").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else rect.setWidth(str.toDouble()); str = attribs.value("height").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'height'")); else rect.setHeight(str.toDouble()); str = attribs.value("useViewSize").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'useViewSize'")); else d->useViewSize = str.toInt(); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); str = attribs.value("layout").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("layout")); else d->layout = Worksheet::Layout(str.toInt()); str = attribs.value("topMargin").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("topMargin")); else d->layoutTopMargin = str.toDouble(); str = attribs.value("bottomMargin").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("bottomMargin")); else d->layoutBottomMargin = str.toDouble(); str = attribs.value("leftMargin").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("leftMargin")); else d->layoutLeftMargin = str.toDouble(); str = attribs.value("rightMargin").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rightMargin")); else d->layoutRightMargin = str.toDouble(); str = attribs.value("verticalSpacing").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("verticalSpacing")); else d->layoutVerticalSpacing = str.toDouble(); str = attribs.value("horizontalSpacing").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("horizontalSpacing")); else d->layoutHorizontalSpacing = str.toDouble(); str = attribs.value("columnCount").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("columnCount")); else d->layoutColumnCount = str.toInt(); str = attribs.value("rowCount").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rowCount")); else d->layoutRowCount = str.toInt(); } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->backgroundOpacity = str.toDouble(); } else if(reader->name() == "cartesianPlot") { CartesianPlot* plot = new CartesianPlot(""); plot->setIsLoading(true); if (!plot->load(reader, preview)) { delete plot; return false; } else addChildFast(plot); } else if(reader->name() == "textLabel") { TextLabel* label = new TextLabel(""); if (!label->load(reader, preview)) { delete label; return false; } else addChildFast(label); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (!preview) { d->m_scene->setSceneRect(rect); d->updateLayout(); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Worksheet::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); //apply the same background color for Worksheet as for the CartesianPlot const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); //load the theme for all the children const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } diff --git a/src/backend/worksheet/WorksheetElementContainer.cpp b/src/backend/worksheet/WorksheetElementContainer.cpp index 17633b041..277c8b765 100644 --- a/src/backend/worksheet/WorksheetElementContainer.cpp +++ b/src/backend/worksheet/WorksheetElementContainer.cpp @@ -1,281 +1,281 @@ /*************************************************************************** File : WorksheetElementContainer.cpp Project : LabPlot Description : Worksheet element container - parent of multiple elements -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2012-2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/WorksheetElementContainer.h" #include "backend/worksheet/WorksheetElementContainerPrivate.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include #include #include #include #include /** * \class WorksheetElementContainer * \brief Worksheet element container - parent of multiple elements * \ingroup worksheet * This class provides the functionality for a containers of multiple * worksheet elements. Such a container can be a plot or group of elements. */ WorksheetElementContainer::WorksheetElementContainer(const QString& name) : WorksheetElement(name), d_ptr(new WorksheetElementContainerPrivate(this)) { connect(this, &WorksheetElementContainer::aspectAdded, this, &WorksheetElementContainer::handleAspectAdded); } WorksheetElementContainer::WorksheetElementContainer(const QString& name, WorksheetElementContainerPrivate* dd) : WorksheetElement(name), d_ptr(dd) { connect(this, &WorksheetElementContainer::aspectAdded, this, &WorksheetElementContainer::handleAspectAdded); } WorksheetElementContainer::~WorksheetElementContainer() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem* WorksheetElementContainer::graphicsItem() const { return const_cast(static_cast(d_ptr)); } QRectF WorksheetElementContainer::rect() const { Q_D(const WorksheetElementContainer); return d->rect; } STD_SWAP_METHOD_SETTER_CMD_IMPL(WorksheetElementContainer, SetVisible, bool, swapVisible) void WorksheetElementContainer::setVisible(bool on) { Q_D(WorksheetElementContainer); //take care of proper ordering on the undo-stack, //when making the container and all its children visible/invisible. //if visible is set true, change the visibility of the container first if (on) { beginMacro( i18n("%1: set visible", name()) ); - exec( new WorksheetElementContainerSetVisibleCmd(d, on, i18n("%1: set visible")) ); + exec( new WorksheetElementContainerSetVisibleCmd(d, on, ki18n("%1: set visible")) ); } else { beginMacro( i18n("%1: set invisible", name()) ); } //change the visibility of all children QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (auto* elem : childList) elem->setVisible(on); //if visible is set false, change the visibility of the container last if (!on) - exec(new WorksheetElementContainerSetVisibleCmd(d, false, i18n("%1: set invisible"))); + exec(new WorksheetElementContainerSetVisibleCmd(d, false, ki18n("%1: set invisible"))); endMacro(); } bool WorksheetElementContainer::isVisible() const { Q_D(const WorksheetElementContainer); return d->isVisible(); } bool WorksheetElementContainer::isFullyVisible() const { QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (const auto* elem : childList) { if (!elem->isVisible()) return false; } return true; } void WorksheetElementContainer::setPrinting(bool on) { Q_D(WorksheetElementContainer); d->m_printing = on; } void WorksheetElementContainer::retransform() { // if (isLoading()) // return; PERFTRACE("WorksheetElementContainer::retransform()"); Q_D(WorksheetElementContainer); QVector childList = children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); for (auto* child : childList) child->retransform(); d->recalcShapeAndBoundingRect(); } void WorksheetElementContainer::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("WorksheetElementContainer::handleResize()"); Q_D(const WorksheetElementContainer); if (pageResize) { QRectF rect(d->rect); rect.setWidth(d->rect.width()*horizontalRatio); rect.setHeight(d->rect.height()*verticalRatio); setRect(rect); } else { //TODO // for (auto* elem : children(IncludeHidden)) // elem->handleResize(horizontalRatio, verticalRatio); // retransform(); } } void WorksheetElementContainer::handleAspectAdded(const AbstractAspect* aspect) { Q_D(WorksheetElementContainer); const WorksheetElement* element = qobject_cast(aspect); if (element && (aspect->parentAspect() == this)) { connect(element, &WorksheetElement::hovered, this, &WorksheetElementContainer::childHovered); connect(element, &WorksheetElement::unhovered, this, &WorksheetElementContainer::childUnhovered); element->graphicsItem()->setParentItem(d); qreal zVal = 0; for (auto* child : children(IncludeHidden)) child->setZValue(zVal++); } if (!isLoading()) d->recalcShapeAndBoundingRect(); } void WorksheetElementContainer::childHovered() { Q_D(WorksheetElementContainer); if (!d->isSelected()) { if (d->m_hovered) d->m_hovered = false; d->update(); } } void WorksheetElementContainer::childUnhovered() { Q_D(WorksheetElementContainer); if (!d->isSelected()) { d->m_hovered = true; d->update(); } } void WorksheetElementContainer::prepareGeometryChange() { Q_D(WorksheetElementContainer); d->prepareGeometryChangeRequested(); } //################################################################ //################### Private implementation ########################## //################################################################ WorksheetElementContainerPrivate::WorksheetElementContainerPrivate(WorksheetElementContainer *owner) : q(owner), m_hovered(false), m_printing(false) { setAcceptHoverEvents(true); } QString WorksheetElementContainerPrivate::name() const { return q->name(); } void WorksheetElementContainerPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { scene()->clearSelection(); setSelected(true); QMenu* menu = q->createContextMenu(); menu->exec(event->screenPos()); } void WorksheetElementContainerPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; update(); } } void WorksheetElementContainerPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; update(); } } bool WorksheetElementContainerPrivate::swapVisible(bool on){ bool oldValue = isVisible(); setVisible(on); emit q->visibleChanged(on); return oldValue; } void WorksheetElementContainerPrivate::prepareGeometryChangeRequested() { prepareGeometryChange(); recalcShapeAndBoundingRect(); } void WorksheetElementContainerPrivate::recalcShapeAndBoundingRect() { // if (q->isLoading()) // return; boundingRectangle = QRectF(); containerShape = QPainterPath(); QVector childList = q->children(AbstractAspect::IncludeHidden | AbstractAspect::Compress); foreach (const WorksheetElement* elem, childList) boundingRectangle |= elem->graphicsItem()->mapRectToParent(elem->graphicsItem()->boundingRect()); float penWidth = 2.; boundingRectangle = QRectF(-boundingRectangle.width()/2 - penWidth / 2, -boundingRectangle.height()/2 - penWidth / 2, boundingRectangle.width() + penWidth, boundingRectangle.height() + penWidth); QPainterPath path; path.addRect(boundingRectangle); //make the shape somewhat thicker then the hoveredPen to make the selection/hovering box more visible containerShape.addPath(WorksheetElement::shapeFromPath(path, QPen(QBrush(), penWidth))); } // Inherited from QGraphicsItem QRectF WorksheetElementContainerPrivate::boundingRect() const { return boundingRectangle; } // Inherited from QGraphicsItem void WorksheetElementContainerPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(containerShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(containerShape); } } diff --git a/src/backend/worksheet/plots/AbstractPlot.cpp b/src/backend/worksheet/plots/AbstractPlot.cpp index d7e88cab9..609ceff88 100644 --- a/src/backend/worksheet/plots/AbstractPlot.cpp +++ b/src/backend/worksheet/plots/AbstractPlot.cpp @@ -1,121 +1,121 @@ /*************************************************************************** File : AbstractPlot.cpp Project : LabPlot Description : Base class for plots of different types -------------------------------------------------------------------- Copyright : (C) 2009 Tilman Benkert (thzs@gmx.net) Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/plots/AbstractPlot.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractCoordinateSystem.h" #include "backend/worksheet/WorksheetElementContainerPrivate.h" #include "backend/lib/commandtemplates.h" #include /** * \class AbstractPlot * \brief Second level container in a Worksheet for logical grouping * * TODO: decide the exact role of AbstractPlot * */ AbstractPlot::AbstractPlot(const QString &name):WorksheetElementContainer(name, new AbstractPlotPrivate(this)), m_coordinateSystem(0), m_plotArea(0), m_title(0){ init(); } AbstractPlot::AbstractPlot(const QString &name, AbstractPlotPrivate *dd) : WorksheetElementContainer(name, dd), m_coordinateSystem(0), m_plotArea(0), m_title(0){ init(); } void AbstractPlot::init(){ graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); } PlotArea* AbstractPlot::plotArea(){ return m_plotArea; } AbstractCoordinateSystem* AbstractPlot::coordinateSystem() const{ return m_coordinateSystem; } TextLabel* AbstractPlot::title(){ return m_title; } void AbstractPlot::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("AbstractPlot::handleResize()"); Q_D(AbstractPlot); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); d->horizontalPadding *= ratio; d->verticalPadding *= ratio; WorksheetElementContainer::handleResize(horizontalRatio, verticalRatio, pageResize); } BASIC_SHARED_D_READER_IMPL(AbstractPlot, float, horizontalPadding, horizontalPadding) BASIC_SHARED_D_READER_IMPL(AbstractPlot, float, verticalPadding, verticalPadding) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(AbstractPlot, SetHorizontalPadding, float, horizontalPadding, retransform) void AbstractPlot::setHorizontalPadding(float padding) { Q_D(AbstractPlot); if (padding != d->horizontalPadding) - exec(new AbstractPlotSetHorizontalPaddingCmd(d, padding, i18n("%1: set horizontal padding"))); + exec(new AbstractPlotSetHorizontalPaddingCmd(d, padding, ki18n("%1: set horizontal padding"))); } STD_SETTER_CMD_IMPL_F_S(AbstractPlot, SetVerticalPadding, float, verticalPadding, retransform) void AbstractPlot::setVerticalPadding(float padding) { Q_D(AbstractPlot); if (padding != d->verticalPadding) - exec(new AbstractPlotSetVerticalPaddingCmd(d, padding, i18n("%1: set vertical padding"))); + exec(new AbstractPlotSetVerticalPaddingCmd(d, padding, ki18n("%1: set vertical padding"))); } //################################################################ //################### Private implementation ##################### //################################################################ AbstractPlotPrivate::AbstractPlotPrivate(AbstractPlot *owner) :WorksheetElementContainerPrivate(owner){ } QString AbstractPlotPrivate::name() const{ return q->name(); } diff --git a/src/backend/worksheet/plots/PlotArea.cpp b/src/backend/worksheet/plots/PlotArea.cpp index 62045f480..79afd2c0e 100644 --- a/src/backend/worksheet/plots/PlotArea.cpp +++ b/src/backend/worksheet/plots/PlotArea.cpp @@ -1,587 +1,587 @@ /*************************************************************************** File : PlotArea.cpp Project : LabPlot Description : Plot area (for background filling and clipping). -------------------------------------------------------------------- Copyright : (C) 2011-2015 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/PlotAreaPrivate.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include #include #include #include /** * \class PlotArea * \brief Plot area (for background filling and clipping). * * \ingroup worksheet */ PlotArea::PlotArea(const QString &name):WorksheetElement(name), d_ptr(new PlotAreaPrivate(this)) { init(); } PlotArea::PlotArea(const QString &name, PlotAreaPrivate *dd) : WorksheetElement(name), d_ptr(dd) { init(); } PlotArea::~PlotArea() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void PlotArea::init() { Q_D(PlotArea); setHidden(true);//we don't show PlotArea aspect in the model view. d->rect = QRectF(0, 0, 1, 1); d->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); KConfig config; KConfigGroup group = config.group("PlotArea"); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int)PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); } QGraphicsItem *PlotArea::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(PlotArea, SetVisible, bool, swapVisible) void PlotArea::setVisible(bool on) { Q_D(PlotArea); - exec(new PlotAreaSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new PlotAreaSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool PlotArea::isVisible() const { Q_D(const PlotArea); return d->isVisible(); } void PlotArea::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("PlotArea::handleResize()"); Q_D(PlotArea); Q_UNUSED(pageResize); d->rect.setWidth(d->rect.width()*horizontalRatio); d->rect.setHeight(d->rect.height()*verticalRatio); // TODO: scale line width } void PlotArea::retransform() { } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(PlotArea, bool, clippingEnabled, clippingEnabled()) CLASS_SHARED_D_READER_IMPL(PlotArea, QRectF, rect, rect) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(PlotArea, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) CLASS_SHARED_D_READER_IMPL(PlotArea, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(PlotArea, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(PlotArea, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(PlotArea, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, backgroundOpacity, backgroundOpacity) CLASS_SHARED_D_READER_IMPL(PlotArea, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(PlotArea, qreal, borderOpacity, borderOpacity) /* ============================ setter methods and undo commands ================= */ STD_SWAP_METHOD_SETTER_CMD_IMPL(PlotArea, SetClippingEnabled, bool, toggleClipping); void PlotArea::setClippingEnabled(bool on) { Q_D(PlotArea); if (d->clippingEnabled() != on) - exec(new PlotAreaSetClippingEnabledCmd(d, on, i18n("%1: toggle clipping"))); + exec(new PlotAreaSetClippingEnabledCmd(d, on, ki18n("%1: toggle clipping"))); } /*! * sets plot area rect in scene coordinates. */ void PlotArea::setRect(const QRectF &newRect) { Q_D(PlotArea); d->setRect(newRect); } //Background STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void PlotArea::setBackgroundType(BackgroundType type) { Q_D(PlotArea); if (type != d->backgroundType) - exec(new PlotAreaSetBackgroundTypeCmd(d, type, i18n("%1: background type changed"))); + exec(new PlotAreaSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void PlotArea::setBackgroundColorStyle(BackgroundColorStyle style) { Q_D(PlotArea); if (style != d->backgroundColorStyle) - exec(new PlotAreaSetBackgroundColorStyleCmd(d, style, i18n("%1: background color style changed"))); + exec(new PlotAreaSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void PlotArea::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(PlotArea); if (style != d->backgroundImageStyle) - exec(new PlotAreaSetBackgroundImageStyleCmd(d, style, i18n("%1: background image style changed"))); + exec(new PlotAreaSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void PlotArea::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(PlotArea); if (style != d->backgroundBrushStyle) - exec(new PlotAreaSetBackgroundBrushStyleCmd(d, style, i18n("%1: background brush style changed"))); + exec(new PlotAreaSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void PlotArea::setBackgroundFirstColor(const QColor &color) { Q_D(PlotArea); if (color != d->backgroundFirstColor) - exec(new PlotAreaSetBackgroundFirstColorCmd(d, color, i18n("%1: set background first color"))); + exec(new PlotAreaSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void PlotArea::setBackgroundSecondColor(const QColor &color) { Q_D(PlotArea); if (color != d->backgroundSecondColor) - exec(new PlotAreaSetBackgroundSecondColorCmd(d, color, i18n("%1: set background second color"))); + exec(new PlotAreaSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundFileName, QString, backgroundFileName, update) void PlotArea::setBackgroundFileName(const QString& fileName) { Q_D(PlotArea); if (fileName != d->backgroundFileName) - exec(new PlotAreaSetBackgroundFileNameCmd(d, fileName, i18n("%1: set background image"))); + exec(new PlotAreaSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBackgroundOpacity, qreal, backgroundOpacity, update) void PlotArea::setBackgroundOpacity(qreal opacity) { Q_D(PlotArea); if (opacity != d->backgroundOpacity) - exec(new PlotAreaSetBackgroundOpacityCmd(d, opacity, i18n("%1: set plot area opacity"))); + exec(new PlotAreaSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set plot area opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderPen, QPen, borderPen, update) void PlotArea::setBorderPen(const QPen &pen) { Q_D(PlotArea); if (pen != d->borderPen) - exec(new PlotAreaSetBorderPenCmd(d, pen, i18n("%1: set plot area border"))); + exec(new PlotAreaSetBorderPenCmd(d, pen, ki18n("%1: set plot area border"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderCornerRadius, qreal, borderCornerRadius, update) void PlotArea::setBorderCornerRadius(qreal radius) { Q_D(PlotArea); if (radius != d->borderCornerRadius) - exec(new PlotAreaSetBorderCornerRadiusCmd(d, radius, i18n("%1: set plot area corner radius"))); + exec(new PlotAreaSetBorderCornerRadiusCmd(d, radius, ki18n("%1: set plot area corner radius"))); } STD_SETTER_CMD_IMPL_F_S(PlotArea, SetBorderOpacity, qreal, borderOpacity, update) void PlotArea::setBorderOpacity(qreal opacity) { Q_D(PlotArea); if (opacity != d->borderOpacity) - exec(new PlotAreaSetBorderOpacityCmd(d, opacity, i18n("%1: set plot area border opacity"))); + exec(new PlotAreaSetBorderOpacityCmd(d, opacity, ki18n("%1: set plot area border opacity"))); } //##################################################################### //################### Private implementation ########################## //##################################################################### PlotAreaPrivate::PlotAreaPrivate(PlotArea *owner):q(owner) { } QString PlotAreaPrivate::name() const { return q->name(); } bool PlotAreaPrivate::clippingEnabled() const { return (flags() & QGraphicsItem::ItemClipsChildrenToShape); } bool PlotAreaPrivate::toggleClipping(bool on) { bool oldValue = clippingEnabled(); setFlag(QGraphicsItem::ItemClipsChildrenToShape, on); return oldValue; } bool PlotAreaPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); return oldValue; } void PlotAreaPrivate::setRect(const QRectF& r) { prepareGeometryChange(); rect = mapRectFromScene(r); } QRectF PlotAreaPrivate::boundingRect () const { float width = rect.width(); float height = rect.height(); float penWidth = borderPen.width(); return QRectF(-width/2 - penWidth/2, -height/2 - penWidth/2, width + penWidth, height + penWidth); } QPainterPath PlotAreaPrivate::shape() const { QPainterPath path; if (qFuzzyIsNull(borderCornerRadius)) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); return path; } void PlotAreaPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { // DEBUG("PlotAreaPrivate::paint()"); Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->setBrush(QBrush(pix)); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } // DEBUG("PlotAreaPrivate::paint() DONE"); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void PlotArea::save(QXmlStreamWriter* writer) const { Q_D(const PlotArea); writer->writeStartElement( "plotArea" ); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeAttribute( "borderCornerRadius", QString::number(d->borderCornerRadius) ); writer->writeEndElement(); writer->writeEndElement(); } //! Load from XML bool PlotArea::load(XmlStreamReader* reader, bool preview) { Q_D(PlotArea); if(!reader->isStartElement() || reader->name() != "plotArea") { reader->raiseError(i18n("no plot area element found")); return false; } if ( !readBasicAttributes(reader) ) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while ( !reader->atEnd() ) { reader->readNext(); if (reader->isEndElement() && reader->name() == "plotArea") break; if ( !reader->isStartElement() ) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("borderOpacity")); else d->borderOpacity = str.toDouble(); str = attribs.value("borderCornerRadius").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("borderCornerRadius")); else d->borderCornerRadius = str.toDouble(); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } void PlotArea::loadThemeConfig(const KConfig& config) { const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); this->borderPen().setColor(group.readEntry("BorderColor",(QColor) this->borderPen().color())); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", this->borderCornerRadius())); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); this->borderPen().setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) this->borderPen().style()))); this->borderPen().setWidthF(group.readEntry("BorderWidth", this->borderPen().widthF())); } void PlotArea::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); group.writeEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle()); group.writeEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()); group.writeEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor()); group.writeEntry("BackgroundImageStyle",(int) this->backgroundImageStyle()); group.writeEntry("BackgroundOpacity", this->backgroundOpacity()); group.writeEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor()); group.writeEntry("BackgroundType",(int) this->backgroundType()); group.writeEntry("BorderColor",(QColor) this->borderPen().color()); group.writeEntry("BorderCornerRadius", this->borderCornerRadius()); group.writeEntry("BorderOpacity", this->borderOpacity()); group.writeEntry("BorderStyle", (int) this->borderPen().style()); group.writeEntry("BorderWidth", this->borderPen().widthF()); } diff --git a/src/backend/worksheet/plots/cartesian/Axis.cpp b/src/backend/worksheet/plots/cartesian/Axis.cpp index 190f7bff4..79a619c54 100644 --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -1,2372 +1,2372 @@ /*************************************************************************** File : Axis.cpp Project : LabPlot Description : Axis for cartesian coordinate systems. -------------------------------------------------------------------- Copyright : (C) 2011-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/plots/cartesian/AxisPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/TextLabel.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/core/AbstractColumn.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" // #include "backend/lib/trace.h" #include "kdefrontend/GuiTools.h" #include #include #include #include #include #include extern "C" { #include "backend/nsl/nsl_math.h" } /** * \class AxisGrid * \brief Helper class to get the axis grid drawn with the z-Value=0. * * The painting of the grid lines is separated from the painting of the axis itself. * This allows to use a different z-values for the grid lines (z=0, drawn below all other objects ) * and for the axis (z=FLT_MAX, drawn on top of all other objects) * * \ingroup worksheet */ class AxisGrid : public QGraphicsItem { public: AxisGrid(AxisPrivate* a) { axis = a; setFlag(QGraphicsItem::ItemIsSelectable, false); setFlag(QGraphicsItem::ItemIsFocusable, false); setAcceptHoverEvents(false); } QRectF boundingRect() const override { QPainterPath gridShape; gridShape.addPath(WorksheetElement::shapeFromPath(axis->majorGridPath, axis->majorGridPen)); gridShape.addPath(WorksheetElement::shapeFromPath(axis->minorGridPath, axis->minorGridPen)); QRectF boundingRectangle = gridShape.boundingRect(); return boundingRectangle; } void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) override { Q_UNUSED(option) Q_UNUSED(widget) if (!axis->isVisible()) return; if (axis->linePath.isEmpty()) return; //draw major grid if (axis->majorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->majorGridOpacity); painter->setPen(axis->majorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->majorGridPath); } //draw minor grid if (axis->minorGridPen.style() != Qt::NoPen) { painter->setOpacity(axis->minorGridOpacity); painter->setPen(axis->minorGridPen); painter->setBrush(Qt::NoBrush); painter->drawPath(axis->minorGridPath); } } private: AxisPrivate* axis; }; /** * \class Axis * \brief Axis for cartesian coordinate systems. * * \ingroup worksheet */ Axis::Axis(const QString& name, AxisOrientation orientation) : WorksheetElement(name), d_ptr(new AxisPrivate(this)), m_menusInitialized(false) { d_ptr->orientation = orientation; init(); } Axis::Axis(const QString& name, AxisOrientation orientation, AxisPrivate* dd) : WorksheetElement(name), d_ptr(dd), m_menusInitialized(false) { d_ptr->orientation = orientation; init(); } void Axis::finalizeAdd() { Q_D(Axis); d->plot = dynamic_cast(parentAspect()); Q_ASSERT(d->plot); d->cSystem = dynamic_cast(d->plot->coordinateSystem()); } void Axis::init() { Q_D(Axis); KConfig config; KConfigGroup group = config.group( "Axis" ); d->autoScale = true; d->position = Axis::AxisCustom; d->offset = group.readEntry("PositionOffset", 0); d->scale = (Axis::AxisScale) group.readEntry("Scale", (int) Axis::ScaleLinear); d->autoScale = group.readEntry("AutoScale", true); d->start = group.readEntry("Start", 0); d->end = group.readEntry("End", 10); d->zeroOffset = group.readEntry("ZeroOffset", 0); d->scalingFactor = group.readEntry("ScalingFactor", 1.0); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int) Qt::SolidLine) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->arrowType = (Axis::ArrowType) group.readEntry("ArrowType", (int)Axis::NoArrow); d->arrowPosition = (Axis::ArrowPosition) group.readEntry("ArrowPosition", (int)Axis::ArrowRight); d->arrowSize = group.readEntry("ArrowSize", Worksheet::convertToSceneUnits(10, Worksheet::Point)); // axis title d->title = new TextLabel(this->name(), TextLabel::AxisTitle); connect( d->title, &TextLabel::changed, this, &Axis::labelChanged); addChild(d->title); d->title->setHidden(true); d->title->graphicsItem()->setParentItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); d->title->graphicsItem()->setAcceptHoverEvents(false); d->title->setText(this->name()); if (d->orientation == AxisVertical) d->title->setRotationAngle(90); d->titleOffsetX = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->titleOffsetY = Worksheet::convertToSceneUnits(2, Worksheet::Point); //distance to the axis tick labels d->majorTicksDirection = (Axis::TicksDirection) group.readEntry("MajorTicksDirection", (int) Axis::ticksOut); d->majorTicksType = (Axis::TicksType) group.readEntry("MajorTicksType", (int) Axis::TicksTotalNumber); d->majorTicksNumber = group.readEntry("MajorTicksNumber", 11); d->majorTicksIncrement = group.readEntry("MajorTicksIncrement", 1.0); d->majorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MajorTicksLineStyle", (int)Qt::SolidLine) ); d->majorTicksPen.setColor( group.readEntry("MajorTicksColor", QColor(Qt::black) ) ); d->majorTicksPen.setWidthF( group.readEntry("MajorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->majorTicksLength = group.readEntry("MajorTicksLength", Worksheet::convertToSceneUnits(6.0, Worksheet::Point)); d->majorTicksOpacity = group.readEntry("MajorTicksOpacity", 1.0); d->minorTicksDirection = (Axis::TicksDirection) group.readEntry("MinorTicksDirection", (int) Axis::ticksOut); d->minorTicksType = (Axis::TicksType) group.readEntry("MinorTicksType", (int) Axis::TicksTotalNumber); d->minorTicksNumber = group.readEntry("MinorTicksNumber", 1); d->minorTicksIncrement = group.readEntry("MinorTicksIncrement", 0.5); d->minorTicksPen.setStyle((Qt::PenStyle) group.readEntry("MinorTicksLineStyle", (int)Qt::SolidLine) ); d->minorTicksPen.setColor( group.readEntry("MinorTicksColor", QColor(Qt::black) ) ); d->minorTicksPen.setWidthF( group.readEntry("MinorTicksWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point) ) ); d->minorTicksLength = group.readEntry("MinorTicksLength", Worksheet::convertToSceneUnits(3.0, Worksheet::Point)); d->minorTicksOpacity = group.readEntry("MinorTicksOpacity", 1.0); //Labels d->labelsFormat = (Axis::LabelsFormat) group.readEntry("LabelsFormat", (int)Axis::FormatDecimal); d->labelsAutoPrecision = group.readEntry("LabelsAutoPrecision", true); d->labelsPrecision = group.readEntry("LabelsPrecision", 1); d->labelsPosition = (Axis::LabelsPosition) group.readEntry("LabelsPosition", (int) Axis::LabelsOut); d->labelsOffset= group.readEntry("LabelsOffset", Worksheet::convertToSceneUnits( 5.0, Worksheet::Point )); d->labelsRotationAngle = group.readEntry("LabelsRotation", 0); d->labelsFont = group.readEntry("LabelsFont", QFont()); d->labelsFont.setPixelSize( Worksheet::convertToSceneUnits( 10.0, Worksheet::Point ) ); d->labelsColor = group.readEntry("LabelsFontColor", QColor(Qt::black)); d->labelsPrefix = group.readEntry("LabelsPrefix", "" ); d->labelsSuffix = group.readEntry("LabelsSuffix", "" ); d->labelsOpacity = group.readEntry("LabelsOpacity", 1.0); //major grid d->majorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MajorGridStyle", (int) Qt::NoPen) ); d->majorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->majorGridPen.setWidthF( group.readEntry("MajorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->majorGridOpacity = group.readEntry("MajorGridOpacity", 1.0); //minor grid d->minorGridPen.setStyle( (Qt::PenStyle) group.readEntry("MinorGridStyle", (int) Qt::NoPen) ); d->minorGridPen.setColor(group.readEntry("MajorGridColor", QColor(Qt::gray)) ); d->minorGridPen.setWidthF( group.readEntry("MinorGridWidth", Worksheet::convertToSceneUnits( 1.0, Worksheet::Point ) ) ); d->minorGridOpacity = group.readEntry("MinorGridOpacity", 1.0); } /*! * For the most frequently edited properties, create Actions and ActionGroups for the context menu. * For some ActionGroups the actual actions are created in \c GuiTool, */ void Axis::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Axis::visibilityChangedSlot); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, &QActionGroup::triggered, this, &Axis::orientationChangedSlot); orientationHorizontalAction = new QAction(i18n("horizontal"), orientationActionGroup); orientationHorizontalAction->setCheckable(true); orientationVerticalAction = new QAction(i18n("vertical"), orientationActionGroup); orientationVerticalAction->setCheckable(true); //Line lineStyleActionGroup = new QActionGroup(this); lineStyleActionGroup->setExclusive(true); connect(lineStyleActionGroup, &QActionGroup::triggered, this, &Axis::lineStyleChanged); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, &QActionGroup::triggered, this, &Axis::lineColorChanged); //Ticks //TODO } void Axis::initMenus() { this->initActions(); //Orientation orientationMenu = new QMenu(i18n("Orientation")); orientationMenu->addAction(orientationHorizontalAction); orientationMenu->addAction(orientationVerticalAction); //Line lineMenu = new QMenu(i18n("Line")); lineStyleMenu = new QMenu(i18n("style"), lineMenu); lineMenu->addMenu( lineStyleMenu ); lineColorMenu = new QMenu(i18n("color"), lineMenu); GuiTools::fillColorMenu( lineColorMenu, lineColorActionGroup ); lineMenu->addMenu( lineColorMenu ); m_menusInitialized = true; } QMenu* Axis::createContextMenu() { if (!m_menusInitialized) initMenus(); Q_D(const Axis); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //Orientation if ( d->orientation == AxisHorizontal ) orientationHorizontalAction->setChecked(true); else orientationVerticalAction->setChecked(true); menu->insertMenu(firstAction, orientationMenu); //Line styles GuiTools::updatePenStyles( lineStyleMenu, lineStyleActionGroup, d->linePen.color() ); GuiTools::selectPenStyleAction(lineStyleActionGroup, d->linePen.style() ); GuiTools::selectColorAction(lineColorActionGroup, d->linePen.color() ); menu->insertMenu(firstAction, lineMenu); menu->insertSeparator(firstAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Axis::icon() const{ Q_D(const Axis); QIcon ico; if (d->orientation == Axis::AxisHorizontal) ico = QIcon::fromTheme("labplot-axis-horizontal"); else ico = QIcon::fromTheme("labplot-axis-vertical"); return ico; } Axis::~Axis() { if (m_menusInitialized) { delete orientationMenu; delete lineMenu; } //no need to delete d->title, since it was added with addChild in init(); //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } QGraphicsItem *Axis::graphicsItem() const { return d_ptr; } /*! * overrides the implementation in WorksheetElement and sets the z-value to the maximal possible, * axes are drawn on top of all other object in the plot. */ void Axis::setZValue(qreal) { Q_D(Axis); d->setZValue(std::numeric_limits::max()); d->gridItem->setParentItem(d->parentItem()); d->gridItem->setZValue(0); } void Axis::retransform() { Q_D(Axis); d->retransform(); } void Axis::setSuppressRetransform(bool value) { Q_D(Axis); d->suppressRetransform = value; } void Axis::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { DEBUG("Axis::handleResize()"); Q_D(Axis); Q_UNUSED(pageResize); double ratio = 0; if (horizontalRatio > 1.0 || verticalRatio > 1.0) ratio = qMax(horizontalRatio, verticalRatio); else ratio = qMin(horizontalRatio, verticalRatio); QPen pen = d->linePen; pen.setWidthF(pen.widthF() * ratio); d->linePen = pen; d->majorTicksLength *= ratio; // ticks are perpendicular to axis line -> verticalRatio relevant d->minorTicksLength *= ratio; d->labelsFont.setPixelSize( d->labelsFont.pixelSize() * ratio ); //TODO: take into account rotated labels d->labelsOffset *= ratio; d->title->handleResize(horizontalRatio, verticalRatio, pageResize); } /* ============================ getter methods ================= */ BASIC_SHARED_D_READER_IMPL(Axis, bool, autoScale, autoScale) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisOrientation, orientation, orientation) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisPosition, position, position) BASIC_SHARED_D_READER_IMPL(Axis, Axis::AxisScale, scale, scale) BASIC_SHARED_D_READER_IMPL(Axis, double, offset, offset) BASIC_SHARED_D_READER_IMPL(Axis, double, start, start) BASIC_SHARED_D_READER_IMPL(Axis, double, end, end) BASIC_SHARED_D_READER_IMPL(Axis, qreal, scalingFactor, scalingFactor) BASIC_SHARED_D_READER_IMPL(Axis, qreal, zeroOffset, zeroOffset) BASIC_SHARED_D_READER_IMPL(Axis, TextLabel*, title, title) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetX, titleOffsetX) BASIC_SHARED_D_READER_IMPL(Axis, qreal, titleOffsetY, titleOffsetY) CLASS_SHARED_D_READER_IMPL(Axis, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, lineOpacity, lineOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowType, arrowType, arrowType) BASIC_SHARED_D_READER_IMPL(Axis, Axis::ArrowPosition, arrowPosition, arrowPosition) BASIC_SHARED_D_READER_IMPL(Axis, qreal, arrowSize, arrowSize) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, majorTicksDirection, majorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, majorTicksType, majorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, majorTicksNumber, majorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksIncrement, majorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, majorTicksColumn, majorTicksColumn) QString& Axis::majorTicksColumnPath() const { return d_ptr->majorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksLength, majorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorTicksPen, majorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorTicksOpacity, majorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksDirection, minorTicksDirection, minorTicksDirection) BASIC_SHARED_D_READER_IMPL(Axis, Axis::TicksType, minorTicksType, minorTicksType) BASIC_SHARED_D_READER_IMPL(Axis, int, minorTicksNumber, minorTicksNumber) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksIncrement, minorTicksIncrement) BASIC_SHARED_D_READER_IMPL(Axis, const AbstractColumn*, minorTicksColumn, minorTicksColumn) QString& Axis::minorTicksColumnPath() const { return d_ptr->minorTicksColumnPath; } BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksLength, minorTicksLength) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorTicksPen, minorTicksPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorTicksOpacity, minorTicksOpacity) BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsFormat, labelsFormat, labelsFormat); BASIC_SHARED_D_READER_IMPL(Axis, bool, labelsAutoPrecision, labelsAutoPrecision); BASIC_SHARED_D_READER_IMPL(Axis, int, labelsPrecision, labelsPrecision); BASIC_SHARED_D_READER_IMPL(Axis, Axis::LabelsPosition, labelsPosition, labelsPosition); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOffset, labelsOffset); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsRotationAngle, labelsRotationAngle); CLASS_SHARED_D_READER_IMPL(Axis, QColor, labelsColor, labelsColor); CLASS_SHARED_D_READER_IMPL(Axis, QFont, labelsFont, labelsFont); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsPrefix, labelsPrefix); CLASS_SHARED_D_READER_IMPL(Axis, QString, labelsSuffix, labelsSuffix); BASIC_SHARED_D_READER_IMPL(Axis, qreal, labelsOpacity, labelsOpacity); CLASS_SHARED_D_READER_IMPL(Axis, QPen, majorGridPen, majorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, majorGridOpacity, majorGridOpacity) CLASS_SHARED_D_READER_IMPL(Axis, QPen, minorGridPen, minorGridPen) BASIC_SHARED_D_READER_IMPL(Axis, qreal, minorGridOpacity, minorGridOpacity) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(Axis, SetAutoScale, bool, autoScale, retransform); void Axis::setAutoScale(bool autoScale) { Q_D(Axis); if (autoScale != d->autoScale) { - exec(new AxisSetAutoScaleCmd(d, autoScale, i18n("%1: set axis auto scaling"))); + exec(new AxisSetAutoScaleCmd(d, autoScale, ki18n("%1: set axis auto scaling"))); if (autoScale) { CartesianPlot *plot = qobject_cast(parentAspect()); if (!plot) return; if (d->orientation == Axis::AxisHorizontal) { d->end = plot->xMax(); d->start = plot->xMin(); } else { d->end = plot->yMax(); d->start = plot->yMin(); } retransform(); emit endChanged(d->end); emit startChanged(d->start); } } } STD_SWAP_METHOD_SETTER_CMD_IMPL(Axis, SetVisible, bool, swapVisible); void Axis::setVisible(bool on) { Q_D(Axis); - exec(new AxisSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new AxisSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Axis::isVisible() const { Q_D(const Axis); return d->isVisible(); } void Axis::setPrinting(bool on) { Q_D(Axis); d->setPrinting(on); } STD_SETTER_CMD_IMPL_F_S(Axis, SetOrientation, Axis::AxisOrientation, orientation, retransform); void Axis::setOrientation( AxisOrientation orientation) { Q_D(Axis); if (orientation != d->orientation) - exec(new AxisSetOrientationCmd(d, orientation, i18n("%1: set axis orientation"))); + exec(new AxisSetOrientationCmd(d, orientation, ki18n("%1: set axis orientation"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetPosition, Axis::AxisPosition, position, retransform); void Axis::setPosition(AxisPosition position) { Q_D(Axis); if (position != d->position) - exec(new AxisSetPositionCmd(d, position, i18n("%1: set axis position"))); + exec(new AxisSetPositionCmd(d, position, ki18n("%1: set axis position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScaling, Axis::AxisScale, scale, retransformTicks); void Axis::setScale(AxisScale scale) { Q_D(Axis); if (scale != d->scale) - exec(new AxisSetScalingCmd(d, scale, i18n("%1: set axis scale"))); + exec(new AxisSetScalingCmd(d, scale, ki18n("%1: set axis scale"))); } STD_SETTER_CMD_IMPL_F(Axis, SetOffset, double, offset, retransform); void Axis::setOffset(double offset, bool undo) { Q_D(Axis); if (offset != d->offset) { if (undo) { - exec(new AxisSetOffsetCmd(d, offset, i18n("%1: set axis offset"))); + exec(new AxisSetOffsetCmd(d, offset, ki18n("%1: set axis offset"))); } else { d->offset = offset; //don't need to call retransform() afterward //since the only usage of this call is in CartesianPlot, where retransform is called for all children anyway. } emit positionChanged(offset); } } STD_SETTER_CMD_IMPL_F_S(Axis, SetStart, double, start, retransform); void Axis::setStart(double start) { Q_D(Axis); if (start != d->start) - exec(new AxisSetStartCmd(d, start, i18n("%1: set axis start"))); + exec(new AxisSetStartCmd(d, start, ki18n("%1: set axis start"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, double, end, retransform); void Axis::setEnd(double end) { Q_D(Axis); if (end != d->end) - exec(new AxisSetEndCmd(d, end, i18n("%1: set axis end"))); + exec(new AxisSetEndCmd(d, end, ki18n("%1: set axis end"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetZeroOffset, qreal, zeroOffset, retransform); void Axis::setZeroOffset(qreal zeroOffset) { Q_D(Axis); if (zeroOffset != d->zeroOffset) - exec(new AxisSetZeroOffsetCmd(d, zeroOffset, i18n("%1: set axis zero offset"))); + exec(new AxisSetZeroOffsetCmd(d, zeroOffset, ki18n("%1: set axis zero offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetScalingFactor, qreal, scalingFactor, retransform); void Axis::setScalingFactor(qreal scalingFactor) { Q_D(Axis); if (scalingFactor != d->scalingFactor) - exec(new AxisSetScalingFactorCmd(d, scalingFactor, i18n("%1: set axis scaling factor"))); + exec(new AxisSetScalingFactorCmd(d, scalingFactor, ki18n("%1: set axis scaling factor"))); } //Title STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, qreal, titleOffsetX, retransform); void Axis::setTitleOffsetX(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetX) - exec(new AxisSetTitleOffsetXCmd(d, offset, i18n("%1: set title offset"))); + exec(new AxisSetTitleOffsetXCmd(d, offset, ki18n("%1: set title offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, qreal, titleOffsetY, retransform); void Axis::setTitleOffsetY(qreal offset) { Q_D(Axis); if (offset != d->titleOffsetY) - exec(new AxisSetTitleOffsetYCmd(d, offset, i18n("%1: set title offset"))); + exec(new AxisSetTitleOffsetYCmd(d, offset, ki18n("%1: set title offset"))); } //Line STD_SETTER_CMD_IMPL_F_S(Axis, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect); void Axis::setLinePen(const QPen &pen) { Q_D(Axis); if (pen != d->linePen) - exec(new AxisSetLinePenCmd(d, pen, i18n("%1: set line style"))); + exec(new AxisSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLineOpacity, qreal, lineOpacity, update); void Axis::setLineOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->lineOpacity) - exec(new AxisSetLineOpacityCmd(d, opacity, i18n("%1: set line opacity"))); + exec(new AxisSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowType, Axis::ArrowType, arrowType, retransformArrow); void Axis::setArrowType(ArrowType type) { Q_D(Axis); if (type != d->arrowType) - exec(new AxisSetArrowTypeCmd(d, type, i18n("%1: set arrow type"))); + exec(new AxisSetArrowTypeCmd(d, type, ki18n("%1: set arrow type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowPosition, Axis::ArrowPosition, arrowPosition, retransformArrow); void Axis::setArrowPosition(ArrowPosition position) { Q_D(Axis); if (position != d->arrowPosition) - exec(new AxisSetArrowPositionCmd(d, position, i18n("%1: set arrow position"))); + exec(new AxisSetArrowPositionCmd(d, position, ki18n("%1: set arrow position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, qreal, arrowSize, retransformArrow); void Axis::setArrowSize(qreal arrowSize) { Q_D(Axis); if (arrowSize != d->arrowSize) - exec(new AxisSetArrowSizeCmd(d, arrowSize, i18n("%1: set arrow size"))); + exec(new AxisSetArrowSizeCmd(d, arrowSize, ki18n("%1: set arrow size"))); } //Major ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksDirection, Axis::TicksDirection, majorTicksDirection, retransformTicks); void Axis::setMajorTicksDirection(const TicksDirection majorTicksDirection) { Q_D(Axis); if (majorTicksDirection != d->majorTicksDirection) - exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, i18n("%1: set major ticks direction"))); + exec(new AxisSetMajorTicksDirectionCmd(d, majorTicksDirection, ki18n("%1: set major ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksType, Axis::TicksType, majorTicksType, retransformTicks); void Axis::setMajorTicksType(const TicksType majorTicksType) { Q_D(Axis); if (majorTicksType!= d->majorTicksType) - exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, i18n("%1: set major ticks type"))); + exec(new AxisSetMajorTicksTypeCmd(d, majorTicksType, ki18n("%1: set major ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksNumber, int, majorTicksNumber, retransformTicks); void Axis::setMajorTicksNumber(int majorTicksNumber) { Q_D(Axis); if (majorTicksNumber != d->majorTicksNumber) - exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, i18n("%1: set the total number of the major ticks"))); + exec(new AxisSetMajorTicksNumberCmd(d, majorTicksNumber, ki18n("%1: set the total number of the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksIncrement, qreal, majorTicksIncrement, retransformTicks); void Axis::setMajorTicksIncrement(qreal majorTicksIncrement) { Q_D(Axis); if (majorTicksIncrement != d->majorTicksIncrement) - exec(new AxisSetMajorTicksIncrementCmd(d, majorTicksIncrement, i18n("%1: set the increment for the major ticks"))); + exec(new AxisSetMajorTicksIncrementCmd(d, majorTicksIncrement, ki18n("%1: set the increment for the major ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksColumn, const AbstractColumn*, majorTicksColumn, retransformTicks) void Axis::setMajorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->majorTicksColumn) { - exec(new AxisSetMajorTicksColumnCmd(d, column, i18n("%1: assign major ticks' values"))); + exec(new AxisSetMajorTicksColumnCmd(d, column, ki18n("%1: assign major ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::majorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksPen, QPen, majorTicksPen, recalcShapeAndBoundingRect); void Axis::setMajorTicksPen(const QPen &pen) { Q_D(Axis); if (pen != d->majorTicksPen) - exec(new AxisSetMajorTicksPenCmd(d, pen, i18n("%1: set major ticks style"))); + exec(new AxisSetMajorTicksPenCmd(d, pen, ki18n("%1: set major ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksLength, qreal, majorTicksLength, retransformTicks); void Axis::setMajorTicksLength(qreal majorTicksLength) { Q_D(Axis); if (majorTicksLength != d->majorTicksLength) - exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, i18n("%1: set major ticks length"))); + exec(new AxisSetMajorTicksLengthCmd(d, majorTicksLength, ki18n("%1: set major ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorTicksOpacity, qreal, majorTicksOpacity, update); void Axis::setMajorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorTicksOpacity) - exec(new AxisSetMajorTicksOpacityCmd(d, opacity, i18n("%1: set major ticks opacity"))); + exec(new AxisSetMajorTicksOpacityCmd(d, opacity, ki18n("%1: set major ticks opacity"))); } //Minor ticks STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksDirection, Axis::TicksDirection, minorTicksDirection, retransformTicks); void Axis::setMinorTicksDirection(const TicksDirection minorTicksDirection) { Q_D(Axis); if (minorTicksDirection != d->minorTicksDirection) - exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, i18n("%1: set minor ticks direction"))); + exec(new AxisSetMinorTicksDirectionCmd(d, minorTicksDirection, ki18n("%1: set minor ticks direction"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksType, Axis::TicksType, minorTicksType, retransformTicks); void Axis::setMinorTicksType(const TicksType minorTicksType) { Q_D(Axis); if (minorTicksType!= d->minorTicksType) - exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, i18n("%1: set minor ticks type"))); + exec(new AxisSetMinorTicksTypeCmd(d, minorTicksType, ki18n("%1: set minor ticks type"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksNumber, int, minorTicksNumber, retransformTicks); void Axis::setMinorTicksNumber(int minorTicksNumber) { Q_D(Axis); if (minorTicksNumber != d->minorTicksNumber) - exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, i18n("%1: set the total number of the minor ticks"))); + exec(new AxisSetMinorTicksNumberCmd(d, minorTicksNumber, ki18n("%1: set the total number of the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksIncrement, qreal, minorTicksIncrement, retransformTicks); void Axis::setMinorTicksIncrement(qreal minorTicksIncrement) { Q_D(Axis); if (minorTicksIncrement != d->minorTicksIncrement) - exec(new AxisSetMinorTicksIncrementCmd(d, minorTicksIncrement, i18n("%1: set the increment for the minor ticks"))); + exec(new AxisSetMinorTicksIncrementCmd(d, minorTicksIncrement, ki18n("%1: set the increment for the minor ticks"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksColumn, const AbstractColumn*, minorTicksColumn, retransformTicks) void Axis::setMinorTicksColumn(const AbstractColumn* column) { Q_D(Axis); if (column != d->minorTicksColumn) { - exec(new AxisSetMinorTicksColumnCmd(d, column, i18n("%1: assign minor ticks' values"))); + exec(new AxisSetMinorTicksColumnCmd(d, column, ki18n("%1: assign minor ticks' values"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Axis::retransformTicks); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Axis::minorTicksColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksPen, QPen, minorTicksPen, recalcShapeAndBoundingRect); void Axis::setMinorTicksPen(const QPen &pen) { Q_D(Axis); if (pen != d->minorTicksPen) - exec(new AxisSetMinorTicksPenCmd(d, pen, i18n("%1: set minor ticks style"))); + exec(new AxisSetMinorTicksPenCmd(d, pen, ki18n("%1: set minor ticks style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksLength, qreal, minorTicksLength, retransformTicks); void Axis::setMinorTicksLength(qreal minorTicksLength) { Q_D(Axis); if (minorTicksLength != d->minorTicksLength) - exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, i18n("%1: set minor ticks length"))); + exec(new AxisSetMinorTicksLengthCmd(d, minorTicksLength, ki18n("%1: set minor ticks length"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorTicksOpacity, qreal, minorTicksOpacity, update); void Axis::setMinorTicksOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorTicksOpacity) - exec(new AxisSetMinorTicksOpacityCmd(d, opacity, i18n("%1: set minor ticks opacity"))); + exec(new AxisSetMinorTicksOpacityCmd(d, opacity, ki18n("%1: set minor ticks opacity"))); } //Labels STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFormat, Axis::LabelsFormat, labelsFormat, retransformTicks); void Axis::setLabelsFormat(const LabelsFormat labelsFormat) { Q_D(Axis); if (labelsFormat != d->labelsFormat) { - exec(new AxisSetLabelsFormatCmd(d, labelsFormat, i18n("%1: set labels format"))); + exec(new AxisSetLabelsFormatCmd(d, labelsFormat, ki18n("%1: set labels format"))); //TODO: this part is not undo/redo-aware if (d->labelsFormatAutoChanged && labelsFormat == Axis::FormatDecimal) d->labelsFormatDecimalOverruled = true; else d->labelsFormatDecimalOverruled = false; } } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsAutoPrecision, bool, labelsAutoPrecision, retransformTickLabelStrings); void Axis::setLabelsAutoPrecision(const bool labelsAutoPrecision) { Q_D(Axis); if (labelsAutoPrecision != d->labelsAutoPrecision) - exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, i18n("%1: set labels precision"))); + exec(new AxisSetLabelsAutoPrecisionCmd(d, labelsAutoPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrecision, int, labelsPrecision, retransformTickLabelStrings); void Axis::setLabelsPrecision(const int labelsPrecision) { Q_D(Axis); if (labelsPrecision != d->labelsPrecision) - exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, i18n("%1: set labels precision"))); + exec(new AxisSetLabelsPrecisionCmd(d, labelsPrecision, ki18n("%1: set labels precision"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPosition, Axis::LabelsPosition, labelsPosition, retransformTickLabelPositions); void Axis::setLabelsPosition(const LabelsPosition labelsPosition) { Q_D(Axis); if (labelsPosition != d->labelsPosition) - exec(new AxisSetLabelsPositionCmd(d, labelsPosition, i18n("%1: set labels position"))); + exec(new AxisSetLabelsPositionCmd(d, labelsPosition, ki18n("%1: set labels position"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, double, labelsOffset, retransformTickLabelPositions); void Axis::setLabelsOffset(double offset) { Q_D(Axis); if (offset != d->labelsOffset) - exec(new AxisSetLabelsOffsetCmd(d, offset, i18n("%1: set label offset"))); + exec(new AxisSetLabelsOffsetCmd(d, offset, ki18n("%1: set label offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsRotationAngle, qreal, labelsRotationAngle, recalcShapeAndBoundingRect); void Axis::setLabelsRotationAngle(qreal angle) { Q_D(Axis); if (angle != d->labelsRotationAngle) - exec(new AxisSetLabelsRotationAngleCmd(d, angle, i18n("%1: set label rotation angle"))); + exec(new AxisSetLabelsRotationAngleCmd(d, angle, ki18n("%1: set label rotation angle"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsColor, QColor, labelsColor, update); void Axis::setLabelsColor(const QColor &color) { Q_D(Axis); if (color != d->labelsColor) - exec(new AxisSetLabelsColorCmd(d, color, i18n("%1: set label color"))); + exec(new AxisSetLabelsColorCmd(d, color, ki18n("%1: set label color"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsFont, QFont, labelsFont, retransformTickLabelStrings); void Axis::setLabelsFont(const QFont &font) { Q_D(Axis); if (font != d->labelsFont) - exec(new AxisSetLabelsFontCmd(d, font, i18n("%1: set label font"))); + exec(new AxisSetLabelsFontCmd(d, font, ki18n("%1: set label font"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsPrefix, QString, labelsPrefix, retransformTickLabelStrings); void Axis::setLabelsPrefix(const QString& prefix) { Q_D(Axis); if (prefix != d->labelsPrefix) - exec(new AxisSetLabelsPrefixCmd(d, prefix, i18n("%1: set label prefix"))); + exec(new AxisSetLabelsPrefixCmd(d, prefix, ki18n("%1: set label prefix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsSuffix, QString, labelsSuffix, retransformTickLabelStrings); void Axis::setLabelsSuffix(const QString& suffix) { Q_D(Axis); if (suffix != d->labelsSuffix) - exec(new AxisSetLabelsSuffixCmd(d, suffix, i18n("%1: set label suffix"))); + exec(new AxisSetLabelsSuffixCmd(d, suffix, ki18n("%1: set label suffix"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOpacity, qreal, labelsOpacity, update); void Axis::setLabelsOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->labelsOpacity) - exec(new AxisSetLabelsOpacityCmd(d, opacity, i18n("%1: set labels opacity"))); + exec(new AxisSetLabelsOpacityCmd(d, opacity, ki18n("%1: set labels opacity"))); } //Major grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridPen, QPen, majorGridPen, retransformMajorGrid); void Axis::setMajorGridPen(const QPen &pen) { Q_D(Axis); if (pen != d->majorGridPen) - exec(new AxisSetMajorGridPenCmd(d, pen, i18n("%1: set major grid style"))); + exec(new AxisSetMajorGridPenCmd(d, pen, ki18n("%1: set major grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMajorGridOpacity, qreal, majorGridOpacity, update); void Axis::setMajorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->majorGridOpacity) - exec(new AxisSetMajorGridOpacityCmd(d, opacity, i18n("%1: set major grid opacity"))); + exec(new AxisSetMajorGridOpacityCmd(d, opacity, ki18n("%1: set major grid opacity"))); } //Minor grid STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridPen, QPen, minorGridPen, retransformMinorGrid); void Axis::setMinorGridPen(const QPen &pen) { Q_D(Axis); if (pen != d->minorGridPen) - exec(new AxisSetMinorGridPenCmd(d, pen, i18n("%1: set minor grid style"))); + exec(new AxisSetMinorGridPenCmd(d, pen, ki18n("%1: set minor grid style"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetMinorGridOpacity, qreal, minorGridOpacity, update); void Axis::setMinorGridOpacity(qreal opacity) { Q_D(Axis); if (opacity != d->minorGridOpacity) - exec(new AxisSetMinorGridOpacityCmd(d, opacity, i18n("%1: set minor grid opacity"))); + exec(new AxisSetMinorGridOpacityCmd(d, opacity, ki18n("%1: set minor grid opacity"))); } //############################################################################## //#################################### SLOTs ################################ //############################################################################## void Axis::labelChanged() { Q_D(Axis); d->recalcShapeAndBoundingRect(); } void Axis::retransformTicks() { Q_D(Axis); d->retransformTicks(); } void Axis::majorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->majorTicksColumn) { d->majorTicksColumn = 0; d->retransformTicks(); } } void Axis::minorTicksColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Axis); if (aspect == d->minorTicksColumn) { d->minorTicksColumn = 0; d->retransformTicks(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Axis::orientationChangedSlot(QAction* action) { if (action == orientationHorizontalAction) this->setOrientation(AxisHorizontal); else this->setOrientation(AxisVertical); } void Axis::lineStyleChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setStyle(GuiTools::penStyleFromAction(lineStyleActionGroup, action)); this->setLinePen(pen); } void Axis::lineColorChanged(QAction* action) { Q_D(const Axis); QPen pen = d->linePen; pen.setColor(GuiTools::colorFromAction(lineColorActionGroup, action)); this->setLinePen(pen); } void Axis::visibilityChangedSlot() { Q_D(const Axis); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### AxisPrivate::AxisPrivate(Axis* owner) : majorTicksColumn(0), minorTicksColumn(0), gridItem(new AxisGrid(this)), q(owner), suppressRetransform(false), labelsFormatDecimalOverruled(false), labelsFormatAutoChanged(false), plot(nullptr), cSystem(nullptr), m_hovered(false), m_suppressRecalc(false), m_printing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); setFlag(QGraphicsItem::ItemIsFocusable, true); setAcceptHoverEvents(true); } QString AxisPrivate::name() const{ return q->name(); } bool AxisPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } QRectF AxisPrivate::boundingRect() const{ return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath AxisPrivate::shape() const{ return axisShape; } /*! recalculates the position of the axis on the worksheet */ void AxisPrivate::retransform() { if (suppressRetransform || !plot) return; // PERFTRACE(name().toLatin1() + ", AxisPrivate::retransform()"); m_suppressRecalc = true; retransformLine(); m_suppressRecalc = false; recalcShapeAndBoundingRect(); } void AxisPrivate::retransformLine() { if (suppressRetransform) return; linePath = QPainterPath(); lines.clear(); QPointF startPoint; QPointF endPoint; if (orientation == Axis::AxisHorizontal) { if (position == Axis::AxisTop) offset = plot->yMax(); else if (position == Axis::AxisBottom) offset = plot->yMin(); else if (position == Axis::AxisCentered) offset = plot->yMin() + (plot->yMax()-plot->yMin())/2; startPoint.setX(start); startPoint.setY(offset); endPoint.setX(end); endPoint.setY(offset); } else { // vertical if (position == Axis::AxisLeft) offset = plot->xMin(); else if (position == Axis::AxisRight) offset = plot->xMax(); else if (position == Axis::AxisCentered) offset = plot->xMin() + (plot->xMax()-plot->xMin())/2; startPoint.setX(offset); startPoint.setY(start); endPoint.setY(end); endPoint.setX(offset); } lines.append(QLineF(startPoint, endPoint)); lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MarkGaps); for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } if (linePath.isEmpty()) { recalcShapeAndBoundingRect(); return; } else { retransformArrow(); retransformTicks(); } } void AxisPrivate::retransformArrow() { if (suppressRetransform) return; arrowPath = QPainterPath(); if (arrowType == Axis::NoArrow || lines.isEmpty()) { recalcShapeAndBoundingRect(); return; } if (arrowPosition == Axis::ArrowRight || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(lines.size()-1).p2(); this->addArrow(endPoint, 1); } if (arrowPosition == Axis::ArrowLeft || arrowPosition == Axis::ArrowBoth) { const QPointF& endPoint = lines.at(0).p1(); this->addArrow(endPoint, -1); } recalcShapeAndBoundingRect(); } void AxisPrivate::addArrow(QPointF startPoint, int direction) { static const double cos_phi = cos(M_PI/6.); if (orientation == Axis::AxisHorizontal) { QPointF endPoint = QPointF(startPoint.x() + direction*arrowSize, startPoint.y()); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()-arrowSize/4*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/8, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y()+arrowSize/4*cos_phi)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()-arrowSize/2*cos_phi)); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/4, endPoint.y())); arrowPath.lineTo(QPointF(endPoint.x()-direction*arrowSize/2, endPoint.y()+arrowSize/2*cos_phi)); arrowPath.lineTo(endPoint); break; } } else { //vertical orientation QPointF endPoint = QPointF(startPoint.x(), startPoint.y()-direction*arrowSize); arrowPath.moveTo(startPoint); arrowPath.lineTo(endPoint); switch (arrowType) { case Axis::NoArrow: break; case Axis::SimpleArrowSmall: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); break; case Axis::SimpleArrowBig: arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.moveTo(endPoint); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); break; case Axis::FilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::FilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowSmall: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/8)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/4*cos_phi, endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(endPoint); break; case Axis::SemiFilledArrowBig: arrowPath.lineTo(QPointF(endPoint.x()-arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(QPointF(endPoint.x(), endPoint.y()+direction*arrowSize/4)); arrowPath.lineTo(QPointF(endPoint.x()+arrowSize/2*cos_phi, endPoint.y()+direction*arrowSize/2)); arrowPath.lineTo(endPoint); break; } } } //! helper function for retransformTicks() bool AxisPrivate::transformAnchor(QPointF* anchorPoint) { QVector points; points.append(*anchorPoint); points = cSystem->mapLogicalToScene(points); if (points.count() != 1) { // point is not mappable or in a coordinate gap return false; } else { *anchorPoint = points.at(0); return true; } } /*! recalculates the position of the axis ticks. */ void AxisPrivate::retransformTicks() { if (suppressRetransform) return; //TODO: check that start and end are > 0 for log and >=0 for sqrt, etc. majorTicksPath = QPainterPath(); minorTicksPath = QPainterPath(); majorTickPoints.clear(); minorTickPoints.clear(); tickLabelValues.clear(); if ( majorTicksNumber < 1 || (majorTicksDirection == Axis::noTicks && minorTicksDirection == Axis::noTicks) ) { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } //determine the spacing for the major ticks double majorTicksSpacing = 0; int tmpMajorTicksNumber = 0; if (majorTicksType == Axis::TicksTotalNumber) { //the total number of the major ticks is given - > determine the spacing tmpMajorTicksNumber = majorTicksNumber; switch (scale) { case Axis::ScaleLinear: majorTicksSpacing = (end-start)/(majorTicksNumber-1); break; case Axis::ScaleLog10: majorTicksSpacing = (log10(end)-log10(start))/(majorTicksNumber-1); break; case Axis::ScaleLog2: majorTicksSpacing = (log(end)-log(start))/log(2)/(majorTicksNumber-1); break; case Axis::ScaleLn: majorTicksSpacing = (log(end)-log(start))/(majorTicksNumber-1); break; case Axis::ScaleSqrt: majorTicksSpacing = (sqrt(end)-sqrt(start))/(majorTicksNumber-1); break; case Axis::ScaleX2: majorTicksSpacing = (pow(end,2)-pow(start,2))/(majorTicksNumber-1); } } else if (majorTicksType == Axis::TicksIncrement) { //the spacing (increment) of the major ticks is given - > determine the number majorTicksSpacing = majorTicksIncrement; switch (scale) { case Axis::ScaleLinear: tmpMajorTicksNumber = qRound((end-start)/majorTicksSpacing + 1); break; case Axis::ScaleLog10: tmpMajorTicksNumber = qRound((log10(end)-log10(start))/majorTicksSpacing + 1); break; case Axis::ScaleLog2: tmpMajorTicksNumber = qRound((log(end)-log(start))/log(2)/majorTicksSpacing + 1); break; case Axis::ScaleLn: tmpMajorTicksNumber = qRound((log(end)-log(start))/majorTicksSpacing + 1); break; case Axis::ScaleSqrt: tmpMajorTicksNumber = qRound((sqrt(end)-sqrt(start))/majorTicksSpacing + 1); break; case Axis::ScaleX2: tmpMajorTicksNumber = qRound((pow(end,2)-pow(start,2))/majorTicksSpacing + 1); } } else { //custom column was provided if (majorTicksColumn) { tmpMajorTicksNumber = majorTicksColumn->rowCount(); } else { retransformTickLabelPositions(); //this calls recalcShapeAndBoundingRect() return; } } int tmpMinorTicksNumber; if (minorTicksType == Axis::TicksTotalNumber) tmpMinorTicksNumber = minorTicksNumber; else if (minorTicksType == Axis::TicksIncrement) tmpMinorTicksNumber = (end - start)/ (majorTicksNumber - 1)/minorTicksIncrement - 1; else (minorTicksColumn) ? tmpMinorTicksNumber = minorTicksColumn->rowCount() : tmpMinorTicksNumber = 0; QPointF anchorPoint; QPointF startPoint; QPointF endPoint; qreal majorTickPos=0.0; qreal minorTickPos; qreal nextMajorTickPos = 0.0; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; bool valid; for (int iMajor = 0; iMajor < tmpMajorTicksNumber; iMajor++) { //calculate major tick's position if (majorTicksType != Axis::TicksCustomColumn) { switch (scale) { case Axis::ScaleLinear: majorTickPos = start + majorTicksSpacing*iMajor; nextMajorTickPos = start + majorTicksSpacing*(iMajor+1); break; case Axis::ScaleLog10: majorTickPos = pow(10, log10(start) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(10, log10(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLog2: majorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*iMajor); nextMajorTickPos = pow(2, log(start)/log(2) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleLn: majorTickPos = exp(log(start) + majorTicksSpacing*iMajor); nextMajorTickPos = exp(log(start) + majorTicksSpacing*(iMajor+1)); break; case Axis::ScaleSqrt: majorTickPos = pow(sqrt(start) + majorTicksSpacing*iMajor, 2); nextMajorTickPos = pow(sqrt(start) + majorTicksSpacing*(iMajor+1), 2); break; case Axis::ScaleX2: majorTickPos = sqrt(sqrt(start) + majorTicksSpacing*iMajor); nextMajorTickPos = sqrt(sqrt(start) + majorTicksSpacing*(iMajor+1)); break; } } else { majorTickPos = majorTicksColumn->valueAt(iMajor); if (std::isnan(majorTickPos)) break; //stop iterating after the first non numerical value in the column } //calculate start and end points for major tick's line if (majorTicksDirection != Axis::noTicks ) { if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(majorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } } } else { // vertical anchorPoint.setY(majorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } } } //add major tick's line to the painter path if (valid) { majorTicksPath.moveTo(startPoint); majorTicksPath.lineTo(endPoint); majorTickPoints << anchorPoint; tickLabelValues<< scalingFactor*majorTickPos+zeroOffset; } } //minor ticks if ((Axis::noTicks != minorTicksDirection) && (tmpMajorTicksNumber > 1) && (tmpMinorTicksNumber > 0) && (iMajorvalueAt(iMinor); if (std::isnan(minorTickPos)) break; //stop iterating after the first non numerical value in the column //in the case a custom column is used for the minor ticks, we draw them _once_ for the whole range of the axis. //execute the minor ticks loop only once. if (iMajor > 0) break; } //calculate start and end points for minor tick's line if (orientation == Axis::AxisHorizontal) { anchorPoint.setX(minorTickPos); anchorPoint.setY(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? -yDirection * minorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksOut) ? yDirection * minorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (minorTicksDirection & Axis::ticksIn) ? -yDirection * minorTicksLength : 0); } } } else { // vertical anchorPoint.setY(minorTickPos); anchorPoint.setX(offset); valid = transformAnchor(&anchorPoint); if (valid) { if (offset < middleX) { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? -xDirection * minorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksOut) ? xDirection * minorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((minorTicksDirection & Axis::ticksIn) ? -xDirection * minorTicksLength : 0, 0); } } } //add minor tick's line to the painter path if (valid) { minorTicksPath.moveTo(startPoint); minorTicksPath.lineTo(endPoint); minorTickPoints << anchorPoint; } } } } //tick positions where changed -> update the position of the tick labels and grid lines retransformTickLabelStrings(); retransformMajorGrid(); retransformMinorGrid(); } /*! creates the tick label strings starting with the most optimal (=the smallest possible number of float digits) precision for the floats */ void AxisPrivate::retransformTickLabelStrings() { if (suppressRetransform) return; // DEBUG("AxisPrivate::retransformTickLabelStrings()"); if (labelsAutoPrecision) { //check, whether we need to increase the current precision int newPrecision = upperLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } else { //check, whether we can reduce the current precision newPrecision = lowerLabelsPrecision(labelsPrecision); if (newPrecision!= labelsPrecision) { labelsPrecision = newPrecision; emit q->labelsPrecisionChanged(labelsPrecision); } } } // DEBUG("labelsPrecision =" << labelsPrecision); //automatically switch from 'decimal' to 'scientific' format for big numbers (>10^4) //and back to decimal when the numbers get smaller after the auto-switch again if (labelsFormat == Axis::FormatDecimal && !labelsFormatDecimalOverruled) { for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { labelsFormat = Axis::FormatScientificE; emit q->labelsFormatChanged(labelsFormat); labelsFormatAutoChanged = true; break; } } } else if (labelsFormatAutoChanged ) { //check whether we still have big numbers bool changeBack = true; for (auto value : tickLabelValues) { if (std::abs(value) > 1e4) { changeBack = false; break; } } if (changeBack) { labelsFormatAutoChanged = false; labelsFormat = Axis::FormatDecimal; emit q->labelsFormatChanged(labelsFormat); } } tickLabelStrings.clear(); QString str; if (labelsFormat == Axis::FormatDecimal) { QString nullStr = QString::number(0, 'f', labelsPrecision); for (auto value : tickLabelValues) { str = QString::number(value, 'f', labelsPrecision); if (str == "-" + nullStr) str = nullStr; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatScientificE) { QString nullStr = QString::number(0, 'e', labelsPrecision); for (auto value : tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { for (auto value : tickLabelValues) { str = "10" + QString::number(log10(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers2) { for (auto value : tickLabelValues) { str = "2" + QString::number(log2(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowersE) { for (auto value : tickLabelValues) { str = "e" + QString::number(log(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatMultipliesPi) { for (auto value : tickLabelValues) { str = "" + QString::number(value / M_PI, 'f', labelsPrecision) + "" + QChar(0x03C0); str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } //recalculate the position of the tick labels retransformTickLabelPositions(); } /*! returns the smallest upper limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::upperLabelsPrecision(int precision) { // DEBUG("AxisPrivate::upperLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, increase the precision. QVector tempValues; for (int i = 0; i < tickLabelValues.size(); ++i) tempValues.append( nsl_math_round_places(tickLabelValues[i], precision) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate for the current precision found, increase the precision and check again return upperLabelsPrecision(precision + 1); } } } //no duplicates for the current precision found: return the current value DEBUG(" upper precision = " << precision); return precision; } /*! returns highest lower limit for the precision where no duplicates for the tick label float occur. */ int AxisPrivate::lowerLabelsPrecision(int precision) { // DEBUG("AxisPrivate::lowerLabelsPrecision() precision =" << precision); //round float to the current precision and look for duplicates. //if there are duplicates, decrease the precision. QVector tempValues; for (int i = 0; i < tickLabelValues.size(); ++i) tempValues.append( nsl_math_round_places(tickLabelValues[i], precision-1) ); for (int i = 0; i < tempValues.size(); ++i) { for (int j = 0; j < tempValues.size(); ++j) { if (i == j) continue; if (tempValues.at(i) == tempValues.at(j)) { //duplicate found for the reduced precision //-> current precision cannot be reduced, return the current value DEBUG(" lower precision = " << precision); return precision; } } } //no duplicates found, reduce further, and check again if (precision == 0) return 0; else return lowerLabelsPrecision(precision - 1); } /*! recalculates the position of the tick labels. Called when the geometry related properties (position, offset, font size, suffix, prefix) of the labels are changed. */ void AxisPrivate::retransformTickLabelPositions() { tickLabelPoints.clear(); if (majorTicksDirection == Axis::noTicks || labelsPosition == Axis::NoLabels) { recalcShapeAndBoundingRect(); return; } QFontMetrics fm(labelsFont); float width = 0; float height = fm.ascent(); QPointF pos; const double middleX = plot->xMin() + (plot->xMax() - plot->xMin())/2; const double middleY = plot->yMin() + (plot->yMax() - plot->yMin())/2; const int xDirection = cSystem->xDirection(); const int yDirection = cSystem->yDirection(); QPointF startPoint, endPoint, anchorPoint; QTextDocument td; td.setDefaultFont(labelsFont); for ( int i = 0; i < majorTickPoints.size(); i++ ) { if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { width = fm.width(tickLabelStrings.at(i)); } else { td.setHtml(tickLabelStrings.at(i)); width = td.size().width(); height = td.size().height(); } anchorPoint = majorTickPoints.at(i); //center align all labels with respect to the end point of the tick line if (orientation == Axis::AxisHorizontal) { if (offset < middleY) { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? -yDirection * majorTicksLength : 0); } else { startPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksOut) ? yDirection * majorTicksLength : 0); endPoint = anchorPoint + QPointF(0, (majorTicksDirection & Axis::ticksIn) ? -yDirection * majorTicksLength : 0); } if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - width/2); pos.setY( endPoint.y() + height + labelsOffset ); } else { pos.setX( startPoint.x() - width/2); pos.setY( startPoint.y() - labelsOffset ); } } else {// vertical if (offset < middleX) { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? -xDirection * majorTicksLength : 0, 0); } else { startPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksOut) ? xDirection * majorTicksLength : 0, 0); endPoint = anchorPoint + QPointF((majorTicksDirection & Axis::ticksIn) ? -xDirection * majorTicksLength : 0, 0); } if (labelsPosition == Axis::LabelsOut) { pos.setX( endPoint.x() - width - labelsOffset ); pos.setY( endPoint.y() + height/2 ); } else { pos.setX( startPoint.x() + labelsOffset ); pos.setY( startPoint.y() + height/2 ); } } tickLabelPoints << pos; } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMajorGrid() { if (suppressRetransform) return; majorGridPath = QPainterPath(); if (majorGridPen.style() == Qt::NoPen || majorTickPoints.size() == 0) { recalcShapeAndBoundingRect(); return; } //major tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMajorTickPoints = cSystem->mapSceneToLogical(majorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); if (logicalMajorTickPoints.isEmpty()) return; //TODO: //when iterating over all grid lines, skip the first and the last points for auto scaled axes, //since we don't want to paint any grid lines at the plot boundaries bool skipLowestTick, skipUpperTick; if (orientation == Axis::AxisHorizontal) { //horizontal axis skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).x(), plot->xMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), plot->xMax()); } else { skipLowestTick = qFuzzyCompare(logicalMajorTickPoints.at(0).y(), plot->yMin()); skipUpperTick = qFuzzyCompare(logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), plot->yMax()); } int start, end; if (skipLowestTick) { if (logicalMajorTickPoints.size() > 1) start = 1; else start = 0; } else { start = 0; } if (skipUpperTick) { if (logicalMajorTickPoints.size() > 1) end = logicalMajorTickPoints.size() - 1; else end = 0; } else { end = logicalMajorTickPoints.size(); } QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (int i=start; ixMin(); double xMax = plot->xMax(); //skip the first and the last points, since we don't want to paint any grid lines at the plot boundaries for (int i = start; i < end; ++i) { const QPointF& point = logicalMajorTickPoints.at(i); lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { majorGridPath.moveTo(line.p1()); majorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::retransformMinorGrid() { if (suppressRetransform) return; minorGridPath = QPainterPath(); if (minorGridPen.style() == Qt::NoPen) { recalcShapeAndBoundingRect(); return; } //minor tick points are already in scene coordinates, convert them back to logical... //TODO: mapping should work without SuppressPageClipping-flag, check float comparisons in the map-function. //Currently, grid lines disappear somtimes without this flag QVector logicalMinorTickPoints = cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis double yMin = plot->yMin(); double yMax = plot->yMax(); for (int i = 0; i < logicalMinorTickPoints.size(); ++i) { const QPointF& point = logicalMinorTickPoints.at(i); lines.append( QLineF(point.x(), yMin, point.x(), yMax) ); } } else { //vertical axis double xMin = plot->xMin(); double xMax = plot->xMax(); for (int i = 0; i < logicalMinorTickPoints.size(); ++i) { const QPointF& point = logicalMinorTickPoints.at(i); lines.append( QLineF(xMin, point.y(), xMax, point.y()) ); } } lines = cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); for (const auto& line : lines) { minorGridPath.moveTo(line.p1()); minorGridPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void AxisPrivate::recalcShapeAndBoundingRect() { if (m_suppressRecalc) return; prepareGeometryChange(); if (linePath.isEmpty()) { axisShape = QPainterPath(); boundingRectangle = QRectF(); title->setPositionInvalid(true); if (plot) plot->prepareGeometryChange(); return; } else { title->setPositionInvalid(false); } axisShape = WorksheetElement::shapeFromPath(linePath, linePen); axisShape.addPath(WorksheetElement::shapeFromPath(arrowPath, linePen)); axisShape.addPath(WorksheetElement::shapeFromPath(majorTicksPath, majorTicksPen)); axisShape.addPath(WorksheetElement::shapeFromPath(minorTicksPath, minorTicksPen)); QPainterPath tickLabelsPath = QPainterPath(); if (labelsPosition != Axis::NoLabels) { QTransform trafo; QPainterPath tempPath; QFontMetrics fm(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i = 0; i < tickLabelPoints.size(); i++) { tempPath = QPainterPath(); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { tempPath.addRect( fm.boundingRect(tickLabelStrings.at(i)) ); } else { td.setHtml(tickLabelStrings.at(i)); tempPath.addRect( QRectF(0, -td.size().height(), td.size().width(), td.size().height()) ); } trafo.reset(); trafo.translate( tickLabelPoints.at(i).x(), tickLabelPoints.at(i).y() ); trafo.rotate( -labelsRotationAngle ); tempPath = trafo.map(tempPath); tickLabelsPath.addPath(WorksheetElement::shapeFromPath(tempPath, linePen)); } axisShape.addPath(WorksheetElement::shapeFromPath(tickLabelsPath, QPen())); } //add title label, if available if ( title->isVisible() && !title->text().text.isEmpty() ) { //determine the new position of the title label: //we calculate the new position here and not in retransform(), //since it depends on the size and position of the tick labels, tickLabelsPath, available here. QRectF rect=linePath.boundingRect(); qreal offsetX = titleOffsetX - labelsOffset; //the distance to the axis line qreal offsetY = titleOffsetY - labelsOffset; //the distance to the axis line if (orientation == Axis::AxisHorizontal) { offsetY -= title->graphicsItem()->boundingRect().height()/2 + tickLabelsPath.boundingRect().height(); title->setPosition( QPointF( (rect.topLeft().x() + rect.topRight().x())/2 + offsetX, rect.bottomLeft().y() - offsetY ) ); } else { offsetX -= title->graphicsItem()->boundingRect().width()/2 + tickLabelsPath.boundingRect().width(); title->setPosition( QPointF( rect.topLeft().x() + offsetX, (rect.topLeft().y() + rect.bottomLeft().y())/2 - offsetY) ); } axisShape.addPath(WorksheetElement::shapeFromPath(title->graphicsItem()->mapToParent(title->graphicsItem()->shape()), linePen)); } boundingRectangle = axisShape.boundingRect(); //if the axis goes beyond the current bounding box of the plot (too high offset is used, too long labels etc.) //request a prepareGeometryChange() for the plot in order to properly keep track of geometry changes if (plot) plot->prepareGeometryChange(); } /*! paints the content of the axis. Reimplemented from \c QGraphicsItem. \sa QGraphicsItem::paint() */ void AxisPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!isVisible()) return; if (linePath.isEmpty()) return; //draw the line if (linePen.style() != Qt::NoPen) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::SolidPattern); painter->drawPath(linePath); //draw the arrow if (arrowType != Axis::NoArrow) painter->drawPath(arrowPath); } //draw the major ticks if (majorTicksDirection != Axis::noTicks) { painter->setOpacity(majorTicksOpacity); painter->setPen(majorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(majorTicksPath); } //draw the minor ticks if (minorTicksDirection != Axis::noTicks) { painter->setOpacity(minorTicksOpacity); painter->setPen(minorTicksPen); painter->setBrush(Qt::NoBrush); painter->drawPath(minorTicksPath); } // draw tick labels if (labelsPosition != Axis::NoLabels) { painter->setOpacity(labelsOpacity); painter->setPen(QPen(labelsColor)); painter->setFont(labelsFont); QTextDocument td; td.setDefaultFont(labelsFont); for (int i = 0; i < tickLabelPoints.size(); i++) { painter->translate(tickLabelPoints.at(i)); painter->save(); painter->rotate(-labelsRotationAngle); if (labelsFormat == Axis::FormatDecimal || labelsFormat == Axis::FormatScientificE) { painter->drawText(QPoint(0,0), tickLabelStrings.at(i)); } else { td.setHtml(tickLabelStrings.at(i)); painter->translate(0, -td.size().height()); td.drawContents(painter); } painter->restore(); painter->translate(-tickLabelPoints.at(i)); } } if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(axisShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(axisShape); } } void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(axisShape.boundingRect()); } } void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(axisShape.boundingRect()); } } void AxisPrivate::setPrinting(bool on) { m_printing = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Axis::save(QXmlStreamWriter* writer) const{ Q_D(const Axis); writer->writeStartElement( "axis" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); writer->writeAttribute( "autoScale", QString::number(d->autoScale) ); writer->writeAttribute( "orientation", QString::number(d->orientation) ); writer->writeAttribute( "position", QString::number(d->position) ); writer->writeAttribute( "scale", QString::number(d->scale) ); writer->writeAttribute( "offset", QString::number(d->offset) ); writer->writeAttribute( "start", QString::number(d->start) ); writer->writeAttribute( "end", QString::number(d->end) ); writer->writeAttribute( "scalingFactor", QString::number(d->scalingFactor) ); writer->writeAttribute( "zeroOffset", QString::number(d->zeroOffset) ); writer->writeAttribute( "titleOffsetX", QString::number(d->titleOffsetX) ); writer->writeAttribute( "titleOffsetY", QString::number(d->titleOffsetY) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //label d->title->save( writer ); //line writer->writeStartElement( "line" ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeAttribute( "arrowType", QString::number(d->arrowType) ); writer->writeAttribute( "arrowPosition", QString::number(d->arrowPosition) ); writer->writeAttribute( "arrowSize", QString::number(d->arrowSize) ); writer->writeEndElement(); //major ticks writer->writeStartElement( "majorTicks" ); writer->writeAttribute( "direction", QString::number(d->majorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->majorTicksType) ); writer->writeAttribute( "number", QString::number(d->majorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->majorTicksIncrement) ); WRITE_COLUMN(d->majorTicksColumn, majorTicksColumn); writer->writeAttribute( "length", QString::number(d->majorTicksLength) ); WRITE_QPEN(d->majorTicksPen); writer->writeAttribute( "opacity", QString::number(d->majorTicksOpacity) ); writer->writeEndElement(); //minor ticks writer->writeStartElement( "minorTicks" ); writer->writeAttribute( "direction", QString::number(d->minorTicksDirection) ); writer->writeAttribute( "type", QString::number(d->minorTicksType) ); writer->writeAttribute( "number", QString::number(d->minorTicksNumber) ); writer->writeAttribute( "increment", QString::number(d->minorTicksIncrement) ); WRITE_COLUMN(d->minorTicksColumn, minorTicksColumn); writer->writeAttribute( "length", QString::number(d->minorTicksLength) ); WRITE_QPEN(d->minorTicksPen); writer->writeAttribute( "opacity", QString::number(d->minorTicksOpacity) ); writer->writeEndElement(); //extra ticks //labels writer->writeStartElement( "labels" ); writer->writeAttribute( "position", QString::number(d->labelsPosition) ); writer->writeAttribute( "offset", QString::number(d->labelsOffset) ); writer->writeAttribute( "rotation", QString::number(d->labelsRotationAngle) ); writer->writeAttribute( "format", QString::number(d->labelsFormat) ); writer->writeAttribute( "precision", QString::number(d->labelsPrecision) ); writer->writeAttribute( "autoPrecision", QString::number(d->labelsAutoPrecision) ); WRITE_QCOLOR(d->labelsColor); WRITE_QFONT(d->labelsFont); writer->writeAttribute( "prefix", d->labelsPrefix ); writer->writeAttribute( "suffix", d->labelsSuffix ); writer->writeAttribute( "opacity", QString::number(d->labelsOpacity) ); writer->writeEndElement(); //grid writer->writeStartElement( "majorGrid" ); WRITE_QPEN(d->majorGridPen); writer->writeAttribute( "opacity", QString::number(d->majorGridOpacity) ); writer->writeEndElement(); writer->writeStartElement( "minorGrid" ); WRITE_QPEN(d->minorGridPen); writer->writeAttribute( "opacity", QString::number(d->minorGridOpacity) ); writer->writeEndElement(); writer->writeEndElement(); // close "axis" section } //! Load from XML bool Axis::load(XmlStreamReader* reader, bool preview) { Q_D(Axis); if (!reader->isStartElement() || reader->name() != "axis") { reader->raiseError(i18n("no axis element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "axis") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); str = attribs.value("autoScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScale'")); else d->autoScale = (bool)str.toInt(); str = attribs.value("orientation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'orientation'")); else d->orientation = (Axis::AxisOrientation)str.toInt(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->position = (Axis::AxisPosition)str.toInt(); str = attribs.value("scale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'scale'")); else d->scale = (Axis::AxisScale)str.toInt(); str = attribs.value("offset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'offset'")); else d->offset = str.toDouble(); str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else d->start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else d->end = str.toDouble(); str = attribs.value("scalingFactor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'scalingFactor'")); else d->scalingFactor = str.toDouble(); str = attribs.value("zeroOffset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'zeroOffset'")); else d->zeroOffset = str.toDouble(); str = attribs.value("titleOffsetX").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'titleOffsetX'")); else d->titleOffsetX = str.toDouble(); str = attribs.value("titleOffsetY").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'titleOffsetY'")); else d->titleOffsetY = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (reader->name() == "textLabel") { d->title->load(reader, preview); } else if (!preview && reader->name() == "line") { attribs = reader->attributes(); READ_QPEN(d->linePen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->lineOpacity = str.toDouble(); str = attribs.value("arrowType").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowType'")); else d->arrowType = (Axis::ArrowType)str.toInt(); str = attribs.value("arrowPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowPosition'")); else d->arrowPosition = (Axis::ArrowPosition)str.toInt(); str = attribs.value("arrowSize").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'arrowSize'")); else d->arrowSize = str.toDouble(); } else if (!preview && reader->name() == "majorTicks") { attribs = reader->attributes(); str = attribs.value("direction").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'direction'")); else d->majorTicksDirection = (Axis::TicksDirection)str.toInt(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->majorTicksType = (Axis::TicksType)str.toInt(); str = attribs.value("number").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'number'")); else d->majorTicksNumber = str.toInt(); str = attribs.value("increment").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'increment'")); else d->majorTicksIncrement = str.toDouble(); READ_COLUMN(majorTicksColumn); str = attribs.value("length").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'length'")); else d->majorTicksLength = str.toDouble(); READ_QPEN(d->majorTicksPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->majorTicksOpacity = str.toDouble(); } else if (!preview && reader->name() == "minorTicks") { attribs = reader->attributes(); str = attribs.value("direction").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'direction'")); else d->minorTicksDirection = (Axis::TicksDirection)str.toInt(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->minorTicksType = (Axis::TicksType)str.toInt(); str = attribs.value("number").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'number'")); else d->minorTicksNumber = str.toInt(); str = attribs.value("increment").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'increment'")); else d->minorTicksIncrement = str.toDouble(); READ_COLUMN(minorTicksColumn); str = attribs.value("length").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'length'")); else d->minorTicksLength = str.toDouble(); READ_QPEN(d->minorTicksPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->minorTicksOpacity = str.toDouble(); } else if (!preview && reader->name() == "labels") { attribs = reader->attributes(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->labelsPosition = (Axis::LabelsPosition)str.toInt(); str = attribs.value("offset").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'offset'")); else d->labelsOffset = str.toDouble(); str = attribs.value("rotation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->labelsRotationAngle = str.toDouble(); str = attribs.value("format").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'format'")); else d->labelsFormat = (Axis::LabelsFormat)str.toInt(); str = attribs.value("precision").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'precision'")); else d->labelsPrecision = str.toInt(); str = attribs.value("autoPrecision").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoPrecision'")); else d->labelsAutoPrecision = str.toInt(); READ_QCOLOR(d->labelsColor); READ_QFONT(d->labelsFont); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->labelsPrefix = attribs.value("prefix").toString(); d->labelsSuffix = attribs.value("suffix").toString(); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->labelsOpacity = str.toDouble(); } else if (!preview && reader->name() == "majorGrid") { attribs = reader->attributes(); READ_QPEN(d->majorGridPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->majorGridOpacity = str.toDouble(); } else if (!preview && reader->name() == "minorGrid") { attribs = reader->attributes(); READ_QPEN(d->minorGridPen); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->minorGridOpacity = str.toDouble(); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void Axis::loadThemeConfig(const KConfig& config) { const KConfigGroup group = config.group("Axis"); QPen p; // Tick label this->setLabelsColor(group.readEntry("LabelsFontColor",(QColor) this->labelsColor())); this->setLabelsOpacity(group.readEntry("LabelsOpacity",this->labelsOpacity())); //Line this->setLineOpacity(group.readEntry("LineOpacity",this->lineOpacity())); p.setColor(group.readEntry("LineColor", (QColor) this->linePen().color())); p.setStyle((Qt::PenStyle)group.readEntry("LineStyle",(int) this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); this->setLinePen(p); //Major ticks this->setMajorGridOpacity(group.readEntry("MajorGridOpacity", this->majorGridOpacity())); p.setColor(group.readEntry("MajorGridColor",(QColor) this->majorGridPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MajorGridStyle",(int) this->majorGridPen().style())); p.setWidthF(group.readEntry("MajorGridWidth", this->majorGridPen().widthF())); this->setMajorGridPen(p); p.setColor(group.readEntry("MajorTicksColor",(QColor)this->majorTicksPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MajorTicksLineStyle",(int) this->majorTicksPen().style())); p.setWidthF(group.readEntry("MajorTicksWidth", this->majorTicksPen().widthF())); this->setMajorTicksPen(p); this->setMajorTicksOpacity(group.readEntry("MajorTicksOpacity",this->majorTicksOpacity())); //Minor ticks this->setMinorGridOpacity(group.readEntry("MinorGridOpacity", this->minorGridOpacity())); p.setColor(group.readEntry("MinorGridColor",(QColor) this->minorGridPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MinorGridStyle",(int) this->minorGridPen().style())); p.setWidthF(group.readEntry("MinorGridWidth", this->minorGridPen().widthF())); this->setMinorGridPen(p); p.setColor(group.readEntry("MinorTicksColor",(QColor) this->minorTicksPen().color())); p.setStyle((Qt::PenStyle)group.readEntry("MinorTicksLineStyle",(int) this->minorTicksPen().style())); p.setWidthF(group.readEntry("MinorTicksWidth", this->minorTicksPen().widthF())); this->setMinorTicksPen(p); this->setMinorTicksOpacity(group.readEntry("MinorTicksOpacity",this->minorTicksOpacity())); const QVector& childElements = children(AbstractAspect::IncludeHidden); for (auto* child : childElements) child->loadThemeConfig(config); } void Axis::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("Axis"); // Tick label group.writeEntry("LabelsFontColor", (QColor) this->labelsColor()); group.writeEntry("LabelsOpacity", this->labelsOpacity()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineColor", (QColor) this->linePen().color()); group.writeEntry("LineStyle", (int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Major ticks group.writeEntry("MajorGridOpacity", this->majorGridOpacity()); group.writeEntry("MajorGridColor", (QColor) this->majorGridPen().color()); group.writeEntry("MajorGridStyle", (int) this->majorGridPen().style()); group.writeEntry("MajorGridWidth", this->majorGridPen().widthF()); group.writeEntry("MajorTicksColor", (QColor)this->majorTicksPen().color()); group.writeEntry("MajorTicksLineStyle", (int) this->majorTicksPen().style()); group.writeEntry("MajorTicksWidth", this->majorTicksPen().widthF()); group.writeEntry("MajorTicksOpacity", this->majorTicksOpacity()); group.writeEntry("MajorTicksType", (int)this->majorTicksType()); //Minor ticks group.writeEntry("MinorGridOpacity", this->minorGridOpacity()); group.writeEntry("MinorGridColor",(QColor) this->minorGridPen().color()); group.writeEntry("MinorGridStyle", (int) this->minorGridPen().style()); group.writeEntry("MinorGridWidth", this->minorGridPen().widthF()); group.writeEntry("MinorTicksColor", (QColor) this->minorTicksPen().color()); group.writeEntry("MinorTicksLineStyle",( int) this->minorTicksPen().style()); group.writeEntry("MinorTicksWidth", this->minorTicksPen().widthF()); group.writeEntry("MinorTicksOpacity", this->minorTicksOpacity()); group.writeEntry("MinorTicksType", (int)this->minorTicksType()); const QVector& childElements = children(AbstractAspect::IncludeHidden); childElements.at(0)->saveThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index 299680483..c67f9a642 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,3046 +1,3046 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2018 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2017 by Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CartesianPlot.h" #include "CartesianPlotPrivate.h" #include "Axis.h" #include "XYCurve.h" #include "Histogram.h" #include "XYEquationCurve.h" #include "XYDataReductionCurve.h" #include "XYDifferentiationCurve.h" #include "XYIntegrationCurve.h" #include "XYInterpolationCurve.h" #include "XYSmoothCurve.h" #include "XYFitCurve.h" #include "XYFourierFilterCurve.h" #include "XYFourierTransformCurve.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CustomPoint.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/AbstractPlotPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/Axis.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "kdefrontend/spreadsheet/PlotDataDialog.h" //for PlotDataDialog::AnalysisAction. TODO: find a better place for this enum. #include "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #include #include #include #include #include #include #include #include #include #include /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name):AbstractPlot(name, new CartesianPlotPrivate(this)), m_legend(nullptr), m_zoomFactor(1.2), m_menusInitialized(false), addNewMenu(nullptr), zoomMenu(nullptr), dataAnalysisMenu(nullptr), themeMenu(nullptr) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd):AbstractPlot(name, dd), m_legend(nullptr), m_zoomFactor(1.2), addNewMenu(nullptr), zoomMenu(nullptr), dataAnalysisMenu(nullptr), themeMenu(nullptr) { init(); } CartesianPlot::~CartesianPlot() { if (m_menusInitialized) { delete addNewMenu; delete zoomMenu; delete themeMenu; } delete m_coordinateSystem; //don't need to delete objects added with addChild() //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } /*! initializes all member variables of \c CartesianPlot */ void CartesianPlot::init() { Q_D(CartesianPlot); d->cSystem = new CartesianCoordinateSystem(this); m_coordinateSystem = d->cSystem; d->rangeType = CartesianPlot::RangeFree; d->rangeLastValues = 1000; d->rangeFirstValues = 1000; d->autoScaleX = true; d->autoScaleY = true; d->xScale = ScaleLinear; d->yScale = ScaleLinear; d->xRangeBreakingEnabled = false; d->yRangeBreakingEnabled = false; //the following factor determines the size of the offset between the min/max points of the curves //and the coordinate system ranges, when doing auto scaling //Factor 0 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. //TODO: make this factor optional. //Provide in the UI the possibility to choose between "exact" or 0% offset, 2%, 5% and 10% for the auto fit option d->autoScaleOffsetFactor = 0.0f; m_plotArea = new PlotArea(name() + " plot area"); addChildFast(m_plotArea); //Plot title m_title = new TextLabel(this->name(), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //offset between the plot area and the area defining the coordinate system, in scene units. d->horizontalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.5, Worksheet::Centimeter); connect(this, SIGNAL(aspectAdded(const AbstractAspect*)), this, SLOT(childAdded(const AbstractAspect*))); connect(this, SIGNAL(aspectRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*)), this, SLOT(childRemoved(const AbstractAspect*,const AbstractAspect*,const AbstractAspect*))); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); graphicsItem()->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsFocusable, true); } /*! initializes all children of \c CartesianPlot and setups a default plot of type \c type with a plot title. */ void CartesianPlot::initDefault(Type type) { Q_D(CartesianPlot); switch (type) { case FourAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; //Axes Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); QPen pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("x axis 2", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisTop); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); pen = axis->majorGridPen(); pen.setStyle(Qt::SolidLine); axis->setMajorGridPen(pen); pen = axis->minorGridPen(); pen.setStyle(Qt::DotLine); axis->setMinorGridPen(pen); axis->setSuppressRetransform(false); axis = new Axis("y axis 2", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisRight); axis->setStart(0); axis->setEnd(1); axis->setOffset(1); axis->setMajorTicksDirection(Axis::ticksIn); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksIn); axis->setMinorTicksNumber(1); axis->setLabelsPosition(Axis::NoLabels); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxes: { d->xMin = 0; d->xMax = 1; d->yMin = 0; d->yMax = 1; Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisBottom); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisLeft); axis->setStart(0); axis->setEnd(1); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->setSuppressRetransform(false); break; } case TwoAxesCentered: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCentered); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } case TwoAxesCenteredZero: { d->xMin = -0.5; d->xMax = 0.5; d->yMin = -0.5; d->yMax = 0.5; d->horizontalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); d->verticalPadding = Worksheet::convertToSceneUnits(1.0, Worksheet::Centimeter); QPen pen = m_plotArea->borderPen(); pen.setStyle(Qt::NoPen); m_plotArea->setBorderPen(pen); Axis* axis = new Axis("x axis 1", Axis::AxisHorizontal); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); axis = new Axis("y axis 1", Axis::AxisVertical); axis->setSuppressRetransform(true); addChild(axis); axis->setPosition(Axis::AxisCustom); axis->setOffset(0); axis->setStart(-0.5); axis->setEnd(0.5); axis->setMajorTicksDirection(Axis::ticksBoth); axis->setMajorTicksNumber(6); axis->setMinorTicksDirection(Axis::ticksBoth); axis->setMinorTicksNumber(1); axis->setArrowType(Axis::FilledArrowSmall); axis->title()->setText(QString()); axis->setSuppressRetransform(false); break; } } d->xMinPrev = d->xMin; d->xMaxPrev = d->xMax; d->yMinPrev = d->yMin; d->yMaxPrev = d->yMax; //Geometry, specify the plot rect in scene coordinates. //TODO: Use default settings for left, top, width, height and for min/max for the coordinate system float x = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(2, Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(10, Worksheet::Centimeter); //all plot children are initialized -> set the geometry of the plot in scene coordinates. d->rect = QRectF(x,y,w,h); } void CartesianPlot::initActions() { //"add new" actions addCurveAction = new QAction(QIcon::fromTheme("labplot-xy-curve"), i18n("xy-curve"), this); addHistogramPlot = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("Histogram"), this); addEquationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-equation-curve"), i18n("xy-curve from a mathematical equation"), this); // no icons yet addDataReductionCurveAction = new QAction(i18n("xy-curve from a data reduction"), this); addDifferentiationCurveAction = new QAction(i18n("xy-curve from a differentiation"), this); addIntegrationCurveAction = new QAction(i18n("xy-curve from an integration"), this); addInterpolationCurveAction = new QAction(i18n("xy-curve from an interpolation"), this); addSmoothCurveAction = new QAction(i18n("xy-curve from a smooth"), this); addFitCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fit-curve"), i18n("xy-curve from a fit to data"), this); addFourierFilterCurveAction = new QAction(i18n("xy-curve from a Fourier filter"), this); addFourierTransformCurveAction = new QAction(i18n("xy-curve from a Fourier transform"), this); // addInterpolationCurveAction = new QAction(QIcon::fromTheme("labplot-xy-interpolation-curve"), i18n("xy-curve from an interpolation"), this); // addSmoothCurveAction = new QAction(QIcon::fromTheme("labplot-xy-smooth-curve"), i18n("xy-curve from a smooth"), this); // addFourierFilterCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_filter-curve"), i18n("xy-curve from a Fourier filter"), this); // addFourierTransformCurveAction = new QAction(QIcon::fromTheme("labplot-xy-fourier_transform-curve"), i18n("xy-curve from a Fourier transform"), this); addLegendAction = new QAction(QIcon::fromTheme("text-field"), i18n("legend"), this); if (children().size()>0) addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action addHorizontalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-horizontal"), i18n("horizontal axis"), this); addVerticalAxisAction = new QAction(QIcon::fromTheme("labplot-axis-vertical"), i18n("vertical axis"), this); addTextLabelAction = new QAction(QIcon::fromTheme("labplot-text-label"), i18n("text label"), this); addCustomPointAction = new QAction(QIcon::fromTheme("draw-cross"), i18n("custom point"), this); connect(addCurveAction, SIGNAL(triggered()), SLOT(addCurve())); connect(addHistogramPlot,SIGNAL(triggered()), SLOT(addHistogram())); connect(addEquationCurveAction, SIGNAL(triggered()), SLOT(addEquationCurve())); connect(addDataReductionCurveAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationCurveAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationCurveAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationCurveAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothCurveAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); connect(addFitCurveAction, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterCurveAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); connect(addFourierTransformCurveAction, SIGNAL(triggered()), SLOT(addFourierTransformCurve())); connect(addLegendAction, SIGNAL(triggered()), SLOT(addLegend())); connect(addHorizontalAxisAction, SIGNAL(triggered()), SLOT(addHorizontalAxis())); connect(addVerticalAxisAction, SIGNAL(triggered()), SLOT(addVerticalAxis())); connect(addTextLabelAction, SIGNAL(triggered()), SLOT(addTextLabel())); connect(addCustomPointAction, SIGNAL(triggered()), SLOT(addCustomPoint())); //Analysis menu actions addDataOperationAction = new QAction(i18n("Data operation"), this); addDataReductionAction = new QAction(i18n("Reduce data"), this); addDifferentiationAction = new QAction(i18n("Differentiate"), this); addIntegrationAction = new QAction(i18n("Integrate"), this); addInterpolationAction = new QAction(i18n("Interpolate"), this); addSmoothAction = new QAction(i18n("Smooth"), this); QAction* fitAction = new QAction(i18n("Linear"), this); fitAction->setData(PlotDataDialog::FitLinear); addFitAction.append(fitAction); fitAction = new QAction(i18n("Power"), this); fitAction->setData(PlotDataDialog::FitPower); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 1)"), this); fitAction->setData(PlotDataDialog::FitExp1); addFitAction.append(fitAction); fitAction = new QAction(i18n("Exponential (degree 2)"), this); fitAction->setData(PlotDataDialog::FitExp2); addFitAction.append(fitAction); fitAction = new QAction(i18n("Inverse exponential"), this); fitAction->setData(PlotDataDialog::FitInvExp); addFitAction.append(fitAction); fitAction = new QAction(i18n("Gauss"), this); fitAction->setData(PlotDataDialog::FitGauss); addFitAction.append(fitAction); fitAction = new QAction(i18n("Cauchy-Lorentz"), this); fitAction->setData(PlotDataDialog::FitCauchyLorentz); addFitAction.append(fitAction); fitAction = new QAction(i18n("Arc Tangent"), this); fitAction->setData(PlotDataDialog::FitTan); addFitAction.append(fitAction); fitAction = new QAction(i18n("Hyperbolic tangent"), this); fitAction->setData(PlotDataDialog::FitTanh); addFitAction.append(fitAction); fitAction = new QAction(i18n("Error function"), this); fitAction->setData(PlotDataDialog::FitErrFunc); addFitAction.append(fitAction); fitAction = new QAction(i18n("Custom"), this); fitAction->setData(PlotDataDialog::FitCustom); addFitAction.append(fitAction); addFourierFilterAction = new QAction(i18n("Fourier filter"), this); connect(addDataReductionAction, SIGNAL(triggered()), SLOT(addDataReductionCurve())); connect(addDifferentiationAction, SIGNAL(triggered()), SLOT(addDifferentiationCurve())); connect(addIntegrationAction, SIGNAL(triggered()), SLOT(addIntegrationCurve())); connect(addInterpolationAction, SIGNAL(triggered()), SLOT(addInterpolationCurve())); connect(addSmoothAction, SIGNAL(triggered()), SLOT(addSmoothCurve())); for (const auto& action: addFitAction) connect(action, SIGNAL(triggered()), SLOT(addFitCurve())); connect(addFourierFilterAction, SIGNAL(triggered()), SLOT(addFourierFilterCurve())); //zoom/navigate actions scaleAutoAction = new QAction(QIcon::fromTheme("labplot-auto-scale-all"), i18n("auto scale"), this); scaleAutoXAction = new QAction(QIcon::fromTheme("labplot-auto-scale-x"), i18n("auto scale X"), this); scaleAutoYAction = new QAction(QIcon::fromTheme("labplot-auto-scale-y"), i18n("auto scale Y"), this); zoomInAction = new QAction(QIcon::fromTheme("zoom-in"), i18n("zoom in"), this); zoomOutAction = new QAction(QIcon::fromTheme("zoom-out"), i18n("zoom out"), this); zoomInXAction = new QAction(QIcon::fromTheme("labplot-zoom-in-x"), i18n("zoom in X"), this); zoomOutXAction = new QAction(QIcon::fromTheme("labplot-zoom-out-x"), i18n("zoom out X"), this); zoomInYAction = new QAction(QIcon::fromTheme("labplot-zoom-in-y"), i18n("zoom in Y"), this); zoomOutYAction = new QAction(QIcon::fromTheme("labplot-zoom-out-y"), i18n("zoom out Y"), this); shiftLeftXAction = new QAction(QIcon::fromTheme("labplot-shift-left-x"), i18n("shift left X"), this); shiftRightXAction = new QAction(QIcon::fromTheme("labplot-shift-right-x"), i18n("shift right X"), this); shiftUpYAction = new QAction(QIcon::fromTheme("labplot-shift-up-y"), i18n("shift up Y"), this); shiftDownYAction = new QAction(QIcon::fromTheme("labplot-shift-down-y"), i18n("shift down Y"), this); connect(scaleAutoAction, SIGNAL(triggered()), SLOT(scaleAuto())); connect(scaleAutoXAction, SIGNAL(triggered()), SLOT(scaleAutoX())); connect(scaleAutoYAction, SIGNAL(triggered()), SLOT(scaleAutoY())); connect(zoomInAction, SIGNAL(triggered()), SLOT(zoomIn())); connect(zoomOutAction, SIGNAL(triggered()), SLOT(zoomOut())); connect(zoomInXAction, SIGNAL(triggered()), SLOT(zoomInX())); connect(zoomOutXAction, SIGNAL(triggered()), SLOT(zoomOutX())); connect(zoomInYAction, SIGNAL(triggered()), SLOT(zoomInY())); connect(zoomOutYAction, SIGNAL(triggered()), SLOT(zoomOutY())); connect(shiftLeftXAction, SIGNAL(triggered()), SLOT(shiftLeftX())); connect(shiftRightXAction, SIGNAL(triggered()), SLOT(shiftRightX())); connect(shiftUpYAction, SIGNAL(triggered()), SLOT(shiftUpY())); connect(shiftDownYAction, SIGNAL(triggered()), SLOT(shiftDownY())); //visibility action visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); } void CartesianPlot::initMenus() { initActions(); addNewMenu = new QMenu(i18n("Add new")); addNewMenu->addAction(addCurveAction); // addNewMenu->addAction(addHistogramPlot); addNewMenu->addAction(addEquationCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addDataReductionCurveAction); addNewMenu->addAction(addDifferentiationCurveAction); addNewMenu->addAction(addIntegrationCurveAction); addNewMenu->addAction(addInterpolationCurveAction); addNewMenu->addAction(addSmoothCurveAction); addNewMenu->addAction(addFitCurveAction); addNewMenu->addAction(addFourierFilterCurveAction); addNewMenu->addAction(addFourierTransformCurveAction); addNewMenu->addSeparator(); addNewMenu->addAction(addLegendAction); addNewMenu->addSeparator(); addNewMenu->addAction(addHorizontalAxisAction); addNewMenu->addAction(addVerticalAxisAction); addNewMenu->addSeparator(); addNewMenu->addAction(addTextLabelAction); addNewMenu->addSeparator(); addNewMenu->addAction(addCustomPointAction); zoomMenu = new QMenu(i18n("Zoom")); zoomMenu->addAction(scaleAutoAction); zoomMenu->addAction(scaleAutoXAction); zoomMenu->addAction(scaleAutoYAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInAction); zoomMenu->addAction(zoomOutAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInXAction); zoomMenu->addAction(zoomOutXAction); zoomMenu->addSeparator(); zoomMenu->addAction(zoomInYAction); zoomMenu->addAction(zoomOutYAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftLeftXAction); zoomMenu->addAction(shiftRightXAction); zoomMenu->addSeparator(); zoomMenu->addAction(shiftUpYAction); zoomMenu->addAction(shiftDownYAction); // Data manipulation menu QMenu* dataManipulationMenu = new QMenu(i18n("Data Manipulation")); dataManipulationMenu->setIcon(QIcon::fromTheme("zoom-draw")); dataManipulationMenu->addAction(addDataOperationAction); dataManipulationMenu->addAction(addDataReductionAction); // Data fit menu QMenu* dataFitMenu = new QMenu(i18n("Fit")); dataFitMenu->setIcon(QIcon::fromTheme("labplot-xy-fit-curve")); dataFitMenu->addAction(addFitAction.at(0)); dataFitMenu->addAction(addFitAction.at(1)); dataFitMenu->addAction(addFitAction.at(2)); dataFitMenu->addAction(addFitAction.at(3)); dataFitMenu->addAction(addFitAction.at(4)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(5)); dataFitMenu->addAction(addFitAction.at(6)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(7)); dataFitMenu->addAction(addFitAction.at(8)); dataFitMenu->addAction(addFitAction.at(9)); dataFitMenu->addSeparator(); dataFitMenu->addAction(addFitAction.at(10)); //analysis menu dataAnalysisMenu = new QMenu(i18n("Analysis")); dataAnalysisMenu->insertMenu(0, dataManipulationMenu); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addDifferentiationAction); dataAnalysisMenu->addAction(addIntegrationAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addAction(addInterpolationAction); dataAnalysisMenu->addAction(addSmoothAction); dataAnalysisMenu->addAction(addFourierFilterAction); dataAnalysisMenu->addSeparator(); dataAnalysisMenu->addMenu(dataFitMenu); //themes menu themeMenu = new QMenu(i18n("Apply Theme")); ThemesWidget* themeWidget = new ThemesWidget(0); connect(themeWidget, SIGNAL(themeSelected(QString)), this, SLOT(loadTheme(QString))); connect(themeWidget, SIGNAL(themeSelected(QString)), themeMenu, SLOT(close())); QWidgetAction* widgetAction = new QWidgetAction(this); widgetAction->setDefaultWidget(themeWidget); themeMenu->addAction(widgetAction); m_menusInitialized = true; } QMenu* CartesianPlot::createContextMenu() { if (!m_menusInitialized) initMenus(); QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); menu->insertMenu(firstAction, addNewMenu); menu->insertMenu(firstAction, zoomMenu); menu->insertSeparator(firstAction); menu->insertMenu(firstAction, themeMenu); menu->insertSeparator(firstAction); return menu; } QMenu* CartesianPlot::analysisMenu() { if (!m_menusInitialized) initMenus(); return dataAnalysisMenu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlot::icon() const { return QIcon::fromTheme("office-chart-line"); } QVector CartesianPlot::dependsOn() const { //aspects which the plotted data in the worksheet depends on (spreadsheets and later matrices) QVector aspects; for (const auto* curve : children()) { if (curve->xColumn() && dynamic_cast(curve->xColumn()->parentAspect()) ) aspects << curve->xColumn()->parentAspect(); if (curve->yColumn() && dynamic_cast(curve->yColumn()->parentAspect()) ) aspects << curve->yColumn()->parentAspect(); } return aspects; } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { if (op == ScaleAuto) scaleAuto(); else if (op == ScaleAutoX) scaleAutoX(); else if (op == ScaleAutoY) scaleAutoY(); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); else if (op == ZoomInX) zoomInX(); else if (op == ZoomOutX) zoomOutX(); else if (op == ZoomInY) zoomInY(); else if (op == ZoomOutY) zoomOutY(); else if (op == ShiftLeftX) shiftLeftX(); else if (op == ShiftRightX) shiftRightX(); else if (op == ShiftUpY) shiftUpY(); else if (op == ShiftDownY) shiftDownY(); } void CartesianPlot::setSuppressDataChangedSignal(bool value) { Q_D(CartesianPlot); d->suppressRetransform = value; } void CartesianPlot::processDropEvent(QDropEvent* event) { PERFTRACE("CartesianPlot::processDropEvent"); const QMimeData* mimeData = event->mimeData(); if (!mimeData) return; //deserialize the mime data to the vector of aspect pointers QByteArray data = mimeData->data(QLatin1String("labplot-dnd")); QVector vec; QDataStream stream(&data, QIODevice::ReadOnly); stream >> vec; QVector columns; for (auto i : vec) { AbstractAspect* aspect = (AbstractAspect*)i; AbstractColumn* column = dynamic_cast(aspect); if (column) columns << column; } //return if there are no columns being dropped. //TODO: extend this later when we allow to drag&drop plots, etc. if (columns.isEmpty()) return; //determine the first column with "x plot designation" as the x-data column for all curves to be created const AbstractColumn* xColumn = nullptr; for (const auto* column : columns) { if (column->plotDesignation() == AbstractColumn::X) { xColumn = column; break; } } //if no column with "x plot designation" is available, use the x-data column of the first curve in the plot, if (xColumn == nullptr) { QVector curves = children(); if (!curves.isEmpty()) xColumn = curves.at(0)->xColumn(); } //use the first dropped column if no column with "x plot designation" nor curves are available if (xColumn == nullptr) xColumn = columns.at(0); //create curves bool curvesAdded = false; for (const auto* column : columns) { if (column == xColumn) continue; XYCurve* curve = new XYCurve(column->name()); curve->suppressRetransform(true); //suppress retransform, all curved will be recalculated at the end curve->setXColumn(xColumn); curve->setYColumn(column); addChild(curve); curve->suppressRetransform(false); curvesAdded = true; } if (curvesAdded) dataChanged(); } bool CartesianPlot::isPanningActive() const { Q_D(const CartesianPlot); return d->panningStarted; } //############################################################################## //################################ getter methods ############################ //############################################################################## BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeType, rangeType, rangeType) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeLastValues, rangeLastValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, int, rangeFirstValues, rangeFirstValues) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleX, autoScaleX) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, xMax, xMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, xScale, xScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, xRangeBreakingEnabled, xRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, xRangeBreaks, xRangeBreaks) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, autoScaleY, autoScaleY) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, double, yMax, yMax) BASIC_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::Scale, yScale, yScale) BASIC_SHARED_D_READER_IMPL(CartesianPlot, bool, yRangeBreakingEnabled, yRangeBreakingEnabled) CLASS_SHARED_D_READER_IMPL(CartesianPlot, CartesianPlot::RangeBreaks, yRangeBreaks, yRangeBreaks) CLASS_SHARED_D_READER_IMPL(CartesianPlot, QString, theme, theme) /*! returns the actual bounding rectangular of the plot area showing data (plot's rectangular minus padding) in plot's coordinates */ QRectF CartesianPlot::dataRect() const { Q_D(const CartesianPlot); return d->dataRect; } CartesianPlot::MouseMode CartesianPlot::mouseMode() const { Q_D(const CartesianPlot); return d->mouseMode; } //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## /*! set the rectangular, defined in scene coordinates */ class CartesianPlotSetRectCmd : public QUndoCommand { public: CartesianPlotSetRectCmd(CartesianPlotPrivate* private_obj, QRectF rect) : m_private(private_obj), m_rect(rect) { setText(i18n("%1: change geometry rect", m_private->name())); }; void redo() override { QRectF tmp = m_private->rect; //TODO: // const double horizontalRatio = m_rect.width() / m_private->rect.width(); // const double verticalRatio = m_rect.height() / m_private->rect.height(); // m_private->q->handleResize(horizontalRatio, verticalRatio, false); m_private->rect = m_rect; m_rect = tmp; m_private->retransform(); emit m_private->q->rectChanged(m_private->rect); }; void undo() override { redo(); } private: CartesianPlotPrivate* m_private; QRectF m_rect; }; void CartesianPlot::setRect(const QRectF& rect) { Q_D(CartesianPlot); if (rect != d->rect) exec(new CartesianPlotSetRectCmd(d, rect)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, rangeChanged); void CartesianPlot::setRangeType(RangeType type) { Q_D(CartesianPlot); if (type != d->rangeType) - exec(new CartesianPlotSetRangeTypeCmd(d, type, i18n("%1: set range type"))); + exec(new CartesianPlotSetRangeTypeCmd(d, type, ki18n("%1: set range type"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, rangeChanged); void CartesianPlot::setRangeLastValues(int values) { Q_D(CartesianPlot); if (values != d->rangeLastValues) - exec(new CartesianPlotSetRangeLastValuesCmd(d, values, i18n("%1: set range"))); + exec(new CartesianPlotSetRangeLastValuesCmd(d, values, ki18n("%1: set range"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, rangeChanged); void CartesianPlot::setRangeFirstValues(int values) { Q_D(CartesianPlot); if (values != d->rangeFirstValues) - exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, i18n("%1: set range"))); + exec(new CartesianPlotSetRangeFirstValuesCmd(d, values, ki18n("%1: set range"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleX; if (m_autoScale) { m_minOld = m_private->xMin; m_maxOld = m_private->xMax; m_private->q->scaleAutoX(); } m_private->autoScaleX = m_autoScale; emit m_private->q->xAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->xMin = m_minOld; m_private->xMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleX = m_autoScaleOld; emit m_private->q->xAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleX(bool autoScaleX) { Q_D(CartesianPlot); if (autoScaleX != d->autoScaleX) exec(new CartesianPlotSetAutoScaleXCmd(d, autoScaleX)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); if (xMin != d->xMin) - exec(new CartesianPlotSetXMinCmd(d, xMin, i18n("%1: set min x"))); + exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); if (xMax != d->xMax) - exec(new CartesianPlotSetXMaxCmd(d, xMax, i18n("%1: set max x"))); + exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) void CartesianPlot::setXScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->xScale) - exec(new CartesianPlotSetXScaleCmd(d, scale, i18n("%1: set x scale"))); + exec(new CartesianPlotSetXScaleCmd(d, scale, ki18n("%1: set x scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreakingEnabled, bool, xRangeBreakingEnabled, retransformScales) void CartesianPlot::setXRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->xRangeBreakingEnabled) - exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, i18n("%1: x-range breaking enabled"))); + exec(new CartesianPlotSetXRangeBreakingEnabledCmd(d, enabled, ki18n("%1: x-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXRangeBreaks, CartesianPlot::RangeBreaks, xRangeBreaks, retransformScales) void CartesianPlot::setXRangeBreaks(const RangeBreaks& breakings) { Q_D(CartesianPlot); - exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, i18n("%1: x-range breaks changed"))); + exec(new CartesianPlotSetXRangeBreaksCmd(d, breakings, ki18n("%1: x-range breaks changed"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_autoScaleOld(false), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; void redo() override { m_autoScaleOld = m_private->autoScaleY; if (m_autoScale) { m_minOld = m_private->yMin; m_maxOld = m_private->yMax; m_private->q->scaleAutoY(); } m_private->autoScaleY = m_autoScale; emit m_private->q->yAutoScaleChanged(m_autoScale); }; void undo() override { if (!m_autoScaleOld) { m_private->yMin = m_minOld; m_private->yMax = m_maxOld; m_private->retransformScales(); } m_private->autoScaleY = m_autoScaleOld; emit m_private->q->yAutoScaleChanged(m_autoScaleOld); } private: CartesianPlotPrivate* m_private; bool m_autoScale; bool m_autoScaleOld; double m_minOld; double m_maxOld; }; void CartesianPlot::setAutoScaleY(bool autoScaleY) { Q_D(CartesianPlot); if (autoScaleY != d->autoScaleY) exec(new CartesianPlotSetAutoScaleYCmd(d, autoScaleY)); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) - exec(new CartesianPlotSetYMinCmd(d, yMin, i18n("%1: set min y"))); + exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) - exec(new CartesianPlotSetYMaxCmd(d, yMax, i18n("%1: set max y"))); + exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) void CartesianPlot::setYScale(Scale scale) { Q_D(CartesianPlot); if (scale != d->yScale) - exec(new CartesianPlotSetYScaleCmd(d, scale, i18n("%1: set y scale"))); + exec(new CartesianPlotSetYScaleCmd(d, scale, ki18n("%1: set y scale"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreakingEnabled, bool, yRangeBreakingEnabled, retransformScales) void CartesianPlot::setYRangeBreakingEnabled(bool enabled) { Q_D(CartesianPlot); if (enabled != d->yRangeBreakingEnabled) - exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, i18n("%1: y-range breaking enabled"))); + exec(new CartesianPlotSetYRangeBreakingEnabledCmd(d, enabled, ki18n("%1: y-range breaking enabled"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYRangeBreaks, CartesianPlot::RangeBreaks, yRangeBreaks, retransformScales) void CartesianPlot::setYRangeBreaks(const RangeBreaks& breaks) { Q_D(CartesianPlot); - exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, i18n("%1: y-range breaks changed"))); + exec(new CartesianPlotSetYRangeBreaksCmd(d, breaks, ki18n("%1: y-range breaks changed"))); } STD_SETTER_CMD_IMPL_S(CartesianPlot, SetTheme, QString, theme) void CartesianPlot::setTheme(const QString& theme) { Q_D(CartesianPlot); if (theme != d->theme) { if (!theme.isEmpty()) { beginMacro( i18n("%1: load theme %2", name(), theme) ); - exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: set theme"))); + exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: set theme"))); loadTheme(theme); endMacro(); } else - exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: disable theming"))); + exec(new CartesianPlotSetThemeCmd(d, theme, ki18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", Axis::AxisHorizontal); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(xMin()); axis->setEnd(xMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addVerticalAxis() { Axis* axis = new Axis("y-axis", Axis::AxisVertical); if (axis->autoScale()) { axis->setUndoAware(false); axis->setStart(yMin()); axis->setEnd(yMax()); axis->setUndoAware(true); } addChild(axis); } void CartesianPlot::addCurve() { addChild(new XYCurve("xy-curve")); } void CartesianPlot::addEquationCurve() { addChild(new XYEquationCurve("f(x)")); } void CartesianPlot::addHistogram() { addChild(new Histogram("Histogram")); } /*! * returns the first selected XYCurve in the plot */ const XYCurve* CartesianPlot::currentCurve() const { for (const auto* curve: this->children()) { if (curve->graphicsItem()->isSelected()) return curve; } return 0; } void CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: reduce '%2'", name(), curCurve->name()) ); curve->setName( i18n("Reduction of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->dataReductionDataChanged(curve->dataReductionData()); } else { beginMacro(i18n("%1: add data reduction curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addDifferentiationCurve() { XYDifferentiationCurve* curve = new XYDifferentiationCurve("Differentiation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: differentiate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Derivative of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->differentiationDataChanged(curve->differentiationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addIntegrationCurve() { XYIntegrationCurve* curve = new XYIntegrationCurve("Integration"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: integrate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Integral of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->integrationDataChanged(curve->integrationData()); } else { beginMacro(i18n("%1: add differentiation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addInterpolationCurve() { XYInterpolationCurve* curve = new XYInterpolationCurve("Interpolation"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: interpolate '%2'", name(), curCurve->name()) ); curve->setName( i18n("Interpolation of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); curve->recalculate(); this->addChild(curve); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addSmoothCurve() { XYSmoothCurve* curve = new XYSmoothCurve("Smooth"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: smooth '%2'", name(), curCurve->name()) ); curve->setName( i18n("Smoothing of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); curve->recalculate(); emit curve->smoothDataChanged(curve->smoothData()); } else { beginMacro(i18n("%1: add smoothing curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFitCurve() { DEBUG("CartesianPlot::addFitCurve()"); XYFitCurve* curve = new XYFitCurve("fit"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: fit to '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fit to '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); //set the fit model category and type const QAction* action = qobject_cast(QObject::sender()); PlotDataDialog::AnalysisAction type = (PlotDataDialog::AnalysisAction)action->data().toInt(); curve->initFitData(type); curve->initStartValues(curCurve); //fit with weights for y if the curve has error bars for y if (curCurve->yErrorType() == XYCurve::SymmetricError && curCurve->yErrorPlusColumn()) { XYFitCurve::FitData fitData = curve->fitData(); fitData.yWeightsType = nsl_fit_weight_instrumental; curve->setFitData(fitData); curve->setYErrorColumn(curCurve->yErrorPlusColumn()); } this->addChild(curve); curve->recalculate(); emit curve->fitDataChanged(curve->fitData()); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierFilterCurve() { XYFourierFilterCurve* curve = new XYFourierFilterCurve("Fourier filter"); const XYCurve* curCurve = currentCurve(); if (curCurve) { beginMacro( i18n("%1: Fourier filtering of '%2'", name(), curCurve->name()) ); curve->setName( i18n("Fourier filtering of '%1'", curCurve->name()) ); curve->setDataSourceType(XYAnalysisCurve::DataSourceCurve); curve->setDataSourceCurve(curCurve); this->addChild(curve); } else { beginMacro(i18n("%1: add Fourier filter curve", name())); this->addChild(curve); } endMacro(); } void CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); } /*! * public helper function to set a legend object created outside of CartesianPlot, e.g. in \c OriginProjectParser. */ void CartesianPlot::addLegend(CartesianPlotLegend* legend) { m_legend = legend; this->addChild(legend); } void CartesianPlot::addLegend() { //don't do anything if there's already a legend if (m_legend) return; m_legend = new CartesianPlotLegend(this, "legend"); this->addChild(m_legend); m_legend->retransform(); //only one legend is allowed -> disable the action if (m_menusInitialized) addLegendAction->setEnabled(false); } void CartesianPlot::addTextLabel() { TextLabel* label = new TextLabel("text label"); this->addChild(label); label->setParentGraphicsItem(graphicsItem()); } void CartesianPlot::addCustomPoint() { CustomPoint* point = new CustomPoint(this, "custom point"); this->addChild(point); } void CartesianPlot::childAdded(const AbstractAspect* child) { Q_D(CartesianPlot); const XYCurve* curve = qobject_cast(child); if (curve) { connect(curve, SIGNAL(dataChanged()), this, SLOT(dataChanged())); connect(curve, SIGNAL(xDataChanged()), this, SLOT(xDataChanged())); connect(curve, SIGNAL(yDataChanged()), this, SLOT(yDataChanged())); connect(curve, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); //update the legend on changes of the name, line and symbol styles connect(curve, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineTypeChanged(XYCurve::LineType)), this, SLOT(updateLegend())); connect(curve, SIGNAL(linePenChanged(QPen)), this, SLOT(updateLegend())); connect(curve, SIGNAL(lineOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsStyleChanged(Symbol::Style)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsSizeChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsRotationAngleChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsOpacityChanged(qreal)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsBrushChanged(QBrush)), this, SLOT(updateLegend())); connect(curve, SIGNAL(symbolsPenChanged(QPen)), this, SLOT(updateLegend())); updateLegend(); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; } else { const Histogram* histo = qobject_cast(child); if (histo) { connect(histo, SIGNAL(HistogramdataChanged()), this, SLOT(HistogramdataChanged())); connect(histo, SIGNAL(xHistogramDataChanged()), this, SLOT(xHistogramDataChanged())); connect(histo, SIGNAL(yHistogramDataChanged()), this, SLOT(yHistogramDataChanged())); connect(histo, SIGNAL(visibilityChanged(bool)), this, SLOT(curveVisibilityChanged())); } } if (!isLoading()) { //if a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty()) { const WorksheetElement* el = dynamic_cast(child); if (el) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(el)->loadThemeConfig(config); } } else { //no theme is available, apply the default colors for curves only, s.a. XYCurve::loadThemeConfig() const XYCurve* curve = dynamic_cast(child); if (curve) { int index = indexOfChild(curve); QColor themeColor; if (index < m_themeColorPalette.size()) themeColor = m_themeColorPalette.at(index); else { if (m_themeColorPalette.size()) themeColor = m_themeColorPalette.last(); } XYCurve* c = const_cast(curve); //Line QPen p = curve->linePen(); p.setColor(themeColor); c->setLinePen(p); //Drop line p = curve->dropLinePen(); p.setColor(themeColor); c->setDropLinePen(p); //Symbol QBrush brush = c->symbolsBrush(); brush.setColor(themeColor); c->setSymbolsBrush(brush); p = c->symbolsPen(); p.setColor(themeColor); c->setSymbolsPen(p); //Filling c->setFillingFirstColor(themeColor); //Error bars p.setColor(themeColor); c->setErrorBarsPen(p); } } } } void CartesianPlot::childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child) { Q_UNUSED(parent); Q_UNUSED(before); if (m_legend == child) { if (m_menusInitialized) addLegendAction->setEnabled(true); m_legend = nullptr; } else { const XYCurve* curve = qobject_cast(child); if (curve) updateLegend(); } } void CartesianPlot::updateLegend() { if (m_legend) m_legend->retransform(); } /*! called when in one of the curves the data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::dataChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); else { //free ranges -> rentransform the curve that sent XYCurve* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto c : children()) c->retransform(); } } } void CartesianPlot::HistogramdataChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoY(); else if (d->autoScaleY) this->scaleAutoY(); else { Histogram* curve = dynamic_cast(QObject::sender()); if (curve) curve->retransform(); else { //no sender available, the function was called in CartesianPlot::dataChanged() (live data source got new data) //-> retransform all available curves since we don't know which curves are affected. //TODO: this logic can be very expensive for (auto c : children()) c->retransform(); } } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::xDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAutoX(); else { XYCurve* curve = dynamic_cast(QObject::sender()); curve->retransform(); } } void CartesianPlot::xHistogramDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAutoX(); else { Histogram* curve = dynamic_cast(QObject::sender()); curve->retransform(); } } /*! called when in one of the curves the x-data was changed. Autoscales the coordinate system and the x-axes, when "auto-scale" is active. */ void CartesianPlot::yDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else { XYCurve* curve = dynamic_cast(QObject::sender()); curve->retransform(); } } void CartesianPlot::yHistogramDataChanged() { if (project()->isLoading()) return; Q_D(CartesianPlot); if (d->suppressRetransform) return; d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else { Histogram* curve = dynamic_cast(QObject::sender()); curve->retransform(); } } void CartesianPlot::curveVisibilityChanged() { Q_D(CartesianPlot); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; updateLegend(); if (d->autoScaleX && d->autoScaleY) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else if (d->autoScaleY) this->scaleAutoY(); } void CartesianPlot::setMouseMode(const MouseMode mouseMode) { Q_D(CartesianPlot); d->mouseMode = mouseMode; d->setHandlesChildEvents(mouseMode != CartesianPlot::SelectionMode); QList items = d->childItems(); if (d->mouseMode == CartesianPlot::SelectionMode) { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, false); } else { for (auto* item: items) item->setFlag(QGraphicsItem::ItemStacksBehindParent, true); } //when doing zoom selection, prevent the graphics item from being movable //if it's currently movable (no worksheet layout available) const Worksheet* worksheet = dynamic_cast(parentAspect()); if (worksheet) { if (mouseMode == CartesianPlot::SelectionMode) { if (worksheet->layout() != Worksheet::NoLayout) graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); else graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, true); } else //zoom m_selection graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); } } void CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } d->curvesXMinMaxIsDirty = false; } bool update = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; update = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; update = true; } if (update) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAutoY() { Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; const double min = curve->yColumn()->minimum(count); if (min < d->curvesYMin) d->curvesYMin = min; const double max = curve->yColumn()->maximum(count); if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (d->curvesYMin > 0.0) d->curvesYMin = 0.0; if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } d->curvesYMinMaxIsDirty = false; } bool update = false; if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; update = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; update = true; } if (update) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } } void CartesianPlot::scaleAuto() { DEBUG("CartesianPlot::scaleAuto()"); Q_D(CartesianPlot); int count = 0; switch (d->rangeType) { case CartesianPlot::RangeFree: count = 0; break; case CartesianPlot::RangeLast: count = -d->rangeLastValues; break; case CartesianPlot::RangeFirst: count = d->rangeFirstValues; break; } if (d->curvesXMinMaxIsDirty) { d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } //loop over all histograms and determine the maximum and minimum x-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; const double min = curve->xColumn()->minimum(count); if (min < d->curvesXMin) d->curvesXMin = min; const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; const double min = curve->yColumn()->minimum(count); if (min < d->curvesYMin) d->curvesYMin = min; const double max = curve->yColumn()->maximum(count); if (max > d->curvesYMax) d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (d->curvesYMin > 0.0) d->curvesYMin = 0.0; const double max = curve->getYMaximum(); if (max > d->curvesYMax) d->curvesYMax = max; } } bool updateX = false; bool updateY = false; if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) { d->xMin = d->curvesXMin; updateX = true; } if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) { d->xMax = d->curvesXMax; updateX = true; } if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) { d->yMin = d->curvesYMin; updateY = true; } if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) { d->yMax = d->curvesYMax; updateY = true; } DEBUG(" xmin/xmax = " << d->xMin << '/' << d->xMax << ", ymin/ymax = " << d->yMin << '/' << d->yMax); if (updateX || updateY) { if (updateX) { if (d->xMax == d->xMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->xMax != 0) { d->xMax = d->xMax*1.1; d->xMin = d->xMin*0.9; } else { d->xMax = 0.1; d->xMin = -0.1; } } else { double offset = (d->xMax - d->xMin)*d->autoScaleOffsetFactor; d->xMin -= offset; d->xMax += offset; } } if (updateY) { if (d->yMax == d->yMin) { //in case min and max are equal (e.g. if we plot a single point), subtract/add 10% of the value if (d->yMax != 0) { d->yMax = d->yMax*1.1; d->yMin = d->yMin*0.9; } else { d->yMax = 0.1; d->yMin = -0.1; } } else { double offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } } d->retransformScales(); } } void CartesianPlot::zoomIn() { DEBUG("CartesianPlot::zoomIn()"); Q_D(CartesianPlot); double oldRange = (d->xMax - d->xMin); double newRange = (d->xMax - d->xMin) / m_zoomFactor; d->xMax = d->xMax + (newRange - oldRange) / 2; d->xMin = d->xMin - (newRange - oldRange) / 2; oldRange = (d->yMax - d->yMin); newRange = (d->yMax - d->yMin) / m_zoomFactor; d->yMax = d->yMax + (newRange - oldRange) / 2; d->yMin = d->yMin - (newRange - oldRange) / 2; d->retransformScales(); } void CartesianPlot::zoomOut() { Q_D(CartesianPlot); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; oldRange = (d->yMax-d->yMin); newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInX() { Q_D(CartesianPlot); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)/m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutX() { Q_D(CartesianPlot); double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomInY() { Q_D(CartesianPlot); double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)/m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::zoomOutY() { Q_D(CartesianPlot); double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; d->retransformScales(); } void CartesianPlot::shiftLeftX() { Q_D(CartesianPlot); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); double offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); double offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); double offsetY = (d->yMax-d->yMin)*0.1; d->yMax -= offsetY; d->yMin -= offsetY; d->retransformScales(); } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlot::visibilityChanged() { Q_D(CartesianPlot); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### CartesianPlotPrivate::CartesianPlotPrivate(CartesianPlot* plot) : AbstractPlotPrivate(plot), curvesXMinMaxIsDirty(false), curvesYMinMaxIsDirty(false), curvesXMin(INFINITY), curvesXMax(-INFINITY), curvesYMin(INFINITY), curvesYMax(-INFINITY), q(plot), mouseMode(CartesianPlot::SelectionMode), cSystem(nullptr), suppressRetransform(false), panningStarted(false), m_selectionBandIsShown(false) { setData(0, WorksheetElement::NameCartesianPlot); } /*! updates the position of plot rectangular in scene coordinates to \c r and recalculates the scales. The size of the plot corresponds to the size of the plot area, the area which is filled with the background color etc. and which can pose the parent item for several sub-items (like TextLabel). Note, the size of the area used to define the coordinate system doesn't need to be equal to this plot area. Also, the size (=bounding box) of CartesianPlot can be greater than the size of the plot area. */ void CartesianPlotPrivate::retransform() { if (suppressRetransform) return; PERFTRACE("CartesianPlotPrivate::retransform()"); prepareGeometryChange(); setPos( rect.x()+rect.width()/2, rect.y()+rect.height()/2); updateDataRect(); retransformScales(); //plotArea position is always (0, 0) in parent's coordinates, don't need to update here q->plotArea()->setRect(rect); //call retransform() for the title and the legend (if available) //when a predefined position relative to the (Left, Centered etc.) is used, //the actual position needs to be updated on plot's geometry changes. if (q->title()) q->title()->retransform(); if (q->m_legend) q->m_legend->retransform(); WorksheetElementContainerPrivate::recalcShapeAndBoundingRect(); } void CartesianPlotPrivate::retransformScales() { PERFTRACE("CartesianPlotPrivate::retransformScales()"); DEBUG(" xmin/xmax = " << xMin << '/'<< xMax << ", ymin/ymax = " << yMin << '/' << yMax); CartesianPlot* plot = dynamic_cast(q); QVector scales; //perform the mapping from the scene coordinates to the plot's coordinates here. QRectF itemRect = mapRectFromScene(rect); //check ranges for log-scales if (xScale != CartesianPlot::ScaleLinear) checkXRange(); //check whether we have x-range breaks - the first break, if available, should be valid bool hasValidBreak = (xRangeBreakingEnabled && !xRangeBreaks.list.isEmpty() && xRangeBreaks.list.first().isValid()); static const int breakGap = 20; double sceneStart, sceneEnd, logicalStart, logicalEnd; //create x-scales int plotSceneStart = itemRect.x() + horizontalPadding; int plotSceneEnd = itemRect.x() + itemRect.width() - horizontalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = xMin; logicalEnd = xMax; //TODO: how should we handle the case sceneStart == sceneEnd? //(to reproduce, create plots and adjust the spacing/pading to get zero size for the plots) if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = xMin; for (const auto& rb: xRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &xRangeBreaks.list.first()) sceneStart += breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the x-data range) sceneStart = sceneEndLast+breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = xMax; if (sceneStart != sceneEnd) scales << this->createScale(xScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setXScales(scales); //check ranges for log-scales if (yScale != CartesianPlot::ScaleLinear) checkYRange(); //check whether we have y-range breaks - the first break, if available, should be valid hasValidBreak = (yRangeBreakingEnabled && !yRangeBreaks.list.isEmpty() && yRangeBreaks.list.first().isValid()); //create y-scales scales.clear(); plotSceneStart = itemRect.y()+itemRect.height()-verticalPadding; plotSceneEnd = itemRect.y()+verticalPadding; if (!hasValidBreak) { //no breaks available -> range goes from the plot beginning to the end of the plot sceneStart = plotSceneStart; sceneEnd = plotSceneEnd; logicalStart = yMin; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } else { int sceneEndLast = plotSceneStart; int logicalEndLast = yMin; for (const auto& rb: yRangeBreaks.list) { if (!rb.isValid()) break; //current range goes from the end of the previous one (or from the plot beginning) to curBreak.start sceneStart = sceneEndLast; if (&rb == &yRangeBreaks.list.first()) sceneStart -= breakGap; sceneEnd = plotSceneStart + (plotSceneEnd-plotSceneStart) * rb.position; logicalStart = logicalEndLast; logicalEnd = rb.start; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); sceneEndLast = sceneEnd; logicalEndLast = rb.end; } //add the remaining range going from the last available range break to the end of the plot (=end of the y-data range) sceneStart = sceneEndLast-breakGap; sceneEnd = plotSceneEnd; logicalStart = logicalEndLast; logicalEnd = yMax; if (sceneStart != sceneEnd) scales << this->createScale(yScale, sceneStart, sceneEnd, logicalStart, logicalEnd); } cSystem->setYScales(scales); //calculate the changes in x and y and save the current values for xMin, xMax, yMin, yMax double deltaXMin = 0; double deltaXMax = 0; double deltaYMin = 0; double deltaYMax = 0; if (xMin != xMinPrev) { deltaXMin = xMin - xMinPrev; emit plot->xMinChanged(xMin); } if (xMax != xMaxPrev) { deltaXMax = xMax - xMaxPrev; emit plot->xMaxChanged(xMax); } if (yMin != yMinPrev) { deltaYMin = yMin - yMinPrev; emit plot->yMinChanged(yMin); } if (yMax!=yMaxPrev) { deltaYMax = yMax - yMaxPrev; emit plot->yMaxChanged(yMax); } xMinPrev = xMin; xMaxPrev = xMax; yMinPrev = yMin; yMaxPrev = yMax; //adjust auto-scale axes for (auto* axis: q->children()) { if (!axis->autoScale()) continue; if (axis->orientation() == Axis::AxisHorizontal) { if (deltaXMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(xMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(xMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setEnd(yMax); axis->setUndoAware(true); axis->setSuppressRetransform(false); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setSuppressRetransform(true); axis->setStart(yMin); axis->setUndoAware(true); axis->setSuppressRetransform(false); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaXMin != 0) { // axis->setOffset(axis->offset() + deltaXMin, false); // } } } // call retransform() on the parent to trigger the update of all axes and curvesю //no need to do this on load since all plots are retransformed again after the project is loaded. if (!q->isLoading()) q->retransform(); } /* * calculates the rectangular of the are showing the actual data (plot's rect minus padding), * in plot's coordinates. */ void CartesianPlotPrivate::updateDataRect() { dataRect = mapRectFromScene(rect); dataRect.setX(dataRect.x() + horizontalPadding); dataRect.setY(dataRect.y() + verticalPadding); dataRect.setWidth(dataRect.width() - horizontalPadding); dataRect.setHeight(dataRect.height() - verticalPadding); } void CartesianPlotPrivate::rangeChanged() { curvesXMinMaxIsDirty = true; curvesYMinMaxIsDirty = true; if (autoScaleX && autoScaleY) q->scaleAuto(); else if (autoScaleX) q->scaleAutoX(); else if (autoScaleY) q->scaleAutoY(); } /*! * don't allow any negative values for the x range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkXRange() { double min = 0.01; if (xMin <= 0.0) { (min < xMax*min) ? xMin = min : xMin = xMax*min; emit q->xMinChanged(xMin); } else if (xMax <= 0.0) { (-min > xMin*min) ? xMax = -min : xMax = xMin*min; emit q->xMaxChanged(xMax); } } /*! * don't allow any negative values for the y range when log or sqrt scalings are used */ void CartesianPlotPrivate::checkYRange() { double min = 0.01; if (yMin <= 0.0) { (min < yMax*min) ? yMin = min : yMin = yMax*min; emit q->yMinChanged(yMin); } else if (yMax <= 0.0) { (-min > yMin*min) ? yMax = -min : yMax = yMin*min; emit q->yMaxChanged(yMax); } } CartesianScale* CartesianPlotPrivate::createScale(CartesianPlot::Scale type, double sceneStart, double sceneEnd, double logicalStart, double logicalEnd) { DEBUG("CartesianPlotPrivate::createScale() scence start/end = " << sceneStart << '/' << sceneEnd << ", logical start/end = " << logicalStart << '/' << logicalEnd); // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (std::numeric_limits::lowest(), std::numeric_limits::max()); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else { double base; if (type == CartesianPlot::ScaleLog10) base = 10.0; else if (type == CartesianPlot::ScaleLog2) base = 2.0; else base = M_E; return CartesianScale::createLogScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd, base); } } /*! * Reimplemented from QGraphicsItem. */ QVariant CartesianPlotPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (change == QGraphicsItem::ItemPositionChange) { const QPointF& itemPos = value.toPointF();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); emit q->rectChanged(newRect); } return QGraphicsItem::itemChange(change, value); } //############################################################################## //################################## Events ################################## //############################################################################## void CartesianPlotPrivate::mousePressEvent(QGraphicsSceneMouseEvent *event) { if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { if (mouseMode == CartesianPlot::ZoomSelectionMode) m_selectionStart = event->pos(); else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionStart.setX(event->pos().x()); m_selectionStart.setY(dataRect.height()/2); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(-dataRect.width()/2); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else { if ( dataRect.contains(event->pos()) ){ panningStarted = true; m_panningStart = event->pos(); setCursor(Qt::ClosedHandCursor); } QGraphicsItem::mousePressEvent(event); } } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { if (panningStarted && dataRect.contains(event->pos()) ) { //don't retransform on small mouse movement deltas const int deltaXScene = (m_panningStart.x() - event->pos().x()); const int deltaYScene = (m_panningStart.y() - event->pos().y()); if (abs(deltaXScene) < 5 && abs(deltaYScene) < 5) return; const QPointF logicalEnd = cSystem->mapSceneToLogical(event->pos()); const QPointF logicalStart = cSystem->mapSceneToLogical(m_panningStart); const float deltaX = (logicalStart.x() - logicalEnd.x()); const float deltaY = (logicalStart.y() - logicalEnd.y()); xMax += deltaX; xMin += deltaX; yMax += deltaY; yMin += deltaY; retransformScales(); m_panningStart = event->pos(); } else QGraphicsItem::mouseMoveEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { QGraphicsItem::mouseMoveEvent(event); if ( !boundingRect().contains(event->pos()) ) { q->info(""); return; } QString info; QPointF logicalStart = cSystem->mapSceneToLogical(m_selectionStart); if (mouseMode == CartesianPlot::ZoomSelectionMode) { m_selectionEnd = event->pos(); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()) + QString::fromUtf8(", Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { m_selectionEnd.setX(event->pos().x()); m_selectionEnd.setY(-dataRect.height()/2); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δx=") + QString::number(logicalEnd.x()-logicalStart.x()); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionEnd.setX(dataRect.width()/2); m_selectionEnd.setY(event->pos().y()); QPointF logicalEnd = cSystem->mapSceneToLogical(m_selectionEnd); info = QString::fromUtf8("Δy=") + QString::number(logicalEnd.y()-logicalStart.y()); } q->info(info); update(); } } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { setCursor(Qt::ArrowCursor); if (mouseMode == CartesianPlot::SelectionMode) { panningStarted = false; //TODO: why do we do this all the time?!?! const QPointF& itemPos = pos();//item's center point in parent's coordinates; const qreal x = itemPos.x(); const qreal y = itemPos.y(); //calculate the new rect and set it QRectF newRect; const qreal w = rect.width(); const qreal h = rect.height(); newRect.setX(x-w/2); newRect.setY(y-h/2); newRect.setWidth(w); newRect.setHeight(h); suppressRetransform = true; q->setRect(newRect); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } else if (mouseMode == CartesianPlot::ZoomSelectionMode || mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) { //don't zoom if very small region was selected, avoid occasional/unwanted zooming if ( qAbs(m_selectionEnd.x()-m_selectionStart.x()) < 20 || qAbs(m_selectionEnd.y()-m_selectionStart.y()) < 20 ) { m_selectionBandIsShown = false; return; } //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); QPointF logicalZoomEnd = cSystem->mapSceneToLogical(m_selectionEnd, AbstractCoordinateSystem::SuppressPageClipping); if (m_selectionEnd.x() > m_selectionStart.x()) { xMin = logicalZoomStart.x(); xMax = logicalZoomEnd.x(); } else { xMin = logicalZoomEnd.x(); xMax = logicalZoomStart.x(); } if (m_selectionEnd.y() > m_selectionStart.y()) { yMin = logicalZoomEnd.y(); yMax = logicalZoomStart.y(); } else { yMin = logicalZoomStart.y(); yMax = logicalZoomEnd.y(); } m_selectionBandIsShown = false; retransformScales(); } } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { //determine first, which axes are selected and zoom only in the corresponding direction. //zoom the entire plot if no axes selected. bool zoomX = false; bool zoomY = false; for (auto* axis: q->children()) { if (!axis->graphicsItem()->isSelected()) continue; if (axis->orientation() == Axis::AxisHorizontal) zoomX = true; else zoomY = true; } if (event->delta() > 0) { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomIn(); } else { if (zoomX) q->zoomInX(); if (zoomY) q->zoomInY(); } } else { if (!zoomX && !zoomY) { //no special axis selected -> zoom in everything q->zoomOut(); } else { if (zoomX) q->zoomOutX(); if (zoomY) q->zoomOutY(); } } } void CartesianPlotPrivate::hoverMoveEvent(QGraphicsSceneHoverEvent* event) { QPointF point = event->pos(); QString info; if (dataRect.contains(point)) { QPointF logicalPoint = cSystem->mapSceneToLogical(point); if (mouseMode == CartesianPlot::ZoomSelectionMode && !m_selectionBandIsShown) info = "x=" + QString::number(logicalPoint.x()) + ", y=" + QString::number(logicalPoint.y()); else if (mouseMode == CartesianPlot::ZoomXSelectionMode && !m_selectionBandIsShown) { QPointF p1(logicalPoint.x(), yMin); QPointF p2(logicalPoint.x(), yMax); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "x=" + QString::number(logicalPoint.x()); update(); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode && !m_selectionBandIsShown) { QPointF p1(xMin, logicalPoint.y()); QPointF p2(xMax, logicalPoint.y()); m_selectionStartLine.setP1(cSystem->mapLogicalToScene(p1)); m_selectionStartLine.setP2(cSystem->mapLogicalToScene(p2)); info = "y=" + QString::number(logicalPoint.y()); update(); } } q->info(info); QGraphicsItem::hoverMoveEvent(event); } void CartesianPlotPrivate::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget * widget) { if (!isVisible()) return; painter->setPen(QPen(Qt::black, 3)); if ((mouseMode == CartesianPlot::ZoomXSelectionMode || mouseMode == CartesianPlot::ZoomYSelectionMode) && (!m_selectionBandIsShown)) painter->drawLine(m_selectionStartLine); if (m_selectionBandIsShown) { painter->save(); painter->setPen(QPen(Qt::black, 5)); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->setBrush(Qt::blue); painter->setOpacity(0.2); painter->drawRect(QRectF(m_selectionStart, m_selectionEnd)); painter->restore(); } WorksheetElementContainerPrivate::paint(painter, option, widget); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlot::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlot); writer->writeStartElement( "cartesianPlot" ); writeBasicAttributes(writer); writeCommentElement(writer); //applied theme if (!d->theme.isEmpty()) { writer->writeStartElement( "theme" ); writer->writeAttribute("name", d->theme); writer->writeEndElement(); } //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->rect.x()) ); writer->writeAttribute( "y", QString::number(d->rect.y()) ); writer->writeAttribute( "width", QString::number(d->rect.width()) ); writer->writeAttribute( "height", QString::number(d->rect.height()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //coordinate system and padding writer->writeStartElement( "coordinateSystem" ); writer->writeAttribute( "autoScaleX", QString::number(d->autoScaleX) ); writer->writeAttribute( "autoScaleY", QString::number(d->autoScaleY) ); writer->writeAttribute( "xMin", QString::number(d->xMin) ); writer->writeAttribute( "xMax", QString::number(d->xMax) ); writer->writeAttribute( "yMin", QString::number(d->yMin) ); writer->writeAttribute( "yMax", QString::number(d->yMax) ); writer->writeAttribute( "xScale", QString::number(d->xScale) ); writer->writeAttribute( "yScale", QString::number(d->yScale) ); writer->writeAttribute( "horizontalPadding", QString::number(d->horizontalPadding) ); writer->writeAttribute( "verticalPadding", QString::number(d->verticalPadding) ); writer->writeEndElement(); //x-scale breaks if (d->xRangeBreakingEnabled || !d->xRangeBreaks.list.isEmpty()) { writer->writeStartElement("xRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->xRangeBreakingEnabled) ); for (const auto& rb: d->xRangeBreaks.list) { writer->writeStartElement("xRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //y-scale breaks if (d->yRangeBreakingEnabled || !d->yRangeBreaks.list.isEmpty()) { writer->writeStartElement("yRangeBreaks"); writer->writeAttribute( "enabled", QString::number(d->yRangeBreakingEnabled) ); for (const auto& rb: d->yRangeBreaks.list) { writer->writeStartElement("yRangeBreak"); writer->writeAttribute("start", QString::number(rb.start)); writer->writeAttribute("end", QString::number(rb.end)); writer->writeAttribute("position", QString::number(rb.position)); writer->writeAttribute("style", QString::number(rb.style)); writer->writeEndElement(); } writer->writeEndElement(); } //serialize all children (plot area, title text label, axes and curves) for (auto *elem: children(IncludeHidden)) elem->save(writer); writer->writeEndElement(); // close "cartesianPlot" section } //! Load from XML bool CartesianPlot::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlot); if (!reader->isStartElement() || reader->name() != "cartesianPlot") { reader->raiseError(i18n("no cartesianPlot element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool titleLabelRead = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlot") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "theme") { attribs = reader->attributes(); d->theme = attribs.value("name").toString(); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->rect.setX( str.toDouble() ); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->rect.setY( str.toDouble() ); str = attribs.value("width").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'width'")); else d->rect.setWidth( str.toDouble() ); str = attribs.value("height").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'height'")); else d->rect.setHeight( str.toDouble() ); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "coordinateSystem") { attribs = reader->attributes(); str = attribs.value("autoScaleX").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleX'")); else d->autoScaleX = bool(str.toInt()); str = attribs.value("autoScaleY").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoScaleY'")); else d->autoScaleY = bool(str.toInt()); str = attribs.value("xMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xMin'")); else { d->xMin = str.toDouble(); d->xMinPrev = d->xMin; } str = attribs.value("xMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xMax'")); else { d->xMax = str.toDouble(); d->xMaxPrev = d->xMax; } str = attribs.value("yMin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yMin'")); else { d->yMin = str.toDouble(); d->yMinPrev = d->yMin; } str = attribs.value("yMax").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yMax'")); else { d->yMax = str.toDouble(); d->yMaxPrev = d->yMax; } str = attribs.value("xScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'xScale'")); else d->xScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("yScale").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'yScale'")); else d->yScale = CartesianPlot::Scale(str.toInt()); str = attribs.value("horizontalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPadding'")); else d->horizontalPadding = str.toDouble(); str = attribs.value("verticalPadding").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPadding'")); else d->verticalPadding = str.toDouble(); } else if (!preview && reader->name() == "xRangeBreaks") { //delete default rang break d->xRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->xRangeBreakingEnabled = str.toInt(); } else if (!preview && reader->name() == "xRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->xRangeBreaks.list << b; } else if (!preview && reader->name() == "yRangeBreaks") { //delete default rang break d->yRangeBreaks.list.clear(); attribs = reader->attributes(); str = attribs.value("enabled").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'enabled'")); else d->yRangeBreakingEnabled = str.toInt(); } else if (!preview && reader->name() == "yRangeBreak") { attribs = reader->attributes(); RangeBreak b; str = attribs.value("start").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'start'")); else b.start = str.toDouble(); str = attribs.value("end").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'end'")); else b.end = str.toDouble(); str = attribs.value("position").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else b.position = str.toDouble(); str = attribs.value("style").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'style'")); else b.style = CartesianPlot::RangeBreakStyle(str.toInt()); d->yRangeBreaks.list << b; } else if (reader->name() == "textLabel") { if (!titleLabelRead) { //the first text label is always the title label m_title->load(reader, preview); titleLabelRead = true; } else { TextLabel* label = new TextLabel("text label"); if (label->load(reader, preview)) { addChildFast(label); label->setParentGraphicsItem(graphicsItem()); } else { delete label; return false; } } } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis(""); if (axis->load(reader, preview)) addChildFast(axis); else { delete axis; return false; } } else if (reader->name() == "xyCurve") { XYCurve* curve = new XYCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = new XYEquationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = new XYDataReductionCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = new XYDifferentiationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = new XYIntegrationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = new XYInterpolationCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = new XYFitCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = new XYFourierFilterCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = new XYFourierTransformCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = new XYSmoothCurve(""); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, ""); if (m_legend->load(reader, preview)) addChildFast(m_legend); else { delete m_legend; return false; } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, ""); if (point->load(reader, preview)) addChildFast(point); else { delete point; return false; } } else if(reader->name() == "Histogram") { Histogram* curve = new Histogram("Histogram"); if (curve->load(reader, preview)) addChildFast(curve); else { removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } if (preview) return true; d->retransform(); //if a theme was used, initialize the color palette if (!d->theme.isEmpty()) { //TODO: check whether the theme config really exists KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); this->setColorPalette(config); } else { //initialize the color palette with default colors this->setColorPalette(KConfig()); } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void CartesianPlot::loadTheme(const QString& theme) { KConfig config(ThemeHandler::themeFilePath(theme), KConfig::SimpleConfig); loadThemeConfig(config); } void CartesianPlot::loadThemeConfig(const KConfig& config) { QString str = config.name(); str = str.right(str.length() - str.lastIndexOf(QDir::separator()) - 1); this->setTheme(str); //load the color palettes for the curves this->setColorPalette(config); //load the theme for all the children for (auto* child: children(AbstractAspect::IncludeHidden)) child->loadThemeConfig(config); Q_D(CartesianPlot); d->update(this->rect()); } void CartesianPlot::saveTheme(KConfig &config) { const QVector& axisElements = children(AbstractAspect::IncludeHidden); const QVector& plotAreaElements = children(AbstractAspect::IncludeHidden); const QVector& textLabelElements = children(AbstractAspect::IncludeHidden); axisElements.at(0)->saveThemeConfig(config); plotAreaElements.at(0)->saveThemeConfig(config); textLabelElements.at(0)->saveThemeConfig(config); for (auto *child: children(AbstractAspect::IncludeHidden)) child->saveThemeConfig(config); } //Generating colors from 5-color theme palette void CartesianPlot::setColorPalette(const KConfig& config) { if (config.hasGroup(QLatin1String("Theme"))) { KConfigGroup group = config.group(QLatin1String("Theme")); //read the five colors defining the palette m_themeColorPalette.clear(); m_themeColorPalette.append(group.readEntry("ThemePaletteColor1", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor2", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor3", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor4", QColor())); m_themeColorPalette.append(group.readEntry("ThemePaletteColor5", QColor())); } else { //no theme is available, provide 5 "default colors" m_themeColorPalette.clear(); m_themeColorPalette.append(QColor(25, 25, 25)); m_themeColorPalette.append(QColor(0, 0, 127)); m_themeColorPalette.append(QColor(127 ,0, 0)); m_themeColorPalette.append(QColor(0, 127, 0)); m_themeColorPalette.append(QColor(85, 0, 127)); } //generate 30 additional shades if the color palette contains more than one color if (m_themeColorPalette.at(0) != m_themeColorPalette.at(1)) { QColor c; //3 factors to create shades from theme's palette float fac[3] = {0.25f,0.45f,0.65f}; //Generate 15 lighter shades for (int i = 0; i < 5; i++) { for (int j = 1; j < 4; j++) { c.setRed( m_themeColorPalette.at(i).red()*(1-fac[j-1]) ); c.setGreen( m_themeColorPalette.at(i).green()*(1-fac[j-1]) ); c.setBlue( m_themeColorPalette.at(i).blue()*(1-fac[j-1]) ); m_themeColorPalette.append(c); } } //Generate 15 darker shades for (int i = 0; i < 5; i++) { for (int j = 4; j < 7; j++) { c.setRed( m_themeColorPalette.at(i).red()+((255-m_themeColorPalette.at(i).red())*fac[j-4]) ); c.setGreen( m_themeColorPalette.at(i).green()+((255-m_themeColorPalette.at(i).green())*fac[j-4]) ); c.setBlue( m_themeColorPalette.at(i).blue()+((255-m_themeColorPalette.at(i).blue())*fac[j-4]) ); m_themeColorPalette.append(c); } } } } const QList& CartesianPlot::themeColorPalette() const { return m_themeColorPalette; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp index 1f895abd4..13acf11e9 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp @@ -1,1148 +1,1148 @@ /*************************************************************************** File : CartesianPlotLegend.cpp Project : LabPlot Description : Legend for the cartesian plot -------------------------------------------------------------------- Copyright : (C) 2013-2018 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class CartesianPlotLegend \brief Legend for the cartesian plot. \ingroup kdefrontend */ #include "CartesianPlotLegend.h" #include "backend/worksheet/plots/cartesian/CartesianPlotLegendPrivate.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/worksheet/TextLabel.h" #include "backend/lib/commandtemplates.h" #include #include #include #include #include #include #include CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name) : WorksheetElement(name), d_ptr(new CartesianPlotLegendPrivate(this)), m_plot(plot) { init(); } CartesianPlotLegend::CartesianPlotLegend(CartesianPlot* plot, const QString &name, CartesianPlotLegendPrivate *dd) : WorksheetElement(name), d_ptr(dd), m_plot(plot) { init(); } CartesianPlotLegend::~CartesianPlotLegend() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void CartesianPlotLegend::init() { Q_D(CartesianPlotLegend); KConfig config; KConfigGroup group = config.group( "CartesianPlotLegend" ); d->labelFont = group.readEntry("LabelsFont", QFont()); d->labelFont.setPixelSize( Worksheet::convertToSceneUnits( 10, Worksheet::Point ) ); d->labelColor = Qt::black; d->labelColumnMajor = true; d->lineSymbolWidth = group.readEntry("LineSymbolWidth", Worksheet::convertToSceneUnits(1, Worksheet::Centimeter)); d->rowCount = 0; d->columnCount = 0; d->position.horizontalPosition = CartesianPlotLegend::hPositionRight; d->position.verticalPosition = CartesianPlotLegend::vPositionBottom; //Title d->title = new TextLabel(this->name(), TextLabel::PlotLegendTitle); addChild(d->title); d->title->setHidden(true); d->title->setParentGraphicsItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); connect(d->title, &TextLabel::changed, this, &CartesianPlotLegend::retransform); //Background d->backgroundType = (PlotArea::BackgroundType) group.readEntry("BackgroundType", (int) PlotArea::Color); d->backgroundColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("BackgroundColorStyle", (int) PlotArea::SingleColor); d->backgroundImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("BackgroundImageStyle", (int) PlotArea::Scaled); d->backgroundBrushStyle = (Qt::BrushStyle) group.readEntry("BackgroundBrushStyle", (int) Qt::SolidPattern); d->backgroundFileName = group.readEntry("BackgroundFileName", QString()); d->backgroundFirstColor = group.readEntry("BackgroundFirstColor", QColor(Qt::white)); d->backgroundSecondColor = group.readEntry("BackgroundSecondColor", QColor(Qt::black)); d->backgroundOpacity = group.readEntry("BackgroundOpacity", 1.0); //Border d->borderPen = QPen(group.readEntry("BorderColor", QColor(Qt::black)), group.readEntry("BorderWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)), (Qt::PenStyle) group.readEntry("BorderStyle", (int)Qt::SolidLine)); d->borderCornerRadius = group.readEntry("BorderCornerRadius", 0.0); d->borderOpacity = group.readEntry("BorderOpacity", 1.0); //Layout d->layoutTopMargin = group.readEntry("LayoutTopMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2f, Worksheet::Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1f, Worksheet::Centimeter)); d->layoutColumnCount = group.readEntry("LayoutColumnCount", 1); graphicsItem()->setFlag(QGraphicsItem::ItemIsSelectable, true); graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable); graphicsItem()->setFlag(QGraphicsItem::ItemSendsGeometryChanges); this->initActions(); } void CartesianPlotLegend::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CartesianPlotLegend::visibilityChanged); } QMenu* CartesianPlotLegend::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon CartesianPlotLegend::icon() const{ return QIcon::fromTheme("text-field"); } STD_SWAP_METHOD_SETTER_CMD_IMPL(CartesianPlotLegend, SetVisible, bool, swapVisible) void CartesianPlotLegend::setVisible(bool on) { Q_D(CartesianPlotLegend); - exec(new CartesianPlotLegendSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new CartesianPlotLegendSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool CartesianPlotLegend::isVisible() const{ Q_D(const CartesianPlotLegend); return d->isVisible(); } void CartesianPlotLegend::setPrinting(bool on) { Q_D(CartesianPlotLegend); d->m_printing = on; } QGraphicsItem *CartesianPlotLegend::graphicsItem() const{ return d_ptr; } void CartesianPlotLegend::retransform() { d_ptr->retransform(); } void CartesianPlotLegend::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); //TODO // Q_D(const CartesianPlotLegend); } //############################################################################## //################################ getter methods ############################ //############################################################################## CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QFont, labelFont, labelFont) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, labelColor, labelColor) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, bool, labelColumnMajor, labelColumnMajor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, CartesianPlotLegend::PositionWrapper, position, position) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, lineSymbolWidth, lineSymbolWidth) //Title TextLabel* CartesianPlotLegend::title() { return d_ptr->title; } //Background BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundType, backgroundType, backgroundType) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundColorStyle, backgroundColorStyle, backgroundColorStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, PlotArea::BackgroundImageStyle, backgroundImageStyle, backgroundImageStyle) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, Qt::BrushStyle, backgroundBrushStyle, backgroundBrushStyle) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundFirstColor, backgroundFirstColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QColor, backgroundSecondColor, backgroundSecondColor) CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QString, backgroundFileName, backgroundFileName) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, backgroundOpacity, backgroundOpacity) //Border CLASS_SHARED_D_READER_IMPL(CartesianPlotLegend, QPen, borderPen, borderPen) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderCornerRadius, borderCornerRadius) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, borderOpacity, borderOpacity) //Layout BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutTopMargin, layoutTopMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutBottomMargin, layoutBottomMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutLeftMargin, layoutLeftMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutRightMargin, layoutRightMargin) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutHorizontalSpacing, layoutHorizontalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, float, layoutVerticalSpacing, layoutVerticalSpacing) BASIC_SHARED_D_READER_IMPL(CartesianPlotLegend, int, layoutColumnCount, layoutColumnCount) //############################################################################## //###################### setter methods and undo commands #################### //############################################################################## STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelFont, QFont, labelFont, retransform) void CartesianPlotLegend::setLabelFont(const QFont& font) { Q_D(CartesianPlotLegend); if (font!= d->labelFont) - exec(new CartesianPlotLegendSetLabelFontCmd(d, font, i18n("%1: set font"))); + exec(new CartesianPlotLegendSetLabelFontCmd(d, font, ki18n("%1: set font"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColor, QColor, labelColor, update) void CartesianPlotLegend::setLabelColor(const QColor& color) { Q_D(CartesianPlotLegend); if (color!= d->labelColor) - exec(new CartesianPlotLegendSetLabelColorCmd(d, color, i18n("%1: set font color"))); + exec(new CartesianPlotLegendSetLabelColorCmd(d, color, ki18n("%1: set font color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLabelColumnMajor, bool, labelColumnMajor, retransform) void CartesianPlotLegend::setLabelColumnMajor(bool columnMajor) { Q_D(CartesianPlotLegend); if (columnMajor != d->labelColumnMajor) - exec(new CartesianPlotLegendSetLabelColumnMajorCmd(d, columnMajor, i18n("%1: change column order"))); + exec(new CartesianPlotLegendSetLabelColumnMajorCmd(d, columnMajor, ki18n("%1: change column order"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLineSymbolWidth, float, lineSymbolWidth, retransform) void CartesianPlotLegend::setLineSymbolWidth(float width) { Q_D(CartesianPlotLegend); if (width != d->lineSymbolWidth) - exec(new CartesianPlotLegendSetLineSymbolWidthCmd(d, width, i18n("%1: change line+symbol width"))); + exec(new CartesianPlotLegendSetLineSymbolWidthCmd(d, width, ki18n("%1: change line+symbol width"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetPosition, CartesianPlotLegend::PositionWrapper, position, updatePosition); void CartesianPlotLegend::setPosition(const PositionWrapper& pos) { Q_D(CartesianPlotLegend); if (pos.point!=d->position.point || pos.horizontalPosition!=d->position.horizontalPosition || pos.verticalPosition!=d->position.verticalPosition) - exec(new CartesianPlotLegendSetPositionCmd(d, pos, i18n("%1: set position"))); + exec(new CartesianPlotLegendSetPositionCmd(d, pos, ki18n("%1: set position"))); } //Background STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundType, PlotArea::BackgroundType, backgroundType, update) void CartesianPlotLegend::setBackgroundType(PlotArea::BackgroundType type) { Q_D(CartesianPlotLegend); if (type != d->backgroundType) - exec(new CartesianPlotLegendSetBackgroundTypeCmd(d, type, i18n("%1: background type changed"))); + exec(new CartesianPlotLegendSetBackgroundTypeCmd(d, type, ki18n("%1: background type changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundColorStyle, PlotArea::BackgroundColorStyle, backgroundColorStyle, update) void CartesianPlotLegend::setBackgroundColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundColorStyle) - exec(new CartesianPlotLegendSetBackgroundColorStyleCmd(d, style, i18n("%1: background color style changed"))); + exec(new CartesianPlotLegendSetBackgroundColorStyleCmd(d, style, ki18n("%1: background color style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundImageStyle, PlotArea::BackgroundImageStyle, backgroundImageStyle, update) void CartesianPlotLegend::setBackgroundImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundImageStyle) - exec(new CartesianPlotLegendSetBackgroundImageStyleCmd(d, style, i18n("%1: background image style changed"))); + exec(new CartesianPlotLegendSetBackgroundImageStyleCmd(d, style, ki18n("%1: background image style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundBrushStyle, Qt::BrushStyle, backgroundBrushStyle, update) void CartesianPlotLegend::setBackgroundBrushStyle(Qt::BrushStyle style) { Q_D(CartesianPlotLegend); if (style != d->backgroundBrushStyle) - exec(new CartesianPlotLegendSetBackgroundBrushStyleCmd(d, style, i18n("%1: background brush style changed"))); + exec(new CartesianPlotLegendSetBackgroundBrushStyleCmd(d, style, ki18n("%1: background brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFirstColor, QColor, backgroundFirstColor, update) void CartesianPlotLegend::setBackgroundFirstColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundFirstColor) - exec(new CartesianPlotLegendSetBackgroundFirstColorCmd(d, color, i18n("%1: set background first color"))); + exec(new CartesianPlotLegendSetBackgroundFirstColorCmd(d, color, ki18n("%1: set background first color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundSecondColor, QColor, backgroundSecondColor, update) void CartesianPlotLegend::setBackgroundSecondColor(const QColor &color) { Q_D(CartesianPlotLegend); if (color!= d->backgroundSecondColor) - exec(new CartesianPlotLegendSetBackgroundSecondColorCmd(d, color, i18n("%1: set background second color"))); + exec(new CartesianPlotLegendSetBackgroundSecondColorCmd(d, color, ki18n("%1: set background second color"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundFileName, QString, backgroundFileName, update) void CartesianPlotLegend::setBackgroundFileName(const QString& fileName) { Q_D(CartesianPlotLegend); if (fileName!= d->backgroundFileName) - exec(new CartesianPlotLegendSetBackgroundFileNameCmd(d, fileName, i18n("%1: set background image"))); + exec(new CartesianPlotLegendSetBackgroundFileNameCmd(d, fileName, ki18n("%1: set background image"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBackgroundOpacity, float, backgroundOpacity, update) void CartesianPlotLegend::setBackgroundOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->backgroundOpacity) - exec(new CartesianPlotLegendSetBackgroundOpacityCmd(d, opacity, i18n("%1: set opacity"))); + exec(new CartesianPlotLegendSetBackgroundOpacityCmd(d, opacity, ki18n("%1: set opacity"))); } //Border STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderPen, QPen, borderPen, update) void CartesianPlotLegend::setBorderPen(const QPen &pen) { Q_D(CartesianPlotLegend); if (pen != d->borderPen) - exec(new CartesianPlotLegendSetBorderPenCmd(d, pen, i18n("%1: set border style"))); + exec(new CartesianPlotLegendSetBorderPenCmd(d, pen, ki18n("%1: set border style"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderCornerRadius, qreal, borderCornerRadius, update) void CartesianPlotLegend::setBorderCornerRadius(float radius) { Q_D(CartesianPlotLegend); if (radius != d->borderCornerRadius) - exec(new CartesianPlotLegendSetBorderCornerRadiusCmd(d, radius, i18n("%1: set border corner radius"))); + exec(new CartesianPlotLegendSetBorderCornerRadiusCmd(d, radius, ki18n("%1: set border corner radius"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetBorderOpacity, qreal, borderOpacity, update) void CartesianPlotLegend::setBorderOpacity(float opacity) { Q_D(CartesianPlotLegend); if (opacity != d->borderOpacity) - exec(new CartesianPlotLegendSetBorderOpacityCmd(d, opacity, i18n("%1: set border opacity"))); + exec(new CartesianPlotLegendSetBorderOpacityCmd(d, opacity, ki18n("%1: set border opacity"))); } //Layout STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutTopMargin, float, layoutTopMargin, retransform) void CartesianPlotLegend::setLayoutTopMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutTopMargin) - exec(new CartesianPlotLegendSetLayoutTopMarginCmd(d, margin, i18n("%1: set layout top margin"))); + exec(new CartesianPlotLegendSetLayoutTopMarginCmd(d, margin, ki18n("%1: set layout top margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutBottomMargin, float, layoutBottomMargin, retransform) void CartesianPlotLegend::setLayoutBottomMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutBottomMargin) - exec(new CartesianPlotLegendSetLayoutBottomMarginCmd(d, margin, i18n("%1: set layout bottom margin"))); + exec(new CartesianPlotLegendSetLayoutBottomMarginCmd(d, margin, ki18n("%1: set layout bottom margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutLeftMargin, float, layoutLeftMargin, retransform) void CartesianPlotLegend::setLayoutLeftMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutLeftMargin) - exec(new CartesianPlotLegendSetLayoutLeftMarginCmd(d, margin, i18n("%1: set layout left margin"))); + exec(new CartesianPlotLegendSetLayoutLeftMarginCmd(d, margin, ki18n("%1: set layout left margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutRightMargin, float, layoutRightMargin, retransform) void CartesianPlotLegend::setLayoutRightMargin(float margin) { Q_D(CartesianPlotLegend); if (margin != d->layoutRightMargin) - exec(new CartesianPlotLegendSetLayoutRightMarginCmd(d, margin, i18n("%1: set layout right margin"))); + exec(new CartesianPlotLegendSetLayoutRightMarginCmd(d, margin, ki18n("%1: set layout right margin"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutVerticalSpacing, float, layoutVerticalSpacing, retransform) void CartesianPlotLegend::setLayoutVerticalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutVerticalSpacing) - exec(new CartesianPlotLegendSetLayoutVerticalSpacingCmd(d, spacing, i18n("%1: set layout vertical spacing"))); + exec(new CartesianPlotLegendSetLayoutVerticalSpacingCmd(d, spacing, ki18n("%1: set layout vertical spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutHorizontalSpacing, float, layoutHorizontalSpacing, retransform) void CartesianPlotLegend::setLayoutHorizontalSpacing(float spacing) { Q_D(CartesianPlotLegend); if (spacing != d->layoutHorizontalSpacing) - exec(new CartesianPlotLegendSetLayoutHorizontalSpacingCmd(d, spacing, i18n("%1: set layout horizontal spacing"))); + exec(new CartesianPlotLegendSetLayoutHorizontalSpacingCmd(d, spacing, ki18n("%1: set layout horizontal spacing"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlotLegend, SetLayoutColumnCount, int, layoutColumnCount, retransform) void CartesianPlotLegend::setLayoutColumnCount(int count) { Q_D(CartesianPlotLegend); if (count != d->layoutColumnCount) - exec(new CartesianPlotLegendSetLayoutColumnCountCmd(d, count, i18n("%1: set layout column count"))); + exec(new CartesianPlotLegendSetLayoutColumnCountCmd(d, count, ki18n("%1: set layout column count"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlotLegend::visibilityChangedSlot() { Q_D(const CartesianPlotLegend); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## CartesianPlotLegendPrivate::CartesianPlotLegendPrivate(CartesianPlotLegend *owner):q(owner), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false) { setAcceptHoverEvents(true); } QString CartesianPlotLegendPrivate::name() const { return q->name(); } QRectF CartesianPlotLegendPrivate::boundingRect() const { return rect; } void CartesianPlotLegendPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } /*! Returns the shape of the CartesianPlotLegend as a QPainterPath in local coordinates */ QPainterPath CartesianPlotLegendPrivate::shape() const { QPainterPath path; if ( qFuzzyIsNull(borderCornerRadius) ) path.addRect(rect); else path.addRoundedRect(rect, borderCornerRadius, borderCornerRadius); return path; } bool CartesianPlotLegendPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the rectangular of the legend. */ void CartesianPlotLegendPrivate::retransform() { if (suppressRetransform) return; prepareGeometryChange(); curvesList.clear(); for (auto* curve : q->m_plot->children()) { if (curve && curve->isVisible()) curvesList.push_back(curve); } int curveCount = curvesList.size(); columnCount = (curveCount= curveCount ) break; curve = curvesList.at(index); if (curve) { if (!curve->isVisible()) continue; w = fm.width(curve->name()); if (w>maxTextWidth) maxTextWidth = w; } } maxColumnTextWidths.append(maxTextWidth); legendWidth += maxTextWidth; } legendWidth += layoutLeftMargin + layoutRightMargin; //margins legendWidth += columnCount*lineSymbolWidth + layoutHorizontalSpacing; //width of the columns without the text legendWidth += (columnCount-1)*2*layoutHorizontalSpacing; //spacings between the columns if (title->isVisible() && !title->text().text.isEmpty()) { float titleWidth = title->graphicsItem()->boundingRect().width(); if (titleWidth > legendWidth) legendWidth = titleWidth; } //determine the height of the legend float legendHeight = layoutTopMargin + layoutBottomMargin; //margins legendHeight += rowCount*h; //height of the rows legendHeight += (rowCount-1)*layoutVerticalSpacing; //spacing between the rows if (title->isVisible() && !title->text().text.isEmpty()) legendHeight += title->graphicsItem()->boundingRect().height(); //legend title rect.setX(-legendWidth/2); rect.setY(-legendHeight/2); rect.setWidth(legendWidth); rect.setHeight(legendHeight); updatePosition(); } /*! calculates the position of the legend, when the position relative to the parent was specified (left, right, etc.) */ void CartesianPlotLegendPrivate::updatePosition() { //position the legend relative to the actual plot size minus small offset //TODO: make the offset dependent on the size of axis ticks. const QRectF parentRect = q->m_plot->dataRect(); float hOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); float vOffset = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (position.horizontalPosition != CartesianPlotLegend::hPositionCustom) { if (position.horizontalPosition == CartesianPlotLegend::hPositionLeft) position.point.setX(parentRect.x() + rect.width()/2 + hOffset); else if (position.horizontalPosition == CartesianPlotLegend::hPositionCenter) position.point.setX(parentRect.x() + parentRect.width()/2); else if (position.horizontalPosition == CartesianPlotLegend::hPositionRight) position.point.setX(parentRect.x() + parentRect.width() - rect.width()/2 - hOffset); } if (position.verticalPosition != CartesianPlotLegend::vPositionCustom) { if (position.verticalPosition == CartesianPlotLegend::vPositionTop) position.point.setY(parentRect.y() + rect.height()/2 + vOffset); else if (position.verticalPosition == CartesianPlotLegend::vPositionCenter) position.point.setY(parentRect.y() + parentRect.height()/2); else if (position.verticalPosition == CartesianPlotLegend::vPositionBottom) position.point.setY(parentRect.y() + parentRect.height() - rect.height()/2 -vOffset); } suppressItemChangeEvent=true; setPos(position.point); suppressItemChangeEvent=false; emit q->positionChanged(position); suppressRetransform = true; title->retransform(); suppressRetransform = false; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the legend. \sa QGraphicsItem::paint(). */ void CartesianPlotLegendPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->save(); //draw the area painter->setOpacity(backgroundOpacity); painter->setPen(Qt::NoPen); if (backgroundType == PlotArea::Color) { switch (backgroundColorStyle) { case PlotArea::SingleColor:{ painter->setBrush(QBrush(backgroundFirstColor)); break; } case PlotArea::HorizontalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient:{ QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, backgroundFirstColor); linearGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient:{ QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, backgroundFirstColor); radialGrad.setColorAt(1, backgroundSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (backgroundType == PlotArea::Image) { if ( !backgroundFileName.trimmed().isEmpty() ) { QPixmap pix(backgroundFileName); switch (backgroundImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->drawPixmap(rect.topLeft(),pix); break; case PlotArea::Centered: painter->drawPixmap(QPointF(rect.center().x()-pix.size().width()/2,rect.center().y()-pix.size().height()/2),pix); break; case PlotArea::Tiled: painter->drawTiledPixmap(rect,pix); break; case PlotArea::CenterTiled: painter->drawTiledPixmap(rect,pix,QPoint(rect.size().width()/2,rect.size().height()/2)); } } } else if (backgroundType == PlotArea::Pattern) { painter->setBrush(QBrush(backgroundFirstColor,backgroundBrushStyle)); } if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); //draw the border if (borderPen.style() != Qt::NoPen) { painter->setPen(borderPen); painter->setBrush(Qt::NoBrush); painter->setOpacity(borderOpacity); if ( qFuzzyIsNull(borderCornerRadius) ) painter->drawRect(rect); else painter->drawRoundedRect(rect, borderCornerRadius, borderCornerRadius); } //draw curve's line+symbol and the names int curveCount = curvesList.size(); QFontMetrics fm(labelFont); float h=fm.ascent(); XYCurve* curve; painter->setFont(labelFont); //translate to left upper conner of the bounding rect plus the layout offset and the height of the title painter->translate(-rect.width()/2+layoutLeftMargin, -rect.height()/2+layoutTopMargin); if (title->isVisible() && !title->text().text.isEmpty()) painter->translate(0, title->graphicsItem()->boundingRect().height()); painter->save(); int index; for (int c=0; c= curveCount ) break; curve = curvesList.at(index); //curve's line (painted at the half of the ascent size) if (curve->lineType() != XYCurve::NoLine) { painter->setPen(curve->linePen()); painter->setOpacity(curve->lineOpacity()); painter->drawLine(0, h/2, lineSymbolWidth, h/2); } //error bars if ( (curve->xErrorType() != XYCurve::NoError && curve->xErrorPlusColumn()) || (curve->yErrorType() != XYCurve::NoError && curve->yErrorPlusColumn()) ) { painter->setOpacity(curve->errorBarsOpacity()); painter->setPen(curve->errorBarsPen()); //curve's error bars for x float errorBarsSize = Worksheet::convertToSceneUnits(10, Worksheet::Point); if (curve->symbolsStyle()!=Symbol::NoSymbols && errorBarsSizesymbolsSize()*1.4) errorBarsSize = curve->symbolsSize()*1.4; switch (curve->errorBarsType()) { case XYCurve::ErrorBarsSimple: //horiz. line if (curve->xErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line if (curve->yErrorType() != XYCurve::NoError) painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); break; case XYCurve::ErrorBarsWithEnds: //horiz. line if (curve->xErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //caps for the horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2-errorBarsSize/2, h/2+errorBarsSize/4); painter->drawLine(lineSymbolWidth/2+errorBarsSize/2, h/2-errorBarsSize/4, lineSymbolWidth/2+errorBarsSize/2, h/2+errorBarsSize/4); } //vert. line if (curve->yErrorType() != XYCurve::NoError) { painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); //caps for the vert. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2-errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2-errorBarsSize/2); painter->drawLine(lineSymbolWidth/2-errorBarsSize/4, h/2+errorBarsSize/2, lineSymbolWidth/2+errorBarsSize/4, h/2+errorBarsSize/2); } break; } } //curve's symbol if (curve->symbolsStyle()!=Symbol::NoSymbols) { painter->setOpacity(curve->symbolsOpacity()); painter->setBrush(curve->symbolsBrush()); painter->setPen(curve->symbolsPen()); QPainterPath path = Symbol::pathFromStyle(curve->symbolsStyle()); QTransform trafo; trafo.scale(curve->symbolsSize(), curve->symbolsSize()); path = trafo.map(path); if (curve->symbolsRotationAngle() != 0) { trafo.reset(); trafo.rotate(curve->symbolsRotationAngle()); path = trafo.map(path); } painter->translate(QPointF(lineSymbolWidth/2, h/2)); painter->drawPath(path); painter->translate(-QPointF(lineSymbolWidth/2, h/2)); } //curve's name painter->setPen(QPen(labelColor)); painter->setOpacity(1.0); //TODO: support HTML text? painter->drawText(QPoint(lineSymbolWidth+layoutHorizontalSpacing, h), curve->name()); painter->translate(0,layoutVerticalSpacing+h); } //translate to the beginning of the next column painter->restore(); int deltaX = lineSymbolWidth+layoutHorizontalSpacing+maxColumnTextWidths.at(c); //the width of the current columns deltaX += 2*layoutHorizontalSpacing; //spacing between two columns painter->translate(deltaX,0); painter->save(); } painter->restore(); painter->restore(); if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(shape()); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(shape()); } } QVariant CartesianPlotLegendPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //convert item's center point in parent's coordinates CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = value.toPointF(); tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. emit q->positionChanged(tempPosition); } return QGraphicsItem::itemChange(change, value); } void CartesianPlotLegendPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //convert position of the item in parent coordinates to label's position QPointF point = pos(); if (point!=position.point) { //position was changed -> set the position related member variables suppressRetransform = true; CartesianPlotLegend::PositionWrapper tempPosition; tempPosition.point = point; tempPosition.horizontalPosition = CartesianPlotLegend::hPositionCustom; tempPosition.verticalPosition = CartesianPlotLegend::vPositionCustom; q->setPosition(tempPosition); suppressRetransform = false; } QGraphicsItem::mouseReleaseEvent(event); } void CartesianPlotLegendPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void CartesianPlotLegendPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CartesianPlotLegend::save(QXmlStreamWriter* writer) const { Q_D(const CartesianPlotLegend); writer->writeStartElement( "cartesianPlotLegend" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_QCOLOR(d->labelColor); WRITE_QFONT(d->labelFont); writer->writeAttribute( "columnMajor", QString::number(d->labelColumnMajor) ); writer->writeAttribute( "lineSymbolWidth", QString::number(d->lineSymbolWidth) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //geometry writer->writeStartElement( "geometry" ); writer->writeAttribute( "x", QString::number(d->position.point.x()) ); writer->writeAttribute( "y", QString::number(d->position.point.y()) ); writer->writeAttribute( "horizontalPosition", QString::number(d->position.horizontalPosition) ); writer->writeAttribute( "verticalPosition", QString::number(d->position.verticalPosition) ); writer->writeEndElement(); //title d->title->save(writer); //background writer->writeStartElement( "background" ); writer->writeAttribute( "type", QString::number(d->backgroundType) ); writer->writeAttribute( "colorStyle", QString::number(d->backgroundColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->backgroundImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->backgroundBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->backgroundFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->backgroundFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->backgroundFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->backgroundSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->backgroundSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->backgroundSecondColor.blue()) ); writer->writeAttribute( "fileName", d->backgroundFileName ); writer->writeAttribute( "opacity", QString::number(d->backgroundOpacity) ); writer->writeEndElement(); //border writer->writeStartElement( "border" ); WRITE_QPEN(d->borderPen); writer->writeAttribute( "borderOpacity", QString::number(d->borderOpacity) ); writer->writeEndElement(); //layout writer->writeStartElement( "layout" ); writer->writeAttribute( "topMargin", QString::number(d->layoutTopMargin) ); writer->writeAttribute( "bottomMargin", QString::number(d->layoutBottomMargin) ); writer->writeAttribute( "leftMargin", QString::number(d->layoutLeftMargin) ); writer->writeAttribute( "rightMargin", QString::number(d->layoutRightMargin) ); writer->writeAttribute( "verticalSpacing", QString::number(d->layoutVerticalSpacing) ); writer->writeAttribute( "horizontalSpacing", QString::number(d->layoutHorizontalSpacing) ); writer->writeAttribute( "columnCount", QString::number(d->layoutColumnCount) ); writer->writeEndElement(); writer->writeEndElement(); // close "cartesianPlotLegend" section } //! Load from XML bool CartesianPlotLegend::load(XmlStreamReader* reader, bool preview) { Q_D(CartesianPlotLegend); if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "cartesianPlotLegend") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_QCOLOR(d->labelColor); READ_QFONT(d->labelFont); str = attribs.value("columnMajor").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'columnMajor'")); else d->labelColumnMajor = str.toInt(); str = attribs.value("lineSymbolWidth").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'lineSymbolWidth'")); else d->lineSymbolWidth = str.toDouble(); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.point.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.point.setY(str.toDouble()); str = attribs.value("horizontalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'horizontalPosition'")); else d->position.horizontalPosition = (CartesianPlotLegend::HorizontalPosition)str.toInt(); str = attribs.value("verticalPosition").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'verticalPosition'")); else d->position.verticalPosition = (CartesianPlotLegend::VerticalPosition)str.toInt(); } else if (reader->name() == "textLabel") { if (!d->title->load(reader, preview)) { delete d->title; d->title=0; return false; } } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); str = attribs.value("type").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->backgroundType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->backgroundColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->backgroundImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->backgroundBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->backgroundFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->backgroundFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->backgroundFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->backgroundSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->backgroundSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->backgroundSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->backgroundFileName = str; str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->backgroundOpacity = str.toDouble(); } else if (!preview && reader->name() == "border") { attribs = reader->attributes(); READ_QPEN(d->borderPen); str = attribs.value("borderOpacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("borderOpacity")); else d->borderOpacity = str.toDouble(); } else if (!preview && reader->name() == "layout") { attribs = reader->attributes(); str = attribs.value("topMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("topMargin")); else d->layoutTopMargin = str.toDouble(); str = attribs.value("bottomMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("bottomMargin")); else d->layoutBottomMargin = str.toDouble(); str = attribs.value("leftMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("leftMargin")); else d->layoutLeftMargin = str.toDouble(); str = attribs.value("rightMargin").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("rightMargin")); else d->layoutRightMargin = str.toDouble(); str = attribs.value("verticalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("verticalSpacing")); else d->layoutVerticalSpacing = str.toDouble(); str = attribs.value("horizontalSpacing").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("horizontalSpacing")); else d->layoutHorizontalSpacing = str.toDouble(); str = attribs.value("columnCount").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("columnCount")); else d->layoutColumnCount = str.toInt(); } } return true; } void CartesianPlotLegend::loadThemeConfig(const KConfig& config) { KConfigGroup groupLabel = config.group("Label"); this->setLabelColor(groupLabel.readEntry("FontColor", QColor(Qt::white))); const KConfigGroup group = config.group("CartesianPlot"); this->setBackgroundBrushStyle((Qt::BrushStyle)group.readEntry("BackgroundBrushStyle",(int) this->backgroundBrushStyle())); this->setBackgroundColorStyle((PlotArea::BackgroundColorStyle)(group.readEntry("BackgroundColorStyle",(int) this->backgroundColorStyle()))); this->setBackgroundFirstColor(group.readEntry("BackgroundFirstColor",(QColor) this->backgroundFirstColor())); this->setBackgroundImageStyle((PlotArea::BackgroundImageStyle)group.readEntry("BackgroundImageStyle",(int) this->backgroundImageStyle())); this->setBackgroundOpacity(group.readEntry("BackgroundOpacity", this->backgroundOpacity())); this->setBackgroundSecondColor(group.readEntry("BackgroundSecondColor",(QColor) this->backgroundSecondColor())); this->setBackgroundType((PlotArea::BackgroundType)(group.readEntry("BackgroundType",(int) this->backgroundType()))); this->borderPen().setColor(group.readEntry("BorderColor",(QColor) this->borderPen().color())); this->setBorderCornerRadius(group.readEntry("BorderCornerRadius", this->borderCornerRadius())); this->setBorderOpacity(group.readEntry("BorderOpacity", this->borderOpacity())); this->borderPen().setStyle((Qt::PenStyle)(group.readEntry("BorderStyle", (int) this->borderPen().style()))); this->borderPen().setWidthF(group.readEntry("BorderWidth", this->borderPen().widthF())); title()->loadThemeConfig(config); } diff --git a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp index bbcf7f4ad..914f9dda5 100644 --- a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp +++ b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp @@ -1,493 +1,493 @@ /*************************************************************************** File : CustomPoint.cpp Project : LabPlot Description : Custom user-defined point on the plot -------------------------------------------------------------------- Copyright : (C) 2015 Ankit Wagadre (wagadre.ankit@gmail.com) Copyright : (C) 2015 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #include "CustomPoint.h" #include "CustomPointPrivate.h" #include "backend/worksheet/Worksheet.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/XmlStreamReader.h" #include #include #include #include #include #include /** * \class CustomPoint * \brief A customizable point. * * The position can be either specified by mouse events or by providing the * x- and y- coordinates in parent's coordinate system */ CustomPoint::CustomPoint(const CartesianPlot* plot, const QString& name):WorksheetElement(name), d_ptr(new CustomPointPrivate(this,plot)) { init(); } CustomPoint::CustomPoint(const QString& name, CustomPointPrivate* dd):WorksheetElement(name), d_ptr(dd) { init(); } CustomPoint::~CustomPoint() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void CustomPoint::init() { Q_D(CustomPoint); KConfig config; KConfigGroup group; group = config.group("CustomPoint"); d->position.setX( group.readEntry("PositionXValue", d->plot->xMin() + (d->plot->xMax()-d->plot->xMin())/2) ); d->position.setY( group.readEntry("PositionYValue", d->plot->yMin() + (d->plot->yMax()-d->plot->yMin())/2) ); d->symbolStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::Circle); d->symbolSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->symbolRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::red)) ); d->symbolPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); d->symbolPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); this->initActions(); retransform(); } void CustomPoint::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &CustomPoint::visibilityChanged); } /*! Returns an icon to be used in the project explorer. */ QIcon CustomPoint::icon() const { return QIcon::fromTheme("draw-cross"); } QMenu* CustomPoint::createContextMenu() { QMenu* menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } QGraphicsItem* CustomPoint::graphicsItem() const { return d_ptr; } void CustomPoint::retransform() { Q_D(CustomPoint); d->retransform(); } void CustomPoint::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(horizontalRatio); Q_UNUSED(verticalRatio); Q_UNUSED(pageResize); } /* ============================ getter methods ================= */ CLASS_SHARED_D_READER_IMPL(CustomPoint, QPointF, position, position) //symbols BASIC_SHARED_D_READER_IMPL(CustomPoint, Symbol::Style, symbolStyle, symbolStyle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolOpacity, symbolOpacity) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolRotationAngle, symbolRotationAngle) BASIC_SHARED_D_READER_IMPL(CustomPoint, qreal, symbolSize, symbolSize) CLASS_SHARED_D_READER_IMPL(CustomPoint, QBrush, symbolBrush, symbolBrush) CLASS_SHARED_D_READER_IMPL(CustomPoint, QPen, symbolPen, symbolPen) /* ============================ setter methods and undo commands ================= */ STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetPosition, QPointF, position, retransform) void CustomPoint::setPosition(const QPointF& position) { Q_D(CustomPoint); if (position != d->position) - exec(new CustomPointSetPositionCmd(d, position, i18n("%1: set position"))); + exec(new CustomPointSetPositionCmd(d, position, ki18n("%1: set position"))); } //Symbol STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolStyle, Symbol::Style, symbolStyle, retransform) void CustomPoint::setSymbolStyle(Symbol::Style style) { Q_D(CustomPoint); if (style != d->symbolStyle) - exec(new CustomPointSetSymbolStyleCmd(d, style, i18n("%1: set symbol style"))); + exec(new CustomPointSetSymbolStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolSize, qreal, symbolSize, retransform) void CustomPoint::setSymbolSize(qreal size) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + size, 1 + d->symbolSize)) - exec(new CustomPointSetSymbolSizeCmd(d, size, i18n("%1: set symbol size"))); + exec(new CustomPointSetSymbolSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolRotationAngle, qreal, symbolRotationAngle, retransform) void CustomPoint::setSymbolRotationAngle(qreal angle) { Q_D(CustomPoint); if (!qFuzzyCompare(1 + angle, 1 + d->symbolRotationAngle)) - exec(new CustomPointSetSymbolRotationAngleCmd(d, angle, i18n("%1: rotate symbols"))); + exec(new CustomPointSetSymbolRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolBrush, QBrush, symbolBrush, update) void CustomPoint::setSymbolBrush(const QBrush &brush) { Q_D(CustomPoint); if (brush != d->symbolBrush) - exec(new CustomPointSetSymbolBrushCmd(d, brush, i18n("%1: set symbol filling"))); + exec(new CustomPointSetSymbolBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolPen, QPen, symbolPen, update) void CustomPoint::setSymbolPen(const QPen &pen) { Q_D(CustomPoint); if (pen != d->symbolPen) - exec(new CustomPointSetSymbolPenCmd(d, pen, i18n("%1: set symbol outline style"))); + exec(new CustomPointSetSymbolPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(CustomPoint, SetSymbolOpacity, qreal, symbolOpacity, update) void CustomPoint::setSymbolOpacity(qreal opacity) { Q_D(CustomPoint); if (opacity != d->symbolOpacity) - exec(new CustomPointSetSymbolOpacityCmd(d, opacity, i18n("%1: set symbol opacity"))); + exec(new CustomPointSetSymbolOpacityCmd(d, opacity, ki18n("%1: set symbol opacity"))); } STD_SWAP_METHOD_SETTER_CMD_IMPL_F(CustomPoint, SetVisible, bool, swapVisible, retransform); void CustomPoint::setVisible(bool on) { Q_D(CustomPoint); - exec(new CustomPointSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new CustomPointSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool CustomPoint::isVisible() const { Q_D(const CustomPoint); return d->isVisible(); } void CustomPoint::setPrinting(bool on) { Q_D(CustomPoint); d->m_printing = on; } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CustomPoint::visibilityChanged() { Q_D(const CustomPoint); this->setVisible(!d->isVisible()); } //############################################################################## //####################### Private implementation ############################### //############################################################################## CustomPointPrivate::CustomPointPrivate(CustomPoint* owner, const CartesianPlot* p) : plot(p), suppressItemChangeEvent(false), suppressRetransform(false), m_printing(false), m_hovered(false), m_visible(true), q(owner) { setFlag(QGraphicsItem::ItemSendsGeometryChanges); setFlag(QGraphicsItem::ItemIsMovable); setFlag(QGraphicsItem::ItemIsSelectable); setAcceptHoverEvents(true); } QString CustomPointPrivate::name() const { return q->name(); } /*! calculates the position and the bounding box of the item/point. Called on geometry or properties changes. */ void CustomPointPrivate::retransform() { if (suppressRetransform) return; //calculate the point in the scene coordinates const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); QVector list, listScene; list<mapLogicalToScene(list, CartesianCoordinateSystem::DefaultMapping); if (!listScene.isEmpty()) { m_visible = true; positionScene = listScene.at(0); suppressItemChangeEvent=true; setPos(positionScene); suppressItemChangeEvent=false; } else { m_visible = false; } recalcShapeAndBoundingRect(); } bool CustomPointPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->changed(); emit q->visibleChanged(on); return oldValue; } /*! Returns the outer bounds of the item as a rectangle. */ QRectF CustomPointPrivate::boundingRect() const { return transformedBoundingRectangle; } /*! Returns the shape of this item as a QPainterPath in local coordinates. */ QPainterPath CustomPointPrivate::shape() const { return pointShape; } /*! recalculates the outer bounds and the shape of the item. */ void CustomPointPrivate::recalcShapeAndBoundingRect() { prepareGeometryChange(); pointShape = QPainterPath(); if (m_visible && symbolStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolStyle); QTransform trafo; trafo.scale(symbolSize, symbolSize); path = trafo.map(path); trafo.reset(); if (symbolRotationAngle != 0) { trafo.rotate(symbolRotationAngle); path = trafo.map(path); } pointShape = trafo.map(path); transformedBoundingRectangle = pointShape.boundingRect(); } } void CustomPointPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option) Q_UNUSED(widget) if (!m_visible) return; if (symbolStyle != Symbol::NoSymbols) { painter->setOpacity(symbolOpacity); painter->setPen(symbolPen); painter->setBrush(symbolBrush); painter->drawPath(pointShape); } if (m_hovered && !isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Shadow), 2, Qt::SolidLine)); painter->drawPath(pointShape); } if (isSelected() && !m_printing){ painter->setPen(QPen(QApplication::palette().color(QPalette::Highlight), 2, Qt::SolidLine)); painter->drawPath(pointShape); } } QVariant CustomPointPrivate::itemChange(GraphicsItemChange change, const QVariant &value) { if (suppressItemChangeEvent) return value; if (change == QGraphicsItem::ItemPositionChange) { //emit the signals in order to notify the UI. //we don't set the position related member variables during the mouse movements. //this is done on mouse release events only. const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); emit q->positionChanged(cSystem->mapSceneToLogical(value.toPointF())); } return QGraphicsItem::itemChange(change, value); } void CustomPointPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { //position was changed -> set the position member variables suppressRetransform = true; const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); q->setPosition(cSystem->mapSceneToLogical(pos())); suppressRetransform = false; QGraphicsItem::mouseReleaseEvent(event); } void CustomPointPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void CustomPointPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void CustomPointPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CustomPoint::save(QXmlStreamWriter* writer) const { Q_D(const CustomPoint); writer->writeStartElement("customPoint"); writeBasicAttributes(writer); writeCommentElement(writer); //geometry writer->writeStartElement("geometry"); writer->writeAttribute( "x", QString::number(d->position.x()) ); writer->writeAttribute( "y", QString::number(d->position.y()) ); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Symbols writer->writeStartElement("symbol"); writer->writeAttribute( "symbolStyle", QString::number(d->symbolStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolSize) ); WRITE_QBRUSH(d->symbolBrush); WRITE_QPEN(d->symbolPen); writer->writeEndElement(); writer->writeEndElement(); // close "CustomPoint" section } //! Load from XML bool CustomPoint::load(XmlStreamReader* reader, bool preview) { Q_D(CustomPoint); if (!reader->isStartElement() || reader->name() != "customPoint") { reader->raiseError(i18n("no custom point element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "customPoint") break; if (!reader->isStartElement()) continue; if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "geometry") { attribs = reader->attributes(); str = attribs.value("x").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'x'")); else d->position.setX(str.toDouble()); str = attribs.value("y").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'y'")); else d->position.setY(str.toDouble()); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "symbol") { attribs = reader->attributes(); str = attribs.value("symbolStyle").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'symbolStyle'")); else d->symbolStyle = (Symbol::Style)str.toInt(); str = attribs.value("opacity").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->symbolOpacity = str.toDouble(); str = attribs.value("rotation").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->symbolRotationAngle = str.toDouble(); str = attribs.value("size").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'size'")); else d->symbolSize = str.toDouble(); READ_QBRUSH(d->symbolBrush); READ_QPEN(d->symbolPen); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } retransform(); return true; } diff --git a/src/backend/worksheet/plots/cartesian/Histogram.cpp b/src/backend/worksheet/plots/cartesian/Histogram.cpp index 59a3789b6..a37230e26 100644 --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -1,1593 +1,1593 @@ /*************************************************************************** File : Histogram.cpp Project : LabPlot Description : Histogram -------------------------------------------------------------------- Copyright : (C) 2016 Anu Mittal (anu22mittal@gmail.com) Copyright : (C) 2016-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class Histogram \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "Histogram.h" #include "HistogramPrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/AbstractCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "tools/ImageTools.h" #include #include #include #include #include extern "C" { #include #include #include } Histogram::Histogram(const QString &name) : WorksheetElement(name), d_ptr(new HistogramPrivate(this)) { init(); } Histogram::Histogram(const QString &name, HistogramPrivate *dd) : WorksheetElement(name), d_ptr(dd) { init(); } void Histogram::init() { Q_D(Histogram); KConfig config; KConfigGroup group = config.group("Histogram"); d->xColumn = NULL; d->histogramType = (Histogram::HistogramType) group.readEntry("histogramType", (int)Histogram::Ordinary); d->binsOption = (Histogram::BinsOption) group.readEntry("binOption", (int)Histogram::Number); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->valuesType = (Histogram::ValuesType) group.readEntry("ValuesType", (int)Histogram::NoValues); d->valuesColumn = NULL; d->valuesPosition = (Histogram::ValuesPosition) group.readEntry("ValuesPosition", (int)Histogram::ValuesAbove); d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (Histogram::FillingPosition) group.readEntry("FillingPosition", (int)Histogram::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); this->initActions(); } void Histogram::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, &QAction::triggered, this, &Histogram::visibilityChangedSlot); } QMenu* Histogram::createContextMenu() { QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon Histogram::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* Histogram::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(Histogram, SetVisible, bool, swapVisible) void Histogram::setVisible(bool on) { Q_D(Histogram); - exec(new HistogramSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new HistogramSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool Histogram::isVisible() const { Q_D(const Histogram); return d->isVisible(); } void Histogram::setPrinting(bool on) { Q_D(Histogram); d->m_printing = on; } void Histogram::setHistrogramType(Histogram::HistogramType histogramType) { d_ptr->histogramType = histogramType; DEBUG(histogramType); } Histogram::HistogramType Histogram::getHistrogramType() { return d_ptr->histogramType; } void Histogram::setbinsOption(Histogram::BinsOption binsOption) { d_ptr->histogramData.binsOption = binsOption; } void Histogram::setBinValue(int binValue) { d_ptr->histogramData.binValue= binValue; } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn*, xColumn, xColumn) QString& Histogram::xColumnPath() const { return d_ptr->xColumnPath; } CLASS_SHARED_D_READER_IMPL(Histogram, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::HistogramData, histogramData, histogramData) //values BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(Histogram, const AbstractColumn *, valuesColumn, valuesColumn) QString& Histogram::valuesColumnPath() const { return d_ptr->valuesColumnPath; } BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(Histogram, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(Histogram, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(Histogram, Histogram::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(Histogram, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(Histogram, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(Histogram, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(Histogram, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(Histogram, qreal, fillingOpacity, fillingOpacity) double Histogram::getYMaximum() const { return d_ptr->getYMaximum(); } bool Histogram::isSourceDataChangedSinceLastPlot() const { Q_D(const Histogram); return d->sourceDataChangedSinceLastPlot; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(Histogram, SetHistogramData, Histogram::HistogramData, histogramData, recalculate); void Histogram::setHistogramData(const Histogram::HistogramData& histogramData) { Q_D(Histogram); if ((histogramData.binValue != d->histogramData.binValue) || (histogramData.binsOption != d->histogramData.binsOption) ) - exec(new HistogramSetHistogramDataCmd(d, histogramData, i18n("%1: set equation"))); + exec(new HistogramSetHistogramDataCmd(d, histogramData, ki18n("%1: set equation"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetXColumn, const AbstractColumn*, xColumn, retransform) void Histogram::setXColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->xColumn) { - exec(new HistogramSetXColumnCmd(d, column, i18n("%1: assign x values"))); + exec(new HistogramSetXColumnCmd(d, column, ki18n("%1: assign x values"))); emit sourceDataChangedSinceLastPlot(); //emit xHistogramDataChanged() in order to notify the plot about the changes emit xHistogramDataChanged(); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::xHistogramDataChanged); connect(column, &AbstractColumn::dataChanged, this, &Histogram::handleSourceDataChanged); //update the curve itself on changes connect(column, &AbstractColumn::dataChanged, this, &Histogram::retransform); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::xColumnAboutToBeRemoved); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void Histogram::setLinePen(const QPen &pen) { Q_D(Histogram); if (pen != d->linePen) - exec(new HistogramSetLinePenCmd(d, pen, i18n("%1: set line style"))); + exec(new HistogramSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesType, Histogram::ValuesType, valuesType, updateValues) void Histogram::setValuesType(Histogram::ValuesType type) { Q_D(Histogram); if (type != d->valuesType) - exec(new HistogramSetValuesTypeCmd(d, type, i18n("%1: set values type"))); + exec(new HistogramSetValuesTypeCmd(d, type, ki18n("%1: set values type"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColumn, const AbstractColumn*, valuesColumn, updateValues) void Histogram::setValuesColumn(const AbstractColumn* column) { Q_D(Histogram); if (column != d->valuesColumn) { - exec(new HistogramSetValuesColumnCmd(d, column, i18n("%1: set values column"))); + exec(new HistogramSetValuesColumnCmd(d, column, ki18n("%1: set values column"))); if (column) { connect(column, &AbstractColumn::dataChanged, this, &Histogram::updateValues); connect(column->parentAspect(), &AbstractAspect::aspectAboutToBeRemoved, this, &Histogram::valuesColumnAboutToBeRemoved); } } } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPosition, Histogram::ValuesPosition, valuesPosition, updateValues) void Histogram::setValuesPosition(ValuesPosition position) { Q_D(Histogram); if (position != d->valuesPosition) - exec(new HistogramSetValuesPositionCmd(d, position, i18n("%1: set values position"))); + exec(new HistogramSetValuesPositionCmd(d, position, ki18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesDistance, qreal, valuesDistance, updateValues) void Histogram::setValuesDistance(qreal distance) { Q_D(Histogram); if (distance != d->valuesDistance) - exec(new HistogramSetValuesDistanceCmd(d, distance, i18n("%1: set values distance"))); + exec(new HistogramSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void Histogram::setValuesRotationAngle(qreal angle) { Q_D(Histogram); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) - exec(new HistogramSetValuesRotationAngleCmd(d, angle, i18n("%1: rotate values"))); + exec(new HistogramSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void Histogram::setValuesOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->valuesOpacity) - exec(new HistogramSetValuesOpacityCmd(d, opacity, i18n("%1: set values opacity"))); + exec(new HistogramSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity"))); } //TODO: Format, Precision STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesPrefix, QString, valuesPrefix, updateValues) void Histogram::setValuesPrefix(const QString& prefix) { Q_D(Histogram); if (prefix!= d->valuesPrefix) - exec(new HistogramSetValuesPrefixCmd(d, prefix, i18n("%1: set values prefix"))); + exec(new HistogramSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesSuffix, QString, valuesSuffix, updateValues) void Histogram::setValuesSuffix(const QString& suffix) { Q_D(Histogram); if (suffix!= d->valuesSuffix) - exec(new HistogramSetValuesSuffixCmd(d, suffix, i18n("%1: set values suffix"))); + exec(new HistogramSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesFont, QFont, valuesFont, updateValues) void Histogram::setValuesFont(const QFont& font) { Q_D(Histogram); if (font!= d->valuesFont) - exec(new HistogramSetValuesFontCmd(d, font, i18n("%1: set values font"))); + exec(new HistogramSetValuesFontCmd(d, font, ki18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetValuesColor, QColor, valuesColor, updatePixmap) void Histogram::setValuesColor(const QColor& color) { Q_D(Histogram); if (color != d->valuesColor) - exec(new HistogramSetValuesColorCmd(d, color, i18n("%1: set values color"))); + exec(new HistogramSetValuesColorCmd(d, color, ki18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingPosition, Histogram::FillingPosition, fillingPosition, updateFilling) void Histogram::setFillingPosition(FillingPosition position) { Q_D(Histogram); if (position != d->fillingPosition) - exec(new HistogramSetFillingPositionCmd(d, position, i18n("%1: filling position changed"))); + exec(new HistogramSetFillingPositionCmd(d, position, ki18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void Histogram::setFillingType(PlotArea::BackgroundType type) { Q_D(Histogram); if (type != d->fillingType) - exec(new HistogramSetFillingTypeCmd(d, type, i18n("%1: filling type changed"))); + exec(new HistogramSetFillingTypeCmd(d, type, ki18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void Histogram::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(Histogram); if (style != d->fillingColorStyle) - exec(new HistogramSetFillingColorStyleCmd(d, style, i18n("%1: filling color style changed"))); + exec(new HistogramSetFillingColorStyleCmd(d, style, ki18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void Histogram::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(Histogram); if (style != d->fillingImageStyle) - exec(new HistogramSetFillingImageStyleCmd(d, style, i18n("%1: filling image style changed"))); + exec(new HistogramSetFillingImageStyleCmd(d, style, ki18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void Histogram::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(Histogram); if (style != d->fillingBrushStyle) - exec(new HistogramSetFillingBrushStyleCmd(d, style, i18n("%1: filling brush style changed"))); + exec(new HistogramSetFillingBrushStyleCmd(d, style, ki18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void Histogram::setFillingFirstColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingFirstColor) - exec(new HistogramSetFillingFirstColorCmd(d, color, i18n("%1: set filling first color"))); + exec(new HistogramSetFillingFirstColorCmd(d, color, ki18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void Histogram::setFillingSecondColor(const QColor& color) { Q_D(Histogram); if (color!= d->fillingSecondColor) - exec(new HistogramSetFillingSecondColorCmd(d, color, i18n("%1: set filling second color"))); + exec(new HistogramSetFillingSecondColorCmd(d, color, ki18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingFileName, QString, fillingFileName, updatePixmap) void Histogram::setFillingFileName(const QString& fileName) { Q_D(Histogram); if (fileName!= d->fillingFileName) - exec(new HistogramSetFillingFileNameCmd(d, fileName, i18n("%1: set filling image"))); + exec(new HistogramSetFillingFileNameCmd(d, fileName, ki18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(Histogram, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void Histogram::setFillingOpacity(qreal opacity) { Q_D(Histogram); if (opacity != d->fillingOpacity) - exec(new HistogramSetFillingOpacityCmd(d, opacity, i18n("%1: set filling opacity"))); + exec(new HistogramSetFillingOpacityCmd(d, opacity, ki18n("%1: set filling opacity"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void Histogram::retransform() { d_ptr->retransform(); } void Histogram::handleSourceDataChanged() { Q_D(Histogram); d->sourceDataChangedSinceLastPlot = true; emit sourceDataChangedSinceLastPlot(); } //TODO void Histogram::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_UNUSED(verticalRatio); Q_D(const Histogram); //setValuesDistance(d->distance*); QFont font=d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); retransform(); } void Histogram::updateValues() { d_ptr->updateValues(); } void Histogram::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->xColumn) { d->xColumn = 0; d->retransform(); } } void Histogram::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(Histogram); if (aspect == d->valuesColumn) { d->valuesColumn = 0; d->updateValues(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void Histogram::visibilityChangedSlot() { Q_D(const Histogram); this->setVisible(!d->isVisible()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## HistogramPrivate::HistogramPrivate(Histogram *owner) : m_printing(false), m_hovered(false), m_suppressRecalc(false), m_suppressRetransform(false), m_hoverEffectImageIsDirty(false), m_selectionEffectImageIsDirty(false), q(owner) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); } QString HistogramPrivate::name() const { return q->name(); } QRectF HistogramPrivate::boundingRect() const { return boundingRectangle; } double HistogramPrivate::getYMaximum() { if (histogram) { double yMaxRange = 0.0; switch(histogramType) { case Histogram::Ordinary: { size_t maxYAddes = gsl_histogram_max_bin(histogram); yMaxRange = gsl_histogram_get(histogram, maxYAddes); break; } case Histogram::Cumulative: { yMaxRange = xColumn->rowCount(); break; } case Histogram::AvgShift: { //TODO } } return yMaxRange; } return -INFINITY; } /*! Returns the shape of the Histogram as a QPainterPath in local coordinates */ QPainterPath HistogramPrivate::shape() const { return curveShape; } void HistogramPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } bool HistogramPrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the position of the points to be drawn. Called when the data was changed. Triggers the update of lines, drop lines, symbols etc. */ void HistogramPrivate::retransform() { if (m_suppressRetransform) return; //qDebug()<<"HistogramPrivate::retransform() " << q->name(); symbolPointsLogical.clear(); symbolPointsScene.clear(); connectedPointsLogical.clear(); if (NULL == xColumn) { linePath = QPainterPath(); valuesPath = QPainterPath(); // dropLinePath = QPainterPath(); recalcShapeAndBoundingRect(); return; } int startRow = 0; int endRow = xColumn->rowCount() - 1; QPointF tempPoint; AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); //take over only valid and non masked points. for (int row = startRow; row <= endRow; row++ ) { if (xColumn->isValid(row) && !xColumn->isMasked(row)) { switch(xColMode) { case AbstractColumn::Numeric: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::Integer: //TODO case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); } else { if (connectedPointsLogical.size()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } //calculate the scene coordinates const AbstractPlot* plot = dynamic_cast(q->parentAspect()); if (!plot) return; const CartesianCoordinateSystem* cSystem = dynamic_cast(plot->coordinateSystem()); Q_ASSERT(cSystem); visiblePoints = std::vector(symbolPointsLogical.count(), false); cSystem->mapLogicalToScene(symbolPointsLogical, symbolPointsScene, visiblePoints); m_suppressRecalc = true; updateLines(); updateValues(); m_suppressRecalc = false; } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. */ void HistogramPrivate::updateLines() { linePath = QPainterPath(); lines.clear(); const int count=symbolPointsLogical.count(); //nothing to do, if no data points available if (count<=1) { recalcShapeAndBoundingRect(); return; } int startRow = 0; int endRow = xColumn->rowCount() - 1; QPointF tempPoint,tempPoint1; double xAxisMin = xColumn->minimum(); double xAxisMax = xColumn->maximum(); switch (histogramData.binsOption) { case Histogram::Number: bins = (size_t)histogramData.binValue; break; case Histogram::SquareRoot: bins = (size_t)sqrt(histogramData.binValue); break; case Histogram::RiceRule: bins = (size_t)2*cbrt(histogramData.binValue); break; case Histogram::Width: bins = (size_t) (xAxisMax-xAxisMin)/histogramData.binValue; break; case Histogram::SturgisRule: bins =(size_t) 1 + 3.33*log(histogramData.binValue); break; } double width = (xAxisMax-xAxisMin)/bins; histogram = gsl_histogram_alloc (bins); // demo- number of bins gsl_histogram_set_ranges_uniform (histogram, xAxisMin,xAxisMax+1); //checking height of each column /*for(int i=0;i < bins; ++i) { qDebug() <isValid(row) && !xColumn->isMasked(row) ) gsl_histogram_increment(histogram, xColumn->valueAt(row)); } for(size_t i=0; i < bins; ++i) { tempPoint.setX(xAxisMin); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(gsl_histogram_get(histogram,i)); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin); tempPoint.setY(gsl_histogram_get(histogram,i)); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(gsl_histogram_get(histogram,i)); lines.append(QLineF(tempPoint,tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(gsl_histogram_get(histogram,i)); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); xAxisMin+= width; } break; } case Histogram::Cumulative: { double point =0.0; for (int row = startRow; row <= endRow; row++ ) { if ( xColumn->isValid(row) && !xColumn->isMasked(row)) gsl_histogram_increment(histogram, xColumn->valueAt(row)); } for(size_t i=0; i < bins; ++i) { point+= gsl_histogram_get(histogram,i); tempPoint.setX(xAxisMin); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(point); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin); tempPoint.setY(point); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(point); lines.append(QLineF(tempPoint,tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(point); tempPoint1.setX(xAxisMin+width); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); tempPoint.setX(xAxisMin+width); tempPoint.setY(0.0); tempPoint1.setX(xAxisMin); tempPoint1.setY(0.0); lines.append(QLineF(tempPoint, tempPoint1)); xAxisMin+= width; } break; } case Histogram::AvgShift: { //TODO break; } } //calculate the lines connecting the data points for (int i = 0; i(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); lines = cSystem->mapLogicalToScene(lines); //new line path for (const auto& line: lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } updateFilling(); recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void HistogramPrivate::updateValues() { valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == Histogram::NoValues) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot if (valuesType == Histogram::ValuesY || valuesType == Histogram::ValuesYBracketed) { switch(histogramType) { case Histogram::Ordinary: for(size_t i=0; irowCount()); const AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::Numeric: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; case AbstractColumn::Integer: case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h = fm.ascent(); double xAxisMin = xColumn->minimum(); double xAxisMax = xColumn->maximum(); double width = (xAxisMax-xAxisMin)/bins; switch(valuesPosition) { case Histogram::ValuesAbove: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() -w/2 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y() - valuesDistance ); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesUnder: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() -w/2+xAxisMin ); tempPoint.setY( symbolPointsScene.at(i).y() + valuesDistance + h/2); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesLeft: for (int i = 0; i < valuesStrings.size(); i++) { w=fm.width(valuesStrings.at(i)); tempPoint.setX( symbolPointsScene.at(i).x() - valuesDistance - w - 1 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y()); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; case Histogram::ValuesRight: for (int i = 0; i < valuesStrings.size(); i++) { tempPoint.setX( symbolPointsScene.at(i).x() + valuesDistance - 1 +xAxisMin); tempPoint.setY( symbolPointsScene.at(i).y() ); valuesPoints.append(tempPoint); xAxisMin+= 9*width; } break; } QTransform trafo; QPainterPath path; for (int i = 0; i < valuesPoints.size(); i++) { path = QPainterPath(); path.addText( QPoint(0,0), valuesFont, valuesStrings.at(i) ); trafo.reset(); trafo.translate( valuesPoints.at(i).x(), valuesPoints.at(i).y() ); if (valuesRotationAngle!=0) trafo.rotate( -valuesRotationAngle ); valuesPath.addPath(trafo.map(path)); } recalcShapeAndBoundingRect(); } void HistogramPrivate::updateFilling() { fillPolygons.clear(); if (fillingPosition==Histogram::NoFilling) { recalcShapeAndBoundingRect(); return; } QVector fillLines; const CartesianPlot* plot = dynamic_cast(q->parentAspect()); const AbstractCoordinateSystem* cSystem = plot->coordinateSystem(); //if there're no interpolation lines available (Histogram::NoLine selected), create line-interpolation, //use already available lines otherwise. if (lines.size()) fillLines = lines; else { for (int i=0; imapLogicalToScene(fillLines); } //no lines available (no points), nothing to do if (!fillLines.size()) return; //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //starting point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//first point of the curve, may not be visible currently QPointF edge; float xEnd=0, yEnd=0; if (fillingPosition == Histogram::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == Histogram::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == Histogram::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax()>0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax()>0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == Histogram::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i=0; icheck whether we have a break in between. const bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon add it to the polygon list and start a new polygon if (fillingPosition==Histogram::FillingAbove || fillingPosition==Histogram::FillingBelow || fillingPosition==Histogram::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2!=end) pol << end; //close the last polygon if (fillingPosition==Histogram::FillingAbove || fillingPosition==Histogram::FillingBelow || fillingPosition==Histogram::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void HistogramPrivate::recalcShapeAndBoundingRect() { //if (m_suppressRecalc) // return; prepareGeometryChange(); curveShape = QPainterPath(); curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (valuesType != Histogram::NoValues) curveShape.addPath(valuesPath); boundingRectangle = curveShape.boundingRect(); for (const auto& pol : fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); updatePixmap(); } void HistogramPrivate::draw(QPainter *painter) { //drawing line painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); //draw filling if (fillingPosition != Histogram::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw values if (valuesType != Histogram::NoValues) { painter->setOpacity(valuesOpacity); painter->setPen(valuesColor); painter->setBrush(Qt::SolidPattern); drawValues(painter); } } void HistogramPrivate::updatePixmap() { QPixmap pixmap(boundingRectangle.width(), boundingRectangle.height()); if (boundingRectangle.width()==0 || boundingRectangle.width()==0) { m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; return; } pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void HistogramPrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); draw(painter); if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Shadow)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Highlight)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); return; } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which us much faster (factor 10) */ void HistogramPrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i=0; idrawPath(trafo.map(path)); } } void HistogramPrivate::drawFilling(QPainter* painter) { for (const auto& pol : fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatioByExpanding,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(),Qt::IgnoreAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(),Qt::KeepAspectRatio,Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0,0),pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2,-pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2,pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor,fillingBrushStyle)); painter->drawPolygon(pol); } } void HistogramPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void HistogramPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } void HistogramPrivate::recalculate() { emit q->HistogramdataChanged(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Histogram::save(QXmlStreamWriter* writer) const { Q_D(const Histogram); writer->writeStartElement( "Histogram" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->xColumn, xColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); WRITE_QPEN(d->linePen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //write Histogram specific information writer->writeStartElement( "typeChanged" ); writer->writeAttribute( "Histogramtype", QString::number(d->histogramData.type) ); writer->writeAttribute( "BinsOption", QString::number(d->histogramData.binsOption) ); writer->writeAttribute( "binValue", QString::number(d->histogramData.binValue)); writer->writeEndElement(); if (d->xColumn) d->xColumn->save(writer); writer->writeEndElement(); //close "Histogram" section } //! Load from XML bool Histogram::load(XmlStreamReader* reader, bool preview) { Q_D(Histogram); if(!reader->isStartElement() || reader->name() != "Histogram") { reader->raiseError(i18n("no histogram element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "Histogram") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (!preview && reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(xColumn); str = attribs.value("visible").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "typeChanged") { attribs = reader->attributes(); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->histogramType = (Histogram::HistogramType)str.toInt(); str = attribs.value("BinsOption").toString(); d->binsOption = (Histogram::BinsOption)str.toInt(); str = attribs.value("binValue").toString(); d->histogramData.binValue = str.toInt(); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'type'")); else d->valuesType = (Histogram::ValuesType)str.toInt(); READ_COLUMN(valuesColumn); str = attribs.value("position").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'position'")); else d->valuesPosition = (Histogram::ValuesPosition)str.toInt(); str = attribs.value("distance").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'distance'")); else d->valuesDistance = str.toDouble(); str = attribs.value("rotation").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'rotation'")); else d->valuesRotationAngle = str.toDouble(); str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->valuesOpacity = str.toDouble(); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'opacity'")); else d->valuesOpacity = str.toDouble(); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); str = attribs.value("position").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("position")); else d->fillingPosition = Histogram::FillingPosition(str.toInt()); str = attribs.value("type").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("type")); else d->fillingType = PlotArea::BackgroundType(str.toInt()); str = attribs.value("colorStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("colorStyle")); else d->fillingColorStyle = PlotArea::BackgroundColorStyle(str.toInt()); str = attribs.value("imageStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("imageStyle")); else d->fillingImageStyle = PlotArea::BackgroundImageStyle(str.toInt()); str = attribs.value("brushStyle").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("brushStyle")); else d->fillingBrushStyle = Qt::BrushStyle(str.toInt()); str = attribs.value("firstColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->fillingSecondColor.setBlue(str.toInt()); str = attribs.value("fileName").toString(); d->fillingFileName = str; str = attribs.value("opacity").toString(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("opacity")); else d->fillingOpacity = str.toDouble(); } else if(reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } d->xColumn = column; } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp b/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp index cc6397404..f233cac66 100644 --- a/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYAnalysisCurve.cpp @@ -1,225 +1,225 @@ /*************************************************************************** File : XYAnalysisCurve.h Project : LabPlot Description : Base class for all analysis curves -------------------------------------------------------------------- Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYAnalysisCurve \brief Base class for all analysis curves \ingroup worksheet */ #include "XYAnalysisCurve.h" #include "XYAnalysisCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include XYAnalysisCurve::XYAnalysisCurve(const QString& name) : XYCurve(name, new XYAnalysisCurvePrivate(this)) { init(); } XYAnalysisCurve::XYAnalysisCurve(const QString& name, XYAnalysisCurvePrivate* dd) : XYCurve(name, dd) { init(); } // XYCurve::XYCurve(const QString &name) : WorksheetElement(name), d_ptr(new XYCurvePrivate(this)) { // init(); // } // // XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd) : WorksheetElement(name), d_ptr(dd) { // init(); // } XYAnalysisCurve::~XYAnalysisCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYAnalysisCurve::init() { Q_D(XYAnalysisCurve); d->dataSourceType = XYAnalysisCurve::DataSourceSpreadsheet; d->dataSourceCurve = nullptr; d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, XYAnalysisCurve::DataSourceType, dataSourceType, dataSourceType) BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const XYCurve*, dataSourceCurve, dataSourceCurve) const QString& XYAnalysisCurve::dataSourceCurvePath() const { return d_ptr->dataSourceCurvePath; } BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYAnalysisCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYAnalysisCurve::xDataColumnPath() const { Q_D(const XYAnalysisCurve); return d->xDataColumnPath; } const QString& XYAnalysisCurve::yDataColumnPath() const { Q_D(const XYAnalysisCurve); return d->yDataColumnPath; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetDataSourceType, XYAnalysisCurve::DataSourceType, dataSourceType) void XYAnalysisCurve::setDataSourceType(DataSourceType type) { Q_D(XYAnalysisCurve); if (type != d->dataSourceType) - exec(new XYAnalysisCurveSetDataSourceTypeCmd(d, type, i18n("%1: data source type changed"))); + exec(new XYAnalysisCurveSetDataSourceTypeCmd(d, type, ki18n("%1: data source type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYAnalysisCurve, SetDataSourceCurve, const XYCurve*, dataSourceCurve, retransform) void XYAnalysisCurve::setDataSourceCurve(const XYCurve* curve) { Q_D(XYAnalysisCurve); if (curve != d->dataSourceCurve) { - exec(new XYAnalysisCurveSetDataSourceCurveCmd(d, curve, i18n("%1: data source curve changed"))); + exec(new XYAnalysisCurveSetDataSourceCurveCmd(d, curve, ki18n("%1: data source curve changed"))); handleSourceDataChanged(); //handle the changes when different columns were provided for the source curve connect(curve, SIGNAL(xColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); connect(curve, SIGNAL(yColumnChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //handle the changes when the data inside of the source curve columns connect(curve, SIGNAL(xDataChanged()), this, SLOT(handleSourceDataChanged())); connect(curve, SIGNAL(yDataChanged()), this, SLOT(handleSourceDataChanged())); //TODO: add disconnect in the undo-function } } STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYAnalysisCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYAnalysisCurve); if (column != d->xDataColumn) { - exec(new XYAnalysisCurveSetXDataColumnCmd(d, column, i18n("%1: assign x-data"))); + exec(new XYAnalysisCurveSetXDataColumnCmd(d, column, ki18n("%1: assign x-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYAnalysisCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYAnalysisCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYAnalysisCurve); if (column != d->yDataColumn) { - exec(new XYAnalysisCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); + exec(new XYAnalysisCurveSetYDataColumnCmd(d, column, ki18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYAnalysisCurve::handleSourceDataChanged() { Q_D(XYAnalysisCurve); d->sourceDataChangedSinceLastRecalc = true; emit sourceDataChanged(); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYAnalysisCurvePrivate::XYAnalysisCurvePrivate(XYAnalysisCurve* owner) : XYCurvePrivate(owner), xDataColumn(nullptr), yDataColumn(nullptr), xColumn(nullptr), yColumn(nullptr), xVector(nullptr), yVector(nullptr), q(owner) { } XYAnalysisCurvePrivate::~XYAnalysisCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYAnalysisCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYAnalysisCurve); writer->writeStartElement("xyAnalysisCurve"); //write xy-curve information XYCurve::save(writer); //write data source specific information writer->writeStartElement("dataSource"); writer->writeAttribute( "type", QString::number(d->dataSourceType) ); WRITE_PATH(d->dataSourceCurve, dataSourceCurve); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); writer->writeEndElement(); writer->writeEndElement(); //"xyAnalysiCurve" } //! Load from XML bool XYAnalysisCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYAnalysisCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyAnalysisCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (reader->name() == "dataSource") { attribs = reader->attributes(); READ_INT_VALUE("type", dataSourceType, XYAnalysisCurve::DataSourceType); READ_PATH(dataSourceCurve); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp index 38abb4008..e78413d67 100644 --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -1,2466 +1,2466 @@ /*************************************************************************** File : XYCurve.cpp Project : LabPlot Description : A xy-curve -------------------------------------------------------------------- Copyright : (C) 2010-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYCurve \brief A 2D-curve, provides an interface for editing many properties of the curve. \ingroup worksheet */ #include "XYCurve.h" #include "XYCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/worksheet/plots/cartesian/CartesianCoordinateSystem.h" #include "backend/worksheet/plots/cartesian/CartesianPlot.h" #include "backend/lib/commandtemplates.h" #include "backend/core/Project.h" #include "backend/spreadsheet/Spreadsheet.h" #include "backend/worksheet/Worksheet.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include "backend/gsl/errors.h" #include "tools/ImageTools.h" #include #include #include #include #include extern "C" { #include #include } XYCurve::XYCurve(const QString &name) : WorksheetElement(name), d_ptr(new XYCurvePrivate(this)), m_menusInitialized(false) { init(); } XYCurve::XYCurve(const QString& name, XYCurvePrivate* dd) : WorksheetElement(name), d_ptr(dd), m_menusInitialized(false) { init(); } XYCurve::~XYCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYCurve::finalizeAdd() { Q_D(XYCurve); d->plot = dynamic_cast(parentAspect()); Q_ASSERT(d->plot); d->cSystem = dynamic_cast(d->plot->coordinateSystem()); } void XYCurve::init() { Q_D(XYCurve); KConfig config; KConfigGroup group = config.group("XYCurve"); d->xColumn = nullptr; d->yColumn = nullptr; d->lineType = (XYCurve::LineType) group.readEntry("LineType", (int)XYCurve::Line); d->lineSkipGaps = group.readEntry("SkipLineGaps", false); d->lineInterpolationPointsCount = group.readEntry("LineInterpolationPointsCount", 1); d->linePen.setStyle( (Qt::PenStyle) group.readEntry("LineStyle", (int)Qt::SolidLine) ); d->linePen.setColor( group.readEntry("LineColor", QColor(Qt::black)) ); d->linePen.setWidthF( group.readEntry("LineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->lineOpacity = group.readEntry("LineOpacity", 1.0); d->dropLineType = (XYCurve::DropLineType) group.readEntry("DropLineType", (int)XYCurve::NoLine); d->dropLinePen.setStyle( (Qt::PenStyle) group.readEntry("DropLineStyle", (int)Qt::SolidLine) ); d->dropLinePen.setColor( group.readEntry("DropLineColor", QColor(Qt::black))); d->dropLinePen.setWidthF( group.readEntry("DropLineWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->dropLineOpacity = group.readEntry("DropLineOpacity", 1.0); d->symbolsStyle = (Symbol::Style)group.readEntry("SymbolStyle", (int)Symbol::NoSymbols); d->symbolsSize = group.readEntry("SymbolSize", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->symbolsRotationAngle = group.readEntry("SymbolRotation", 0.0); d->symbolsOpacity = group.readEntry("SymbolOpacity", 1.0); d->symbolsBrush.setStyle( (Qt::BrushStyle)group.readEntry("SymbolFillingStyle", (int)Qt::SolidPattern) ); d->symbolsBrush.setColor( group.readEntry("SymbolFillingColor", QColor(Qt::black)) ); d->symbolsPen.setStyle( (Qt::PenStyle)group.readEntry("SymbolBorderStyle", (int)Qt::SolidLine) ); d->symbolsPen.setColor( group.readEntry("SymbolBorderColor", QColor(Qt::black)) ); d->symbolsPen.setWidthF( group.readEntry("SymbolBorderWidth", Worksheet::convertToSceneUnits(0.0, Worksheet::Point)) ); d->valuesType = (XYCurve::ValuesType) group.readEntry("ValuesType", (int)XYCurve::NoValues); d->valuesColumn = nullptr; d->valuesPosition = (XYCurve::ValuesPosition) group.readEntry("ValuesPosition", (int)XYCurve::ValuesAbove); d->valuesDistance = group.readEntry("ValuesDistance", Worksheet::convertToSceneUnits(5, Worksheet::Point)); d->valuesRotationAngle = group.readEntry("ValuesRotation", 0.0); d->valuesOpacity = group.readEntry("ValuesOpacity", 1.0); d->valuesPrefix = group.readEntry("ValuesPrefix", ""); d->valuesSuffix = group.readEntry("ValuesSuffix", ""); d->valuesFont = group.readEntry("ValuesFont", QFont()); d->valuesFont.setPixelSize( Worksheet::convertToSceneUnits( 8, Worksheet::Point ) ); d->valuesColor = group.readEntry("ValuesColor", QColor(Qt::black)); d->fillingPosition = (XYCurve::FillingPosition) group.readEntry("FillingPosition", (int)XYCurve::NoFilling); d->fillingType = (PlotArea::BackgroundType) group.readEntry("FillingType", (int)PlotArea::Color); d->fillingColorStyle = (PlotArea::BackgroundColorStyle) group.readEntry("FillingColorStyle", (int) PlotArea::SingleColor); d->fillingImageStyle = (PlotArea::BackgroundImageStyle) group.readEntry("FillingImageStyle", (int) PlotArea::Scaled); d->fillingBrushStyle = (Qt::BrushStyle) group.readEntry("FillingBrushStyle", (int) Qt::SolidPattern); d->fillingFileName = group.readEntry("FillingFileName", QString()); d->fillingFirstColor = group.readEntry("FillingFirstColor", QColor(Qt::white)); d->fillingSecondColor = group.readEntry("FillingSecondColor", QColor(Qt::black)); d->fillingOpacity = group.readEntry("FillingOpacity", 1.0); d->xErrorType = (XYCurve::ErrorType) group.readEntry("XErrorType", (int)XYCurve::NoError); d->xErrorPlusColumn = nullptr; d->xErrorMinusColumn = nullptr; d->yErrorType = (XYCurve::ErrorType) group.readEntry("YErrorType", (int)XYCurve::NoError); d->yErrorPlusColumn = nullptr; d->yErrorMinusColumn = nullptr; d->errorBarsType = (XYCurve::ErrorBarsType) group.readEntry("ErrorBarsType", (int)XYCurve::ErrorBarsSimple); d->errorBarsCapSize = group.readEntry( "ErrorBarsCapSize", Worksheet::convertToSceneUnits(10, Worksheet::Point) ); d->errorBarsPen.setStyle( (Qt::PenStyle)group.readEntry("ErrorBarsStyle", (int)Qt::SolidLine) ); d->errorBarsPen.setColor( group.readEntry("ErrorBarsColor", QColor(Qt::black)) ); d->errorBarsPen.setWidthF( group.readEntry("ErrorBarsWidth", Worksheet::convertToSceneUnits(1.0, Worksheet::Point)) ); d->errorBarsOpacity = group.readEntry("ErrorBarsOpacity", 1.0); } void XYCurve::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); navigateToAction = new QAction(QIcon::fromTheme("go-next-view"), "", this); connect(navigateToAction, SIGNAL(triggered()), this, SLOT(navigateTo())); m_menusInitialized = true; } QMenu* XYCurve::createContextMenu() { if (!m_menusInitialized) initActions(); QMenu *menu = WorksheetElement::createContextMenu(); QAction* firstAction = menu->actions().at(1); //skip the first action because of the "title-action" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); //"data analysis" menu CartesianPlot* plot = dynamic_cast(parentAspect()); menu->insertMenu(visibilityAction, plot->analysisMenu()); menu->insertSeparator(visibilityAction); //"Navigate to spreadsheet"-action, show only if x- or y-columns have data from a spreadsheet AbstractAspect* parentSpreadsheet = 0; if (xColumn() && dynamic_cast(xColumn()->parentAspect()) ) parentSpreadsheet = xColumn()->parentAspect(); else if (yColumn() && dynamic_cast(yColumn()->parentAspect()) ) parentSpreadsheet = yColumn()->parentAspect(); if (parentSpreadsheet) { navigateToAction->setText(i18n("Navigate to \"%1\"", parentSpreadsheet->name())); navigateToAction->setData(parentSpreadsheet->path()); menu->insertAction(visibilityAction, navigateToAction); menu->insertSeparator(visibilityAction); } //if the context menu is called on an item that is not selected yet, select it if (!graphicsItem()->isSelected()) graphicsItem()->setSelected(true); return menu; } /*! Returns an icon to be used in the project explorer. */ QIcon XYCurve::icon() const { return QIcon::fromTheme("labplot-xy-curve"); } QGraphicsItem* XYCurve::graphicsItem() const { return d_ptr; } STD_SWAP_METHOD_SETTER_CMD_IMPL(XYCurve, SetVisible, bool, swapVisible) void XYCurve::setVisible(bool on) { Q_D(XYCurve); - exec(new XYCurveSetVisibleCmd(d, on, on ? i18n("%1: set visible") : i18n("%1: set invisible"))); + exec(new XYCurveSetVisibleCmd(d, on, on ? ki18n("%1: set visible") : ki18n("%1: set invisible"))); } bool XYCurve::isVisible() const { Q_D(const XYCurve); return d->isVisible(); } void XYCurve::setPrinting(bool on) { Q_D(XYCurve); d->setPrinting(on); } //############################################################################## //########################## getter methods ################################## //############################################################################## //data source BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xColumn, xColumn) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yColumn, yColumn) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, xColumnPath, xColumnPath) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, yColumnPath, yColumnPath) //line BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::LineType, lineType, lineType) BASIC_SHARED_D_READER_IMPL(XYCurve, bool, lineSkipGaps, lineSkipGaps) BASIC_SHARED_D_READER_IMPL(XYCurve, int, lineInterpolationPointsCount, lineInterpolationPointsCount) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, linePen, linePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, lineOpacity, lineOpacity) //droplines BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::DropLineType, dropLineType, dropLineType) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, dropLinePen, dropLinePen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, dropLineOpacity, dropLineOpacity) //symbols BASIC_SHARED_D_READER_IMPL(XYCurve, Symbol::Style, symbolsStyle, symbolsStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsOpacity, symbolsOpacity) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsRotationAngle, symbolsRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, symbolsSize, symbolsSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QBrush, symbolsBrush, symbolsBrush) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, symbolsPen, symbolsPen) //values BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesType, valuesType, valuesType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn *, valuesColumn, valuesColumn) const QString& XYCurve::valuesColumnPath() const { return d_ptr->valuesColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ValuesPosition, valuesPosition, valuesPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesDistance, valuesDistance) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesRotationAngle, valuesRotationAngle) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, valuesOpacity, valuesOpacity) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesPrefix, valuesPrefix) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, valuesSuffix, valuesSuffix) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, valuesColor, valuesColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QFont, valuesFont, valuesFont) //filling BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::FillingPosition, fillingPosition, fillingPosition) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundType, fillingType, fillingType) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundColorStyle, fillingColorStyle, fillingColorStyle) BASIC_SHARED_D_READER_IMPL(XYCurve, PlotArea::BackgroundImageStyle, fillingImageStyle, fillingImageStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, Qt::BrushStyle, fillingBrushStyle, fillingBrushStyle) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingFirstColor, fillingFirstColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QColor, fillingSecondColor, fillingSecondColor) CLASS_SHARED_D_READER_IMPL(XYCurve, QString, fillingFileName, fillingFileName) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, fillingOpacity, fillingOpacity) //error bars BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, xErrorType, xErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorPlusColumn, xErrorPlusColumn) const QString& XYCurve::xErrorPlusColumnPath() const { return d_ptr->xErrorPlusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, xErrorMinusColumn, xErrorMinusColumn) const QString& XYCurve::xErrorMinusColumnPath() const { return d_ptr->xErrorMinusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorType, yErrorType, yErrorType) BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorPlusColumn, yErrorPlusColumn) const QString& XYCurve::yErrorPlusColumnPath() const { return d_ptr->yErrorPlusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, const AbstractColumn*, yErrorMinusColumn, yErrorMinusColumn) const QString& XYCurve::yErrorMinusColumnPath() const { return d_ptr->yErrorMinusColumnPath; } BASIC_SHARED_D_READER_IMPL(XYCurve, XYCurve::ErrorBarsType, errorBarsType, errorBarsType) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsCapSize, errorBarsCapSize) CLASS_SHARED_D_READER_IMPL(XYCurve, QPen, errorBarsPen, errorBarsPen) BASIC_SHARED_D_READER_IMPL(XYCurve, qreal, errorBarsOpacity, errorBarsOpacity) /*! * return \c true if the data in the source columns (x, y) used in the analysis curves, \c false otherwise */ bool XYCurve::isSourceDataChangedSinceLastRecalc() const { Q_D(const XYCurve); return d->sourceDataChangedSinceLastRecalc; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXColumn, const AbstractColumn*, xColumn, retransform) void XYCurve::setXColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xColumn) { - exec(new XYCurveSetXColumnCmd(d, column, i18n("%1: x-data source changed"))); + exec(new XYCurveSetXColumnCmd(d, column, ki18n("%1: x-data source changed"))); //emit xDataChanged() in order to notify the plot about the changes emit xDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SIGNAL(xDataChanged())); //update the curve itself on changes connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransform())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xColumnAboutToBeRemoved(const AbstractAspect*))); //TODO: add disconnect in the undo-function } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYColumn, const AbstractColumn*, yColumn, retransform) void XYCurve::setYColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yColumn) { - exec(new XYCurveSetYColumnCmd(d, column, i18n("%1: y-data source changed"))); + exec(new XYCurveSetYColumnCmd(d, column, ki18n("%1: y-data source changed"))); //emit yDataChanged() in order to notify the plot about the changes emit yDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SIGNAL(yDataChanged())); //update the curve itself on changes connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransform())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yColumnAboutToBeRemoved(const AbstractAspect*))); //TODO: add disconnect in the undo-function } } } void XYCurve::setXColumnPath(const QString& path) { Q_D(XYCurve); d->xColumnPath = path; } void XYCurve::setYColumnPath(const QString& path) { Q_D(XYCurve); d->yColumnPath = path; } //Line STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineType, XYCurve::LineType, lineType, updateLines) void XYCurve::setLineType(LineType type) { Q_D(XYCurve); if (type != d->lineType) - exec(new XYCurveSetLineTypeCmd(d, type, i18n("%1: line type changed"))); + exec(new XYCurveSetLineTypeCmd(d, type, ki18n("%1: line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineSkipGaps, bool, lineSkipGaps, updateLines) void XYCurve::setLineSkipGaps(bool skip) { Q_D(XYCurve); if (skip != d->lineSkipGaps) - exec(new XYCurveSetLineSkipGapsCmd(d, skip, i18n("%1: set skip line gaps"))); + exec(new XYCurveSetLineSkipGapsCmd(d, skip, ki18n("%1: set skip line gaps"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineInterpolationPointsCount, int, lineInterpolationPointsCount, updateLines) void XYCurve::setLineInterpolationPointsCount(int count) { Q_D(XYCurve); if (count != d->lineInterpolationPointsCount) - exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, i18n("%1: set the number of interpolation points"))); + exec(new XYCurveSetLineInterpolationPointsCountCmd(d, count, ki18n("%1: set the number of interpolation points"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLinePen, QPen, linePen, recalcShapeAndBoundingRect) void XYCurve::setLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->linePen) - exec(new XYCurveSetLinePenCmd(d, pen, i18n("%1: set line style"))); + exec(new XYCurveSetLinePenCmd(d, pen, ki18n("%1: set line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetLineOpacity, qreal, lineOpacity, updatePixmap); void XYCurve::setLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->lineOpacity) - exec(new XYCurveSetLineOpacityCmd(d, opacity, i18n("%1: set line opacity"))); + exec(new XYCurveSetLineOpacityCmd(d, opacity, ki18n("%1: set line opacity"))); } //Drop lines STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineType, XYCurve::DropLineType, dropLineType, updateDropLines) void XYCurve::setDropLineType(DropLineType type) { Q_D(XYCurve); if (type != d->dropLineType) - exec(new XYCurveSetDropLineTypeCmd(d, type, i18n("%1: drop line type changed"))); + exec(new XYCurveSetDropLineTypeCmd(d, type, ki18n("%1: drop line type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLinePen, QPen, dropLinePen, recalcShapeAndBoundingRect) void XYCurve::setDropLinePen(const QPen &pen) { Q_D(XYCurve); if (pen != d->dropLinePen) - exec(new XYCurveSetDropLinePenCmd(d, pen, i18n("%1: set drop line style"))); + exec(new XYCurveSetDropLinePenCmd(d, pen, ki18n("%1: set drop line style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetDropLineOpacity, qreal, dropLineOpacity, updatePixmap) void XYCurve::setDropLineOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->dropLineOpacity) - exec(new XYCurveSetDropLineOpacityCmd(d, opacity, i18n("%1: set drop line opacity"))); + exec(new XYCurveSetDropLineOpacityCmd(d, opacity, ki18n("%1: set drop line opacity"))); } // Symbols-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsStyle, Symbol::Style, symbolsStyle, updateSymbols) void XYCurve::setSymbolsStyle(Symbol::Style style) { Q_D(XYCurve); if (style != d->symbolsStyle) - exec(new XYCurveSetSymbolsStyleCmd(d, style, i18n("%1: set symbol style"))); + exec(new XYCurveSetSymbolsStyleCmd(d, style, ki18n("%1: set symbol style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsSize, qreal, symbolsSize, updateSymbols) void XYCurve::setSymbolsSize(qreal size) { Q_D(XYCurve); if (!qFuzzyCompare(1 + size, 1 + d->symbolsSize)) - exec(new XYCurveSetSymbolsSizeCmd(d, size, i18n("%1: set symbol size"))); + exec(new XYCurveSetSymbolsSizeCmd(d, size, ki18n("%1: set symbol size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsRotationAngle, qreal, symbolsRotationAngle, updateSymbols) void XYCurve::setSymbolsRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->symbolsRotationAngle)) - exec(new XYCurveSetSymbolsRotationAngleCmd(d, angle, i18n("%1: rotate symbols"))); + exec(new XYCurveSetSymbolsRotationAngleCmd(d, angle, ki18n("%1: rotate symbols"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsBrush, QBrush, symbolsBrush, updatePixmap) void XYCurve::setSymbolsBrush(const QBrush &brush) { Q_D(XYCurve); if (brush != d->symbolsBrush) - exec(new XYCurveSetSymbolsBrushCmd(d, brush, i18n("%1: set symbol filling"))); + exec(new XYCurveSetSymbolsBrushCmd(d, brush, ki18n("%1: set symbol filling"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsPen, QPen, symbolsPen, updateSymbols) void XYCurve::setSymbolsPen(const QPen &pen) { Q_D(XYCurve); if (pen != d->symbolsPen) - exec(new XYCurveSetSymbolsPenCmd(d, pen, i18n("%1: set symbol outline style"))); + exec(new XYCurveSetSymbolsPenCmd(d, pen, ki18n("%1: set symbol outline style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetSymbolsOpacity, qreal, symbolsOpacity, updatePixmap) void XYCurve::setSymbolsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->symbolsOpacity) - exec(new XYCurveSetSymbolsOpacityCmd(d, opacity, i18n("%1: set symbols opacity"))); + exec(new XYCurveSetSymbolsOpacityCmd(d, opacity, ki18n("%1: set symbols opacity"))); } //Values-Tab STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesType, XYCurve::ValuesType, valuesType, updateValues) void XYCurve::setValuesType(XYCurve::ValuesType type) { Q_D(XYCurve); if (type != d->valuesType) - exec(new XYCurveSetValuesTypeCmd(d, type, i18n("%1: set values type"))); + exec(new XYCurveSetValuesTypeCmd(d, type, ki18n("%1: set values type"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColumn, const AbstractColumn*, valuesColumn, updateValues) void XYCurve::setValuesColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->valuesColumn) { - exec(new XYCurveSetValuesColumnCmd(d, column, i18n("%1: set values column"))); + exec(new XYCurveSetValuesColumnCmd(d, column, ki18n("%1: set values column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateValues())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(valuesColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPosition, XYCurve::ValuesPosition, valuesPosition, updateValues) void XYCurve::setValuesPosition(ValuesPosition position) { Q_D(XYCurve); if (position != d->valuesPosition) - exec(new XYCurveSetValuesPositionCmd(d, position, i18n("%1: set values position"))); + exec(new XYCurveSetValuesPositionCmd(d, position, ki18n("%1: set values position"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesDistance, qreal, valuesDistance, updateValues) void XYCurve::setValuesDistance(qreal distance) { Q_D(XYCurve); if (distance != d->valuesDistance) - exec(new XYCurveSetValuesDistanceCmd(d, distance, i18n("%1: set values distance"))); + exec(new XYCurveSetValuesDistanceCmd(d, distance, ki18n("%1: set values distance"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesRotationAngle, qreal, valuesRotationAngle, updateValues) void XYCurve::setValuesRotationAngle(qreal angle) { Q_D(XYCurve); if (!qFuzzyCompare(1 + angle, 1 + d->valuesRotationAngle)) - exec(new XYCurveSetValuesRotationAngleCmd(d, angle, i18n("%1: rotate values"))); + exec(new XYCurveSetValuesRotationAngleCmd(d, angle, ki18n("%1: rotate values"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesOpacity, qreal, valuesOpacity, updatePixmap) void XYCurve::setValuesOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->valuesOpacity) - exec(new XYCurveSetValuesOpacityCmd(d, opacity, i18n("%1: set values opacity"))); + exec(new XYCurveSetValuesOpacityCmd(d, opacity, ki18n("%1: set values opacity"))); } //TODO: Format, Precision STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesPrefix, QString, valuesPrefix, updateValues) void XYCurve::setValuesPrefix(const QString& prefix) { Q_D(XYCurve); if (prefix != d->valuesPrefix) - exec(new XYCurveSetValuesPrefixCmd(d, prefix, i18n("%1: set values prefix"))); + exec(new XYCurveSetValuesPrefixCmd(d, prefix, ki18n("%1: set values prefix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesSuffix, QString, valuesSuffix, updateValues) void XYCurve::setValuesSuffix(const QString& suffix) { Q_D(XYCurve); if (suffix != d->valuesSuffix) - exec(new XYCurveSetValuesSuffixCmd(d, suffix, i18n("%1: set values suffix"))); + exec(new XYCurveSetValuesSuffixCmd(d, suffix, ki18n("%1: set values suffix"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesFont, QFont, valuesFont, updateValues) void XYCurve::setValuesFont(const QFont& font) { Q_D(XYCurve); if (font != d->valuesFont) - exec(new XYCurveSetValuesFontCmd(d, font, i18n("%1: set values font"))); + exec(new XYCurveSetValuesFontCmd(d, font, ki18n("%1: set values font"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetValuesColor, QColor, valuesColor, updatePixmap) void XYCurve::setValuesColor(const QColor& color) { Q_D(XYCurve); if (color != d->valuesColor) - exec(new XYCurveSetValuesColorCmd(d, color, i18n("%1: set values color"))); + exec(new XYCurveSetValuesColorCmd(d, color, ki18n("%1: set values color"))); } //Filling STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingPosition, XYCurve::FillingPosition, fillingPosition, updateFilling) void XYCurve::setFillingPosition(FillingPosition position) { Q_D(XYCurve); if (position != d->fillingPosition) - exec(new XYCurveSetFillingPositionCmd(d, position, i18n("%1: filling position changed"))); + exec(new XYCurveSetFillingPositionCmd(d, position, ki18n("%1: filling position changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingType, PlotArea::BackgroundType, fillingType, updatePixmap) void XYCurve::setFillingType(PlotArea::BackgroundType type) { Q_D(XYCurve); if (type != d->fillingType) - exec(new XYCurveSetFillingTypeCmd(d, type, i18n("%1: filling type changed"))); + exec(new XYCurveSetFillingTypeCmd(d, type, ki18n("%1: filling type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingColorStyle, PlotArea::BackgroundColorStyle, fillingColorStyle, updatePixmap) void XYCurve::setFillingColorStyle(PlotArea::BackgroundColorStyle style) { Q_D(XYCurve); if (style != d->fillingColorStyle) - exec(new XYCurveSetFillingColorStyleCmd(d, style, i18n("%1: filling color style changed"))); + exec(new XYCurveSetFillingColorStyleCmd(d, style, ki18n("%1: filling color style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingImageStyle, PlotArea::BackgroundImageStyle, fillingImageStyle, updatePixmap) void XYCurve::setFillingImageStyle(PlotArea::BackgroundImageStyle style) { Q_D(XYCurve); if (style != d->fillingImageStyle) - exec(new XYCurveSetFillingImageStyleCmd(d, style, i18n("%1: filling image style changed"))); + exec(new XYCurveSetFillingImageStyleCmd(d, style, ki18n("%1: filling image style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingBrushStyle, Qt::BrushStyle, fillingBrushStyle, updatePixmap) void XYCurve::setFillingBrushStyle(Qt::BrushStyle style) { Q_D(XYCurve); if (style != d->fillingBrushStyle) - exec(new XYCurveSetFillingBrushStyleCmd(d, style, i18n("%1: filling brush style changed"))); + exec(new XYCurveSetFillingBrushStyleCmd(d, style, ki18n("%1: filling brush style changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFirstColor, QColor, fillingFirstColor, updatePixmap) void XYCurve::setFillingFirstColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingFirstColor) - exec(new XYCurveSetFillingFirstColorCmd(d, color, i18n("%1: set filling first color"))); + exec(new XYCurveSetFillingFirstColorCmd(d, color, ki18n("%1: set filling first color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingSecondColor, QColor, fillingSecondColor, updatePixmap) void XYCurve::setFillingSecondColor(const QColor& color) { Q_D(XYCurve); if (color != d->fillingSecondColor) - exec(new XYCurveSetFillingSecondColorCmd(d, color, i18n("%1: set filling second color"))); + exec(new XYCurveSetFillingSecondColorCmd(d, color, ki18n("%1: set filling second color"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingFileName, QString, fillingFileName, updatePixmap) void XYCurve::setFillingFileName(const QString& fileName) { Q_D(XYCurve); if (fileName != d->fillingFileName) - exec(new XYCurveSetFillingFileNameCmd(d, fileName, i18n("%1: set filling image"))); + exec(new XYCurveSetFillingFileNameCmd(d, fileName, ki18n("%1: set filling image"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetFillingOpacity, qreal, fillingOpacity, updatePixmap) void XYCurve::setFillingOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->fillingOpacity) - exec(new XYCurveSetFillingOpacityCmd(d, opacity, i18n("%1: set filling opacity"))); + exec(new XYCurveSetFillingOpacityCmd(d, opacity, ki18n("%1: set filling opacity"))); } //Error bars STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorType, XYCurve::ErrorType, xErrorType, updateErrorBars) void XYCurve::setXErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->xErrorType) - exec(new XYCurveSetXErrorTypeCmd(d, type, i18n("%1: x-error type changed"))); + exec(new XYCurveSetXErrorTypeCmd(d, type, ki18n("%1: x-error type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorPlusColumn, const AbstractColumn*, xErrorPlusColumn, updateErrorBars) void XYCurve::setXErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorPlusColumn) { - exec(new XYCurveSetXErrorPlusColumnCmd(d, column, i18n("%1: set x-error column"))); + exec(new XYCurveSetXErrorPlusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xErrorPlusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetXErrorMinusColumn, const AbstractColumn*, xErrorMinusColumn, updateErrorBars) void XYCurve::setXErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->xErrorMinusColumn) { - exec(new XYCurveSetXErrorMinusColumnCmd(d, column, i18n("%1: set x-error column"))); + exec(new XYCurveSetXErrorMinusColumnCmd(d, column, ki18n("%1: set x-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(xErrorMinusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorType, XYCurve::ErrorType, yErrorType, updateErrorBars) void XYCurve::setYErrorType(ErrorType type) { Q_D(XYCurve); if (type != d->yErrorType) - exec(new XYCurveSetYErrorTypeCmd(d, type, i18n("%1: y-error type changed"))); + exec(new XYCurveSetYErrorTypeCmd(d, type, ki18n("%1: y-error type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorPlusColumn, const AbstractColumn*, yErrorPlusColumn, updateErrorBars) void XYCurve::setYErrorPlusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorPlusColumn) { - exec(new XYCurveSetYErrorPlusColumnCmd(d, column, i18n("%1: set y-error column"))); + exec(new XYCurveSetYErrorPlusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yErrorPlusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetYErrorMinusColumn, const AbstractColumn*, yErrorMinusColumn, updateErrorBars) void XYCurve::setYErrorMinusColumn(const AbstractColumn* column) { Q_D(XYCurve); if (column != d->yErrorMinusColumn) { - exec(new XYCurveSetYErrorMinusColumnCmd(d, column, i18n("%1: set y-error column"))); + exec(new XYCurveSetYErrorMinusColumnCmd(d, column, ki18n("%1: set y-error column"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(updateErrorBars())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(yErrorMinusColumnAboutToBeRemoved(const AbstractAspect*))); } } } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsCapSize, qreal, errorBarsCapSize, updateErrorBars) void XYCurve::setErrorBarsCapSize(qreal size) { Q_D(XYCurve); if (size != d->errorBarsCapSize) - exec(new XYCurveSetErrorBarsCapSizeCmd(d, size, i18n("%1: set error bar cap size"))); + exec(new XYCurveSetErrorBarsCapSizeCmd(d, size, ki18n("%1: set error bar cap size"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsType, XYCurve::ErrorBarsType, errorBarsType, updateErrorBars) void XYCurve::setErrorBarsType(ErrorBarsType type) { Q_D(XYCurve); if (type != d->errorBarsType) - exec(new XYCurveSetErrorBarsTypeCmd(d, type, i18n("%1: error bar type changed"))); + exec(new XYCurveSetErrorBarsTypeCmd(d, type, ki18n("%1: error bar type changed"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsPen, QPen, errorBarsPen, recalcShapeAndBoundingRect) void XYCurve::setErrorBarsPen(const QPen& pen) { Q_D(XYCurve); if (pen != d->errorBarsPen) - exec(new XYCurveSetErrorBarsPenCmd(d, pen, i18n("%1: set error bar style"))); + exec(new XYCurveSetErrorBarsPenCmd(d, pen, ki18n("%1: set error bar style"))); } STD_SETTER_CMD_IMPL_F_S(XYCurve, SetErrorBarsOpacity, qreal, errorBarsOpacity, updatePixmap) void XYCurve::setErrorBarsOpacity(qreal opacity) { Q_D(XYCurve); if (opacity != d->errorBarsOpacity) - exec(new XYCurveSetErrorBarsOpacityCmd(d, opacity, i18n("%1: set error bar opacity"))); + exec(new XYCurveSetErrorBarsOpacityCmd(d, opacity, ki18n("%1: set error bar opacity"))); } void XYCurve::suppressRetransform(bool b) { Q_D(XYCurve); d->suppressRetransform(b); } //############################################################################## //################################# SLOTS #################################### //############################################################################## void XYCurve::retransform() { Q_D(XYCurve); d->retransform(); } void XYCurve::updateValues() { Q_D(XYCurve); d->updateValues(); } void XYCurve::updateErrorBars() { Q_D(XYCurve); d->updateErrorBars(); } //TODO void XYCurve::handleResize(double horizontalRatio, double verticalRatio, bool pageResize) { Q_UNUSED(pageResize); Q_D(const XYCurve); setSymbolsSize(d->symbolsSize * horizontalRatio); QPen pen = d->symbolsPen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setSymbolsPen(pen); pen = d->linePen; pen.setWidthF(pen.widthF() * (horizontalRatio + verticalRatio) / 2.0); setLinePen(pen); //setValuesDistance(d->distance*); QFont font=d->valuesFont; font.setPointSizeF(font.pointSizeF()*horizontalRatio); setValuesFont(font); } void XYCurve::xColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xColumn) { d->xColumn = 0; d->retransform(); } } void XYCurve::yColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yColumn) { d->yColumn = 0; d->retransform(); } } void XYCurve::valuesColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->valuesColumn) { d->valuesColumn = 0; d->updateValues(); } } void XYCurve::xErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xErrorPlusColumn) { d->xErrorPlusColumn = 0; d->updateErrorBars(); } } void XYCurve::xErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->xErrorMinusColumn) { d->xErrorMinusColumn = 0; d->updateErrorBars(); } } void XYCurve::yErrorPlusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yErrorPlusColumn) { d->yErrorPlusColumn = 0; d->updateErrorBars(); } } void XYCurve::yErrorMinusColumnAboutToBeRemoved(const AbstractAspect* aspect) { Q_D(XYCurve); if (aspect == d->yErrorMinusColumn) { d->yErrorMinusColumn = 0; d->updateErrorBars(); } } //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void XYCurve::visibilityChanged() { Q_D(const XYCurve); this->setVisible(!d->isVisible()); } void XYCurve::navigateTo() { project()->navigateTo(navigateToAction->data().toString()); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYCurvePrivate::XYCurvePrivate(XYCurve *owner) : sourceDataChangedSinceLastRecalc(false), q(owner), plot(nullptr), cSystem(nullptr), m_hoverEffectImageIsDirty(false), m_selectionEffectImageIsDirty(false), m_hovered(false), m_suppressRecalc(false), m_suppressRetransform(false), m_printing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); setAcceptHoverEvents(true); } QString XYCurvePrivate::name() const { return q->name(); } QRectF XYCurvePrivate::boundingRect() const { return boundingRectangle; } /*! Returns the shape of the XYCurve as a QPainterPath in local coordinates */ QPainterPath XYCurvePrivate::shape() const { return curveShape; } void XYCurvePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } bool XYCurvePrivate::swapVisible(bool on) { bool oldValue = isVisible(); setVisible(on); emit q->visibilityChanged(on); return oldValue; } /*! recalculates the position of the points to be drawn. Called when the data was changed. Triggers the update of lines, drop lines, symbols etc. */ void XYCurvePrivate::retransform() { DEBUG("\nXYCurvePrivate::retransform() name = " << name().toStdString() << ", m_suppressRetransform = " << m_suppressRetransform); DEBUG(" plot = " << plot); if (m_suppressRetransform || !plot) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform()"); #endif symbolPointsLogical.clear(); symbolPointsScene.clear(); connectedPointsLogical.clear(); if ( (NULL == xColumn) || (NULL == yColumn) ) { DEBUG(" xColumn or yColumn == NULL"); linePath = QPainterPath(); dropLinePath = QPainterPath(); symbolsPath = QPainterPath(); valuesPath = QPainterPath(); errorBarsPath = QPainterPath(); recalcShapeAndBoundingRect(); return; } if (!plot->isPanningActive()) { WAIT_CURSOR; QApplication::processEvents(QEventLoop::AllEvents, 0); } QPointF tempPoint; AbstractColumn::ColumnMode xColMode = xColumn->columnMode(); AbstractColumn::ColumnMode yColMode = yColumn->columnMode(); //take over only valid and non masked points. for (int row = 0; row < xColumn->rowCount(); row++) { if ( xColumn->isValid(row) && yColumn->isValid(row) && (!xColumn->isMasked(row)) && (!yColumn->isMasked(row)) ) { switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: tempPoint.setX(xColumn->valueAt(row)); break; case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } switch (yColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: tempPoint.setY(yColumn->valueAt(row)); break; case AbstractColumn::Text: //TODO case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: break; } symbolPointsLogical.append(tempPoint); connectedPointsLogical.push_back(true); } else { if (!connectedPointsLogical.empty()) connectedPointsLogical[connectedPointsLogical.size()-1] = false; } } //calculate the scene coordinates visiblePoints = std::vector(symbolPointsLogical.count(), false); { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::retransform(), map logical points to scene coordinates"); #endif cSystem->mapLogicalToScene(symbolPointsLogical, symbolPointsScene, visiblePoints); } m_suppressRecalc = true; updateLines(); updateDropLines(); updateSymbols(); updateValues(); m_suppressRecalc = false; updateErrorBars(); RESET_CURSOR; } /*! recalculates the painter path for the lines connecting the data points. Called each time when the type of this connection is changed. */ void XYCurvePrivate::updateLines() { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines()"); #endif linePath = QPainterPath(); lines.clear(); if (lineType == XYCurve::NoLine) { DEBUG(" nothing to do, since line type is XYCurve::NoLine"); updateFilling(); recalcShapeAndBoundingRect(); return; } unsigned int count = (unsigned int)symbolPointsLogical.count(); if (count <= 1) { DEBUG(" nothing to do, since no data points available"); recalcShapeAndBoundingRect(); return; } //calculate the lines connecting the data points { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate the lines connecting the data points"); #endif QPointF tempPoint1, tempPoint2; QPointF curPoint, nextPoint; switch (lineType) { case XYCurve::NoLine: break; case XYCurve::Line: for (unsigned int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); } break; case XYCurve::StartHorizontal: for (unsigned int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(nextPoint.x(), curPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, nextPoint)); } break; case XYCurve::StartVertical: for (unsigned int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x(), nextPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1,nextPoint)); } break; case XYCurve::MidpointHorizontal: for (unsigned int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, curPoint.y()); tempPoint2 = QPointF(curPoint.x() + (nextPoint.x()-curPoint.x())/2, nextPoint.y()); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, tempPoint2)); lines.append(QLineF(tempPoint2, nextPoint)); } break; case XYCurve::MidpointVertical: for (unsigned int i = 0; i < count - 1; i++) { if (!lineSkipGaps && !connectedPointsLogical[i]) continue; curPoint = symbolPointsLogical.at(i); nextPoint = symbolPointsLogical.at(i+1); tempPoint1 = QPointF(curPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); tempPoint2 = QPointF(nextPoint.x(), curPoint.y() + (nextPoint.y()-curPoint.y())/2); lines.append(QLineF(curPoint, tempPoint1)); lines.append(QLineF(tempPoint1, tempPoint2)); lines.append(QLineF(tempPoint2, nextPoint)); } break; case XYCurve::Segments2: { int skip=0; for (unsigned int i = 0; i < count - 1; i++) { if (skip != 1) { if (!lineSkipGaps && !connectedPointsLogical[i]) { skip = 0; continue; } lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); skip++; } else skip = 0; } break; } case XYCurve::Segments3: { int skip = 0; for (unsigned int i = 0; i < count - 1; i++) { if (skip != 2) { if (!lineSkipGaps && !connectedPointsLogical[i]) { skip = 0; continue; } lines.append(QLineF(symbolPointsLogical.at(i), symbolPointsLogical.at(i+1))); skip++; } else skip = 0; } break; } case XYCurve::SplineCubicNatural: case XYCurve::SplineCubicPeriodic: case XYCurve::SplineAkimaNatural: case XYCurve::SplineAkimaPeriodic: { gsl_interp_accel *acc = gsl_interp_accel_alloc(); gsl_spline *spline = 0; double* x = new double[count]; double* y = new double[count]; for (unsigned int i = 0; i < count; i++) { x[i] = symbolPointsLogical.at(i).x(); y[i] = symbolPointsLogical.at(i).y(); } gsl_set_error_handler_off(); if (lineType == XYCurve::SplineCubicNatural) spline = gsl_spline_alloc(gsl_interp_cspline, count); else if (lineType == XYCurve::SplineCubicPeriodic) spline = gsl_spline_alloc(gsl_interp_cspline_periodic, count); else if (lineType == XYCurve::SplineAkimaNatural) spline = gsl_spline_alloc(gsl_interp_akima, count); else if (lineType == XYCurve::SplineAkimaPeriodic) spline = gsl_spline_alloc(gsl_interp_akima_periodic, count); if (!spline) { QString msg; if ( (lineType == XYCurve::SplineAkimaNatural || lineType == XYCurve::SplineAkimaPeriodic) && count < 5) msg = i18n("Error: Akima spline interpolation requires a minimum of 5 points."); else msg = i18n("Error: Couldn't initialize the spline function."); emit q->info(msg); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_interp_accel_free (acc); return; } int status = gsl_spline_init (spline, x, y, count); if (status) { //TODO: check in gsl/interp.c when GSL_EINVAL is thrown QString gslError; if (status == GSL_EINVAL) gslError = i18n("x values must be monotonically increasing."); else gslError = gslErrorToString(status); emit q->info( i18n("Error: %1").arg(gslError) ); recalcShapeAndBoundingRect(); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); return; } //create interpolating points std::vector xinterp, yinterp; double step; double xi, yi, x1, x2; for (unsigned int i = 0; i < count - 1; i++) { x1 = x[i]; x2 = x[i+1]; step=fabs(x2 - x1)/(lineInterpolationPointsCount + 1); for (xi = x1; xi < x2; xi += step) { yi = gsl_spline_eval(spline, xi, acc); xinterp.push_back(xi); yinterp.push_back(yi); } } for (unsigned int i = 0; i < xinterp.size() - 1; i++) lines.append(QLineF(xinterp[i], yinterp[i], xinterp[i+1], yinterp[i+1])); lines.append(QLineF(xinterp[xinterp.size()-1], yinterp[yinterp.size()-1], x[count-1], y[count-1])); delete[] x; delete[] y; gsl_spline_free (spline); gsl_interp_accel_free (acc); break; } } } //map the lines to scene coordinates { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), map lines to scene coordinates"); #endif lines = cSystem->mapLogicalToScene(lines); } { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::updateLines(), calculate new line path"); #endif //new line path for (const auto& line : lines) { linePath.moveTo(line.p1()); linePath.lineTo(line.p2()); } } updateFilling(); recalcShapeAndBoundingRect(); } /*! recalculates the painter path for the drop lines. Called each time when the type of the drop lines is changed. */ void XYCurvePrivate::updateDropLines() { dropLinePath = QPainterPath(); if (dropLineType == XYCurve::NoDropLine) { recalcShapeAndBoundingRect(); return; } //calculate drop lines QVector lines; float xMin = 0; float yMin = 0; xMin = plot->xMin(); yMin = plot->yMin(); switch (dropLineType) { case XYCurve::NoDropLine: break; case XYCurve::DropLineX: for(int i=0; i(yColumn)->minimum())) ); } break; case XYCurve::DropLineXMaxBaseline: for(int i=0; i(yColumn)->maximum())) ); } break; } //map the drop lines to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines for (const auto& line : lines) { dropLinePath.moveTo(line.p1()); dropLinePath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateSymbols() { symbolsPath = QPainterPath(); if (symbolsStyle != Symbol::NoSymbols) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); symbolsPath.addPath(trafo.map(path)); } } recalcShapeAndBoundingRect(); } /*! recreates the value strings to be shown and recalculates their draw position. */ void XYCurvePrivate::updateValues() { DEBUG("XYCurvePrivate::updateValues()"); valuesPath = QPainterPath(); valuesPoints.clear(); valuesStrings.clear(); if (valuesType == XYCurve::NoValues) { recalcShapeAndBoundingRect(); return; } //determine the value string for all points that are currently visible in the plot switch (valuesType) { case XYCurve::NoValues: case XYCurve::ValuesX: { for(int i=0; ivaluesColumn->rowCount()) endRow = valuesColumn->rowCount(); else endRow = symbolPointsLogical.size(); AbstractColumn::ColumnMode xColMode = valuesColumn->columnMode(); for (int i = 0; i < endRow; ++i) { if (!visiblePoints[i]) continue; if ( !valuesColumn->isValid(i) || valuesColumn->isMasked(i) ) continue; switch (xColMode) { case AbstractColumn::Numeric: case AbstractColumn::Integer: valuesStrings << valuesPrefix + QString::number(valuesColumn->valueAt(i)) + valuesSuffix; break; case AbstractColumn::Text: valuesStrings << valuesPrefix + valuesColumn->textAt(i) + valuesSuffix; case AbstractColumn::DateTime: case AbstractColumn::Month: case AbstractColumn::Day: //TODO break; } } } } //Calculate the coordinates where to paint the value strings. //The coordinates depend on the actual size of the string. QPointF tempPoint; QFontMetrics fm(valuesFont); qreal w; qreal h=fm.ascent(); for (int i=0; i1000) { recalcShapeAndBoundingRect(); return; } QVector fillLines; //if there're no interpolation lines available (XYCurve::NoLine selected), create line-interpolation, //use already available lines otherwise. if (!lines.isEmpty()) fillLines = lines; else { for (int i=0; imapLogicalToScene(fillLines); //no lines available (no points), nothing to do if (fillLines.isEmpty()) return; } //create polygon(s): //1. Depending on the current zoom-level, only a subset of the curve may be visible in the plot //and more of the filling area should be shown than the area defined by the start and end points of the currently visible points. //We check first whether the curve crosses the boundaries of the plot and determine new start and end points and put them to the boundaries. //2. Furthermore, depending on the current filling type we determine the end point (x- or y-coordinate) where all polygons are closed at the end. QPolygonF pol; QPointF start = fillLines.at(0).p1(); //starting point of the current polygon, initialize with the first visible point QPointF end = fillLines.at(fillLines.size()-1).p2(); //starting point of the current polygon, initialize with the last visible point const QPointF& first = symbolPointsLogical.at(0); //first point of the curve, may not be visible currently const QPointF& last = symbolPointsLogical.at(symbolPointsLogical.size()-1);//first point of the curve, may not be visible currently QPointF edge; float xEnd=0, yEnd=0; if (fillingPosition == XYCurve::FillingAbove) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())).y(); } else if (fillingPosition == XYCurve::FillingBelow) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } //coordinate at which to close all polygons yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).y(); } else if (fillingPosition == XYCurve::FillingZeroBaseline) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.y(), edge.y())) { if (plot->yMax() > 0) { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMax())); } else { if (first.x() < plot->xMin()) start = edge; else if (first.x() > plot->xMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else start = cSystem->mapLogicalToScene(QPointF(first.x(), plot->yMin())); } } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.y(), edge.y())) { if (plot->yMax() > 0) { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMax())); } else { if (last.x() < plot->xMin()) end = edge; else if (last.x() > plot->xMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); else end = cSystem->mapLogicalToScene(QPointF(last.x(), plot->yMin())); } } yEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin()>0 ? plot->yMin() : 0)).y(); } else if (fillingPosition == XYCurve::FillingLeft) { edge = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMax(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMax(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())).x(); } else { //FillingRight edge = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMin())); //start point if (AbstractCoordinateSystem::essentiallyEqual(start.x(), edge.x())) { if (first.y() < plot->yMin()) start = edge; else if (first.y() > plot->yMax()) start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else start = cSystem->mapLogicalToScene(QPointF(plot->xMin(), first.y())); } //end point if (AbstractCoordinateSystem::essentiallyEqual(end.x(), edge.x())) { if (last.y() < plot->yMin()) end = edge; else if (last.y() > plot->yMax()) end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), plot->yMax())); else end = cSystem->mapLogicalToScene(QPointF(plot->xMin(), last.y())); } //coordinate at which to close all polygons xEnd = cSystem->mapLogicalToScene(QPointF(plot->xMax(), plot->yMin())).x(); } if (start != fillLines.at(0).p1()) pol << start; QPointF p1, p2; for (int i=0; icheck whether we have a break in between. const bool gap = false; //TODO if (!gap) { //-> we have no break in the curve -> connect the points by a horizontal/vertical line pol << fillLines.at(i-1).p2() << p1; } else { //-> we have a break in the curve -> close the polygon add it to the polygon list and start a new polygon if (fillingPosition==XYCurve::FillingAbove || fillingPosition==XYCurve::FillingBelow || fillingPosition==XYCurve::FillingZeroBaseline) { pol << QPointF(fillLines.at(i-1).p2().x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, fillLines.at(i-1).p2().y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; pol.clear(); start = p1; } } pol << p1 << p2; } if (p2!=end) pol << end; //close the last polygon if (fillingPosition==XYCurve::FillingAbove || fillingPosition==XYCurve::FillingBelow || fillingPosition==XYCurve::FillingZeroBaseline) { pol << QPointF(end.x(), yEnd); pol << QPointF(start.x(), yEnd); } else { pol << QPointF(xEnd, end.y()); pol << QPointF(xEnd, start.y()); } fillPolygons << pol; recalcShapeAndBoundingRect(); } void XYCurvePrivate::updateErrorBars() { errorBarsPath = QPainterPath(); if (xErrorType==XYCurve::NoError && yErrorType==XYCurve::NoError) { recalcShapeAndBoundingRect(); return; } QVector lines; float errorPlus, errorMinus; //the cap size for the errorbars is given in scene units. //determine first the (half of the) cap size in logical units: // * take the first visible point in logical units // * convert it to scene units // * add to this point an offset corresponding to the cap size in scene units // * convert this point back to logical units // * subtract from this point the original coordinates (without the new offset) // to determine the cap size in logical units. float capSizeX = 0; float capSizeY = 0; if (errorBarsType != XYCurve::ErrorBarsSimple && !symbolPointsLogical.isEmpty()) { //determine the index of the first visible point size_t i = 0; while (i no error bars to draw //cap size for x-error bars QPointF pointScene = cSystem->mapLogicalToScene(symbolPointsLogical.at((int)i)); pointScene.setY(pointScene.y()-errorBarsCapSize); QPointF pointLogical = cSystem->mapSceneToLogical(pointScene); capSizeX = (pointLogical.y() - symbolPointsLogical.at((int)i).y())/2; //cap size for y-error bars pointScene = cSystem->mapLogicalToScene(symbolPointsLogical.at((int)i)); pointScene.setX(pointScene.x()+errorBarsCapSize); pointLogical = cSystem->mapSceneToLogical(pointScene); capSizeY = (pointLogical.x() - symbolPointsLogical.at((int)i).x())/2; } for (int i=0; i < symbolPointsLogical.size(); ++i) { if (!visiblePoints[i]) continue; const QPointF& point = symbolPointsLogical.at(i); //error bars for x if (xErrorType != XYCurve::NoError) { //determine the values for the errors if (xErrorPlusColumn && xErrorPlusColumn->isValid(i) && !xErrorPlusColumn->isMasked(i)) errorPlus = xErrorPlusColumn->valueAt(i); else errorPlus = 0; if (xErrorType==XYCurve::SymmetricError) errorMinus = errorPlus; else { if (xErrorMinusColumn && xErrorMinusColumn->isValid(i) && !xErrorMinusColumn->isMasked(i)) errorMinus = xErrorMinusColumn->valueAt(i); else errorMinus = 0; } //draw the error bars switch (errorBarsType) { case XYCurve::ErrorBarsSimple: lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); break; case XYCurve::ErrorBarsWithEnds: lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()), QPointF(point.x()+errorPlus, point.y()))); if (errorMinus!=0) { lines.append(QLineF(QPointF(point.x()-errorMinus, point.y()-capSizeX), QPointF(point.x()-errorMinus, point.y()+capSizeX))); } if (errorPlus!=0) { lines.append(QLineF(QPointF(point.x()+errorPlus, point.y()-capSizeX), QPointF(point.x()+errorPlus, point.y()+capSizeX))); } break; } } //error bars for y if (yErrorType != XYCurve::NoError) { //determine the values for the errors if (yErrorPlusColumn && yErrorPlusColumn->isValid(i) && !yErrorPlusColumn->isMasked(i)) errorPlus = yErrorPlusColumn->valueAt(i); else errorPlus = 0; if (yErrorType == XYCurve::SymmetricError) errorMinus = errorPlus; else { if (yErrorMinusColumn && yErrorMinusColumn->isValid(i) && !yErrorMinusColumn->isMasked(i) ) errorMinus = yErrorMinusColumn->valueAt(i); else errorMinus = 0; } //draw the error bars switch (errorBarsType) { case XYCurve::ErrorBarsSimple: lines.append(QLineF(QPointF(point.x(), point.y()-errorMinus), QPointF(point.x(), point.y()+errorPlus))); break; case XYCurve::ErrorBarsWithEnds: lines.append(QLineF(QPointF(point.x(), point.y()-errorMinus), QPointF(point.x(), point.y()+errorPlus))); if (errorMinus != 0) lines.append(QLineF(QPointF(point.x()-capSizeY, point.y()-errorMinus), QPointF(point.x()+capSizeY, point.y()-errorMinus))); if (errorPlus != 0) lines.append(QLineF(QPointF(point.x()-capSizeY, point.y()+errorPlus), QPointF(point.x()+capSizeY, point.y()+errorPlus))); break; } } } //map the error bars to scene coordinates lines = cSystem->mapLogicalToScene(lines); //new painter path for the drop lines for (const auto& line : lines) { errorBarsPath.moveTo(line.p1()); errorBarsPath.lineTo(line.p2()); } recalcShapeAndBoundingRect(); } /*! recalculates the outer bounds and the shape of the curve. */ void XYCurvePrivate::recalcShapeAndBoundingRect() { DEBUG("XYCurvePrivate::recalcShapeAndBoundingRect() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::recalcShapeAndBoundingRect()"); #endif prepareGeometryChange(); curveShape = QPainterPath(); if (lineType != XYCurve::NoLine) curveShape.addPath(WorksheetElement::shapeFromPath(linePath, linePen)); if (dropLineType != XYCurve::NoDropLine) curveShape.addPath(WorksheetElement::shapeFromPath(dropLinePath, dropLinePen)); if (symbolsStyle != Symbol::NoSymbols) curveShape.addPath(symbolsPath); if (valuesType != XYCurve::NoValues) curveShape.addPath(valuesPath); if (xErrorType != XYCurve::NoError || yErrorType != XYCurve::NoError) curveShape.addPath(WorksheetElement::shapeFromPath(errorBarsPath, errorBarsPen)); boundingRectangle = curveShape.boundingRect(); for (const auto& pol : fillPolygons) boundingRectangle = boundingRectangle.united(pol.boundingRect()); //TODO: when the selection is painted, line intersections are visible. //simplified() removes those artifacts but is horrible slow for curves with large number of points. //search for an alternative. //curveShape = curveShape.simplified(); DEBUG(" Calling updatePixmap()"); updatePixmap(); } void XYCurvePrivate::draw(QPainter* painter) { #ifdef PERFTRACE_CURVES PERFTRACE(name().toLatin1() + ", XYCurvePrivate::draw()"); #endif //draw filling if (fillingPosition != XYCurve::NoFilling) { painter->setOpacity(fillingOpacity); painter->setPen(Qt::SolidLine); drawFilling(painter); } //draw lines if (lineType != XYCurve::NoLine) { painter->setOpacity(lineOpacity); painter->setPen(linePen); painter->setBrush(Qt::NoBrush); painter->drawPath(linePath); } //draw drop lines if (dropLineType != XYCurve::NoDropLine) { painter->setOpacity(dropLineOpacity); painter->setPen(dropLinePen); painter->setBrush(Qt::NoBrush); painter->drawPath(dropLinePath); } //draw error bars if ( (xErrorType != XYCurve::NoError) || (yErrorType != XYCurve::NoError) ) { painter->setOpacity(errorBarsOpacity); painter->setPen(errorBarsPen); painter->setBrush(Qt::NoBrush); painter->drawPath(errorBarsPath); } //draw symbols if (symbolsStyle != Symbol::NoSymbols) { painter->setOpacity(symbolsOpacity); painter->setPen(symbolsPen); painter->setBrush(symbolsBrush); drawSymbols(painter); } //draw values if (valuesType != XYCurve::NoValues) { painter->setOpacity(valuesOpacity); //don't use any painter pen, since this will force QPainter to render the text outline which is expensive painter->setPen(Qt::NoPen); painter->setBrush(valuesColor); drawValues(painter); } } void XYCurvePrivate::updatePixmap() { DEBUG("XYCurvePrivate::updatePixmap() m_suppressRecalc = " << m_suppressRecalc); if (m_suppressRecalc) return; WAIT_CURSOR; m_hoverEffectImageIsDirty = true; m_selectionEffectImageIsDirty = true; if (boundingRectangle.width() == 0 || boundingRectangle.height() == 0) { DEBUG(" boundingRectangle.width() or boundingRectangle.height() == 0"); m_pixmap = QPixmap(); RESET_CURSOR; return; } QPixmap pixmap(ceil(boundingRectangle.width()), ceil(boundingRectangle.height())); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); painter.setRenderHint(QPainter::Antialiasing, true); painter.translate(-boundingRectangle.topLeft()); draw(&painter); painter.end(); m_pixmap = pixmap; update(); RESET_CURSOR; } /*! Reimplementation of QGraphicsItem::paint(). This function does the actual painting of the curve. \sa QGraphicsItem::paint(). */ void XYCurvePrivate::paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget) { Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); if ( KSharedConfig::openConfig()->group("Settings_Worksheet").readEntry("DoubleBuffering", true) ) painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); //draw the cached pixmap (fast) else draw(painter); //draw directly again (slow) if (m_hovered && !isSelected() && !m_printing) { if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Shadow)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); return; } if (isSelected() && !m_printing) { if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(QApplication::palette().color(QPalette::Highlight)); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); return; } } /*! Drawing of symbolsPath is very slow, so we draw every symbol in the loop which is much faster (factor 10) */ void XYCurvePrivate::drawSymbols(QPainter* painter) { QPainterPath path = Symbol::pathFromStyle(symbolsStyle); QTransform trafo; trafo.scale(symbolsSize, symbolsSize); path = trafo.map(path); trafo.reset(); if (symbolsRotationAngle != 0) { trafo.rotate(symbolsRotationAngle); path = trafo.map(path); } for (const auto& point : symbolPointsScene) { trafo.reset(); trafo.translate(point.x(), point.y()); painter->drawPath(trafo.map(path)); } } void XYCurvePrivate::drawValues(QPainter* painter) { QTransform trafo; QPainterPath path; for (int i=0; idrawPath(trafo.map(path)); } } void XYCurvePrivate::drawFilling(QPainter* painter) { for (const auto& pol : fillPolygons) { QRectF rect = pol.boundingRect(); if (fillingType == PlotArea::Color) { switch (fillingColorStyle) { case PlotArea::SingleColor: { painter->setBrush(QBrush(fillingFirstColor)); break; } case PlotArea::HorizontalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::VerticalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomLeft()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::TopLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.topLeft(), rect.bottomRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::BottomLeftDiagonalLinearGradient: { QLinearGradient linearGrad(rect.bottomLeft(), rect.topRight()); linearGrad.setColorAt(0, fillingFirstColor); linearGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(linearGrad)); break; } case PlotArea::RadialGradient: { QRadialGradient radialGrad(rect.center(), rect.width()/2); radialGrad.setColorAt(0, fillingFirstColor); radialGrad.setColorAt(1, fillingSecondColor); painter->setBrush(QBrush(radialGrad)); break; } } } else if (fillingType == PlotArea::Image) { if ( !fillingFileName.trimmed().isEmpty() ) { QPixmap pix(fillingFileName); switch (fillingImageStyle) { case PlotArea::ScaledCropped: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatioByExpanding, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Scaled: pix = pix.scaled(rect.size().toSize(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::ScaledAspectRatio: pix = pix.scaled(rect.size().toSize(), Qt::KeepAspectRatio, Qt::SmoothTransformation); painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); break; case PlotArea::Centered: { QPixmap backpix(rect.size().toSize()); backpix.fill(); QPainter p(&backpix); p.drawPixmap(QPointF(0, 0), pix); p.end(); painter->setBrush(QBrush(backpix)); painter->setBrushOrigin(-pix.size().width()/2, -pix.size().height()/2); break; } case PlotArea::Tiled: painter->setBrush(QBrush(pix)); break; case PlotArea::CenterTiled: painter->setBrush(QBrush(pix)); painter->setBrushOrigin(pix.size().width()/2, pix.size().height()/2); } } } else if (fillingType == PlotArea::Pattern) painter->setBrush(QBrush(fillingFirstColor, fillingBrushStyle)); painter->drawPolygon(pol); } } void XYCurvePrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { m_hovered = true; emit q->hovered(); update(); } } void XYCurvePrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; emit q->unhovered(); update(); } } void XYCurvePrivate::setPrinting(bool on) { m_printing = on; } void XYCurvePrivate::suppressRetransform(bool on) { m_suppressRetransform = on; m_suppressRecalc = on; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYCurve); writer->writeStartElement( "xyCurve" ); writeBasicAttributes( writer ); writeCommentElement( writer ); //general writer->writeStartElement( "general" ); WRITE_COLUMN(d->xColumn, xColumn); WRITE_COLUMN(d->yColumn, yColumn); writer->writeAttribute( "visible", QString::number(d->isVisible()) ); writer->writeEndElement(); //Line writer->writeStartElement( "lines" ); writer->writeAttribute( "type", QString::number(d->lineType) ); writer->writeAttribute( "skipGaps", QString::number(d->lineSkipGaps) ); writer->writeAttribute( "interpolationPointsCount", QString::number(d->lineInterpolationPointsCount) ); WRITE_QPEN(d->linePen); writer->writeAttribute( "opacity", QString::number(d->lineOpacity) ); writer->writeEndElement(); //Drop lines writer->writeStartElement( "dropLines" ); writer->writeAttribute( "type", QString::number(d->dropLineType) ); WRITE_QPEN(d->dropLinePen); writer->writeAttribute( "opacity", QString::number(d->dropLineOpacity) ); writer->writeEndElement(); //Symbols writer->writeStartElement( "symbols" ); writer->writeAttribute( "symbolsStyle", QString::number(d->symbolsStyle) ); writer->writeAttribute( "opacity", QString::number(d->symbolsOpacity) ); writer->writeAttribute( "rotation", QString::number(d->symbolsRotationAngle) ); writer->writeAttribute( "size", QString::number(d->symbolsSize) ); WRITE_QBRUSH(d->symbolsBrush); WRITE_QPEN(d->symbolsPen); writer->writeEndElement(); //Values writer->writeStartElement( "values" ); writer->writeAttribute( "type", QString::number(d->valuesType) ); WRITE_COLUMN(d->valuesColumn, valuesColumn); writer->writeAttribute( "position", QString::number(d->valuesPosition) ); writer->writeAttribute( "distance", QString::number(d->valuesDistance) ); writer->writeAttribute( "rotation", QString::number(d->valuesRotationAngle) ); writer->writeAttribute( "opacity", QString::number(d->valuesOpacity) ); //TODO values format and precision writer->writeAttribute( "prefix", d->valuesPrefix ); writer->writeAttribute( "suffix", d->valuesSuffix ); WRITE_QCOLOR(d->valuesColor); WRITE_QFONT(d->valuesFont); writer->writeEndElement(); //Filling writer->writeStartElement( "filling" ); writer->writeAttribute( "position", QString::number(d->fillingPosition) ); writer->writeAttribute( "type", QString::number(d->fillingType) ); writer->writeAttribute( "colorStyle", QString::number(d->fillingColorStyle) ); writer->writeAttribute( "imageStyle", QString::number(d->fillingImageStyle) ); writer->writeAttribute( "brushStyle", QString::number(d->fillingBrushStyle) ); writer->writeAttribute( "firstColor_r", QString::number(d->fillingFirstColor.red()) ); writer->writeAttribute( "firstColor_g", QString::number(d->fillingFirstColor.green()) ); writer->writeAttribute( "firstColor_b", QString::number(d->fillingFirstColor.blue()) ); writer->writeAttribute( "secondColor_r", QString::number(d->fillingSecondColor.red()) ); writer->writeAttribute( "secondColor_g", QString::number(d->fillingSecondColor.green()) ); writer->writeAttribute( "secondColor_b", QString::number(d->fillingSecondColor.blue()) ); writer->writeAttribute( "fileName", d->fillingFileName ); writer->writeAttribute( "opacity", QString::number(d->fillingOpacity) ); writer->writeEndElement(); //Error bars writer->writeStartElement( "errorBars" ); writer->writeAttribute( "xErrorType", QString::number(d->xErrorType) ); WRITE_COLUMN(d->xErrorPlusColumn, xErrorPlusColumn); WRITE_COLUMN(d->xErrorMinusColumn, xErrorMinusColumn); writer->writeAttribute( "yErrorType", QString::number(d->yErrorType) ); WRITE_COLUMN(d->yErrorPlusColumn, yErrorPlusColumn); WRITE_COLUMN(d->yErrorMinusColumn, yErrorMinusColumn); writer->writeAttribute( "type", QString::number(d->errorBarsType) ); writer->writeAttribute( "capSize", QString::number(d->errorBarsCapSize) ); WRITE_QPEN(d->errorBarsPen); writer->writeAttribute( "opacity", QString::number(d->errorBarsOpacity) ); writer->writeEndElement(); writer->writeEndElement(); //close "xyCurve" section } //! Load from XML bool XYCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYCurve); if (!reader->isStartElement() || reader->name() != "xyCurve") { reader->raiseError(i18n("no xy-curve element found")); return false; } if (!readBasicAttributes(reader)) return false; QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; } else if (reader->name() == "general") { attribs = reader->attributes(); READ_COLUMN(xColumn); READ_COLUMN(yColumn); str = attribs.value("visible").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'visible'")); else d->setVisible(str.toInt()); } else if (!preview && reader->name() == "lines") { attribs = reader->attributes(); READ_INT_VALUE("type", lineType, XYCurve::LineType); READ_INT_VALUE("skipGaps", lineSkipGaps, int); READ_INT_VALUE("interpolationPointsCount", lineInterpolationPointsCount, int); READ_QPEN(d->linePen); READ_DOUBLE_VALUE("opacity", lineOpacity); } else if (!preview && reader->name() == "dropLines") { attribs = reader->attributes(); READ_INT_VALUE("type", dropLineType, XYCurve::DropLineType); READ_QPEN(d->dropLinePen); READ_DOUBLE_VALUE("opacity", dropLineOpacity); } else if (!preview && reader->name() == "symbols") { attribs = reader->attributes(); READ_INT_VALUE("symbolsStyle", symbolsStyle, Symbol::Style); READ_DOUBLE_VALUE("opacity", symbolsOpacity); READ_DOUBLE_VALUE("rotation", symbolsRotationAngle); READ_DOUBLE_VALUE("size", symbolsSize); READ_QBRUSH(d->symbolsBrush); READ_QPEN(d->symbolsPen); } else if (!preview && reader->name() == "values") { attribs = reader->attributes(); READ_INT_VALUE("type", valuesType, XYCurve::ValuesType); READ_COLUMN(valuesColumn); READ_INT_VALUE("position", valuesPosition, XYCurve::ValuesPosition); READ_DOUBLE_VALUE("distance", valuesDistance); READ_DOUBLE_VALUE("rotation", valuesRotationAngle); READ_DOUBLE_VALUE("opacity", valuesOpacity); //don't produce any warning if no prefix or suffix is set (empty string is allowd here in xml) d->valuesPrefix = attribs.value("prefix").toString(); d->valuesSuffix = attribs.value("suffix").toString(); READ_QCOLOR(d->valuesColor); READ_QFONT(d->valuesFont); } else if (!preview && reader->name() == "filling") { attribs = reader->attributes(); READ_INT_VALUE("position", fillingPosition, XYCurve::FillingPosition); READ_INT_VALUE("type", fillingType, PlotArea::BackgroundType); READ_INT_VALUE("colorStyle", fillingColorStyle, PlotArea::BackgroundColorStyle); READ_INT_VALUE("imageStyle", fillingImageStyle, PlotArea::BackgroundImageStyle ); READ_INT_VALUE("brushStyle", fillingBrushStyle, Qt::BrushStyle); str = attribs.value("firstColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_r")); else d->fillingFirstColor.setRed(str.toInt()); str = attribs.value("firstColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_g")); else d->fillingFirstColor.setGreen(str.toInt()); str = attribs.value("firstColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("firstColor_b")); else d->fillingFirstColor.setBlue(str.toInt()); str = attribs.value("secondColor_r").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_r")); else d->fillingSecondColor.setRed(str.toInt()); str = attribs.value("secondColor_g").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_g")); else d->fillingSecondColor.setGreen(str.toInt()); str = attribs.value("secondColor_b").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("secondColor_b")); else d->fillingSecondColor.setBlue(str.toInt()); READ_STRING_VALUE("fileName", fillingFileName); READ_DOUBLE_VALUE("opacity", fillingOpacity); } else if (!preview && reader->name() == "errorBars") { attribs = reader->attributes(); READ_INT_VALUE("xErrorType", xErrorType, XYCurve::ErrorType); READ_COLUMN(xErrorPlusColumn); READ_COLUMN(xErrorMinusColumn); READ_INT_VALUE("yErrorType", yErrorType, XYCurve::ErrorType); READ_COLUMN(yErrorPlusColumn); READ_COLUMN(yErrorMinusColumn); READ_INT_VALUE("type", errorBarsType, XYCurve::ErrorBarsType); READ_DOUBLE_VALUE("capSize", errorBarsCapSize); READ_QPEN(d->errorBarsPen); READ_DOUBLE_VALUE("opacity", errorBarsOpacity); } } return true; } //############################################################################## //######################### Theme management ################################## //############################################################################## void XYCurve::loadThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); int index = parentAspect()->indexOfChild(this); const CartesianPlot* plot = dynamic_cast(parentAspect()); QColor themeColor; if (indexthemeColorPalette().size()) themeColor = plot->themeColorPalette().at(index); else { if (plot->themeColorPalette().size()) themeColor = plot->themeColorPalette().last(); } QPen p; Q_D(XYCurve); d->m_suppressRecalc = true; //Line p.setStyle((Qt::PenStyle)group.readEntry("LineStyle", (int)this->linePen().style())); p.setWidthF(group.readEntry("LineWidth", this->linePen().widthF())); p.setColor(themeColor); this->setLinePen(p); this->setLineOpacity(group.readEntry("LineOpacity", this->lineOpacity())); //Drop line p.setStyle((Qt::PenStyle)group.readEntry("DropLineStyle",(int) this->dropLinePen().style())); p.setWidthF(group.readEntry("DropLineWidth", this->dropLinePen().widthF())); p.setColor(themeColor); this->setDropLinePen(p); this->setDropLineOpacity(group.readEntry("DropLineOpacity", this->dropLineOpacity())); //Symbol this->setSymbolsOpacity(group.readEntry("SymbolOpacity", this->symbolsOpacity())); QBrush brush = symbolsBrush(); brush.setColor(themeColor); this->setSymbolsBrush(brush); p = symbolsPen(); p.setColor(themeColor); this->setSymbolsPen(p); //Values this->setValuesOpacity(group.readEntry("ValuesOpacity", this->valuesOpacity())); this->setValuesColor(group.readEntry("ValuesColor", this->valuesColor())); //Filling this->setFillingBrushStyle((Qt::BrushStyle)group.readEntry("FillingBrushStyle",(int) this->fillingBrushStyle())); this->setFillingColorStyle((PlotArea::BackgroundColorStyle)group.readEntry("FillingColorStyle",(int) this->fillingColorStyle())); this->setFillingOpacity(group.readEntry("FillingOpacity", this->fillingOpacity())); this->setFillingPosition((XYCurve::FillingPosition)group.readEntry("FillingPosition",(int) this->fillingPosition())); this->setFillingSecondColor(group.readEntry("FillingSecondColor",(QColor) this->fillingSecondColor())); this->setFillingFirstColor(themeColor); this->setFillingType((PlotArea::BackgroundType)group.readEntry("FillingType",(int) this->fillingType())); //Error Bars p.setStyle((Qt::PenStyle)group.readEntry("ErrorBarsStyle",(int) this->errorBarsPen().style())); p.setWidthF(group.readEntry("ErrorBarsWidth", this->errorBarsPen().widthF())); p.setColor(themeColor); this->setErrorBarsPen(p); this->setErrorBarsOpacity(group.readEntry("ErrorBarsOpacity",this->errorBarsOpacity())); d->m_suppressRecalc = false; d->recalcShapeAndBoundingRect(); } void XYCurve::saveThemeConfig(const KConfig& config) { KConfigGroup group = config.group("XYCurve"); //Drop line group.writeEntry("DropLineColor",(QColor) this->dropLinePen().color()); group.writeEntry("DropLineStyle",(int) this->dropLinePen().style()); group.writeEntry("DropLineWidth", this->dropLinePen().widthF()); group.writeEntry("DropLineOpacity",this->dropLineOpacity()); //Error Bars group.writeEntry("ErrorBarsCapSize",this->errorBarsCapSize()); group.writeEntry("ErrorBarsOpacity",this->errorBarsOpacity()); group.writeEntry("ErrorBarsColor",(QColor) this->errorBarsPen().color()); group.writeEntry("ErrorBarsStyle",(int) this->errorBarsPen().style()); group.writeEntry("ErrorBarsWidth", this->errorBarsPen().widthF()); //Filling group.writeEntry("FillingBrushStyle",(int) this->fillingBrushStyle()); group.writeEntry("FillingColorStyle",(int) this->fillingColorStyle()); group.writeEntry("FillingOpacity", this->fillingOpacity()); group.writeEntry("FillingPosition",(int) this->fillingPosition()); group.writeEntry("FillingSecondColor",(QColor) this->fillingSecondColor()); group.writeEntry("FillingType",(int) this->fillingType()); //Line group.writeEntry("LineOpacity", this->lineOpacity()); group.writeEntry("LineStyle",(int) this->linePen().style()); group.writeEntry("LineWidth", this->linePen().widthF()); //Symbol group.writeEntry("SymbolOpacity", this->symbolsOpacity()); //Values group.writeEntry("ValuesOpacity", this->valuesOpacity()); group.writeEntry("ValuesColor", (QColor) this->valuesColor()); group.writeEntry("ValuesFont", this->valuesFont()); int index = parentAspect()->indexOfChild(this); if(index<5) { KConfigGroup themeGroup = config.group("Theme"); for(int i = index; i<5; i++) { QString s = "ThemePaletteColor" + QString::number(i+1); themeGroup.writeEntry(s,(QColor) this->linePen().color()); } } } diff --git a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp index c6dacf547..bcfa3d0c7 100644 --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -1,417 +1,417 @@ /*************************************************************************** File : XYDataReductionCurve.cpp Project : LabPlot Description : A xy-curve defined by a data reduction -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYDataReductionCurve \brief A xy-curve defined by a data reduction \ingroup worksheet */ #include "XYDataReductionCurve.h" #include "XYDataReductionCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include XYDataReductionCurve::XYDataReductionCurve(const QString& name) : XYAnalysisCurve(name, new XYDataReductionCurvePrivate(this)) { } XYDataReductionCurve::XYDataReductionCurve(const QString& name, XYDataReductionCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYDataReductionCurve::~XYDataReductionCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDataReductionCurve::recalculate() { Q_D(XYDataReductionCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDataReductionCurve::icon() const { return QIcon::fromTheme("labplot-xy-data-reduction-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, XYDataReductionCurve::DataReductionData, dataReductionData, dataReductionData) const XYDataReductionCurve::DataReductionResult& XYDataReductionCurve::dataReductionResult() const { Q_D(const XYDataReductionCurve); return d->dataReductionResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYDataReductionCurve, SetDataReductionData, XYDataReductionCurve::DataReductionData, dataReductionData, recalculate); void XYDataReductionCurve::setDataReductionData(const XYDataReductionCurve::DataReductionData& reductionData) { Q_D(XYDataReductionCurve); - exec(new XYDataReductionCurveSetDataReductionDataCmd(d, reductionData, i18n("%1: set options and perform the data reduction"))); + exec(new XYDataReductionCurveSetDataReductionDataCmd(d, reductionData, ki18n("%1: set options and perform the data reduction"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDataReductionCurvePrivate::XYDataReductionCurvePrivate(XYDataReductionCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYDataReductionCurvePrivate::~XYDataReductionCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYDataReductionCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create dataReduction result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result dataReductionResult = XYDataReductionCurve::DataReductionResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the data reduction to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (dataReductionData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = dataReductionData.xRange.first(); xmax = dataReductionData.xRange.last(); } for (int row = 0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to use const size_t n = (size_t)xdataVector.size(); if (n < 2) { dataReductionResult.available = true; dataReductionResult.valid = false; dataReductionResult.status = i18n("Not enough data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // dataReduction settings const nsl_geom_linesim_type type = dataReductionData.type; const double tol = dataReductionData.tolerance; const double tol2 = dataReductionData.tolerance2; DEBUG("n =" << n); DEBUG("type:" << nsl_geom_linesim_type_name[type]); DEBUG("tolerance/step:" << tol); DEBUG("tolerance2/repeat/maxtol/region:" << tol2); /////////////////////////////////////////////////////////// emit q->completed(10); size_t npoints = 0; double calcTolerance = 0; // calculated tolerance from Douglas-Peucker variant size_t *index = (size_t *) malloc(n*sizeof(size_t)); switch (type) { case nsl_geom_linesim_type_douglas_peucker_variant: // tol used as number of points npoints = tol; calcTolerance = nsl_geom_linesim_douglas_peucker_variant(xdata, ydata, n, npoints, index); break; case nsl_geom_linesim_type_douglas_peucker: npoints = nsl_geom_linesim_douglas_peucker(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_nthpoint: // tol used as step npoints = nsl_geom_linesim_nthpoint(n, (int)tol, index); break; case nsl_geom_linesim_type_raddist: npoints = nsl_geom_linesim_raddist(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_perpdist: // tol2 used as repeat npoints = nsl_geom_linesim_perpdist_repeat(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_interp: npoints = nsl_geom_linesim_interp(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_visvalingam_whyatt: npoints = nsl_geom_linesim_visvalingam_whyatt(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_reumann_witkam: npoints = nsl_geom_linesim_reumann_witkam(xdata, ydata, n, tol, index); break; case nsl_geom_linesim_type_opheim: npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; case nsl_geom_linesim_type_lang: // tol2 used as region npoints = nsl_geom_linesim_opheim(xdata, ydata, n, tol, tol2, index); break; } DEBUG("npoints =" << npoints); if (type == nsl_geom_linesim_type_douglas_peucker_variant) { DEBUG("calculated tolerance =" << calcTolerance); } else Q_UNUSED(calcTolerance); emit q->completed(80); xVector->resize((int)npoints); yVector->resize((int)npoints); for (int i = 0; i < (int)npoints; i++) { (*xVector)[i] = xdata[index[i]]; (*yVector)[i] = ydata[index[i]]; } emit q->completed(90); const double posError = nsl_geom_linesim_positional_squared_error(xdata, ydata, n, index); const double areaError = nsl_geom_linesim_area_error(xdata, ydata, n, index); free(index); /////////////////////////////////////////////////////////// //write the result dataReductionResult.available = true; dataReductionResult.valid = true; if (npoints > 0) dataReductionResult.status = QString("OK"); else dataReductionResult.status = QString("FAILURE"); dataReductionResult.elapsedTime = timer.elapsed(); dataReductionResult.npoints = npoints; dataReductionResult.posError = posError; dataReductionResult.areaError = areaError; //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; emit q->completed(100); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDataReductionCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDataReductionCurve); writer->writeStartElement("xyDataReductionCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-dataReduction-curve specific information // dataReduction data writer->writeStartElement("dataReductionData"); writer->writeAttribute( "autoRange", QString::number(d->dataReductionData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->dataReductionData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->dataReductionData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->dataReductionData.type) ); writer->writeAttribute( "autoTolerance", QString::number(d->dataReductionData.autoTolerance) ); writer->writeAttribute( "tolerance", QString::number(d->dataReductionData.tolerance) ); writer->writeAttribute( "autoTolerance2", QString::number(d->dataReductionData.autoTolerance2) ); writer->writeAttribute( "tolerance2", QString::number(d->dataReductionData.tolerance2) ); writer->writeEndElement();// dataReductionData // dataReduction results (generated columns) writer->writeStartElement("dataReductionResult"); writer->writeAttribute( "available", QString::number(d->dataReductionResult.available) ); writer->writeAttribute( "valid", QString::number(d->dataReductionResult.valid) ); writer->writeAttribute( "status", d->dataReductionResult.status ); writer->writeAttribute( "time", QString::number(d->dataReductionResult.elapsedTime) ); writer->writeAttribute( "npoints", QString::number(d->dataReductionResult.npoints) ); writer->writeAttribute( "posError", QString::number(d->dataReductionResult.posError) ); writer->writeAttribute( "areaError", QString::number(d->dataReductionResult.areaError) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"dataReductionResult" writer->writeEndElement(); //"xyDataReductionCurve" } //! Load from XML bool XYDataReductionCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDataReductionCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyDataReductionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "dataReductionData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", dataReductionData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", dataReductionData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", dataReductionData.xRange.last()); READ_INT_VALUE("type", dataReductionData.type, nsl_geom_linesim_type); READ_INT_VALUE("autoTolerance", dataReductionData.autoTolerance, int); READ_DOUBLE_VALUE("tolerance", dataReductionData.tolerance); READ_INT_VALUE("autoTolerance2", dataReductionData.autoTolerance2, int); READ_DOUBLE_VALUE("tolerance2", dataReductionData.tolerance2); } else if (!preview && reader->name() == "dataReductionResult") { attribs = reader->attributes(); READ_INT_VALUE("available", dataReductionResult.available, int); READ_INT_VALUE("valid", dataReductionResult.valid, int); READ_STRING_VALUE("status", dataReductionResult.status); READ_INT_VALUE("time", dataReductionResult.elapsedTime, int); READ_INT_VALUE("npoints", dataReductionResult.npoints, size_t); READ_DOUBLE_VALUE("posError", dataReductionResult.posError); READ_DOUBLE_VALUE("areaError", dataReductionResult.areaError); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp index 5eb4c7a2a..e3f7edafd 100644 --- a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp @@ -1,365 +1,365 @@ /*************************************************************************** File : XYDifferentiationCurve.cpp Project : LabPlot Description : A xy-curve defined by an differentiation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYDifferentiationCurve \brief A xy-curve defined by an differentiation \ingroup worksheet */ #include "XYDifferentiationCurve.h" #include "XYDifferentiationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" extern "C" { #include } #include #include #include #include XYDifferentiationCurve::XYDifferentiationCurve(const QString& name) : XYAnalysisCurve(name, new XYDifferentiationCurvePrivate(this)) { } XYDifferentiationCurve::XYDifferentiationCurve(const QString& name, XYDifferentiationCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYDifferentiationCurve::~XYDifferentiationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDifferentiationCurve::recalculate() { Q_D(XYDifferentiationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYDifferentiationCurve::icon() const { return QIcon::fromTheme("labplot-xy-differentiation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, XYDifferentiationCurve::DifferentiationData, differentiationData, differentiationData) const XYDifferentiationCurve::DifferentiationResult& XYDifferentiationCurve::differentiationResult() const { Q_D(const XYDifferentiationCurve); return d->differentiationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYDifferentiationCurve, SetDifferentiationData, XYDifferentiationCurve::DifferentiationData, differentiationData, recalculate); void XYDifferentiationCurve::setDifferentiationData(const XYDifferentiationCurve::DifferentiationData& differentiationData) { Q_D(XYDifferentiationCurve); - exec(new XYDifferentiationCurveSetDifferentiationDataCmd(d, differentiationData, i18n("%1: set options and perform the differentiation"))); + exec(new XYDifferentiationCurveSetDifferentiationDataCmd(d, differentiationData, ki18n("%1: set options and perform the differentiation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDifferentiationCurvePrivate::XYDifferentiationCurvePrivate(XYDifferentiationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYDifferentiationCurvePrivate::~XYDifferentiationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate void XYDifferentiationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create differentiation result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result differentiationResult = XYDifferentiationCurve::DifferentiationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { differentiationResult.available = true; differentiationResult.valid = false; differentiationResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (differentiationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = differentiationData.xRange.first(); xmax = differentiationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to differentiate const size_t n = (size_t)xdataVector.size(); if (n < 3) { differentiationResult.available = true; differentiationResult.valid = false; differentiationResult.status = i18n("Not enough data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // differentiation settings const nsl_diff_deriv_order_type derivOrder = differentiationData.derivOrder; const int accOrder = differentiationData.accOrder; DEBUG(nsl_diff_deriv_order_name[derivOrder] << "derivative"); DEBUG("accuracy order:" << accOrder); /////////////////////////////////////////////////////////// int status = 0; switch (derivOrder) { case nsl_diff_deriv_order_first: status = nsl_diff_first_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_second: status = nsl_diff_second_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_third: status = nsl_diff_third_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fourth: status = nsl_diff_fourth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_fifth: status = nsl_diff_fifth_deriv(xdata, ydata, n, accOrder); break; case nsl_diff_deriv_order_sixth: status = nsl_diff_sixth_deriv(xdata, ydata, n, accOrder); break; } xVector->resize((int)n); yVector->resize((int)n); memcpy(xVector->data(), xdata, n * sizeof(double)); memcpy(yVector->data(), ydata, n * sizeof(double)); /////////////////////////////////////////////////////////// //write the result differentiationResult.available = true; differentiationResult.valid = true; differentiationResult.status = QString::number(status); differentiationResult.elapsedTime = timer.elapsed(); //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYDifferentiationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYDifferentiationCurve); writer->writeStartElement("xyDifferentiationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-differentiation-curve specific information // differentiation data writer->writeStartElement("differentiationData"); writer->writeAttribute( "derivOrder", QString::number(d->differentiationData.derivOrder) ); writer->writeAttribute( "accOrder", QString::number(d->differentiationData.accOrder) ); writer->writeAttribute( "autoRange", QString::number(d->differentiationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->differentiationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->differentiationData.xRange.last()) ); writer->writeEndElement();// differentiationData // differentiation results (generated columns) writer->writeStartElement("differentiationResult"); writer->writeAttribute( "available", QString::number(d->differentiationResult.available) ); writer->writeAttribute( "valid", QString::number(d->differentiationResult.valid) ); writer->writeAttribute( "status", d->differentiationResult.status ); writer->writeAttribute( "time", QString::number(d->differentiationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"differentiationResult" writer->writeEndElement(); //"xyDifferentiationCurve" } //! Load from XML bool XYDifferentiationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYDifferentiationCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyDifferentiationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "differentiationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", differentiationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", differentiationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", differentiationData.xRange.last()); READ_INT_VALUE("derivOrder", differentiationData.derivOrder, nsl_diff_deriv_order_type); READ_INT_VALUE("accOrder", differentiationData.accOrder, int); } else if (!preview && reader->name() == "differentiationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", differentiationResult.available, int); READ_INT_VALUE("valid", differentiationResult.valid, int); READ_STRING_VALUE("status", differentiationResult.status); READ_INT_VALUE("time", differentiationResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp index 6a24e34d2..96adfa6cb 100644 --- a/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYEquationCurve.cpp @@ -1,231 +1,231 @@ /*************************************************************************** File : XYEquationCurve.cpp Project : LabPlot Description : A xy-curve defined by a mathematical equation -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYEquationCurve \brief A xy-curve defined by a mathematical equation \ingroup worksheet */ #include "XYEquationCurve.h" #include "XYEquationCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/gsl/ExpressionParser.h" #include #include XYEquationCurve::XYEquationCurve(const QString& name) : XYCurve(name, new XYEquationCurvePrivate(this)) { init(); } XYEquationCurve::XYEquationCurve(const QString& name, XYEquationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYEquationCurve::~XYEquationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYEquationCurve::init() { Q_D(XYEquationCurve); d->xColumn->setHidden(true); addChildFast(d->xColumn); d->yColumn->setHidden(true); addChildFast(d->yColumn); //TODO: read from the saved settings for XYEquationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; setUndoAware(false); suppressRetransform(true); setXColumn(d->xColumn); setYColumn(d->yColumn); suppressRetransform(false); setUndoAware(true); } void XYEquationCurve::recalculate() { Q_D(XYEquationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYEquationCurve::icon() const { return QIcon::fromTheme("labplot-xy-equation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYEquationCurve, XYEquationCurve::EquationData, equationData, equationData) //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYEquationCurve, SetEquationData, XYEquationCurve::EquationData, equationData, recalculate); void XYEquationCurve::setEquationData(const XYEquationCurve::EquationData& equationData) { Q_D(XYEquationCurve); if ( (equationData.expression1 != d->equationData.expression1) || (equationData.expression2 != d->equationData.expression2) || (equationData.min != d->equationData.min) || (equationData.max != d->equationData.max) || (equationData.count != d->equationData.count) ) - exec(new XYEquationCurveSetEquationDataCmd(d, equationData, i18n("%1: set equation"))); + exec(new XYEquationCurveSetEquationDataCmd(d, equationData, ki18n("%1: set equation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYEquationCurvePrivate::XYEquationCurvePrivate(XYEquationCurve* owner) : XYCurvePrivate(owner), xColumn(new Column("x", AbstractColumn::Numeric)), yColumn(new Column("y", AbstractColumn::Numeric)), xVector(static_cast* >(xColumn->data())), yVector(static_cast* >(yColumn->data())), q(owner) { } XYEquationCurvePrivate::~XYEquationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYEquationCurvePrivate::recalculate() { //resize the vector if a new number of point to calculate was provided if (equationData.count != xVector->size()) { if (equationData.count >= 1) { xVector->resize(equationData.count); yVector->resize(equationData.count); } else { //invalid number of points provided xVector->clear(); yVector->clear(); emit q->dataChanged(); return; } } else { if (equationData.count < 1) return; } ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = false; if (equationData.type == XYEquationCurve::Cartesian) { rc = parser->evaluateCartesian( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Polar) { rc = parser->evaluatePolar( equationData.expression1, equationData.min, equationData.max, equationData.count, xVector, yVector ); } else if (equationData.type == XYEquationCurve::Parametric) { rc = parser->evaluateParametric(equationData.expression1, equationData.expression2, equationData.min, equationData.max, equationData.count, xVector, yVector); } if (!rc) { xVector->clear(); yVector->clear(); } emit q->dataChanged(); } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYEquationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYEquationCurve); writer->writeStartElement( "xyEquationCurve" ); //write xy-curve information XYCurve::save(writer); //write xy-equationCurve specific information writer->writeStartElement( "equationData" ); writer->writeAttribute( "type", QString::number(d->equationData.type) ); writer->writeAttribute( "expression1", d->equationData.expression1 ); writer->writeAttribute( "expression2", d->equationData.expression2 ); writer->writeAttribute( "min", d->equationData.min); writer->writeAttribute( "max", d->equationData.max ); writer->writeAttribute( "count", QString::number(d->equationData.count) ); writer->writeEndElement(); writer->writeEndElement(); } //! Load from XML bool XYEquationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYEquationCurve); if (!reader->isStartElement() || reader->name() != "xyEquationCurve") { reader->raiseError(i18n("no xy equation curve element found")); return false; } QString attributeWarning = i18n( "Attribute '%1' missing or empty, default value is used" ); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyEquationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "equationData") { attribs = reader->attributes(); READ_INT_VALUE("type", equationData.type, XYEquationCurve::EquationType); READ_STRING_VALUE("expression1", equationData.expression1); READ_STRING_VALUE("expression2", equationData.expression2); READ_STRING_VALUE("min", equationData.min); READ_STRING_VALUE("max", equationData.max); READ_INT_VALUE("count", equationData.count, int); } } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index e1ed5c067..9b5f7e49d 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2396 +1,2396 @@  /*************************************************************************** File : XYFitCurve.cpp Project : LabPlot Description : A xy-curve defined by a fit model -------------------------------------------------------------------- Copyright : (C) 2014-2017 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016-2018 Stefan Gerlach (stefan.gerlach@uni.kn) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFitCurve \brief A xy-curve defined by a fit model \ingroup worksheet */ #include "XYFitCurve.h" #include "XYFitCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" #include "backend/gsl/ExpressionParser.h" extern "C" { #include #include #include #include #include #include #include "backend/gsl/parser.h" #include "backend/nsl/nsl_sf_stats.h" #include "backend/nsl/nsl_stats.h" } #include #include #include XYFitCurve::XYFitCurve(const QString& name) : XYAnalysisCurve(name, new XYFitCurvePrivate(this)) { } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFitCurve::~XYFitCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } void XYFitCurve::evaluate(bool preview) { Q_D(XYFitCurve); d->evaluate(preview); } void XYFitCurve::initStartValues(const XYCurve* curve) { Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; initStartValues(fitData, curve); } void XYFitCurve::initStartValues(XYFitCurve::FitData& fitData, const XYCurve* curve) { DEBUG("XYFitCurve::initStartValues()"); if (!curve) { DEBUG(" no curve given"); return; } const Column* tmpXDataColumn = dynamic_cast(curve->xColumn()); const Column* tmpYDataColumn = dynamic_cast(curve->yColumn()); if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG(" data columns not available"); return; } DEBUG(" x data rows = " << tmpXDataColumn->rowCount()); nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; int degree = fitData.degree; DEBUG(" fit model type = " << modelType << ", degree = " << degree); QVector& paramStartValues = fitData.paramStartValues; //QVector* xVector = static_cast* >(tmpXDataColumn->data()); //double xmean = gsl_stats_mean(xVector->constData(), 1, tmpXDataColumn->rowCount()); double xmin = tmpXDataColumn->minimum(); double xmax = tmpXDataColumn->maximum(); //double ymin = tmpYDataColumn->minimum(); //double ymax = tmpYDataColumn->maximum(); double xrange = xmax-xmin; //double yrange = ymax-ymin; DEBUG(" x min/max = " << xmin << ' ' << xmax); //DEBUG(" y min/max = " << ymin << ' ' << ymax); switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // not needed (works anyway) break; //TODO: handle basic models case nsl_fit_model_power: case nsl_fit_model_exponential: case nsl_fit_model_inverse_exponential: case nsl_fit_model_fourier: break; } break; case nsl_fit_model_peak: // use equidistant mu's and (xmax-xmin)/(10*degree) as sigma(, gamma) switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (int d = 0; d < degree; d++) { paramStartValues[3*d+2] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[3*d+1] = xrange/(10.*degree); // sigma } break; case nsl_fit_model_voigt: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = xmin + (d+1.)*xrange/(degree+1.); // mu paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xrange/(10.*degree); // gamma } break; case nsl_fit_model_pseudovoigt1: for (int d = 0; d < degree; d++) { paramStartValues[4*d+1] = 0.5; // eta paramStartValues[4*d+2] = xrange/(10.*degree); // sigma paramStartValues[4*d+3] = xmin + (d+1.)*xrange/(degree+1.); // mu } break; } break; case nsl_fit_model_growth: switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: case nsl_fit_model_sigmoid: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[1] = (xmax+xmin)/2.; paramStartValues[2] = xrange/10.; break; case nsl_fit_model_hill: paramStartValues[2] = xrange/10.; break; case nsl_fit_model_gompertz: //TODO break; } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: // use (xmax+xmin)/2 as mu and (xmax-xmin)/10 as sigma paramStartValues[2] = (xmin+xmax)/2.; paramStartValues[1] = xrange/10.; break; //TODO: other types default: break; } break; case nsl_fit_model_custom: // not possible break; } } /*! * sets the parameter names for given model category, model type and degree in \c fitData for given action */ void XYFitCurve::initFitData(PlotDataDialog::AnalysisAction action) { if (!action) return; Q_D(XYFitCurve); XYFitCurve::FitData& fitData = d->fitData; if (action == PlotDataDialog::FitLinear) { //Linear fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = (int)nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = (int)nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = (int)nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = 0; } XYFitCurve::initFitData(fitData); } /*! * sets the model expression and the parameter names for given model category, model type and degree in \c fitData */ void XYFitCurve::initFitData(XYFitCurve::FitData& fitData) { nsl_fit_model_category modelCategory = fitData.modelCategory; int modelType = fitData.modelType; QString& model = fitData.model; QStringList& paramNames = fitData.paramNames; QStringList& paramNamesUtf8 = fitData.paramNamesUtf8; int degree = fitData.degree; QVector& paramStartValues = fitData.paramStartValues; QVector& paramLowerLimits = fitData.paramLowerLimits; QVector& paramUpperLimits = fitData.paramUpperLimits; QVector& paramFixed = fitData.paramFixed; if (modelCategory != nsl_fit_model_custom) { DEBUG("XYFitCurve::initFitData() for model category = " << nsl_fit_model_category_name[modelCategory] << ", model type = " << modelType << ", degree = " << degree); paramNames.clear(); } else { DEBUG("XYFitCurve::initFitData() for model category = nsl_fit_model_custom, model type = " << modelType << ", degree = " << degree); } paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = { UTF8_QSTRING("₁"), UTF8_QSTRING("₂"), UTF8_QSTRING("₃"), UTF8_QSTRING("₄"), UTF8_QSTRING("₅"), UTF8_QSTRING("₆"), UTF8_QSTRING("₇"), UTF8_QSTRING("₈"), UTF8_QSTRING("₉"), UTF8_QSTRING("₁₀")}; switch (modelCategory) { case nsl_fit_model_basic: model = nsl_fit_model_basic_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_polynomial: paramNames << "c0" << "c1"; paramNamesUtf8 << UTF8_QSTRING("c₀") << UTF8_QSTRING("c₁"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << UTF8_QSTRING("c₂"); } else if (degree > 2) { for (int i = 2; i <= degree; ++i) { QString numStr = QString::number(i); model += "+c" + numStr + "*x^" + numStr; paramNames << 'c' + numStr; paramNamesUtf8 << 'c' + indices[i-1]; } } break; case nsl_fit_model_power: if (degree == 1) { paramNames << "a" << "b"; } else { paramNames << "a" << "b" << "c"; model = "a + b*x^c"; } break; case nsl_fit_model_exponential: if (degree == 1) { paramNames << "a" << "b"; } else { for (int i = 1; i <= degree; i++) { QString numStr = QString::number(i); if (i == 1) model = "a1*exp(b1*x)"; else model += " + a" + numStr + "*exp(b" + numStr + "*x)"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; case nsl_fit_model_inverse_exponential: paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << UTF8_QSTRING("ω") << UTF8_QSTRING("a₀") << UTF8_QSTRING("a₁") << UTF8_QSTRING("b₁"); if (degree > 1) { for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); model += "+ (a" + numStr + "*cos(" + numStr + "*w*x) + b" + numStr + "*sin(" + numStr + "*w*x))"; paramNames << 'a' + numStr << 'b' + numStr; paramNamesUtf8 << 'a' + indices[i-1] << 'b' + indices[i-1]; } } break; } break; case nsl_fit_model_peak: model = nsl_fit_model_peak_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1./sqrt(2*pi) * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* exp(-((x-mu" + numStr + ")/s" + numStr + ")^2/2)"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; default: model = "1./pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + " * g" + numStr + "/(g" + numStr + "^2+(x-mu" + numStr + ")^2)"; paramNames << 'a' + numStr << 'g' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/pi * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/s" + numStr + ')'; paramNames << 'a' + numStr << "s" + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; default: model = "1/4 * ("; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "/s" + numStr + "* sech((x-mu" + numStr + ")/2/s" + numStr + ")**2"; paramNames << 'a' + numStr << 's' + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } model += ')'; } break; case nsl_fit_model_voigt: switch(degree) { case 1: paramNames << "a" << "mu" << "s" << "g"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ") << UTF8_QSTRING("γ"); break; default: model = ""; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*voigt(x-mu" + numStr + ",s" + numStr + ",g" + numStr + ')'; paramNames << "a" + numStr << "mu" + numStr << "s" + numStr << "g" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1] << UTF8_QSTRING("σ") + indices[i-1] << UTF8_QSTRING("γ") + indices[i-1]; } } break; case nsl_fit_model_pseudovoigt1: switch(degree) { case 1: paramNames << "a" << "et" << "w" << "mu"; // eta function exists! paramNamesUtf8 << "A" << UTF8_QSTRING("η") << "w" << UTF8_QSTRING("μ"); break; default: model=""; for (int i = 1; i <= degree; ++i) { QString numStr = QString::number(i); if (i > 1) model += " + "; model += 'a' + numStr + "*pseudovoigt1(x-mu" + numStr + ",eta" + numStr + ",w" + numStr + ')'; paramNames << "a" + numStr << "eta" + numStr << "w" + numStr << "mu" + numStr; paramNamesUtf8 << 'A' + indices[i-1] << UTF8_QSTRING("η") + indices[i-1] << 'w' + indices[i-1] << UTF8_QSTRING("μ") + indices[i-1]; } } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch (modelType) { case nsl_fit_model_atan: case nsl_fit_model_tanh: case nsl_fit_model_algebraic_sigmoid: case nsl_fit_model_erf: case nsl_fit_model_gudermann: paramNames << "a" << "mu" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << UTF8_QSTRING("σ"); break; case nsl_fit_model_sigmoid: paramNames << "a" << "mu" << "k"; paramNamesUtf8 << "A" << UTF8_QSTRING("μ") << "k"; break; case nsl_fit_model_hill: paramNames << "a" << "n" << "a"; paramNamesUtf8 << "A" << "n" << UTF8_QSTRING("σ"); break; case nsl_fit_model_gompertz: paramNames << "a" << "b" << "c"; break; } break; case nsl_fit_model_distribution: model = nsl_sf_stats_distribution_equation[fitData.modelType]; switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_laplace: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: paramNames << "a" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gaussian_tail: paramNames << "A" << "s" << "a" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "a" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential: paramNames << "a" << "l" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_exponential_power: paramNames << "a" << "s" << "b" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "a" << "g" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_rayleigh: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_landau: paramNames << "a"; paramNamesUtf8 << "A"; break; case nsl_sf_stats_levy_alpha_stable: // unused distributions case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; case nsl_sf_stats_gamma: paramNames << "a" << "k" << "t"; paramNamesUtf8 << "A"<< "k" << UTF8_QSTRING("θ"); break; case nsl_sf_stats_flat: paramNames << "A" << "b" << "a"; break; case nsl_sf_stats_chi_squared: paramNames << "a" << "n"; paramNamesUtf8 << "A" << "n"; break; case nsl_sf_stats_fdist: paramNames << "a" << "n1" << "n2"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν₁") << UTF8_QSTRING("ν₂"); break; case nsl_sf_stats_tdist: paramNames << "a" << "n"; paramNamesUtf8 << "A" << UTF8_QSTRING("ν"); break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "A" << "a" << "b"; break; case nsl_sf_stats_weibull: paramNames << "a" << "k" << "l" << "mu"; paramNamesUtf8 << "A" << "k" << UTF8_QSTRING("λ") << UTF8_QSTRING("μ"); break; case nsl_sf_stats_gumbel1: paramNames << "a" << "s" << "mu" << "b"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ") << UTF8_QSTRING("μ") << UTF8_QSTRING("β"); break; case nsl_sf_stats_gumbel2: paramNames << "A" << "a" << "b" << "mu"; paramNamesUtf8 << "A" << "a" << "b" << UTF8_QSTRING("μ"); break; case nsl_sf_stats_poisson: paramNames << "a" << "l"; paramNamesUtf8 << "A" << UTF8_QSTRING("λ"); break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "a" << "p" << "n"; paramNamesUtf8 << "A" << "p" << "n"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "a" << "p"; paramNamesUtf8 << "A" << "p"; break; case nsl_sf_stats_hypergeometric: paramNames << "a" << "n1" << "n2" << "t"; paramNamesUtf8 << "A" << UTF8_QSTRING("n₁") << UTF8_QSTRING("n₂") << "t"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "a" << "s"; paramNamesUtf8 << "A" << UTF8_QSTRING("σ"); break; case nsl_sf_stats_frechet: paramNames << "a" << "g" << "s" << "mu"; paramNamesUtf8 << "A" << UTF8_QSTRING("γ") << UTF8_QSTRING("σ") << UTF8_QSTRING("μ"); break; } break; case nsl_fit_model_custom: break; } DEBUG("model: " << model.toStdString()); if (paramNamesUtf8.isEmpty()) paramNamesUtf8 << paramNames; //resize the vector for the start values and set the elements to 1.0 //in case a custom model is used, do nothing, we take over the previous values if (modelCategory != nsl_fit_model_custom) { const int np = paramNames.size(); paramStartValues.resize(np); paramFixed.resize(np); paramLowerLimits.resize(np); paramUpperLimits.resize(np); for (int i = 0; i < np; ++i) { paramStartValues[i] = 1.0; paramFixed[i] = false; paramLowerLimits[i] = -std::numeric_limits::max(); paramUpperLimits[i] = std::numeric_limits::max(); } // set some model-dependent start values // TODO: see initStartValues() if (modelCategory == nsl_fit_model_distribution) { if (modelType == (int)nsl_sf_stats_flat) paramStartValues[2] = -1.0; else if (modelType == (int)nsl_sf_stats_levy) paramStartValues[2] = 0.0; else if (modelType == (int)nsl_sf_stats_exponential_power || modelType == (int)nsl_sf_stats_weibull || modelType == (int)nsl_sf_stats_gumbel2 || modelType == (int)nsl_sf_stats_frechet) paramStartValues[3] = 0.0; else if (modelType == (int)nsl_sf_stats_binomial || modelType == (int)nsl_sf_stats_negative_binomial || modelType == (int)nsl_sf_stats_pascal || modelType == (int)nsl_sf_stats_geometric || modelType == (int)nsl_sf_stats_logarithmic) paramStartValues[1] = 0.5; } } } /*! Returns an icon to be used in the project explorer. */ QIcon XYFitCurve::icon() const { return QIcon::fromTheme("labplot-xy-fit-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xErrorColumnPath() const { Q_D(const XYFitCurve); return d->xErrorColumnPath; } const QString& XYFitCurve::yErrorColumnPath() const { Q_D(const XYFitCurve); return d->yErrorColumnPath; } BASIC_SHARED_D_READER_IMPL(XYFitCurve, XYFitCurve::FitData, fitData, fitData) const XYFitCurve::FitResult& XYFitCurve::fitResult() const { Q_D(const XYFitCurve); return d->fitResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_S(XYFitCurve, SetXErrorColumn, const AbstractColumn*, xErrorColumn) void XYFitCurve::setXErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xErrorColumn) { - exec(new XYFitCurveSetXErrorColumnCmd(d, column, i18n("%1: assign x-error"))); + exec(new XYFitCurveSetXErrorColumnCmd(d, column, ki18n("%1: assign x-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } STD_SETTER_CMD_IMPL_S(XYFitCurve, SetYErrorColumn, const AbstractColumn*, yErrorColumn) void XYFitCurve::setYErrorColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yErrorColumn) { - exec(new XYFitCurveSetYErrorColumnCmd(d, column, i18n("%1: assign y-error"))); + exec(new XYFitCurveSetYErrorColumnCmd(d, column, ki18n("%1: assign y-error"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } // TODO: do not recalculate STD_SETTER_CMD_IMPL_F_S(XYFitCurve, SetFitData, XYFitCurve::FitData, fitData, recalculate); void XYFitCurve::setFitData(const XYFitCurve::FitData& fitData) { Q_D(XYFitCurve); - exec(new XYFitCurveSetFitDataCmd(d, fitData, i18n("%1: set fit options and perform the fit"))); + exec(new XYFitCurveSetFitDataCmd(d, fitData, ki18n("%1: set fit options and perform the fit"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYAnalysisCurvePrivate(owner), xErrorColumn(nullptr), yErrorColumn(nullptr), residualsColumn(nullptr), residualsVector(nullptr), q(owner) { } XYFitCurvePrivate::~XYFitCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // data structure to pass parameter to fit functions struct data { size_t n; //number of data points double* x; //pointer to the vector with x-data values double* y; //pointer to the vector with y-data values double* weight; //pointer to the vector with weight values nsl_fit_model_category modelCategory; int modelType; int degree; QString* func; // string containing the definition of the model/function QStringList* paramNames; double* paramMin; // lower parameter limits double* paramMax; // upper parameter limits bool* paramFixed; // parameter fixed? }; /*! * \param paramValues vector containing current values of the fit parameters * \param params * \param f vector with the weighted residuals weight[i]*(Yi - y[i]) */ int func_f(const gsl_vector* paramValues, void* params, gsl_vector* f) { //DEBUG("func_f"); size_t n = ((struct data*)params)->n; double* x = ((struct data*)params)->x; double* y = ((struct data*)params)->y; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; QByteArray funcba = ((struct data*)params)->func->toLatin1(); // a local byte array is needed! const char *func = funcba.constData(); // function to evaluate QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; // set current values of the parameters for (int i = 0; i < paramNames->size(); i++) { double v = gsl_vector_get(paramValues, (size_t)i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(v, min[i], max[i])); QDEBUG("Parameter"<n; double* xVector = ((struct data*)params)->x; double* weight = ((struct data*)params)->weight; nsl_fit_model_category modelCategory = ((struct data*)params)->modelCategory; unsigned int modelType = ((struct data*)params)->modelType; unsigned int degree = ((struct data*)params)->degree; QStringList* paramNames = ((struct data*)params)->paramNames; double *min = ((struct data*)params)->paramMin; double *max = ((struct data*)params)->paramMax; bool *fixed = ((struct data*)params)->paramFixed; // calculate the Jacobian matrix: // Jacobian matrix J(i,j) = df_i / dx_j // where f_i = w_i*(Y_i - y_i), // Y_i = model and the x_j are the parameters double x; switch (modelCategory) { case nsl_fit_model_basic: switch (modelType) { case nsl_fit_model_polynomial: // Y(x) = c0 + c1*x + ... + cn*x^n for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < (unsigned int)paramNames->size(); ++j) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_polynomial_param_deriv(x, j, weight[i])); } } break; case nsl_fit_model_power: // Y(x) = a*x^b or Y(x) = a + b*x^c. if (degree == 1) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power1_param_deriv(j, x, a, b, weight[i])); } } } else if (degree == 2) { const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double c = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_power2_param_deriv(j, x, b, c, weight[i])); } } } break; case nsl_fit_model_exponential: { // Y(x) = a*exp(b*x) + c*exp(d*x) + ... double *p = new double[2*degree]; for (unsigned int i = 0; i < 2*degree; i++) p[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, i), min[i], max[i]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2*degree; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponentialn_param_deriv(j, x, p, weight[i])); } } delete[] p; break; } case nsl_fit_model_inverse_exponential: { // Y(x) = a*(1-exp(b*x))+c const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_inverse_exponential_param_deriv(j, x, a, b, weight[i])); } } break; } case nsl_fit_model_fourier: { // Y(x) = a0 + (a1*cos(w*x) + b1*sin(w*x)) + ... + (an*cos(n*w*x) + bn*sin(n*w*x) //parameters: w, a0, a1, b1, ... an, bn double* a = new double[degree]; double* b = new double[degree]; double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); a[0] = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); b[0] = 0; for (unsigned int i = 1; i < degree; ++i) { a[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i), min[2*i], max[2*i]); b[i] = nsl_fit_map_bound(gsl_vector_get(paramValues, 2*i+1), min[2*i+1], max[2*i+1]); } for (size_t i = 0; i < n; i++) { x = xVector[i]; double wd = 0; //first derivative with respect to the w parameter for (unsigned int j = 1; j < degree; ++j) { wd += -a[j]*j*x*sin(j*w*x) + b[j]*j*x*cos(j*w*x); } gsl_matrix_set(J, i, 0, weight[i]*wd); gsl_matrix_set(J, i, 1, weight[i]); for (unsigned int j = 1; j <= degree; ++j) { gsl_matrix_set(J, (size_t)i, (size_t)(2*j), nsl_fit_model_fourier_param_deriv(0, j, x, w, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(2*j+1), nsl_fit_model_fourier_param_deriv(1, j, x, w, weight[i])); } for (unsigned int j = 0; j <= 2*degree+1; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } delete[] a; delete[] b; break; } } break; case nsl_fit_model_peak: switch (modelType) { case nsl_fit_model_gaussian: case nsl_fit_model_lorentz: case nsl_fit_model_sech: case nsl_fit_model_logistic: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j), min[3*j], max[3*j]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+1), min[3*j+1], max[3*j+1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3*j+2), min[3*j+2], max[3*j+2]); switch (modelType) { case nsl_fit_model_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_gaussian_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_gaussian_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_gaussian_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_lorentz: // a,s,t gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_lorentz_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_lorentz_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_lorentz_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_sech: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_sech_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_sech_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_sech_param_deriv(2, x, a, s, mu, weight[i])); break; case nsl_fit_model_logistic: gsl_matrix_set(J, (size_t)i, (size_t)(3*j), nsl_fit_model_logistic_param_deriv(0, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+1), nsl_fit_model_logistic_param_deriv(1, x, a, s, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(3*j+2), nsl_fit_model_logistic_param_deriv(2, x, a, s, mu, weight[i])); break; } } for (unsigned int j = 0; j < 3*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_voigt: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, mu, s, g, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, mu, s, g, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; case nsl_fit_model_pseudovoigt1: for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < degree; ++j) { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j), min[4*j], max[4*j]); const double eta = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+1), min[4*j+1], max[4*j+1]); const double w = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+2), min[4*j+2], max[4*j+2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 4*j+3), min[4*j+3], max[4*j+3]); gsl_matrix_set(J, (size_t)i, (size_t)(4*j), nsl_fit_model_voigt_param_deriv(0, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+1), nsl_fit_model_voigt_param_deriv(1, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+2), nsl_fit_model_voigt_param_deriv(2, x, a, eta, w, mu, weight[i])); gsl_matrix_set(J, (size_t)i, (size_t)(4*j+3), nsl_fit_model_voigt_param_deriv(3, x, a, eta, w, mu, weight[i])); } for (unsigned int j = 0; j < 4*degree; j++) if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } break; } break; case nsl_fit_model_growth: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) { gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } else { switch (modelType) { case nsl_fit_model_atan: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_atan_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_tanh: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_tanh_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_algebraic_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_algebraic_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_sigmoid: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sigmoid_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_erf: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_erf_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_hill: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hill_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gompertz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gompertz_param_deriv(j, x, a, mu, s, weight[i])); break; case nsl_fit_model_gudermann: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gudermann_param_deriv(j, x, a, mu, s, weight[i])); break; } } } } } break; case nsl_fit_model_distribution: switch (modelType) { case nsl_sf_stats_gaussian: case nsl_sf_stats_exponential: case nsl_sf_stats_laplace: case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_rayleigh_tail: case nsl_sf_stats_lognormal: case nsl_sf_stats_logistic: case nsl_sf_stats_sech: case nsl_sf_stats_levy: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_gaussian: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_exponential: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exponential_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_laplace: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_laplace_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_cauchy_lorentz: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lorentz_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_rayleigh_tail: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_tail_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_lognormal: if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_lognormal_param_deriv(j, x, a, s, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); break; case nsl_sf_stats_logistic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logistic_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_sech: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_sech_dist_param_deriv(j, x, a, s, mu, weight[i])); break; case nsl_sf_stats_levy: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_levy_param_deriv(j, x, a, s, mu, weight[i])); break; } } } } break; } case nsl_sf_stats_gaussian_tail: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gaussian_tail_param_deriv(j, x, A, s, a, mu, weight[i])); } } break; } case nsl_sf_stats_exponential_power: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_exp_pow_param_deriv(j, x, a, s, b, mu, weight[i])); } } break; } case nsl_sf_stats_rayleigh: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_rayleigh_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_gamma: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gamma_param_deriv(j, x, a, k, t, weight[i])); } } break; } case nsl_sf_stats_flat: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_flat_param_deriv(j, x, A, b, a, weight[i])); } } break; } case nsl_sf_stats_chi_squared: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_chi_square_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_tdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double nu = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_students_t_param_deriv(j, x, a, nu, weight[i])); } } break; } case nsl_sf_stats_fdist: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_fdist_param_deriv(j, x, a, n1, n2, weight[i])); } } break; } case nsl_sf_stats_beta: case nsl_sf_stats_pareto: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_beta: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_beta_param_deriv(j, x, A, a, b, weight[i])); break; case nsl_sf_stats_pareto: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pareto_param_deriv(j, x, A, a, b, weight[i])); break; } } } } break; } case nsl_sf_stats_weibull: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double k = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { if (x > 0) gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_weibull_param_deriv(j, x, a, k, l, mu, weight[i])); else gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); } } } break; } case nsl_sf_stats_gumbel1: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel1_param_deriv(j, x, a, s, mu, b, weight[i])); } } break; } case nsl_sf_stats_gumbel2: { const double A = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double b = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_gumbel2_param_deriv(j, x, A, a, b, mu, weight[i])); } } break; } case nsl_sf_stats_poisson: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double l = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_poisson_param_deriv(j, x, a, l, weight[i])); } } break; } case nsl_sf_stats_maxwell_boltzmann: { // Y(x) = a*sqrt(2/pi) * x^2/s^3 * exp(-(x/s)^2/2) const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_maxwell_param_deriv(j, x, a, s, weight[i])); } } break; } case nsl_sf_stats_frechet: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double g = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double s = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double mu = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_frechet_param_deriv(j, x, a, g, s, mu, weight[i])); } } break; } case nsl_sf_stats_landau: { // const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); for (size_t i = 0; i < n; i++) { x = xVector[i]; if (fixed[0]) gsl_matrix_set(J, (size_t)i, 0, 0.); else gsl_matrix_set(J, (size_t)i, 0, nsl_fit_model_landau_param_deriv(0, x, weight[i])); } break; } case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double N = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 3; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_negative_binomial: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_negative_binomial_param_deriv(j, x, a, p, N, weight[i])); break; case nsl_sf_stats_pascal: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_pascal_param_deriv(j, x, a, p, N, weight[i])); break; } } } } break; } case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double p = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 2; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else { switch (modelType) { case nsl_sf_stats_geometric: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_geometric_param_deriv(j, x, a, p, weight[i])); break; case nsl_sf_stats_logarithmic: gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_logarithmic_param_deriv(j, x, a, p, weight[i])); break; } } } } break; } case nsl_sf_stats_hypergeometric: { const double a = nsl_fit_map_bound(gsl_vector_get(paramValues, 0), min[0], max[0]); const double n1 = nsl_fit_map_bound(gsl_vector_get(paramValues, 1), min[1], max[1]); const double n2 = nsl_fit_map_bound(gsl_vector_get(paramValues, 2), min[2], max[2]); const double t = nsl_fit_map_bound(gsl_vector_get(paramValues, 3), min[3], max[3]); for (size_t i = 0; i < n; i++) { x = xVector[i]; for (unsigned int j = 0; j < 4; j++) { if (fixed[j]) gsl_matrix_set(J, (size_t)i, (size_t)j, 0.); else gsl_matrix_set(J, (size_t)i, (size_t)j, nsl_fit_model_hypergeometric_param_deriv(j, x, a, n1, n2, t, weight[i])); } } break; } // unused distributions case nsl_sf_stats_levy_alpha_stable: case nsl_sf_stats_levy_skew_alpha_stable: case nsl_sf_stats_bernoulli: break; } break; case nsl_fit_model_custom: QByteArray funcba = ((struct data*)params)->func->toLatin1(); const char* func = funcba.data(); QByteArray nameba; double value; const unsigned int np = paramNames->size(); for (size_t i = 0; i < n; i++) { x = xVector[i]; assign_variable("x", x); for (unsigned int j = 0; j < np; j++) { for (unsigned int k = 0; k < np; k++) { if (k != j) { nameba = paramNames->at(k).toLatin1(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, k), min[k], max[k]); assign_variable(nameba.data(), value); } } nameba = paramNames->at(j).toLatin1(); const char *name = nameba.data(); value = nsl_fit_map_bound(gsl_vector_get(paramValues, j), min[j], max[j]); assign_variable(name, value); const double f_p = parse(func); double eps = 1.e-9; if (std::abs(f_p) > 0) eps *= std::abs(f_p); // scale step size with function value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // DEBUG("evaluate deriv"<* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); residualsVector = static_cast* >(residualsColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->addChild(residualsColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); residualsVector->clear(); } } void XYFitCurvePrivate::recalculate() { DEBUG("XYFitCurvePrivate::recalculate()"); QElapsedTimer timer; timer.start(); // prepare source data columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { DEBUG("ERROR: Preparing source data columns failed!"); return; } prepareResultColumns(); // clear the previous result fitResult = XYFitCurve::FitResult(); //fit settings const unsigned int maxIters = fitData.maxIterations; //maximal number of iterations const double delta = fitData.eps; //fit tolerance const unsigned int np = fitData.paramNames.size(); //number of fit parameters if (np == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Model has no parameters."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //determine the data source columns if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (yErrorColumn) { if (yErrorColumn->rowCount() < tmpXDataColumn->rowCount()) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("Not sufficient weight data points provided."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } } //copy all valid data point for the fit to temporary vectors QVector xdataVector; QVector ydataVector; QVector xerrorVector; QVector yerrorVector; double xmin, xmax; if (fitData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = fitData.fitRange.first(); xmax = fitData.fitRange.last(); } DEBUG("fit range = " << xmin << " .. " << xmax); for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y and errors, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { if (dataSourceType == XYAnalysisCurve::DataSourceCurve || (!xErrorColumn && !yErrorColumn) || !fitData.useDataErrors) { // x-y xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } else if (!xErrorColumn) { // x-y-dy if (!std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } else { // x-y-dx-dy if (!std::isnan(xErrorColumn->valueAt(row)) && !std::isnan(yErrorColumn->valueAt(row))) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); xerrorVector.append(xErrorColumn->valueAt(row)); yerrorVector.append(yErrorColumn->valueAt(row)); } } } } } //number of data points to fit const size_t n = xdataVector.size(); DEBUG("number of data points: " << n); if (n == 0) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } if (n < np) { fitResult.available = true; fitResult.valid = false; fitResult.status = i18n("The number of data points (%1) must be greater than or equal to the number of parameters (%2).", n, np); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); double* xerror = xerrorVector.data(); // size may be 0 double* yerror = yerrorVector.data(); // size may be 0 DEBUG("x errors: " << xerrorVector.size()); DEBUG("y errors: " << yerrorVector.size()); double* weight = new double[n]; for (size_t i = 0; i < n; i++) weight[i] = 1.; switch (fitData.yWeightsType) { case nsl_fit_weight_no: case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: break; case nsl_fit_weight_instrumental: // yerror are sigmas for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./gsl_pow_2(yerror[i]); break; case nsl_fit_weight_direct: // yerror are weights for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: // yerror are inverse weights for(int i = 0; i < (int)n; i++) if (i < yerrorVector.size()) weight[i] = 1./yerror[i]; break; case nsl_fit_weight_statistical: for (int i = 0; i < (int)n; i++) weight[i] = 1./ydata[i]; break; case nsl_fit_weight_relative: for (int i = 0; i < (int)n; i++) weight[i] = 1./gsl_pow_2(ydata[i]); break; } /////////////////////// GSL >= 2 has a complete new interface! But the old one is still supported. /////////////////////////// // GSL >= 2 : "the 'fdf' field of gsl_multifit_function_fdf is now deprecated and does not need to be specified for nonlinear least squares problems" unsigned int nf = 0; // number of fixed parameter for (unsigned int i = 0; i < np; i++) { const bool fixed = fitData.paramFixed.data()[i]; if (fixed) nf++; DEBUG("parameter " << i << " fixed: " << fixed); } //function to fit gsl_multifit_function_fdf f; DEBUG("model = " << fitData.model.toStdString()); struct data params = {n, xdata, ydata, weight, fitData.modelCategory, fitData.modelType, fitData.degree, &fitData.model, &fitData.paramNames, fitData.paramLowerLimits.data(), fitData.paramUpperLimits.data(), fitData.paramFixed.data()}; f.f = &func_f; f.df = &func_df; f.fdf = &func_fdf; f.n = n; f.p = np; f.params = ¶ms; DEBUG("initialize the derivative solver (using Levenberg-Marquardt robust solver)"); const gsl_multifit_fdfsolver_type* T = gsl_multifit_fdfsolver_lmsder; gsl_multifit_fdfsolver* s = gsl_multifit_fdfsolver_alloc(T, n, np); DEBUG("set start values"); double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); DEBUG("scale start values if limits are set"); for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_unbound(x_init[i], x_min[i], x_max[i]); DEBUG(" DONE"); gsl_vector_view x = gsl_vector_view_array(x_init, np); DEBUG("Turning off GSL error handler to avoid overflow/underflow"); gsl_set_error_handler_off(); DEBUG("Initialize solver with function f and initial guess x"); gsl_multifit_fdfsolver_set(s, &f, &x.vector); DEBUG("Iterate ..."); int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; // update weights for Y-depending weights (using function values from residuals) if (fitData.yWeightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i } else if (fitData.yWeightsType == nsl_fit_weight_relative_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(gsl_vector_get(s->f, i)/sqrt(weight[i]) + ydata[i]); // 1/Y_i^2 } status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); // second run for x-error fitting if (xerrorVector.size() > 0) { DEBUG("Rerun fit with x errors"); unsigned int iter2 = 0; double chi = 0, chiOld = 0; double *fun = new double[n]; do { iter2++; chiOld = chi; //printf("iter2 = %d\n", iter2); // calculate function from residuals for (size_t i = 0; i < n; i++) fun[i] = gsl_vector_get(s->f, i) * 1./sqrt(weight[i]) + ydata[i]; // calculate weight[i] for (size_t i = 0; i < n; i++) { // calculate df[i] size_t index = i-1; if (i == 0) index = i; if (i == n-1) index = i-2; double df = (fun[index+1] - fun[index])/(xdata[index+1] - xdata[index]); //printf("df = %g\n", df); double sigmasq = 1.; switch (fitData.xWeightsType) { // x-error type: f'(x)^2*s_x^2 = f'(x)/w_x case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // xerror = w_x sigmasq = df*df/xerror[i]; break; case nsl_fit_weight_instrumental: // xerror = s_x sigmasq = df*df*xerror[i]*xerror[i]; break; case nsl_fit_weight_inverse: // xerror = 1/w_x = s_x^2 sigmasq = df*df*xerror[i]; break; case nsl_fit_weight_statistical: // s_x^2 = 1/w_x = x sigmasq = xdata[i]; break; case nsl_fit_weight_relative: // s_x^2 = 1/w_x = x^2 sigmasq = xdata[i]*xdata[i]; break; case nsl_fit_weight_statistical_fit: // unused case nsl_fit_weight_relative_fit: break; } if (yerrorVector.size() > 0) { switch (fitData.yWeightsType) { // y-error types: s_y^2 = 1/w_y case nsl_fit_weight_no: break; case nsl_fit_weight_direct: // yerror = w_y sigmasq += 1./yerror[i]; break; case nsl_fit_weight_instrumental: // yerror = s_y sigmasq += yerror[i]*yerror[i]; break; case nsl_fit_weight_inverse: // yerror = 1/w_y sigmasq += yerror[i]; break; case nsl_fit_weight_statistical: // unused case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: // s_y^2 = 1/w_y = Y_i sigmasq += fun[i]; break; case nsl_fit_weight_relative_fit: // s_y^2 = 1/w_y = Y_i^2 sigmasq += fun[i]*fun[i]; break; } } //printf ("sigma[%d] = %g\n", i, sqrt(sigmasq)); weight[i] = 1./sigmasq; } // update weights gsl_multifit_fdfsolver_set(s, &f, &x.vector); do { // fit iter++; writeSolverState(s); status = gsl_multifit_fdfsolver_iterate (s); //printf ("status = %s\n", gsl_strerror (status)); if (status) { DEBUG("iter " << iter << ", status = " << gsl_strerror(status)); break; } status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); chi = gsl_blas_dnrm2(s->f); //printf("chi = %.12g (dchi = %g)\n", chi, fabs(chi-chiOld)); } while (iter2 < maxIters && fabs(chi-chiOld) > fitData.eps); delete[] fun; } delete[] weight; // unscale start parameter for (unsigned int i = 0; i < np; i++) x_init[i] = nsl_fit_map_bound(x_init[i], x_min[i], x_max[i]); //get the covariance matrix //TODO: scale the Jacobian when limits are used before constructing the covar matrix? gsl_matrix* covar = gsl_matrix_alloc(np, np); #if GSL_MAJOR_VERSION >= 2 // the Jacobian is not part of the solver anymore gsl_matrix *J = gsl_matrix_alloc(s->fdf->n, s->fdf->p); gsl_multifit_fdfsolver_jac(s, J); gsl_multifit_covar(J, 0.0, covar); #else gsl_multifit_covar(s->J, 0.0, covar); #endif //write the result fitResult.available = true; fitResult.valid = true; fitResult.status = gslErrorToString(status); fitResult.iterations = iter; fitResult.dof = n - (np - nf); // samples - (parameter - fixed parameter) //gsl_blas_dnrm2() - computes the Euclidian norm (||r||_2 = \sqrt {\sum r_i^2}) of the vector with the elements weight[i]*(Yi - y[i]) //gsl_blas_dasum() - computes the absolute sum \sum |r_i| of the elements of the vector with the elements weight[i]*(Yi - y[i]) fitResult.sse = gsl_pow_2(gsl_blas_dnrm2(s->f)); if (fitResult.dof != 0) { fitResult.rms = fitResult.sse/fitResult.dof; fitResult.rsd = sqrt(fitResult.rms); } fitResult.mse = fitResult.sse/n; fitResult.rmse = sqrt(fitResult.mse); fitResult.mae = gsl_blas_dasum(s->f)/n; // SST needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); // for a linear model without intercept R-squared is calculated differently // see https://cran.r-project.org/doc/FAQ/R-FAQ.html#Why-does-summary_0028_0029-report-strange-results-for-the-R_005e2-estimate-when-I-fit-a-linear-model-with-no-intercept_003f if (fitData.modelCategory == nsl_fit_model_basic && fitData.modelType == nsl_fit_model_polynomial && fitData.degree == 1 && x_init[0] == 0) { DEBUG("Using alternative R^2 for linear model without intercept"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } if (fitResult.sst < fitResult.sse) { DEBUG("Using alternative R^2 since R^2 would be negative (probably custom model without intercept)"); fitResult.sst = gsl_stats_tss_m(ydata, 1, n, 0); } fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof, 1); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms, np, 1); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.logLik = nsl_stats_logLik(fitResult.sse, n); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np, 1); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np, 1); //parameter values // GSL: const double c = GSL_MAX_DBL(1., sqrt(fitResult.rms)); // increase error for poor fit // NIST: const double c = sqrt(fitResult.rms); // increase error for poor fit, decrease for good fit const double c = sqrt(fitResult.rms); fitResult.paramValues.resize(np); fitResult.errorValues.resize(np); fitResult.tdist_tValues.resize(np); fitResult.tdist_pValues.resize(np); fitResult.tdist_marginValues.resize(np); for (unsigned int i = 0; i < np; i++) { // scale resulting values if they are bounded fitResult.paramValues[i] = nsl_fit_map_bound(gsl_vector_get(s->x, i), x_min[i], x_max[i]); // use results as start values if desired if (fitData.useResults) { fitData.paramStartValues.data()[i] = fitResult.paramValues[i]; DEBUG("saving parameter " << i << ": " << fitResult.paramValues[i] << ' ' << fitData.paramStartValues.data()[i]); } fitResult.errorValues[i] = c*sqrt(gsl_matrix_get(covar, i, i)); fitResult.tdist_tValues[i] = nsl_stats_tdist_t(fitResult.paramValues.at(i), fitResult.errorValues.at(i)); fitResult.tdist_pValues[i] = nsl_stats_tdist_p(fitResult.tdist_tValues.at(i), fitResult.dof); fitResult.tdist_marginValues[i] = nsl_stats_tdist_margin(0.05, fitResult.dof, fitResult.errorValues.at(i)); } // fill residuals vector. To get residuals on the correct x values, fill the rest with zeros. residualsVector->resize(tmpXDataColumn->rowCount()); if (fitData.autoRange) { // evaluate full range of residuals xVector->resize(tmpXDataColumn->rowCount()); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*xVector)[i] = tmpXDataColumn->valueAt(i); ExpressionParser* parser = ExpressionParser::getInstance(); bool rc = parser->evaluateCartesian(fitData.model, xVector, residualsVector, fitData.paramNames, fitResult.paramValues); for (int i = 0; i < tmpXDataColumn->rowCount(); i++) (*residualsVector)[i] = tmpYDataColumn->valueAt(i) - (*residualsVector)[i]; if (!rc) residualsVector->clear(); } else { // only selected range size_t j = 0; for (int i = 0; i < tmpXDataColumn->rowCount(); i++) { if (tmpXDataColumn->valueAt(i) >= xmin && tmpXDataColumn->valueAt(i) <= xmax) residualsVector->data()[i] = - gsl_vector_get(s->f, j++); else // outside range residualsVector->data()[i] = 0; } } residualsColumn->setChanged(); //free resources gsl_multifit_fdfsolver_free(s); gsl_matrix_free(covar); //calculate the fit function (vectors) evaluate(); fitResult.elapsedTime = timer.elapsed(); //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } /* evaluate fit function */ void XYFitCurvePrivate::evaluate(bool preview) { DEBUG("XYFitCurvePrivate::evaluate() preview = " << preview); // prepare source data columns const AbstractColumn* tmpXDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { DEBUG(" spreadsheet columns as data source"); tmpXDataColumn = xDataColumn; } else { DEBUG(" curve columns as data source"); tmpXDataColumn = dataSourceCurve->xColumn(); } if (!tmpXDataColumn) { DEBUG("ERROR: Preparing source data column failed!"); return; } prepareResultColumns(); if (!xVector || !yVector) { DEBUG(" xVector or yVector not defined!"); return; } ExpressionParser* parser = ExpressionParser::getInstance(); double xmin, xmax; if (fitData.autoEvalRange) { // evaluate fit on full data range xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { // use given range for evaluation xmin = fitData.evalRange.first(); xmax = fitData.evalRange.last(); } DEBUG(" eval range = " << xmin << " .. " << xmax); xVector->resize((int)fitData.evaluatedPoints); yVector->resize((int)fitData.evaluatedPoints); DEBUG(" vector size = " << xVector->size()); QVector paramValues = fitResult.paramValues; if (preview) // results not available yet paramValues = fitData.paramStartValues; // Debug /* if (paramValues.size() == 0) DEBUG(" ERROR: No parameter defined!"); for (auto value: paramValues) DEBUG(" param value = " << value); */ bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), (int)fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, paramValues); if (!rc) { xVector->clear(); yVector->clear(); residualsVector->clear(); } // Debug /* DEBUG(" x | y"); for (int i = 0; i < qMin(10, xVector->size()); i++) DEBUG(" " << (*xVector)[i] << " | " << (*yVector)[i]); */ // TODO: do we weed to do something to make preview work? // this should be already done by dataChanged() q->retransform(); // PREVIEW redraw the curve (this breaks context menu fit!) if (preview) { // emit q->dataChanged(); // sourceDataChangedSinceLastRecalc = false; } } /*! * writes out the current state of the solver \c s */ void XYFitCurvePrivate::writeSolverState(gsl_multifit_fdfsolver* s) { QString state; //current parameter values, semicolon separated double* min = fitData.paramLowerLimits.data(); double* max = fitData.paramUpperLimits.data(); for (int i = 0; i < fitData.paramNames.size(); ++i) { const double x = gsl_vector_get(s->x, i); // map parameter if bounded state += QString::number(nsl_fit_map_bound(x, min[i], max[i])) + '\t'; } //current value of the chi2-function state += QString::number(gsl_pow_2(gsl_blas_dnrm2(s->f))); state += ';'; fitResult.solverOutput += state; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFitCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFitCurve); writer->writeStartElement("xyFitCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fit-curve specific information //fit data - only save model expression and parameter names for custom model, otherwise they are set in XYFitCurve::initFitData() writer->writeStartElement("fitData"); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("fitRangeMin", QString::number(d->fitData.fitRange.first(), 'g', 15)); writer->writeAttribute("fitRangeMax", QString::number(d->fitData.fitRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("xWeightsType", QString::number(d->fitData.xWeightsType)); writer->writeAttribute("weightsType", QString::number(d->fitData.yWeightsType)); writer->writeAttribute("degree", QString::number(d->fitData.degree)); if (d->fitData.modelCategory == nsl_fit_model_custom) writer->writeAttribute("model", d->fitData.model); writer->writeAttribute("maxIterations", QString::number(d->fitData.maxIterations)); writer->writeAttribute("eps", QString::number(d->fitData.eps, 'g', 15)); writer->writeAttribute("evaluatedPoints", QString::number(d->fitData.evaluatedPoints)); writer->writeAttribute("autoEvalRange", QString::number(d->fitData.autoEvalRange)); writer->writeAttribute("useDataErrors", QString::number(d->fitData.useDataErrors)); writer->writeAttribute("useResults", QString::number(d->fitData.useResults)); if (d->fitData.modelCategory == nsl_fit_model_custom) { writer->writeStartElement("paramNames"); foreach (const QString &name, d->fitData.paramNames) writer->writeTextElement("name", name); writer->writeEndElement(); } writer->writeStartElement("paramStartValues"); foreach (const double &value, d->fitData.paramStartValues) writer->writeTextElement("startValue", QString::number(value, 'g', 15)); writer->writeEndElement(); // use 16 digits to handle -DBL_MAX writer->writeStartElement("paramLowerLimits"); foreach (const double &limit, d->fitData.paramLowerLimits) writer->writeTextElement("lowerLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); // use 16 digits to handle DBL_MAX writer->writeStartElement("paramUpperLimits"); foreach (const double &limit, d->fitData.paramUpperLimits) writer->writeTextElement("upperLimit", QString::number(limit, 'g', 16)); writer->writeEndElement(); writer->writeStartElement("paramFixed"); foreach (const double &fixed, d->fitData.paramFixed) writer->writeTextElement("fixed", QString::number(fixed)); writer->writeEndElement(); writer->writeEndElement(); //"fitData" //fit results (generated columns and goodness of the fit) writer->writeStartElement("fitResult"); writer->writeAttribute("available", QString::number(d->fitResult.available)); writer->writeAttribute("valid", QString::number(d->fitResult.valid)); writer->writeAttribute("status", d->fitResult.status); writer->writeAttribute("iterations", QString::number(d->fitResult.iterations)); writer->writeAttribute("time", QString::number(d->fitResult.elapsedTime)); writer->writeAttribute("dof", QString::number(d->fitResult.dof)); writer->writeAttribute("sse", QString::number(d->fitResult.sse, 'g', 15)); writer->writeAttribute("sst", QString::number(d->fitResult.sst, 'g', 15)); writer->writeAttribute("rms", QString::number(d->fitResult.rms, 'g', 15)); writer->writeAttribute("rsd", QString::number(d->fitResult.rsd, 'g', 15)); writer->writeAttribute("mse", QString::number(d->fitResult.mse, 'g', 15)); writer->writeAttribute("rmse", QString::number(d->fitResult.rmse, 'g', 15)); writer->writeAttribute("mae", QString::number(d->fitResult.mae, 'g', 15)); writer->writeAttribute("rsquare", QString::number(d->fitResult.rsquare, 'g', 15)); writer->writeAttribute("rsquareAdj", QString::number(d->fitResult.rsquareAdj, 'g', 15)); writer->writeAttribute("chisq_p", QString::number(d->fitResult.chisq_p, 'g', 15)); writer->writeAttribute("fdist_F", QString::number(d->fitResult.fdist_F, 'g', 15)); writer->writeAttribute("fdist_p", QString::number(d->fitResult.fdist_p, 'g', 15)); writer->writeAttribute("aic", QString::number(d->fitResult.aic, 'g', 15)); writer->writeAttribute("bic", QString::number(d->fitResult.bic, 'g', 15)); writer->writeAttribute("solverOutput", d->fitResult.solverOutput); writer->writeStartElement("paramValues"); foreach (const double &value, d->fitResult.paramValues) writer->writeTextElement("value", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("errorValues"); foreach (const double &value, d->fitResult.errorValues) writer->writeTextElement("error", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_tValues"); foreach (const double &value, d->fitResult.tdist_tValues) writer->writeTextElement("tdist_t", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_pValues"); foreach (const double &value, d->fitResult.tdist_pValues) writer->writeTextElement("tdist_p", QString::number(value, 'g', 15)); writer->writeEndElement(); writer->writeStartElement("tdist_marginValues"); foreach (const double &value, d->fitResult.tdist_marginValues) writer->writeTextElement("tdist_margin", QString::number(value, 'g', 15)); writer->writeEndElement(); //save calculated columns if available if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->save(writer); d->yColumn->save(writer); d->residualsColumn->save(writer); } writer->writeEndElement(); //"fitResult" writer->writeEndElement(); //"xyFitCurve" } //! Load from XML bool XYFitCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFitCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.fitRange.first()); // old name READ_DOUBLE_VALUE("xRangeMax", fitData.fitRange.last()); // old name READ_DOUBLE_VALUE("fitRangeMin", fitData.fitRange.first()); READ_DOUBLE_VALUE("fitRangeMax", fitData.fitRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("xWeightsType", fitData.xWeightsType, nsl_fit_weight_type); READ_INT_VALUE("weightsType", fitData.yWeightsType, nsl_fit_weight_type); READ_INT_VALUE("degree", fitData.degree, int); if (d->fitData.modelCategory == nsl_fit_model_custom) { READ_STRING_VALUE("model", fitData.model); DEBUG("read model = " << d->fitData.model.toStdString()); } READ_INT_VALUE("maxIterations", fitData.maxIterations, int); READ_DOUBLE_VALUE("eps", fitData.eps); READ_INT_VALUE("fittedPoints", fitData.evaluatedPoints, size_t); // old name READ_INT_VALUE("evaluatedPoints", fitData.evaluatedPoints, size_t); READ_INT_VALUE("evaluateFullRange", fitData.autoEvalRange, bool); // old name READ_INT_VALUE("autoEvalRange", fitData.autoEvalRange, bool); READ_INT_VALUE("useDataErrors", fitData.useDataErrors, bool); READ_INT_VALUE("useResults", fitData.useResults, bool); //set the model expression and the parameter names (can be derived from the saved values for category, type and degree) XYFitCurve::initFitData(d->fitData); } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); } else if (!preview && reader->name() == "lowerLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // -DBL_MAX results in conversion error d->fitData.paramLowerLimits << x; else d->fitData.paramLowerLimits << -std::numeric_limits::max(); } else if (!preview && reader->name() == "upperLimit") { bool ok; double x = reader->readElementText().toDouble(&ok); if (ok) // DBL_MAX results in conversion error d->fitData.paramUpperLimits << x; else d->fitData.paramUpperLimits << std::numeric_limits::max(); } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_pValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); } else if (!preview && reader->name() == "fitResult") { attribs = reader->attributes(); READ_INT_VALUE("available", fitResult.available, int); READ_INT_VALUE("valid", fitResult.valid, int); READ_STRING_VALUE("status", fitResult.status); READ_INT_VALUE("iterations", fitResult.iterations, int); READ_INT_VALUE("time", fitResult.elapsedTime, int); READ_DOUBLE_VALUE("dof", fitResult.dof); READ_DOUBLE_VALUE("sse", fitResult.sse); READ_DOUBLE_VALUE("sst", fitResult.sst); READ_DOUBLE_VALUE("rms", fitResult.rms); READ_DOUBLE_VALUE("rsd", fitResult.rsd); READ_DOUBLE_VALUE("mse", fitResult.mse); READ_DOUBLE_VALUE("rmse", fitResult.rmse); READ_DOUBLE_VALUE("mae", fitResult.mae); READ_DOUBLE_VALUE("rsquare", fitResult.rsquare); READ_DOUBLE_VALUE("rsquareAdj", fitResult.rsquareAdj); READ_DOUBLE_VALUE("chisq_p", fitResult.chisq_p); READ_DOUBLE_VALUE("fdist_F", fitResult.fdist_F); READ_DOUBLE_VALUE("fdist_p", fitResult.fdist_p); READ_DOUBLE_VALUE("aic", fitResult.aic); READ_DOUBLE_VALUE("bic", fitResult.bic); READ_STRING_VALUE("solverOutput", fitResult.solverOutput); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; else if (column->name() == "residuals") d->residualsColumn = column; } } if (preview) return true; // new fit model style (reset model type of old projects) if (d->fitData.modelCategory == nsl_fit_model_basic && d->fitData.modelType >= NSL_FIT_MODEL_BASIC_COUNT) { DEBUG("reset old fit model"); d->fitData.modelType = 0; d->fitData.degree = 1; // reset size of fields not touched by initFitData() d->fitData.paramStartValues.resize(2); d->fitData.paramFixed.resize(2); d->fitResult.paramValues.resize(2); d->fitResult.errorValues.resize(2); d->fitResult.tdist_tValues.resize(2); d->fitResult.tdist_pValues.resize(2); d->fitResult.tdist_marginValues.resize(2); } // not present in old projects if (d->fitResult.tdist_tValues.size() == 0) d->fitResult.tdist_tValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_pValues.size() == 0) d->fitResult.tdist_pValues.resize(d->fitResult.paramValues.size()); if (d->fitResult.tdist_marginValues.size() == 0) d->fitResult.tdist_marginValues.resize(d->fitResult.paramValues.size()); // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn && d->residualsColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); addChild(d->residualsColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); d->residualsVector = static_cast* >(d->residualsColumn->data()); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp index 84a955310..971dcede2 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -1,398 +1,398 @@ /*************************************************************************** File : XYFourierFilterCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier filter -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierFilterCurve \brief A xy-curve defined by a Fourier filter \ingroup worksheet */ #include "XYFourierFilterCurve.h" #include "XYFourierFilterCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #ifdef HAVE_FFTW3 #include #endif #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include // qWarning() XYFourierFilterCurve::XYFourierFilterCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierFilterCurvePrivate(this)) { } XYFourierFilterCurve::XYFourierFilterCurve(const QString& name, XYFourierFilterCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFourierFilterCurve::~XYFourierFilterCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierFilterCurve::recalculate() { Q_D(XYFourierFilterCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierFilterCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_filter-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, XYFourierFilterCurve::FilterData, filterData, filterData) const XYFourierFilterCurve::FilterResult& XYFourierFilterCurve::filterResult() const { Q_D(const XYFourierFilterCurve); return d->filterResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierFilterCurve, SetFilterData, XYFourierFilterCurve::FilterData, filterData, recalculate); void XYFourierFilterCurve::setFilterData(const XYFourierFilterCurve::FilterData& filterData) { Q_D(XYFourierFilterCurve); - exec(new XYFourierFilterCurveSetFilterDataCmd(d, filterData, i18n("%1: set filter options and perform the Fourier filter"))); + exec(new XYFourierFilterCurveSetFilterDataCmd(d, filterData, ki18n("%1: set filter options and perform the Fourier filter"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierFilterCurvePrivate::XYFourierFilterCurvePrivate(XYFourierFilterCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYFourierFilterCurvePrivate::~XYFourierFilterCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYFourierFilterCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create filter result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result filterResult = XYFourierFilterCurve::FilterResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the differentiation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (filterData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = filterData.xRange.first(); xmax = filterData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to filter const size_t n = (size_t)xdataVector.size(); if (n == 0) { filterResult.available = true; filterResult.valid = false; filterResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // filter settings const nsl_filter_type type = filterData.type; const nsl_filter_form form = filterData.form; const int order = filterData.order; const double cutoff = filterData.cutoff, cutoff2 = filterData.cutoff2; const nsl_filter_cutoff_unit unit = filterData.unit, unit2 = filterData.unit2; DEBUG("n ="< 0. Giving up."; return; } DEBUG("cut off @" << cutindex << cutindex2); DEBUG("bandwidth =" << bandwidth); // run filter int status = nsl_filter_fourier(ydata, n, type, form, order, cutindex, bandwidth); xVector->resize((int)n); yVector->resize((int)n); memcpy(xVector->data(), xdataVector.data(), n*sizeof(double)); memcpy(yVector->data(), ydata, n*sizeof(double)); /////////////////////////////////////////////////////////// //write the result filterResult.available = true; filterResult.valid = true; filterResult.status = gslErrorToString(status); filterResult.elapsedTime = timer.elapsed(); //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierFilterCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierFilterCurve); writer->writeStartElement("xyFourierFilterCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_filter-curve specific information //filter data writer->writeStartElement("filterData"); writer->writeAttribute( "autoRange", QString::number(d->filterData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->filterData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->filterData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->filterData.type) ); writer->writeAttribute( "form", QString::number(d->filterData.form) ); writer->writeAttribute( "order", QString::number(d->filterData.order) ); writer->writeAttribute( "cutoff", QString::number(d->filterData.cutoff) ); writer->writeAttribute( "unit", QString::number(d->filterData.unit) ); writer->writeAttribute( "cutoff2", QString::number(d->filterData.cutoff2) ); writer->writeAttribute( "unit2", QString::number(d->filterData.unit2) ); writer->writeEndElement();// filterData //filter results (generated columns) writer->writeStartElement("filterResult"); writer->writeAttribute( "available", QString::number(d->filterResult.available) ); writer->writeAttribute( "valid", QString::number(d->filterResult.valid) ); writer->writeAttribute( "status", d->filterResult.status ); writer->writeAttribute( "time", QString::number(d->filterResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"filterResult" writer->writeEndElement(); //"xyFourierFilterCurve" } //! Load from XML bool XYFourierFilterCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierFilterCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierFilterCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "filterData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", filterData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", filterData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", filterData.xRange.last()); READ_INT_VALUE("type", filterData.type, nsl_filter_type); READ_INT_VALUE("form", filterData.form, nsl_filter_form); READ_INT_VALUE("order", filterData.order, int); READ_DOUBLE_VALUE("cutoff", filterData.cutoff); READ_INT_VALUE("unit", filterData.unit, nsl_filter_cutoff_unit); READ_DOUBLE_VALUE("cutoff2", filterData.cutoff2); READ_INT_VALUE("unit2", filterData.unit2, nsl_filter_cutoff_unit); } else if (!preview && reader->name() == "filterResult") { attribs = reader->attributes(); READ_INT_VALUE("available", filterResult.available, int); READ_INT_VALUE("valid", filterResult.valid, int); READ_STRING_VALUE("status", filterResult.status); READ_INT_VALUE("time", filterResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp index 2bc840872..2626572f2 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -1,384 +1,384 @@ /*************************************************************************** File : XYFourierTransformCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier transform -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYFourierTransformCurve \brief A xy-curve defined by a Fourier transform \ingroup worksheet */ #include "XYFourierTransformCurve.h" #include "XYFourierTransformCurvePrivate.h" #include "backend/core/AbstractColumn.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include #include // qWarning() XYFourierTransformCurve::XYFourierTransformCurve(const QString& name) : XYAnalysisCurve(name, new XYFourierTransformCurvePrivate(this)) { } XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYFourierTransformCurve::~XYFourierTransformCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierTransformCurve::recalculate() { Q_D(XYFourierTransformCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYFourierTransformCurve::icon() const { return QIcon::fromTheme("labplot-xy-fourier_transform-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, XYFourierTransformCurve::TransformData, transformData, transformData) const XYFourierTransformCurve::TransformResult& XYFourierTransformCurve::transformResult() const { Q_D(const XYFourierTransformCurve); return d->transformResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYFourierTransformCurve, SetTransformData, XYFourierTransformCurve::TransformData, transformData, recalculate); void XYFourierTransformCurve::setTransformData(const XYFourierTransformCurve::TransformData& transformData) { Q_D(XYFourierTransformCurve); - exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, i18n("%1: set transform options and perform the Fourier transform"))); + exec(new XYFourierTransformCurveSetTransformDataCmd(d, transformData, ki18n("%1: set transform options and perform the Fourier transform"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYFourierTransformCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create transform result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result transformResult = XYFourierTransformCurve::TransformResult(); if (!xDataColumn || !yDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (xDataColumn->rowCount()!=yDataColumn->rowCount()) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the transform to temporary vectors QVector xdataVector; QVector ydataVector; const double xmin = transformData.xRange.first(); const double xmax = transformData.xRange.last(); for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(xDataColumn->valueAt(row)) && !std::isnan(yDataColumn->valueAt(row)) && !xDataColumn->isMasked(row) && !yDataColumn->isMasked(row)) { // only when inside given range if (xDataColumn->valueAt(row) >= xmin && xDataColumn->valueAt(row) <= xmax) { xdataVector.append(xDataColumn->valueAt(row)); ydataVector.append(yDataColumn->valueAt(row)); } } } //number of data points to transform unsigned int n = (unsigned int)ydataVector.size(); if (n == 0) { transformResult.available = true; transformResult.valid = false; transformResult.status = i18n("No data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // transform settings const nsl_sf_window_type windowType = transformData.windowType; const nsl_dft_result_type type = transformData.type; const bool twoSided = transformData.twoSided; const bool shifted = transformData.shifted; const nsl_dft_xscale xScale = transformData.xScale; DEBUG("n =" << n); DEBUG("window type:" << nsl_sf_window_type_name[windowType]); DEBUG("type:" << nsl_dft_result_type_name[type]); DEBUG("scale:" << nsl_dft_xscale_name[xScale]); DEBUG("two sided:" << twoSided); DEBUG("shifted:" << shifted); #ifndef NDEBUG QDebug out = qDebug(); for (unsigned int i=0; i < n; i++) out<= n/2 && shifted) xdata[i] = (n-1)/(xmax-xmin)*(i/(double)n-1.); else xdata[i] = (n-1)*i/(xmax-xmin)/n; } break; case nsl_dft_xscale_index: for (unsigned int i=0; i < N; i++) { if (i >= n/2 && shifted) xdata[i] = (int)i-(int) N; else xdata[i] = i; } break; case nsl_dft_xscale_period: { double f0 = (n-1)/(xmax-xmin)/n; for (unsigned int i=0; i < N; i++) { double f = (n-1)*i/(xmax-xmin)/n; xdata[i] = 1/(f+f0); } break; } } #ifndef NDEBUG out = qDebug(); for (unsigned int i=0; i < N; i++) out << ydata[i] << '(' << xdata[i] << ')'; #endif xVector->resize((int)N); yVector->resize((int)N); if(shifted) { memcpy(xVector->data(), &xdata[n/2], n/2*sizeof(double)); memcpy(&xVector->data()[n/2], xdata, n/2*sizeof(double)); memcpy(yVector->data(), &ydata[n/2], n/2*sizeof(double)); memcpy(&yVector->data()[n/2], ydata, n/2*sizeof(double)); } else { memcpy(xVector->data(), xdata, N*sizeof(double)); memcpy(yVector->data(), ydata, N*sizeof(double)); } /////////////////////////////////////////////////////////// //write the result transformResult.available = true; transformResult.valid = true; transformResult.status = gslErrorToString(status); transformResult.elapsedTime = timer.elapsed(); //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYFourierTransformCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYFourierTransformCurve); writer->writeStartElement("xyFourierTransformCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-fourier_transform-curve specific information //transform data writer->writeStartElement("transformData"); writer->writeAttribute( "autoRange", QString::number(d->transformData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->transformData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->transformData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->transformData.type) ); writer->writeAttribute( "twoSided", QString::number(d->transformData.twoSided) ); writer->writeAttribute( "shifted", QString::number(d->transformData.shifted) ); writer->writeAttribute( "xScale", QString::number(d->transformData.xScale) ); writer->writeAttribute( "windowType", QString::number(d->transformData.windowType) ); writer->writeEndElement();// transformData //transform results (generated columns) writer->writeStartElement("transformResult"); writer->writeAttribute( "available", QString::number(d->transformResult.available) ); writer->writeAttribute( "valid", QString::number(d->transformResult.valid) ); writer->writeAttribute( "status", d->transformResult.status ); writer->writeAttribute( "time", QString::number(d->transformResult.elapsedTime) ); //save calculated columns if available if (d->xColumn && d->yColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"transformResult" writer->writeEndElement(); //"xyFourierTransformCurve" } //! Load from XML bool XYFourierTransformCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYFourierTransformCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyFourierTransformCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "transformData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", transformData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", transformData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", transformData.xRange.last()); READ_INT_VALUE("type", transformData.type, nsl_dft_result_type); READ_INT_VALUE("twoSided", transformData.twoSided, bool); READ_INT_VALUE("shifted", transformData.shifted, bool); READ_INT_VALUE("xScale", transformData.xScale, nsl_dft_xscale); READ_INT_VALUE("windowType", transformData.windowType, nsl_sf_window_type); } else if (!preview && reader->name() == "transformResult") { attribs = reader->attributes(); READ_INT_VALUE("available", transformResult.available, int); READ_INT_VALUE("valid", transformResult.valid, int); READ_STRING_VALUE("status", transformResult.status); READ_INT_VALUE("time", transformResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name() == "x") d->xColumn = column; else if (column->name() == "y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } else qWarning()<<" d->xColumn == NULL!"; return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp index 11092a825..b91737ebc 100644 --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -1,365 +1,365 @@ /*************************************************************************** File : XYIntegrationCurve.cpp Project : LabPlot Description : A xy-curve defined by an integration -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYIntegrationCurve \brief A xy-curve defined by an integration \ingroup worksheet */ #include "XYIntegrationCurve.h" #include "XYIntegrationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" extern "C" { #include } #include #include #include #include XYIntegrationCurve::XYIntegrationCurve(const QString& name) : XYAnalysisCurve(name, new XYIntegrationCurvePrivate(this)) { } XYIntegrationCurve::XYIntegrationCurve(const QString& name, XYIntegrationCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYIntegrationCurve::~XYIntegrationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYIntegrationCurve::recalculate() { Q_D(XYIntegrationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYIntegrationCurve::icon() const { return QIcon::fromTheme("labplot-xy-integration-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, XYIntegrationCurve::IntegrationData, integrationData, integrationData) const XYIntegrationCurve::IntegrationResult& XYIntegrationCurve::integrationResult() const { Q_D(const XYIntegrationCurve); return d->integrationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYIntegrationCurve, SetIntegrationData, XYIntegrationCurve::IntegrationData, integrationData, recalculate); void XYIntegrationCurve::setIntegrationData(const XYIntegrationCurve::IntegrationData& integrationData) { Q_D(XYIntegrationCurve); - exec(new XYIntegrationCurveSetIntegrationDataCmd(d, integrationData, i18n("%1: set options and perform the integration"))); + exec(new XYIntegrationCurveSetIntegrationDataCmd(d, integrationData, ki18n("%1: set options and perform the integration"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYIntegrationCurvePrivate::XYIntegrationCurvePrivate(XYIntegrationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYIntegrationCurvePrivate::~XYIntegrationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYIntegrationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create integration result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result integrationResult = XYIntegrationCurve::IntegrationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the integration to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (integrationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = integrationData.xRange.first(); xmax = integrationData.xRange.last(); } for (int row = 0; row < tmpXDataColumn->rowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } const size_t n = (size_t)xdataVector.size(); // number of data points to integrate if (n < 2) { integrationResult.available = true; integrationResult.valid = false; integrationResult.status = i18n("Not enough data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // integration settings const nsl_int_method_type method = integrationData.method; const bool absolute = integrationData.absolute; DEBUG("method:"<resize((int)np); yVector->resize((int)np); memcpy(xVector->data(), xdata, np * sizeof(double)); memcpy(yVector->data(), ydata, np * sizeof(double)); /////////////////////////////////////////////////////////// //write the result integrationResult.available = true; integrationResult.valid = true; integrationResult.status = QString::number(status); integrationResult.elapsedTime = timer.elapsed(); integrationResult.value = ydata[np-1]; //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYIntegrationCurve::save(QXmlStreamWriter* writer) const{ Q_D(const XYIntegrationCurve); writer->writeStartElement("xyIntegrationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-integration-curve specific information // integration data writer->writeStartElement("integrationData"); writer->writeAttribute( "autoRange", QString::number(d->integrationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->integrationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->integrationData.xRange.last()) ); writer->writeAttribute( "method", QString::number(d->integrationData.method) ); writer->writeAttribute( "absolute", QString::number(d->integrationData.absolute) ); writer->writeEndElement();// integrationData // integration results (generated columns) writer->writeStartElement("integrationResult"); writer->writeAttribute( "available", QString::number(d->integrationResult.available) ); writer->writeAttribute( "valid", QString::number(d->integrationResult.valid) ); writer->writeAttribute( "status", d->integrationResult.status ); writer->writeAttribute( "time", QString::number(d->integrationResult.elapsedTime) ); writer->writeAttribute( "value", QString::number(d->integrationResult.value) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"integrationResult" writer->writeEndElement(); //"xyIntegrationCurve" } //! Load from XML bool XYIntegrationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYIntegrationCurve); if (!reader->isStartElement() || reader->name() != "xyIntegrationCurve") { reader->raiseError(i18n("no xy integration curve element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyIntegrationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "integrationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", integrationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", integrationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", integrationData.xRange.last()); READ_INT_VALUE("method", integrationData.method, nsl_int_method_type); READ_INT_VALUE("absolute", integrationData.absolute, bool); } else if (!preview && reader->name() == "integrationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", integrationResult.available, int); READ_INT_VALUE("valid", integrationResult.valid, int); READ_STRING_VALUE("status", integrationResult.status); READ_INT_VALUE("time", integrationResult.elapsedTime, int); READ_DOUBLE_VALUE("value", integrationResult.value); } else if (!preview && reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp index 75f375a05..e3f7eed89 100644 --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -1,554 +1,554 @@ /*************************************************************************** File : XYInterpolationCurve.cpp Project : LabPlot Description : A xy-curve defined by an interpolation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2016-2017 Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYInterpolationCurve \brief A xy-curve defined by an interpolation \ingroup worksheet */ #include "XYInterpolationCurve.h" #include "XYInterpolationCurvePrivate.h" #include "CartesianCoordinateSystem.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include "backend/gsl/errors.h" extern "C" { #include #include #include "backend/nsl/nsl_diff.h" #include "backend/nsl/nsl_int.h" } #include #include #include XYInterpolationCurve::XYInterpolationCurve(const QString& name) : XYAnalysisCurve(name, new XYInterpolationCurvePrivate(this)) { } XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYInterpolationCurve::~XYInterpolationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYInterpolationCurve::recalculate() { Q_D(XYInterpolationCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYInterpolationCurve::icon() const { return QIcon::fromTheme("labplot-xy-interpolation-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, XYInterpolationCurve::InterpolationData, interpolationData, interpolationData) const XYInterpolationCurve::InterpolationResult& XYInterpolationCurve::interpolationResult() const { Q_D(const XYInterpolationCurve); return d->interpolationResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYInterpolationCurve, SetInterpolationData, XYInterpolationCurve::InterpolationData, interpolationData, recalculate); void XYInterpolationCurve::setInterpolationData(const XYInterpolationCurve::InterpolationData& interpolationData) { Q_D(XYInterpolationCurve); - exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, i18n("%1: set options and perform the interpolation"))); + exec(new XYInterpolationCurveSetInterpolationDataCmd(d, interpolationData, ki18n("%1: set options and perform the interpolation"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYInterpolationCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create interpolation result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result interpolationResult = XYInterpolationCurve::InterpolationResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the interpolation to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (interpolationData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = interpolationData.xRange.first(); xmax = interpolationData.xRange.last(); } for (int row=0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to interpolate const size_t n = (size_t)xdataVector.size(); if (n < 2) { interpolationResult.available = true; interpolationResult.valid = false; interpolationResult.status = i18n("Not enough data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // interpolation settings const nsl_interp_type type = interpolationData.type; const nsl_interp_pch_variant variant = interpolationData.variant; const double tension = interpolationData.tension; const double continuity = interpolationData.continuity; const double bias = interpolationData.bias; const nsl_interp_evaluate evaluate = interpolationData.evaluate; const size_t npoints = interpolationData.npoints; DEBUG("type:"<data(), yVector->data(), npoints); break; case nsl_interp_evaluate_second_derivative: nsl_diff_second_deriv_second_order(xVector->data(), yVector->data(), npoints); break; case nsl_interp_evaluate_integral: nsl_int_trapezoid(xVector->data(), yVector->data(), npoints, 0); break; } } // check values for (int i = 0; i < (int)npoints; i++) { if ((*yVector)[i] > std::numeric_limits::max()) (*yVector)[i] = std::numeric_limits::max(); else if ((*yVector)[i] < std::numeric_limits::lowest()) (*yVector)[i] = std::numeric_limits::lowest(); } gsl_spline_free(spline); gsl_interp_accel_free(acc); /////////////////////////////////////////////////////////// //write the result interpolationResult.available = true; interpolationResult.valid = true; interpolationResult.status = gslErrorToString(status); interpolationResult.elapsedTime = timer.elapsed(); //redraw the curve emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void XYInterpolationCurve::save(QXmlStreamWriter* writer) const { Q_D(const XYInterpolationCurve); writer->writeStartElement("xyInterpolationCurve"); //write the base class XYAnalysisCurve::save(writer); //write xy-interpolation-curve specific information // interpolation data writer->writeStartElement("interpolationData"); writer->writeAttribute( "autoRange", QString::number(d->interpolationData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->interpolationData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->interpolationData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->interpolationData.type) ); writer->writeAttribute( "variant", QString::number(d->interpolationData.variant) ); writer->writeAttribute( "tension", QString::number(d->interpolationData.tension) ); writer->writeAttribute( "continuity", QString::number(d->interpolationData.continuity) ); writer->writeAttribute( "bias", QString::number(d->interpolationData.bias) ); writer->writeAttribute( "npoints", QString::number(d->interpolationData.npoints) ); writer->writeAttribute( "pointsMode", QString::number(d->interpolationData.pointsMode) ); writer->writeAttribute( "evaluate", QString::number(d->interpolationData.evaluate) ); writer->writeEndElement();// interpolationData // interpolation results (generated columns) writer->writeStartElement("interpolationResult"); writer->writeAttribute( "available", QString::number(d->interpolationResult.available) ); writer->writeAttribute( "valid", QString::number(d->interpolationResult.valid) ); writer->writeAttribute( "status", d->interpolationResult.status ); writer->writeAttribute( "time", QString::number(d->interpolationResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"interpolationResult" writer->writeEndElement(); //"xyInterpolationCurve" } //! Load from XML bool XYInterpolationCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYInterpolationCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xyInterpolationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "interpolationData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", interpolationData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", interpolationData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", interpolationData.xRange.last()); READ_INT_VALUE("type", interpolationData.type, nsl_interp_type); READ_INT_VALUE("variant", interpolationData.variant, nsl_interp_pch_variant); READ_DOUBLE_VALUE("tension", interpolationData.tension); READ_DOUBLE_VALUE("continuity", interpolationData.continuity); READ_DOUBLE_VALUE("bias", interpolationData.bias); READ_INT_VALUE("npoints", interpolationData.npoints, size_t); READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); } else if (!preview && reader->name() == "interpolationResult") { attribs = reader->attributes(); READ_INT_VALUE("available", interpolationResult.available, int); READ_INT_VALUE("valid", interpolationResult.valid, int); READ_STRING_VALUE("status", interpolationResult.status); READ_INT_VALUE("time", interpolationResult.elapsedTime, int); } else if (reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; } diff --git a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp index ec98b98a7..b51868bbd 100644 --- a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp @@ -1,383 +1,383 @@ /*************************************************************************** File : XYSmoothCurve.cpp Project : LabPlot Description : A xy-curve defined by a smooth -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ /*! \class XYSmoothCurve \brief A xy-curve defined by a smooth \ingroup worksheet */ #include "XYSmoothCurve.h" #include "XYSmoothCurvePrivate.h" #include "backend/core/column/Column.h" #include "backend/lib/commandtemplates.h" #include "backend/lib/macros.h" #include #include #include #include extern "C" { #include // gsl_pow_* #include "backend/nsl/nsl_stats.h" #include "backend/nsl/nsl_sf_kernel.h" } XYSmoothCurve::XYSmoothCurve(const QString& name) : XYAnalysisCurve(name, new XYSmoothCurvePrivate(this)) { } XYSmoothCurve::XYSmoothCurve(const QString& name, XYSmoothCurvePrivate* dd) : XYAnalysisCurve(name, dd) { } XYSmoothCurve::~XYSmoothCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYSmoothCurve::recalculate() { Q_D(XYSmoothCurve); d->recalculate(); } /*! Returns an icon to be used in the project explorer. */ QIcon XYSmoothCurve::icon() const { return QIcon::fromTheme("labplot-xy-smooth-curve"); } //############################################################################## //########################## getter methods ################################## //############################################################################## BASIC_SHARED_D_READER_IMPL(XYSmoothCurve, XYSmoothCurve::SmoothData, smoothData, smoothData) const XYSmoothCurve::SmoothResult& XYSmoothCurve::smoothResult() const { Q_D(const XYSmoothCurve); return d->smoothResult; } //############################################################################## //################# setter methods and undo commands ########################## //############################################################################## STD_SETTER_CMD_IMPL_F_S(XYSmoothCurve, SetSmoothData, XYSmoothCurve::SmoothData, smoothData, recalculate); void XYSmoothCurve::setSmoothData(const XYSmoothCurve::SmoothData& smoothData) { Q_D(XYSmoothCurve); - exec(new XYSmoothCurveSetSmoothDataCmd(d, smoothData, i18n("%1: set options and perform the smooth"))); + exec(new XYSmoothCurveSetSmoothDataCmd(d, smoothData, ki18n("%1: set options and perform the smooth"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYSmoothCurvePrivate::XYSmoothCurvePrivate(XYSmoothCurve* owner) : XYAnalysisCurvePrivate(owner), q(owner) { } XYSmoothCurvePrivate::~XYSmoothCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } void XYSmoothCurvePrivate::recalculate() { QElapsedTimer timer; timer.start(); //create smooth result columns if not available yet, clear them otherwise if (!xColumn) { xColumn = new Column("x", AbstractColumn::Numeric); yColumn = new Column("y", AbstractColumn::Numeric); xVector = static_cast* >(xColumn->data()); yVector = static_cast* >(yColumn->data()); xColumn->setHidden(true); q->addChild(xColumn); yColumn->setHidden(true); q->addChild(yColumn); q->setUndoAware(false); q->setXColumn(xColumn); q->setYColumn(yColumn); q->setUndoAware(true); } else { xVector->clear(); yVector->clear(); } // clear the previous result smoothResult = XYSmoothCurve::SmoothResult(); //determine the data source columns const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYAnalysisCurve::DataSourceSpreadsheet) { //spreadsheet columns as data source tmpXDataColumn = xDataColumn; tmpYDataColumn = yDataColumn; } else { //curve columns as data source tmpXDataColumn = dataSourceCurve->xColumn(); tmpYDataColumn = dataSourceCurve->yColumn(); } if (!tmpXDataColumn || !tmpYDataColumn) { emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //check column sizes if (tmpXDataColumn->rowCount() != tmpYDataColumn->rowCount()) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Number of x and y data points must be equal."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } //copy all valid data point for the smooth to temporary vectors QVector xdataVector; QVector ydataVector; double xmin; double xmax; if (smoothData.autoRange) { xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } else { xmin = smoothData.xRange.first(); xmax = smoothData.xRange.last(); } for (int row = 0; rowrowCount(); ++row) { //only copy those data where _all_ values (for x and y, if given) are valid if (!std::isnan(tmpXDataColumn->valueAt(row)) && !std::isnan(tmpYDataColumn->valueAt(row)) && !tmpXDataColumn->isMasked(row) && !tmpYDataColumn->isMasked(row)) { // only when inside given range if (tmpXDataColumn->valueAt(row) >= xmin && tmpXDataColumn->valueAt(row) <= xmax) { xdataVector.append(tmpXDataColumn->valueAt(row)); ydataVector.append(tmpYDataColumn->valueAt(row)); } } } //number of data points to smooth const size_t n = (size_t)xdataVector.size(); if (n < 2) { smoothResult.available = true; smoothResult.valid = false; smoothResult.status = i18n("Not enough data points available."); emit q->dataChanged(); sourceDataChangedSinceLastRecalc = false; return; } double* xdata = xdataVector.data(); double* ydata = ydataVector.data(); // smooth settings const nsl_smooth_type type = smoothData.type; const size_t points = smoothData.points; const nsl_smooth_weight_type weight = smoothData.weight; const double percentile = smoothData.percentile; const int order = smoothData.order; const nsl_smooth_pad_mode mode = smoothData.mode; const double lvalue = smoothData.lvalue; const double rvalue = smoothData.rvalue; DEBUG("type:"<writeStartElement("smoothData"); writer->writeAttribute( "autoRange", QString::number(d->smoothData.autoRange) ); writer->writeAttribute( "xRangeMin", QString::number(d->smoothData.xRange.first()) ); writer->writeAttribute( "xRangeMax", QString::number(d->smoothData.xRange.last()) ); writer->writeAttribute( "type", QString::number(d->smoothData.type) ); writer->writeAttribute( "points", QString::number(d->smoothData.points) ); writer->writeAttribute( "weight", QString::number(d->smoothData.weight) ); writer->writeAttribute( "percentile", QString::number(d->smoothData.percentile) ); writer->writeAttribute( "order", QString::number(d->smoothData.order) ); writer->writeAttribute( "mode", QString::number(d->smoothData.mode) ); writer->writeAttribute( "lvalue", QString::number(d->smoothData.lvalue) ); writer->writeAttribute( "rvalue", QString::number(d->smoothData.rvalue) ); writer->writeEndElement();// smoothData // smooth results (generated columns) writer->writeStartElement("smoothResult"); writer->writeAttribute( "available", QString::number(d->smoothResult.available) ); writer->writeAttribute( "valid", QString::number(d->smoothResult.valid) ); writer->writeAttribute( "status", d->smoothResult.status ); writer->writeAttribute( "time", QString::number(d->smoothResult.elapsedTime) ); //save calculated columns if available if (d->xColumn) { d->xColumn->save(writer); d->yColumn->save(writer); } writer->writeEndElement(); //"smoothResult" writer->writeEndElement(); //"xySmoothCurve" } //! Load from XML bool XYSmoothCurve::load(XmlStreamReader* reader, bool preview) { Q_D(XYSmoothCurve); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "xySmoothCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyAnalysisCurve") { if ( !XYAnalysisCurve::load(reader, preview) ) return false; } else if (!preview && reader->name() == "smoothData") { attribs = reader->attributes(); READ_INT_VALUE("autoRange", smoothData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", smoothData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", smoothData.xRange.last()); READ_INT_VALUE("type", smoothData.type, nsl_smooth_type); READ_INT_VALUE("points", smoothData.points, size_t); READ_INT_VALUE("weight", smoothData.weight, nsl_smooth_weight_type); READ_DOUBLE_VALUE("percentile", smoothData.percentile); READ_INT_VALUE("order", smoothData.order, int); READ_INT_VALUE("mode", smoothData.mode, nsl_smooth_pad_mode); READ_DOUBLE_VALUE("lvalue", smoothData.lvalue); READ_DOUBLE_VALUE("rvalue", smoothData.rvalue); } else if (!preview && reader->name() == "smoothResult") { attribs = reader->attributes(); READ_INT_VALUE("available", smoothResult.available, int); READ_INT_VALUE("valid", smoothResult.valid, int); READ_STRING_VALUE("status", smoothResult.status); READ_INT_VALUE("time", smoothResult.elapsedTime, int); } else if (!preview && reader->name() == "column") { Column* column = new Column("", AbstractColumn::Numeric); if (!column->load(reader, preview)) { delete column; return false; } if (column->name()=="x") d->xColumn = column; else if (column->name()=="y") d->yColumn = column; } } if (preview) return true; // wait for data to be read before using the pointers QThreadPool::globalInstance()->waitForDone(); if (d->xColumn && d->yColumn) { d->xColumn->setHidden(true); addChild(d->xColumn); d->yColumn->setHidden(true); addChild(d->yColumn); d->xVector = static_cast* >(d->xColumn->data()); d->yVector = static_cast* >(d->yColumn->data()); setUndoAware(false); XYCurve::d_ptr->xColumn = d->xColumn; XYCurve::d_ptr->yColumn = d->yColumn; setUndoAware(true); } return true; }