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,75 @@ 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 \param startIndex and \param endIndex, endIndex is excluded. + * If startIndex is greater than endIndex the indices are swapped + * \param startIndex + * \param endIndex + * \return + */ +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 +1322,6 @@ if (count == 0 && d->statisticsAvailable) max = const_cast(this)->statistics().maximum; else { - ColumnMode mode = columnMode(); int start, end; if (count == 0) { @@ -1309,48 +1334,75 @@ 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 \param startIndex and \param endIndex. + * If startIndex is greater than endIndex the indices are swapped + * \param startIndex + * \param endIndex + * \return + */ +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.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 @@ -710,8 +710,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(); @@ -968,15 +974,19 @@ 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"))); + } } 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"))); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetXScale, CartesianPlot::Scale, xScale, retransformScales) @@ -1044,15 +1054,19 @@ 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"))); + } } 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"))); + } } STD_SETTER_CMD_IMPL_F_S(CartesianPlot, SetYScale, CartesianPlot::Scale, yScale, retransformScales) @@ -1755,7 +1769,103 @@ bool CartesianPlot::scaleAutoX() { Q_D(CartesianPlot); if (d->curvesXMinMaxIsDirty) { - calculateCurvesXMinMax(); + 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; + + bool valueFound = false; + double min = d->curvesXMin; + double max = d->curvesXMax; + int start = 0; + int end = 0; + + //loop over all xy-curves and determine the maximum and minimum x-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + if (!curve->xColumn()) + continue; + + if (count == 0 && curve->yColumn()) { + 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 = curve->xColumn()->rowCount(); + break; + } + case CartesianPlot::RangeLast: { + start = curve->xColumn()->rowCount() - d->rangeLastValues; + end = curve->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; + + if (max > d->curvesXMax) + d->curvesXMax = max; + } + + //loop over all histograms and determine the maximum and minimum x-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + if (!curve->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) { + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesXMin = point.x(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesXMax = point.x(); + } + d->curvesYMinMaxIsDirty = true; d->curvesXMinMaxIsDirty = false; } @@ -1796,7 +1906,103 @@ Q_D(CartesianPlot); if (d->curvesYMinMaxIsDirty) { - calculateCurvesYMinMax(); + int count = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + count = 0; + break; + case CartesianPlot::RangeLast: + count = -d->rangeLastValues; + break; + case CartesianPlot::RangeFirst: + count = d->rangeFirstValues; + break; + } + + d->curvesYMin = INFINITY; + d->curvesYMax = -INFINITY; + + //loop over all xy-curves and determine the maximum and minimum y-values + for (const auto* curve : this->children()) { + if (!curve->isVisible()) + continue; + if (!curve->yColumn()) + continue; + + double min = d->curvesYMin; + double max = d->curvesYMax; + int start = 0; + int end = 0; + + if (count == 0 && curve->xColumn()) { + 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 = curve->yColumn()->rowCount(); + break; + } + case CartesianPlot::RangeLast: { + start = curve->yColumn()->rowCount() - d->rangeLastValues; + end = curve->yColumn()->rowCount(); + break; + } + case CartesianPlot::RangeFirst: { + start = 0; + end = d->rangeFirstValues; + break; + } + } + } + curve->minMaxY(end, 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 + 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) { + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesYMin = point.y(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesYMax = point.x(); + } + + d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = false; } @@ -1838,11 +2044,47 @@ 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) { + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMin, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesXMin = point.x(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(d->curvesXMax, 0), AbstractCoordinateSystem::SuppressPageClipping); + point.setX(point.x() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + 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) { + QPointF point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMin), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() + errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesYMin = point.y(); + + point = coordinateSystem()->mapLogicalToScene(QPointF(0, d->curvesYMax), AbstractCoordinateSystem::SuppressPageClipping); + point.setY(point.y() - errorBarsCapSize); + point = coordinateSystem()->mapSceneToLogical(point, AbstractCoordinateSystem::SuppressPageClipping); + d->curvesYMax = point.x(); + } d->curvesYMinMaxIsDirty = false; } @@ -1910,20 +2152,12 @@ return (updateX || updateY); } +/*! + * Calculates and sets curves y min and max. This function does not respect the range + * of the y axis + */ void CartesianPlot::calculateCurvesXMinMax() { 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; @@ -1937,73 +2171,32 @@ if (!xColumn) continue; - double min = xColumn->minimum(count); + double min = d->curvesXMin; + double max = d->curvesXMax; + + 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; + } + + 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 @@ -2023,24 +2216,20 @@ } } +/*! + * Calculates and sets curves y min and max. This function does not respect the range + * of the x axis + */ void CartesianPlot::calculateCurvesYMinMax() { 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()) @@ -2050,73 +2239,30 @@ if (!yColumn) continue; - double min = curve->yColumn()->minimum(count); + int start =0; + int end = 0; + switch (d->rangeType) { + case CartesianPlot::RangeFree: + start = 0; + end = yColumn->rowCount(); + break; + case CartesianPlot::RangeLast: + start = yColumn->rowCount() - d->rangeLastValues; + end = yColumn->rowCount(); + break; + case CartesianPlot::RangeFirst: + start = 0; + end = d->rangeFirstValues; + break; + } + + curve->minMaxY(start, end, min, max, true); + 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) { - case CartesianPlot::RangeFree: - start = 0; - end = yColumn->rowCount(); - break; - case CartesianPlot::RangeLast: - start = yColumn->rowCount() - d->rangeLastValues; - end = yColumn->rowCount(); - break; - case CartesianPlot::RangeFirst: - 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; - } - } - } } //loop over all histograms and determine the maximum y-value @@ -2945,6 +3091,15 @@ yMax = logicalZoomEnd.y(); } + if (mouseMode == CartesianPlot::ZoomSelectionMode) { + curvesXMinMaxIsDirty = true; + curvesYMinMaxIsDirty = true; + } else if (mouseMode == CartesianPlot::ZoomXSelectionMode) { + curvesYMinMaxIsDirty = true; + } else if (mouseMode == CartesianPlot::ZoomYSelectionMode) { + curvesXMinMaxIsDirty = true; + } + m_selectionBandIsShown = false; retransformScales(); } 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) const; + bool minMaxY(int indexMin, int indexMax, double& yMin, double& yMax, bool includeErrorBars) 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 @@ -2148,12 +2148,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 @@ -2168,10 +2172,10 @@ 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; @@ -2179,11 +2183,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; @@ -2195,10 +2199,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; @@ -2208,9 +2212,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; @@ -2229,12 +2233,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; } @@ -2247,11 +2252,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; @@ -2302,11 +2307,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; @@ -2372,11 +2377,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; @@ -2441,11 +2446,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; @@ -2471,6 +2476,102 @@ 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); +} + +/*! + * Index max is anymore included + * \param column + * \param errorType + * \param errorPlusColumn + * \param errorMinusColumn + * \param indexMin + * \param indexMax + * \param min + * \param max + * \param includeErrorBars + * \return + */ +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; + + double minTemp = INFINITY; + double maxTemp = -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 < minTemp) + minTemp = value - errorMinus; + + if (value + errorPlus > maxTemp) + maxTemp = value + errorPlus; + } + min = minTemp; + max = maxTemp; + return true; +} + /*! * \brief XYCurve::activateCurve * Checks if the mousepos distance to the curve is less than @p pow(maxDist,2)