Q3DSurface selected point signal - c++

I am using QtDataVisualization (Q3DSurface in particular) to make a simple 3D surface graph.
The Q3DSurface supports selection of a point on the graph by showing a highlighted ball on the data point where the user has clicked. The selection pointer shows the coordinates of the point. It looks like this: surface with selected point
However, I'm not able to find a signal that is emitted when the selection happens. Having read through the documentation of Q3DSurface and QSurface3DSeries, I failed to find any corresponding signal. There is only a selectedPointChanged(const QPoint &position) in QSurface3DSeries, but it operates with a two-dimensional QPoint which is not suitable for the case.
What I am trying to do is store a history of selected points, that is why I need such a signal to keep track of previous coordinates. I tried looking into implementing a custom Q3DInputHandler, but not sure that it can resolve the issue. I would be grateful for any advice on the solution.

The desired signal is itemLabelChanged(const QString &label) in QAbstract3DSeries
It is emitted when a label, which represents the chosen point's coordinates, is changed. By connecting this signal to the slot, it is possible to retrieve the label's text (const QString &label parameter).

A good solution can be found here
Briefly, it is possible to get the 3D position by using const QSurfaceDataItem *QSurfaceDataProxy::itemAt(const QPoint &position) being position the value provided by the QAbstract3DGraph signal selectedElementChanged(QAbstract3DGraph::ElementType type).
For example:
Q3DSurface* surface = new Q3DSurface();
surface->setSelectionMode(QAbstract3DGraph::SelectionRowAndColumn);
QSurface3DSeries* surfaceSeries = new QSurface3DSeries();
surfaceSeries->dataProxy()->resetArray(dataArray); // QSurfaceDataArray* dataArray containing the surface data
surface->addSeries(surfaceSeries);
QObject::connect(surfaceSeries, &QSurface3DSeries::selectedPointChanged, this, [surfaceSeries](const QPoint &pos)
{
if ((pos.x() >= 0) && (pos.y() >= 0)) {
QVector3D vector = surfaceSeries->dataProxy()->itemAt(pos)->position();
qDebug() << vector;
}
});

Related

Get mouse coordinates in QChartView's axis system

Is there a way to get mouse's coordinates on plotting area of a QChartView? Preferably in the axis units. The goal is to display mouse's coordinates while moving the mouse around on the plot so the user can measure plotted objects.
I couldn't find any built in function for this on QChartView, so I'm trying to use QChartView::mouseMoveEvent(QMouseEvent *event) to try and calculate the resulting position in the plotting area. The problem is I can't get any reference to the plot area's coordinate system.
I've tried using mapToScene, mapToItem and mapToParent and also the reverse mapFrom... on all objects I can grab a hold of to try to do this, but to no avail.
I've found that QChartView::chart->childItems()[2] is indeed the plotting area, excluding the axis and axis labels. I can then call QChartView::chart->childItems()[2]->setCursor(Qt::CrossCursor) to make a cross appear only on the plotting area and not on the adjacent objects. But still, nothing I try seems to make a correct reference to this object's coordinate system.
QChartView is simply a QGraphicsView with an embedded scene(). To get coordinates within any of the charts, you have to go through several coordinate transformations:
Start with the view widget coordinates
view->mapToScene: widget (view) coordinates → scene coordinates
chart->mapFromScene: scene coordinates → chart item coordinates
chart->mapToValue: chart item coordinates → value in a given series.
End with value coordinates in a given series.
The term "chart item" and "chart widget" are synonyms, since QChart is-a QGraphicsWidget is-a QGraphicsItem. Note that QGraphicsWidget is not a QWidget!
Implementing it like this works like a charm (thanks, Marcel!):
auto const widgetPos = event->localPos();
auto const scenePos = mapToScene(QPoint(static_cast<int>(widgetPos.x()), static_cast<int>(widgetPos.y())));
auto const chartItemPos = chart()->mapFromScene(scenePos);
auto const valueGivenSeries = chart()->mapToValue(chartItemPos);
qDebug() << "widgetPos:" << widgetPos;
qDebug() << "scenePos:" << scenePos;
qDebug() << "chartItemPos:" << chartItemPos;
qDebug() << "valSeries:" << valueGivenSeries;

How to appropriately get position of QGraphicsRectItem after drag-release?

