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;
Related
I have built a small custom qml item that is used as a selection area (something like the QRubberBand component provided in Qt Widgets). The item also give the ability to user to resize the content of the selection, so by grabbing the bottom corner of the selection rectangle it is possible to drag to enlarge the content. After the user has done resizing I would like to compute the QTransform matrix of the transformation. QTransform provides a convenient QTransform::scale method to get a scale transformation matrix (which I can use by comparing the width and height ratio with the previous size of the selection). The problem is that QTransform::scale assumes that the center point of the transformation is the center of the object, but I would like my transformation origin to be the top left of the selection (since the user is dragging from the bottom-right).
So for example, if I have the following code:
QRectF selectionRect = QRectF(QPointF(10,10), QPointF(200,100));
// let's resize the rectangle by changing its bottom-right corner
auto newSelectionRect = selectionRect;
newSelectionRect.setBottomRight(QPointF(250, 120));
QTransform t;
t.scale(newSelectionRect.width()/selectionRect.width(), newSelectionRect.height()/selectionRect.height());
The problem here is that if I apply the transformation t to my original selectionRect I don't get my new rectangle newSelectionRect back, but I get the following:
QRectF selectionRect = QRectF(QPointF(10,10)*sx, QPointF(200,100)*sy);
where sx and sy are the scale factors of the transform. I would like a way to compute the QTransform of my transformation that gives back newSelectionRect when applied to selectionRect.
The problem lies in this assumption:
QTransform::scale assumes that the center point of the transformation is the center of the object
All transformations performed by QTransform are referred to the origin of the axis, is just an application of various tranformation matrixes (https://en.wikipedia.org/wiki/Transformation_matrix):
Also, QTransform::translate (https://doc.qt.io/qt-5/qtransform.html#translate) states:
Moves the coordinate system dx along the x axis and dy along the y axis, and returns a reference to the matrix.
Thereby, what you are looking for is:
QTransform t;
t.translate(+10, +10); // Move the origin to the top left corner of the rectangle
t.scale(newSelectionRect.width()/selectionRect.width(), newSelectionRect.height()/selectionRect.height()); // scale
t.translate(-10, -10); // move the origin back to where it was
QRectF resultRect = t.mapRect(selectionRect); // resultRect == newSelectionRect!
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;
}
});
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.
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.
I need to get x and y coordinates of mouse click on QwtPlot drawing area (not the whole widget!). The problem is that there's only method bool event(QEvent*) that is called on every event. I've found some solutions (http://www.qtcentre.org/archive/index.php/t-9502.html), using QwtPlotPicker but that doesn't work for me, I'm using Qwt 6 and there are no such methods like setSelectionState().
What is other methods for achieving mouse click events on drawing area in QwtPlot?
There's been some changes in Qwt 6 comparing to 5.
Now we need to set state machine using QwtPlotPicker::setStateMachine(QwtPickerMachine) method. There are a few options (derived classes):
QwtPickerClickPointMachine
QwtPickerClickRectMachine
QwtPickerDragPointMachine
QwtPickerDragRectMachine
QwtPickerPolygonMachine
QwtPickerTrackerMachine
depending on our needs.
Next thing we need to do is connect() signal selected(...) from QwtPlotPicker with our custom slot where we can obtain x and y coordinates or other interesting data.
I'm capturing the mouse events using the mouse event methods in a subclass of QwtPlotCanvas, and using the conversion methods in QwtPlot to map the mouse event x and y coordinates to the values they represent in the plot.
Create a subclass of QwtPlotCanvas and override the mousePressEvent method like this.
void SpecialMapPlotCanvas::mousePressEvent (QMouseEvent* event) {
QWidget::mousePressEvent (event);
double x = plot() -> invTransform (plot() -> xBottom, event -> pos().x());
double y = plot() -> invTransform (plot() -> yLeft, event -> pos().y());
std::cout << "Values " << x << " " << y << "\n";
}
Then set the canvas on the QwtPlot by instantiating an object of this class and passing it to QwtPlot::setCanvas. Then SpecialMapPlotCanvas::plot() gives you a reference to the owning QwtPlot, and its invTransform methods can be used to convert the click coordinates to plot values. If you use the mouse events on QwtPlot itself you get the wrong answers because the mouse event coordinates here relate to the whole QwtPlot widget area (as you say), not just the canvas.