diff --git a/src/backend/core/AbstractColumn.h b/src/backend/core/AbstractColumn.h --- a/src/backend/core/AbstractColumn.h +++ b/src/backend/core/AbstractColumn.h @@ -162,7 +162,9 @@ virtual void clear(); virtual double maximum(int count = 0) const; + virtual double maximum(int startIndex, int endIndex) const; virtual double minimum(int count = 0) const; + virtual double minimum(int startIndex, int endIndex) const; bool isValid(int row) const; diff --git a/src/backend/core/AbstractColumn.cpp b/src/backend/core/AbstractColumn.cpp --- a/src/backend/core/AbstractColumn.cpp +++ b/src/backend/core/AbstractColumn.cpp @@ -555,11 +555,23 @@ return -INFINITY; } +double AbstractColumn::minimum(int startIndex, int endIndex) const { + Q_UNUSED(startIndex); + Q_UNUSED(endIndex); + return -INFINITY; +} + double AbstractColumn::maximum(int count) const { Q_UNUSED(count); return INFINITY; } +double AbstractColumn::maximum(int startIndex, int endIndex) const { + Q_UNUSED(startIndex); + Q_UNUSED(endIndex); + return INFINITY; +} + //////////////////////////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////////////////////////// diff --git a/src/backend/core/column/Column.h b/src/backend/core/column/Column.h --- a/src/backend/core/column/Column.h +++ b/src/backend/core/column/Column.h @@ -116,7 +116,9 @@ Properties properties() const override; double maximum(int count = 0) const override; + double maximum(int startIndex, int endIndex) const override; double minimum(int count = 0) const override; + double minimum(int startIndex, int endIndex) const override; void setChanged(); void setSuppressDataChangedSignal(const bool); diff --git a/src/backend/core/column/Column.cpp b/src/backend/core/column/Column.cpp --- a/src/backend/core/column/Column.cpp +++ b/src/backend/core/column/Column.cpp @@ -1224,7 +1224,6 @@ if (count == 0 && d->statisticsAvailable) min = const_cast(this)->statistics().minimum; else { - ColumnMode mode = columnMode(); int start, end; if (count == 0) { @@ -1237,48 +1236,74 @@ start = qMax(rowCount() + count, 0); end = rowCount(); } + return minimum(start, end); + } - switch (mode) { - case Numeric: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const double val = vec->at(row); - if (std::isnan(val)) - continue; + return min; +} - if (val < min) - min = val; - } - break; - } - case Integer: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const int val = vec->at(row); +/*! + * \brief Column::minimum + * Calculates the minimum value in the column between the \p startIndex and \p endIndex, endIndex is excluded. + * If startIndex is greater than endIndex the indices are swapped + * \p startIndex + * \p endIndex + */ +double Column::minimum(int startIndex, int endIndex) const { + double min = INFINITY; - if (val < min) - min = val; - } - break; - } - case Text: - break; - case DateTime: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const qint64 val = vec->at(row).toMSecsSinceEpoch(); + if (rowCount() == 0) + return min; - if (val < min) - min = val; - } - break; + if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) + std::swap(startIndex, endIndex); + + startIndex = qMax(startIndex, 0); + endIndex = qMax(endIndex, 0); + + startIndex = qMin(startIndex, rowCount() - 1); + endIndex = qMin(endIndex, rowCount() - 1); + + ColumnMode mode = columnMode(); + switch (mode) { + case Numeric: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const double val = vec->at(row); + if (std::isnan(val)) + continue; + + if (val < min) + min = val; } - case Day: - case Month: - default: - break; + break; + } + case Integer: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const int val = vec->at(row); + + if (val < min) + min = val; } + break; + } + case Text: + break; + case DateTime: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const qint64 val = vec->at(row).toMSecsSinceEpoch(); + if (val < min) + min = val; + } + break; + } + case Day: + case Month: + default: + break; } return min; @@ -1296,7 +1321,6 @@ if (count == 0 && d->statisticsAvailable) max = const_cast(this)->statistics().maximum; else { - ColumnMode mode = columnMode(); int start, end; if (count == 0) { @@ -1309,48 +1333,74 @@ start = qMax(rowCount() + count, 0); end = rowCount(); } + return maximum(start, end); + } - switch (mode) { - case Numeric: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const double val = vec->at(row); - if (std::isnan(val)) - continue; + return max; +} - if (val > max) - max = val; - } - break; - } - case Integer: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const int val = vec->at(row); +/*! + * \brief Column::maximum + * Calculates the maximum value in the column between the \p startIndex and \p endIndex. + * If startIndex is greater than endIndex the indices are swapped + * \p startIndex + * \p endIndex + */ +double Column::maximum(int startIndex, int endIndex) const { + double max = -INFINITY; + if (rowCount() == 0) + return max; - if (val > max) - max = val; - } - break; - } - case Text: - break; - case DateTime: { - auto* vec = static_cast*>(data()); - for (int row = start; row < end; ++row) { - const qint64 val = vec->at(row).toMSecsSinceEpoch(); + ColumnMode mode = columnMode(); - if (val > max) - max = val; - } - break; + if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) + std::swap(startIndex, endIndex); + + startIndex = qMax(startIndex, 0); + endIndex = qMax(endIndex, 0); + + startIndex = qMin(startIndex, rowCount() - 1); + endIndex = qMin(endIndex, rowCount() - 1); + + switch (mode) { + case Numeric: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const double val = vec->at(row); + if (std::isnan(val)) + continue; + + if (val > max) + max = val; } - case Day: - case Month: - default: - break; + break; + } + case Integer: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const int val = vec->at(row); + + if (val > max) + max = val; } + break; + } + case Text: + break; + case DateTime: { + auto* vec = static_cast*>(data()); + for (int row = startIndex; row < endIndex; ++row) { + const qint64 val = vec->at(row).toMSecsSinceEpoch(); + if (val > max) + max = val; + } + break; + } + case Day: + case Month: + default: + break; } return max; diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.h b/src/backend/worksheet/plots/cartesian/CartesianPlot.h --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.h +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.h @@ -161,8 +161,8 @@ void setColorPalette(const KConfig&); const XYCurve* currentCurve() const; - void calculateCurvesXMinMax(); - void calculateCurvesYMinMax(); + void calculateCurvesXMinMax(bool completeRange = true); + void calculateCurvesYMinMax(bool completeRange = true); CartesianPlotLegend* m_legend{nullptr}; double m_zoomFactor{1.2}; diff --git a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp --- a/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp +++ b/src/backend/worksheet/plots/cartesian/CartesianPlot.cpp @@ -712,8 +712,14 @@ } void CartesianPlot::navigate(CartesianPlot::NavigationOperation op) { - if (op == ScaleAuto) scaleAuto(); - else if (op == ScaleAutoX) scaleAutoX(); + Q_D(CartesianPlot); + if (op == ScaleAuto) { + if (d->curvesXMinMaxIsDirty || d->curvesYMinMaxIsDirty) { + d->curvesXMinMaxIsDirty = true; + d->curvesYMinMaxIsDirty = true; + } + scaleAuto(); + } else if (op == ScaleAutoX) scaleAutoX(); else if (op == ScaleAutoY) scaleAutoY(); else if (op == ZoomIn) zoomIn(); else if (op == ZoomOut) zoomOut(); @@ -970,15 +976,23 @@ STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMin, double, xMin, retransformScales) void CartesianPlot::setXMin(double xMin) { Q_D(CartesianPlot); - if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) + if (xMin != d->xMin && xMin != -INFINITY && xMin != INFINITY) { + d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMinCmd(d, xMin, ki18n("%1: set min x"))); + if (d->autoScaleY) + scaleAutoY(); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXMax, double, xMax, retransformScales) void CartesianPlot::setXMax(double xMax) { Q_D(CartesianPlot); - if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) + if (xMax != d->xMax && xMax != -INFINITY && xMax != INFINITY) { + d->curvesYMinMaxIsDirty = true; exec(new CartesianPlotSetXMaxCmd(d, xMax, ki18n("%1: set max x"))); + if (d->autoScaleY) + scaleAutoY(); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) @@ -1046,15 +1060,23 @@ STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMin, double, yMin, retransformScales) void CartesianPlot::setYMin(double yMin) { Q_D(CartesianPlot); - if (yMin != d->yMin) + if (yMin != d->yMin) { + d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMinCmd(d, yMin, ki18n("%1: set min y"))); + if (d->autoScaleX) + scaleAutoX(); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYMax, double, yMax, retransformScales) void CartesianPlot::setYMax(double yMax) { Q_D(CartesianPlot); - if (yMax != d->yMax) + if (yMax != d->yMax) { + d->curvesXMinMaxIsDirty = true; exec(new CartesianPlotSetYMaxCmd(d, yMax, ki18n("%1: set max y"))); + if (d->autoScaleX) + scaleAutoX(); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) @@ -1757,7 +1779,64 @@ bool CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { - calculateCurvesXMinMax(); + calculateCurvesXMinMax(false); + + //loop over all histograms and determine the maximum and minimum x-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + if (!curve->dataColumn()) + continue; + + const double min = curve->getXMinimum(); + if (min < d->curvesXMin) + d->curvesXMin = min; + + const double max = curve->getXMaximum(); + if (max > d->curvesXMax) + d->curvesXMax = max; + } + + // do it at the end, because it must be from the real min/max values + double errorBarsCapSize = -1; + for (auto* curve : this->children()) { + if (curve->yErrorType() != XYCurve::ErrorType::NoError) { + errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); + } + } + + if (errorBarsCapSize > 0) { + // must be done, because retransformScales uses xMin/xMax + if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) + d->xMin = d->curvesXMin; + + if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) + d->xMax = d->curvesXMax; + // When the previous scale is completely different. The mapTo functions scale with wrong values. To prevent + // this a rescale must be done. + // The errorBarsCapSize is in Scene coordinates. So this value must be transformed into a logical value. Due + // to nonlinear scalings it cannot only be multiplied with a scaling factor and depends on the position of the + // column value + // dirty hack: call setIsLoading(true) to suppress the call of retransform() in retransformScales() since a + // retransform is already done at the end of this function + setIsLoading(true); + d->retransformScales(); + setIsLoading(false); + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + // Problem is, when the scaling is not linear (for example log(x)) and the minimum is 0. In this + // case mapLogicalToScene returns (0,0) which is smaller than the curves minimum + if (point.x() < d->curvesXMin) + d->curvesXMin = point.x(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.x() > d->curvesXMax) + d->curvesXMax = point.x(); + } + d->curvesYMinMaxIsDirty = true; d->curvesXMinMaxIsDirty = false; } @@ -1798,7 +1877,53 @@ Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { - calculateCurvesYMinMax(); + calculateCurvesYMinMax(false); // loop over all curves + + //loop over all histograms and determine the maximum y-value + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + + const double min = curve->getYMinimum(); + if (d->curvesYMin > min) + d->curvesYMin = min; + + const double max = curve->getYMaximum(); + if (max > d->curvesYMax) + d->curvesYMax = max; + } + + // do it at the end, because it must be from the real min/max values + double errorBarsCapSize = -1; + for (auto* curve : this->children()) { + if (curve->xErrorType() != XYCurve::ErrorType::NoError) { + errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); + } + } + + if (errorBarsCapSize > 0) { + if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) + d->yMin = d->curvesYMin; + + if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) + d->yMax = d->curvesYMax; + setIsLoading(true); + d->retransformScales(); + setIsLoading(false); + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.y() < d->curvesYMin) + d->curvesYMin = point.y(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.y() > d->curvesYMax) + d->curvesYMax = point.y(); + } + + d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = false; } @@ -1840,11 +1965,67 @@ if (d->curvesXMinMaxIsDirty) { calculateCurvesXMinMax(); + double errorBarsCapSize = -1; + for (auto* curve : this->children()) { + if (curve->yErrorType() != XYCurve::ErrorType::NoError) { + errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); + } + } + + if (errorBarsCapSize > 0) { + if (d->curvesXMin != d->xMin && d->curvesXMin != INFINITY) + d->xMin = d->curvesXMin; + + if (d->curvesXMax != d->xMax && d->curvesXMax != -INFINITY) + d->xMax = d->curvesXMax; + setIsLoading(true); + d->retransformScales(); + setIsLoading(false); + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.x() < d->curvesXMin) + d->curvesXMin = point.x(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.x() > d->curvesXMax) + d->curvesXMax = point.x(); + } d->curvesXMinMaxIsDirty = false; } if (d->curvesYMinMaxIsDirty) { calculateCurvesYMinMax(); + double errorBarsCapSize = -1; + for (auto* curve : this->children()) { + if (curve->xErrorType() != XYCurve::ErrorType::NoError) { + errorBarsCapSize = qMax(errorBarsCapSize, curve->errorBarsCapSize()); + } + } + + if (errorBarsCapSize > 0) { + if (d->curvesYMin != d->yMin && d->curvesYMin != INFINITY) + d->yMin = d->curvesYMin; + + if (d->curvesYMax != d->yMax && d->curvesYMax != -INFINITY) + d->yMax = d->curvesYMax; + setIsLoading(true); + d->retransformScales(); + setIsLoading(false); + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.y() < d->curvesYMin) + d->curvesYMin = point.y(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + if (point.y() > d->curvesYMax) + d->curvesYMax = point.x(); + } d->curvesYMinMaxIsDirty = false; } @@ -1912,20 +2093,12 @@ return (updateX || updateY); } -void CartesianPlot::calculateCurvesXMinMax() { +/*! + * Calculates and sets curves y min and max. This function does not respect the range + * of the y axis + */ +void CartesianPlot::calculateCurvesXMinMax(bool completeRange) { Q_D(CartesianPlot); - int count = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - count = 0; - break; - case CartesianPlot::RangeLast: - count = -d->rangeLastValues; - break; - case CartesianPlot::RangeFirst: - count = d->rangeFirstValues; - break; - } d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; @@ -1939,73 +2112,40 @@ if (!xColumn) continue; - double min = xColumn->minimum(count); + double min = d->curvesXMin; + double max = d->curvesXMax; + + int start =0; + int end = 0; + if (d->rangeType == CartesianPlot::RangeFree && curve->yColumn() + && !completeRange) { + start = curve->indexForX(yMin(), curve->yColumn()); + end = curve->indexForX(yMax(), curve->yColumn()); + if (end < curve->yColumn()->rowCount()) + end ++; + } else { + switch (d->rangeType) { + case CartesianPlot::RangeFree: + start = 0; + end = xColumn->rowCount(); + break; + case CartesianPlot::RangeLast: + start = xColumn->rowCount() - d->rangeLastValues; + end = xColumn->rowCount(); + break; + case CartesianPlot::RangeFirst: + start = 0; + end = d->rangeFirstValues; + break; + } + } + + curve->minMaxX(start, end, min, max, true); if (min < d->curvesXMin) d->curvesXMin = min; - double max = xColumn->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; - - //take error bars into account - auto xErrorType = curve->xErrorType(); - if (xErrorType != XYCurve::NoError) { - //consider error bars only if error columns are set - auto* xErrorPlusColumn = curve->xErrorPlusColumn(); - auto* xErrorMinusColumn = curve->xErrorMinusColumn(); - if ( (xErrorType == XYCurve::SymmetricError && xErrorPlusColumn) - || (xErrorType == XYCurve::AsymmetricError && (xErrorPlusColumn || xErrorMinusColumn)) ) { - - int start =0; - int end = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - start = 0; - end = xColumn->rowCount(); - break; - case CartesianPlot::RangeLast: - start = xColumn->rowCount() - d->rangeLastValues; - end = xColumn->rowCount(); - break; - case CartesianPlot::RangeFirst: - start = 0; - end = d->rangeFirstValues; - break; - } - for (int i = start; i < end; ++i) { - if (!xColumn->isValid(i) || xColumn->isMasked(i)) - continue; - - if ( (xErrorPlusColumn && i >= xErrorPlusColumn->rowCount()) - || (xErrorMinusColumn && i >= xErrorMinusColumn->rowCount()) ) - continue; - - //determine the values for the errors - double errorPlus, errorMinus; - if (xErrorPlusColumn && xErrorPlusColumn->isValid(i) && !xErrorPlusColumn->isMasked(i)) - errorPlus = xErrorPlusColumn->valueAt(i); - else - errorPlus = 0; - - if (xErrorType == XYCurve::SymmetricError) - errorMinus = errorPlus; - else { - if (xErrorMinusColumn && xErrorMinusColumn->isValid(i) && !xErrorMinusColumn->isMasked(i)) - errorMinus = xErrorMinusColumn->valueAt(i); - else - errorMinus = 0; - } - - min = xColumn->valueAt(i) - errorMinus; - if (min < d->curvesXMin) - d->curvesXMin = min; - - max = xColumn->valueAt(i) + errorPlus; - if (max > d->curvesXMax) - d->curvesXMax = max; - } - } - } } //loop over all histograms and determine the maximum and minimum x-values @@ -2025,24 +2165,20 @@ } } -void CartesianPlot::calculateCurvesYMinMax() { +/*! + * Calculates and sets curves y min and max. This function does not respect the range + * of the x axis + */ +void CartesianPlot::calculateCurvesYMinMax(bool completeRange) { Q_D(CartesianPlot); - int count = 0; - switch (d->rangeType) { - case CartesianPlot::RangeFree: - count = 0; - break; - case CartesianPlot::RangeLast: - count = -d->rangeLastValues; - break; - case CartesianPlot::RangeFirst: - count = d->rangeFirstValues; - break; - } d->curvesYMin = INFINITY; d->curvesYMax = -INFINITY; + double min = d->curvesYMin; + double max = d->curvesYMax; + + //loop over all xy-curves and determine the maximum and minimum y-values for (const auto* curve : this->children()) { if (!curve->isVisible()) @@ -2052,26 +2188,16 @@ if (!yColumn) continue; - double min = curve->yColumn()->minimum(count); - if (min < d->curvesYMin) - d->curvesYMin = min; - - double max = curve->yColumn()->maximum(count); - if (max > d->curvesYMax) - d->curvesYMax = max; - - //take error bars into account - auto yErrorType = curve->yErrorType(); - if (yErrorType != XYCurve::NoError) { - //consider error bars only if error columns are set - auto* yErrorPlusColumn = curve->yErrorPlusColumn(); - auto* yErrorMinusColumn = curve->yErrorMinusColumn(); - if ( (yErrorType == XYCurve::SymmetricError && yErrorPlusColumn) - || (yErrorType == XYCurve::AsymmetricError && (yErrorPlusColumn || yErrorMinusColumn)) ) { - - int start =0; - int end = 0; - switch (d->rangeType) { + int start =0; + int end = 0; + if (d->rangeType == CartesianPlot::RangeFree && curve->xColumn() && + !completeRange) { + start = curve->indexForX(xMin(), curve->xColumn()); + end = curve->indexForX(xMax(), curve->xColumn()); + if (end < curve->xColumn()->rowCount()) + end ++; // because minMaxY excludes indexMax + } else { + switch (d->rangeType) { case CartesianPlot::RangeFree: start = 0; end = yColumn->rowCount(); @@ -2084,41 +2210,16 @@ start = 0; end = d->rangeFirstValues; break; - } - for (int i = start; i < end; ++i) { - if (!yColumn->isValid(i) || yColumn->isMasked(i)) - continue; - - if ( (yErrorPlusColumn && i >= yErrorPlusColumn->rowCount()) - || (yErrorMinusColumn && i >= yErrorMinusColumn->rowCount()) ) - continue; - - //determine the values for the errors - double errorPlus, errorMinus; - if (yErrorPlusColumn && yErrorPlusColumn->isValid(i) && !yErrorPlusColumn->isMasked(i)) - errorPlus = yErrorPlusColumn->valueAt(i); - else - errorPlus = 0; - - if (yErrorType == XYCurve::SymmetricError) - errorMinus = errorPlus; - else { - if (yErrorMinusColumn && yErrorMinusColumn->isValid(i) && !yErrorMinusColumn->isMasked(i)) - errorMinus = yErrorMinusColumn->valueAt(i); - else - errorMinus = 0; - } - - min = yColumn->valueAt(i) - errorMinus; - if (min < d->curvesYMin) - d->curvesYMin = min; - - max = yColumn->valueAt(i) + errorPlus; - if (max > d->curvesYMax) - d->curvesYMax = max; - } } } + + curve->minMaxY(start, end, min, max, true); + + if (min < d->curvesYMin) + d->curvesYMin = min; + + if (max > d->curvesYMax) + d->curvesYMax = max; } //loop over all histograms and determine the maximum y-value @@ -2145,6 +2246,8 @@ setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); + d->curvesXMinMaxIsDirty = true; + d->curvesYMinMaxIsDirty = true; double oldRange = (d->xMax - d->xMin); double newRange = (d->xMax - d->xMin) / m_zoomFactor; d->xMax = d->xMax + (newRange - oldRange) / 2; @@ -2165,6 +2268,8 @@ setAutoScaleX(false); setAutoScaleY(false); setUndoAware(true); + d->curvesXMinMaxIsDirty = true; + d->curvesYMinMaxIsDirty = true; double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; @@ -2181,11 +2286,19 @@ void CartesianPlot::zoomInX() { Q_D(CartesianPlot); + setUndoAware(false); setAutoScaleX(false); + setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)/m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; + + if (d->autoScaleY) { + autoScaleY(); + return; + } d->retransformScales(); } @@ -2195,10 +2308,16 @@ setUndoAware(false); setAutoScaleX(false); setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double oldRange = (d->xMax-d->xMin); double newRange = (d->xMax-d->xMin)*m_zoomFactor; d->xMax = d->xMax + (newRange-oldRange)/2; d->xMin = d->xMin - (newRange-oldRange)/2; + + if (d->autoScaleY) { + autoScaleY(); + return; + } d->retransformScales(); } @@ -2208,10 +2327,16 @@ setUndoAware(false); setAutoScaleY(false); setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)/m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; + + if (d->autoScaleX) { + autoScaleX(); + return; + } d->retransformScales(); } @@ -2221,10 +2346,17 @@ setUndoAware(false); setAutoScaleY(false); setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double oldRange = (d->yMax-d->yMin); double newRange = (d->yMax-d->yMin)*m_zoomFactor; d->yMax = d->yMax + (newRange-oldRange)/2; d->yMin = d->yMin - (newRange-oldRange)/2; + + if (d->autoScaleX) { + autoScaleX(); + return; + } + d->retransformScales(); } @@ -2234,9 +2366,16 @@ setUndoAware(false); setAutoScaleX(false); setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double offsetX = (d->xMax-d->xMin)*0.1; d->xMax -= offsetX; d->xMin -= offsetX; + + if (d->autoScaleY) { + scaleAutoY(); + return; + } + d->retransformScales(); } @@ -2246,9 +2385,16 @@ setUndoAware(false); setAutoScaleX(false); setUndoAware(true); + d->curvesYMinMaxIsDirty = true; double offsetX = (d->xMax-d->xMin)*0.1; d->xMax += offsetX; d->xMin += offsetX; + + if (d->autoScaleY) { + scaleAutoY(); + return; + } + d->retransformScales(); } @@ -2258,9 +2404,16 @@ setUndoAware(false); setAutoScaleY(false); setUndoAware(true); + d->curvesXMinMaxIsDirty = true; double offsetY = (d->yMax-d->yMin)*0.1; d->yMax += offsetY; d->yMin += offsetY; + + if (d->autoScaleX) { + scaleAutoX(); + return; + } + d->retransformScales(); } @@ -2270,9 +2423,16 @@ setUndoAware(false); setAutoScaleY(false); setUndoAware(true); + d->curvesXMinMaxIsDirty = true; double offsetY = (d->yMax-d->yMin)*0.1; d->yMax -= offsetY; d->yMin -= offsetY; + + if (d->autoScaleX) { + scaleAutoX(); + return; + } + d->retransformScales(); } @@ -2918,6 +3078,7 @@ m_selectionBandIsShown = false; return; } + bool retransformPlot = true; //determine the new plot ranges QPointF logicalZoomStart = cSystem->mapSceneToLogical(m_selectionStart, AbstractCoordinateSystem::SuppressPageClipping); @@ -2938,8 +3099,31 @@ yMax = logicalZoomEnd.y(); } + if (mouseMode == CartesianPlot::ZoomSelectionMode) { + curvesXMinMaxIsDirty = true; + curvesYMinMaxIsDirty = true; + q->setAutoScaleX(false); + q->setAutoScaleY(false); + } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { + curvesYMinMaxIsDirty = true; + q->setAutoScaleX(false); + if (q->autoScaleY()) { + retransformPlot = false; + q->scaleAutoY(); + } + } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { + curvesXMinMaxIsDirty = true; + q->setAutoScaleY(false); + if (q->autoScaleX()) { + retransformPlot = false; + q->scaleAutoX(); + } + } + + if (retransformPlot) + retransformScales(); + m_selectionBandIsShown = false; - retransformScales(); } void CartesianPlotPrivate::wheelEvent(QGraphicsSceneWheelEvent* event) { diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.h b/src/backend/worksheet/plots/cartesian/XYCurve.h --- a/src/backend/worksheet/plots/cartesian/XYCurve.h +++ b/src/backend/worksheet/plots/cartesian/XYCurve.h @@ -70,10 +70,13 @@ double y(double x, bool &valueFound) const; QDateTime yDateTime(double x, bool &valueFound) const; int indexForX(double x) const; + int indexForX(double x, const AbstractColumn *column) const; int indexForX(double x, QVector& column, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; int indexForX(const double x, const QVector &column, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; int indexForX(double x, QVector& lines, AbstractColumn::Properties properties = AbstractColumn::Properties::No) const; - + bool minMax(const AbstractColumn *column, const ErrorType errorType, const AbstractColumn *errorPlusColumn, const AbstractColumn *errorMinusColumn, int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars) const; + bool minMaxX(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars = true) const; + bool minMaxY(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars = true) const; bool activateCurve(QPointF mouseScenePos, double maxDist = -1); void setHover(bool on); diff --git a/src/backend/worksheet/plots/cartesian/XYCurve.cpp b/src/backend/worksheet/plots/cartesian/XYCurve.cpp --- a/src/backend/worksheet/plots/cartesian/XYCurve.cpp +++ b/src/backend/worksheet/plots/cartesian/XYCurve.cpp @@ -2140,12 +2140,16 @@ * @return -1 if index not found, otherwise the index */ int XYCurve::indexForX(double x) const { - int rowCount = xColumn()->rowCount(); + return indexForX(x, xColumn()); +} + +int XYCurve::indexForX(double x, const AbstractColumn* column) const { + int rowCount = column->rowCount(); double prevValue = 0; qint64 prevValueDateTime = 0; - AbstractColumn::ColumnMode xColumnMode = xColumn()->columnMode(); - int properties = xColumn()->properties(); + AbstractColumn::ColumnMode xColumnMode = column->columnMode(); + int properties = column->properties(); if (properties == AbstractColumn::Properties::MonotonicIncreasing || properties == AbstractColumn::Properties::MonotonicDecreasing) { // bisects the index every time, so it is possible to find the value in log_2(rowCount) steps @@ -2160,22 +2164,22 @@ xColumnMode == AbstractColumn::ColumnMode::Integer)) { for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); - double value = xColumn()->valueAt(index); + double value = column->valueAt(index); if (higherIndex - lowerIndex < 2) { - if (qAbs(xColumn()->valueAt(lowerIndex) - x) < qAbs(xColumn()->valueAt(higherIndex) - x)) + if (qAbs(column->valueAt(lowerIndex) - x) < qAbs(column->valueAt(higherIndex) - x)) index = lowerIndex; else index = higherIndex; return index; } - if (value >= x && increase) + if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; - else if (value < x && increase) + else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; @@ -2187,10 +2191,10 @@ qint64 xInt64 = static_cast(x); for (unsigned int i = 0; i < maxSteps; i++) { // so no log_2(rowCount) needed int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); - qint64 value = xColumn()->dateTimeAt(index).toMSecsSinceEpoch(); + qint64 value = column->dateTimeAt(index).toMSecsSinceEpoch(); if (higherIndex - lowerIndex < 2) { - if (abs(xColumn()->dateTimeAt(lowerIndex).toMSecsSinceEpoch() - xInt64) < abs(xColumn()->dateTimeAt(higherIndex).toMSecsSinceEpoch() - xInt64)) + if (abs(column->dateTimeAt(lowerIndex).toMSecsSinceEpoch() - xInt64) < abs(column->dateTimeAt(higherIndex).toMSecsSinceEpoch() - xInt64)) index = lowerIndex; else index = higherIndex; @@ -2200,9 +2204,9 @@ if (value > xInt64 && increase) higherIndex = index; - else if (value > xInt64 && !increase) + else if (value >= xInt64 && !increase) lowerIndex = index; - else if (value < xInt64 && increase) + else if (value <= xInt64 && increase) lowerIndex = index; else if (value < xInt64 && !increase) higherIndex = index; @@ -2221,12 +2225,13 @@ if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || xColumnMode == AbstractColumn::ColumnMode::Integer)) { for (int row = 0; row < rowCount; row++) { - if (xColumn()->isValid(row)) { + if (column->isValid(row)) { if (row == 0) - prevValue = xColumn()->valueAt(row); + prevValue = column->valueAt(row); double value = xColumn()->valueAt(row); - if (qAbs(value - x) <= qAbs(prevValue - x)) { // <= prevents also that row - 1 become < 0 + if (abs(value - x) <= abs(prevValue - x)) { // <= prevents also that row - 1 become < 0 + if (row < rowCount - 1) prevValue = value; index = row; } @@ -2239,11 +2244,11 @@ qint64 xInt64 = static_cast(x); int index = 0; for (int row = 0; row < rowCount; row++) { - if (xColumn()->isValid(row)) { + if (column->isValid(row)) { if (row == 0) - prevValueDateTime = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); + prevValueDateTime = column->dateTimeAt(row).toMSecsSinceEpoch(); - qint64 value = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); + qint64 value = column->dateTimeAt(row).toMSecsSinceEpoch(); if (abs(value - xInt64) <= abs(prevValueDateTime - xInt64)) { // "<=" prevents also that row - 1 become < 0 prevValueDateTime = value; index = row; @@ -2294,11 +2299,11 @@ return index; } - if (value >= x && increase) + if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; - else if (value < x && increase) + else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; @@ -2364,11 +2369,11 @@ return index; } - if (value >= x && increase) + if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; - else if (value < x && increase) + else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; @@ -2433,11 +2438,11 @@ return index; } - if (value >= x && increase) + if (value > x && increase) higherIndex = index; else if (value >= x && !increase) lowerIndex = index; - else if (value < x && increase) + else if (value <= x && increase) lowerIndex = index; else if (value < x && !increase) higherIndex = index; @@ -2463,6 +2468,100 @@ return -1; } +bool XYCurve::minMaxY(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars) const { + return minMax(yColumn(), yErrorType(), yErrorPlusColumn(), yErrorMinusColumn(), indexMin, indexMax, yMin, yMax, includeErrorBars); +} + +bool XYCurve::minMaxX(int indexMin, int indexMax, double& xMin, double& xMax, bool includeErrorBars) const { + return minMax(xColumn(), xErrorType(), xErrorPlusColumn(), xErrorMinusColumn(), indexMin, indexMax, xMin, xMax, includeErrorBars); +} + +/*! + * Calculates the minimum \p min and maximum \p max of a curve with optionally respecting the error bars + * \p indexMax is not included + * \p column + * \p errorType + * \p errorPlusColumn + * \p errorMinusColumn + * \p indexMin + * \p indexMax + * \p min + * \p max + * \ includeErrorBars If true respect the error bars in the min/max calculation + */ +bool XYCurve::minMax(const AbstractColumn* column, const ErrorType errorType, const AbstractColumn* errorPlusColumn, const AbstractColumn* errorMinusColumn, int indexMin, int indexMax, double& min, double& max, bool includeErrorBars) const { + if (!includeErrorBars || errorType == XYCurve::NoError) { + min = column->minimum(indexMin, indexMax); + max = column->maximum(indexMin, indexMax); + return true; + } + + if (column->rowCount() == 0) + return false; + + min = INFINITY; + max = -INFINITY; + + for (int i = indexMin; i < indexMax; ++i) { + if (!column->isValid(i) || column->isMasked(i)) + continue; + + if ( (errorPlusColumn && i >= errorPlusColumn->rowCount()) + || (errorMinusColumn && i >= errorMinusColumn->rowCount()) ) + continue; + + //determine the values for the errors + double errorPlus, errorMinus; + if (errorPlusColumn && errorPlusColumn->isValid(i) && !errorPlusColumn->isMasked(i)) + if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || + errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Integer) + errorPlus = errorPlusColumn->valueAt(i); + else if (errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || + errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Month || + errorPlusColumn->columnMode() == AbstractColumn::ColumnMode::Day) + errorPlus = errorPlusColumn->dateTimeAt(i).toMSecsSinceEpoch(); + else + return false; + else + errorPlus = 0; + + if (errorType == XYCurve::SymmetricError) + errorMinus = errorPlus; + else { + if (errorMinusColumn && errorMinusColumn->isValid(i) && !errorMinusColumn->isMasked(i)) + if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Numeric || + errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Integer) + errorMinus = errorMinusColumn->valueAt(i); + else if (errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::DateTime || + errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Month || + errorMinusColumn->columnMode() == AbstractColumn::ColumnMode::Day) + errorMinus = errorMinusColumn->dateTimeAt(i).toMSecsSinceEpoch(); + else + return false; + else + errorMinus = 0; + } + + double value; + if (column->columnMode() == AbstractColumn::ColumnMode::Numeric || + column->columnMode() == AbstractColumn::ColumnMode::Integer) + value = column->valueAt(i); + else if (column->columnMode() == AbstractColumn::ColumnMode::DateTime || + column->columnMode() == AbstractColumn::ColumnMode::Month || + column->columnMode() == AbstractColumn::ColumnMode::Day) { + value = column->dateTimeAt(i).toMSecsSinceEpoch(); + } else + return false; + + if (value - errorMinus < min) + min = value - errorMinus; + + if (value + errorPlus > max) + max = value + errorPlus; + } + return true; +} + /*! * \brief XYCurve::activateCurve * Checks if the mousepos distance to the curve is less than @p pow(maxDist,2)