Qt Adjusting axes does not adjust the chart itself - c++

I am currently trying to get my head around Line Charts in Qt. For some reason, my charts seem to show really weird behavior. When I am using the createDefaultAxis it will set the minimum and maximum values according to the minimum and maximum values of the Series behind the chart. While this seems alright at first it's already different from the behavior in this example. The minimum Y-Value there is 1, the minimum value on the axis, however, is 0.
At first, I thought this might just be due to a change to the QChart class, so I created my own Axis and tried again. This time I made the y-Axis range from 0 to 100 and the x-Axis range from 1 to 52. Also, I changed the tick counts to 4 and 52. However, the chart still looked like before and didn't seem to be affected by the changes to the axis.
I included a screenshot of that program here
I hope you can help me fix that. The goal would be that the values of the series match the values on the axes.
Edit: Here is the main.cpp:
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLineSeries *series = new QLineSeries();
series->append(0, 6);
series->append(2, 4);
series->append(3, 8);
series->append(7, 4);
series->append(10, 5);
*series << QPointF(11, 1) << QPointF(13, 3) << QPointF(17, 6) << QPointF(18, 3) << QPointF(20, 2);
QChart *chart = new QChart();
chart->legend()->hide();
chart->addSeries(series);
QValueAxis *axisX = new QValueAxis();
axisX->setRange(1, 52);
axisX->setMin(1);
axisX->setMax(52);
axisX->setTickCount(52);
QValueAxis *axisY = new QValueAxis();
axisY->setRange(0, 100);
axisY->setMin(0);
axisY->setMax(100);
axisY->setTickCount(4);
chart->setAxisX(axisX);
chart->setAxisY(axisY);
chart->setTitle("Simple line chart example");
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QMainWindow window;
window.setCentralWidget(chartView);
window.resize(400, 300);
window.show();
return a.exec();
}

The series isn't attached to any axis, it will by default scale to utilize the entire plot area of the chart.
You should attach the series to axis created as :
//... After setting up your axis X and Y
chart->setAxisX(axisX);
chart->setAxisY(axisY);
//attach the series to the specific axis.
series->attachAxis(axisX);
series->attachAxis(axisY);

to add axis on chart use following technique.
addAxis(xAxis,postion);
addAxis(yAxis,postion);
and then attached series to it

Related

QChart points are incorrectly plotted when I change the XAxis and YAxis

I am making a QScatterSeries. I have some points plotted in 3 different series (red, green, blue). It all plots perfectly when I use chart->createDefaultAxes(). All points are where they should be. The issue is that when I change the X Axis and Y Axis in my chart, all my points become incorrectly plotted. None of them are where they should be. This is strange because I have hard-coded my points.
I have tried adding my series to the chart before and after adding the X and Y Axis to my chart, thinking that may have caused my points to become confused. But that did not fix it.
Chart when I use the default axes:
char *myargv[2];
int myargc = 1;
myargv[0] = strdup("");
// First, create QApplication
QApplication a(myargc, myargv);
// Create our red, green, blue acceleration series
redSeries = new QScatterSeries;
redSeries->setMarkerSize(15.0);
redSeries->append(1, 20);
redSeries->append(2, 30);
redSeries->append(3, 44);
redSeries->setColor(Qt::red);
greenSeries = new QScatterSeries;
greenSeries->setMarkerSize(15.0);
greenSeries->append(1, 10);
greenSeries->append(2, -9);
greenSeries->append(3, 20);
greenSeries->setColor(Qt::green);
blueSeries = new QScatterSeries;
blueSeries->setMarkerSize(15.0);
blueSeries->append(1, -20);
blueSeries->append(2, -10);
blueSeries->append(3, 0);
blueSeries->setColor(Qt::blue);
// Customize our chart
QChart *chart = new QChart();
chart->createDefaultAxes();
chart->setTitle("Example Plot");
QValueAxis *axisX = new QValueAxis;
axisX->setRange(0, 130);
axisX->setTickCount(10);
QValueAxis *axisY = new QValueAxis;
axisY->setRange(-50, 50);
axisY->setTickCount(10);
// Add series to chart
chart->addSeries(redSeries);
chart->addSeries(greenSeries);
chart->addSeries(blueSeries);
// Create chartView
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
//chartView->chart()->setAxisX(axisX);
//chartView->chart()->setAxisY(axisY);
// Render everything within our Window
QMainWindow w;
w.setCentralWidget(chartView);
w.resize(400, 300);
w.show();
return a.exec();
}
When I try to use my own X and Y axis, I simply comment out the chart->createDefaultAxes() and comment back in the:
//chartView->chart()->setAxisX(axisX);
//chartView->chart()->setAxisY(axisY);
This causes me to get the incorrectly plotted chart:
My question is, what am I doing wrong? Also, in other parts of my code, I plan to dynamically receive data and use that data to plot points on my graph, such as this:
while (receivingData) {
redSeries->append(x, y); // Will not work because my hard-coded points (above) do not work
greenSeries->append(x, y);
blueSeries->append(x, y);
}
What do I have to do to ensure that all my points are plotted correctly with my custom X and Y Axis? I am assuming that I will most likely have to do some math within my append to match my QChart's new dimensions but how do I achieve that?
the answer is ,add call to
QAbstractSeries::attachAxis
somewhere,
I suspect.
I found the answer to my question in another post.
Posting here in case it helps someone
QtCharts add custom axis

