Display the plot values on mouse over. - Detect Scatter points - c++

I am attempting to display the plot values of different points on my QCustomPlot in which I have a Line style of lsLine. I know i could set a mouse over signal on the QCustomPlot but that wont really help since I just need to be informed when the mouse is over my plotted line.My question is is there any way to find out if the mouse is over my scatter point. Is there a signal i could connect to that would tell me when the mouse is over a scatter point ?

Reimplement QCustomPlot::mouseMoveEvent or connect to QCustomPlot::mouseMove.
Then use axes' coordToPixel to translate (cursor) pixel coords to plot coords and search nearest points in your QCPDataMap with QMap::lowerBound(cursorX).

You can easily just connect a slot to the mouseMove signal that QCustomPlot emits. You can then use QCPAxis::pixelToCoord to find the coordinate :
connect(this, SIGNAL(mouseMove(QMouseEvent*)), this,SLOT(showPointToolTip(QMouseEvent*)));
void QCustomPlot::showPointToolTip(QMouseEvent *event)
{
int x = this->xAxis->pixelToCoord(event->pos().x());
int y = this->yAxis->pixelToCoord(event->pos().y());
setToolTip(QString("%1 , %2").arg(x).arg(y));
}

when You use datetime format (including more point per second) of X axis, then pixel to coord fails.
If you want to display coordinates between points, then this is the fastest way
maybe usefull (with connected signal QCustomplot::MouseMove)
void MainWindow::onMouseMoveGraph(QMouseEvent* evt)
{
int x = this->ui->customPlot->xAxis->pixelToCoord(evt->pos().x());
int y = this->ui->customPlot->yAxis->pixelToCoord(evt->pos().y());
qDebug()<<"pixelToCoord: "<<data.key<<data.value; //this is correct when step is greater 1 second
if (this->ui->customPlot->selectedGraphs().count()>0)
{
QCPGraph* graph = this->ui->customPlot->selectedGraphs().first();
QCPData data = graph->data()->lowerBound(x).value();
double dbottom = graph->valueAxis()->range().lower; //Yaxis bottom value
double dtop = graph->valueAxis()->range().upper; //Yaxis top value
long ptop = graph->valueAxis()->axisRect()->top(); //graph top margin
long pbottom = graph->valueAxis()->axisRect()->bottom(); //graph bottom position
// result for Y axis
double valueY = (evt->pos().y() - ptop) / (double)(pbottom - ptop)*(double)(dbottom - dtop) + dtop;
//or shortly for X-axis
double valueX = (evt->pos().x() - graph->keyAxis()->axisRect()->left()); //graph width in pixels
double ratio = (double)(graph->keyAxis()->axisRect()->right() - graph->keyAxis()->axisRect()->left()) / (double)(graph->keyAxis()->range().lower - graph->keyAxis()->range().upper); //ratio px->graph width
//and result for X-axis
valueX=-valueX / ratio + graph->keyAxis()->range().lower;
qDebug()<<"calculated:"<<valueX<<valueY;
}
}

Related

How to keep mouse coordinates within window height/width limits

Suppose I have a 600 by 600 window created through glfwCreateWindow().
I have a few models rendered in the scene and to move around them in the 3D space I use a camera class which moves around with my cursor as well as AWSD/spacebar keys.
To make the movement "seamless" I use glfwSetInputMode(this->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED).
Now then the problem is that I am also trying to implent a mouse picker of sorts (casting ray from cursor position to the scene).
So I implemented the mouse picker from a video I found on yt, but it doesnt work properly. The reason being is that my mouse X and Y coordinates get bigger than width and height if for example I rotate on spot (and I can increase it this way indefinitely).
I understand that this is happening because I have no cursor to be bound within resolution limits due to glfwSetInputMode(this->window, GLFW_CURSOR, GLFW_CURSOR_DISABLED). I am asking how I should correct this so that I can keep the seamlessness as well as be able to limit the cursor coordinates within width and height (otherwise the mouse picker function wont work because normalized mouse coordinates go over [1,1] which completely breaks it and so on).
I will be grateful for any answers.
EDIT:
#httpdigest's answer put into very elementary code:
//store offsetX and offsetY values (and dont forget to give them 0 as initial value)
overshootX = mouseX - offsetX;
overshootY = mouseY - offsetY;
if (overshootX > width) {
offsetX = offsetX + overshootX - width;
}
if (overshootX < 0) {
offsetX = offsetX + overshootX;
}
if (overshootY > height) {
offsetY = offsetY + overshootY - height;
}
if (overshootY < 0) {
offsetY = offsetY + overshootY;
}
float withinWindowCursorPosX = mouseX - offsetX;
float withinWindowCursorPosY = mouseY - offsetY;
One way of doing it is to store an offset (initially (0, 0)) by how much the cursor moved beyond the bounds of the window/viewport (separate for X and Y).
Essentially, everytime when you either read the current cursor position or are being told of an update by an event, you calculate overshoot = cursorPos - offset (separate for X and Y). If overshoot exceeds (width, height) you increment offset by the excess it exceeded (width, height). Likewise, if overshoot is less than (0, 0) you decrement offset by the negative overshoot.
That way, you have a "sliding" window/rectangle which gets slided around as you keep on moving the mouse cursor beyond either edge of the screen.
Now, whenever you want to obtain the corrected cursor position (within your window), you simply use withinWindowCursorPos = cursorPos - offset.
You can then use withinWindowCursorPos for your picking calculations that require a cursor within the window/viewport.