I wanted to have an online monitoring system that could tell where the shape is currently, but am getting very weird coordinates of the item, also the dimensions of it get higher by 1 each time I create new one and drag it.
Initial position (map size is 751 by 751, checked by outputting to qDebug(), scene bound to yellow space) :
Dragging it to the left top corner.
As you can see in the beginning it was on (200;200), but after dragging it is on (-201;-196). After deleting it and creating new shape on the same position with the same properties, new shape can't be seen because it is outside of the map, which suggests that edits don't show correct data.
Here is the code of updating the edits:
void CallableGraphicsRectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent* event)
{
QGraphicsRectItem::mouseReleaseEvent(event);
ptr->updateEdits(this);
}
Here is what I managed to cut down into updateEdits():
void MainWindow::updateEdits(QAbstractGraphicsShapeItem* item)
{
//stuff not related to scene
auto posReal = item->scenePos();
auto pos = posReal.toPoint();
//create QString from coordinates
QString coordinate;
coordinate.setNum(pos.x());
ui->leftXEdit->setText(coordinate);
coordinate.setNum(pos.y());
ui->upperYEdit->setText(coordinate);
//get width and height for rect, radius for circle
auto boundingRectReal = item->sceneBoundingRect();
auto boundingRect = boundingRectReal.toRect();
ui->widthEdit->setText(QString::number(boundingRect.width()));
//disables height edit for circles, not really relevant
if (!items[currentShapeIndex].isRect)
{
ui->heightEdit->setDisabled(true);
}
else
{
ui->heightEdit->setDisabled(false);
ui->heightEdit->setText(QString::number(boundingRect.height()));
}
}
Here is how I anchor the QGraphicsScene to the left top corner of the yellow area:
scene->setSceneRect(0, 0, mapSize.width() - 20, mapSize.height() - 20);
ui->graphicsView->setScene(scene);
How can I report the right data to the edits?
You're better off overriding the itemChange method and using the ItemPositionHasChanged notification. You have to set the ItemSendsGeometryChanges flag on the item so that it receives these notifications.
I'm not sure that your item's final position has been set when you're still in the mouseReleaseEvent method. Tracking it in itemChange will ensure that the data is valid, and this kind of thing is what it's for.
Also, note that "pos" is in the item's parent coordinates, and "boundingRect" is in the item's coordinate space. You should use "scenePos" and "sceneBoundingRect" if you want to be sure you're using scene coordinates. If the item doesn't have a parent, then "pos" and "scenePos" will return the same values, but "boundingRect" and "sceneBoundingRect" will generally differ.

Constraining child QGraphicsItem to scene?

Does anyone have a better way to constrain a child of a QGraphicsItem to a scene?
I have successfully properly constrained a parent QGraphicsItem to its scene by overriding itemChange, but now I need to do the same for the child QGraphicsItem.
Example Use-case:
This code works... for the most part. The only problem is the QGraphicsItem's velocity when hitting either side will affect its endstop position:
QVariant SizeGripItem::HandleItem::itemChange(GraphicsItemChange change,
const QVariant &value)
{
QPointF newPos = value.toPointF();
if (change == ItemPositionChange)
{
if(scene())
{
newPos.setY(pos().y()); // Y-coordinate is constant.
if(scenePos().x() < 0 ) //If child item is off the left side of the scene,
{
if (newPos.x() < pos().x()) // and is trying to move left,
{
newPos.setX(pos().x()); // then hold its position
}
}
else if( scenePos().x() > scene()->sceneRect().right()) //If child item is off the right side of the scene,
{
if (newPos.x() > pos().x()) //and is trying to move right,
{
newPos.setX(pos().x()); // then hold its position
}
}
}
}
return newPos;
}
For the parent item, I used:
newPos.setX(qMin(scRect.right(), qMax(newPos.x(), scRect.left())));
which worked perfectly, but I'm stumped as to how or if I could use that here.
First, to be specific, scenes effectively have no boundaries. What you're trying to do is constrain the item to the scene rectangle that you've set elsewhere.
The problem I see is in your use of scenePos. This is an ItemPositionChange; the item's scenePos hasn't been updated with the new position yet, so when you check for scenePos being out of the scene rect, you're really checking the result of the last position change, not the current one. Because of that, your item ends up just off the edge of the scene rectangle and then sticks there. How far off the edge depends on how fast you were moving the mouse, which dictates how much distance there is between ItemPositionChange notifications.
Instead, you need to compare the new position to the scene rectangle and then restrict the value that gets returned to be within the scene rectangle. You need the new position in scene coordinates to do the comparison, so you need something like:
QPoint new_scene_pos = mapToScene (new_pos);
if (new_scene_pos.x() < scene()->sceneRect().left())
{
new_scene_pos.setX (scene()->sceneRect().left());
new_pos = mapFromScene (new_scene_pos);
}
This isn't complete code, obviously, but these are the conversions and checks you need to do to keep it in on the left side. The right side is very similar, so just use the new_scene_pos for the comparison there.
Note that I didn't assume that the left edge of sceneRecT is at 0. I'm sure that's what you coded where you set the sceneRect, but using the actual left value rather than assuming it's 0 eliminates any problems if you end up later changing the range of scene coordinates you're planning to work with.
I used "left" instead of "x" on the sceneRect call just because it parallels using "right" for the other side. They're exactly the same, but I think it reads slightly better in this case.