How to make only one point label visible for QLineSeries/QXYSeries

I am using the qtcharts module of qt.
I am using c++ but it does not matter if the solution comes for another language (I will translate it afterwards).
Problem: I plot a bunch of QLineSeries in a QChart and I want to display the point labels only when hovering them.
I planned to use the signal QXYSeries::hovered() to detect when the mouse moves over a point (the same when the mouse moves away the point).
I know that there exists a member function QXYSeries::setPointLabelsVisible() but it makes visible all the points of the series.
I want to be able to display only one point at a time because the series are relatively large and displaying all the labels would degrade the readability.
Question: Is it possible to display only one point label for a QLineSeries ? If yes, how ?
I could not find such a feature anywhere in the Qt documentation.
Here is a baseline code sample to start with (for convenience):
Declaration:
#include <QMainWindow>
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow();
};
Definition:
#include <QApplication>
#include <QLineSeries>
#include <QDateTimeAxis>
#include <QValueAxis>
#include <QChartView>
#include <QDateTime>
int main(int argc, char ** argv)
{
QApplication app(argc, argv);
MainWindow w;
w.show();
return app.exec();
}
MainWindow::MainWindow()
{
setWindowTitle("QtCharts baseline");
resize(800, 500);
QtCharts::QChart * chart = new QtCharts::QChart;
chart->setTitle("Baseline sample");
chart->legend()->setAlignment(Qt::AlignRight);
QtCharts::QDateTimeAxis * time_axis = new QtCharts::QDateTimeAxis;
time_axis->setFormat("hh:mm:ss");
time_axis->setTitleText("Time");
time_axis->setTickCount(5);
QtCharts::QValueAxis * value_axis = new QtCharts::QValueAxis;
value_axis->setTitleText("Value (unit)");
value_axis->setTickCount(6);
chart->addAxis(time_axis, Qt::AlignBottom);
chart->addAxis(value_axis, Qt::AlignLeft);
QtCharts::QLineSeries * ls = new QtCharts::QLineSeries;
ls->setName("Test series");
ls->setPointsVisible(true);
//ls->setPointLabelsVisible(true);
QDateTime dt = QDateTime::currentDateTime();
ls->append(dt.toMSecsSinceEpoch(), -10);
ls->append(dt.addSecs(1).toMSecsSinceEpoch(), 8);
ls->append(dt.addSecs(2).toMSecsSinceEpoch(), 27);
ls->append(dt.addSecs(3).toMSecsSinceEpoch(), 12);
ls->append(dt.addSecs(4).toMSecsSinceEpoch(), 42);
chart->addSeries(ls);
ls->attachAxis(time_axis);
ls->attachAxis(value_axis);
QtCharts::QChartView * view = new QtCharts::QChartView;
view->setChart(chart);
this->setCentralWidget(view);
}
One technique you could try is making a "shadow" copy of the line series with just the one or few points you need, sitting on top of the actual data line. Draw with a transparent pen so the line doesn't show up but set the labels to be visible. You can add/remove/change points to the shadow copy of the line series, and only the labels you want drawn can be added to the series. The link shows one
example of the technique.

