QcustomPlot 曲线光标自动更随 进阶

在上一篇文章简单说明了如何实现光标的更随,大部分是基于QCPTracer的函数实现,但是在深入了解其实现原理对源码进行解读时,发现源码是如下方法实现光标对数据的更随,实现的方法太傻瓜了,所以对其做了简单优化,可以通过如下方式修改大大提高鼠标跟随的效率,具体实现如下——

class QCP_LIB_DECL QCPItemTracer : public QCPAbstractItem
{
  Q_OBJECT
  /// \cond INCLUDE_QPROPERTIES
  Q_PROPERTY(QPen pen READ pen WRITE setPen)
  Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen)
  Q_PROPERTY(QBrush brush READ brush WRITE setBrush)
  Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush)
  Q_PROPERTY(double size READ size WRITE setSize)
  Q_PROPERTY(TracerStyle style READ style WRITE setStyle)
  Q_PROPERTY(QCPGraph* graph READ graph WRITE setGraph)
  Q_PROPERTY(double graphKey READ graphKey WRITE setGraphKey)
  Q_PROPERTY(bool interpolating READ interpolating WRITE setInterpolating)
  /// \endcond
public:
  /*!
    The different visual appearances a tracer item can have. Some styles size may be controlled with \ref setSize.
    
    \see setStyle
  */
  enum TracerStyle { tsNone        ///< The tracer is not visible
                     ,tsPlus       ///< A plus shaped crosshair with limited size
                     ,tsCrosshair  ///< A plus shaped crosshair which spans the complete axis rect
                     ,tsCircle     ///< A circle
                     ,tsSquare     ///< A square
                   };
  Q_ENUMS(TracerStyle)

  explicit QCPItemTracer(QCustomPlot *parentPlot);
  virtual ~QCPItemTracer();

  // getters:
  QPen pen() const { return mPen; }
  QPen selectedPen() const { return mSelectedPen; }
  QBrush brush() const { return mBrush; }
  QBrush selectedBrush() const { return mSelectedBrush; }
  double size() const { return mSize; }
  TracerStyle style() const { return mStyle; }
  QCPGraph *graph() const { return mGraph; }
  double graphKey() const { return mGraphKey; }
  bool interpolating() const { return mInterpolating; }

  // setters;
  void setPen(const QPen &pen);
  void setSelectedPen(const QPen &pen);
  void setBrush(const QBrush &brush);
  void setSelectedBrush(const QBrush &brush);
  void setSize(double size);
  void setStyle(TracerStyle style);
  void setGraph(QCPGraph *graph);
  void setGraphKey(double key);
  void setInterpolating(bool enabled);

  // reimplemented virtual methods:
  virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const Q_DECL_OVERRIDE;
  
  // non-virtual methods:
  void updatePosition(); //重点二 该函数负责更新对象, 其中当然也包含了根据Key寻找value的实现

  QCPItemPosition * const position;

protected:
  // property members:
  QPen mPen, mSelectedPen;
  QBrush mBrush, mSelectedBrush;
  double mSize;
  TracerStyle mStyle;
  QCPGraph *mGraph;
  double mGraphKey; //重点一 这里保存曲线的key数据,后续会根据key寻找对应的value
  bool mInterpolating;

  // reimplemented virtual methods:
  virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;

  // non-virtual methods:
  QPen mainPen() const;
  QBrush mainBrush() const;
};

头文件说明完毕,接下来要说明的是cpp——

void QCPItemTracer::updatePosition()
{
  if (mGraph)
  {
    if (mParentPlot->hasPlottable(mGraph))
    {
      if (mGraph->data()->size() > 1)
      {
        QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin();
        QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd()-1;
        if (mGraphKey <= first->key)//判断当前的key是否小于第一个点的key
          position->setCoords(first->key, first->value);//设置位置为第一个点
        else if (mGraphKey >= last->key))//判断当前的key是否大于第一个点的key
          position->setCoords(last->key, last->value);//设置位置为最后一个点
        else
        {
          QCPGraphDataContainer::const_iterator it = mGraph->data()->findBegin(mGraphKey);//敲重点! 这里是找key对应value的关键实现 调用containner的函数找到key对应的数据索引,没看错 他就是一个一个找过去的,没有使用任何算法实现,当数据量很大时弊端就出现了,鼠标每次移动都需要从头开始重新遍历寻找
          if (it != mGraph->data()->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators
          {
            QCPGraphDataContainer::const_iterator prevIt = it;
            ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before
            if (mInterpolating)
            {
              // interpolate between iterators around mGraphKey:
              double slope = 0;
              if (!qFuzzyCompare((double)it->key, (double)prevIt->key))
                slope = (it->value-prevIt->value)/(it->key-prevIt->key);
              position->setCoords(mGraphKey, (mGraphKey-prevIt->key)*slope+prevIt->value);
            } else
            {
              // find iterator with key closest to mGraphKey:
              if (mGraphKey < (prevIt->key+it->key)*0.5)
                position->setCoords(prevIt->key, prevIt->value);
              else
                position->setCoords(it->key, it->value);
            }
          } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty)
            position->setCoords(it->key, it->value);
        }
      } else if (mGraph->data()->size() == 1)
      {
        QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin();
        position->setCoords(it->key, it->value);
      } else
        qDebug() << Q_FUNC_INFO << "graph has no data";
    } else
      qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)";
  }
}

