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 @@ -161,7 +161,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 @@ -115,7 +115,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 @@ -1197,7 +1197,6 @@ if (count == 0 && d->statisticsAvailable) min = const_cast(this)->statistics().minimum; else { - ColumnMode mode = columnMode(); int start, end; if (count == 0) { @@ -1210,48 +1209,72 @@ start = qMax(rowCount() + count, 0); end = rowCount(); } + return minimum(start, end - 1); + } - 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. + * 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 (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) + std::swap(startIndex, endIndex); - if (val < min) - min = val; - } - break; + 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; @@ -1269,7 +1292,6 @@ if (count == 0 && d->statisticsAvailable) max = const_cast(this)->statistics().maximum; else { - ColumnMode mode = columnMode(); int start, end; if (count == 0) { @@ -1282,48 +1304,72 @@ start = qMax(rowCount() + count, 0); end = rowCount(); } + return maximum(start, end - 1); + } - 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; + ColumnMode mode = columnMode(); - 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(); + if (startIndex > endIndex && startIndex >= 0 && endIndex >= 0) + std::swap(startIndex, endIndex); - if (val > max) - max = val; - } - break; + 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 @@ -702,8 +702,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(); @@ -957,15 +963,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) @@ -1033,15 +1043,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) @@ -1691,6 +1705,10 @@ d->curvesXMin = INFINITY; d->curvesXMax = -INFINITY; + bool valueFound = false; + double min, max; + int indexMin, indexMax; + //loop over all xy-curves and determine the maximum and minimum x-values for (const auto* curve : this->children()) { if (!curve->isVisible()) @@ -1698,11 +1716,35 @@ if (!curve->xColumn()) continue; - const double min = curve->xColumn()->minimum(count); + if (count == 0) { + AbstractColumn::Properties propertie = curve->xColumn()->properties(); + switch (propertie) { + case AbstractColumn::Properties::MonotonicIncreasing: { + indexMin = curve->indexForX(yMin(), curve->yColumn()); + indexMax = curve->indexForX(yMax(), curve->yColumn()); + min = curve->xColumn()->minimum(indexMin, indexMax); + max = curve->xColumn()->maximum(indexMin, indexMax); + break; + } case AbstractColumn::Properties::MonotonicDecreasing: { + indexMin = curve->indexForX(yMax(), curve->yColumn()); + indexMax = curve->indexForX(yMin(), curve->yColumn()); + min = curve->xColumn()->minimum(indexMin, indexMax); + max = curve->xColumn()->maximum(indexMin, indexMax); + break; + } case AbstractColumn::Properties::Constant: + default: { + min = curve->xColumn()->minimum(count); + max = curve->xColumn()->maximum(count); + } + } + } else { + min = curve->xColumn()->minimum(count); + max = curve->xColumn()->maximum(count); + } + if (min < d->curvesXMin) d->curvesXMin = min; - const double max = curve->xColumn()->maximum(count); if (max > d->curvesXMax) d->curvesXMax = max; } @@ -1722,7 +1764,7 @@ if (max > d->curvesXMax) d->curvesXMax = max; } - + d->curvesYMinMaxIsDirty = true; d->curvesXMinMaxIsDirty = false; } @@ -1786,11 +1828,42 @@ if (!curve->yColumn()) continue; - const double min = curve->yColumn()->minimum(count); + bool valueFound = false; + double min, max; + int indexMin, indexMax; + + if (count == 0) { + AbstractColumn::Properties propertie = curve->yColumn()->properties(); + switch (propertie) { + case AbstractColumn::Properties::MonotonicIncreasing: { + indexMin = curve->indexForX(xMin()); + indexMax = curve->indexForX(xMax()); + min = curve->yColumn()->minimum(indexMin, indexMax); + max = curve->yColumn()->maximum(indexMin, indexMax); + break; + } case AbstractColumn::Properties::MonotonicDecreasing: { + indexMin = curve->indexForX(xMax()); + indexMax = curve->indexForX(xMin()); + min = curve->yColumn()->minimum(indexMin, indexMax); + max = curve->yColumn()->maximum(indexMin, indexMax); + break; + } case AbstractColumn::Properties::Constant: { + min = curve->yColumn()->minimum(count); + max = curve->yColumn()->maximum(count); + break; + } default: { + min = curve->yColumn()->minimum(count); + max = curve->yColumn()->maximum(count); + } + } + } else { + min = curve->yColumn()->minimum(count); + max = curve->yColumn()->maximum(count); + } + if (min < d->curvesYMin) d->curvesYMin = min; - const double max = curve->yColumn()->maximum(count); if (max > d->curvesYMax) d->curvesYMax = max; } @@ -1809,6 +1882,7 @@ d->curvesYMax = max; } + d->curvesXMinMaxIsDirty = true; d->curvesYMinMaxIsDirty = false; } @@ -2651,6 +2725,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,6 +70,7 @@ 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; 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 @@ -2132,12 +2132,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 @@ -2152,10 +2156,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 (abs(xColumn()->valueAt(lowerIndex) - x) < abs(xColumn()->valueAt(higherIndex) - x)) + if (abs(column->valueAt(lowerIndex) - x) < abs(column->valueAt(higherIndex) - x)) index = lowerIndex; else index = higherIndex; @@ -2179,10 +2183,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; @@ -2212,11 +2216,11 @@ 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); + double value = column->valueAt(row); if (abs(value - x) <= abs(prevValue - x)) { // <= prevents also that row - 1 become < 0 if (row < rowCount - 1) prevValue = value; @@ -2233,11 +2237,11 @@ xColumnMode == AbstractColumn::ColumnMode::Day)) { qint64 xInt64 = static_cast(x); 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 if (row < rowCount - 1) prevValueDateTime = value;