How to set the coordinate system?

I have a class MaskGraphicsWidget.cpp inheriting from QGraphicsView which contains several functions, the constructor is:
MaskGraphicsWidget::MaskGraphicsWidget(QTreeWidget * tree, QWidget* parent) : QGraphicsView(parent), m_tree_widget(tree){
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_mask_scene = new QGraphicsScene;
m_mask_scene->setSceneRect(0, 0, 250, 250);
setScene(m_mask_scene);
primitive = 0;
minX = 0;
minY = 0;
}
where I initialize my scene m_mask_scene and I set the coordinate system.
I also have a function MousePressEvent :
http://pastebin.com/wjwDTQzw
And my MaskGraphicsWidget.h is like that :
QGraphicsScene* m_mask_scene;
QList<QPointF> m_polygon_points;
QList<QGraphicsLineItem*> m_polygon_lines;
int minX;
int minY;
My problem is that, I want to set the Pos of the QGraphicsPolygonItem poly but if I set it to the value min and max (that I calculate somewhere else), the item moves from the current Pos to the Pos of min and max. Basically it sets the Pos of the item in his own coordinate system. How can I write that I want to set the Pos in the coordinate system of the m_mask_scene ?
Sorry for my English, if you did not understand, feel free to add a comment !
Edit 1 :
m_polygon_points and m_polygon_lines are filled somewhere else and it's working. (see pastebin)
Edit 2 : Added the plan, see below for better (I hope) understanding !
Explanation of the plan : the black polygon is what I got (I do not set any Pos), but if I do set the Pos at min and max, I get the red polygon. I want to set the Pos at min and max AND still have the black polygon ! Sorry for my poor paint skills
Edit 3 : Of course, if I print the Pos of the QGraphicsPolygonItem poly, it shows (0,0).
Edit 4 : Added a pastebin to not overload the post, I've put everything in it, please ignore the PrimitiveItem things, and the case 0 and 1 which are not for the QGraphicsPolygonItem.
Edit 5 : To clarify, I catch the event on MousePressEvent:
if it's a left click, I use it to create a new point (creating new lines for my polygon)
if it's a right click, I don't use the event but instead I just close my polygon, delete all the lines added to the scene and adding the polygon in the scene.
QPointF point(event->x(), event->y());
With event being a QMouseEvent, a call to x() and y() returns coordinates relative to the widget that receives the event. In this case, MaskGraphicsWidget, which is a QGraphicsView.
If you add an item to a scene and want to set its position, you're setting its position relative to its parent. In the case of an item with no parent, the position is relative to the scene (in scene coordinates).
So your current code is trying to set view coordinates, not scene coordinates. You need to convert the position of the mouse coordinates to the scene: -
QPointF scenePoint = mapToScene(event->pos());
Then use this to set the position of the newly created item.
Note that a QGraphicsView is like a window looking into a world (the QGraphicsScene), so it may not match the scene coordinates.

Dynamically add QGraphicsEllipseItems

Basically, each time I click on a graphicsview, I'd like a new QGraphicsEllipseItem to appear. The number is up to the user. Also, I'd like to query all of them in the end with pos() to get all their positions. The number of ellipses is not known before hand and their positions are movable by the ItemIsMovable flag. Anyone know how could I do this?
I could make an array of pointers to the graphicsitem class but that would possibly waste memory and limit the number of ellipses I can make. Thanks.
You can add as much items as you want to the scene (as long as there is free memory space, of course):
myGraphicsScene->addEllipse(rect, pen, brush);
In order to add items for each click, reimplement the mousePressEvent of the QGraphicsView:
void MyGraphicsView::mousePressEvent(QMouseEvent *e)
{
int rx = 10; // radius of the ellipse
int ry = 20;
QRect rect(e->x() - rx, e->y() - ry, 2*rx, 2*ry);
scene()->addEllipse(rect, pen, brush);
// call the mousePressEvent of the super class:
QGraphicsView::mousePressEvent(e);
}
You don't have to store the pointers by yourself. When you want to query some piece of information of all items in the scene, just loop over the list of items provided by the scene:
foreach(QGraphicsItem *item, myGraphicsScene->items())
qDebug() << "Item geometry =" << item->boundingRect();
(or for the position only: item->pos())
If you want to query a piece of information of a subclass of QGraphicsItem, you can cast the item to your type using Qt's QGraphicsItem casting mechanism, which returns a null pointer if the item isn't of the requested type. After checking the pointer to be not null, you can access your own member:
foreach(QGraphicsItem *item, myGraphicsScene->items())
{
MyDerivedItem *derivedItem = qgraphicsitem_cast<MyDerivedItem*>(item);
if(derivedItem) // check success of QGraphicsItem cast
qDebug() << derivedItem->yourCustomMethod();
}