我采用二分查找的方法对于数据便利做了优化,按照我这里的实验数据在三条100万个点的数据曲线做更随的情况下,比原有跟随流畅度有了很大提升,不会像之前稍微有点卡顿;并且新加一个变量保存key对应的value,添加函数可以获取该值,具体实现如下——

class QCP_LIB_DECL QCPItemTracer : public QCPAbstractItem
{
  Q_OBJECT
  /// \cond INCLUDE_QPROPERTIES
  Q_PROPERTY(QPen pen READ pen WRITE setPen)
  Q_PROPERTY(QPen selectedPen READ selectedPen WRITE setSelectedPen)
  Q_PROPERTY(QBrush brush READ brush WRITE setBrush)
  Q_PROPERTY(QBrush selectedBrush READ selectedBrush WRITE setSelectedBrush)
  Q_PROPERTY(double size READ size WRITE setSize)
  Q_PROPERTY(TracerStyle style READ style WRITE setStyle)
  Q_PROPERTY(QCPGraph* graph READ graph WRITE setGraph)
  Q_PROPERTY(double graphKey READ graphKey WRITE setGraphKey)
  Q_PROPERTY(bool interpolating READ interpolating WRITE setInterpolating)
  /// \endcond
public:
  /*!
    The different visual appearances a tracer item can have. Some styles size may be controlled with \ref setSize.
    
    \see setStyle
  */
  enum TracerStyle { tsNone        ///< The tracer is not visible
                     ,tsPlus       ///< A plus shaped crosshair with limited size
                     ,tsCrosshair  ///< A plus shaped crosshair which spans the complete axis rect
                     ,tsCircle     ///< A circle
                     ,tsSquare     ///< A square
                   };
  Q_ENUMS(TracerStyle)

  explicit QCPItemTracer(QCustomPlot *parentPlot);
  virtual ~QCPItemTracer();

  // getters:
  QPen pen() const { return mPen; }
  QPen selectedPen() const { return mSelectedPen; }
  QBrush brush() const { return mBrush; }
  QBrush selectedBrush() const { return mSelectedBrush; }
  double size() const { return mSize; }
  TracerStyle style() const { return mStyle; }
  QCPGraph *graph() const { return mGraph; }
  double graphKey() const { return mGraphKey; }
  double graphValue() const {return mGraphValue; } //Bing Lee,用来返回key对应的value值 2018-11-21 09:11:58
  bool interpolating() const { return mInterpolating; }

  // setters;
  void setPen(const QPen &pen);
  void setSelectedPen(const QPen &pen);
  void setBrush(const QBrush &brush);
  void setSelectedBrush(const QBrush &brush);
  void setSize(double size);
  void setStyle(TracerStyle style);
  void setGraph(QCPGraph *graph);
  void setGraphKey(double key);
  void setInterpolating(bool enabled);
  void setBinarySearch(bool enabled) {mBinarySearch = enabled;} //Bing Lee,用来判断是否使用二分查找 2018-11-21 09:11:54

  // reimplemented virtual methods:
  virtual double selectTest(const QPointF &pos, bool onlySelectable, QVariant *details=0) const Q_DECL_OVERRIDE;
  
  // non-virtual methods:
  void updatePosition();

  QCPItemPosition * const position;

protected:
  // property members:
  QPen mPen, mSelectedPen;
  QBrush mBrush, mSelectedBrush;
  double mSize;
  TracerStyle mStyle;
  QCPGraph *mGraph;
  double mGraphKey;
  double mGraphValue;Bing Lee,用来保存key对应的value 2018-11-21 
  bool mInterpolating;
  bool mBinarySearch;


  // reimplemented virtual methods:
  virtual void draw(QCPPainter *painter) Q_DECL_OVERRIDE;