QLineSeries does not show up when working with numbers < 1e-14

When graphing a line series within a small range using QtChart library (for example, between 0 and 1e-15), the line series seemingly does not get rendered. Using larger numbers with the same exact code works perfectly.
I've tried boiling down my code to be as simple as possible, to find any mistakes I was making, but I cannot find any. I suspect this may be a bug in Qt, but I'm posting here in case I missed something before I file a bug report.
I've also failed to find any information online about people experiencing similar bugs, but that may just be an indication that my google-fu is weak.
I originally encountered this bug using PyQt5, but have recreated it in C++ to ensure it was not a PyQt5 specific bug.
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtCharts/QChartView>
#include <QtCharts/QLineSeries>
#include <QtCharts/QLegend>
#include <QtCharts/QValueAxis>
QT_CHARTS_USE_NAMESPACE
#define FACTOR 1e-13
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QLineSeries *lineseries = new QLineSeries();
lineseries->setName("series");
lineseries->setUseOpenGL(true);
for (double i = 0.0; i < 10.0; i += 1.0)
lineseries->append(QPointF(i*FACTOR, i * FACTOR));
QChart *chart = new QChart();
chart->addSeries(lineseries);
chart->setTitle("Line example");
QValueAxis *axisX = new QValueAxis();
chart->setAxisX(axisX, lineseries);
QValueAxis *axisY = new QValueAxis();
chart->setAxisY(axisY, lineseries);
axisY->setRange(0.0, 9.0*FACTOR);
axisX->setLabelFormat("%.3E");
axisY->setLabelFormat("%.3E");
chart->legend()->setVisible(true);
chart->legend()->setAlignment(Qt::AlignBottom);
QChartView *chartView = new QChartView(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QMainWindow window;
window.setCentralWidget(chartView);
window.resize(840, 900);
window.show();
return a.exec();
}
Running the code attached, I would expect to observe a linear line series rendered in the plot, but I see nothing rendered.
Modifying the FACTOR constant to something >= 1e-12 will make the line series display properly.

Reverse axis of QPolarChart

