Display Text On QtCharts - c++

I am using the QtCharts class to make a line graph. I want to be able to add note/text on the graph itself. How can I do this? I found a example similar to what I want " https://doc.qt.io/qt-5/qtcharts-callout-example.html" is there a easier way?

This is my version. There may be errors
textitem.h
#pragma once
#include <QtCharts/QChartGlobal>
#include <QtWidgets/QGraphicsItem>
QT_CHARTS_BEGIN_NAMESPACE
class QChart;
class QAbstractSeries;
QT_CHARTS_END_NAMESPACE
QT_CHARTS_USE_NAMESPACE
class TextItem : public QGraphicsItem {
public:
TextItem(QString text, QPoint pos, QChart *chart, QAbstractSeries *series);
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
void setText(const QString &text);
void setAnchor(QPointF point);
private:
QChart *_chart;
QAbstractSeries *_series;
QString _text;
QRectF _textRect;
QPointF _anchor;
};
textitem.cpp
#include "textitem.h"
#include <QtCharts/QChart>
#include <QPainter>
#include <QRect>
TextItem::TextItem(QString text, QPoint pos, QChart *chart, QAbstractSeries *series)
: QGraphicsItem(chart), _chart(chart), _series(series), _anchor(pos) {
setText(text);
}
QRectF TextItem::boundingRect() const {
QPointF anchor = mapFromParent(_chart->mapToPosition(_anchor, _series));
QRectF rect;
rect.setLeft(qMin(_textRect.left(), anchor.x()));
rect.setRight(qMax(_textRect.right(), anchor.x()));
rect.setTop(qMin(_textRect.top(), anchor.y()));
rect.setBottom(qMax(_textRect.bottom(), anchor.y()));
return rect;
}
void TextItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
Q_UNUSED(option)
Q_UNUSED(widget)
QPointF anchor = mapFromParent(_chart->mapToPosition(_anchor, _series));
painter->drawText(anchor, _text);
}
void TextItem::setText(const QString &text) {
_text = text;
QFontMetrics metrics((QFont()));
_textRect = metrics.boundingRect(QRect(0, 0, 150, 150),
Qt::AlignLeft, _text);
_textRect.translate(5, 5);
prepareGeometryChange();
}
void TextItem::setAnchor(QPointF point) { _anchor = point; }

I know it's a late reply and not anymore relevant for you, though it might be for others:
As far as I can see, they already use a pretty easy way of doing this.
In their example, they use the QGraphicsTextItem to add text at a specific position in a chart.
Assume you have a series and want to add text to each point in this series which is visible in the chart.
Here is an (python PySide6) example of how you can do it (you can do it the same way in c++):
chart = QChart() # some chart
series = QScatterSeries() # some series which has been added to the chart
# create text items and add to chart
my_text_items = []
for i in range(series.count()):
text_item = QGraphicsTextItem("point with idx: {}".format(i), parent=chart)
my_text_items.append(text_item)
# define function to set/update the position of the text items (has to be called during resize)
def update_position_of_text_items(text_items, series):
for i, text_item in enumerate(text_items):
# get point at index i
point = series.at(i)
# map its position of the series to the position in the chart
position_in_chart = series.chart().mapToPosition(point, series)
# set the position of the text item at index i accordingly
text_item.setPos(position_in_chart)
update_position_of_text_items(my_text_items, series)
Make sure to call the update_position_of_text_items(..) when resizing your widget.

Related

How to use QPainter::setTransform in paint() in Qt?

I have a simple QGraphicsView with text written over it.That text is wrapped under a bounding rect. I have truncated a text using QFontMetrics::elidedText.
When initially view gets loaded, it shows all truncated text properly. But when I zoom-in only few truncated text gets visible. If for few more times I zoomed-in, then some times it shows all the truncated text and some times , it never shows any truncated text.
Before truncated text code gets added, zoom-in was working properly.
widget.cpp
void widget::ZoomIn()
{
double scaleFactor = 1.1;
view->scale(scaleFactor,scaleFactor);
}
void Widget::on_textButton_clicked()
{
//logic to get string s and its co-ordinates firstPos and secondPos
Text* t = new Text() ;
QGraphicsTextItem* text = t->drawText(s,firstPos,secondPos,50,40);
scene->addItem(text);
}
Text.cpp
Text::Text(const QString &text,int left,int top,int width,int height)
: QGraphicsTextItem (text),currentString(text),
Left(left),Top(top),Width(width),Height(height)
{}
Text::Text(){}
QGraphicsTextItem *Text::drawText(QString s, int left,int top,int width,int height)
{
QGraphicsTextItem *text = new Text(s,left,top,width,height);
return text;
}
void Text::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
Q_UNUSED(option);
Q_UNUSED(widget);
QFont defaultFont = QApplication::font();
QFontMetrics fm = QFontMetrics(defaultFont);
QRect rect = QRect(Left,Top,Width,Height);
QString elidedText = fm.elidedText(currentString, Qt::ElideLeft,rect.width());
QTransform trans = view->transform();
QTransform prevTrans;
painter->setTransform(trans);
painter->drawText(rect,Qt::AlignRight,elidedText);
painter->setTransform(prevTrans);
}
I understood that, in zoom-in I am changing transformation and painter is drawing on old transformation. So I have to take transformation from view and set it with painter then drawText() and then set painter transformation as previous transformation.
I did , but still problem persist.