  // non-virtual methods:
  QPen mainPen() const;
  QBrush mainBrush() const;
};
void QCPItemTracer::updatePosition()
{
  if (mGraph)
  {
    if (mParentPlot->hasPlottable(mGraph))
    {
      if (mGraph->data()->size() > 1)
      {
        QCPGraphDataContainer::const_iterator first = mGraph->data()->constBegin();
        QCPGraphDataContainer::const_iterator last = mGraph->data()->constEnd()-1;
        if (mGraphKey <= first->key)
          position->setCoords(first->key, first->value);
        else if (mGraphKey >= last->key)
          position->setCoords(last->key, last->value);
        else
        {
            QCPGraphDataContainer::const_iterator it = mGraph->data()->begin();
            if(mBinarySearch)
            {//使用二分法快速查找所在点数据 Bing Lee 2018-11-21 08:59:26
                int low = 0, high = mGraph->data()->size();
                while(high > low)
                {
                    int middle = (low + high) / 2;
                    if(mGraphKey < mGraph->data()->constBegin()->mainKey() ||
                            mGraphKey > (mGraph->data()->constEnd()-1)->mainKey())
                        break;

                    if(mGraphKey == (mGraph->data()->constBegin() + middle)->mainKey())
                    {
                        it = (mGraph->data()->constBegin() + middle);
                        break;
                    }
                    if(mGraphKey > (mGraph->data()->constBegin() + middle)->mainKey())
                    {
                        low = middle;
                    }
                    else if(mGraphKey < (mGraph->data()->constBegin() + middle)->mainKey())
                    {
                        high = middle;
                    }
                    if(high - low <= 1)
                    {   //差值计算所在位置数据
                        it = (mGraph->data()->constBegin() + low);
                      break;
                    }
                }
            }
            else
            {
                it = mGraph->data()->findBegin(mGraphKey);
            }
          if (it != mGraph->data()->constEnd()) // mGraphKey is not exactly on last iterator, but somewhere between iterators
          {
            QCPGraphDataContainer::const_iterator prevIt = it;
            ++it; // won't advance to constEnd because we handled that case (mGraphKey >= last->key) before
            if (mInterpolating) //上边只是获取差值计算位置的左端点索引,这里是差值过程,原来就有的
            {
              // interpolate between iterators around mGraphKey:
              double slope = 0;
              if (!qFuzzyCompare((double)it->key, (double)prevIt->key))
                slope = (it->value-prevIt->value)/(it->key-prevIt->key);
              position->setCoords(mGraphKey, (mGraphKey-prevIt->key)*slope+prevIt->value);
              mGraphValue = (mGraphKey-prevIt->key)*slope+prevIt->value;//保存key 对应的value值
            } else
            {
              // find iterator with key closest to mGraphKey:
              if (mGraphKey < (prevIt->key+it->key)*0.5)
              {
                position->setCoords(prevIt->key, prevIt->value);
                mGraphValue = prevIt->value; //保存key 对应的value值
              }
              else
              {
                position->setCoords(it->key, it->value);
                mGraphValue = it->value;//保存key 对应的value值
              }
            }
          } else // mGraphKey is exactly on last iterator (should actually be caught when comparing first/last keys, but this is a failsafe for fp uncertainty)
          {
              position->setCoords(it->key, it->value);
              mGraphValue = it->value;//保存key 对应的value值
          }
        }
      } else if (mGraph->data()->size() == 1)
      {
        QCPGraphDataContainer::const_iterator it = mGraph->data()->constBegin();
        position->setCoords(it->key, it->value);
        mGraphValue = it->value;//保存key 对应的value值
      } else
        qDebug() << Q_FUNC_INFO << "graph has no data";
    } else
      qDebug() << Q_FUNC_INFO << "graph not contained in QCustomPlot instance (anymore)";
  }
}

如果懒得看,直接复制粘贴修改对应函数就行,我所用的QCustomPlot版本如下所示—— 2.0.1

****************************************************************************
**           Author: Emanuel Eichhammer                                   **
**  Website/Contact: http://www.qcustomplot.com/                          **
**             Date: 25.06.18                                             **
**          Version: 2.0.1                                                **
****************************************************************************/

介于很多小伙伴找我咨询代码问题,找到之前面目全非的代码重新修改编译了一下,可能有一点小不同但是效果相同,小手点下关注再去下载哦~
[CustomPlotTest.zip](https://download.csdn.net/download/Bing_Lee/12829241)

可以通过继承QCustomPlot类并重载其mouseMoveEvent()函数来实现光标跟随鼠标移动的效果。具体实现步骤如下: 1. 在继承的子类头文件中声明一个QCPItemStraightLine类型的指针,用于指向光标对象。 ```c++ class MyCustomPlot : public QCustomPlot { Q_OBJECT public: explicit MyCustomPlot(QWidget *parent = nullptr); protected: void mouseMoveEvent(QMouseEvent *event) override; private: QCPItemStraightLine *mCursorLine; }; ``` 2. 在子类实现文件中构造函数中初始化光标对象,并将其添加到绘图区域中。 ```c++ MyCustomPlot::MyCustomPlot(QWidget *parent) : QCustomPlot(parent) { // 初始化光标对象 mCursorLine = new QCPItemStraightLine(this); mCursorLine->setPen(QPen(Qt::red)); mCursorLine->setLayer("overlay"); // 设置光标在顶层 // 将光标对象添加到绘图区域 addItem(mCursorLine); } ``` 3. 在重载的mouseMoveEvent()函数中,根据鼠标位置光标对象的位置。 ```c++ void MyCustomPlot::mouseMoveEvent(QMouseEvent *event) { // 获取鼠标在绘图区域中的位置 QPointF pos = event->localPos(); // 光标对象的位置 mCursorLine->point1->setCoords(pos.x(), yAxis->range().lower); mCursorLine->point2->setCoords(pos.x(), yAxis->range().upper); // 重新绘制绘图区域 replot(); // 将事件传递给父类处理 QCustomPlot::mouseMoveEvent(event); } ``` 通过上述步骤,就可以实现不用UI的形式给QCustomPlot添加光标,使得光标能够跟随鼠标移动了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

机器人梦想家

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值