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 @@ -91,6 +91,13 @@ // QMatrix // etc. }; + enum Properties { + No = 0x00, + Constant = 0x01, + MonotonicIncreasing = 0x02, // prev_value >= value for all values in column + MonotonicDecreasing = 0x04 // prev_value <= value for all values in column + // add new values with next bit set (0x08) + }; struct ColumnStatistics { ColumnStatistics() { @@ -187,6 +194,8 @@ virtual int integerAt(int row) const; virtual void setIntegerAt(int row, int new_value); virtual void replaceInteger(int first, const QVector& new_values); + virtual int properties() const; + virtual void updateProperties(); signals: void plotDesignationAboutToChange(const AbstractColumn* source); 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 @@ -560,6 +560,23 @@ Q_UNUSED(first) Q_UNUSED(new_values) } +/** + * @brief AbstractColumn::properties + * Returns the properties hold by this column (no, monotonic increasing, monotonic decreasing,...) + * Will be used in XYCurve to improve the search velocity for the y value for a specific x value + * @return + */ +int AbstractColumn::properties() const { + return AbstractColumn::Properties::No; +} + +/** + * @brief AbstractColumn::updateProperties + * updates the properties AbstractColumn::Properties + */ +void AbstractColumn::updateProperties() { +} + /**********************************************************************/ double AbstractColumn::minimum(int count) const { Q_UNUSED(count); 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 @@ -104,6 +104,8 @@ int integerAt(int) const override; void setIntegerAt(int, int) override; void replaceInteger(int, const QVector&) override; + int properties() const override; + void updateProperties() override; double maximum(int count = 0) const override; double minimum(int count = 0) const override; 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 @@ -243,6 +243,7 @@ d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; } /** @@ -256,6 +257,7 @@ d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; } /** @@ -355,6 +357,7 @@ void Column::setTextAt(int row, const QString& new_value) { DEBUG("Column::setTextAt()"); d->statisticsAvailable = false;; + d->propertiesAvailable = false; exec(new ColumnSetTextCmd(d, row, new_value)); } @@ -367,6 +370,7 @@ DEBUG("Column::replaceTexts()"); if (!new_values.isEmpty()) { //TODO: do we really need this check? d->statisticsAvailable = false;; + d->propertiesAvailable = false; exec(new ColumnReplaceTextsCmd(d, first, new_values)); } } @@ -378,6 +382,7 @@ */ void Column::setDateAt(int row, QDate new_value) { d->statisticsAvailable = false;; + d->propertiesAvailable = false; setDateTimeAt(row, QDateTime(new_value, timeAt(row))); } @@ -388,6 +393,7 @@ */ void Column::setTimeAt(int row, QTime new_value) { d->statisticsAvailable = false;; + d->propertiesAvailable = false; setDateTimeAt(row, QDateTime(dateAt(row), new_value)); } @@ -398,6 +404,7 @@ */ void Column::setDateTimeAt(int row, const QDateTime& new_value) { d->statisticsAvailable = false;; + d->propertiesAvailable = false; exec(new ColumnSetDateTimeCmd(d, row, new_value)); } @@ -409,6 +416,7 @@ void Column::replaceDateTimes(int first, const QVector& new_values) { if (!new_values.isEmpty()) { d->statisticsAvailable = false;; + d->propertiesAvailable = false; exec(new ColumnReplaceDateTimesCmd(d, first, new_values)); } } @@ -422,6 +430,7 @@ // DEBUG("Column::setValueAt()"); d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetValueCmd(d, row, new_value)); } @@ -435,6 +444,7 @@ if (!new_values.isEmpty()) { d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceValuesCmd(d, first, new_values)); } } @@ -448,6 +458,7 @@ DEBUG("Column::setIntegerAt()"); d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnSetIntegerCmd(d, row, new_value)); } @@ -461,9 +472,30 @@ if (!new_values.isEmpty()) { d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; exec(new ColumnReplaceIntegersCmd(d, first, new_values)); } } +/*! + * \brief Column::properties + * Returns the column properties of this curve (monoton increasing, monoton decreasing, ... ) + * See AbstractColumn::properties + * \return + */ +int Column::properties() const{ + if (!d->propertiesAvailable) + d->updateProperties(); + + return d->properties; +} + +/*! + * \brief Column::updateProperties + * Rechecking the properties of the column + */ +void Column::updateProperties(){ + d->updateProperties(); +} const Column::ColumnStatistics& Column::statistics() const { if (!d->statisticsAvailable) @@ -573,6 +605,7 @@ } statistics.entropy = -entropy; + d->statisticsAvailable = true; } @@ -665,11 +698,15 @@ * This is used e.g. in \c XYFitCurvePrivate::recalculate() */ void Column::setChanged() { + + d->propertiesAvailable = false; + if (!m_suppressDataChangedSignal) emit dataChanged(this); d->statisticsAvailable = false;; d->hasValuesAvailable = false; + } //////////////////////////////////////////////////////////////////////////////// @@ -1088,6 +1125,7 @@ d->statisticsAvailable = false;; d->hasValuesAvailable = false; + d->propertiesAvailable = false; DEBUG("Column::handleFormatChange() DONE"); } diff --git a/src/backend/core/column/ColumnPrivate.h b/src/backend/core/column/ColumnPrivate.h --- a/src/backend/core/column/ColumnPrivate.h +++ b/src/backend/core/column/ColumnPrivate.h @@ -105,12 +105,17 @@ void setIntegerAt(int row, int new_value); void replaceInteger(int first, const QVector&); + void updateProperties(); + mutable AbstractColumn::ColumnStatistics statistics; bool statisticsAvailable; //is 'statistics' already available or needs to be (re-)calculated? bool hasValues; bool hasValuesAvailable; //is 'hasValues' already available or needs to be (re-)calculated? + mutable bool propertiesAvailable; //is 'properties' already available (true) or needs to be (re-)calculated (false)? + mutable int properties; // declares the properties of the curve (monotonic increasing/decreasing ...). Speed up algorithms + private: AbstractColumn::ColumnMode m_column_mode; // type of column data void* m_data; //pointer to the data container (QVector) diff --git a/src/backend/core/column/ColumnPrivate.cpp b/src/backend/core/column/ColumnPrivate.cpp --- a/src/backend/core/column/ColumnPrivate.cpp +++ b/src/backend/core/column/ColumnPrivate.cpp @@ -39,7 +39,9 @@ m_column_mode(mode), m_plot_designation(AbstractColumn::NoDesignation), m_width(0), - m_owner(owner) { + m_owner(owner), + propertiesAvailable(false), + properties(AbstractColumn::Properties::No){ Q_ASSERT(owner != nullptr); switch(mode) { @@ -94,7 +96,9 @@ m_data(data), m_plot_designation(AbstractColumn::NoDesignation), m_width(0), - m_owner(owner) { + m_owner(owner), + propertiesAvailable(false), + properties(AbstractColumn::Properties::No){ switch(mode) { case AbstractColumn::Numeric: @@ -588,7 +592,6 @@ if (!m_owner->m_suppressDataChangedSignal) emit m_owner->dataChanged(m_owner); - return true; } @@ -1226,6 +1229,119 @@ emit m_owner->dataChanged(m_owner); } +/*! + * \brief ColumnPrivate::updateProperties + * updates the properties. Will be called, when data in the column changed. + * The properies will be used to speed up some algorithms. + * See where variable properties will be used. + */ +void ColumnPrivate::updateProperties() { + + // TODO: for double Properties::Constant will never be used. Use an epsilon (difference smaller than epsilon is zero) + if (rowCount() == 0) { + properties = AbstractColumn::Properties::No; + propertiesAvailable = true; + return; + } + + double prevValue = NAN; + int prevValueInt = 0; + qint64 prevValueDatetime; + + if (m_column_mode == AbstractColumn::Integer) + prevValueInt = integerAt(0); + else if (m_column_mode == AbstractColumn::Numeric) + prevValue = valueAt(0); + else if (m_column_mode == AbstractColumn::DateTime || + m_column_mode == AbstractColumn::Month || + m_column_mode == AbstractColumn::Day) + prevValueDatetime = dateTimeAt(0).toMSecsSinceEpoch(); + + + int monotonic_decreasing = -1; + int monotonic_increasing = -1; + + double value; + int valueInt; + qint64 valueDateTime; + + for (int row=1; row< rowCount(); row++) { + + if (m_column_mode == AbstractColumn::Integer) { + valueInt = integerAt(row); + + // check monotonic increasing + if (valueInt >= prevValue && monotonic_increasing < 0) + monotonic_increasing = 1; + else if (valueInt < prevValue && monotonic_increasing >=0) + monotonic_increasing = 0; + // else: nothing + + // check monotonic decreasing + if (valueInt <= prevValue && monotonic_decreasing < 0) + monotonic_decreasing = 1; + else if (valueInt > prevValue && monotonic_decreasing >=0) + monotonic_decreasing = 0; + + prevValueInt = valueInt; + + } else if (m_column_mode == AbstractColumn::Numeric) { + value = valueAt(row); + + // check monotonic increasing + if (value >= prevValue && monotonic_increasing < 0) + monotonic_increasing = 1; + else if (value < prevValue) + monotonic_increasing = 0; + // else: nothing + + // check monotonic decreasing + if (value <= prevValue && monotonic_decreasing < 0) + monotonic_decreasing = 1; + else if (value > prevValue) + monotonic_decreasing = 0; + + prevValue = value; + + } else if (m_column_mode == AbstractColumn::DateTime || + m_column_mode == AbstractColumn::Month || + m_column_mode == AbstractColumn::Day) { + + valueDateTime = dateTimeAt(row).toMSecsSinceEpoch(); + + // check monotonic increasing + if (valueDateTime >= prevValueDatetime && monotonic_increasing < 0) + monotonic_increasing = 1; + else if (valueDateTime < prevValueDatetime) + monotonic_increasing = 0; + // else: nothing + + // check monotonic decreasing + if (valueDateTime <= prevValueDatetime && monotonic_decreasing < 0) + monotonic_decreasing = 1; + else if (valueDateTime > prevValueDatetime) + monotonic_decreasing = 0; + + prevValueDatetime = valueDateTime; + }else { + properties = AbstractColumn::Properties::No; + return; + } + } + + properties = AbstractColumn::Properties::No; + if (monotonic_increasing && monotonic_decreasing) + properties += AbstractColumn::Properties::Constant; + else if (monotonic_decreasing) + properties += AbstractColumn::Properties::MonotonicDecreasing; + else if (monotonic_increasing) + properties += AbstractColumn::Properties::MonotonicIncreasing; + + + DEBUG("updateProperties: " << name().toLocal8Bit().data() << "properties: " << properties); + propertiesAvailable = true; +} + //////////////////////////////////////////////////////////////////////////////// //@} //////////////////////////////////////////////////////////////////////////////// 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 @@ -2608,6 +2608,22 @@ else info += QDateTime::fromMSecsSinceEpoch(logicalPoint.y()).toString(yRangeDateTimeFormat); update(); + } else if (mouseMode == CartesianPlot::MouseMode::SelectionMode) { + + // hover the nearest curve to the mousepointer + bool curve_hovered = false; + for (auto curve: q->children()){ + if(curve_hovered){ + curve->setHover(false); + continue; + } + if(curve->activateCurve(event->pos())){ + curve->setHover(true); + curve_hovered = true; + continue; + } + curve->setHover(false); + } } } q->info(info); 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 @@ -66,6 +66,11 @@ bool load(XmlStreamReader*, bool preview) override; void loadThemeConfig(const KConfig&) override; void saveThemeConfig(const KConfig&) override; + static int calculateMaxSteps(unsigned int value); + double y(double x, bool &valueFound) const; + bool activateCurve(QPointF mouseScenePos, double maxDist = -1); + bool pointLiesNearLine(QPointF& p1, QPointF& p2, QPointF& pos, double maxDist); + void setHover(bool on); POINTER_D_ACCESSOR_DECL(const AbstractColumn, xColumn, XColumn) POINTER_D_ACCESSOR_DECL(const AbstractColumn, yColumn, YColumn) 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 @@ -845,7 +845,7 @@ m_printing(false) { setFlag(QGraphicsItem::ItemIsSelectable, true); - setAcceptHoverEvents(true); + setAcceptHoverEvents(false); } QString XYCurvePrivate::name() const { @@ -864,7 +864,12 @@ } void XYCurvePrivate::contextMenuEvent(QGraphicsSceneContextMenuEvent* event) { - q->createContextMenu()->exec(event->screenPos()); + + if (q->activateCurve(event->pos(), linePen.width())) { + q->createContextMenu()->exec(event->screenPos()); + return; + } + QGraphicsItem::contextMenuEvent(event); } bool XYCurvePrivate::swapVisible(bool on) { @@ -1677,6 +1682,467 @@ recalcShapeAndBoundingRect(); } +/*! + * \brief XYCurve::calculateMaxSteps + * calculates log2(x)+1 for an integer value. + * Used in y(double x) to calculate the maximum steps + * source: https://stackoverflow.com/questions/11376288/fast-computing-of-log2-for-64-bit-integers + * source: http://graphics.stanford.edu/~seander/bithacks.html#IntegerLogLookup + * \param value + * \return + */ +// TODO: testing if it is faster than calculating log2. + int XYCurve::calculateMaxSteps (unsigned int value) { + const char LogTable256[256] = + { + -1,0,1,1,2,2,2,2,3,3,3,3,3,3,3,3, + 4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 5,5,5,5,5,5,5,5,5,5,5,5,5,5,5,5, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7, + 7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7 + }; + + unsigned int r; // r will be lg(v) + unsigned int t, tt; // temporaries + if ((tt = value >> 16)) + r = (t = tt >> 8) ? 24 + LogTable256[t] : 16 + LogTable256[tt]; + else + r = (t = value >> 8) ? 8 + LogTable256[t] : LogTable256[value]; + + return r+1; +} + + /*! + * \brief XYCurve::y + * Find y value which corresponds to a @par x . @par valueFound indicates, if value was found. + * When monotonic increasing or decreasing a different algorithm will be used, which needs less steps (mean) (log_2(rowCount)) to find the value. + * \param x + * \param valueFound + * \return + */ +double XYCurve::y(double x, bool &valueFound) const { + int rowCount = xColumn()->rowCount(); + + double prevValue=0; + qint64 prevValueDateTime; + AbstractColumn::ColumnMode xColumnMode = xColumn()->columnMode(); + AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); + int properties = xColumn()->properties(); + if(properties == AbstractColumn::Properties::No){ + // naiv way + if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || + xColumnMode == AbstractColumn::ColumnMode::Integer) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + for (int row = 0; row < rowCount; row++) { + if (xColumn()->isValid(row) && yColumn()->isValid(row)) { + if (row == 0) + prevValue = xColumn()->valueAt(row); + + double value = xColumn()->valueAt(row); + if (abs(value-x) <= abs(prevValue-x)) { // <= prevents also that row-1 become < 0 + if (row < rowCount-1) + prevValue = value; + else { + valueFound = true; + return yColumn()->valueAt(row); + } + }else{ + valueFound = true; + return yColumn()->valueAt(row-1); + } + } + } + } else if ((xColumnMode == AbstractColumn::ColumnMode::DateTime || + xColumnMode == AbstractColumn::ColumnMode::Month || + xColumnMode == AbstractColumn::ColumnMode::Day) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + qint64 xInt64 = static_cast(x); + for (int row = 0; row < rowCount; row++) { + if (xColumn()->isValid(row) && yColumn()->isValid(row)) { + if (row == 0) + prevValueDateTime = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); + + qint64 value = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); + if (abs(value-xInt64) <= abs(prevValueDateTime-xInt64)) { // <= prevents also that row-1 become < 0 + if (row < rowCount-1) + prevValueDateTime = value; + else { + valueFound = true; + return yColumn()->valueAt(row); + } + }else{ + valueFound = true; + return yColumn()->valueAt(row-1); + } + } + } + + } + }else 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 + bool increase = true; + if(properties == AbstractColumn::Properties::MonotonicDecreasing) + increase = false; + + int lowerIndex = 0; + int higherIndex = rowCount-1; + + unsigned int max_steps = calculateMaxSteps(static_cast(rowCount)); + + if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || + xColumnMode == AbstractColumn::ColumnMode::Integer) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + for (int i=0; i< max_steps; i++) { // so no log_2(rowCount) needed + int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); + double value = xColumn()->valueAt(index); + + if (higherIndex-lowerIndex < 3) { + if (abs(xColumn()->valueAt(lowerIndex) - x) < abs(xColumn()->valueAt(higherIndex)-x)) + index = lowerIndex; + else + index = higherIndex; + + valueFound = true; + return yColumn()->valueAt(index); + } + + if (value > x && increase) + higherIndex = index; + else if (value > x && !increase) + lowerIndex = index; + else if (value < x && increase) + lowerIndex = index; + else if (value < x && !increase) + higherIndex = index; + + } + } else if ((xColumnMode == AbstractColumn::ColumnMode::DateTime || + xColumnMode == AbstractColumn::ColumnMode::Month || + xColumnMode == AbstractColumn::ColumnMode::Day) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + qint64 xInt64 = static_cast(x); + for (int i=0; i< max_steps; i++) { // so no log_2(rowCount) needed + int index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); + qint64 value = xColumn()->dateTimeAt(index).toMSecsSinceEpoch(); + + if (higherIndex-lowerIndex < 3) { + if (abs(xColumn()->dateTimeAt(lowerIndex).toMSecsSinceEpoch() - xInt64) < abs(xColumn()->dateTimeAt(higherIndex).toMSecsSinceEpoch()-xInt64)) + index = lowerIndex; + else + index = higherIndex; + + valueFound = true; + return yColumn()->valueAt(index); + } + + if (value > xInt64 && increase) + higherIndex = index; + else if (value > xInt64 && !increase) + lowerIndex = index; + else if (value < xInt64 && increase) + lowerIndex = index; + else if (value < xInt64 && !increase) + higherIndex = index; + + } + } + + } + valueFound = false; + return 0; +} + +/*! + * \brief XYCurve::activateCurve + * Checks if the mousepos distance to the curve is less than @p pow(maxDist,2) + * \param mouseScenePos + * \param maxDist + * \return Returns true if the distance is smaller than pow(maxDist,2). + */ +bool XYCurve::activateCurve(QPointF mouseScenePos, double maxDist) { + + Q_D(XYCurve); + + if (!xColumn()) + return false; + + int rowCount = xColumn()->rowCount(); + if (rowCount == 0) + return false; + + if (maxDist < 0) + maxDist = d->linePen.width(); + + double maxDistSquare = pow(maxDist,2); + + int properties = xColumn()->properties(); + if (properties == AbstractColumn::Properties::No) { + AbstractColumn::ColumnMode xColumnMode = xColumn()->columnMode(); + AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); + if ((xColumnMode == AbstractColumn::ColumnMode::DateTime || + xColumnMode == AbstractColumn::ColumnMode::Month || + xColumnMode == AbstractColumn::ColumnMode::Day) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + QPointF curvePosPrevScene; + for (int row = 0; row< rowCount; row++) { + if (xColumn()->isValid(row) && yColumn()->isValid(row)) { + qint64 xDateTime = xColumn()->dateTimeAt(row).toMSecsSinceEpoch(); + double yValue = yColumn()->valueAt(row); + + QPointF curvePosScene = d->cSystem->mapLogicalToScene(QPointF(xDateTime, yValue)); + + if (row == 0) + curvePosPrevScene = curvePosScene; + double xDistSquare = pow(mouseScenePos.x() - curvePosScene.x(),2); + double yDistSquare = pow(mouseScenePos.y() - curvePosScene.y(),2); + double distance = xDistSquare+yDistSquare; + + if (distance <= maxDistSquare) + return true; + + // point is not in the near of the point, but it can be in the near of the connection line of two points + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) + return true; + } + } + } + + } else if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || + xColumnMode == AbstractColumn::ColumnMode::Integer) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + QPointF curvePosPrevScene; + for (int row = 0; row< rowCount; row++) { + if (xColumn()->isValid(row) && yColumn()->isValid(row)) { + double xvalue = xColumn()->valueAt(row); + double yvalue = yColumn()->valueAt(row); + QPointF curvePosScene = d->cSystem->mapLogicalToScene(QPointF(xvalue,yvalue)); + double xDistSquare = pow(mouseScenePos.x() - curvePosScene.x(),2); + double yDistSquare = pow(mouseScenePos.y() - curvePosScene.y(),2); + double distance = xDistSquare+yDistSquare; + //DEBUG("Distance: " << distance << "MaxDistSquare: " << maxDistSquare); + if (distance <= maxDistSquare) + return true; + + if (row == 0) + curvePosPrevScene = curvePosScene; + // point is not in the near of the point, but it can be in the near of the connection line of two points + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) + return true; + } + } + } + } + } else if (properties == AbstractColumn::Properties::MonotonicIncreasing || + properties == AbstractColumn::Properties::MonotonicDecreasing) { + bool increase = true; + if (properties == AbstractColumn::Properties::MonotonicDecreasing) + increase = false; + + int lowerIndex = 0; + int higherIndex = rowCount-1; + QPointF point(mouseScenePos.x()-maxDist,mouseScenePos.y()); + double x = d->cSystem->mapSceneToLogical(point).x(); + + unsigned int max_steps = calculateMaxSteps(static_cast(rowCount)); + int index; + AbstractColumn::ColumnMode xColumnMode = xColumn()->columnMode(); + AbstractColumn::ColumnMode yColumnMode = yColumn()->columnMode(); + if ((xColumnMode == AbstractColumn::ColumnMode::Numeric || + xColumnMode == AbstractColumn::ColumnMode::Integer) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + for (unsigned int i=0; i< max_steps; i++) { // so no log_2(rowCount) needed + index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); + double value = xColumn()->valueAt(index); + + if (higherIndex-lowerIndex < 2) { + if (abs(xColumn()->valueAt(lowerIndex) - x) < abs(xColumn()->valueAt(higherIndex)-x)) { + index = lowerIndex; + break; + } else { + index = higherIndex; + break; + } + } + + if (value > x && increase) + higherIndex = index; + else if (value > x && !increase) + lowerIndex = index; + else if (value < x && increase) + lowerIndex = index; + else if (value < x && !increase) + higherIndex = index; + } + + double xvalue = xColumn()->valueAt(index); + double yvalue = yColumn()->valueAt(index); + QPointF curvePosScene(d->cSystem->mapLogicalToScene(QPointF(xvalue,yvalue))); + QPointF curvePosPrevScene = curvePosScene; + while (curvePosScene.x() <= pow(mouseScenePos.x()+maxDist,2) && index < rowCount && index >= 0) { + if (pow(mouseScenePos.x()- curvePosScene.x(),2)+pow(mouseScenePos.y()-curvePosScene.y(),2) <= maxDistSquare) + return true; + + // point is not in the near of the point, but it can be in the near of the connection line of two points + if (increase) { + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) + return true; + } + } else { + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene, curvePosScene, mouseScenePos, maxDist)) + return true; + } + } + + if (increase) + index++; + else + index--; + + xvalue = xColumn()->valueAt(index); + yvalue = yColumn()->valueAt(index); + curvePosScene = d->cSystem->mapLogicalToScene(QPointF(xvalue,yvalue)); + } + } else if ((xColumnMode == AbstractColumn::ColumnMode::DateTime || + xColumnMode == AbstractColumn::ColumnMode::Month || + xColumnMode == AbstractColumn::ColumnMode::Day) && + (yColumnMode == AbstractColumn::ColumnMode::Numeric || + yColumnMode == AbstractColumn::ColumnMode::Integer)) { + + for (unsigned int i=0; i< max_steps; i++) { // so no log_2(rowCount) needed + + index = lowerIndex + round(static_cast(higherIndex - lowerIndex)/2); + qint64 value = xColumn()->dateTimeAt(index).toMSecsSinceEpoch(); + + if (higherIndex-lowerIndex < 2) { + if (abs(xColumn()->dateTimeAt(lowerIndex).toMSecsSinceEpoch() - x) < abs(xColumn()->dateTimeAt(higherIndex).toMSecsSinceEpoch()-x)) { + index = lowerIndex; + break; + } else { + index = higherIndex; + break; + } + } + + if (value > x && increase) + higherIndex = index; + else if (value > x && !increase) + lowerIndex = index; + else if (value < x && increase) + lowerIndex = index; + else if (value < x && !increase) + higherIndex = index; + } + + qint64 xvalue = xColumn()->dateTimeAt(index).toMSecsSinceEpoch(); + double yvalue = yColumn()->valueAt(index); + QPointF curvePosScene(d->cSystem->mapLogicalToScene(QPointF(xvalue,yvalue))); + QPointF curvePosPrevScene = curvePosScene; + + // TODO: must be optimized + while (curvePosScene.x() <= pow(mouseScenePos.x()+maxDist,2) && index < rowCount && index >= 0) { + if (pow(mouseScenePos.x()- curvePosScene.x(),2)+pow(mouseScenePos.y()-curvePosScene.y(),2) <= maxDistSquare) + return true; + + // point is not in the near of the point, but it can be in the near of the connection line of two points + if (increase) { + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene,curvePosScene, mouseScenePos, maxDist)) + return true; + } + } else { + if (curvePosScene.x() >= mouseScenePos.x() && curvePosPrevScene.x() < mouseScenePos.x()) { + if (pointLiesNearLine(curvePosPrevScene, curvePosScene, mouseScenePos, maxDist)) + return true; + } + } + + if (increase) + index++; + else + index--; + + xvalue = xColumn()->dateTimeAt(index).toMSecsSinceEpoch(); + yvalue = yColumn()->valueAt(index); + curvePosPrevScene = curvePosScene; + curvePosScene = d->cSystem->mapLogicalToScene(QPointF(xvalue,yvalue)); + } + + } + + } + return false; +} + +/*! + * \brief XYCurve::pointLiesNearLine + * Calculates if a point \param pos lies near than maxDist to the line created by the points \param p1 and \param p2 + * https://stackoverflow.com/questions/11604680/point-laying-near-line + * \param p1 + * \param p2 + * \param pos + * \param maxDist + * \return + */ +bool XYCurve::pointLiesNearLine(QPointF& p1, QPointF& p2, QPointF& pos, double maxDist) { + double dx12 = p2.x()-p1.x(); + double dy12 = p2.y()-p1.y(); + double vecLenght = sqrt(pow(dx12,2)+pow(dy12,2)); + + if (vecLenght == 0) { + if (pow(p1.x()-pos.x(),2)+pow(p1.y()-pos.y(),2) <= pow(maxDist,2)) + return true; + return false; + } + QPointF unitvec(dx12/vecLenght,dy12/vecLenght); + + double dx1m = pos.x() - p1.x(); + double dy1m = pos.y() - p1.y(); + + double dist_segm = abs(dx1m*unitvec.y() - dy1m*unitvec.x()); + double scalar_product = dx1m*unitvec.x()+dy1m*unitvec.y(); + DEBUG("DIST_SEGMENT " << dist_segm << ", SCALAR_PRODUCT: " << scalar_product << ", VEC_LENGTH: " << vecLenght); + + if (scalar_product > 0) { + if (scalar_product < vecLenght && dist_segm < maxDist) { + return true; + } + } + return false; +} +/*! + * \brief XYCurve::setHover + * Will be called in CartesianPlot::hoverMoveEvent() + * See d->setHover(on) for more documentation + * \param on + */ +void XYCurve::setHover(bool on) { + Q_D(XYCurve); + d->setHover(on); +} + void XYCurvePrivate::updateErrorBars() { errorBarsPath = QPainterPath(); if (xErrorType==XYCurve::NoError && yErrorType==XYCurve::NoError) { @@ -2111,22 +2577,39 @@ } } -void XYCurvePrivate::hoverEnterEvent(QGraphicsSceneHoverEvent*) { - const auto* plot = dynamic_cast(q->parentAspect()); - if (plot->mouseMode() == CartesianPlot::SelectionMode && !isSelected()) { - m_hovered = true; - emit q->hovered(); - update(); +/*! + * \brief XYCurvePrivate::mousePressEvent + * checks with activateCurve, if the mousePress was in the near + * of the curve. If it was, the curve will be selected + * \param event + */ +void XYCurvePrivate::mousePressEvent(QGraphicsSceneMouseEvent* event) { + + if(q->activateCurve(event->pos(), linePen.width())){ + setSelected(true); + return; } + + event->ignore(); + setSelected(false); + QGraphicsItem::mousePressEvent(event); + return; } -void XYCurvePrivate::hoverLeaveEvent(QGraphicsSceneHoverEvent*) { - const auto* plot = dynamic_cast(q->parentAspect()); - if (plot->mouseMode() == CartesianPlot::SelectionMode && m_hovered) { - m_hovered = false; - emit q->unhovered(); - update(); - } +/*! + * \brief XYCurvePrivate::setHover + * Will be called from CartesianPlot::hoverMoveEvent which + * determines, which curve is hovered + * \param on + */ +void XYCurvePrivate::setHover(bool on) { + + if(on == m_hovered) + return; // don't update if state not changed + + m_hovered = on; + on ? emit q->hovered() : emit q->unhovered(); + update(); } void XYCurvePrivate::setPrinting(bool on) { diff --git a/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h b/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h --- a/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h +++ b/src/backend/worksheet/plots/cartesian/XYCurvePrivate.h @@ -55,6 +55,7 @@ void updatePixmap(); void setPrinting(bool); void suppressRetransform(bool); + void setHover(bool on); //data source const AbstractColumn* xColumn; @@ -135,8 +136,7 @@ private: void contextMenuEvent(QGraphicsSceneContextMenuEvent*) override; - void hoverEnterEvent(QGraphicsSceneHoverEvent*) override; - void hoverLeaveEvent(QGraphicsSceneHoverEvent*) override; + void mousePressEvent(QGraphicsSceneMouseEvent*) override; void paint(QPainter*, const QStyleOptionGraphicsItem*, QWidget* widget = nullptr) override; void drawSymbols(QPainter*); diff --git a/src/commonfrontend/worksheet/WorksheetView.cpp b/src/commonfrontend/worksheet/WorksheetView.cpp --- a/src/commonfrontend/worksheet/WorksheetView.cpp +++ b/src/commonfrontend/worksheet/WorksheetView.cpp @@ -943,22 +943,22 @@ //to select curves having overlapping bounding boxes we need to check whether the cursor //is inside of item's shapes and not inside of the bounding boxes (Qt's default behaviour). - for (auto* item : items(event->pos())) { - if (!dynamic_cast(item)) - continue; - - if ( item->shape().contains(item->mapFromScene(mapToScene(event->pos()))) ) { - //deselect currently selected items - for (auto* selectedItem : scene()->selectedItems()) - selectedItem->setSelected(false); - - //select the item under the cursor and update the current selection - item->setSelected(true); - selectionChanged(); - - return; - } - } +// for (auto* item : items(event->pos())) { +// if (!dynamic_cast(item)) +// continue; + +// if ( item->shape().contains(item->mapFromScene(mapToScene(event->pos()))) ) { +// //deselect currently selected items +// for (auto* selectedItem : scene()->selectedItems()) +// selectedItem->setSelected(false); + +// //select the item under the cursor and update the current selection +// item->setSelected(true); +// selectionChanged(); + +// return; +// } +// } // select the worksheet in the project explorer if the view was clicked // and there is no selection currently. We need this for the case when