diff --git a/src/backend/cantorWorksheet/CantorWorksheet.cpp b/src/backend/cantorWorksheet/CantorWorksheet.cpp index cc762b9eb..6eee33de0 100644 --- a/src/backend/cantorWorksheet/CantorWorksheet.cpp +++ b/src/backend/cantorWorksheet/CantorWorksheet.cpp @@ -1,312 +1,312 @@ /*************************************************************************** File : CantorWorksheet.cpp Project : LabPlot Description : Aspect providing a Cantor Worksheets for Multiple backends -------------------------------------------------------------------- Copyright : (C) 2015 Garvit Khatri (garvitdelhi@gmail.com) Copyright : (C) 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 "CantorWorksheet.h" #include "VariableParser.h" #include "backend/core/column/Column.h" #include "backend/core/column/ColumnPrivate.h" #include "backend/core/Project.h" #include "commonfrontend/cantorWorksheet/CantorWorksheetView.h" #include #include #include #include #include #include #include "cantor/cantor_part.h" #include #include #include CantorWorksheet::CantorWorksheet(AbstractScriptingEngine* engine, const QString &name, bool loading) : AbstractPart(name), scripted(engine), m_backendName(name), m_session(0), m_part(0), m_variableModel(0), m_worksheetAccess(0) { if(!loading) init(); } /*! initializes Cantor's part and plugins */ bool CantorWorksheet::init(QByteArray* content) { KPluginFactory* factory = KPluginLoader(QLatin1String("libcantorpart")).factory(); if (factory) { m_part = factory->create(this, QVariantList() << m_backendName << QLatin1String("--noprogress")); if (!m_part) { qDebug() << "Could not create the Cantor Part."; return false; } m_worksheetAccess = m_part->findChild(Cantor::WorksheetAccessInterface::Name); //load worksheet content if available if(content) m_worksheetAccess->loadWorksheetFromByteArray(content); connect(m_worksheetAccess, SIGNAL(sessionChanged()), this, SLOT(sessionChanged())); //Cantor's session m_session = m_worksheetAccess->session(); connect(m_session, SIGNAL(statusChanged(Cantor::Session::Status)), this, SIGNAL(statusChanged(Cantor::Session::Status))); //variable model m_variableModel = m_session->variableModel(); connect(m_variableModel, SIGNAL(rowsInserted(const QModelIndex, int, int)), this, SLOT(rowsInserted(const QModelIndex, int, int))); connect(m_variableModel, SIGNAL(rowsAboutToBeRemoved(const QModelIndex, int, int)), this, SLOT(rowsAboutToBeRemoved(const QModelIndex, int, int))); connect(m_variableModel, SIGNAL(modelReset()), this, SLOT(modelReset())); //available plugins Cantor::PanelPluginHandler* handler = m_part->findChild(QLatin1String("PanelPluginHandler")); if(!handler) { KMessageBox::error(view(), i18n("no PanelPluginHandle found for the Cantor Part.")); return false; } m_plugins = handler->plugins(); } else { //we can only get to this here if we open a project having Cantor content and Cantor plugins were not found. //return false here, a proper error message will be created in load() and propagated further. return false; } return true; } //SLots void CantorWorksheet::rowsInserted(const QModelIndex& parent, int first, int last) { Q_UNUSED(parent) for(int i = first; i <= last; ++i) { const QString name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); const QString value = m_variableModel->data(m_variableModel->index(first, 1)).toString(); VariableParser* parser = new VariableParser(m_backendName, value); if(parser->isParsed()) { Column* col = child(name); if (col) { col->replaceValues(0, parser->values()); } else { col = new Column(name, parser->values()); col->setUndoAware(false); addChild(col); //TODO: Cantor currently ignores the order of variables in the worksheets //and adds new variables at the last position in the model. //Fix this in Cantor and switch to insertChildBefore here later. //insertChildBefore(col, child(i)); } } else { //the already existing variable doesn't contain any numerical values -> remove it Column* col = child(name); if (col) removeChild(col); } delete(parser); } project()->setChanged(true); } void CantorWorksheet::sessionChanged() { //TODO: signal is never emitted in Cantor project()->setChanged(true); } void CantorWorksheet::modelReset() { for(int i = 0; i < childCount(); ++i) child(i)->remove(); } void CantorWorksheet::rowsAboutToBeRemoved(const QModelIndex & parent, int first, int last) { + Q_UNUSED(parent); + Q_UNUSED(first); + Q_UNUSED(last); //TODO: Cantor removes rows from the model even when the variable was changed only. //We don't want this behaviour since this removes the columns from the datasource in the curve. //We need to fix/change this in Cantor. return; // Q_UNUSED(parent) // for(int i = first; i <= last; ++i) { // const QString name = m_variableModel->data(m_variableModel->index(first, 0)).toString(); // Column* column = child(name); // if(column) // column->remove(); // } } QList CantorWorksheet::getPlugins(){ return m_plugins; } KParts::ReadWritePart* CantorWorksheet::part() { return m_part; } QIcon CantorWorksheet::icon() const { if(m_session) return QIcon::fromTheme(m_session->backend()->icon()); return QIcon(); } QWidget* CantorWorksheet::view() const { if (!m_view) { m_view = new CantorWorksheetView(const_cast(this)); m_view->setBaseSize(1500, 1500); // connect(m_view, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString))); } return m_view; } //! Return a new context menu. /** * The caller takes ownership of the menu. */ QMenu* CantorWorksheet::createContextMenu() { QMenu* menu = AbstractPart::createContextMenu(); Q_ASSERT(menu); emit requestProjectContextMenu(menu); return menu; } QString CantorWorksheet::backendName() { return this->m_backendName; } //TODO bool CantorWorksheet::exportView() const { return false; } bool CantorWorksheet::printView() { m_part->action("file_print")->trigger(); return true; } bool CantorWorksheet::printPreview() const { m_part->action("file_print_preview")->trigger(); return true; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void CantorWorksheet::save(QXmlStreamWriter* writer) const{ writer->writeStartElement("cantorWorksheet"); writeBasicAttributes(writer); writeCommentElement(writer); //general writer->writeStartElement( "general" ); writer->writeAttribute( "backend_name", m_backendName); //TODO: save worksheet settings writer->writeEndElement(); //save the content of Cantor's worksheet QByteArray content = m_worksheetAccess->saveWorksheetToByteArray(); writer->writeStartElement("worksheet"); writer->writeAttribute("content", content.toBase64()); writer->writeEndElement(); //save columns(variables) for (auto* col : children(IncludeHidden)) col->save(writer); writer->writeEndElement(); // close "cantorWorksheet" section } //! Load from XML bool CantorWorksheet::load(XmlStreamReader* reader, bool preview) { if(!reader->isStartElement() || reader->name() != "cantorWorksheet"){ reader->raiseError(i18n("no Cantor worksheet element found")); return false; } if (!readBasicAttributes(reader)) return false; - if (preview) - return true; - QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; bool rc = false; while (!reader->atEnd()){ reader->readNext(); if (reader->isEndElement() && reader->name() == "cantorWorksheet") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment"){ if (!readCommentElement(reader)) return false; - } else if (reader->name() == "general"){ + } else if (!preview && reader->name() == "general"){ attribs = reader->attributes(); m_backendName = attribs.value("backend_name").toString().trimmed(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'backend_name'")); - } else if (reader->name() == "worksheet"){ + } else if (!preview && reader->name() == "worksheet"){ attribs = reader->attributes(); str = attribs.value("content").toString().trimmed(); if(str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'content'")); QByteArray content = QByteArray::fromBase64(str.toAscii()); rc = init(&content); if (!rc) { QString msg = i18n("This project has Cantor content but no Cantor plugins were found. Please check your installation. The project will be closed."); reader->raiseError(msg); return false; } - } else if(reader->name() == "column") { + } else if(!preview && reader->name() == "column") { Column* column = new Column(""); column->setUndoAware(false); if (!column->load(reader, preview)) { delete column; return false; } addChild(column); } else { // unknown element reader->raiseWarning(i18n("unknown element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } return true; } diff --git a/src/backend/datapicker/DatapickerCurve.cpp b/src/backend/datapicker/DatapickerCurve.cpp index 636cbf94f..c8093b675 100644 --- a/src/backend/datapicker/DatapickerCurve.cpp +++ b/src/backend/datapicker/DatapickerCurve.cpp @@ -1,641 +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, SIGNAL(triggered()), this, SLOT(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(const 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"))); 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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(const 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; - if (preview) - return true; - 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 (reader->name() == "general") { + } 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(reader->name() == "symbolProperties") { + } 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() == "errorBarProperties") { + } 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 fc87b5b11..f48aa18cb 100644 --- a/src/backend/datapicker/DatapickerImage.cpp +++ b/src/backend/datapicker/DatapickerImage.cpp @@ -1,827 +1,824 @@ /*************************************************************************** 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(0), d(new DatapickerImagePrivate(this)), 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_view) { m_view = new DatapickerImageView(const_cast(this)); connect(m_view, SIGNAL(statusInfo(QString)), this, SIGNAL(statusInfo(QString))); } return m_view; } 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(); DatapickerImageView* view = reinterpret_cast(m_view); WAIT_CURSOR; 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)) ) { DatapickerImageView* view = reinterpret_cast(m_view); view->print(&printer); } delete dlg; return ret; } bool DatapickerImage::printPreview() const { const DatapickerImageView* view = reinterpret_cast(m_view); QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, SIGNAL(paintRequested(QPrinter*)), view, SLOT(print(QPrinter*))); 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"))); 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"))); } 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"))); } 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"))); } 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"))); ; } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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; - if (preview) - return true; - 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 (reader->name() == "general") { + } 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 (reader->name() == "axisPoint") { + } 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 (reader->name() == "editorSettings") { + } 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(reader->name() == "symbolProperties") { + } 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 5bd216990..6d30b3570 100644 --- a/src/backend/datapicker/DatapickerPoint.cpp +++ b/src/backend/datapicker/DatapickerPoint.cpp @@ -1,565 +1,562 @@ /*************************************************************************** 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.3; /** * \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, SIGNAL(plusDeltaXPosChanged(QPointF)), plusDeltaXItem, SLOT(setPosition(QPointF))); ErrorBarItem* minusDeltaXItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaX); minusDeltaXItem->setPosition(minusDeltaXPos()); connect(this, SIGNAL(minusDeltaXPosChanged(QPointF)), minusDeltaXItem, SLOT(setPosition(QPointF))); m_errorBarItemList<setPosition(plusDeltaYPos()); connect(this, SIGNAL(plusDeltaYPosChanged(QPointF)), plusDeltaYItem, SLOT(setPosition(QPointF))); ErrorBarItem* minusDeltaYItem = new ErrorBarItem(this, ErrorBarItem::MinusDeltaY); minusDeltaYItem->setPosition(minusDeltaYPos()); connect(this, SIGNAL(minusDeltaYPosChanged(QPointF)), minusDeltaYItem, SLOT(setPosition(QPointF))); 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"))); } 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"))); setMinusDeltaXPos(QPointF(-qAbs(pos.x()), pos.y())); } else { exec(new DatapickerPointSetPlusDeltaXPosCmd(d, pos, i18n("%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"))); setPlusDeltaXPos(QPointF(qAbs(pos.x()), pos.y())); } else { exec(new DatapickerPointSetMinusDeltaXPosCmd(d, pos, i18n("%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"))); setMinusDeltaYPos(QPointF(pos.x(), qAbs(pos.y()))); } else { exec(new DatapickerPointSetPlusDeltaYPosCmd(d, pos, i18n("%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"))); setPlusDeltaYPos(QPointF(pos.x(), -qAbs(pos.y()))); } else { exec(new DatapickerPointSetMinusDeltaYPosCmd(d, pos, i18n("%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() { foreach (ErrorBarItem* 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; - if (preview) - return true; - 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 (reader->name() == "geometry") { + } 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 (reader->name() == "errorBar") { + } 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/matrix/Matrix.cpp b/src/backend/matrix/Matrix.cpp index de7101df3..62daeb80f 100644 --- a/src/backend/matrix/Matrix.cpp +++ b/src/backend/matrix/Matrix.cpp @@ -1,1275 +1,1272 @@ /*************************************************************************** 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 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) { //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) { 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_view) { MatrixView* view = new MatrixView(const_cast(this)); m_model = view->model(); m_view = view; } return m_view; } 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 (reinterpret_cast(m_view)->selectedColumnCount() == 0) { dlg->setExportSelection(false); } bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { const QString path = dlg->path(); const MatrixView* view = reinterpret_cast(m_view); 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(); view->exportToLaTeX(path, verticalHeader, horizontalHeader, latexHeader, gridLines, entire, captions); } else if (dlg->format() == ExportSpreadsheetDialog::FITS) { const int exportTo = dlg->exportToFits(); view->exportToFits(path, exportTo ); } else { const QString separator = dlg->separator(); 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)) ) { const MatrixView* view = reinterpret_cast(m_view); view->print(&printer); } delete dlg; return ret; } bool Matrix::printPreview() const { const MatrixView* view = reinterpret_cast(m_view); QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, SIGNAL(paintRequested(QPrinter*)), view, SLOT(print(QPrinter*))); 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //TODO: make this undoable? void Matrix::setHeaderFormat(Matrix::HeaderFormat format) { d->headerFormat = format; m_model->updateHeader(); if (m_view) (reinterpret_cast(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; //! 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), d->numericFormat, d->precision); } 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); 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) reinterpret_cast(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 = reinterpret_cast(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 = reinterpret_cast(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() { reinterpret_cast(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; - if (preview) - return true; - 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(reader->name() == "formula") { + } else if(!preview && reader->name() == "formula") { d->formula = reader->text().toString().trimmed(); - } else if (reader->name() == "format") { + } 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 (reader->name() == "dimension") { + } 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 (reader->name() == "row_heights") { + } 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 (reader->name() == "column_widths") { + } 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 (reader->name() == "column") { + } 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); 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); 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); } 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); } 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); } 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); } break; } return columnOffset; } void Matrix::finalizeImport(int columnOffset, int startColumn, int endColumn, const QString& dateTimeFormat, AbstractFileFilter::ImportMode importMode) { Q_UNUSED(columnOffset); Q_UNUSED(startColumn); Q_UNUSED(endColumn); Q_UNUSED(dateTimeFormat); Q_UNUSED(importMode); setSuppressDataChangedSignal(false); setChanged(); setUndoAware(true); } diff --git a/src/backend/note/Note.cpp b/src/backend/note/Note.cpp index 070c62652..fc1c2f496 100644 --- a/src/backend/note/Note.cpp +++ b/src/backend/note/Note.cpp @@ -1,184 +1,181 @@ /*************************************************************************** File : Notes.cpp Project : LabPlot Description : Notes Widget for taking notes -------------------------------------------------------------------- Copyright : (C) 2009-2015 Garvit Khatri (garvitdelhi@gmail.com) Copyright : (C) 2016 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 "Note.h" #include "commonfrontend/note/NoteView.h" #include "backend/lib/XmlStreamReader.h" #include "backend/lib/macros.h" #include #include #include #include #include #include #include Note::Note(const QString& name) : AbstractPart(name) { KConfig config; KConfigGroup group = config.group("Notes"); m_backgroundColor = group.readEntry("BackgroundColor", QColor(Qt::yellow)); m_textColor = group.readEntry("TextColor", QColor(Qt::black)); m_textFont = group.readEntry("TextFont", QFont()); } QIcon Note::icon() const { return QIcon::fromTheme("document-new"); } bool Note::printView() { QPrinter printer; QPrintDialog* dlg = new QPrintDialog(&printer, m_view); dlg->setWindowTitle(i18n("Print Worksheet")); bool ret; if ( (ret = (dlg->exec() == QDialog::Accepted)) ) { NoteView* view = reinterpret_cast(m_view); view->print(&printer); } delete dlg; return ret; } bool Note::printPreview() const { const NoteView* view = reinterpret_cast(m_view); QPrintPreviewDialog* dlg = new QPrintPreviewDialog(m_view); connect(dlg, SIGNAL(paintRequested(QPrinter*)), view, SLOT(print(QPrinter*))); return dlg->exec(); } bool Note::exportView() const { return false; } void Note::setNote(const QString& note) { m_note = note; } const QString& Note::note() const { return m_note; } void Note::setBackgroundColor(const QColor& color) { m_backgroundColor = color; emit (backgroundColorChanged(color)); } const QColor& Note::backgroundColor() const { return m_backgroundColor; } void Note::setTextColor(const QColor& color) { m_textColor = color; emit (textColorChanged(color)); } const QColor& Note::textColor() const{ return m_textColor; } void Note::setTextFont(const QFont& font) { m_textFont = font; emit (textFontChanged(font)); } const QFont& Note::textFont() const { return m_textFont; } QWidget* Note::view() const { if (!m_view) m_view = new NoteView(const_cast(this)); return m_view; } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## //! Save as XML void Note::save(QXmlStreamWriter* writer) const { writer->writeStartElement("note"); writeBasicAttributes(writer); writeCommentElement(writer); writer->writeStartElement("background"); WRITE_QCOLOR(m_backgroundColor); writer->writeEndElement(); writer->writeStartElement("text"); WRITE_QCOLOR(m_textColor); WRITE_QFONT(m_textFont); writer->writeAttribute("text", m_note); writer->writeEndElement(); writer->writeEndElement(); // close "note" section } bool Note::load(XmlStreamReader* reader, bool preview) { if (!reader->isStartElement() || reader->name() != "note") { reader->raiseError(i18n("no note element found")); return false; } if (!readBasicAttributes(reader)) return false; - if (preview) - return true; - 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() == "note") break; if (!reader->isStartElement()) continue; if (reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "background") { + } else if (!preview && reader->name() == "background") { attribs = reader->attributes(); READ_QCOLOR(m_backgroundColor); - } else if (reader->name() == "text") { + } else if (!preview && reader->name() == "text") { attribs = reader->attributes(); READ_QCOLOR(m_textColor); READ_QFONT(m_textFont); m_note = attribs.value("text").toString(); } } return true; } diff --git a/src/backend/worksheet/TextLabel.cpp b/src/backend/worksheet/TextLabel.cpp index d2fde4394..cb550cf5f 100644 --- a/src/backend/worksheet/TextLabel.cpp +++ b/src/backend/worksheet/TextLabel.cpp @@ -1,878 +1,875 @@ /*************************************************************************** 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-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 "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) { init(); } TextLabel::TextLabel(const QString &name, TextLabelPrivate *dd, Type type):WorksheetElement(name), d_ptr(dd), m_type(type) { 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, SIGNAL(finished()), this, SLOT(updateTeXImage())); this->initActions(); } void TextLabel::initActions() { visibilityAction = new QAction(i18n("visible"), this); visibilityAction->setCheckable(true); connect(visibilityAction, SIGNAL(triggered()), this, SLOT(visibilityChanged())); } 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" visibilityAction->setChecked(isVisible()); menu->insertAction(firstAction, visibilityAction); 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, float, 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } /*! sets the position without undo/redo-stuff */ void TextLabel::setPosition(const 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, float, rotationAngle, recalcShapeAndBoundingRect); void TextLabel::setRotationAngle(float angle) { Q_D(TextLabel); if (angle != d->rotationAngle) exec(new TextLabelSetRotationAngleCmd(d, angle, i18n("%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"))); } 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"))); } 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"))); } 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) { QImage todraw = teXImage.scaled(boundingRect().width(), boundingRect().height(), Qt::IgnoreAspectRatio, Qt::SmoothTransformation); painter->drawImage(boundingRect(), todraw); } } 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(q->hoveredPen); painter->setOpacity(q->hoveredOpacity); painter->drawPath(labelShape); } if (isSelected() && !m_printing) { painter->setPen(q->selectedPen); painter->setOpacity(q->selectedOpacity); 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(const 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; q->hovered(); update(); } } void TextLabelPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; 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; - if (preview) - return true; - Q_D(TextLabel); QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs; QString str; QRectF rect; bool teXImageFound = false; while (!reader->atEnd()) { reader->readNext(); if (reader->isEndElement() && reader->name() == "textLabel") break; if (!reader->isStartElement()) continue; - if (reader->name() == "comment") { + if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "geometry") { + } 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 (reader->name() == "text") { + } else if (!preview && reader->name() == "text") { d->textWrapper.text = reader->readElementText(); - } else if (reader->name() == "format") { + } 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 (reader->name() == "teXImage") { + } 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; } } //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) { //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/plots/PlotArea.cpp b/src/backend/worksheet/plots/PlotArea.cpp index c962f2c66..62045f480 100644 --- a/src/backend/worksheet/plots/PlotArea.cpp +++ b/src/backend/worksheet/plots/PlotArea.cpp @@ -1,590 +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"))); } 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"))); } /*! * 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } //##################################################################### //################### 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; - if (preview) - return true; - 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 (reader->name() == "comment") { + if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "background") { + } 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() == "border") { + } 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 1f5049181..1c4835559 100644 --- a/src/backend/worksheet/plots/cartesian/Axis.cpp +++ b/src/backend/worksheet/plots/cartesian/Axis.cpp @@ -1,2341 +1,2338 @@ /*************************************************************************** File : Axis.cpp Project : LabPlot Description : Axis for cartesian coordinate systems. -------------------------------------------------------------------- Copyright : (C) 2011-2015 Alexander Semke (alexander.semke@web.de) Copyright : (C) 2013 Stefan Gerlach (stefan.gerlach@uni-konstanz.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/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 #include /** * \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 { 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) { 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, CartesianPlot* plot, const AxisOrientation& orientation) : WorksheetElement(name), d_ptr(new AxisPrivate(this, plot)) { d_ptr->orientation = orientation; init(); } Axis::Axis(const QString& name, const AxisOrientation& orientation, AxisPrivate* dd) : WorksheetElement(name), d_ptr(dd) { d_ptr->orientation = orientation; init(); } 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, SIGNAL(changed()), this, SLOT(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); this->initActions(); this->initMenus(); } /*! * 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, SIGNAL(triggered()), this, SLOT(visibilityChanged())); //Orientation orientationActionGroup = new QActionGroup(this); orientationActionGroup->setExclusive(true); connect(orientationActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(orientationChanged(QAction*))); 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, SIGNAL(triggered(QAction*)), this, SLOT(lineStyleChanged(QAction*))); lineColorActionGroup = new QActionGroup(this); lineColorActionGroup->setExclusive(true); connect(lineColorActionGroup, SIGNAL(triggered(QAction*)), this, SLOT(lineColorChanged(QAction*))); //Ticks //TODO } void Axis::initMenus() { //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 ); } QMenu* Axis::createContextMenu() { 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 (orientationMenu) delete orientationMenu; if (lineMenu) 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 WorkhseetElement 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(FLT_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, float, offset, offset) BASIC_SHARED_D_READER_IMPL(Axis, float, start, start) BASIC_SHARED_D_READER_IMPL(Axis, float, 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, float, titleOffsetX, titleOffsetX) BASIC_SHARED_D_READER_IMPL(Axis, float, 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, float, 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, float, 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"))); 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"))); } 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"))); } 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"))); } 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"))); } STD_SETTER_CMD_IMPL_F(Axis, SetOffset, float, offset, retransform); void Axis::setOffset(float offset, bool undo) { Q_D(Axis); if (offset != d->offset) { if (undo) { exec(new AxisSetOffsetCmd(d, offset, i18n("%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, float, start, retransform); void Axis::setStart(float start) { Q_D(Axis); if (start != d->start) exec(new AxisSetStartCmd(d, start, i18n("%1: set axis start"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetEnd, float, end, retransform); void Axis::setEnd(float end) { Q_D(Axis); if (end != d->end) exec(new AxisSetEndCmd(d, end, i18n("%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"))); } 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"))); } //Title STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetX, float, titleOffsetX, retransform); void Axis::setTitleOffsetX(float offset) { Q_D(Axis); if (offset != d->titleOffsetX) exec(new AxisSetTitleOffsetXCmd(d, offset, i18n("%1: set title offset"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetTitleOffsetY, float, titleOffsetY, retransform); void Axis::setTitleOffsetY(float offset) { Q_D(Axis); if (offset != d->titleOffsetY) exec(new AxisSetTitleOffsetYCmd(d, offset, i18n("%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"))); } 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"))); } 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"))); } 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"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetArrowSize, float, arrowSize, retransformArrow); void Axis::setArrowSize(float arrowSize) { Q_D(Axis); if (arrowSize != d->arrowSize) exec(new AxisSetArrowSizeCmd(d, arrowSize, i18n("%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"))); } 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"))); } 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"))); } 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"))); } 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"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransformTicks())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(majorTicksColumnAboutToBeRemoved(const AbstractAspect*))); //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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } 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"))); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(retransformTicks())); connect(column->parentAspect(), SIGNAL(aspectAboutToBeRemoved(const AbstractAspect*)), this, SLOT(minorTicksColumnAboutToBeRemoved(const AbstractAspect*))); //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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } STD_SETTER_CMD_IMPL_F_S(Axis, SetLabelsOffset, float, labelsOffset, retransformTickLabelPositions); void Axis::setLabelsOffset(float offset) { Q_D(Axis); if (offset != d->labelsOffset) exec(new AxisSetLabelsOffsetCmd(d, offset, i18n("%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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } //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"))); } 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"))); } //############################################################################## //#################################### 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::orientationChanged(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::visibilityChanged() { Q_D(const Axis); this->setVisible(!d->isVisible()); } //##################################################################### //################### Private implementation ########################## //##################################################################### AxisPrivate::AxisPrivate(Axis* owner, CartesianPlot* plot) : majorTicksColumn(0), minorTicksColumn(0), gridItem(new AxisGrid(this)), q(owner), suppressRetransform(false), m_plot(plot), m_cSystem(dynamic_cast(plot->coordinateSystem())), 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) 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 = m_plot->yMax(); else if (position == Axis::AxisBottom) offset = m_plot->yMin(); else if (position == Axis::AxisCentered) offset = m_plot->yMin() + (m_plot->yMax()-m_plot->yMin())/2; startPoint.setX(start); startPoint.setY(offset); endPoint.setX(end); endPoint.setY(offset); } else { // vertical if (position == Axis::AxisLeft) offset = m_plot->xMin(); else if (position == Axis::AxisRight) offset = m_plot->xMax(); else if (position == Axis::AxisCentered) offset = m_plot->xMin() + (m_plot->xMax()-m_plot->xMin())/2; startPoint.setX(offset); startPoint.setY(start); endPoint.setY(end); endPoint.setX(offset); } lines.append(QLineF(startPoint, endPoint)); lines = m_cSystem->mapLogicalToScene(lines, AbstractCoordinateSystem::MarkGaps); foreach (const QLineF& 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(const QPointF& startPoint, int direction) { static const float cos_phi = cos(3.14159/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 = m_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; int xDirection = m_cSystem->xDirection(); int yDirection = m_cSystem->yDirection(); float middleX = m_plot->xMin() + (m_plot->xMax() - m_plot->xMin())/2; float middleY = m_plot->yMin() + (m_plot->yMax() - m_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); tickLabelStrings.clear(); QString str; if (labelsFormat == Axis::FormatDecimal) { QString nullStr = QString::number(0, 'f', labelsPrecision); foreach (float 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); foreach (float value, tickLabelValues) { str = QString::number(value, 'e', labelsPrecision); if (str == "-" + nullStr) str = nullStr; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers10) { foreach (float value, tickLabelValues) { str = "10" + QString::number(log10(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowers2) { foreach (float value, tickLabelValues) { str = "2" + QString::number(log2(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatPowersE) { foreach (float value, tickLabelValues) { str = "e" + QString::number(log(value), 'f', labelsPrecision) + ""; str = labelsPrefix + str + labelsSuffix; tickLabelStrings << str; } } else if (labelsFormat == Axis::FormatMultipliesPi) { foreach (float 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( round(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 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( round(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 return precision; } } } //no duplicates found, reduce further, and check again if (precision == 0) return 0; else return lowerLabelsPrecision(precision - 1); } double AxisPrivate::round(double value, int precision) { //DEBUG("AxisPrivate::round() value =" << value << "precision =" << precision); double result = roundf(value * pow(10, precision)) / pow(10, precision); //DEBUG(" result =" << result); return result; } /*! 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(); QString label; QPointF pos; float middleX = m_plot->xMin() + (m_plot->xMax() - m_plot->xMin())/2; float middleY = m_plot->yMin() + (m_plot->yMax() - m_plot->yMin())/2; int xDirection = m_cSystem->xDirection(); int yDirection = m_cSystem->yDirection(); QPointF startPoint, endPoint, anchorPoint; QTextDocument td; td.setDefaultFont(labelsFont); for ( int i=0; i logicalMajorTickPoints = m_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((float)logicalMajorTickPoints.at(0).x(), m_plot->xMin()); skipUpperTick = qFuzzyCompare((float)logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).x(), m_plot->xMax()); } else { skipLowestTick = qFuzzyCompare((float)logicalMajorTickPoints.at(0).y(), m_plot->yMin()); skipUpperTick = qFuzzyCompare((float)logicalMajorTickPoints.at(logicalMajorTickPoints.size()-1).y(), m_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 float yMin = m_plot->yMin(); float yMax = m_plot->yMax(); for (int i=start; ixMin(); float xMax = m_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; imapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); foreach (const QLineF& 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 = m_cSystem->mapSceneToLogical(minorTickPoints, AbstractCoordinateSystem::SuppressPageClipping); QVector lines; if (orientation == Axis::AxisHorizontal) { //horizontal axis float yMin = m_plot->yMin(); float yMax = m_plot->yMax(); for (int i=0; ixMin(); float xMax = m_plot->xMax(); for (int i=0; imapLogicalToScene(lines, AbstractCoordinateSystem::SuppressPageClipping); foreach (const QLineF& 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 (m_plot) m_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; iisVisible() && !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(); float offsetX = titleOffsetX - labelsOffset; //the distance to the axis line float 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 (m_plot) m_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) { // DEBUG("AxisPrivate::paint()"); 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(q->hoveredPen); painter->setOpacity(q->hoveredOpacity); painter->drawPath(axisShape); } if (isSelected() && !m_printing) { painter->setPen(q->selectedPen); painter->setOpacity(q->selectedOpacity); painter->drawPath(axisShape); } // DEBUG("AxisPrivate::paint() DONE"); } void AxisPrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { q->createContextMenu()->exec(event->screenPos()); } void AxisPrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { if (!isSelected()) { m_hovered = true; q->hovered(); update(axisShape.boundingRect()); } } void AxisPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; 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; - if (preview) - return true; - 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() == "axis") break; if (!reader->isStartElement()) continue; - if (reader->name() == "comment") { + if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "general") { + } 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 (reader->name() == "line") { + } 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 (reader->name() == "majorTicks") { + } 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 (reader->name() == "minorTicks") { + } 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 (reader->name() == "labels") { + } 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 (reader->name() == "majorGrid") { + } 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 (reader->name() == "minorGrid") { + } 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())); this->setMajorTicksType((Axis::TicksType)group.readEntry("MajorTicksType",(int)this->majorTicksType())); //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())); this->setMinorTicksType((Axis::TicksType)group.readEntry("MinorTicksType",(int)this->minorTicksType())); 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 c6de69be8..cb103c66e 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,2910 +1,2913 @@ /*************************************************************************** File : CartesianPlot.cpp Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2016 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/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 #include #include // DBL_MAX /** * \class CartesianPlot * \brief A xy-plot. * * */ CartesianPlot::CartesianPlot(const QString &name):AbstractPlot(name, new CartesianPlotPrivate(this)), m_legend(0), 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(0), 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 1 corresponds to the exact match - min/max values of the curves correspond to the start/end values of the ranges. d->autoScaleOffsetFactor = 0.05; //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 m_plotArea = new PlotArea(name() + " plot area"); addChildFast(m_plotArea); //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", this, 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", this, 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", this, 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", this, 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", this, 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", this, 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", this, 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", this, 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", this, 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", this, 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; //Plot title m_title = new TextLabel(this->name(), TextLabel::PlotTitle); addChild(m_title); m_title->setHidden(true); m_title->setParentGraphicsItem(m_plotArea->graphicsItem()); //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); 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(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(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"); } 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(); } //############################################################################## //################################ 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, float, xMin, xMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, 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, float, yMin, yMin) BASIC_SHARED_D_READER_IMPL(CartesianPlot, float, 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) /*! return the actual bounding rectangular of the plot (plot's rectangular minus padding) in plot's coordinates */ //TODO: return here a private variable only, update this variable on rect and padding changes. QRectF CartesianPlot::plotRect() { Q_D(const CartesianPlot); QRectF rect = d->mapRectFromScene(d->rect); rect.setX(rect.x() + d->horizontalPadding); rect.setY(rect.y() + d->verticalPadding); rect.setWidth(rect.width() - d->horizontalPadding); rect.setHeight(rect.height()-d->verticalPadding); return rect; } 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())); }; virtual void redo() { QRectF tmp = m_private->rect; 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); }; virtual void undo() { 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"))); } 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"))); } 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"))); } class CartesianPlotSetAutoScaleXCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleXCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change x-range auto scaling", m_private->name())); }; virtual void redo() { 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); }; virtual void undo() { 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; float m_minOld; float 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, float, xMin, retransformScales) void CartesianPlot::setXMin(float xMin) { Q_D(CartesianPlot); if (xMin != d->xMin) exec(new CartesianPlotSetXMinCmd(d, xMin, i18n("%1: set min x"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, float, xMax, retransformScales) void CartesianPlot::setXMax(float xMax) { Q_D(CartesianPlot); if (xMax != d->xMax) exec(new CartesianPlotSetXMaxCmd(d, xMax, i18n("%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"))); } 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"))); } 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"))); } class CartesianPlotSetAutoScaleYCmd : public QUndoCommand { public: CartesianPlotSetAutoScaleYCmd(CartesianPlotPrivate* private_obj, bool autoScale) : m_private(private_obj), m_autoScale(autoScale), m_minOld(0.0), m_maxOld(0.0) { setText(i18n("%1: change y-range auto scaling", m_private->name())); }; virtual void redo() { 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); }; virtual void undo() { 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; float m_minOld; float 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, float, yMin, retransformScales) void CartesianPlot::setYMin(float yMin) { Q_D(CartesianPlot); if (yMin != d->yMin) exec(new CartesianPlotSetYMinCmd(d, yMin, i18n("%1: set min y"))); } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, float, yMax, retransformScales) void CartesianPlot::setYMax(float yMax) { Q_D(CartesianPlot); if (yMax != d->yMax) exec(new CartesianPlotSetYMaxCmd(d, yMax, i18n("%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"))); } 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"))); } 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"))); } 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"))); loadTheme(theme); endMacro(); } else exec(new CartesianPlotSetThemeCmd(d, theme, i18n("%1: disable theming"))); } } //################################################################ //########################## Slots ############################### //################################################################ void CartesianPlot::addHorizontalAxis() { Axis* axis = new Axis("x-axis", this, 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", this, 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(XYCurve::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(XYCurve::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(XYCurve::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(XYCurve::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(XYCurve::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() { 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(XYCurve::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); 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(XYCurve::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); } 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::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 a theme was selected, apply the theme settings for newly added children, too if (!d->theme.isEmpty() && !isLoading()) { const WorksheetElement* el = dynamic_cast(child); if (el) { KConfig config(ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig); const_cast(el)->loadThemeConfig(config); } } } 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 + qDebug()<<"HERE"; for (auto curve : children()) - curve->retransform(); + QtConcurrent::run(curve, &XYCurve::retransform); +// curve->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 curve : children()) 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::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()); Q_ASSERT(curve); 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()); Q_ASSERT(curve); 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()); Q_ASSERT(curve); 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()); Q_ASSERT(curve); 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 { float 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 { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } d->retransformScales(); } } void 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; } 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 { float 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 { float offset = (d->yMax - d->yMin)*d->autoScaleOffsetFactor; d->yMin -= offset; d->yMax += offset; } } d->retransformScales(); } } void CartesianPlot::zoomIn() { DEBUG("CartesianPlot::zoomIn()"); Q_D(CartesianPlot); float oldRange = (d->xMax - d->xMin); float 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); float oldRange = (d->xMax-d->xMin); float 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); float oldRange = (d->xMax-d->xMin); float 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); float oldRange = (d->xMax-d->xMin); float 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); float oldRange = (d->yMax-d->yMin); float 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); float oldRange = (d->yMax-d->yMin); float 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); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; d->retransformScales(); } void CartesianPlot::shiftRightX() { Q_D(CartesianPlot); float offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; d->retransformScales(); } void CartesianPlot::shiftUpY() { Q_D(CartesianPlot); float offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; d->retransformScales(); } void CartesianPlot::shiftDownY() { Q_D(CartesianPlot); float 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), // m_printing(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); 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()"); 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 float deltaXMin = 0; float deltaXMax = 0; float deltaYMin = 0; float 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(); } 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) { // Interval interval (logicalStart-0.01, logicalEnd+0.01); //TODO: move this to CartesianScale Interval interval (-1E15, 1E15); // Interval interval (logicalStart, logicalEnd); if (type == CartesianPlot::ScaleLinear) return CartesianScale::createLinearScale(interval, sceneStart, sceneEnd, logicalStart, logicalEnd); else { float 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; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and forward the changes to the frontend QRectF newRect; float w = rect.width(); float 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); } 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(q->plotRect().height()/2); } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { m_selectionStart.setX(-q->plotRect().width()/2); m_selectionStart.setY(event->pos().y()); } m_selectionEnd = m_selectionStart; m_selectionBandIsShown = true; } else QGraphicsItem::mousePressEvent(event); } void CartesianPlotPrivate::mouseMoveEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) 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(-q->plotRect().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(q->plotRect().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(); } //TODO: implement the navigation in plot on mouse move events, //calculate the position changes and call shift*()-functions } void CartesianPlotPrivate::mouseReleaseEvent(QGraphicsSceneMouseEvent* event) { if (mouseMode == CartesianPlot::SelectionMode) { const QPointF& itemPos = pos();//item's center point in parent's coordinates; float x = itemPos.x(); float y = itemPos.y(); //calculate the new rect and set it QRectF newRect; float w = rect.width(); float 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 (q->plotRect().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) { // DEBUG("CartesianPlotPrivate::paint()"); 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); // DEBUG("CartesianPlotPrivate::paint() DONE"); } //############################################################################## //################## 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; 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") { m_title = new TextLabel(""); if (m_title->load(reader, preview)) addChildFast(m_title); else { delete m_title; m_title=0; return false; } } else if (reader->name() == "plotArea") m_plotArea->load(reader, preview); else if (reader->name() == "axis") { Axis* axis = new Axis("", this); 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 (m_title) { m_title->setHidden(true); m_title->graphicsItem()->setParentItem(m_plotArea->graphicsItem()); } //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); } 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); beginMacro( i18n("%1: Load theme %2.", AbstractAspect::name(), str) ); 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()); endMacro(); } 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) { KConfigGroup group = config.group("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())); //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.25,0.45,0.65}; //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 f2892cd01..3542e29d9 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlotLegend.cpp @@ -1,1155 +1,1152 @@ /*************************************************************************** File : CartesianPlotLegend.cpp Project : LabPlot Description : Legend for the cartesian plot -------------------------------------------------------------------- Copyright : (C) 2013-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 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 #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); d->title->setText(this->name()); addChild(d->title); d->title->setHidden(true); d->title->setParentGraphicsItem(graphicsItem()); d->title->graphicsItem()->setFlag(QGraphicsItem::ItemIsMovable, false); connect(d->title, SIGNAL(changed()), this, SLOT(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.2, Worksheet::Centimeter)); d->layoutBottomMargin = group.readEntry("LayoutBottomMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutLeftMargin = group.readEntry("LayoutLeftMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutRightMargin = group.readEntry("LayoutRightMargin", Worksheet::convertToSceneUnits(0.2, Worksheet::Centimeter)); d->layoutVerticalSpacing = group.readEntry("LayoutVerticalSpacing", Worksheet::convertToSceneUnits(0.1, Worksheet::Centimeter)); d->layoutHorizontalSpacing = group.readEntry("LayoutHorizontalSpacing", Worksheet::convertToSceneUnits(0.1, 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, SIGNAL(triggered()), this, SLOT(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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //############################################################################## //################################# SLOTS #################################### //############################################################################## //############################################################################## //###### SLOTs for changes triggered via QActions in the context menu ######## //############################################################################## void CartesianPlotLegend::visibilityChanged() { 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(); QVector children = q->m_plot->children(); foreach(XYCurve* curve, 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->plotRect(); 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->yErrorType() != XYCurve::NoError) ) { 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 painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/2); break; case XYCurve::ErrorBarsWithEnds: //horiz. line painter->drawLine(lineSymbolWidth/2-errorBarsSize/2, h/2, lineSymbolWidth/2+errorBarsSize/2, h/2); //vert. line painter->drawLine(lineSymbolWidth/2, h/2-errorBarsSize/2, lineSymbolWidth/2, h/2+errorBarsSize/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); //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); 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(q->hoveredPen); painter->setOpacity(q->hoveredOpacity); painter->drawPath(shape()); } if (isSelected() && !m_printing) { painter->setPen(q->selectedPen); painter->setOpacity(q->selectedOpacity); 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; q->hovered(); update(); } } void CartesianPlotLegendPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; 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 (!reader->isStartElement() || reader->name() != "cartesianPlotLegend") { reader->raiseError(i18n("no cartesian plot legend element found")); return false; } if (!readBasicAttributes(reader)) return false; - if (preview) - return true; - 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 (reader->name() == "comment") { + if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "general") { + } 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 (reader->name() == "geometry") { + } 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 (reader->name() == "background") { + } 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() == "border") { + } 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 (reader->name() == "layout") { + } 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 c55ead6b7..1fa4dd266 100644 --- a/src/backend/worksheet/plots/cartesian/CustomPoint.cpp +++ b/src/backend/worksheet/plots/cartesian/CustomPoint.cpp @@ -1,498 +1,495 @@ /*************************************************************************** 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, SIGNAL(triggered()), this, SLOT(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"))); } //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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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(q->hoveredPen); painter->setOpacity(q->hoveredOpacity); painter->drawPath(pointShape); } if (isSelected() && !m_printing) { painter->setPen(q->selectedPen); painter->setOpacity(q->selectedOpacity); 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()); emit 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; q->hovered(); update(); } } void CustomPointPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { if (m_hovered) { m_hovered = false; 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; - if (preview) - return true; - 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 (reader->name() == "comment") { + if (!preview && reader->name() == "comment") { if (!readCommentElement(reader)) return false; - } else if (reader->name() == "geometry") { + } 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 (reader->name() == "symbol") { + } 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 38695aa35..9fa6930b3 100644 --- a/src/backend/worksheet/plots/cartesian/Histogram.cpp +++ b/src/backend/worksheet/plots/cartesian/Histogram.cpp @@ -1,1611 +1,1608 @@ /*************************************************************************** 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 #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, SIGNAL(triggered()), this, SLOT(visibilityChanged())); } 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"))); } 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"))); } 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"))); emit sourceDataChangedSinceLastPlot(); //emit xHistogramDataChanged() in order to notify the plot about the changes emit xHistogramDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SIGNAL(xHistogramDataChanged())); connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //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(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"))); } //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"))); } 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"))); 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(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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } //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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } 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"))); } //############################################################################## //################################# 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::visibilityChanged() { 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++) { w=fm.width(valuesStrings.at(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. 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(); foreach(const QPolygonF& 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) { // qDebug()<<"HistogramPrivate::paint, " + q->name(); Q_UNUSED(option); Q_UNUSED(widget); if (!isVisible()) return; // QTime timer; // timer.start(); painter->setPen(Qt::NoPen); painter->setBrush(Qt::NoBrush); painter->setRenderHint(QPainter::SmoothPixmapTransform, true); // TODO: draw directly draw(painter); // or use pixmap for double buffering // painter->drawPixmap(boundingRectangle.topLeft(), m_pixmap); // qDebug() << "Paint the pixmap: " << timer.elapsed() << "ms"; if (m_hovered && !isSelected() && !m_printing) { // timer.start(); if (m_hoverEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(q->hoveredPen.color()); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_hoverEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_hoverEffectImageIsDirty = false; } painter->setOpacity(q->hoveredOpacity*2); painter->drawImage(boundingRectangle.topLeft(), m_hoverEffectImage, m_pixmap.rect()); // qDebug() << "Paint hovering effect: " << timer.elapsed() << "ms"; return; } if (isSelected() && !m_printing) { // timer.start(); if (m_selectionEffectImageIsDirty) { QPixmap pix = m_pixmap; pix.fill(q->selectedPen.color()); pix.setAlphaChannel(m_pixmap.alphaChannel()); m_selectionEffectImage = ImageTools::blurred(pix.toImage(), m_pixmap.rect(), 5); m_selectionEffectImageIsDirty = false; } painter->setOpacity(q->selectedOpacity*2); painter->drawImage(boundingRectangle.topLeft(), m_selectionEffectImage, m_pixmap.rect()); // qDebug() << "Paint selection effect: " << timer.elapsed() << "ms"; 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) { foreach(const QPolygonF& 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; q->hovered(); update(); } } void HistogramPrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { const CartesianPlot* plot = dynamic_cast(q->parentAspect()); if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { m_hovered = false; 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; - if (preview) - return true; - 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 (reader->name() == "general") { + } 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 (reader->name() == "typeChanged") { + } 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 (reader->name() == "values") { + } 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 (reader->name() == "filling") { + } 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/XYDataReductionCurve.cpp b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp index 2e11817c0..5497da21b 100644 --- a/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDataReductionCurve.cpp @@ -1,476 +1,477 @@ /*************************************************************************** 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 // isnan #include #include #include #include XYDataReductionCurve::XYDataReductionCurve(const QString& name) : XYCurve(name, new XYDataReductionCurvePrivate(this)) { init(); } XYDataReductionCurve::XYDataReductionCurve(const QString& name, XYDataReductionCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYDataReductionCurve::~XYDataReductionCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDataReductionCurve::init() { Q_D(XYDataReductionCurve); //TODO: read from the saved settings for XYDataReductionCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYDataReductionCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYDataReductionCurve::xDataColumnPath() const { Q_D(const XYDataReductionCurve); return d->xDataColumnPath; } const QString& XYDataReductionCurve::yDataColumnPath() const { Q_D(const XYDataReductionCurve); return d->yDataColumnPath; } 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_S(XYDataReductionCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYDataReductionCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYDataReductionCurve); if (column != d->xDataColumn) { exec(new XYDataReductionCurveSetXDataColumnCmd(d, column, i18n("%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(XYDataReductionCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYDataReductionCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYDataReductionCurve); if (column != d->yDataColumn) { exec(new XYDataReductionCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDataReductionCurvePrivate::XYDataReductionCurvePrivate(XYDataReductionCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYDataReductionCurvePrivate::~XYDataReductionCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 == XYCurve::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 unsigned int n = 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(npoints); yVector->resize(npoints); for (unsigned int i = 0; i < 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 xy-curve information XYCurve::save(writer); //write xy-dataReduction-curve specific information // dataReduction data writer->writeStartElement("dataReductionData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xyDataReductionCurve") { reader->raiseError(i18n("no xy dataReduction 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() == "xyDataReductionCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "dataReductionData") { + } else if (!preview && reader->name() == "dataReductionData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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 (reader->name() == "dataReductionResult") { + } 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, int); 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 881a9c276..de4a8918f 100644 --- a/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYDifferentiationCurve.cpp @@ -1,423 +1,424 @@ /*************************************************************************** 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" #include // isnan #include // DBL_MIN extern "C" { #include } #include #include #include #include XYDifferentiationCurve::XYDifferentiationCurve(const QString& name) : XYCurve(name, new XYDifferentiationCurvePrivate(this)) { init(); } XYDifferentiationCurve::XYDifferentiationCurve(const QString& name, XYDifferentiationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYDifferentiationCurve::~XYDifferentiationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYDifferentiationCurve::init() { Q_D(XYDifferentiationCurve); //TODO: read from the saved settings for XYDifferentiationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYDifferentiationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYDifferentiationCurve::xDataColumnPath() const { Q_D(const XYDifferentiationCurve); return d->xDataColumnPath; } const QString& XYDifferentiationCurve::yDataColumnPath() const { Q_D(const XYDifferentiationCurve); return d->yDataColumnPath; } 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_S(XYDifferentiationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYDifferentiationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYDifferentiationCurve); if (column != d->xDataColumn) { exec(new XYDifferentiationCurveSetXDataColumnCmd(d, column, i18n("%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(XYDifferentiationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYDifferentiationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYDifferentiationCurve); if (column != d->yDataColumn) { exec(new XYDifferentiationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYDifferentiationCurvePrivate::XYDifferentiationCurvePrivate(XYDifferentiationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), 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 == XYCurve::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 unsigned int n = 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(n); yVector->resize(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 xy-curve information XYCurve::save(writer); //write xy-differentiation-curve specific information // differentiation data writer->writeStartElement("differentiationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xyDifferentiationCurve") { reader->raiseError(i18n("no xy differentiation 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() == "xyDifferentiationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "differentiationData") { + } else if (!preview && reader->name() == "differentiationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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 (reader->name() == "differentiationResult") { + } 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/XYFitCurve.cpp b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp index a667a2f0d..89e3ae1ae 100644 --- a/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFitCurve.cpp @@ -1,2147 +1,2148 @@ /*************************************************************************** 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-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 * * * ***************************************************************************/ /*! \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 #include XYFitCurve::XYFitCurve(const QString& name) : XYCurve(name, new XYFitCurvePrivate(this)) { init(); } XYFitCurve::XYFitCurve(const QString& name, XYFitCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFitCurve::~XYFitCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFitCurve::init() { Q_D(XYFitCurve); //TODO: read from the saved settings for XYFitCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } void XYFitCurve::recalculate() { Q_D(XYFitCurve); d->recalculate(); } 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 = nsl_fit_model_polynomial; fitData.degree = 1; } else if (action == PlotDataDialog::FitPower) { //Power fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_power; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp1) { //Exponential (degree 1) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 1; } else if (action == PlotDataDialog::FitExp2) { //Exponential (degree 2) fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_exponential; fitData.degree = 2; } else if (action == PlotDataDialog::FitInvExp) { //Inverse exponential fitData.modelCategory = nsl_fit_model_basic; fitData.modelType = nsl_fit_model_inverse_exponential; } else if (action == PlotDataDialog::FitGauss) { //Gauss fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_gaussian; fitData.degree = 1; } else if (action == PlotDataDialog::FitCauchyLorentz) { //Cauchy-Lorentz fitData.modelCategory = nsl_fit_model_peak; fitData.modelType = nsl_fit_model_lorentz; fitData.degree = 1; } else if (action == PlotDataDialog::FitTan) { //Arc tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_atan; } else if (action == PlotDataDialog::FitTanh) { //Hyperbolic tangent fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_tanh; } else if (action == PlotDataDialog::FitErrFunc) { //Error function fitData.modelCategory = nsl_fit_model_growth; fitData.modelType = nsl_fit_model_erf; } else { //Custom fitData.modelCategory = nsl_fit_model_custom; fitData.modelType = nsl_fit_model_custom; } 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; unsigned 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; DEBUG("XYFitCurve::initFitData() for model category = " << modelCategory << ", model type = " << modelType << ", degree = " << degree); if (modelCategory != nsl_fit_model_custom) paramNames.clear(); paramNamesUtf8.clear(); // 10 indices used in multi degree models QStringList indices = {QString::fromUtf8("\u2081"), QString::fromUtf8("\u2082"), QString::fromUtf8("\u2083"), QString::fromUtf8("\u2084"), QString::fromUtf8("\u2085"), QString::fromUtf8("\u2086"), QString::fromUtf8("\u2087"), QString::fromUtf8("\u2088"), QString::fromUtf8("\u2089"), QString::fromUtf8("\u2081") + QString::fromUtf8("\u2080")}; 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 << QString::fromUtf8("c\u2080") << QString::fromUtf8("c\u2081"); if (degree == 2) { model += " + c2*x^2"; paramNames << "c2"; paramNamesUtf8 << QString::fromUtf8("c\u2082"); } 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: degree = 1; paramNames << "a" << "b" << "c"; break; case nsl_fit_model_fourier: paramNames << "w" << "a0" << "a1" << "b1"; paramNamesUtf8 << QString::fromUtf8("\u03c9") << QString::fromUtf8("a\u2080") << QString::fromUtf8("a\u2081") << QString::fromUtf8("b\u2081"); 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 ((nsl_fit_model_type_peak)modelType) { case nsl_fit_model_gaussian: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./sqrt(2*pi) * (a1/s1 * exp(-((x-mu1)/s1)^2/2) + a2/s2 * exp(-((x-mu2)/s2)^2/2) + a3/s3 * exp(-((x-mu3)/s3)^2/2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); 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 << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_lorentz: switch (degree) { case 1: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1./pi * (a1 * g1/(g1^2+(x-mu1)^2) + a2 * g2/(g2^2+(x-mu2)^2) + a3 * g3/(g3^2+(x-mu3)^2))"; paramNames << "g1" << "mu1" << "a1" << "g2" << "mu2" << "a2" << "g3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03b3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03b3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03b3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); 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 << "g" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03b3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_sech: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/pi * (a1/s1 * sech((x-mu1)/s1) + a2/s2 * sech((x-mu2)/s2) + a3/s3 * sech((x-mu3)/s3))"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); 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 << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; case nsl_fit_model_logistic: switch (degree) { case 1: paramNames << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case 2: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082"); break; case 3: model = "1/4 * (a1/s1 * sech((x-mu1)/2/s1)**2 + a2/s2 * sech((x-mu2)/2/s2)**2 + a3/s3 * sech((x-mu3)/2/s3)**2)"; paramNames << "s1" << "mu1" << "a1" << "s2" << "mu2" << "a2" << "s3" << "mu3" << "a3"; paramNamesUtf8 << QString::fromUtf8("\u03c3\u2081") << QString::fromUtf8("\u03bc\u2081") << QString::fromUtf8("A\u2081") << QString::fromUtf8("\u03c3\u2082") << QString::fromUtf8("\u03bc\u2082") << QString::fromUtf8("A\u2082") << QString::fromUtf8("\u03c3\u2083") << QString::fromUtf8("\u03bc\u2083") << QString::fromUtf8("A\u2083"); 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 << "s" + numStr << "mu" + numStr << "a" + numStr; paramNamesUtf8 << QString::fromUtf8("\u03c3") + indices[i-1] << QString::fromUtf8("\u03bc") + indices[i-1] << QString::fromUtf8("A") + indices[i-1]; } model += ")"; } break; } break; case nsl_fit_model_growth: model = nsl_fit_model_growth_equation[fitData.modelType]; switch ((nsl_fit_model_type_growth)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 << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_sigmoid: paramNames << "k" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_fit_model_hill: paramNames << "s" << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "n" << "A"; 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 ((nsl_sf_stats_distribution)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 << "s" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gaussian_tail: paramNames << "s" << "mu" << "A" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "A" << "a"; break; case nsl_sf_stats_exponential: paramNames << "l" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_exponential_power: paramNames << "s" << "mu" << "b" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03bc") << "b" << "A"; break; case nsl_sf_stats_cauchy_lorentz: case nsl_sf_stats_levy: paramNames << "g" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_rayleigh: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; 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 << "t" << "k" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b8") << "k" << "A"; break; case nsl_sf_stats_flat: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_chi_squared: paramNames << "n" << "a"; paramNamesUtf8 << "n" << "A"; break; case nsl_sf_stats_fdist: paramNames << "n1" << "n2" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2081") << QString::fromUtf8("\u03bd") + QString::fromUtf8("\u2082") << "A"; break; case nsl_sf_stats_tdist: paramNames << "n" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bd") << "A"; break; case nsl_sf_stats_beta: case nsl_sf_stats_pareto: paramNames << "a" << "b" << "A"; break; case nsl_sf_stats_weibull: paramNames << "k" << "l" << "mu" << "a"; paramNamesUtf8 << "k" << QString::fromUtf8("\u03bb") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel1: paramNames << "s" << "b" << "mu" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << QString::fromUtf8("\u03b2") << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_gumbel2: paramNames << "a" << "b" << "mu" << "A"; paramNamesUtf8 << "a" << "b" << QString::fromUtf8("\u03bc") << "A"; break; case nsl_sf_stats_poisson: paramNames << "l" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03bb") << "A"; break; case nsl_sf_stats_binomial: case nsl_sf_stats_negative_binomial: case nsl_sf_stats_pascal: paramNames << "p" << "n" << "a"; paramNamesUtf8 << "p" << "n" << "A"; break; case nsl_sf_stats_geometric: case nsl_sf_stats_logarithmic: paramNames << "p" << "a"; paramNamesUtf8 << "p" << "A"; break; case nsl_sf_stats_hypergeometric: paramNames << "n1" << "n2" << "t" << "a"; paramNamesUtf8 << "n" + QString::fromUtf8("\u2081") << "n" + QString::fromUtf8("\u2082") << "t" << "A"; break; case nsl_sf_stats_maxwell_boltzmann: paramNames << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03c3") << "A"; break; case nsl_sf_stats_frechet: paramNames << "g" << "mu" << "s" << "a"; paramNamesUtf8 << QString::fromUtf8("\u03b3") << QString::fromUtf8("\u03bc") << QString::fromUtf8("\u03c3") << "A"; break; } break; case nsl_fit_model_custom: break; } 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] = -DBL_MAX; paramUpperLimits[i] = DBL_MAX; } // set some model-dependent start values if (modelCategory == nsl_fit_model_distribution) { nsl_sf_stats_distribution type = (nsl_sf_stats_distribution)modelType; if (type == nsl_sf_stats_flat) paramStartValues[0] = -1.0; else if (type == nsl_sf_stats_frechet || type == nsl_sf_stats_levy || type == nsl_sf_stats_exponential_power) paramStartValues[1] = 0.0; else if (type == nsl_sf_stats_weibull || type == nsl_sf_stats_gumbel2) paramStartValues[2] = 0.0; else if (type == nsl_sf_stats_binomial || type == nsl_sf_stats_negative_binomial || type == nsl_sf_stats_pascal || type == nsl_sf_stats_geometric || type == nsl_sf_stats_logarithmic) paramStartValues[0] = 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*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yDataColumn, yDataColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, xErrorColumn, xErrorColumn) BASIC_SHARED_D_READER_IMPL(XYFitCurve, const AbstractColumn*, yErrorColumn, yErrorColumn) const QString& XYFitCurve::xDataColumnPath() const { Q_D(const XYFitCurve); return d->xDataColumnPath; } const QString& XYFitCurve::yDataColumnPath() const { Q_D(const XYFitCurve); return d->yDataColumnPath; } 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, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFitCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->xDataColumn) { exec(new XYFitCurveSetXDataColumnCmd(d, column, i18n("%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(XYFitCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFitCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFitCurve); if (column != d->yDataColumn) { exec(new XYFitCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); 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"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFitCurvePrivate::XYFitCurvePrivate(XYFitCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xErrorColumn(0), yErrorColumn(0), xColumn(0), yColumn(0), residualsColumn(0), xVector(0), yVector(0), residualsVector(0), 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; unsigned 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) { 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 x = gsl_vector_get(paramValues, i); // bound values if limits are set QByteArray paramnameba = paramNames->at(i).toLatin1(); assign_variable(paramnameba.constData(), nsl_fit_map_bound(x, min[i], max[i])); QDEBUG("Parameter"<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); const double eps = 1.e-9 * fabs(f_p); // adapt step size to value value += eps; assign_variable(name, value); const double f_pdp = parse(func); // qDebug()<<"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(); } // 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 const AbstractColumn* tmpXDataColumn = 0; const AbstractColumn* tmpYDataColumn = 0; if (dataSourceType == XYCurve::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()) { 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() < xDataColumn->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.xRange.first(); xmax = fitData.xRange.last(); } 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 == XYCurve::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.weightsType) { case nsl_fit_weight_no: break; case nsl_fit_weight_instrumental: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(yerror[i]); break; case nsl_fit_weight_direct: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = yerror[i]; break; case nsl_fit_weight_inverse: if (yerrorVector.size() > 0) for(size_t i = 0; i < n; i++) weight[i] = 1./yerror[i]; break; case nsl_fit_weight_statistical: for (size_t i = 0; i < n; i++) weight[i] = 1./ydata[i]; break; case nsl_fit_weight_relative: for (size_t i = 0; i < n; i++) weight[i] = 1./gsl_pow_2(ydata[i]); break; case nsl_fit_weight_statistical_fit: case nsl_fit_weight_relative_fit: 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" for (unsigned int i = 0; i < np; i++) DEBUG("parameter " << i << " fixed: " << fitData.paramFixed.data()[i]); //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; // 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); // set start values double* x_init = fitData.paramStartValues.data(); double* x_min = fitData.paramLowerLimits.data(); double* x_max = fitData.paramUpperLimits.data(); // 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]); gsl_vector_view x = gsl_vector_view_array(x_init, np); // initialize solver with function f and initial guess x gsl_multifit_fdfsolver_set(s, &f, &x.vector); // iterate int status; unsigned int iter = 0; fitResult.solverOutput.clear(); writeSolverState(s); do { iter++; // update weights for Y-depending weights if (fitData.weightsType == nsl_fit_weight_statistical_fit) { for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i } else if (fitData.weightsType == 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) + 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"); // y'(x) double *yd = new double[n]; for (size_t i = 0; i < n; i++) { size_t index = i; if (index == n-1) index = n-2; yd[i] = gsl_vector_get(s->f, index+1) + ydata[index+1] - gsl_vector_get(s->f, index) - ydata[index]; yd[i] /= (xdata[index+1] - xdata[index]); } switch (fitData.weightsType) { case nsl_fit_weight_no: break; case nsl_fit_weight_instrumental: for (size_t i = 0; i < n; i++) { double sigma; if (yerrorVector.size() > 0) // x- and y-error // sigma = sqrt(sigma_y^2 + (y'(x)*sigma_x)^2) sigma = sqrt(gsl_pow_2(yerror[i]) + gsl_pow_2(yd[i] * xerror[i])); else // only x-error sigma = yd[i] * xerror[i]; weight[i] = 1./gsl_pow_2(sigma); } break; // other weight types: y'(x) considered correctly? case nsl_fit_weight_direct: for (size_t i = 0; i < n; i++) { weight[i] = xerror[i]/yd[i]; if (yerrorVector.size() > 0) weight[i] += yerror[i]; } break; case nsl_fit_weight_inverse: for (size_t i = 0; i < n; i++) { weight[i] = yd[i]/xerror[i]; if (yerrorVector.size() > 0) weight[i] += 1./yerror[i]; } break; case nsl_fit_weight_statistical: case nsl_fit_weight_relative: break; case nsl_fit_weight_statistical_fit: for (size_t i = 0; i < n; i++) weight[i] = 1./(gsl_vector_get(s->f, i) + ydata[i]); // 1/Y_i break; case 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) + ydata[i]); // 1/Y_i^2 break; } delete[] yd; do { iter++; status = gsl_multifit_fdfsolver_iterate(s); writeSolverState(s); if (status) break; status = gsl_multifit_test_delta(s->dx, s->x, delta, delta); } while (status == GSL_CONTINUE && iter < maxIters); } delete[] weight; // unscale start values 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; //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; //needed for coefficient of determination, R-squared fitResult.sst = gsl_stats_tss(ydata, 1, n); fitResult.rsquare = nsl_stats_rsquare(fitResult.sse, fitResult.sst); fitResult.rsquareAdj = nsl_stats_rsquareAdj(fitResult.rsquare, np, fitResult.dof); fitResult.chisq_p = nsl_stats_chisq_p(fitResult.sse, fitResult.dof); fitResult.fdist_F = nsl_stats_fdist_F(fitResult.sst, fitResult.rms); fitResult.fdist_p = nsl_stats_fdist_p(fitResult.fdist_F, np, fitResult.dof); fitResult.aic = nsl_stats_aic(fitResult.sse, n, np); fitResult.bic = nsl_stats_bic(fitResult.sse, n, np); //parameter values const double c = GSL_MIN_DBL(1., sqrt(fitResult.rms)); //limit error for poor fit 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.evaluateFullRange) { // 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) ExpressionParser* parser = ExpressionParser::getInstance(); if (fitData.evaluateFullRange) { // evaluate fit on full data range if selected xmin = tmpXDataColumn->minimum(); xmax = tmpXDataColumn->maximum(); } xVector->resize(fitData.evaluatedPoints); yVector->resize(fitData.evaluatedPoints); bool rc = parser->evaluateCartesian(fitData.model, QString::number(xmin), QString::number(xmax), fitData.evaluatedPoints, xVector, yVector, fitData.paramNames, fitResult.paramValues); if (!rc) { xVector->clear(); yVector->clear(); } fitResult.elapsedTime = timer.elapsed(); //redraw the curve 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 xy-curve information XYCurve::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->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); WRITE_COLUMN(d->xErrorColumn, xErrorColumn); WRITE_COLUMN(d->yErrorColumn, yErrorColumn); writer->writeAttribute("autoRange", QString::number(d->fitData.autoRange)); writer->writeAttribute("xRangeMin", QString::number(d->fitData.xRange.first(), 'g', 15)); writer->writeAttribute("xRangeMax", QString::number(d->fitData.xRange.last(), 'g', 15)); writer->writeAttribute("modelCategory", QString::number(d->fitData.modelCategory)); writer->writeAttribute("modelType", QString::number(d->fitData.modelType)); writer->writeAttribute("weightsType", QString::number(d->fitData.weightsType)); 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("evaluateFullRange", QString::number(d->fitData.evaluateFullRange)); 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(); //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->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); if (!reader->isStartElement() || reader->name() != "xyFitCurve") { reader->raiseError(i18n("no xy fit 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() == "xyFitCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "fitData") { + } else if (!preview && reader->name() == "fitData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); READ_COLUMN(xErrorColumn); READ_COLUMN(yErrorColumn); READ_INT_VALUE("autoRange", fitData.autoRange, bool); READ_DOUBLE_VALUE("xRangeMin", fitData.xRange.first()); READ_DOUBLE_VALUE("xRangeMax", fitData.xRange.last()); READ_INT_VALUE("modelCategory", fitData.modelCategory, nsl_fit_model_category); READ_INT_VALUE("modelType", fitData.modelType, unsigned int); READ_INT_VALUE("weightsType", fitData.weightsType, 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.evaluateFullRange, 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 (reader->name() == "name") { // needed for custom model + } else if (!preview && reader->name() == "name") { // needed for custom model d->fitData.paramNames << reader->readElementText(); - } else if (reader->name() == "startValue") { + } else if (!preview && reader->name() == "startValue") { d->fitData.paramStartValues << reader->readElementText().toDouble(); - } else if (reader->name() == "fixed") { + } else if (!preview && reader->name() == "fixed") { d->fitData.paramFixed << (bool)reader->readElementText().toInt(); - } else if (reader->name() == "lowerLimit") { + } 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 << -DBL_MAX; - } else if (reader->name() == "upperLimit") { + } 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 << DBL_MAX; - } else if (reader->name() == "value") { + } else if (!preview && reader->name() == "value") { d->fitResult.paramValues << reader->readElementText().toDouble(); - } else if (reader->name() == "error") { + } else if (!preview && reader->name() == "error") { d->fitResult.errorValues << reader->readElementText().toDouble(); - } else if (reader->name() == "tdist_t") { + } else if (!preview && reader->name() == "tdist_t") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); - } else if (reader->name() == "tdist_p") { + } else if (!preview && reader->name() == "tdist_p") { d->fitResult.tdist_tValues << reader->readElementText().toDouble(); - } else if (reader->name() == "tdist_margin") { + } else if (!preview && reader->name() == "tdist_margin") { d->fitResult.tdist_marginValues << reader->readElementText().toDouble(); - } else if (reader->name() == "fitResult") { + } 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 505a54dd3..48c304a0e 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierFilterCurve.cpp @@ -1,462 +1,463 @@ /*************************************************************************** File : XYFourierFilterCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier filter -------------------------------------------------------------------- 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 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" #include // isnan extern "C" { #include #ifdef HAVE_FFTW3 #include #endif #include "backend/nsl/nsl_sf_poly.h" } #include #include #include XYFourierFilterCurve::XYFourierFilterCurve(const QString& name) : XYCurve(name, new XYFourierFilterCurvePrivate(this)) { init(); } XYFourierFilterCurve::XYFourierFilterCurve(const QString& name, XYFourierFilterCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFourierFilterCurve::~XYFourierFilterCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierFilterCurve::init() { Q_D(XYFourierFilterCurve); //TODO: read from the saved settings for XYFourierFilterCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFourierFilterCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYFourierFilterCurve::xDataColumnPath() const { Q_D(const XYFourierFilterCurve); return d->xDataColumnPath; } const QString& XYFourierFilterCurve::yDataColumnPath() const { Q_D(const XYFourierFilterCurve); return d->yDataColumnPath; } 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_S(XYFourierFilterCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFourierFilterCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFourierFilterCurve); if (column != d->xDataColumn) { exec(new XYFourierFilterCurveSetXDataColumnCmd(d, column, i18n("%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(XYFourierFilterCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFourierFilterCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFourierFilterCurve); if (column != d->yDataColumn) { exec(new XYFourierFilterCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierFilterCurvePrivate::XYFourierFilterCurvePrivate(XYFourierFilterCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYFourierFilterCurvePrivate::~XYFourierFilterCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 == XYCurve::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 unsigned int n = 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 unsigned 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(n); yVector->resize(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 xy-curve information XYCurve::save(writer); //write xy-fourier_filter-curve specific information //filter data writer->writeStartElement("filterData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xyFourierFilterCurve") { reader->raiseError(i18n("no xy Fourier filter 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() == "xyFourierFilterCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "filterData") { + } else if (!preview && reader->name() == "filterData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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 (reader->name() == "filterResult") { + } 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) + 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 27cc6a8b6..ae72c7c3c 100644 --- a/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYFourierTransformCurve.cpp @@ -1,448 +1,449 @@ /*************************************************************************** File : XYFourierTransformCurve.cpp Project : LabPlot Description : A xy-curve defined by a Fourier transform -------------------------------------------------------------------- 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 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" #include // isnan extern "C" { #include "backend/nsl/nsl_sf_poly.h" } #include #include #include #include XYFourierTransformCurve::XYFourierTransformCurve(const QString& name) : XYCurve(name, new XYFourierTransformCurvePrivate(this)) { init(); } XYFourierTransformCurve::XYFourierTransformCurve(const QString& name, XYFourierTransformCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYFourierTransformCurve::~XYFourierTransformCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYFourierTransformCurve::init() { Q_D(XYFourierTransformCurve); //TODO: read from the saved settings for XYFourierTransformCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYFourierTransformCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYFourierTransformCurve::xDataColumnPath() const { Q_D(const XYFourierTransformCurve); return d->xDataColumnPath; } const QString& XYFourierTransformCurve::yDataColumnPath() const { Q_D(const XYFourierTransformCurve); return d->yDataColumnPath; } 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_S(XYFourierTransformCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYFourierTransformCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYFourierTransformCurve); if (column != d->xDataColumn) { exec(new XYFourierTransformCurveSetXDataColumnCmd(d, column, i18n("%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(XYFourierTransformCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYFourierTransformCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYFourierTransformCurve); if (column != d->yDataColumn) { exec(new XYFourierTransformCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYFourierTransformCurvePrivate::XYFourierTransformCurvePrivate(XYFourierTransformCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYFourierTransformCurvePrivate::~XYFourierTransformCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 = 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(N); yVector->resize(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 xy-curve information XYCurve::save(writer); //write xy-fourier_transform-curve specific information //transform data writer->writeStartElement("transformData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xyFourierTransformCurve") { reader->raiseError(i18n("no xy Fourier transform 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() == "xyFourierTransformCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "transformData") { + } else if (!preview && reader->name() == "transformData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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 (reader->name() == "transformResult") { + } 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 ea382756c..771c5e853 100644 --- a/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYIntegrationCurve.cpp @@ -1,420 +1,421 @@ /*************************************************************************** File : XYIntegrationCurve.cpp Project : LabPlot Description : A xy-curve defined by an integration -------------------------------------------------------------------- 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 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" #include // isnan #include // DBL_MIN extern "C" { #include } #include #include #include #include XYIntegrationCurve::XYIntegrationCurve(const QString& name) : XYCurve(name, new XYIntegrationCurvePrivate(this)) { init(); } XYIntegrationCurve::XYIntegrationCurve(const QString& name, XYIntegrationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYIntegrationCurve::~XYIntegrationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYIntegrationCurve::init() { Q_D(XYIntegrationCurve); //TODO: read from the saved settings for XYIntegrationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYIntegrationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYIntegrationCurve::xDataColumnPath() const { Q_D(const XYIntegrationCurve); return d->xDataColumnPath; } const QString& XYIntegrationCurve::yDataColumnPath() const { Q_D(const XYIntegrationCurve); return d->yDataColumnPath; } 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_S(XYIntegrationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYIntegrationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYIntegrationCurve); if (column != d->xDataColumn) { exec(new XYIntegrationCurveSetXDataColumnCmd(d, column, i18n("%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(XYIntegrationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYIntegrationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYIntegrationCurve); if (column != d->yDataColumn) { exec(new XYIntegrationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYIntegrationCurvePrivate::XYIntegrationCurvePrivate(XYIntegrationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYIntegrationCurvePrivate::~XYIntegrationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 == XYCurve::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; 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)); } } } const size_t n = 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(np); yVector->resize(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 xy-curve information XYCurve::save(writer); //write xy-integration-curve specific information // integration data writer->writeStartElement("integrationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "integrationData") { + } else if (!preview && reader->name() == "integrationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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 (reader->name() == "integrationResult") { + } 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 (reader->name() == "column") { + } 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 09b1cbd1d..70bbf2eee 100644 --- a/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYInterpolationCurve.cpp @@ -1,620 +1,621 @@ /*************************************************************************** File : XYInterpolationCurve.cpp Project : LabPlot Description : A xy-curve defined by an interpolation -------------------------------------------------------------------- Copyright : (C) 2016 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 20016-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" #include // isnan #include // DBL_MIN extern "C" { #include #include #include "backend/nsl/nsl_diff.h" #include "backend/nsl/nsl_int.h" } #include #include #include XYInterpolationCurve::XYInterpolationCurve(const QString& name) : XYCurve(name, new XYInterpolationCurvePrivate(this)) { init(); } XYInterpolationCurve::XYInterpolationCurve(const QString& name, XYInterpolationCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYInterpolationCurve::~XYInterpolationCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYInterpolationCurve::init() { Q_D(XYInterpolationCurve); //TODO: read from the saved settings for XYInterpolationCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYInterpolationCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYInterpolationCurve::xDataColumnPath() const { Q_D(const XYInterpolationCurve); return d->xDataColumnPath; } const QString& XYInterpolationCurve::yDataColumnPath() const { Q_D(const XYInterpolationCurve); return d->yDataColumnPath; } 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_S(XYInterpolationCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYInterpolationCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYInterpolationCurve); if (column != d->xDataColumn) { exec(new XYInterpolationCurveSetXDataColumnCmd(d, column, i18n("%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(XYInterpolationCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYInterpolationCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYInterpolationCurve); if (column != d->yDataColumn) { exec(new XYInterpolationCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYInterpolationCurvePrivate::XYInterpolationCurvePrivate(XYInterpolationCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYInterpolationCurvePrivate::~XYInterpolationCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 == XYCurve::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 unsigned int n = 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 unsigned int 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 (unsigned int i = 0; i < npoints; i++) { if ((*yVector)[i] > CartesianScale::LIMIT_MAX) (*yVector)[i] = CartesianScale::LIMIT_MAX; else if ((*yVector)[i] < CartesianScale::LIMIT_MIN) (*yVector)[i] = CartesianScale::LIMIT_MIN; } 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 xy-curve information XYCurve::save(writer); //write xy-interpolation-curve specific information // interpolation data writer->writeStartElement("interpolationData"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xyInterpolationCurve") { reader->raiseError(i18n("no xy interpolation 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() == "xyInterpolationCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "interpolationData") { + } else if (!preview && reader->name() == "interpolationData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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, int); READ_INT_VALUE("pointsMode", interpolationData.pointsMode, XYInterpolationCurve::PointsMode); READ_INT_VALUE("evaluate", interpolationData.evaluate, nsl_interp_evaluate); - } else if (reader->name() == "interpolationResult") { + } 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 73c5db30a..fc9fbf2d3 100644 --- a/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYSmoothCurve.cpp @@ -1,442 +1,443 @@ /*************************************************************************** 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) : XYCurve(name, new XYSmoothCurvePrivate(this)) { init(); } XYSmoothCurve::XYSmoothCurve(const QString& name, XYSmoothCurvePrivate* dd) : XYCurve(name, dd) { init(); } XYSmoothCurve::~XYSmoothCurve() { //no need to delete the d-pointer here - it inherits from QGraphicsItem //and is deleted during the cleanup in QGraphicsScene } void XYSmoothCurve::init() { Q_D(XYSmoothCurve); //TODO: read from the saved settings for XYSmoothCurve? d->lineType = XYCurve::Line; d->symbolsStyle = Symbol::NoSymbols; } 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, const AbstractColumn*, xDataColumn, xDataColumn) BASIC_SHARED_D_READER_IMPL(XYSmoothCurve, const AbstractColumn*, yDataColumn, yDataColumn) const QString& XYSmoothCurve::xDataColumnPath() const { Q_D(const XYSmoothCurve); return d->xDataColumnPath; } const QString& XYSmoothCurve::yDataColumnPath() const { Q_D(const XYSmoothCurve); return d->yDataColumnPath; } 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_S(XYSmoothCurve, SetXDataColumn, const AbstractColumn*, xDataColumn) void XYSmoothCurve::setXDataColumn(const AbstractColumn* column) { Q_D(XYSmoothCurve); if (column != d->xDataColumn) { exec(new XYSmoothCurveSetXDataColumnCmd(d, column, i18n("%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(XYSmoothCurve, SetYDataColumn, const AbstractColumn*, yDataColumn) void XYSmoothCurve::setYDataColumn(const AbstractColumn* column) { Q_D(XYSmoothCurve); if (column != d->yDataColumn) { exec(new XYSmoothCurveSetYDataColumnCmd(d, column, i18n("%1: assign y-data"))); handleSourceDataChanged(); if (column) { connect(column, SIGNAL(dataChanged(const AbstractColumn*)), this, SLOT(handleSourceDataChanged())); //TODO disconnect on undo } } } 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"))); } //############################################################################## //######################### Private implementation ############################# //############################################################################## XYSmoothCurvePrivate::XYSmoothCurvePrivate(XYSmoothCurve* owner) : XYCurvePrivate(owner), xDataColumn(0), yDataColumn(0), xColumn(0), yColumn(0), xVector(0), yVector(0), q(owner) { } XYSmoothCurvePrivate::~XYSmoothCurvePrivate() { //no need to delete xColumn and yColumn, they are deleted //when the parent aspect is removed } // ... // see XYFitCurvePrivate 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 == XYCurve::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 unsigned int n = 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 unsigned int points = smoothData.points; const nsl_smooth_weight_type weight = smoothData.weight; const double percentile = smoothData.percentile; const unsigned 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"); WRITE_COLUMN(d->xDataColumn, xDataColumn); WRITE_COLUMN(d->yDataColumn, yDataColumn); 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); if (!reader->isStartElement() || reader->name() != "xySmoothCurve") { reader->raiseError(i18n("no xy smooth 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() == "xySmoothCurve") break; if (!reader->isStartElement()) continue; if (reader->name() == "xyCurve") { if ( !XYCurve::load(reader, preview) ) return false; - if (preview) - return true; - } else if (reader->name() == "smoothData") { + } else if (!preview && reader->name() == "smoothData") { attribs = reader->attributes(); READ_COLUMN(xDataColumn); READ_COLUMN(yDataColumn); 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, int); 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 (reader->name() == "smoothResult") { + } 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 (reader->name() == "column") { + } 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; }