Sort Arrows Disappear When Subclassing QHeaderView

I am subclassing QHeaderView to add a filtering icon in the horizontal header of a QTableView. The QTableView has sorting capability activated consume a QSortFilterProxyModel, until now it works fine.
However when I try to subclass QHeaderView and use it as column header, only the first column shows the filter icon.
headerview_filter.h
#ifndef HEADERVIEW_FILTER_H
#define HEADERVIEW_FILTER_H
#include <QHeaderView>
class HeaderView_Filter : public QHeaderView
{
Q_OBJECT
public:
explicit HeaderView_Filter(Qt::Orientation orientation, QWidget * parent = nullptr);
void paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const override;
private:
const QPolygonF _funel = QPolygonF({{22.0,36.0},{22.0,22.0},{10.0,10.0},{40.0,10.0},{28.0,22.0},{28.0,36.0}});
};
#endif // HEADERVIEW_FILTER_H
headerview_filter.cpp
#include "headerview_filter.h"
HeaderView_Filter::HeaderView_Filter(Qt::Orientation orientation, QWidget * parent)
: QHeaderView(orientation, parent)
{
setSectionsClickable(true);
}
void HeaderView_Filter::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
const double scale = 0.6*rect.height()/50.0;
painter->setBrush(Qt::black);
painter->translate(0,5);
painter->scale(scale, scale);
painter->drawPolygon(_funel);
painter->restore();
}
using it in form :
auto* tableView = _ui->tableView_Data;
tableView->setModel(_sortFilterProxyModel);
tableView->setSortingEnabled(true);
tableView->setHorizontalHeader(new HeaderView_Filter(Qt::Horizontal,tableView));
I found the solution while typing it and prefered to post the code for other to use.
The position of the drawing must be translated relative to the drawing rectangle provided as argument of paintSection :
void HeaderView_Filter::paintSection(QPainter *painter, const QRect &rect, int logicalIndex) const
{
painter->save();
QHeaderView::paintSection(painter, rect, logicalIndex);
painter->restore();
const double scale = 0.6*rect.height()/50.0;
painter->setBrush(Qt::black);
// Here
painter->translate(rect.x(),rect.y()+5);
//
painter->scale(scale, scale);
painter->drawPolygon(_funel);
painter->restore();
}

Custom QGraphicsItem That Contains Child QGraphicsItems

