【问题标题】:Showing tooltip in a Qt chart with multiple y axes在具有多个 y 轴的 Qt 图表中显示工具提示
【发布时间】:2018-12-07 16:52:55
【问题描述】:

我正在努力实现永久工具提示功能,即在悬停时显示工具提示,并使工具提示在点击我的图表时永久保持,其中我有一个线系列和样条系列。这基本上是 Qt Callout 示例的不同版本,这里是the link。我实现的唯一更改是我使用 QValueAxis 在左侧添加了两个 y 轴,每个系列一个。它们共享相同的 x 轴。现在,当我尝试运行程序时,工具提示对于直线曲线正确显示,但在样条曲线的情况下它会错位。它出现在我将鼠标指针悬停在曲线上的点下方的某个距离处。

当我通过 createdefaultaxes() 使用单个 y 轴时,不会出现此问题。

单y轴结果

具有多个 y 轴的结果

我已经在下面发布了与点击和悬停功能相关的代码。此代码中使用了“Callout”类的对象,所以这里是code for Callout(与我之前发布的链接相同)。我无法弄清楚哪些标注代码的 sn-ps 与此问题最相关,因此我不得不发布链接。

// chart
    m_chart = new QChart;

    QLineSeries *series = new QLineSeries;
    //appended some (x,y) values to series and added to chart
    m_chart->addSeries(series);

    QSplineSeries *series2 = new QSplineSeries;
    //appended some (x,y) values to series2 and added to chart
    m_chart->addSeries(series2);

    //m_chart->createDefaultAxes(); for single y axis
    QValueAxis *axisX = new QValueAxis; 
    QValueAxis *axisY1 = new QValueAxis;  //for multiple y axes
    QValueAxis *axisY2 = new QValueAxis;

    m_chart->addAxis(axisX, Qt::AlignBottom);
    m_chart->addAxis(axisY1, Qt::AlignLeft);
    m_chart->addAxis(axisY2, Qt::AlignLeft);

    series->attachAxis(axisX);
    series->attachAxis(axisY1);
    series2->attachAxis(axisX);
    series2->attachAxis(axisY2);

    m_chart->setAcceptHoverEvents(true);

    setRenderHint(QPainter::Antialiasing);
    scene()->addItem(m_chart);

    connect(series, &QLineSeries::clicked, this, &View::keepCallout);
    connect(series, &QLineSeries::hovered, this, &View::tooltip);

    connect(series2, &QSplineSeries::clicked, this, &View::keepCallout);
    connect(series2, &QSplineSeries::hovered, this, &View::tooltip);

    this->setMouseTracking(true);

// keepCallout() function
    {
     m_callouts.append(m_tooltip);//m_callouts is a QList of "Callout" class type
     m_tooltip = new Callout(m_chart);
    }

//tooltip(QPointF point, bool state) function
{
    if (m_tooltip == 0)
        m_tooltip = new Callout(m_chart);

    if (state) {
        m_tooltip->setText(QString("X: %1 \nY: %2").arg(point.x()).arg(point.y()));
        m_tooltip->setAnchor(point);
        m_tooltip->setZValue(11);
        m_tooltip->updateGeometry();
        m_tooltip->show();
  } else {
        m_tooltip->hide();
  }
}