I need to modify a QPolarChart to create a Satellite SkyPlot.
For this, I need to reverse the radial axis (elevation axis) so that 90 is placed on the origin and 0 on the outer ring.
In the list of members for QValueAxis I found the method setReverse, which seems to do what I need.
However, doing setReverse(true) on the elevation axis does not flip the axis as I was expecting.
How can I reverse the axis then?
This is the minimal code required to run this example:
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtCharts/QChartView>
#include <QtCharts/QPolarChart>
#include <QtCharts/QValueAxis>
#include <QScatterSeries>
QT_CHARTS_USE_NAMESPACE
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QtCharts::QPolarChart *chart = new QtCharts::QPolarChart();
chart->legend()->hide();
QScatterSeries *series = new QScatterSeries();
for (int i = 0; i < 360; i += 10) {
series->append(i, i);
}
chart->addSeries(series);
QValueAxis *azimuthAxis = new QValueAxis();
azimuthAxis->setRange(0, 360);
azimuthAxis->setTickCount(9);
azimuthAxis->setLabelFormat("%d");
azimuthAxis->setLabelsVisible(true);
chart->addAxis(azimuthAxis, QPolarChart::PolarOrientationAngular);
QValueAxis *elevationAxis = new QValueAxis();
elevationAxis->setRange(0, 90);
elevationAxis->setTickCount(7);
elevationAxis->setLabelFormat("%d");
elevationAxis->setLabelsVisible(true);
elevationAxis->setReverse(true); // <-- REVERSE THE AXIS
chart->addAxis(elevationAxis, QPolarChart::PolarOrientationRadial);
QtCharts::QChartView *chartView = new QtCharts::QChartView();
chartView->setChart(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QMainWindow window;
window.setCentralWidget(chartView);
window.resize(800, 600);
window.show();
return app.exec();
}
UPDATE:
After reading Damien's comment:
You may check with isReverse if the command setReverse was taken into account
I made the test by putting qDebug() << elevationAxis->isReverse(); immediately before and immediately after elevationAxis->setReverse(true);, and it prints out false and true, respectively.
qDebug() << elevationAxis->isReverse(); // <-- Prints out "false"
elevationAxis->setReverse(true); // <-- REVERSE THE AXIS
qDebug() << elevationAxis->isReverse(); // <-- Prints out "true"
So the reverse property is indeed changed. However, this change is not reflected visually in the axis.
The documentation for the reverse property reads:
This property holds whether a reverse axis is used.
By default, the value is false.
The reverse axis is supported with a line, spline, and scatter series,
as well as an area series with a cartesian chart. All axes of the same
orientation attached to the same series must be reversed if one is
reversed or the behavior is undefined.
A polar chart is certainly NOT a cartesian chart, so that might be the root of the problem.
I couldn't make the setReversed(true) work whatever the way I tried to do it.
So I found another solution: Do it manually.
I printed the series in reversed way:
QScatterSeries *series = new QScatterSeries();
for (int i = 0; i < 360; i += 10) {
series->append(i, MAX_ELEVATION - i);
}
MAX_ELEVATION equals to 90.
Then I reversed the labels names. For that I had to replace QValueAxis by its derived QCategoryAxis.
The full code
#include <QtWidgets/QApplication>
#include <QtWidgets/QMainWindow>
#include <QtCharts/QChartView>
#include <QtCharts/QPolarChart>
#include <QtCharts/QValueAxis>
#include <QScatterSeries>
#include <QtCharts/QCategoryAxis>
QT_CHARTS_USE_NAMESPACE
#define MAX_ELEVATION 90
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
QtCharts::QPolarChart *chart = new QtCharts::QPolarChart();
chart->legend()->hide();
QScatterSeries *series = new QScatterSeries();
for (int i = 0; i < 360; i += 10) {
series->append(i, MAX_ELEVATION - i);
}
chart->addSeries(series);
QValueAxis *azimuthAxis = new QValueAxis();
azimuthAxis->setRange(0, 360);
azimuthAxis->setTickCount(9);
azimuthAxis->setLabelFormat("%d");
azimuthAxis->setLabelsVisible(true);
chart->addAxis(azimuthAxis, QPolarChart::PolarOrientationAngular);
/*
QValueAxis *elevationAxis = new QValueAxis();
elevationAxis->setRange(0, 90);
elevationAxis->setTickCount(7);
elevationAxis->setLabelFormat("%d");
elevationAxis->setLabelsVisible(true);
elevationAxis->setReverse(true); // <-- REVERSE THE AXIS
chart->addAxis(elevationAxis, QPolarChart::PolarOrientationRadial);
*/
QCategoryAxis *elevationAxis = new QCategoryAxis;
elevationAxis->setRange(0, MAX_ELEVATION);
for(unsigned int i = 0; i <= MAX_ELEVATION; i += 15)
elevationAxis->append(QString::number(MAX_ELEVATION-i), i);
elevationAxis->setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
elevationAxis->setLabelsVisible(true);
chart->addAxis(elevationAxis, QPolarChart::PolarOrientationRadial);
QtCharts::QChartView *chartView = new QtCharts::QChartView();
chartView->setChart(chart);
chartView->setRenderHint(QPainter::Antialiasing);
QMainWindow window;
window.setCentralWidget(chartView);
window.resize(800, 600);
window.show();
return app.exec();
}
It is equivalent to what setReversed(true) should do. It will display your series exactly the way you want without modifying anything of the way the data are computed.
Here you can see what it looks like:
It worked successfully for me.
Hope it can help.

