diff --git a/src/backend/datasources/filters/AsciiFilter.cpp b/src/backend/datasources/filters/AsciiFilter.cpp index a42a5e9e3..5ebb89991 100644 --- a/src/backend/datasources/filters/AsciiFilter.cpp +++ b/src/backend/datasources/filters/AsciiFilter.cpp @@ -1,1283 +1,1283 @@ /*************************************************************************** File : AsciiFilter.cpp Project : LabPlot Description : ASCII I/O-filter -------------------------------------------------------------------- Copyright : (C) 2009-2017 Stefan Gerlach (stefan.gerlach@uni.kn) Copyright : (C) 2009-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 * * * ***************************************************************************/ #include "backend/datasources/FileDataSource.h" #include "backend/core/column/Column.h" #include "backend/datasources/filters/AsciiFilter.h" #include "backend/datasources/filters/AsciiFilterPrivate.h" #include "backend/lib/macros.h" #include "backend/lib/trace.h" #include #include #include #include #include #include /*! \class AsciiFilter \brief Manages the import/export of data organized as columns (vectors) from/to an ASCII-file. \ingroup datasources */ AsciiFilter::AsciiFilter() : AbstractFileFilter(), d(new AsciiFilterPrivate(this)) {} AsciiFilter::~AsciiFilter() {} /*! reads the content of the device \c device. */ void AsciiFilter::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromDevice(device, dataSource, importMode, lines); } void AsciiFilter::readFromLiveDeviceNotFile(QIODevice &device, AbstractDataSource * dataSource) { d->readFromLiveDevice(device, dataSource); } qint64 AsciiFilter::readFromLiveDevice(QIODevice& device, AbstractDataSource* dataSource, qint64 from) { return d->readFromLiveDevice(device, dataSource, from); } /*! reads the content of the file \c fileName. */ QVector AsciiFilter::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { d->readDataFromFile(fileName, dataSource, importMode, lines); return QVector(); //TODO: remove this later once all read*-functions in the filter classes don't return any preview strings anymore } QVector AsciiFilter::preview(const QString& fileName, int lines) { return d->preview(fileName, lines); } /*! reads the content of the file \c fileName to the data source \c dataSource. */ //void AsciiFilter::read(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode) { // d->read(fileName, dataSource, importMode); //} /*! writes the content of the data source \c dataSource to the file \c fileName. */ void AsciiFilter::write(const QString& fileName, AbstractDataSource* dataSource) { d->write(fileName, dataSource); // emit() } /*! loads the predefined filter settings for \c filterName */ void AsciiFilter::loadFilterSettings(const QString& filterName) { Q_UNUSED(filterName); } /*! saves the current settings as a new filter with the name \c filterName */ void AsciiFilter::saveFilterSettings(const QString& filterName) const { Q_UNUSED(filterName); } /*! returns the list with the names of all saved (system wide or user defined) filter settings. */ QStringList AsciiFilter::predefinedFilters() { return QStringList(); } /*! returns the list of all predefined separator characters. */ QStringList AsciiFilter::separatorCharacters() { return (QStringList() << "auto" << "TAB" << "SPACE" << "," << ";" << ":" << ",TAB" << ";TAB" << ":TAB" << ",SPACE" << ";SPACE" << ":SPACE"); } /*! returns the list of all predefined comment characters. */ QStringList AsciiFilter::commentCharacters() { return (QStringList() << "#" << "!" << "//" << "+" << "c" << ":" << ";"); } /*! returns the list of all predefined data types. */ QStringList AsciiFilter::dataTypes() { const QMetaObject& mo = AbstractColumn::staticMetaObject; const QMetaEnum& me = mo.enumerator(mo.indexOfEnumerator("ColumnMode")); QStringList list; for (int i = 0; i <= 100; i++) // me.keyCount() does not work because we have holes in enum if (me.valueToKey(i)) list << me.valueToKey(i); return list; } /*! returns the number of columns in the file \c fileName. */ int AsciiFilter::columnNumber(const QString& fileName, const QString& separator) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of columns"); return -1; } QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); QStringList lineStringList; if (separator.length() > 0) lineStringList = line.split(separator); else lineStringList = line.split(QRegExp("\\s+")); DEBUG("number of columns : " << lineStringList.size()); return lineStringList.size(); } size_t AsciiFilter::lineNumber(const QString& fileName) { KFilterDev device(fileName); if (!device.open(QIODevice::ReadOnly)) { DEBUG("Could not open file " << fileName.toStdString() << " for determining number of lines"); return 0; } size_t lineCount = 0; while (!device.atEnd()) { device.readLine(); lineCount++; } //TODO: wc is much faster but not portable /* QElapsedTimer myTimer; myTimer.start(); QProcess wc; wc.start(QString("wc"), QStringList() << "-l" << fileName); size_t lineCount = 0; while (wc.waitForReadyRead()) lineCount = wc.readLine().split(' ')[0].toInt(); lineCount++; // last line not counted DEBUG(" Elapsed time counting lines : " << myTimer.elapsed() << " ms"); */ return lineCount; } /*! returns the number of lines in the device \c device or 0 if not available. resets the position to 0! */ size_t AsciiFilter::lineNumber(QIODevice &device) { // device.hasReadLine() always returns 0 for KFilterDev! if (device.isSequential()) return 0; size_t lineCount = 0; device.seek(0); while (!device.atEnd()) { device.readLine(); lineCount++; } device.seek(0); return lineCount; } void AsciiFilter::setCommentCharacter(const QString& s) { d->commentCharacter = s; } QString AsciiFilter::commentCharacter() const { return d->commentCharacter; } void AsciiFilter::setSeparatingCharacter(const QString& s) { d->separatingCharacter = s; } QString AsciiFilter::separatingCharacter() const { return d->separatingCharacter; } void AsciiFilter::setDateTimeFormat(const QString &f) { d->dateTimeFormat = f; } QString AsciiFilter::dateTimeFormat() const { return d->dateTimeFormat; } void AsciiFilter::setNumberFormat(QLocale::Language lang) { d->numberFormat = lang; } QLocale::Language AsciiFilter::numberFormat() const { return d->numberFormat; } void AsciiFilter::setAutoModeEnabled(const bool b) { d->autoModeEnabled = b; } bool AsciiFilter::isAutoModeEnabled() const { return d->autoModeEnabled; } void AsciiFilter::setHeaderEnabled(const bool b) { d->headerEnabled = b; } bool AsciiFilter::isHeaderEnabled() const { return d->headerEnabled; } void AsciiFilter::setSkipEmptyParts(const bool b) { d->skipEmptyParts = b; } bool AsciiFilter::skipEmptyParts() const { return d->skipEmptyParts; } void AsciiFilter::setCreateIndexEnabled(bool b) { d->createIndexEnabled = b; } void AsciiFilter::setSimplifyWhitespacesEnabled(bool b) { d->simplifyWhitespacesEnabled = b; } bool AsciiFilter::simplifyWhitespacesEnabled() const { return d->simplifyWhitespacesEnabled; } void AsciiFilter::setVectorNames(const QString s) { d->vectorNames = s.simplified().split(' '); } QStringList AsciiFilter::vectorNames() const { return d->vectorNames; } QVector AsciiFilter::columnModes() { return d->columnModes; } void AsciiFilter::setStartRow(const int r) { d->startRow = r; } int AsciiFilter::startRow() const { return d->startRow; } void AsciiFilter::setEndRow(const int r) { d->endRow = r; } int AsciiFilter::endRow() const { return d->endRow; } void AsciiFilter::setStartColumn(const int c) { d->startColumn = c; } int AsciiFilter::startColumn() const { return d->startColumn; } void AsciiFilter::setEndColumn(const int c) { d->endColumn = c; } int AsciiFilter::endColumn() const { return d->endColumn; } //##################################################################### //################### Private implementation ########################## //##################################################################### AsciiFilterPrivate::AsciiFilterPrivate(AsciiFilter* owner) : q(owner), commentCharacter("#"), separatingCharacter("auto"), autoModeEnabled(true), headerEnabled(true), skipEmptyParts(false), simplifyWhitespacesEnabled(true), createIndexEnabled(false), startRow(1), endRow(-1), startColumn(1), endColumn(-1), m_prepared(false), m_columnOffset(0) { } /*! * returns -1 if the device couldn't be opened, 1 if the current read position in the device is at the end and 0 otherwise. */ int AsciiFilterPrivate::prepareDeviceToRead(QIODevice& device) { if (!device.open(QIODevice::ReadOnly)) return -1; if (device.atEnd()) // empty file return 1; // Parse the first line: // Determine the number of columns, create the columns and use (if selected) the first row to name them QString firstLine; do { // skip comment lines firstLine = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } } while (firstLine.startsWith(commentCharacter)); DEBUG(" device position after first line and comments = " << device.pos()); QString firstLineOriginal = firstLine; firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("First line: \'" << firstLine.toStdString() << '\''); // determine separator and split first line QStringList firstLineStringList; if (separatingCharacter == "auto") { DEBUG("automatic separator"); QRegExp regExp("(\\s+)|(,\\s+)|(;\\s+)|(:\\s+)"); firstLineStringList = firstLine.split(regExp, QString::SkipEmptyParts); if (!firstLineStringList.isEmpty()) { int length1 = firstLineStringList.at(0).length(); if (firstLineStringList.size() > 1) { int pos2 = firstLine.indexOf(firstLineStringList.at(1), length1); m_separator = firstLine.mid(length1, pos2 - length1); } else { //old: separator = line.right(line.length() - length1); m_separator = ' '; } } } else { // use given separator // replace symbolic "TAB" with '\t' m_separator = separatingCharacter.replace(QLatin1String("TAB"), "\t", Qt::CaseInsensitive); // replace symbolic "SPACE" with ' ' m_separator = m_separator.replace(QLatin1String("SPACE"), QLatin1String(" "), Qt::CaseInsensitive); firstLineStringList = firstLine.split(m_separator, QString::SkipEmptyParts); } DEBUG("separator: \'" << m_separator.toStdString() << '\''); DEBUG("number of columns: " << firstLineStringList.size()); DEBUG("headerEnabled = " << headerEnabled); if (headerEnabled) { // use first line to name vectors vectorNames = firstLineStringList; QDEBUG("vector names =" << vectorNames); startRow++; } // set range to read if (endColumn == -1) endColumn = firstLineStringList.size(); // last column if (createIndexEnabled) { vectorNames.prepend("index"); endColumn++; } m_actualCols = endColumn - startColumn + 1; //TEST: readline-seek-readline fails /* qint64 testpos = device.pos(); DEBUG("read data line @ pos " << testpos << " : " << device.readLine().toStdString()); device.seek(testpos); testpos = device.pos(); DEBUG("read data line again @ pos " << testpos << " : " << device.readLine().toStdString()); */ // this also resets position to start of file m_actualRows = AsciiFilter::lineNumber(device); // Find first data line (ignoring comment lines) DEBUG("Skipping " << startRow - 1 << " lines"); for (int i = 0; i < startRow - 1; ++i) { QString line = device.readLine(); if (device.atEnd()) { if (device.isSequential()) break; else return 1; } if (line.startsWith(commentCharacter)) // ignore commented lines i--; } // parse first data line to determine data type for each column if (device.isSequential()) firstLine = firstLineOriginal; else firstLine = device.readLine(); firstLine.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) firstLine = firstLine.simplified(); DEBUG("first data line : \'" << firstLine.toStdString() << '\''); firstLineStringList = firstLine.split(m_separator, QString::SkipEmptyParts); QDEBUG("first data line, parsed: " << firstLineStringList); columnModes.resize(m_actualCols); int col = 0; if (createIndexEnabled) { columnModes[0] = AbstractColumn::Integer; col = 1; } for (const auto& valueString: firstLineStringList) { // only parse columns available in first data line if (col == m_actualCols) break; columnModes[col++] = AbstractFileFilter::columnMode(valueString, dateTimeFormat, numberFormat); } QDEBUG("column modes = " << columnModes); int actualEndRow = endRow; DEBUG("endRow = " << endRow); if (endRow == -1 || endRow > m_actualRows) actualEndRow = m_actualRows; if (m_actualRows > actualEndRow) m_actualRows = actualEndRow; // reset to start of file if (!device.isSequential()) device.seek(0); DEBUG("start/end column: " << startColumn << ' ' << endColumn); DEBUG("start/end row: " << startRow << ' ' << actualEndRow); DEBUG("actual cols/rows (w/o header incl. start rows): " << m_actualCols << ' ' << m_actualRows); if (m_actualRows == 0 && !device.isSequential()) return 1; return 0; } /*! reads the content of the file \c fileName to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromFile(const QString& fileName, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromFile(): fileName = \'" << fileName.toStdString() << "\', dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); KFilterDev device(fileName); readDataFromDevice(device, dataSource, importMode, lines); } qint64 AsciiFilterPrivate::readFromLiveDevice(QIODevice & device, AbstractDataSource * dataSource, qint64 from) { Q_ASSERT(dataSource != nullptr); FileDataSource* spreadsheet = dynamic_cast(dataSource); if (!m_prepared) { DEBUG("device is sequential = " << device.isSequential()); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) DEBUG("Device error = " << deviceError); if (deviceError) return 0; ////////// /////////////////////////// prepare import for spreadsheet spreadsheet->setUndoAware(false); //make the available columns undo unaware before we resize and rename them below, //the same will be done for new columns in this->resize(). for (int i = 0; i < spreadsheet->childCount(); i++) spreadsheet->child(i)->setUndoAware(false); qDebug() << "fds resizing!"; spreadsheet->removeColumns(0, 2); spreadsheet->clear(); spreadsheet->resize(AbstractFileFilter::Replace, vectorNames, m_actualCols); qDebug() << "fds resized to col: " << m_actualCols; qDebug() << "fds rowCount: " << spreadsheet->rowCount(); //also here we need a cheaper version of this if (!spreadsheet->keepLastValues()) spreadsheet->setRowCount(m_actualRows); else { spreadsheet->setRowCount(spreadsheet->keepNvalues()); m_actualRows = spreadsheet->keepNvalues(); } m_dataContainer.resize(m_actualCols); for (int n = 0; n < m_actualCols; n++) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) spreadsheet->child(n)->setColumnMode(columnModes[n]); switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } qDebug() << "prepared!"; } qint64 bytesread = 0; // if there's data do be read if (device.bytesAvailable() > 0) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportTotal: "); #endif qDebug() << "got new data"; FileDataSource::ReadingType readingType; if (!m_prepared) readingType = FileDataSource::ReadingType::TillEnd; else { //we have to read all the data when reading from end //so we set readingType to TillEnd if (spreadsheet->readingType() == FileDataSource::ReadingType::FromEnd) readingType = FileDataSource::ReadingType::TillEnd; else readingType = spreadsheet->readingType(); } //move to the last read position, from == total bytes read //since the other source types are sequencial we cannot seek on them if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) device.seek(from); qDebug() <<"available bytes: " << device.bytesAvailable(); //count the new lines, increase actualrows on each //now we read all the new lines, if we want to use sample rate //then here we can do it, if we have actually sample rate number of lines :-? int newLinesForSampleRateNotTillEnd = 0; int newLinesTillEnd = 0; QVector newData; if (readingType != FileDataSource::ReadingType::TillEnd) { newData.reserve(spreadsheet->sampleRate()); newData.resize(spreadsheet->sampleRate()); } int newDataIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportReadingFromFile: "); #endif while (!device.atEnd()) { if (readingType != FileDataSource::ReadingType::TillEnd) newData[newDataIdx++] = device.readLine(); else newData.push_back(device.readLine()); newLinesTillEnd++; if (readingType != FileDataSource::ReadingType::TillEnd) { newLinesForSampleRateNotTillEnd++; //for Continous reading and FromEnd we read sample rate number of lines if possible if (newLinesForSampleRateNotTillEnd == spreadsheet->sampleRate()) break; } } } //now we reset the readingType if (spreadsheet->readingType() == FileDataSource::ReadingType::FromEnd) readingType = spreadsheet->readingType(); //we had less new lines than the sample rate specified if (readingType != FileDataSource::ReadingType::TillEnd) qDebug() << "Removed empty lines: " << newData.removeAll(""); //back to the last read position before counting when reading from files if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) device.seek(from); const int spreadsheetRowCountBeforeResize = spreadsheet->rowCount(); int currentRow; // indexes the position in the vector(column) int linesToRead; if (!m_prepared) linesToRead = newLinesTillEnd; if (m_prepared) { //increase row count if we don't have a fixed size //but only after the preparation step if (!spreadsheet->keepLastValues()) { if (readingType != FileDataSource::ReadingType::TillEnd) m_actualRows += qMin(newData.size(), spreadsheet->sampleRate()); else m_actualRows += newData.size(); } //fixed size if (spreadsheet->keepLastValues()) { if (readingType == FileDataSource::ReadingType::TillEnd) { //we had more lines than the fixed size, so we read m_actualRows number of lines if (newLinesTillEnd > m_actualRows) { linesToRead = m_actualRows; //TODO after reading we should skip the next data lines //because it's TillEnd actually } else linesToRead = newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinouslyFixed or FromEnd linesToRead = qMin(spreadsheet->sampleRate(), newLinesTillEnd); } } else { //appending linesToRead = m_actualRows - spreadsheetRowCountBeforeResize; } } //new rows/resize columns if we don't have a fixed size //TODO if the user changes this value..m_resizedToFixedSize..setResizedToFixedSize if (!spreadsheet->keepLastValues()) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportResizing: "); #endif if (spreadsheet->rowCount() < m_actualRows) spreadsheet->setRowCount(m_actualRows); if (!m_prepared) currentRow = 0; else { // indexes the position in the vector(column) currentRow = spreadsheetRowCountBeforeResize; } // if we have fixed size, we do this only once in preparation, here we can use // m_prepared and we need something to decide whether it has a fixed size or increasing for (int n = 0; n < m_actualCols; n++) { // data() returns a void* which is a pointer to any data type (see ColumnPrivate.cpp) switch (columnModes[n]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(n)->data()); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[n] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } else { //when we have a fixed size we have to pop sampleRate number of lines if specified //here popping, setting currentRow if (!m_prepared) currentRow = m_actualRows - qMin(newLinesTillEnd, m_actualRows); else { if (readingType == FileDataSource::ReadingType::TillEnd) { if (newLinesTillEnd > m_actualRows) currentRow = 0; else currentRow = m_actualRows - newLinesTillEnd; } else { //we read max sample rate number of lines when the reading mode //is ContinouslyFixed or FromEnd currentRow = m_actualRows - qMin(spreadsheet->sampleRate(), newLinesTillEnd); } } if (m_prepared) { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportPopping: "); #endif for (int row = 0; row < linesToRead; ++row) { for (int col = 0; col < m_actualCols; ++col) { switch (columnModes[col]) { case AbstractColumn::Numeric: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Integer: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::Text: { QVector* vector = static_cast*>(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } case AbstractColumn::DateTime: { QVector* vector = static_cast* >(spreadsheet->child(col)->data()); vector->pop_front(); vector->reserve(m_actualRows); vector->resize(m_actualRows); m_dataContainer[col] = static_cast(vector); break; } //TODO case AbstractColumn::Month: case AbstractColumn::Day: break; } } } } } // from the last row we read the new data in the spreadsheet - qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd; + qDebug() << "reading from line: " << currentRow << " lines till end: " << newLinesTillEnd; qDebug() << "Lines to read: " << linesToRead <<" actual rows: " << m_actualRows; newDataIdx = 0; if (readingType == FileDataSource::ReadingType::FromEnd) { if (m_prepared) { if (newData.size() > spreadsheet->sampleRate()) newDataIdx = newData.size() - spreadsheet->sampleRate(); //since we skip a couple of lines, we need to count those bytes too for (int i = 0; i < newDataIdx; ++i) bytesread += newData.at(i).size(); } } qDebug() << "newDataIdx: " << newDataIdx; //TODO static int indexColumnIdx = 0; { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportFillingContainers: "); #endif for (int i = 0; i < linesToRead; ++i) { QString line; if (readingType == FileDataSource::ReadingType::FromEnd) line = newData.at(newDataIdx++); else line = newData.at(i); if (spreadsheet->sourceType() == FileDataSource::SourceType::FileOrPipe) bytesread += line.size(); - qDebug() << "line bytes: " << line.size() << " line: " << line; - qDebug() << "reading in row: " << currentRow; + //qDebug() << "line bytes: " << line.size() << " line: " << line; + //qDebug() << "reading in row: " << currentRow; if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; QLocale locale(numberFormat); QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); if (createIndexEnabled) { if (spreadsheet->keepLastValues()) lineStringList.prepend(QString::number(indexColumnIdx++)); else lineStringList.prepend(QString::number(currentRow)); } for (int n = 0; n < m_actualCols; ++n) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : NAN); - qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); + //qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : 0); - qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); + //qDebug() << "dataContainer[" << n << "] size:" << static_cast*>(m_dataContainer[n])->size(); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = NAN; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = "NAN"; break; case AbstractColumn::Month: //TODO break; case AbstractColumn::Day: //TODO break; } } } currentRow++; } } { #ifdef PERFTRACE_LIVE_IMPORT PERFTRACE("AsciiLiveDataImportColumnComments: "); #endif ////////// // set the comments for each of the columns if datasource is a spreadsheet const int rows = spreadsheet->rowCount(); for (int n = 0; n < m_actualCols; ++n) { Column* column = spreadsheet->column(n); QString comment; switch (column->columnMode()) { case AbstractColumn::Numeric: comment = i18np("numerical data, %1 element", "numerical data, %1 elements", rows); break; case AbstractColumn::Text: comment = i18np("text data, %1 element", "text data, %1 elements", rows); break; } column->setComment(comment); column->setSuppressDataChangedSignal(false); column->setChanged(); } } } else qDebug() << "No new data available"; ////////////////// m_prepared = true; return bytesread; } /*! reads the content of device \c device to the data source \c dataSource. Uses the settings defined in the data source. */ void AsciiFilterPrivate::readDataFromDevice(QIODevice& device, AbstractDataSource* dataSource, AbstractFileFilter::ImportMode importMode, int lines) { DEBUG("AsciiFilterPrivate::readDataFromDevice(): dataSource = " << dataSource << ", mode = " << ENUM_TO_STRING(AbstractFileFilter, ImportMode, importMode) << ", lines = " << lines); Q_ASSERT(dataSource != nullptr); if (!m_prepared) { DEBUG("device is sequential = " << device.isSequential()); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) DEBUG("Device error = " << deviceError); if (deviceError == 1 && importMode == AbstractFileFilter::Replace && dataSource) dataSource->clear(); if (deviceError) return; // matrix data has only one column mode (which is not text) if (dynamic_cast(dataSource)) { auto mode = columnModes[0]; (mode == AbstractColumn::Text) ? mode = AbstractColumn::Numeric : 0; for (auto& c: columnModes) (c != mode) ? c = mode : 0; } m_columnOffset = dataSource->prepareImport(m_dataContainer, importMode, m_actualRows - startRow + 1, m_actualCols, vectorNames, columnModes); m_prepared = true; } DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data int currentRow = 0; // indexes the position in the vector(column) if (lines == -1) lines = m_actualRows; DEBUG("reading " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); i++) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend the index if required //TODO: come up maybe with a solution with adding the index inside of the loop below, //without conversion to string, prepending to the list and then conversion back to integer. if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); for (int n = 0; n < m_actualCols; n++) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : NAN); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); static_cast*>(m_dataContainer[n])->operator[](currentRow) = (isNumber ? value : NAN); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueDateTime.isValid() ? valueDateTime : QDateTime(); break; } case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else { // missing columns in this line switch (columnModes[n]) { case AbstractColumn::Numeric: static_cast*>(m_dataContainer[n])->operator[](currentRow) = NAN; break; case AbstractColumn::Integer: static_cast*>(m_dataContainer[n])->operator[](currentRow) = 0; break; case AbstractColumn::DateTime: static_cast*>(m_dataContainer[n])->operator[](currentRow) = QDateTime(); break; case AbstractColumn::Text: static_cast*>(m_dataContainer[n])->operator[](currentRow) = "NAN"; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } } currentRow++; emit q->completed(100 * currentRow/m_actualRows); } dataSource->finalizeImport(m_columnOffset, startColumn, endColumn, dateTimeFormat, importMode); } /*! * generates the preview for the file \c fileName reading the provided number of \c lines. */ QVector AsciiFilterPrivate::preview(const QString& fileName, int lines) { QVector dataStrings; KFilterDev device(fileName); const int deviceError = prepareDeviceToRead(device); if (deviceError != 0) { DEBUG("Device error = " << deviceError); return dataStrings; } //number formatting DEBUG("locale = " << QLocale::languageToString(numberFormat).toStdString()); QLocale locale(numberFormat); // Read the data if (lines == -1) lines = m_actualRows; DEBUG("generating preview for " << qMin(lines, m_actualRows) << " lines"); for (int i = 0; i < qMin(lines, m_actualRows); i++) { QString line = device.readLine(); line.remove(QRegExp("[\\n\\r]")); // remove any newline if (simplifyWhitespacesEnabled) line = line.simplified(); if (line.isEmpty() || line.startsWith(commentCharacter)) // skip empty or commented lines continue; if (startRow > 1) { // skip start lines startRow--; continue; } QStringList lineStringList = line.split(m_separator, QString::SkipEmptyParts); //prepend index if required if (createIndexEnabled) lineStringList.prepend(QString::number(i+1)); QStringList lineString; for (int n = 0; n < m_actualCols; n++) { if (n < lineStringList.size()) { const QString& valueString = lineStringList.at(n); // set value depending on data type switch (columnModes[n]) { case AbstractColumn::Numeric: { bool isNumber; const double value = locale.toDouble(valueString, &isNumber); lineString += QString::number(isNumber ? value : NAN); break; } case AbstractColumn::Integer: { bool isNumber; const int value = locale.toInt(valueString, &isNumber); lineString += QString::number(isNumber ? value : NAN); break; } case AbstractColumn::DateTime: { const QDateTime valueDateTime = QDateTime::fromString(valueString, dateTimeFormat); lineString += valueDateTime.isValid() ? valueDateTime.toString(dateTimeFormat) : QLatin1String(" "); break; } case AbstractColumn::Text: lineString += valueString; break; case AbstractColumn::Month: // never happens case AbstractColumn::Day: break; } } else // missing columns in this line lineString += QLatin1String("NAN"); } dataStrings << lineString; } return dataStrings; } /*! writes the content of \c dataSource to the file \c fileName. */ void AsciiFilterPrivate::write(const QString & fileName, AbstractDataSource* dataSource) { Q_UNUSED(fileName); Q_UNUSED(dataSource); //TODO: save data to ascii file } //############################################################################## //################## Serialization/Deserialization ########################### //############################################################################## /*! Saves as XML. */ void AsciiFilter::save(QXmlStreamWriter* writer) const { writer->writeStartElement( "asciiFilter"); writer->writeAttribute( "commentCharacter", d->commentCharacter); writer->writeAttribute( "separatingCharacter", d->separatingCharacter); writer->writeAttribute( "autoMode", QString::number(d->autoModeEnabled)); writer->writeAttribute( "createIndex", QString::number(d->createIndexEnabled)); writer->writeAttribute( "header", QString::number(d->headerEnabled)); writer->writeAttribute( "vectorNames", d->vectorNames.join(' ')); writer->writeAttribute( "skipEmptyParts", QString::number(d->skipEmptyParts)); writer->writeAttribute( "simplifyWhitespaces", QString::number(d->simplifyWhitespacesEnabled)); writer->writeAttribute( "startRow", QString::number(d->startRow)); writer->writeAttribute( "endRow", QString::number(d->endRow)); writer->writeAttribute( "startColumn", QString::number(d->startColumn)); writer->writeAttribute( "endColumn", QString::number(d->endColumn)); writer->writeEndElement(); } /*! Loads from XML. */ bool AsciiFilter::load(XmlStreamReader* reader) { if (!reader->isStartElement() || reader->name() != "asciiFilter") { reader->raiseError(i18n("no ascii filter element found")); return false; } QString attributeWarning = i18n("Attribute '%1' missing or empty, default value is used"); QXmlStreamAttributes attribs = reader->attributes(); QString str = attribs.value("commentCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'commentCharacter'")); else d->commentCharacter = str; str = attribs.value("separatingCharacter").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'separatingCharacter'")); else d->separatingCharacter = str; str = attribs.value("createIndex").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'createIndex'")); else d->createIndexEnabled = str.toInt(); str = attribs.value("autoMode").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'autoMode'")); else d->autoModeEnabled = str.toInt(); str = attribs.value("header").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'header'")); else d->headerEnabled = str.toInt(); str = attribs.value("vectorNames").toString(); d->vectorNames = str.split(' '); //may be empty str = attribs.value("simplifyWhitespaces").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'simplifyWhitespaces'")); else d->simplifyWhitespacesEnabled = str.toInt(); str = attribs.value("skipEmptyParts").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'skipEmptyParts'")); else d->skipEmptyParts = str.toInt(); str = attribs.value("startRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startRow'")); else d->startRow = str.toInt(); str = attribs.value("endRow").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endRow'")); else d->endRow = str.toInt(); str = attribs.value("startColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'startColumn'")); else d->startColumn = str.toInt(); str = attribs.value("endColumn").toString(); if (str.isEmpty()) reader->raiseWarning(attributeWarning.arg("'endColumn'")); else d->endColumn = str.toInt(); return true; } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp index aca7b8e8a..1185d55d1 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -1,2699 +1,2722 @@ /*************************************************************************** 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 "kdefrontend/ThemeHandler.h" #include "kdefrontend/widgets/ThemesWidget.h" #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) { init(); } CartesianPlot::CartesianPlot(const QString &name, CartesianPlotPrivate *dd):AbstractPlot(name, dd), m_legend(0), m_zoomFactor(1.2) { init(); } CartesianPlot::~CartesianPlot() { delete m_coordinateSystem; delete addNewMenu; delete zoomMenu; delete themeMenu; //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"); addChild(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); initActions(); initMenus(); 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); 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 = new Axis("x axis 2", this, Axis::AxisHorizontal); 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 = new Axis("y axis 1", this, Axis::AxisVertical); 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 = new Axis("y axis 2", this, Axis::AxisVertical); 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()); 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); 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 = new Axis("y axis 1", this, Axis::AxisVertical); 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); 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); 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 = new Axis("y axis 1", this, Axis::AxisVertical); 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()); 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); 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 = new Axis("y axis 1", this, Axis::AxisVertical); 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()); 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); d->retransform(); } 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); 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); addFitAction.append(new QAction(i18n("Linear"), this)); addFitAction.append(new QAction(i18n("Power"), this)); addFitAction.append(new QAction(i18n("Exponential (degree 1)"), this)); addFitAction.append(new QAction(i18n("Exponential (degree 2)"), this)); addFitAction.append(new QAction(i18n("Inverse exponential"), this)); addFitAction.append(new QAction(i18n("Gauss"), this)); addFitAction.append(new QAction(i18n("Cauchy-Lorentz"), this)); addFitAction.append(new QAction(i18n("Arc Tangent"), this)); addFitAction.append(new QAction(i18n("Hyperbolic tangent"), this)); addFitAction.append(new QAction(i18n("Error function"), this)); addFitAction.append(new QAction(i18n("Custom"), this)); 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() { 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); // TODO: SLOT: loadTheme(KConfig config) 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); } QMenu* CartesianPlot::createContextMenu() { 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() const { 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(); } //############################################################################## //################################ 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, retransformScales); +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeType, CartesianPlot::RangeType, rangeType, retransformScales) 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, retransformScales); +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeLastValues, int, rangeLastValues, retransformScales) 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, retransformScales); +STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetRangeFirstValues, int, rangeFirstValues, retransformScales) 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); +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); +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); +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); +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); +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); +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); +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); } XYCurve* CartesianPlot::addCurve() { XYCurve* curve = new XYCurve("xy-curve"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return curve; } XYEquationCurve* CartesianPlot::addEquationCurve() { XYEquationCurve* curve = new XYEquationCurve("f(x)"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return curve; } XYDataReductionCurve* CartesianPlot::addDataReductionCurve() { XYDataReductionCurve* curve = new XYDataReductionCurve("Data reduction"); this->addChild(curve); return curve; } /*! * 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; } XYDifferentiationCurve* 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); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYIntegrationCurve* 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); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYInterpolationCurve* 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); this->addChild(curve); curve->recalculate(); emit curve->interpolationDataChanged(curve->interpolationData()); } else { beginMacro(i18n("%1: add interpolation curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } Histogram* CartesianPlot::addHistogram(){ Histogram* curve= new Histogram("Histogram"); this->addChild(curve); return curve; } XYSmoothCurve* 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); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFitCurve* 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()); curve->initFitData(action, addFitAction); this->addChild(curve); curve->recalculate(); emit curve->fitDataChanged(curve->fitData()); } else { beginMacro(i18n("%1: add fit curve", name())); this->addChild(curve); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFourierFilterCurve* 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); } this->applyThemeOnNewCurve(curve); endMacro(); return curve; } XYFourierTransformCurve* CartesianPlot::addFourierTransformCurve() { XYFourierTransformCurve* curve = new XYFourierTransformCurve("Fourier transform"); this->addChild(curve); this->applyThemeOnNewCurve(curve); return 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 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) { 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); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); 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 curve->retransform(); } void CartesianPlot::HistogramdataChanged(){ Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = true; if (d->autoScaleX) this->scaleAuto(); else if (d->autoScaleX) this->scaleAutoX(); else 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); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); - d->curvesXMinMaxIsDirty = true; + d->curvesXMinMaxIsDirty = true; + emit xDataChangedSignal(); + if (d->autoScaleX) this->scaleAutoX(); else curve->retransform(); - emit xDataChangedSignal(); } void CartesianPlot::xHistogramDataChanged(){ if (project()->isLoading()) return; Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesXMinMaxIsDirty = true; if (d->autoScaleX) { this->scaleAutoX(); this->scaleAutoY(); } else 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); XYCurve* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else curve->retransform(); } void CartesianPlot::yHistogramDataChanged(){ if (project()->isLoading()) return; Q_D(CartesianPlot); Histogram* curve = dynamic_cast(QObject::sender()); Q_ASSERT(curve); d->curvesYMinMaxIsDirty = true; if (d->autoScaleY) this->scaleAutoY(); else 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); //loop over all xy-curves and determine the maximum x-value if (d->curvesXMinMaxIsDirty) { d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY) { if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY) { if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } QList childrenHistogram = this->children(); foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->xColumn()->minimum() != INFINITY){ if (curve->xColumn()->minimum() < d->curvesXMin) d->curvesXMin = curve->xColumn()->minimum(); } if (curve->xColumn()->maximum() != -INFINITY){ if (curve->xColumn()->maximum() > d->curvesXMax) d->curvesXMax = curve->xColumn()->maximum(); } } 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); //loop over all xy-curves and determine the maximum y-value if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; for (const auto* curve: this->children()) { if (!curve->isVisible()) continue; if (!curve->yColumn()) continue; if (curve->yColumn()->minimum() != INFINITY) { if (curve->yColumn()->minimum() < d->curvesYMin) d->curvesYMin = curve->yColumn()->minimum(); } if (curve->yColumn()->maximum() != -INFINITY) { if (curve->yColumn()->maximum() > d->curvesYMax) d->curvesYMax = curve->yColumn()->maximum(); } } d->curvesYMinMaxIsDirty = false; } QList childrenHistogram = this->children(); foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; d->curvesYMin = 0.0; if (curve->getYMaximum() != -INFINITY) { if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } } 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); //loop over all xy-curves and determine the maximum x-value QList children = this->children(); QList childrenHistogram = this->children(); - -// switch (d->rangeType) { -// case CartesianPlot::RangeLast: -// case CartesianPlot::RangeFirst: -// //TODO: -// break; -// case CartesianPlot::RangeFree: { - if (d->curvesXMinMaxIsDirty) { - d->curvesXMin = INFINITY; - d->curvesXMax = -INFINITY; - for (const auto* curve: children) { - if (!curve->isVisible()) - continue; - if (!curve->xColumn()) - continue; - - if (curve->xColumn()->minimum() != INFINITY) { - if (curve->xColumn()->minimum() < d->curvesXMin) - d->curvesXMin = curve->xColumn()->minimum(); - } - - if (curve->xColumn()->maximum() != -INFINITY) { - if (curve->xColumn()->maximum() > d->curvesXMax) - d->curvesXMax = curve->xColumn()->maximum(); - } - } - foreach(const Histogram* curve, childrenHistogram) { - if (!curve->isVisible()) - continue; - if (!curve->xColumn()) - continue; - - if (curve->xColumn()->minimum() != INFINITY){ - if (curve->xColumn()->minimum() < d->curvesXMin) - d->curvesXMin = curve->xColumn()->minimum(); - } - - if (curve->xColumn()->maximum() != -INFINITY){ - if (curve->xColumn()->maximum() > d->curvesXMax) - d->curvesXMax = curve->xColumn()->maximum(); - } - } - d->curvesXMinMaxIsDirty = false; - } -// } -// } + double min = INFINITY; + double max = -INFINITY; + + double val; + switch (d->rangeType) { + case CartesianPlot::RangeLast: + + for (const XYCurve* curve : children) { + if (curve->xColumn() != nullptr) { + val = curve->xColumn()->minimumFirst(d->rangeLastValues); + if (val < min) + min = val; + val = curve->xColumn()->maximumFirst(d->rangeLastValues); + if (val > max) + max = val; + } + } + + d->curvesXMin = min; + d->curvesXMax = max; + + break; + case CartesianPlot::RangeFirst: + + for (const XYCurve* curve : children) { + if (curve->xColumn() != nullptr) { + val = curve->xColumn()->minimumLast(d->rangeFirstValues); + if (val < min) + min = val; + val = curve->xColumn()->maximumLast(d->rangeFirstValues); + if (val > max) + max = val; + } + } + + d->curvesXMin = min; + d->curvesXMax = max; + + break; + case CartesianPlot::RangeFree: + if (d->curvesXMinMaxIsDirty) { + d->curvesXMin = INFINITY; + d->curvesXMax = -INFINITY; + for (const auto* curve: children) { + if (!curve->isVisible()) + continue; + if (!curve->xColumn()) + continue; + + if (curve->xColumn()->minimum() != INFINITY) { + if (curve->xColumn()->minimum() < d->curvesXMin) + d->curvesXMin = curve->xColumn()->minimum(); + } + + if (curve->xColumn()->maximum() != -INFINITY) { + if (curve->xColumn()->maximum() > d->curvesXMax) + d->curvesXMax = curve->xColumn()->maximum(); + } + } + foreach(const Histogram* curve, childrenHistogram) { + if (!curve->isVisible()) + continue; + if (!curve->xColumn()) + continue; + + if (curve->xColumn()->minimum() != INFINITY){ + if (curve->xColumn()->minimum() < d->curvesXMin) + d->curvesXMin = curve->xColumn()->minimum(); + } + + if (curve->xColumn()->maximum() != -INFINITY){ + if (curve->xColumn()->maximum() > d->curvesXMax) + d->curvesXMax = curve->xColumn()->maximum(); + } + } + d->curvesXMinMaxIsDirty = false; + } + break; + } //TODO: this function seems to be broken. Maybe this was caused by the last merge from the histogram branch. if (d->curvesYMinMaxIsDirty) { d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; for (const auto* curve: children) { if (!curve->isVisible()) continue; if (!curve->xColumn()) continue; if (curve->yColumn()->minimum() != INFINITY) { if (curve->yColumn()->minimum() < d->curvesYMin) d->curvesYMin = curve->yColumn()->minimum(); } if (curve->yColumn()->maximum() != -INFINITY) { if (curve->yColumn()->maximum() > d->curvesYMax) d->curvesYMax = curve->yColumn()->maximum(); } } } foreach(const Histogram* curve, childrenHistogram) { if (!curve->isVisible()) continue; d->curvesYMin = 0.0; if (curve->getYMaximum() != -INFINITY){ if ( curve->getYMaximum() > d->curvesYMax) d->curvesYMax = curve->getYMaximum(); } } 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(); } -void CartesianPlot::setXMinMax(const int xmin, const int xmax) { - Q_D(CartesianPlot); - - if ((xmin != d->xMin) || (xmax != d->xMax)) { - d->xMin = xmin; - d->xMax = xmax; - - 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), m_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() { DEBUG("CartesianPlotPrivate::retransform()"); if (m_suppressRetransform) return; 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() { DEBUG("CartesianPlotPrivate::retransformScales()"); CartesianPlot* plot = dynamic_cast(q); QList 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->setEnd(xMax); axis->setUndoAware(true); } if (deltaXMin != 0) { axis->setUndoAware(false); axis->setStart(xMin); axis->setUndoAware(true); } //TODO; // if (axis->position() == Axis::AxisCustom && deltaYMin != 0) { // axis->setOffset(axis->offset() + deltaYMin, false); // } } else { if (deltaYMax != 0) { axis->setUndoAware(false); axis->setEnd(yMax); axis->setUndoAware(true); } if (deltaYMin != 0) { axis->setUndoAware(false); axis->setStart(yMin); axis->setUndoAware(true); } //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 q->retransform(); } /*! * 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); m_suppressRetransform = true; q->setRect(newRect); m_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) { 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; QString tmpTheme; 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 (reader->name() == "theme") { attribs = reader->attributes(); tmpTheme = attribs.value("name").toString(); } else if (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 (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 (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 (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 (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 (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)) { delete m_title; m_title=0; return false; } else { addChild(m_title); } } else if (reader->name() == "plotArea") { m_plotArea->load(reader); } else if (reader->name() == "axis") { Axis* axis = new Axis("", this); if (!axis->load(reader)) { delete axis; return false; } else { addChild(axis); } } else if (reader->name() == "xyCurve") { XYCurve* curve = addCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyEquationCurve") { XYEquationCurve* curve = addEquationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyDataReductionCurve") { XYDataReductionCurve* curve = addDataReductionCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyDifferentiationCurve") { XYDifferentiationCurve* curve = addDifferentiationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyIntegrationCurve") { XYIntegrationCurve* curve = addIntegrationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyInterpolationCurve") { XYInterpolationCurve* curve = addInterpolationCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFitCurve") { XYFitCurve* curve = addFitCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFourierFilterCurve") { XYFourierFilterCurve* curve = addFourierFilterCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xyFourierTransformCurve") { XYFourierTransformCurve* curve = addFourierTransformCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "xySmoothCurve") { XYSmoothCurve* curve = addSmoothCurve(); if (!curve->load(reader)) { removeChild(curve); return false; } } else if (reader->name() == "cartesianPlotLegend") { m_legend = new CartesianPlotLegend(this, ""); if (!m_legend->load(reader)) { delete m_legend; return false; } else { addChild(m_legend); addLegendAction->setEnabled(false); //only one legend is allowed -> disable the action } } else if (reader->name() == "customPoint") { CustomPoint* point = new CustomPoint(this, ""); if (!point->load(reader)) { delete point; return false; } else { addChild(point); } }else if(reader->name() == "Histogram"){ Histogram* curve = addHistogram(); if (!curve->load(reader)){ removeChild(curve); return false; } } else { // unknown element reader->raiseWarning(i18n("unknown cartesianPlot element '%1'", reader->name().toString())); if (!reader->skipToEndElement()) return false; } } d->retransform();//TODO: This is expensive. why do we need this on load? if (m_title) { m_title->setHidden(true); m_title->graphicsItem()->setParentItem(m_plotArea->graphicsItem()); } //if a theme was used, assign the value to the private member at the very end of load() //so we don't try to load the theme in applyThemeOnNewCurve() when adding curves on project load and calculate the palette if (!tmpTheme.isEmpty()){ KConfig config( ThemeHandler::themeFilePath(tmpTheme), KConfig::SimpleConfig ); //TODO: check whether the theme config really exists d->theme = tmpTheme; 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 QList& axisElements = children(AbstractAspect::IncludeHidden); const QList& plotAreaElements = children(AbstractAspect::IncludeHidden); const QList& 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; } void CartesianPlot::applyThemeOnNewCurve(XYCurve* curve) { Q_D(const CartesianPlot); if (!d->theme.isEmpty()) { KConfig config( ThemeHandler::themeFilePath(d->theme), KConfig::SimpleConfig ); curve->loadThemeConfig(config); } } diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h index 88d9964b3..a85cf3c9c 100644 --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -1,283 +1,281 @@ /*************************************************************************** File : CartesianPlot.h Project : LabPlot Description : Cartesian plot -------------------------------------------------------------------- Copyright : (C) 2011-2017 by Alexander Semke (alexander.semke@web.de) ***************************************************************************/ /*************************************************************************** * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the Free Software * * Foundation, Inc., 51 Franklin Street, Fifth Floor, * * Boston, MA 02110-1301 USA * * * ***************************************************************************/ #ifndef CARTESIANPLOT_H #define CARTESIANPLOT_H #include "backend/worksheet/plots/AbstractPlot.h" #include #include class QToolBar; class CartesianPlotPrivate; class CartesianPlotLegend; class XYCurve; class XYEquationCurve; class XYDataReductionCurve; class XYDifferentiationCurve; class XYIntegrationCurve; class XYInterpolationCurve; class XYSmoothCurve; class XYFitCurve; class XYFourierFilterCurve; class KConfig; class XYFourierTransformCurve; class CartesianPlot:public AbstractPlot { Q_OBJECT public: explicit CartesianPlot(const QString &name); virtual ~CartesianPlot(); enum Scale {ScaleLinear, ScaleLog10, ScaleLog2, ScaleLn, ScaleSqrt, ScaleX2}; enum Type {FourAxes, TwoAxes, TwoAxesCentered, TwoAxesCenteredZero}; enum RangeType {RangeFree, RangeLast, RangeFirst}; enum RangeBreakStyle {RangeBreakSimple, RangeBreakVertical, RangeBreakSloped}; enum MouseMode {SelectionMode, ZoomSelectionMode, ZoomXSelectionMode, ZoomYSelectionMode}; enum NavigationOperation {ScaleAuto, ScaleAutoX, ScaleAutoY, ZoomIn, ZoomOut, ZoomInX, ZoomOutX, ZoomInY, ZoomOutY, ShiftLeftX, ShiftRightX, ShiftUpY, ShiftDownY }; struct RangeBreak { RangeBreak() : start(NAN), end(NAN), position(0.5), style(RangeBreakSloped) {} bool isValid() const { return (!std::isnan(start) && !std::isnan(end)); } float start; float end; float position; RangeBreakStyle style; }; //simple wrapper for QList in order to get our macros working struct RangeBreaks { RangeBreaks() : lastChanged(-1) { RangeBreak b; list << b; }; QList list; int lastChanged; }; void initDefault(Type = FourAxes); QIcon icon() const; QMenu* createContextMenu(); QMenu* analysisMenu() const; void setRect(const QRectF&); QRectF plotRect(); void setMouseMode(const MouseMode); MouseMode mouseMode() const; void navigate(NavigationOperation); const QList& themeColorPalette() const; virtual void save(QXmlStreamWriter*) const; virtual bool load(XmlStreamReader*); virtual void loadThemeConfig(const KConfig&); void saveTheme(KConfig& config); BASIC_D_ACCESSOR_DECL(CartesianPlot::RangeType, rangeType, RangeType) BASIC_D_ACCESSOR_DECL(int, rangeLastValues, RangeLastValues) BASIC_D_ACCESSOR_DECL(int, rangeFirstValues, RangeFirstValues) BASIC_D_ACCESSOR_DECL(bool, autoScaleX, AutoScaleX) BASIC_D_ACCESSOR_DECL(bool, autoScaleY, AutoScaleY) BASIC_D_ACCESSOR_DECL(float, xMin, XMin) BASIC_D_ACCESSOR_DECL(float, xMax, XMax) BASIC_D_ACCESSOR_DECL(float, yMin, YMin) BASIC_D_ACCESSOR_DECL(float, yMax, YMax) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, xScale, XScale) BASIC_D_ACCESSOR_DECL(CartesianPlot::Scale, yScale, YScale) BASIC_D_ACCESSOR_DECL(bool, xRangeBreakingEnabled, XRangeBreakingEnabled) BASIC_D_ACCESSOR_DECL(bool, yRangeBreakingEnabled, YRangeBreakingEnabled) CLASS_D_ACCESSOR_DECL(RangeBreaks, xRangeBreaks, XRangeBreaks) CLASS_D_ACCESSOR_DECL(RangeBreaks, yRangeBreaks, YRangeBreaks) QString theme() const; typedef CartesianPlotPrivate Private; public slots: void setTheme(const QString&); private: void init(); void initActions(); void initMenus(); void setColorPalette(const KConfig&); void applyThemeOnNewCurve(XYCurve* curve); const XYCurve* currentCurve() const; CartesianPlotLegend* m_legend; float m_zoomFactor; QList m_themeColorPalette; QAction* visibilityAction; //"add new" actions QAction* addCurveAction; QAction* addEquationCurveAction; QAction* addHistogramPlot; QAction* addDataReductionCurveAction; QAction* addDifferentiationCurveAction; QAction* addIntegrationCurveAction; QAction* addInterpolationCurveAction; QAction* addSmoothCurveAction; QAction* addFitCurveAction; QAction* addFourierFilterCurveAction; QAction* addFourierTransformCurveAction; QAction* addHorizontalAxisAction; QAction* addVerticalAxisAction; QAction* addLegendAction; QAction* addCustomPointAction; //scaling, zooming, navigation actions QAction* scaleAutoXAction; QAction* scaleAutoYAction; QAction* scaleAutoAction; QAction* zoomInAction; QAction* zoomOutAction; QAction* zoomInXAction; QAction* zoomOutXAction; QAction* zoomInYAction; QAction* zoomOutYAction; QAction* shiftLeftXAction; QAction* shiftRightXAction; QAction* shiftUpYAction; QAction* shiftDownYAction; //analysis menu actions QAction* addDataOperationAction; QAction* addDataReductionAction; QAction* addDifferentiationAction; QAction* addIntegrationAction; QAction* addInterpolationAction; QAction* addSmoothAction; QVector addFitAction; QAction* addFourierFilterAction; QMenu* addNewMenu; QMenu* zoomMenu; QMenu* dataAnalysisMenu; QMenu* themeMenu; Q_DECLARE_PRIVATE(CartesianPlot) public slots: void addHorizontalAxis(); void addVerticalAxis(); XYCurve* addCurve(); Histogram* addHistogram(); XYEquationCurve* addEquationCurve(); XYDataReductionCurve* addDataReductionCurve(); XYDifferentiationCurve* addDifferentiationCurve(); XYIntegrationCurve* addIntegrationCurve(); XYInterpolationCurve* addInterpolationCurve(); XYSmoothCurve* addSmoothCurve(); XYFitCurve* addFitCurve(); XYFourierFilterCurve* addFourierFilterCurve(); XYFourierTransformCurve* addFourierTransformCurve(); void addLegend(); void addCustomPoint(); void scaleAuto(); void scaleAutoX(); void scaleAutoY(); void zoomIn(); void zoomOut(); void zoomInX(); void zoomOutX(); void zoomInY(); void zoomOutY(); void shiftLeftX(); void shiftRightX(); void shiftUpY(); void shiftDownY(); - void setXMinMax(const int xmin, const int xmax); - private slots: void updateLegend(); void childAdded(const AbstractAspect*); void childRemoved(const AbstractAspect* parent, const AbstractAspect* before, const AbstractAspect* child); void dataChanged(); void xDataChanged(); void yDataChanged(); void HistogramdataChanged(); void xHistogramDataChanged(); void yHistogramDataChanged(); void curveVisibilityChanged(); //SLOTs for changes triggered via QActions in the context menu void visibilityChanged(); void loadTheme(const QString&); protected: CartesianPlot(const QString &name, CartesianPlotPrivate *dd); signals: friend class CartesianPlotSetCRangeTypeCmd; friend class CartesianPlotSetCRangeLastValuesCmd; friend class CartesianPlotSetCRangeFirstValuesCmd; friend class CartesianPlotSetRectCmd; friend class CartesianPlotSetAutoScaleXCmd; friend class CartesianPlotSetXMinCmd; friend class CartesianPlotSetXMaxCmd; friend class CartesianPlotSetXScaleCmd; friend class CartesianPlotSetAutoScaleYCmd; friend class CartesianPlotSetYMinCmd; friend class CartesianPlotSetYMaxCmd; friend class CartesianPlotSetYScaleCmd; friend class CartesianPlotSetXRangeBreakingEnabledCmd; friend class CartesianPlotSetYRangeBreakingEnabledCmd; friend class CartesianPlotSetXRangeBreaksCmd; friend class CartesianPlotSetYRangeBreaksCmd; friend class CartesianPlotSetThemeCmd; void rangeTypeChanged(CartesianPlot::RangeType); void rangeLastValuesChanged(int); void rangeFirstValuesChanged(int); void rectChanged(QRectF&); void xAutoScaleChanged(bool); void xMinChanged(float); void xMaxChanged(float); void xScaleChanged(int); void yAutoScaleChanged(bool); void yMinChanged(float); void yMaxChanged(float); void yScaleChanged(int); void xRangeBreakingEnabledChanged(bool); void xRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void yRangeBreakingEnabledChanged(bool); void yRangeBreaksChanged(const CartesianPlot::RangeBreaks&); void themeChanged(const QString&); void xDataChangedSignal(); }; #endif diff --git a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp index b6fe497a3..0aa0f6b0d 100644 --- a/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp +++ b/src/kdefrontend/dockwidgets/CartesianPlotDock.cpp @@ -1,1509 +1,1471 @@ /*************************************************************************** File : CartesianPlotDock.cpp Project : LabPlot Description : widget for cartesian plot properties -------------------------------------------------------------------- Copyright : (C) 2011-2016 by Alexander Semke (alexander.semke@web.de) Copyright : (C) 2012-2013 by 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 "CartesianPlotDock.h" #include "backend/worksheet/plots/PlotArea.h" #include "backend/worksheet/plots/cartesian/XYCurve.h" #include "backend/core/column/Column.h" #include "kdefrontend/widgets/LabelWidget.h" #include "kdefrontend/GuiTools.h" #include "kdefrontend/TemplateHandler.h" #include "kdefrontend/ThemeHandler.h" #include #include #include #include #include #include #include #include #include #include /*! \class CartesianPlotDock \brief Provides a widget for editing the properties of the cartesian plot currently selected in the project explorer. \ingroup kdefrontend */ CartesianPlotDock::CartesianPlotDock(QWidget *parent): QWidget(parent), m_plot(0), labelWidget(0), m_initializing(false), m_rangeButtonsGroup(new QButtonGroup) { ui.setupUi(this); //"Coordinate system"-tab ui.bAddXBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveXBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbXBreak->addItem("1"); ui.bAddYBreak->setIcon( QIcon::fromTheme("list-add") ); ui.bRemoveYBreak->setIcon( QIcon::fromTheme("list-remove") ); ui.cbYBreak->addItem("1"); //"Background"-tab ui.kleBackgroundFileName->setClearButtonShown(true); ui.bOpen->setIcon( QIcon::fromTheme("document-open") ); KUrlCompletion* completion = new KUrlCompletion; ui.kleBackgroundFileName->setCompletionObject(completion); ui.kleBackgroundFileName->setAutoDeleteCompletionObject(true); //"Title"-tab QHBoxLayout* hboxLayout = new QHBoxLayout(ui.tabTitle); labelWidget=new LabelWidget(ui.tabTitle); hboxLayout->addWidget(labelWidget); hboxLayout->setContentsMargins(2,2,2,2); hboxLayout->setSpacing(2); //adjust layouts in the tabs for (int i = 0; i < ui.tabWidget->count(); ++i) { QGridLayout* layout = qobject_cast(ui.tabWidget->widget(i)->layout()); if (!layout) continue; layout->setContentsMargins(2,2,2,2); layout->setHorizontalSpacing(2); layout->setVerticalSpacing(2); } //Validators ui.leXBreakStart->setValidator( new QDoubleValidator(ui.leXBreakStart) ); ui.leXBreakEnd->setValidator( new QDoubleValidator(ui.leXBreakEnd) ); ui.leYBreakStart->setValidator( new QDoubleValidator(ui.leYBreakStart) ); ui.leYBreakEnd->setValidator( new QDoubleValidator(ui.leYBreakEnd) ); //Range m_rangeButtonsGroup->addButton(ui.rbRangesFirstN); m_rangeButtonsGroup->addButton(ui.rbRangesLastN); m_rangeButtonsGroup->addButton(ui.rbRangesFree); ui.leRangesFirstN->setEnabled(ui.rbRangesFirstN->isChecked()); ui.leRangesLastN->setEnabled(ui.rbRangesLastN->isChecked()); connect(ui.leRangesFirstN, SIGNAL(textChanged(QString)), this, SLOT(rangeFirstNchanged(QString))); connect(ui.leRangesLastN, SIGNAL(textChanged(QString)), this, SLOT(rangeLastNchanged(QString))); connect(m_rangeButtonsGroup, SIGNAL(buttonClicked(QAbstractButton*)), this, SLOT(rangeButtonClicked(QAbstractButton*))); //SIGNAL/SLOT //General connect( ui.leName, SIGNAL(returnPressed()), this, SLOT(nameChanged()) ); connect( ui.leComment, SIGNAL(returnPressed()), this, SLOT(commentChanged()) ); connect( ui.chkVisible, SIGNAL(clicked(bool)), this, SLOT(visibilityChanged(bool)) ); connect( ui.sbLeft, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbTop, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbWidth, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.sbHeight, SIGNAL(valueChanged(double)), this, SLOT(geometryChanged()) ); connect( ui.chkAutoScaleX, SIGNAL(stateChanged(int)), this, SLOT(autoScaleXChanged(int)) ); connect( ui.kleXMin, SIGNAL(returnPressed()), this, SLOT(xMinChanged()) ); connect( ui.kleXMax, SIGNAL(returnPressed()), this, SLOT(xMaxChanged()) ); connect( ui.cbXScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(xScaleChanged(int)) ); connect( ui.chkAutoScaleY, SIGNAL(stateChanged(int)), this, SLOT(autoScaleYChanged(int)) ); connect( ui.kleYMin, SIGNAL(returnPressed()), this, SLOT(yMinChanged()) ); connect( ui.kleYMax, SIGNAL(returnPressed()), this, SLOT(yMaxChanged()) ); connect( ui.cbYScaling, SIGNAL(currentIndexChanged(int)), this, SLOT(yScaleChanged(int)) ); //Range breaks connect( ui.chkXBreak, SIGNAL(toggled(bool)), this, SLOT(toggleXBreak(bool)) ); connect( ui.bAddXBreak, SIGNAL(clicked()), this, SLOT(addXBreak()) ); connect( ui.bRemoveXBreak, SIGNAL(clicked()), this, SLOT(removeXBreak()) ); connect( ui.cbXBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentXBreakChanged(int)) ); connect( ui.leXBreakStart, SIGNAL(returnPressed()), this, SLOT(xBreakStartChanged()) ); connect( ui.leXBreakEnd, SIGNAL(returnPressed()), this, SLOT(xBreakEndChanged()) ); connect( ui.sbXBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(xBreakPositionChanged(int)) ); connect( ui.cbXBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(xBreakStyleChanged(int)) ); connect( ui.chkYBreak, SIGNAL(toggled(bool)), this, SLOT(toggleYBreak(bool)) ); connect( ui.bAddYBreak, SIGNAL(clicked()), this, SLOT(addYBreak()) ); connect( ui.bRemoveYBreak, SIGNAL(clicked()), this, SLOT(removeYBreak()) ); connect( ui.cbYBreak, SIGNAL(currentIndexChanged(int)), this, SLOT(currentYBreakChanged(int)) ); connect( ui.leYBreakStart, SIGNAL(returnPressed()), this, SLOT(yBreakStartChanged()) ); connect( ui.leYBreakEnd, SIGNAL(returnPressed()), this, SLOT(yBreakEndChanged()) ); connect( ui.sbYBreakPosition, SIGNAL(valueChanged(int)), this, SLOT(yBreakPositionChanged(int)) ); connect( ui.cbYBreakStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(yBreakStyleChanged(int)) ); //Background connect( ui.cbBackgroundType, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundTypeChanged(int)) ); connect( ui.cbBackgroundColorStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundColorStyleChanged(int)) ); connect( ui.cbBackgroundImageStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundImageStyleChanged(int)) ); connect( ui.cbBackgroundBrushStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(backgroundBrushStyleChanged(int)) ); connect( ui.bOpen, SIGNAL(clicked(bool)), this, SLOT(selectFile()) ); connect( ui.kleBackgroundFileName, SIGNAL(returnPressed()), this, SLOT(fileNameChanged()) ); connect( ui.kleBackgroundFileName, SIGNAL(clearButtonClicked()), this, SLOT(fileNameChanged()) ); connect( ui.kcbBackgroundFirstColor, SIGNAL(changed(QColor)), this, SLOT(backgroundFirstColorChanged(QColor)) ); connect( ui.kcbBackgroundSecondColor, SIGNAL(changed(QColor)), this, SLOT(backgroundSecondColorChanged(QColor)) ); connect( ui.sbBackgroundOpacity, SIGNAL(valueChanged(int)), this, SLOT(backgroundOpacityChanged(int)) ); //Border connect( ui.cbBorderStyle, SIGNAL(currentIndexChanged(int)), this, SLOT(borderStyleChanged(int)) ); connect( ui.kcbBorderColor, SIGNAL(changed(QColor)), this, SLOT(borderColorChanged(QColor)) ); connect( ui.sbBorderWidth, SIGNAL(valueChanged(double)), this, SLOT(borderWidthChanged(double)) ); connect( ui.sbBorderCornerRadius, SIGNAL(valueChanged(double)), this, SLOT(borderCornerRadiusChanged(double)) ); connect( ui.sbBorderOpacity, SIGNAL(valueChanged(int)), this, SLOT(borderOpacityChanged(int)) ); //Padding connect( ui.sbPaddingHorizontal, SIGNAL(valueChanged(double)), this, SLOT(horizontalPaddingChanged(double)) ); connect( ui.sbPaddingVertical, SIGNAL(valueChanged(double)), this, SLOT(verticalPaddingChanged(double)) ); //theme and template handlers QFrame* frame = new QFrame(this); QHBoxLayout* layout = new QHBoxLayout(frame); m_themeHandler = new ThemeHandler(this); layout->addWidget(m_themeHandler); connect(m_themeHandler, SIGNAL(loadThemeRequested(QString)), this, SLOT(loadTheme(QString))); connect(m_themeHandler, SIGNAL(saveThemeRequested(KConfig&)), this, SLOT(saveTheme(KConfig&))); connect(m_themeHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); //connect(this, SIGNAL(saveThemeEnable(bool)), m_themeHandler, SLOT(saveThemeEnable(bool))); TemplateHandler* templateHandler = new TemplateHandler(this, TemplateHandler::CartesianPlot); layout->addWidget(templateHandler); connect(templateHandler, SIGNAL(loadConfigRequested(KConfig&)), this, SLOT(loadConfigFromTemplate(KConfig&))); connect(templateHandler, SIGNAL(saveConfigRequested(KConfig&)), this, SLOT(saveConfigAsTemplate(KConfig&))); connect(templateHandler, SIGNAL(info(QString)), this, SIGNAL(info(QString))); ui.verticalLayout->addWidget(frame); //TODO: activate the tab again once the functionality is implemented ui.tabWidget->removeTab(2); init(); } CartesianPlotDock::~CartesianPlotDock() { } void CartesianPlotDock::init() { this->retranslateUi(); /* //TODO: activate later once range breaking is implemented //create icons for the different styles for scale breaking QPainter pa; pa.setPen( QPen(Qt::SolidPattern, 0) ); QPixmap pm(20, 20); ui.cbXBreakStyle->setIconSize( QSize(20,20) ); ui.cbYBreakStyle->setIconSize( QSize(20,20) ); //simple pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.end(); ui.cbXBreakStyle->setItemIcon(0, pm); ui.cbYBreakStyle->setItemIcon(0, pm); //vertical pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(8,14,8,6); pa.drawLine(12,14,12,6); pa.end(); ui.cbXBreakStyle->setItemIcon(1, pm); ui.cbYBreakStyle->setItemIcon(1, pm); //sloped pm.fill(Qt::transparent); pa.begin( &pm ); pa.setRenderHint(QPainter::Antialiasing); pa.setBrush(Qt::SolidPattern); pa.drawLine(3,10,8,10); pa.drawLine(12,10,17,10); pa.drawLine(6,14,10,6); pa.drawLine(10,14,14,6); pa.end(); ui.cbXBreakStyle->setItemIcon(2, pm); ui.cbYBreakStyle->setItemIcon(2, pm); */ } void CartesianPlotDock::setPlots(QList list) { m_initializing = true; m_plotList = list; m_plot = list.first(); connect(m_plot, SIGNAL(xDataChangedSignal()), this, SLOT(plotXdataChanged())); double min = INFINITY; for (auto* plot: m_plotList) { for (const XYCurve* curve : plot->children()) { if (curve->xColumn() != nullptr) { if (curve->xColumn()->rowCount() < min) min = curve->xColumn()->rowCount(); } } } min = (min == INFINITY ? 2 : min); ui.leRangesFirstN->setText(QString::number(min)); ui.leRangesLastN->setText(QString::number(min)); ui.leRangesFirstN->setValidator(new QDoubleValidator(2, min, 2)); ui.leRangesLastN->setValidator(new QDoubleValidator(2, min, 2)); QList labels; for (auto* plot: list) labels.append(plot->title()); labelWidget->setLabels(labels); //if there is more then one plot in the list, disable the name and comment fields in the tab "general" if (list.size() == 1) { ui.lName->setEnabled(true); ui.leName->setEnabled(true); ui.lComment->setEnabled(true); ui.leComment->setEnabled(true); ui.leName->setText(m_plot->name()); ui.leComment->setText(m_plot->comment()); } else { ui.lName->setEnabled(false); ui.leName->setEnabled(false); ui.lComment->setEnabled(false); ui.leComment->setEnabled(false); ui.leName->setText(""); ui.leComment->setText(""); } //show the properties of the first plot this->load(); //update active widgets backgroundTypeChanged(ui.cbBackgroundType->currentIndex()); m_themeHandler->setCurrentTheme(m_plot->theme()); //Deactivate the geometry related widgets, if the worksheet layout is active. //Currently, a plot can only be a child of the worksheet itself, so we only need to ask the parent aspect (=worksheet). //TODO redesign this, if the hierarchy will be changend in future (a plot is a child of a new object group/container or so) Worksheet* w = dynamic_cast(m_plot->parentAspect()); if (w) { bool b = (w->layout() == Worksheet::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); connect(w, SIGNAL(layoutChanged(Worksheet::Layout)), this, SLOT(layoutChanged(Worksheet::Layout))); } //SIGNALs/SLOTs connect( m_plot, SIGNAL(aspectDescriptionChanged(const AbstractAspect*)), this, SLOT(plotDescriptionChanged(const AbstractAspect*)) ); connect( m_plot, SIGNAL(rectChanged(QRectF&)), this, SLOT(plotRectChanged(QRectF&)) ); connect( m_plot, SIGNAL(xAutoScaleChanged(bool)), this, SLOT(plotXAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(xMinChanged(float)), this, SLOT(plotXMinChanged(float)) ); connect( m_plot, SIGNAL(xMaxChanged(float)), this, SLOT(plotXMaxChanged(float)) ); connect( m_plot, SIGNAL(xScaleChanged(int)), this, SLOT(plotXScaleChanged(int)) ); connect( m_plot, SIGNAL(yAutoScaleChanged(bool)), this, SLOT(plotYAutoScaleChanged(bool)) ); connect( m_plot, SIGNAL(yMinChanged(float)), this, SLOT(plotYMinChanged(float)) ); connect( m_plot, SIGNAL(yMaxChanged(float)), this, SLOT(plotYMaxChanged(float)) ); connect( m_plot, SIGNAL(yScaleChanged(int)), this, SLOT(plotYScaleChanged(int)) ); connect( m_plot, SIGNAL(visibleChanged(bool)), this, SLOT(plotVisibleChanged(bool)) ); //range breaks connect( m_plot, SIGNAL(xRangeBreakingEnabledChanged(bool)), this, SLOT(plotXRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(xRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotXRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); connect( m_plot, SIGNAL(yRangeBreakingEnabledChanged(bool)), this, SLOT(plotYRangeBreakingEnabledChanged(bool)) ); connect( m_plot, SIGNAL(yRangeBreaksChanged(CartesianPlot::RangeBreaks)), this, SLOT(plotYRangeBreaksChanged(CartesianPlot::RangeBreaks)) ); // Plot Area connect( m_plot->plotArea(), SIGNAL(backgroundTypeChanged(PlotArea::BackgroundType)), this, SLOT(plotBackgroundTypeChanged(PlotArea::BackgroundType)) ); connect( m_plot->plotArea(), SIGNAL(backgroundColorStyleChanged(PlotArea::BackgroundColorStyle)), this, SLOT(plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundImageStyleChanged(PlotArea::BackgroundImageStyle)), this, SLOT(plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundBrushStyleChanged(Qt::BrushStyle)), this, SLOT(plotBackgroundBrushStyleChanged(Qt::BrushStyle)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFirstColorChanged(QColor&)), this, SLOT(plotBackgroundFirstColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundSecondColorChanged(QColor&)), this, SLOT(plotBackgroundSecondColorChanged(QColor&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundFileNameChanged(QString&)), this, SLOT(plotBackgroundFileNameChanged(QString&)) ); connect( m_plot->plotArea(), SIGNAL(backgroundOpacityChanged(float)), this, SLOT(plotBackgroundOpacityChanged(float)) ); connect( m_plot->plotArea(), SIGNAL(borderPenChanged(QPen&)), this, SLOT(plotBorderPenChanged(QPen&)) ); connect( m_plot->plotArea(), SIGNAL(borderOpacityChanged(float)), this, SLOT(plotBorderOpacityChanged(float)) ); connect( m_plot, SIGNAL(horizontalPaddingChanged(float)), this, SLOT(plotHorizontalPaddingChanged(float)) ); connect( m_plot, SIGNAL(verticalPaddingChanged(float)), this, SLOT(plotVerticalPaddingChanged(float)) ); m_initializing = false; } void CartesianPlotDock::activateTitleTab() { ui.tabWidget->setCurrentWidget(ui.tabTitle); } //************************************************************ //**** SLOTs for changes triggered in CartesianPlotDock ****** //************************************************************ void CartesianPlotDock::retranslateUi() { m_initializing = true; //general ui.cbXScaling->addItem( i18n("linear") ); ui.cbXScaling->addItem( i18n("log(x)") ); ui.cbXScaling->addItem( i18n("log2(x)") ); ui.cbXScaling->addItem( i18n("ln(x)") ); ui.cbYScaling->addItem( i18n("linear") ); ui.cbYScaling->addItem( i18n("log(y)") ); ui.cbYScaling->addItem( i18n("log2(y)") ); ui.cbYScaling->addItem( i18n("ln(y)") ); //scale breakings ui.cbXBreakStyle->addItem( i18n("simple") ); ui.cbXBreakStyle->addItem( i18n("vertical") ); ui.cbXBreakStyle->addItem( i18n("sloped") ); ui.cbYBreakStyle->addItem( i18n("simple") ); ui.cbYBreakStyle->addItem( i18n("vertical") ); ui.cbYBreakStyle->addItem( i18n("sloped") ); //plot area ui.cbBackgroundType->addItem(i18n("color")); ui.cbBackgroundType->addItem(i18n("image")); ui.cbBackgroundType->addItem(i18n("pattern")); ui.cbBackgroundColorStyle->addItem(i18n("single color")); ui.cbBackgroundColorStyle->addItem(i18n("horizontal gradient")); ui.cbBackgroundColorStyle->addItem(i18n("vertical gradient")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from top left)")); ui.cbBackgroundColorStyle->addItem(i18n("diag. gradient (from bottom left)")); ui.cbBackgroundColorStyle->addItem(i18n("radial gradient")); ui.cbBackgroundImageStyle->addItem(i18n("scaled and cropped")); ui.cbBackgroundImageStyle->addItem(i18n("scaled")); ui.cbBackgroundImageStyle->addItem(i18n("scaled, keep proportions")); ui.cbBackgroundImageStyle->addItem(i18n("centered")); ui.cbBackgroundImageStyle->addItem(i18n("tiled")); ui.cbBackgroundImageStyle->addItem(i18n("center tiled")); GuiTools::updatePenStyles(ui.cbBorderStyle, Qt::black); GuiTools::updateBrushStyles(ui.cbBackgroundBrushStyle, Qt::SolidPattern); m_initializing = false; } // "General"-tab void CartesianPlotDock::nameChanged() { if (m_initializing) return; m_plot->setName(ui.leName->text()); } void CartesianPlotDock::commentChanged() { if (m_initializing) return; m_plot->setComment(ui.leComment->text()); } void CartesianPlotDock::visibilityChanged(bool state) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setVisible(state); } void CartesianPlotDock::geometryChanged() { if (m_initializing) return; float x = Worksheet::convertToSceneUnits(ui.sbLeft->value(), Worksheet::Centimeter); float y = Worksheet::convertToSceneUnits(ui.sbTop->value(), Worksheet::Centimeter); float w = Worksheet::convertToSceneUnits(ui.sbWidth->value(), Worksheet::Centimeter); float h = Worksheet::convertToSceneUnits(ui.sbHeight->value(), Worksheet::Centimeter); QRectF rect(x,y,w,h); m_plot->setRect(rect); } /*! Called when the layout in the worksheet gets changed. Enables/disables the geometry widgets if the layout was deactivated/activated. Shows the new geometry values of the first plot if the layout was activated. */ void CartesianPlotDock::layoutChanged(Worksheet::Layout layout) { bool b = (layout == Worksheet::NoLayout); ui.sbTop->setEnabled(b); ui.sbLeft->setEnabled(b); ui.sbWidth->setEnabled(b); ui.sbHeight->setEnabled(b); } void CartesianPlotDock::autoScaleXChanged(int state) { bool checked = (state==Qt::Checked); ui.kleXMin->setEnabled(!checked); ui.kleXMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot: m_plotList) plot->setAutoScaleX(checked); } void CartesianPlotDock::xMinChanged() { if (m_initializing) return; float value = ui.kleXMin->text().toDouble(); for (auto* plot: m_plotList) plot->setXMin(value); } void CartesianPlotDock::xMaxChanged() { if (m_initializing) return; float value = ui.kleXMax->text().toDouble(); for (auto* plot: m_plotList) plot->setXMax(value); } /*! called on scale changes (linear, log) for the x-axis */ void CartesianPlotDock::xScaleChanged(int scale) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setXScale((CartesianPlot::Scale) scale); } void CartesianPlotDock::autoScaleYChanged(int state) { bool checked = (state==Qt::Checked); ui.kleYMin->setEnabled(!checked); ui.kleYMax->setEnabled(!checked); if (m_initializing) return; for (auto* plot: m_plotList) plot->setAutoScaleY(checked); } void CartesianPlotDock::yMinChanged() { if (m_initializing) return; float value = ui.kleYMin->text().toDouble(); for (auto* plot: m_plotList) plot->setYMin(value); } void CartesianPlotDock::yMaxChanged() { if (m_initializing) return; float value = ui.kleYMax->text().toDouble(); for (auto* plot: m_plotList) plot->setYMax(value); } /*! called on scale changes (linear, log) for the y-axis */ void CartesianPlotDock::yScaleChanged(int index) { if (m_initializing) return; CartesianPlot::Scale scale = (CartesianPlot::Scale)index; for (auto* plot: m_plotList) plot->setYScale(scale); } // "Range Breaks"-tab // x-range breaks void CartesianPlotDock::toggleXBreak(bool b) { ui.frameXBreakEdit->setEnabled(b); ui.leXBreakStart->setEnabled(b); ui.leXBreakEnd->setEnabled(b); ui.sbXBreakPosition->setEnabled(b); ui.cbXBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot: m_plotList) plot->setXRangeBreakingEnabled(b); } void CartesianPlotDock::addXBreak() { ui.bRemoveXBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list<setXRangeBreaks(breaks); ui.cbXBreak->addItem(QString::number(ui.cbXBreak->count()+1)); ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); } void CartesianPlotDock::removeXBreak() { ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); ui.cbXBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); if (index < ui.cbXBreak->count()-1) ui.cbXBreak->setCurrentIndex(index); else ui.cbXBreak->setCurrentIndex(ui.cbXBreak->count()-1); ui.bRemoveXBreak->setVisible(ui.cbXBreak->count()!=1); } void CartesianPlotDock::currentXBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->xRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leXBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leXBreakEnd->setText(str); ui.sbXBreakPosition->setValue(rangeBreak.position*100); ui.cbXBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::xBreakStartChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].start = ui.leXBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakEndChanged() { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].end = ui.leXBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } void CartesianPlotDock::xBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbXBreak->currentIndex(); CartesianPlot::RangeBreakStyle style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->xRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setXRangeBreaks(breaks); } // y-range breaks void CartesianPlotDock::toggleYBreak(bool b) { ui.frameYBreakEdit->setEnabled(b); ui.leYBreakStart->setEnabled(b); ui.leYBreakEnd->setEnabled(b); ui.sbYBreakPosition->setEnabled(b); ui.cbYBreakStyle->setEnabled(b); if (m_initializing) return; for (auto* plot: m_plotList) plot->setYRangeBreakingEnabled(b); } void CartesianPlotDock::addYBreak() { ui.bRemoveYBreak->setVisible(true); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); CartesianPlot::RangeBreak b; breaks.list << b; breaks.lastChanged = breaks.list.size() - 1; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->addItem(QString::number(ui.cbYBreak->count()+1)); ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); } void CartesianPlotDock::removeYBreak() { ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list.takeAt(index); breaks.lastChanged = -1; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); ui.cbYBreak->clear(); for (int i = 1; i <= breaks.list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); if (index < ui.cbYBreak->count()-1) ui.cbYBreak->setCurrentIndex(index); else ui.cbYBreak->setCurrentIndex(ui.cbYBreak->count()-1); ui.bRemoveYBreak->setVisible(ui.cbYBreak->count() != 1); } void CartesianPlotDock::currentYBreakChanged(int index) { if (m_initializing) return; if (index == -1) return; m_initializing = true; const CartesianPlot::RangeBreak rangeBreak = m_plot->yRangeBreaks().list.at(index); QString str = std::isnan(rangeBreak.start) ? "" : QString::number(rangeBreak.start); ui.leYBreakStart->setText(str); str = std::isnan(rangeBreak.end) ? "" : QString::number(rangeBreak.end); ui.leYBreakEnd->setText(str); ui.sbYBreakPosition->setValue(rangeBreak.position*100); ui.cbYBreakStyle->setCurrentIndex((int)rangeBreak.style); m_initializing = false; } void CartesianPlotDock::yBreakStartChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].start = ui.leYBreakStart->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakEndChanged() { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].end = ui.leYBreakEnd->text().toDouble(); breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakPositionChanged(int value) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].position = (float)value/100.; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } void CartesianPlotDock::yBreakStyleChanged(int styleIndex) { if (m_initializing) return; int index = ui.cbYBreak->currentIndex(); CartesianPlot::RangeBreakStyle style = CartesianPlot::RangeBreakStyle(styleIndex); CartesianPlot::RangeBreaks breaks = m_plot->yRangeBreaks(); breaks.list[index].style = style; breaks.lastChanged = index; for (auto* plot: m_plotList) plot->setYRangeBreaks(breaks); } // "Plot area"-tab void CartesianPlotDock::backgroundTypeChanged(int index) { PlotArea::BackgroundType type = (PlotArea::BackgroundType)index; if (type == PlotArea::Color) { ui.lBackgroundColorStyle->show(); ui.cbBackgroundColorStyle->show(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->hide(); ui.kleBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle) ui.cbBackgroundColorStyle->currentIndex(); if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); } } else if (type == PlotArea::Image) { ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->show(); ui.cbBackgroundImageStyle->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); ui.lBackgroundFileName->show(); ui.kleBackgroundFileName->show(); ui.bOpen->show(); ui.lBackgroundFirstColor->hide(); ui.kcbBackgroundFirstColor->hide(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else if (type == PlotArea::Pattern) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundColorStyle->hide(); ui.cbBackgroundColorStyle->hide(); ui.lBackgroundImageStyle->hide(); ui.cbBackgroundImageStyle->hide(); ui.lBackgroundBrushStyle->show(); ui.cbBackgroundBrushStyle->show(); ui.lBackgroundFileName->hide(); ui.kleBackgroundFileName->hide(); ui.bOpen->hide(); ui.lBackgroundFirstColor->show(); ui.kcbBackgroundFirstColor->show(); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundType(type); } void CartesianPlotDock::backgroundColorStyleChanged(int index) { PlotArea::BackgroundColorStyle style = (PlotArea::BackgroundColorStyle)index; if (style == PlotArea::SingleColor) { ui.lBackgroundFirstColor->setText(i18n("Color")); ui.lBackgroundSecondColor->hide(); ui.kcbBackgroundSecondColor->hide(); } else { ui.lBackgroundFirstColor->setText(i18n("First color")); ui.lBackgroundSecondColor->show(); ui.kcbBackgroundSecondColor->show(); ui.lBackgroundBrushStyle->hide(); ui.cbBackgroundBrushStyle->hide(); } if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundColorStyle(style); } void CartesianPlotDock::backgroundImageStyleChanged(int index) { if (m_initializing) return; PlotArea::BackgroundImageStyle style = (PlotArea::BackgroundImageStyle)index; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundImageStyle(style); } void CartesianPlotDock::backgroundBrushStyleChanged(int index) { if (m_initializing) return; Qt::BrushStyle style = (Qt::BrushStyle)index; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundBrushStyle(style); } void CartesianPlotDock::backgroundFirstColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFirstColor(c); } void CartesianPlotDock::backgroundSecondColorChanged(const QColor& c) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundSecondColor(c); } /*! opens a file dialog and lets the user select the image file. */ void CartesianPlotDock::selectFile() { KConfigGroup conf(KSharedConfig::openConfig(), "CartesianPlotDock"); QString dir = conf.readEntry("LastImageDir", ""); QString formats; for (const auto& format: QImageReader::supportedImageFormats()) { QString f = "*." + QString(format.constData()); formats.isEmpty() ? formats+=f : formats+=' '+f; } QString path = QFileDialog::getOpenFileName(this, i18n("Select the image file"), dir, i18n("Images (%1)", formats)); if (path.isEmpty()) return; //cancel was clicked in the file-dialog int pos = path.lastIndexOf(QDir::separator()); if (pos != -1) { QString newDir = path.left(pos); if (newDir != dir) conf.writeEntry("LastImageDir", newDir); } ui.kleBackgroundFileName->setText( path ); for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFileName(path); } void CartesianPlotDock::fileNameChanged() { if (m_initializing) return; QString fileName = ui.kleBackgroundFileName->text(); if (!fileName.isEmpty() && !QFile::exists(fileName)) ui.kleBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.kleBackgroundFileName->setStyleSheet(""); for (auto* plot: m_plotList) plot->plotArea()->setBackgroundFileName(fileName); } void CartesianPlotDock::backgroundOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot: m_plotList) plot->plotArea()->setBackgroundOpacity(opacity); } // "Border"-tab void CartesianPlotDock::borderStyleChanged(int index) { if (m_initializing) return; Qt::PenStyle penStyle = Qt::PenStyle(index); QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setStyle(penStyle); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderColorChanged(const QColor& color) { if (m_initializing) return; QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setColor(color); plot->plotArea()->setBorderPen(pen); } m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, color); m_initializing = false; } void CartesianPlotDock::borderWidthChanged(double value) { if (m_initializing) return; QPen pen; for (auto* plot: m_plotList) { pen = plot->plotArea()->borderPen(); pen.setWidthF( Worksheet::convertToSceneUnits(value, Worksheet::Point) ); plot->plotArea()->setBorderPen(pen); } } void CartesianPlotDock::borderCornerRadiusChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->plotArea()->setBorderCornerRadius(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotDock::borderOpacityChanged(int value) { if (m_initializing) return; qreal opacity = (float)value/100.; for (auto* plot: m_plotList) plot->plotArea()->setBorderOpacity(opacity); } void CartesianPlotDock::horizontalPaddingChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setHorizontalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } void CartesianPlotDock::verticalPaddingChanged(double value) { if (m_initializing) return; for (auto* plot: m_plotList) plot->setVerticalPadding(Worksheet::convertToSceneUnits(value, Worksheet::Centimeter)); } //************************************************************* //****** SLOTs for changes triggered in CartesianPlot ********* //************************************************************* //general void CartesianPlotDock::plotDescriptionChanged(const AbstractAspect* aspect) { if (m_plot != aspect) return; m_initializing = true; if (aspect->name() != ui.leName->text()) ui.leName->setText(aspect->name()); else if (aspect->comment() != ui.leComment->text()) ui.leComment->setText(aspect->comment()); m_initializing = false; } void CartesianPlotDock::plotRectChanged(QRectF& rect) { m_initializing = true; ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(rect.x(), Worksheet::Centimeter)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(rect.y(), Worksheet::Centimeter)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(rect.width(), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(rect.height(), Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotXAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleX->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotXMinChanged(float value) { m_initializing = true; ui.kleXMin->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotXMaxChanged(float value) { m_initializing = true; ui.kleXMax->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotXScaleChanged(int scale) { m_initializing = true; ui.cbXScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotYAutoScaleChanged(bool value) { m_initializing = true; ui.chkAutoScaleY->setChecked(value); m_initializing = false; } void CartesianPlotDock::plotYMinChanged(float value) { m_initializing = true; ui.kleYMin->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotYMaxChanged(float value) { m_initializing = true; ui.kleYMax->setText( QString::number(value) ); m_initializing = false; } void CartesianPlotDock::plotYScaleChanged(int scale) { m_initializing = true; ui.cbYScaling->setCurrentIndex( scale ); m_initializing = false; } void CartesianPlotDock::plotVisibleChanged(bool on) { m_initializing = true; ui.chkVisible->setChecked(on); m_initializing = false; } //range breaks void CartesianPlotDock::plotXRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkXBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotXRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } void CartesianPlotDock::plotYRangeBreakingEnabledChanged(bool on) { m_initializing = true; ui.chkYBreak->setChecked(on); m_initializing = false; } void CartesianPlotDock::plotYRangeBreaksChanged(const CartesianPlot::RangeBreaks& breaks) { Q_UNUSED(breaks); } //background void CartesianPlotDock::plotBackgroundTypeChanged(PlotArea::BackgroundType type) { m_initializing = true; ui.cbBackgroundType->setCurrentIndex(type); m_initializing = false; } void CartesianPlotDock::plotBackgroundColorStyleChanged(PlotArea::BackgroundColorStyle style) { m_initializing = true; ui.cbBackgroundColorStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundImageStyleChanged(PlotArea::BackgroundImageStyle style) { m_initializing = true; ui.cbBackgroundImageStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundBrushStyleChanged(Qt::BrushStyle style) { m_initializing = true; ui.cbBackgroundBrushStyle->setCurrentIndex(style); m_initializing = false; } void CartesianPlotDock::plotBackgroundFirstColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundFirstColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundSecondColorChanged(QColor& color) { m_initializing = true; ui.kcbBackgroundSecondColor->setColor(color); m_initializing = false; } void CartesianPlotDock::plotBackgroundFileNameChanged(QString& filename) { m_initializing = true; ui.kleBackgroundFileName->setText(filename); m_initializing = false; } void CartesianPlotDock::plotBackgroundOpacityChanged(float opacity) { m_initializing = true; ui.sbBackgroundOpacity->setValue( round(opacity*100.0) ); m_initializing = false; } void CartesianPlotDock::plotBorderPenChanged(QPen& pen) { m_initializing = true; if (ui.cbBorderStyle->currentIndex() != pen.style()) ui.cbBorderStyle->setCurrentIndex(pen.style()); if (ui.kcbBorderColor->color() != pen.color()) ui.kcbBorderColor->setColor(pen.color()); if (ui.sbBorderWidth->value() != pen.widthF()) ui.sbBorderWidth->setValue(Worksheet::convertFromSceneUnits(pen.widthF(),Worksheet::Point)); m_initializing = false; } void CartesianPlotDock::plotBorderCornerRadiusChanged(float value) { m_initializing = true; ui.sbBorderCornerRadius->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotBorderOpacityChanged(float value) { m_initializing = true; float v = (float)value*100.; ui.sbBorderOpacity->setValue(v); m_initializing = false; } void CartesianPlotDock::plotHorizontalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::plotVerticalPaddingChanged(float value) { m_initializing = true; ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(value, Worksheet::Centimeter)); m_initializing = false; } void CartesianPlotDock::rangeButtonClicked(QAbstractButton * button) { if (button == ui.rbRangesFirstN) { ui.leRangesFirstN->setEnabled(true); ui.leRangesLastN->setEnabled(false); - double min = INFINITY; - double max = -INFINITY; - - double val; - const int count = ui.leRangesFirstN->text().toInt(); - - for (auto* plot: m_plotList) { - for (const XYCurve* curve : plot->children()) { - if (curve->xColumn() != nullptr) { - val = curve->xColumn()->minimumFirst(count); - if (val < min) - min = val; - val = curve->xColumn()->maximumFirst(count); - if (val > max) - max = val; - } - } - } - - for (auto* plot: m_plotList) { - plot->setXMinMax(min, max); - } - + const int count = ui.leRangesFirstN->text().toInt(); + for(auto* plot : m_plotList) { + plot->setRangeType(CartesianPlot::RangeType::RangeFirst); + plot->setRangeFirstValues(count); + } } else if (button == ui.rbRangesLastN) { ui.leRangesFirstN->setEnabled(false); ui.leRangesLastN->setEnabled(true); - - double min = INFINITY; - double max = -INFINITY; - - double val; - const int count = ui.leRangesLastN->text().toInt(); - - for (auto* plot: m_plotList) { - for (const XYCurve* curve : plot->children()) { - if (curve->xColumn() != nullptr) { - val = curve->xColumn()->minimumLast(count); - if (val < min) - min = val; - val = curve->xColumn()->maximumLast(count); - if (val > max) - max = val; - } - } - } - - for (auto* plot: m_plotList) { - plot->setXMinMax(min, max); - } - + const int count = ui.leRangesLastN->text().toInt(); + for(auto* plot : m_plotList) { + plot->setRangeType(CartesianPlot::RangeType::RangeLast); + plot->setRangeLastValues(count); + } } else if (button == ui.rbRangesFree) { ui.leRangesFirstN->setEnabled(false); ui.leRangesLastN->setEnabled(false); - } } void CartesianPlotDock::rangeFirstNchanged(const QString & text) { Q_UNUSED(text) rangeButtonClicked(ui.rbRangesFirstN); } void CartesianPlotDock::rangeLastNchanged(const QString & text) { Q_UNUSED(text) rangeButtonClicked(ui.rbRangesLastN); } void CartesianPlotDock::plotXdataChanged() { if (ui.rbRangesFirstN->isChecked()) { rangeButtonClicked(ui.rbRangesFirstN); } else if (ui.rbRangesLastN->isChecked()) { rangeButtonClicked(ui.rbRangesLastN); } } //************************************************************* //******************** SETTINGS ******************************* //************************************************************* void CartesianPlotDock::loadConfigFromTemplate(KConfig& config) { //extract the name of the template from the file name QString name; int index = config.name().lastIndexOf(QDir::separator()); if (index != -1) name = config.name().right(config.name().size() - index - 1); else name = config.name(); int size = m_plotList.size(); if (size > 1) m_plot->beginMacro(i18n("%1 cartesian plots: template \"%2\" loaded", size, name)); else m_plot->beginMacro(i18n("%1: template \"%2\" loaded", m_plot->name(), name)); this->loadConfig(config); m_plot->endMacro(); } void CartesianPlotDock::load() { //General-tab ui.chkVisible->setChecked(m_plot->isVisible()); ui.sbLeft->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().x(), Worksheet::Centimeter)); ui.sbTop->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().y(), Worksheet::Centimeter)); ui.sbWidth->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().width(), Worksheet::Centimeter)); ui.sbHeight->setValue(Worksheet::convertFromSceneUnits(m_plot->rect().height(), Worksheet::Centimeter)); ui.chkAutoScaleX->setChecked(m_plot->autoScaleX()); ui.kleXMin->setText( QString::number(m_plot->xMin()) ); ui.kleXMax->setText( QString::number(m_plot->xMax()) ); ui.cbXScaling->setCurrentIndex( (int) m_plot->xScale() ); ui.chkAutoScaleY->setChecked(m_plot->autoScaleY()); ui.kleYMin->setText( QString::number(m_plot->yMin()) ); ui.kleYMax->setText( QString::number(m_plot->yMax()) ); ui.cbYScaling->setCurrentIndex( (int)m_plot->yScale() ); //Title labelWidget->load(); //x-range breaks, show the first break ui.chkXBreak->setChecked(m_plot->xRangeBreakingEnabled()); this->toggleXBreak(m_plot->xRangeBreakingEnabled()); ui.bRemoveXBreak->setVisible(m_plot->xRangeBreaks().list.size()>1); ui.cbXBreak->clear(); if (!m_plot->xRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->xRangeBreaks().list.size(); ++i) ui.cbXBreak->addItem(QString::number(i)); } else ui.cbXBreak->addItem("1"); ui.cbXBreak->setCurrentIndex(0); //y-range breaks, show the first break ui.chkYBreak->setChecked(m_plot->yRangeBreakingEnabled()); this->toggleYBreak(m_plot->yRangeBreakingEnabled()); ui.bRemoveYBreak->setVisible(m_plot->yRangeBreaks().list.size()>1); ui.cbYBreak->clear(); if (!m_plot->yRangeBreaks().list.isEmpty()) { for (int i = 1; i <= m_plot->yRangeBreaks().list.size(); ++i) ui.cbYBreak->addItem(QString::number(i)); } else ui.cbYBreak->addItem("1"); ui.cbYBreak->setCurrentIndex(0); //"Plot Area"-tab //Background ui.cbBackgroundType->setCurrentIndex( (int)m_plot->plotArea()->backgroundType() ); ui.cbBackgroundColorStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundColorStyle() ); ui.cbBackgroundImageStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundImageStyle() ); ui.cbBackgroundBrushStyle->setCurrentIndex( (int) m_plot->plotArea()->backgroundBrushStyle() ); ui.kleBackgroundFileName->setText( m_plot->plotArea()->backgroundFileName() ); ui.kcbBackgroundFirstColor->setColor( m_plot->plotArea()->backgroundFirstColor() ); ui.kcbBackgroundSecondColor->setColor( m_plot->plotArea()->backgroundSecondColor() ); ui.sbBackgroundOpacity->setValue( round(m_plot->plotArea()->backgroundOpacity()*100.0) ); //highlight the text field for the background image red if an image is used and cannot be found if (!m_plot->plotArea()->backgroundFileName().isEmpty() && !QFile::exists(m_plot->plotArea()->backgroundFileName())) ui.kleBackgroundFileName->setStyleSheet("QLineEdit{background:red;}"); else ui.kleBackgroundFileName->setStyleSheet(""); //Padding ui.sbPaddingHorizontal->setValue( Worksheet::convertFromSceneUnits(m_plot->horizontalPadding(), Worksheet::Centimeter) ); ui.sbPaddingVertical->setValue( Worksheet::convertFromSceneUnits(m_plot->verticalPadding(), Worksheet::Centimeter) ); //Border ui.kcbBorderColor->setColor( m_plot->plotArea()->borderPen().color() ); ui.cbBorderStyle->setCurrentIndex( (int) m_plot->plotArea()->borderPen().style() ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderPen().widthF(), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(m_plot->plotArea()->borderCornerRadius(), Worksheet::Centimeter) ); ui.sbBorderOpacity->setValue( round(m_plot->plotArea()->borderOpacity()*100) ); GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); } void CartesianPlotDock::loadConfig(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //This data is read in CartesianPlotDock::setPlots(). //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->loadConfig(plotTitleGroup); //Scale breakings //TODO //Background-tab ui.cbBackgroundType->setCurrentIndex( group.readEntry("BackgroundType", (int) m_plot->plotArea()->backgroundType()) ); ui.cbBackgroundColorStyle->setCurrentIndex( group.readEntry("BackgroundColorStyle", (int) m_plot->plotArea()->backgroundColorStyle()) ); ui.cbBackgroundImageStyle->setCurrentIndex( group.readEntry("BackgroundImageStyle", (int) m_plot->plotArea()->backgroundImageStyle()) ); ui.cbBackgroundBrushStyle->setCurrentIndex( group.readEntry("BackgroundBrushStyle", (int) m_plot->plotArea()->backgroundBrushStyle()) ); ui.kleBackgroundFileName->setText( group.readEntry("BackgroundFileName", m_plot->plotArea()->backgroundFileName()) ); ui.kcbBackgroundFirstColor->setColor( group.readEntry("BackgroundFirstColor", m_plot->plotArea()->backgroundFirstColor()) ); ui.kcbBackgroundSecondColor->setColor( group.readEntry("BackgroundSecondColor", m_plot->plotArea()->backgroundSecondColor()) ); ui.sbBackgroundOpacity->setValue( round(group.readEntry("BackgroundOpacity", m_plot->plotArea()->backgroundOpacity())*100.0) ); ui.sbPaddingHorizontal->setValue(Worksheet::convertFromSceneUnits(group.readEntry("HorizontalPadding", m_plot->horizontalPadding()), Worksheet::Centimeter)); ui.sbPaddingVertical->setValue(Worksheet::convertFromSceneUnits(group.readEntry("VerticalPadding", m_plot->verticalPadding()), Worksheet::Centimeter)); //Border-tab ui.kcbBorderColor->setColor( group.readEntry("BorderColor", m_plot->plotArea()->borderPen().color()) ); ui.cbBorderStyle->setCurrentIndex( group.readEntry("BorderStyle", (int) m_plot->plotArea()->borderPen().style()) ); ui.sbBorderWidth->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderWidth", m_plot->plotArea()->borderPen().widthF()), Worksheet::Point) ); ui.sbBorderCornerRadius->setValue( Worksheet::convertFromSceneUnits(group.readEntry("BorderCornerRadius", m_plot->plotArea()->borderCornerRadius()), Worksheet::Centimeter) ); ui.sbBorderOpacity->setValue( group.readEntry("BorderOpacity", m_plot->plotArea()->borderOpacity())*100 ); //Ranges ui.rbRangesFirstN->setChecked(group.readEntry("RangeFirstN", false)); ui.rbRangesLastN->setChecked(group.readEntry("RangeLastN", false)); ui.rbRangesFree->setChecked(group.readEntry("RangeFree", true)); m_initializing = true; GuiTools::updatePenStyles(ui.cbBorderStyle, ui.kcbBorderColor->color()); m_initializing = false; } void CartesianPlotDock::saveConfigAsTemplate(KConfig& config) { KConfigGroup group = config.group("CartesianPlot"); //General //we don't load/save the settings in the general-tab, since they are not style related. //It doesn't make sense to load/save them in the template. //Title KConfigGroup plotTitleGroup = config.group("CartesianPlotTitle"); labelWidget->saveConfig(plotTitleGroup); //Scale breakings //TODO //Background group.writeEntry("BackgroundType", ui.cbBackgroundType->currentIndex()); group.writeEntry("BackgroundColorStyle", ui.cbBackgroundColorStyle->currentIndex()); group.writeEntry("BackgroundImageStyle", ui.cbBackgroundImageStyle->currentIndex()); group.writeEntry("BackgroundBrushStyle", ui.cbBackgroundBrushStyle->currentIndex()); group.writeEntry("BackgroundFileName", ui.kleBackgroundFileName->text()); group.writeEntry("BackgroundFirstColor", ui.kcbBackgroundFirstColor->color()); group.writeEntry("BackgroundSecondColor", ui.kcbBackgroundSecondColor->color()); group.writeEntry("BackgroundOpacity", ui.sbBackgroundOpacity->value()/100.0); group.writeEntry("HorizontalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingHorizontal->value(), Worksheet::Centimeter)); group.writeEntry("VerticalPadding", Worksheet::convertToSceneUnits(ui.sbPaddingVertical->value(), Worksheet::Centimeter)); //Border group.writeEntry("BorderStyle", ui.cbBorderStyle->currentIndex()); group.writeEntry("BorderColor", ui.kcbBorderColor->color()); group.writeEntry("BorderWidth", Worksheet::convertToSceneUnits(ui.sbBorderWidth->value(), Worksheet::Point)); group.writeEntry("BorderCornerRadius", Worksheet::convertToSceneUnits(ui.sbBorderCornerRadius->value(), Worksheet::Centimeter)); group.writeEntry("BorderOpacity", ui.sbBorderOpacity->value()/100.0); //Ranges group.writeEntry("RangeFirstN", ui.rbRangesFirstN->isChecked()); group.writeEntry("RangeLastN", ui.rbRangesLastN->isChecked()); group.writeEntry("RangeFree", ui.rbRangesFree->isChecked()); config.sync(); } void CartesianPlotDock::loadTheme(const QString& theme) { for (auto* plot: m_plotList) plot->setTheme(theme); } void CartesianPlotDock::saveTheme(KConfig& config) const { if(!m_plotList.isEmpty()) m_plotList.at(0)->saveTheme(config); }