I am new at Qt and I want to write my custom QGraphicsItem which contains a rectangle and couple of buttons. I want to write a single custom component that could be easily added to QGraphicsScene and moved or resized with contents(buttons and rectangles) in it. In the end I want to add multiple customized QGraphicsItem to my QGraphicsScene. My question is how can I write this customized QGraphicsItem that contains buttons and rectangles which relative positions to each other are constant.
In this drawing green colored rectangles represent buttons and their relative position to each other always stays same (as if they are placed using qlayouts)
Thanks to #replete, from the example at http://doc.qt.io/qt-5/qtwidgets-graphicsview-dragdroprobot-example.html I was able to create a custom QGraphicsItem with clickable sub-parts in it. In code below BboxItem represents container QGraphicsItem and BboxItemContent represents childs of it. By emitting signals whith mause click events I was able to implement button like features. And I can move the BboxItem by setting its bounding rectangle.
BboxItem related source code:
BboxItemContent::BboxItemContent(QGraphicsItem *parent, int type, QColor color,QRectF *rect)
: QGraphicsObject(parent)
{
content_rectangle = rect;
content_type = type;
switch (type)
{
case 0:
rectangle_color = color;
icon = 0;
break;
case 1:
icon = new QImage(":/resource/assets/info_btn.png");
break;
case 2:
icon = new QImage(":/resource/assets/close_btn.png");
break;
}
}
BboxItemContent::~BboxItemContent()
{
delete icon;
}
QRectF BboxItemContent::boundingRect() const
{
return QRectF(content_rectangle->x(), content_rectangle->y(), content_rectangle->width(), content_rectangle->height());
}
void BboxItemContent::paint(QPainter *painter,
const QStyleOptionGraphicsItem *option, QWidget *widget)
{
if (icon == 0)
{
QPen pen(rectangle_color, 3);
painter->setPen(pen);
painter->drawRect(*content_rectangle);
}
else
{
painter->drawImage(*content_rectangle, *icon);
}
}
void BboxItemContent::mousePressEvent(QGraphicsSceneMouseEvent * event)
{
emit bboxContentClickedSignal();
}
void BboxItemContent::setRect(QRectF *rect)
{
content_rectangle = rect;
update();
}
BboxItem::BboxItem(QGraphicsItem *parent,QRectF *itemRect) : BboxItemContent(parent,0,Qt::red, itemRect)
{
setFlag(ItemHasNoContents);
bbox_area = new BboxItemContent(this, 0, Qt::red, itemRect);
info_btn = new BboxItemContent(this, 1, Qt::red, new QRectF(itemRect->x() - 30, itemRect->y(), 30, 30));
connect(info_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onInfoClickedSlot);
delete_btn= new BboxItemContent(this, 2, Qt::red, new QRectF((itemRect->x()+itemRect->width()), itemRect->y(), 30, 30));
connect(delete_btn, &BboxItemContent::bboxContentClickedSignal, this, &BboxItem::onDeleteClickedSlot);
}
void BboxItem::onDeleteClickedSlot()
{
//delete clicked actions
}
void BboxItem::onInfoClickedSlot()
{
//info clicked actions
}
void BboxItem::setRect(QRectF *rect)
{
bbox_area->setRect(rect);
info_btn->setRect(new QRectF(rect->x() - 30, rect->y(), 30, 30));
delete_btn->setRect(new QRectF((rect->x() + rect->width()), rect->y(), 30, 30));
}
Related Headers:
class BboxItemContent : public QGraphicsObject
{
Q_OBJECT
public:
BboxItemContent(QGraphicsItem *parent = 0, int type = 0, QColor color = Qt::red, QRectF *rect=nullptr);
~BboxItemContent();
// Inherited from QGraphicsItem
QRectF boundingRect() const override;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget = 0) override;
void setRect(QRectF *rect);
signals:
void bboxContentClickedSignal();
protected:
void mousePressEvent(QGraphicsSceneMouseEvent *event);
private:
QImage *icon;
QColor rectangle_color;
QRectF *content_rectangle;
int content_type;
};
class BboxItem : public BboxItemContent {
Q_OBJECT
public:
BboxItem(QGraphicsItem *parent = 0,QRectF *itemRect=nullptr);
void setRect(QRectF *rect);
private slots:
void onDeleteClickedSlot();
void onInfoClickedSlot();
private:
BboxItemContent *delete_btn;
BboxItemContent *bbox_area;
BboxItemContent *info_btn;
};

How to keep the size and position of QGraphicsItem when scaling the view?