How do you plot points in QT?

I am writing an application in C++ with QT where you have n points and compute the convex hull of this. However, once this is computed I have no idea how to plot the points and draw the border of the hull. Making menu buttons and such is simple enough, but I'm not sure I know the tools to do this.
How do you do this?
Graphics View, addEllipse
QGraphicsView does 2D plotting very well and gives you many options for how to display it. It isn't as tailored for plotting scientific data as much as qwt, but just for showing a bunch of points, or geometry or animations and lots of other things it works very well. See Qt's Graphics View Framework documentation and examples.
Here is how you plot a bunch of points in a QGraphicsScene and show it in a QGraphicsView.
#include <QtGui/QApplication>
#include <QGraphicsView>
#include <QGraphicsScene>
#include <QPointF>
#include <QVector>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
QVector <QPointF> points;
// Fill in points with n number of points
for(int i = 0; i< 100; i++)
points.append(QPointF(i*5, i*5));
// Create a view, put a scene in it and add tiny circles
// in the scene
QGraphicsView * view = new QGraphicsView();
QGraphicsScene * scene = new QGraphicsScene();
view->setScene(scene);
for(int i = 0; i< points.size(); i++)
scene->addEllipse(points[i].x(), points[i].y(), 1, 1);
// Show the view
view->show();
// or add the view to the layout inside another widget
return a.exec();
}
Note: You will probably want to call setSceneRect on your view, otherwise the scene will just auto-center it. Read the descriptions for QGraphicsScene and QGraphicsView in the Qt Documentation. You can scale the view to show more or less of the scene and it can put scroll bars in. I answered a related question where I show more about what you can do with a QGraphicsView that you may want to look at also.
You can just create a custom class deriving from QWidget where you override the void paintEvent(QPaintEvent* event) method. In that you put the points into some sort of point list, either std::vector<QPoint> or QList<QPoint> and then paint it with a Polyline method. For instance:
void Foo::paintEvent(QPaintEvent* event)
{
QPainter painter(this);
std::vector<QPoint> points;
// Fill points with the points
painter.drawPolyLine(points.data(), static_cast<int>(points.size()));
}
There is a charting library, qwt, that provides Qt widgets for - erm - charting purposes.
Qt Charts, QML or GraphicsView
This was going to be an update to my QGraphics View example, but it got kind of long, and it really is a completely different method to answer the question.
Qt Charts (LGPL available since 2016) is a great way to do this without needing a third party library.
https://doc.qt.io/qt-5/qtcharts-linechart-example.html
http://blog.qt.io/blog/2016/01/18/qt-charts-2-1-0-release/
https://doc.qt.io/qt-5/qtcharts-overview.html
https://doc.qt.io/qt-5/qlineseries.html#QLineSeries
QLineSeries* series = new QLineSeries();
series->append(0, 6);
series->append(2, 4);
...
chart->addSeries(series);
For the convex hull example specifically, you probably want the QAreaSeries chart.
https://doc.qt.io/qt-5/qtcharts-areachart-example.html
https://doc.qt.io/qt-5/qareaseries.html
QLineSeries *series0 = new QLineSeries();
QLineSeries *series1 = new QLineSeries();
*series0 << QPointF(1, 5) << QPointF(3, 7) << QPointF(7, 6) << QPointF(9, 7) << QPointF(12, 6)
<< QPointF(16, 7) << QPointF(18, 5);
*series1 << QPointF(1, 3) << QPointF(3, 4) << QPointF(7, 3) << QPointF(8, 2) << QPointF(12, 3)
<< QPointF(16, 4) << QPointF(18, 3);
QAreaSeries *series = new QAreaSeries(series0, series1);
Hope that helps.