Create a plot graphs with c++ visual studio

The Y axis will be scaled automatically, depending on the values coming fallowing function.
void mouseHandleCordinate(double val){
// include graph function.
}
So I want to create the plot chart against the time. X axis represent time and Y represent the value coming above function. How I create above graph function.
Always pass the data into void mouseHandleCordinate(double val) function.
As example:
val >>> 2.1,3,1,6,7,5.5,0,9,5,6,7,3.6,2,5,6,7,8,1,2,3,4 >> represent double val
Time>>> 21,20,19,18,17,......., 4,3,2,1 second
not sure I got what you asking but looks like you want to create continuous function following table of sampled points like:
const int N=21; // number of samples
const double t0=21.0,t1=1.0; // start,end times
const double val[N]={ 2.1,3,1,6,7,5.5,0,9,5,6,7,3.6,2,5,6,7,8,1,2,3,4 };
double f(double t) // nearest
{
t = (t-t0)/(t1-t0); // time scaled to <0,1>
t*= (N-1); // time scaled to <0,N) .. index in table
int ix=t; // convert to closest index in val[]
if ((ix<0)||(ix>=N)) return 0.0; // handle undefined times
return val[ix]; // return closest point in val[]
}
This will give you the nearest neighbor style function value. If you need something better use linear or cubic or better interpolation for example:
double f(double t) // linear
{
t = (t-t0)/(t1-t0); // time scaled to <0,1>
t*= (N-1); // time scaled to <0,N) .. index in table
int ix=t; // convert to closest index in val[]
if ((ix<0)||(ix>=N)) return 0.0; // handle undefined times
if (ix==N-1) return val[ix]; // return closest point in val[] if on edge
// linear interpolation
t = t-floor(t); // distance of time between ix and ix+1 points scaled to <0,1>
return val[ix]+(val[ix+1]-val[ix])*t; // return linear interpolated value
}
Now to your problem:
void mouseHandleCordinate(double mx) // mouse x coordinate in [pixels] I assume
{
double t,x,y,x0,y0
// plot
x=0;
y=f(view_start_time)
for (t=view_start_time;t<=view_end_time;t+=(view_end_time-view_start_time)/view_size_in_pixels,x0=x,x++)
{
y0=y; y=f(t);
// render line x0,y0,x,y
}
// mouse highlight
t = view_start_time+((view_end_time-view_start_time)*mx/view_size_in_pixels);
x = mx;
y = f(t);
// render point x,y ... for example with circle r = 16 pixels
}
where:
view_start_time is time of the left most pixel in your plot view
view_end_time is time of the right most pixel in your plot view
view_size_in_pixels is the x resolution in [pixels] of your plot view
[Notes]
I code the stuff directly in SO editor so there may be typos ...
Hoping you are calling the mouseHandleCordinate in some Paint event instead of on mouse movement which should only schedule repaint order... Also you should add y view scale in similar manner to x scale I used ...
For more info see:
How can i produce multi point linear interpolation?

Why mouse align to grid is flickering?