【问题讨论】:

    标签: c++ qt qt5 qtcharts


    【解决方案1】:

    mapToPosition() 返回给定位置的值,该值取决于它们所属的系列,在您的情况下有 2 个轴,此值需要明确指出系列。为此,callout 必须具有存储系列的属性。

    view.cpp

    ...
    void View::keepCallout()
    {
        QAbstractSeries *series = qobject_cast<QAbstractSeries *>(sender());
        m_callouts.append(m_tooltip);
    
        m_tooltip = new Callout(m_chart, series);
        m_tooltip->hide();
    }
    
    void View::tooltip(QPointF point, bool state)
    {
        QAbstractSeries *series = qobject_cast<QAbstractSeries *>(sender());
        if (m_tooltip == 0){
            m_tooltip = new Callout(m_chart, series);
        }
        if (state) {
            m_tooltip->setSeries(series);
            m_tooltip->setText(QString("X: %1 \nY: %2 ").arg(point.x()).arg(point.y()));
            m_tooltip->setAnchor(point);
            m_tooltip->setZValue(11);
            m_tooltip->updateGeometry();
            m_tooltip->show();
        } else {
            m_tooltip->hide();
        }
    }
    

    callout.h

    #ifndef CALLOUT_H
    #define CALLOUT_H
    
    #include <QtCharts/QChartGlobal>
    #include <QtWidgets/QGraphicsItem>
    #include <QtGui/QFont>
    QT_BEGIN_NAMESPACE
    class QGraphicsSceneMouseEvent;
    QT_END_NAMESPACE
    
    QT_CHARTS_BEGIN_NAMESPACE
    class QChart;
    class QAbstractSeries;
    QT_CHARTS_END_NAMESPACE
    
    QT_CHARTS_USE_NAMESPACE
    
    class Callout : public QGraphicsItem
    {
    public:
        Callout(QChart *chart, QAbstractSeries *series);
    
        void setText(const QString &text);
        void setAnchor(QPointF point);
        void updateGeometry();
    
        QRectF boundingRect() const;
        void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
        void setSeries(QAbstractSeries *series);
    
    protected:
        void mousePressEvent(QGraphicsSceneMouseEvent *event);
        void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    
    private:
        QString m_text;
        QRectF m_textRect;
        QRectF m_rect;
        QPointF m_anchor;
        QFont m_font;
        QChart *m_chart;
        QAbstractSeries *m_series;
    };
    
    #endif // CALLOUT_H
    

    callout.cpp

    #include "callout.h"
    #include <QtGui/QPainter>
    #include <QtGui/QFontMetrics>
    #include <QtWidgets/QGraphicsSceneMouseEvent>
    #include <QtGui/QMouseEvent>
    #include <QtCharts/QChart>
    
    Callout::Callout(QChart *chart, QAbstractSeries *series):
        QGraphicsItem(chart),
        m_chart(chart),
        m_series(series)
    {
    }
    
    QRectF Callout::boundingRect() const
    {
        QPointF anchor = mapFromParent(m_chart->mapToPosition(m_anchor, m_series));
        QRectF rect;
        rect.setLeft(qMin(m_rect.left(), anchor.x()));
        rect.setRight(qMax(m_rect.right(), anchor.x()));
        rect.setTop(qMin(m_rect.top(), anchor.y()));
        rect.setBottom(qMax(m_rect.bottom(), anchor.y()));
        return rect;
    }
    
    void Callout::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
    {
        Q_UNUSED(option)
        Q_UNUSED(widget)
        QPainterPath path;
        path.addRoundedRect(m_rect, 5, 5);
    
        QPointF anchor = mapFromParent(m_chart->mapToPosition(m_anchor, m_series));
        if (!m_rect.contains(anchor)) {
            QPointF point1, point2;
    
            // establish the position of the anchor point in relation to m_rect
            bool above = anchor.y() <= m_rect.top();
            bool aboveCenter = anchor.y() > m_rect.top() && anchor.y() <= m_rect.center().y();
            bool belowCenter = anchor.y() > m_rect.center().y() && anchor.y() <= m_rect.bottom();
            bool below = anchor.y() > m_rect.bottom();
    
            bool onLeft = anchor.x() <= m_rect.left();
            bool leftOfCenter = anchor.x() > m_rect.left() && anchor.x() <= m_rect.center().x();
            bool rightOfCenter = anchor.x() > m_rect.center().x() && anchor.x() <= m_rect.right();
            bool onRight = anchor.x() > m_rect.right();
    
            // get the nearest m_rect corner.
            qreal x = (onRight + rightOfCenter) * m_rect.width();
            qreal y = (below + belowCenter) * m_rect.height();
            bool cornerCase = (above && onLeft) || (above && onRight) || (below && onLeft) || (below && onRight);
            bool vertical = qAbs(anchor.x() - x) > qAbs(anchor.y() - y);
    
            qreal x1 = x + leftOfCenter * 10 - rightOfCenter * 20 + cornerCase * !vertical * (onLeft * 10 - onRight * 20);
            qreal y1 = y + aboveCenter * 10 - belowCenter * 20 + cornerCase * vertical * (above * 10 - below * 20);;
            point1.setX(x1);
            point1.setY(y1);
    
            qreal x2 = x + leftOfCenter * 20 - rightOfCenter * 10 + cornerCase * !vertical * (onLeft * 20 - onRight * 10);;
            qreal y2 = y + aboveCenter * 20 - belowCenter * 10 + cornerCase * vertical * (above * 20 - below * 10);;
            point2.setX(x2);
            point2.setY(y2);
    
            path.moveTo(point1);
            path.lineTo(anchor);
            path.lineTo(point2);
            path = path.simplified();
        }
        painter->setBrush(QColor(255, 255, 255));
        painter->drawPath(path);
        painter->drawText(m_textRect, m_text);
    }
    
    void Callout::mousePressEvent(QGraphicsSceneMouseEvent *event)
    {
        event->setAccepted(true);
    }
    
    void Callout::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
    {
        if (event->buttons() & Qt::LeftButton){
            setPos(mapToParent(event->pos() - event->buttonDownPos(Qt::LeftButton)));
            event->setAccepted(true);
        } else {
            event->setAccepted(false);
        }
    }
    
    void Callout::setSeries(QAbstractSeries *series)
    {
        m_series = series;
    }
    
    void Callout::setText(const QString &text)
    {
        m_text = text;
        QFontMetrics metrics(m_font);
        m_textRect = metrics.boundingRect(QRect(0, 0, 150, 150), Qt::AlignLeft, m_text);
        m_textRect.translate(5, 5);
        prepareGeometryChange();
        m_rect = m_textRect.adjusted(-5, -5, 5, 5);
    }
    
    void Callout::setAnchor(QPointF point)
    {
        m_anchor = point;
    }
    
    void Callout::updateGeometry()
    {
        prepareGeometryChange();
        setPos(m_chart->mapToPosition(m_anchor, m_series) + QPoint(10, -50));
    }
    

    完整代码可以在以下link

    【讨论】:

    • 非常感谢您的帮助!它完美地工作。此外,您可能想要更正您发布的链接...网址格式错误。我仍然通过在 github 上搜索你的用户名找到了完整的代码,所以谢谢。
    • 出于好奇,这段代码是否适用于两个以上的 y 轴?如...我通过在图表中添加另一个样条系列来尝试 3 y 轴,但工具提示无法正常工作。我想我需要在代码中的某处进行一些更改才能这样做?
    • 是的,我执行不正确。我忘记添加调用 setSeries() 函数的行。谢谢。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-26
    • 1970-01-01
    • 1970-01-01
    • 2020-04-03
    相关资源
    最近更新 更多