I have some QGraphicsItems in the QGraphicsScene which should keep the same size and position when scaling. I've tried QGraphicsItem::ItemIgnoresTransformations but it turns out that the items get wrong positions. Below is a sample code:
I have subclassed QGraphicsView like this:
class Graphics : public QGraphicsView
{
public:
Graphics();
QGraphicsScene *scene;
QGraphicsRectItem *rect;
QGraphicsRectItem *rect2;
protected:
void wheelEvent(QWheelEvent *event);
};
And in its constructor:
Graphics::Graphics()
{
scene = new QGraphicsScene;
rect = new QGraphicsRectItem(100,100,50,50);
rect2 = new QGraphicsRectItem(-100,-100,50,50);
scene->addLine(0,200,200,0);
rect->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
scene->addItem(rect);
scene->addItem(rect2);
setScene(scene);
scene->addRect(scene->itemsBoundingRect());
}
The wheelEvent virtual function:
void Graphics::wheelEvent(QWheelEvent *event)
{
if(event->delta() < 0)
scale(1.0/2.0, 1.0/2.0);
else
scale(2, 2);
scene->addRect(scene->itemsBoundingRect());
qDebug() << rect->transform();
qDebug() << rect->boundingRect();
qDebug() << rect2->transform();
qDebug() << rect2->boundingRect();
}
orginal view looks like this:
1
take the line as road and rect aside as a symbol. When zoomed out, the rect maintain its size but jumps out of the scene:
2
which should be that topleft of rect to middle of line. I'm also confused with debug info showing that the boundingRect and transform stays the same, which seems that nothing has changed! What causes the problem and is there any way to solve it? Could someone help? Thank you!
Sorry for delay, now I've solved the problem myself.
I found QGraphicsItem::ItemIgnoresTransformations only works when the point you want stick to is at (0,0) in item's coordinate. You need also update boundingRect manually in this way. Nevertheless, the best solution I've found is subclass QGraphicsItem and set matrix in paint() according to world matrix. Below is my code .
QMatrix stableMatrix(const QMatrix &matrix, const QPointF &p)
{
QMatrix newMatrix = matrix;
qreal scaleX, scaleY;
scaleX = newMatrix.m11();
scaleY = newMatrix.m22();
newMatrix.scale(1.0/scaleX, 1.0/scaleY);
qreal offsetX, offsetY;
offsetX = p.x()*(scaleX-1.0);
offsetY = p.y()*(scaleY-1.0);
newMatrix.translate(offsetX, offsetY);
return newMatrix;
}
And the paint function:
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
QWidget *widget)
{
QPointF p(left, top);
painter->setMatrix(stableMatrix(painter->worldMatrix(), p));
painter->drawRect(left, top, width, height);
}
The second argument of stableMatrix is sticked point, in my sample code it's top-left of the item. You can change it to your preference. It works really fine!
Hope this post help :)
The solution to this is even simpler.
QGraphicsItem::ItemIgnoresTransformations
The item ignores inherited transformations (i.e., its position is still anchored to its parent, but the parent or view rotation, zoom or shear transformations are ignored). [...]
And that's the key! Item ignores all transformations, but is still bound to its parent. So you need two items: a parent item that will keep the relative position (without any flags set) and a child item that will do the drawing (with QGraphicsItem::ItemIgnoresTransformations flag set) at parent's (0,0) point.
Here is some working code of a crosshair that have constant size and rotation, while keeping the relative position to its parent:
#include <QGraphicsItem>
#include <QPainter>
class CrossHair : public QGraphicsItem
{
private:
class CrossHairImpl : public QGraphicsItem
{
public:
CrossHairImpl (qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_len(len)
{
setFlag(QGraphicsItem::ItemIgnoresTransformations);
}
QRectF boundingRect (void) const override
{
return QRectF(-m_len, -m_len, m_len*2, m_len*2);
}
void paint (QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) override
{
painter->setPen(QPen(Qt::red, 1));
painter->drawLine(0, -m_len, 0, m_len);
painter->drawLine(-m_len, 0, m_len, 0);
}
private:
qreal m_len;
};
public:
CrossHair (qreal x, qreal y, qreal len, QGraphicsItem *parent = nullptr)
: QGraphicsItem(parent), m_impl(len, this) // <-- IMPORTANT!!!
{
setPos(x, y);
}
QRectF boundingRect (void) const override
{
return QRectF();
}
void paint (QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override
{
// empty
}
private:
CrossHairImpl m_impl;
};

Calling Qpainter's method paint to refresh image and change color

I have created a object call Player which inherits from QGraphicsObject.
What I am trying to do is to change the image of the player and the colour of the bounding shape when I click on it with the mouse. The thing is i don't know what values to send to player->paint() to update the image.
I override the two pure virtual functions as follows
In player.h :
class Player : public QGraphicsObject
{
public:
Player();
QRectF boundingRect() const;
QPainterPath shape() const;
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget);
void mousePressEvent(QGraphicsSceneMouseEvent *event);
}
in player.cpp
Player::Player()
{
QPixmap m_playerPixmap(":/images/images/chevalier/test1.png");
}
QRectF Player::boundingRect() const
{
return QRectF(-15, 0, 128, 130);
}
QPainterPath Player::shape() const
{
QPainterPath path;
path.addEllipse(-15, 70, 100, 60);
return path;
}
void Player::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,QWidget *widget)
{
QPen linepen;
linepen.setWidth(5);
linepen.setColor(Qt::red);
painter->setPen(linepen);
painter->drawPath(shape());
painter->drawPixmap(0, 0, m_playerPixmap);
}
void Player::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
this->paint();
}
Thanks a lot for your help.
You have to call update().
This will mark the item to be updated, and issue a paint event, which will then call paint with the correct parameters