I have a board where i calculate everything with millimeters and at drawout i calculate back to pixels.
I also have a mouse position in pixels, and a grid, where i want to align my mouse.
Functions calculate between pixels and millimeters:
int global::mmToPixel(double value)
{
return global::dpi / (double)25.4 * value;
}
double global::pixelToMm(int value)
{
return ((double)value) / (global::dpi / (double)25.4);
}
My values:
global::dpi = 600.0;
global::gridSize = 2.54;
And the mouse position function:
QPointD canvas::mousePosition(bool tiled)
{
QPoint mp = this->mapFromGlobal(QCursor::pos());
mp.setX(mp.x() - mp.x()%global::mmToPixel(global::gridSize));
mp.setY(mp.y() - mp.y()%global::mmToPixel(global::gridSize));
QPointD p(global::pixelToMm(mp.x()), global::pixelToMm(mp.y()));
return p;
}
Dpi is the dots/inch. gridSize is how big a tile is. mp is contain the mouse coordinates and p is the aligned coordinates.
I tried a lot of versions(calculating with pixels or millimeters) but either of those worked. Sometimes when the mouse is on the edge of the tile it's flickering, and jump 1 or to tiles in a direction(random where).
If i remove the 2 aligner lines it's works perfectly. When i wrote it in c# it worked, but in c++ doesn't now.
QPoint contains 2 int and QPointD is a custom class it contains 2 double value;
Any ideas?
Sorry for my poor english.

How to reset x and y axis in qcustomplot after replotting (Qt)

I am trying to crop my data, and replot only a selected part of the graph, but after the replot, the x axis still has the same values, and my graph starts at (0,0) instead of (cropped_starting_value_x, y).
How can I reset the values of the X axis? I've already tryed rescaleaxes, but it's not working.
Here I only have values from around 19 seconds.
QCPGraph *mainGraph = Customplott[0]->addGraph();
QCPGraph *SecondaryGraph = Customplott[0]->addGraph();
//QCPGraph *TertiaryGraph = Customplott[Number]->addGraph();
//mainGraph->setAdaptiveSampling(true);
mainGraph->setData(Time_temp, Measured_temp);
mainGraph->setScatterStyle(QCPScatterStyle(QCPScatterStyle::ssNone, QPen(Qt::red), QBrush(Qt::white), 3));
mainGraph->setPen(QPen(Qt::blue)); // Magic only happens, when line width == 1
SecondaryGraph->setData(Time_temp, Output_temp);
SecondaryGraph->setPen(QPen(Qt::red));
Customplott[0]->setObjectName("YawRate");
Customplott[0]->xAxis->setLabel("Time [s]");
Customplott[0]->yAxis->setLabel("Yaw Rate");
/* rescale axes according to graph's data */
Customplott[0]->rescaleAxes();
mainGraph->rescaleAxes();
SecondaryGraph->rescaleAxes(true);
Customplott[0]->replot();
Every kind of help is highly appreciated!
You should use this method:
Customplott[0]->xAxis->setRange(minX, maxX);
(documentation here)

recalculate QwtScaleDiv before rendering

I've implemented a QwtPlot which scrolls across the screen as data is added in real-time. Based on user input, an image of the plot is occasionally rendered to a file using QwtPlotRenderer. However, because the axis scrolls during normal operation, the QwtScaleDiv tick marks can look a little wonky at render time (they are right-aligned):
Is there some easy way in which I can recalculate the division prior to rendering so that the first label is on the far left and the last one is on the far right?
This isn't as difficult as it looked at first. Bascially, all you need to do is temporarily replace the axisScaleDiv.
auto divX = this->axisScaleDiv(xBottom);
double ub = divX.upperBound();
double lb = divX.lowerBound();
double numTicks = 11.0; // 10 even divisions
// you can create minor/medium ticks if you want to, I didn't.
QList<double> majorTicks;
for (int i = 0; i < numTicks; ++i)
{
majorTicks.push_back(lb + i * ((ub - lb) / (numTicks - 1)));
}
// set the scale to the newly created division
QwtScaleDiv renderDivX(divX.lowerBound(), divX.upperBound(),
QList<double>(), QList<double>(), majorTicks);
this->setAxisScaleDiv(xBottom, renderDivX);
// DO PLOT RENDERING
QwtPlotRender renderer;
renderer.renderDocument(...);
// RESOTRE PREVIOUS STATE
this->setAxisScaleDiv(xBottom, divX);
this->setAxisScaleDiv(yLeft, divY);
// update the axes
this->updateAxes();