Results 1 to 9 of 9

Thread: Need guidelines to handle the duo curve legend selection

  1. #1
    Join Date
    Jun 2019
    Location
    France, Pau
    Posts
    60
    Thanks
    32
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Need guidelines to handle the duo curve legend selection

    Hello,

    Like it's possible to do easily in QCustomPlot, I need some guidelines that will allow me to select a curve and highlight it, by changing its color and highlight its legend item (QwtPlotLegendItem).

    I need also to do that in reverse : by selecting the legend, I highlight it and its corresponding curve.

    Thank you.

  2. #2
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    You can change the color of a curve and you can click legend items - what type of guideline do you expect ?

    Uwe

  3. #3
    Join Date
    Jun 2019
    Location
    France, Pau
    Posts
    60
    Thanks
    32
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    Hi Uwe,

    When I click on a curve point/line (or near them), I catch that event (BTW, I installed an event filter, an external object, on the QwtPlot object) and then how can I get a pointer to the "selected" item (the QwtPlotCurve) ?

    Speaking of clicking on the QwtPlotLegendItem, I tried also to show a context menu by right clicking inside the legend box, but it's not accurate (read the comment I added in the code) :

    NB: pos is fed from QMouseEvent:os method that is executed inside the event filter installed on QwtPlot object.

    Qt Code:
    1. void MyQwtPlotManager::contextMenuRequest(const QPoint& pos)
    2. {
    3. QMenu*const menuContext = new QMenu(m_pParent);
    4. menuContext->setAttribute(Qt::WA_DeleteOnClose);
    5.  
    6. /*QRect legRec = m_legendItem->geometry(m_pChart->canvas()->rect());
    7.   std::cout << "[debug] legend rect LTRB = "
    8.   << legRec.left() << ","
    9.   << legRec.top() << ","
    10.   << legRec.right() << ","
    11.   << legRec.bottom() << std::endl;
    12.   std::cout << "[debug] mouse pos xy = " << pos.x() << "," << pos.y() << std::endl;*/
    13.  
    14. // NB: mouse is inside the legend but I got these values :
    15. // mouse : (997, 46)
    16. // legend left,top,right,bottom : 880, 10, 1317, 36
    17.  
    18. if (m_legendItem && m_legendItem->geometry(m_pQwtPlot->canvas()->rect()).contains(pos))
    19. {
    20. // add actions related to the legend.
    21. ....
    22. ....
    23. }
    24. }
    To copy to clipboard, switch view to plain text mode 

    Then, if I succeed in selecting a curve legend among the others, I don't know how to retrieve the associated curve !

    Meanwhile, I will have a look to the "event_filter" example.

    Thanks.
    Last edited by embeddedmz; 20th June 2019 at 14:44.

  4. #4
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    Speaking of clicking on the QwtPlotLegendItem, I tried also to show a context menu by right clicking inside the legend box, but it's not accurate (read the comment I added in the code) :
    NB: pos is fed from QMouseEvent::pos method that is executed inside the event filter installed on QwtPlot object.
    QwtPlot is a a composite widget and you are in the coordinate system of the plot canvas.
    You have to set up an event filter for the plot canvas ( recommended ) instead or you have to translate the position. F.e. see QWidget::mapTo/mapFrom or in this simple case: pos -= canvas().pos();

    Uwe

  5. The following user says thank you to Uwe for this useful post:

    embeddedmz (21st June 2019)

  6. #5
    Join Date
    Jun 2019
    Location
    France, Pau
    Posts
    60
    Thanks
    32
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    Hello Uwe,

    Hope you're doing well.

    By order of priority :

    * There's QwtPlotCurve::closestPoint that can allow me to check that the mouse pointer is closer to a point of the curve. But it doesn't take in account the line joining 2 points of the curve : like I said above, I want to highlight the curve by clicking on it : usually the click location is a line... is there an easy way to do it ? I don't know if closestPoint can be used with some simple Trigonometry to check that the mouse cursor is closer to a curve line

    * Qt question : can you tell me please, why in the implementation below, line 65, when I return "true", the replot() I did it before it, doesn't refresh the curve with the new pen width ?

    * I didn't find a "proper" way to modify the font of an already existing legend (look at highlightSelectedLegend below), it is not important for now, but can you propose a strategy ? is it impossible ?

    I post my code below (an event filter class), it works fine to highlight/deselect a single curve by clicking on a point on it/elsewhere or by clicking on its legend (select/deselect), it doesn't affect panning.

    If you have improvements, I'm really interested.

    Header :
    Qt Code:
    1. #ifndef CANVASPICKER_H
    2. #define CANVASPICKER_H
    3.  
    4. #include <QObject>
    5. #include <QPoint>
    6.  
    7. class QwtPlot;
    8. class QwtPlotLegendItem;
    9.  
    10. class CanvasPicker: public QObject
    11. {
    12. Q_OBJECT
    13. public:
    14. CanvasPicker(QwtPlot* plot);
    15. bool eventFilter(QObject*, QEvent*) override;
    16.  
    17. QwtPlotCurve* selectedCurve()
    18. {
    19. return isCurveStillAlive(m_lastSelectedCurve) ? m_lastSelectedCurve : nullptr;
    20. }
    21.  
    22. protected:
    23. void select(const QPoint&); // curve/legend selection
    24.  
    25. QwtPlot* plot();
    26. const QwtPlot* plot() const;
    27.  
    28. protected:
    29. QPoint m_mousePressPosition;
    30.  
    31. private:
    32. void highlightSelectedCurve(QwtPlotCurve* curve);
    33. bool deselectLastSelectedCurve();
    34. // Not easy to code.
    35. //void highlightSelectedLegend(QwtPlotLegendItem* legend, QwtPlotCurve* curve);
    36.  
    37. // Sadly, we can't use weak pointers on QwtPlotCurve.
    38. // Will be used to deselect the last selected curve.
    39. bool isCurveStillAlive(QwtPlotCurve*) const;
    40.  
    41. private:
    42. QwtPlotCurve* m_lastSelectedCurve;
    43.  
    44. };
    45.  
    46. #endif
    To copy to clipboard, switch view to plain text mode 

    Implementation :

    Qt Code:
    1. #include <QMouseEvent>
    2.  
    3. #include <qwt_plot.h>
    4. #include <qwt_plot_canvas.h>
    5. #include <qwt_plot_curve.h>
    6. #include <qwt_plot_legenditem.h>
    7.  
    8. #include "CanvasPicker.h"
    9.  
    10. CanvasPicker::CanvasPicker(QwtPlot* plot) :
    11. QObject(plot),
    12. m_lastSelectedCurve(nullptr)
    13. {
    14. QwtPlotCanvas* canvas = qobject_cast<QwtPlotCanvas*>(plot->canvas());
    15. canvas->installEventFilter(this);
    16.  
    17. // We want the focus, but no focus rect. The
    18. // selected point will be highlighted instead.
    19. canvas->setFocusPolicy(Qt::StrongFocus);
    20. #ifndef QT_NO_CURSOR
    21. canvas->setCursor(Qt::PointingHandCursor);
    22. #endif
    23. canvas->setFocusIndicator(QwtPlotCanvas::ItemFocusIndicator);
    24. canvas->setFocus();
    25. }
    26.  
    27. QwtPlot* CanvasPicker::plot()
    28. {
    29. return qobject_cast<QwtPlot*>(parent());
    30. }
    31.  
    32. const QwtPlot* CanvasPicker::plot() const
    33. {
    34. return qobject_cast<const QwtPlot*>(parent());
    35. }
    36.  
    37. bool CanvasPicker::eventFilter(QObject* object, QEvent* event)
    38. {
    39. if (plot() == nullptr || object != plot()->canvas())
    40. {
    41. return false;
    42. }
    43.  
    44. switch (event->type())
    45. {
    46. case QEvent::MouseButtonPress:
    47. {
    48. const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
    49. if (mouseEvent->button() & Qt::LeftButton)
    50. {
    51. m_mousePressPosition = mouseEvent->pos();
    52. }
    53. break;
    54. }
    55.  
    56. case QEvent::MouseButtonRelease:
    57. {
    58. const QMouseEvent* const mouseEvent = static_cast<QMouseEvent*>(event);
    59. const QPoint mouseReleasePosition = mouseEvent->pos();
    60. if (mouseEvent->button() & Qt::LeftButton)
    61. {
    62. if (m_mousePressPosition == mouseReleasePosition)
    63. {
    64. select(mouseEvent->pos());
    65. //return true; // blocks painting ??!!??
    66. }
    67. }
    68. break;
    69. }
    70.  
    71. default:
    72. break;
    73. }
    74.  
    75. return QObject::eventFilter(object, event);
    76. }
    77.  
    78. void CanvasPicker::select(const QPoint& pos)
    79. {
    80. // 1. check if the legend has been clicked on
    81. QwtPlotItemList curveList = plot()->itemList(QwtPlotItem::Rtti_PlotCurve);
    82. if (curveList.isEmpty())
    83. {
    84. return;
    85. }
    86.  
    87. // we only have one legend item
    88. QwtPlotItemList legendList = plot()->itemList(QwtPlotItem::Rtti_PlotLegend);
    89. QwtPlotLegendItem* legendItem = (legendList.size() > 0) ? \
    90. static_cast<QwtPlotLegendItem*>(legendList[0]) : nullptr;
    91. QVector<QList<QRect>> curveLegendGeometries;
    92. if (legendItem &&
    93. legendItem->geometry(plot()->canvas()->rect()).contains(pos))
    94. {
    95. for (auto curveItem : curveList)
    96. {
    97. //QwtPlotCurve* curve = static_cast<QwtPlotCurve*>(curveItem);
    98. curveLegendGeometries.push_back(legendItem->legendGeometries(curveItem));
    99. }
    100. }
    101.  
    102. if (!curveLegendGeometries.isEmpty())
    103. {
    104. auto it = std::find_if(curveLegendGeometries.begin(), curveLegendGeometries.end(),
    105. [&pos](const QList<QRect>& listRect) -> bool
    106. {
    107. for (const auto& rect : listRect)
    108. {
    109. if (rect.contains(pos))
    110. {
    111. return true;
    112. }
    113. }
    114. return false;
    115. });
    116. if (it != curveLegendGeometries.end())
    117. {
    118. // or a simple substraction...
    119. auto curveIndex = std::distance(curveLegendGeometries.begin(), it);
    120. if (curveIndex < curveList.size())
    121. {
    122. QwtPlotCurve* legendCurve = static_cast<QwtPlotCurve*>(curveList[int(curveIndex)]);
    123. if (m_lastSelectedCurve == legendCurve)
    124. {
    125. deselectLastSelectedCurve();
    126. }
    127. else
    128. {
    129. highlightSelectedCurve(legendCurve);
    130. }
    131.  
    132. //highlightSelectedLegend(legendItem, legendCurve);
    133.  
    134. // Immediate paint doesn't work !
    135. //QwtPlotCanvas* plotCanvas = qobject_cast<QwtPlotCanvas*>(plot()->canvas());
    136. //plotCanvas->setPaintAttribute(QwtPlotCanvas::ImmediatePaint, true);
    137. plot()->replot();
    138. //plotCanvas->setPaintAttribute(QwtPlotCanvas::ImmediatePaint, false);
    139. }
    140.  
    141. return;
    142. }
    143. else
    144. {
    145. if (deselectLastSelectedCurve())
    146. {
    147. plot()->replot();
    148. }
    149. }
    150. }
    151.  
    152. // 2. check if a curve has been selected
    153. QwtPlotCurve* closestCurve = nullptr;
    154. double minDistance = 10e10;
    155. {
    156. double tmp;
    157. for (const auto curve : curveList)
    158. {
    159. QwtPlotCurve* const c = static_cast<QwtPlotCurve*>(curve);
    160. c->closestPoint(pos, &tmp);
    161. if (tmp < minDistance)
    162. {
    163. closestCurve = c;
    164. minDistance = tmp;
    165. }
    166. }
    167. }
    168.  
    169. if (closestCurve && minDistance < 10) // 10 pixels tolerance
    170. {
    171. if (m_lastSelectedCurve == closestCurve)
    172. {
    173. deselectLastSelectedCurve();
    174. }
    175. else
    176. {
    177. highlightSelectedCurve(closestCurve);
    178. }
    179.  
    180. //QwtPlotCanvas* plotCanvas = qobject_cast<QwtPlotCanvas*>(plot()->canvas());
    181. //plotCanvas->setPaintAttribute(QwtPlotCanvas::ImmediatePaint, true);
    182. plot()->replot();
    183. //plotCanvas->setPaintAttribute(QwtPlotCanvas::ImmediatePaint, false);
    184. }
    185. else
    186. {
    187. if (deselectLastSelectedCurve())
    188. {
    189. plot()->replot();
    190. }
    191. }
    192. }
    193.  
    194. bool CanvasPicker::isCurveStillAlive(QwtPlotCurve* curve) const
    195. {
    196. if (!curve)
    197. {
    198. return false;
    199. }
    200. const QwtPlotItemList& curveList = plot()->itemList();
    201. return (std::find(curveList.begin(), curveList.end(), static_cast<QwtPlotItem*>(curve))
    202. != curveList.end());
    203. }
    204.  
    205. void CanvasPicker::highlightSelectedCurve(QwtPlotCurve* curve)
    206. {
    207. // Deselect last selected curve !
    208. deselectLastSelectedCurve();
    209.  
    210. QPen originalPen = curve->pen();
    211. originalPen.setWidth(originalPen.width() + 1);
    212. curve->setPen(originalPen);
    213. m_lastSelectedCurve = curve;
    214. }
    215.  
    216. bool CanvasPicker::deselectLastSelectedCurve()
    217. {
    218. if (m_lastSelectedCurve != nullptr && isCurveStillAlive(m_lastSelectedCurve))
    219. {
    220. QPen originalPen = m_lastSelectedCurve->pen();
    221. originalPen.setWidth(originalPen.width() - 1);
    222. m_lastSelectedCurve->setPen(originalPen);
    223.  
    224. m_lastSelectedCurve = nullptr;
    225.  
    226. return true;
    227. }
    228. return false;
    229. }
    230.  
    231. #if 0
    232. // The API is not very friendly, let's just only highlight the curve, it's sufficient for now :(
    233. void CanvasPicker::highlightSelectedLegend(QwtPlotLegendItem* legend, QwtPlotCurve* curve)
    234. {
    235. // legendData: The default implementation returns one entry with the title()
    236. // of the item and the legendIcon().
    237. QList<QwtLegendData> originalLegData = curve->legendData();
    238. if (originalLegData.size() > 0)
    239. {
    240. originalLegData[0].value(QwtLegendData::TitleRole);
    241. //............
    242. }
    243.  
    244. legend->updateLegend(curve, originalLegData);
    245. }
    246. #endif
    To copy to clipboard, switch view to plain text mode 

  7. #6
    Join Date
    Jun 2019
    Location
    France, Pau
    Posts
    60
    Thanks
    32
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    For those who are interested, I wrote a function version of closestPoint that takes in account the line between 2 points, I was inspired by the code of Qcp. It works pretty well !

    Qt Code:
    1. // Finds the shortest squared distance of point to the line segment defined by lineStart and
    2. // lineEnd.
    3. double squaredDistanceToLine(const QPointF& lineStart, const QPointF& lineEnd, const QPointF& point)
    4. {
    5. QVector2D a(lineStart);
    6. QVector2D b(lineEnd);
    7. QVector2D p(point);
    8. QVector2D v(b - a);
    9.  
    10. double vLengthSqr = double(v.lengthSquared());
    11. if (!qFuzzyIsNull(vLengthSqr))
    12. {
    13. double mu = double(QVector2D::dotProduct(p - a, v)) / vLengthSqr;
    14. if (mu < 0)
    15. {
    16. return double((a - p).lengthSquared());
    17. }
    18. else if (mu > 1)
    19. {
    20. return double((b - p).lengthSquared());
    21. }
    22. else
    23. {
    24. return double(((a + float(mu) * v) - p).lengthSquared());
    25. }
    26. }
    27.  
    28. return double((a - p).lengthSquared());
    29. }
    30.  
    31. // modified method to take in account the line joining 2 points
    32. // if
    33. /*!
    34.   Find the closest curve point for a specific position
    35.  
    36.   \param curve, Curve
    37.   \param pos Position, where to look for the closest curve point
    38.   \param dist If dist != NULL, closestPoint() returns the distance between
    39.   the position and the closest curve point
    40.   \return Index of the closest curve point, or -1 if none can be found
    41.   ( f.e when the curve has no points )
    42.   \note closestPoint() implements a dumb algorithm, that iterates
    43.   over all points
    44. */
    45. int closestPoint(QwtPlotCurve* curve, const QPoint& pos, double* dist)
    46. {
    47. const size_t numSamples = curve->dataSize();
    48.  
    49. if (curve->plot() == nullptr || numSamples == 0)
    50. {
    51. // no data available in view to calculate distance to
    52. return -1;
    53. }
    54.  
    55. const QwtSeriesData<QPointF>* series = curve->data();
    56.  
    57. const QwtScaleMap xMap = curve->plot()->canvasMap( curve->xAxis() );
    58. const QwtScaleMap yMap = curve->plot()->canvasMap( curve->yAxis() );
    59.  
    60. int index = -1;
    61. double dmin = 1.0e10; // or std::numeric_limits<double>::max();
    62.  
    63. if (curve->style() != QwtPlotCurve::Lines || numSamples == 1)
    64. {
    65. // there's no lines joining points or there's only a single data point
    66. for (uint i = 0; i < numSamples; i++)
    67. {
    68. const QPointF sample = series->sample( i );
    69.  
    70. const double cx = xMap.transform(sample.x()) - pos.x();
    71. const double cy = yMap.transform(sample.y()) - pos.y();
    72.  
    73. const double f = qwtSqr(cx) + qwtSqr(cy);
    74. if (f < dmin)
    75. {
    76. index = int(i);
    77. dmin = f;
    78. }
    79. }
    80. }
    81. else
    82. {
    83. // if we are here, there's at least one line segment.
    84. // calculate distance to line segments.
    85. for (uint i = 0; i < numSamples - 1; i++)
    86. {
    87. const QPointF sample = series->sample(i);
    88. const QPointF sample2 = series->sample(i + 1);
    89.  
    90. const QPointF lineStart(xMap.transform(sample.x()), yMap.transform(sample.y()));
    91. const QPointF lineEnd(xMap.transform(sample2.x()), yMap.transform(sample2.y()));
    92.  
    93. const double f = squaredDistanceToLine(lineStart, lineEnd, pos);
    94. if (f < dmin)
    95. {
    96. index = int(i);
    97. dmin = f;
    98. }
    99. }
    100. }
    101.  
    102. if (dist)
    103. {
    104. *dist = qSqrt(dmin);
    105. }
    106.  
    107. return index;
    108. }
    To copy to clipboard, switch view to plain text mode 

  8. #7
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    In case your points are sorted in x or y direction you can find much more efficient implementations - like in CurveTracker::curveLineAt in the curvetracker example.
    Implementations like QwtPlotCurve::closestPoint - or your adaption - are slow as they are iterating over all points and you often find better solutions using the characteristics of the data or by introducing extra data structures like an R-Tree ( https://www.boost.org/doc/libs/1_63_...roduction.html ).

    Not sure, why you are using floats ( QVector2D instead of QLineF ) - but finding the distance between a point and a line is basic school maths and absolutely no reason to violate the GPL by copying trivialities into the source code of your commercial project !

    Uwe
    Last edited by Uwe; 1st July 2019 at 06:39.

  9. The following user says thank you to Uwe for this useful post:

    embeddedmz (3rd July 2019)

  10. #8
    Join Date
    Jun 2019
    Location
    France, Pau
    Posts
    60
    Thanks
    32
    Qt products
    Qt5
    Platforms
    Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    Hi Uwe,

    I didn't see the CurveTracker example as it's placed in "playground" dir...

    Not sure, why you are using floats ( QVector2D instead of QLineF ) - but finding the distance between a point and a line is basic school maths and absolutely no reason to violate the GPL by copying trivialities into the source code of your commercial project !
    How the GPL is violated in my case ? by using QVector2D or because I copied a method from Qcp (I will change it anyway - the method in question uses QVector2D). I will change it !

    is basic school maths
    Not so basic (not like the Pythagorean theorem for example), and I didn't remember I was taught this thing in school like a basic stuff (anyway, the educational system of the country where I was born sucks so hard (and the country too ) ).

    Thanks.

  11. #9
    Join Date
    Feb 2006
    Location
    Munich, Germany
    Posts
    3,309
    Thanked 879 Times in 827 Posts
    Qt products
    Qt3 Qt4 Qt/Embedded
    Platforms
    MacOS X Unix/X11 Windows

    Default Re: Need guidelines to handle the duo curve legend selection

    Quote Originally Posted by embeddedmz View Post
    How the GPL is violated in my case ? by using QVector2D or because I copied a method from Qcp
    Copying code from another project comes with obligations for what you have to do with your code then - what those exactly are depends on the license.
    Note that I recently changed the license of the Qwt examples to a BSD license ( >= Qwt 6.2 ).

    Uwe

Similar Threads

  1. Replies: 11
    Last Post: 11th September 2013, 08:59
  2. Replies: 2
    Last Post: 23rd June 2012, 19:12
  3. connect 1 legend item and 2 curve
    By ruzik in forum Qwt
    Replies: 1
    Last Post: 13th September 2011, 12:21
  4. Replies: 5
    Last Post: 7th February 2011, 09:38
  5. Drawing curve legend
    By lni in forum Qt Programming
    Replies: 1
    Last Post: 16th February 2010, 04:42

Bookmarks

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
Digia, Qt and their respective logos are trademarks of Digia Plc in Finland and/or other countries worldwide.