problem using the Qt library function 'QValueAxis::setRange'
-
I'm using Qt C++ 6.6.1, Qt Creator 12.0.2, MinGW 64-bit, on Windows 11 for creating a desktop application.
I encountered a problem using the Qt library function 'QValueAxis::setRange'.
I have reduced the problem to a small example. The example has a QGrapicsView that has been promoted (by Qt Creator) to QChartView (see "mainwindow.ui"). The mainwindow has two QPushButton. When running the example, first press the button <Graph 1>, then press the button <Graph 2>, which will cause the program to 'crash' while executing the following line of code in 'on_pushButton_2_clicked':m_axisY->setRange(0, 1.0e4); /* the program will CRASH while executing this instruction */
Here is the code:
mainWindow.h#ifndef MAINWINDOW_H #define MAINWINDOW_H #include <QMainWindow> #include <QChart> #include <QValueAxis> QT_BEGIN_NAMESPACE namespace Ui { class MainWindow; } QT_END_NAMESPACE class MainWindow : public QMainWindow { Q_OBJECT public: MainWindow(QWidget *parent = nullptr); ~MainWindow(); private slots: void on_pushButton_1_clicked(); void on_pushButton_2_clicked(); private: Ui::MainWindow *ui; QChart *m_chart; QValueAxis *m_axisY; }; #endif // MAINWINDOW_H
mainWindow.cpp
#include <QValueAxis> #include "mainwindow.h" #include "ui_mainwindow.h" MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); m_chart = new QChart; ui->myView->setChart(m_chart); m_axisY = new QValueAxis(); /* chart takes ownership of m_axisY (via 'addAxis') and will delete it in the chart destructor */ m_chart->addAxis(m_axisY, Qt::AlignLeft); m_axisY->setTickType(QValueAxis::TicksDynamic); } void MainWindow::on_pushButton_1_clicked() { m_axisY->setRange(0, 1.0e-9); m_axisY->setTickInterval(1.0e-10); m_axisY->setTickAnchor(0); } void MainWindow::on_pushButton_2_clicked() { qDebug("here i am: 1"); m_axisY->setRange(0, 1.0e4); /* the program will CRASH while executing this instruction */ qDebug("here i am: 2"); m_axisY->setTickInterval(1.0e3); m_axisY->setTickAnchor(0); } MainWindow::~MainWindow() { delete ui; delete m_chart; }
mainWindow.ui
<?xml version="1.0" encoding="UTF-8"?> <ui version="4.0"> <class>MainWindow</class> <widget class="QMainWindow" name="MainWindow"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>600</height> </rect> </property> <property name="windowTitle"> <string>MainWindow</string> </property> <widget class="QWidget" name="centralwidget"> <widget class="QChartView" name="myView"> <property name="geometry"> <rect> <x>50</x> <y>50</y> <width>691</width> <height>411</height> </rect> </property> </widget> <widget class="QPushButton" name="pushButton_1"> <property name="geometry"> <rect> <x>190</x> <y>490</y> <width>93</width> <height>29</height> </rect> </property> <property name="text"> <string>Graph 1</string> </property> </widget> <widget class="QPushButton" name="pushButton_2"> <property name="geometry"> <rect> <x>470</x> <y>490</y> <width>93</width> <height>29</height> </rect> </property> <property name="text"> <string>Graph 2</string> </property> </widget> </widget> <widget class="QMenuBar" name="menubar"> <property name="geometry"> <rect> <x>0</x> <y>0</y> <width>800</width> <height>26</height> </rect> </property> </widget> <widget class="QStatusBar" name="statusbar"/> </widget> <customwidgets> <customwidget> <class>QChartView</class> <extends>QGraphicsView</extends> <header location="global">qchartview.h</header> </customwidget> </customwidgets> <resources/> <connections/> </ui>
After much debugging I identified the problem to be the following:
When the code for <Graph 1> 'on_pushButton_1_clicked' is executing, it sets the tick interval (via setTickInterval) to 1.0e-10. Then when the code for <Graph 2> 'on_pushButton_2_clicked' is executing it tries to set the range (via setRange) to 1.0e+4, which crashes the program because the following Qt library function is called from within setRangeQList<qreal> ChartValueAxisX::calculateLayout() const { if (m_axis->tickType() == QValueAxis::TicksFixed) { int tickCount = m_axis->tickCount(); Q_ASSERT(tickCount >= 2); QList<qreal> points; points.resize(tickCount); const QRectF &gridRect = gridGeometry(); const qreal deltaX = gridRect.width() / (qreal(tickCount) - 1.0); for (int i = 0; i < tickCount; ++i) points[i] = qreal(i) * deltaX + gridRect.left(); return points; } else { // QValueAxis::TicksDynamic const qreal interval = m_axis->tickInterval(); const qreal anchor = m_axis->tickAnchor(); const qreal maxValue = max(); const qreal minValue = min(); // Find the first major tick right after the min of the range const qreal ticksFromAnchor = (anchor - minValue) / interval; const qreal firstMajorTick = anchor - std::floor(ticksFromAnchor) * interval; const QRectF &gridRect = gridGeometry(); const qreal deltaX = gridRect.width() / (maxValue - minValue); QList<qreal> points; const qreal leftPos = gridRect.left(); qreal value = firstMajorTick; while (value <= maxValue) { points << (value - minValue) * deltaX + leftPos; value += interval; } return points; } }
The problem is that the Qt library calls the above function 'ChartValueAxisX::calculateLayout' too soon, before the application code has an opportunity to set all the properties associated with tickType of 'TicksDynamic'.
In my example, the above Qt library function 'ChartValueAxisX::calculateLayout' tries to create (on the STACK) a QList containing (1.0e+4/1.0e-10=1.0e+14) 'points' which apparently is too many.
The Qt library needs a function that can set the 'range', 'tickInterval', and 'tickAnchor' in one AUTONOMOUS operation/function to eliminate the problem; i.e. Qt library needs a function like the following:void QValueAxis::setTicksDynamicInfo (qreal min, qreal max, qreal tickInterval, qreal tickAnchor);
That would prevent the Qt library from calling 'ChartValueAxisX::calculateLayout' too soon before all of the necessary properties have been set.
Note: My example is an actual set of data from my application. I have multiple data sets that can be graphed by the user selecting the desired dataset from a QListWidget. I am able to work around this problem by temporarily changing the tickType by calling 'setTickType(QValueAxis::TicksFixed)', then setting properties, then changing the tickType back to 'setTickType(QValueAxis::TicksDynamic)' as follows:
m_axisY->setTickType(QValueAxis::TicksFixed); m_axisY->setRange(0, 1.0e4); m_axisY->setTickInterval(1.0e3); m_axisY->setTickCount(10); /* this is needed when 'TicksFixed' */ m_axisY->setTickAnchor(0); m_axisY->setTickType(QValueAxis::TicksDynamic); /* will properly use range, interval, and anchor */
Do the Qt experts on this forum think that this is a Qt library problem? Did I completely overlook something?
-
@EricR Please copy and paste the text of the stack backtrace when this "crash" happens.
There's only one chart here. Can you clarify what
<Graph 1>
and<Graph 2>
refer to?You should also apply a layout to your Designer UI. I do not expect the absence of a layout will break things but it will not be helping either.
-
@ChrisW67 Thanks for the response.
The mainwindow for the user interface has two QPushButton, one is labeled "Graph 1" and the other one is labeled "Graph 2". By first pressing the button <Graph 1> and then <Graph 2> defines the sequence of calls to 'on_pushButton_1_clicked' followed by 'on_pushButton_2_clicked'. Adding a layout to the Designer UI has no impact. I'll have to figure out how to capture/view the stack backtrace.
In summary, the problem occurs when <Graph 2> button is clicked which calls 'm_axisY->setRange(0, 1.0e4)' when the tickInterval was previously set to 1.0e-10 (when <Graph 1> button was clicked), which causes the Qt library to try to add 10e14 QList 'points' on the stack.
-
@jsulm I am not able to obtain a stack trace. While running the example program in the debugger, the program "hangs" when the sequence of <Graph 1> button then <Graph 2> button is clicked. After several minutes of being "hung", the following message box appears:
The reason the program is "hanging" is because the Qt library function 'ChartValueAxisX::calculateLayout' is undesirably trying to create a QList with 1.0E+14 points on the stack. I tried to explain in my original post why this is a Qt library problem; the simple example illustrates the problem. -
@EricR
Nonetheless it would be interesting to very the stack trace. You have to (a) run it from debugger and (b) click OK on that dialog when it crashes. After that we are interested in seeing what it shows in stack view pane in debugger. (Especially after a "run out memory" which this might be, the backtrace can be nonsense. But it would still be interesting to see.)If you set a large range where it has to do a lot of ticks I guess that is a problem, even though it does not look like it's handled gracefully. But you are saying something like
setTickCount(10)
makes that not happen? So set your tick count small before setting a large range? -
@JonB Thanks for continuing to help. Much appreciated. Note that as reported in my original post, I have a workaround. I'm just trying to report a problem.
I did (a) run it from the debugger and (b) clicked <Ok> on the dialog message, but Qt was 'dead', couldn't do anything, even the Windows Task Manager could not kill it. I had to reboot to recover.
My example program does not 'want' to create an axis with zillions of axis tick marks. For 'graph 1' the example code is trying to set the number of major ticks to 10, and for 'graph 2' the example code is also trying to set the number of major ticks to 10. But because of the problem in the Qt library, it tries to create 1.0e+14 major ticks.
If I change the order as you suggested (setting the tickInterval BEFORE setting the 'range'), then I can make the same problem occur by doing the following:
void MainWindow::on_pushButton_1_clicked() { m_axisY->setTickInterval(1.0e3); m_axisY->setRange(0, 1.0e4); m_axisY->setTickAnchor(0); } void MainWindow::on_pushButton_2_clicked() { m_axisY->setTickInterval(1.0e-10); /* the program will CRASH while executing this instruction */ m_axisY->setRange(0, 1.0e-9); m_axisY->setTickAnchor(0); }
Above, 'Graph 1' will result in a range of (0, 1e4) with 10 major ticks, and then in 'Graph 2' the call to 'setInterval(1.0e-10)' will result in the Qt library trying to create 10e+14 major ticks (on the stack), even though the application code only want 10 major ticks.
The solution, as I mentioned in original post, is that the Qt library needs a function that can set the 'range', 'tickInterval', and 'tickAnchor' in one AUTONOMOUS operation/function to eliminate the problem; i.e. Qt library needs a function like the following:
void QValueAxis::setTicksDynamicInfo (qreal min, qreal max, qreal tickInterval, qreal tickAnchor);
That would prevent the Qt library from calling 'ChartValueAxisX::calculateLayout' too soon before all of the necessary properties have been set by the application code.
I'm sorry for taking up a lot of your time. I'm okay for now with my original work-around solution. I just think the issue should be fixed in the Qt library.
-
@EricR said in problem using the Qt library function 'QValueAxis::setRange':
I did (a) run it from the debugger and (b) clicked <Ok> on the dialog message, but Qt was 'dead', couldn't do anything, even the Windows Task Manager could not kill it. I had to reboot to recover.
:)
Above, 'Graph 1' will result in a range of (0, 1e4) with 10 major ticks, and then in 'Graph 2' the call to 'setInterval(1.0e-10)' will result in the Qt library trying to create 10e+14 major ticks (on the stack), even though the application code only want 10 major ticks.
I don't follow how the fact you have two different graphs affects the problem? That is confusing me. Why do you need two graphs to reproduce? If you need to set tick count or interval or whatever low before setting range high on any one graph then do so. But I haven't tried your code.
Are you saying no matter whether you do
setRange()
thensetTickInterval()
or the other way round either way you get too many ticks before you can change both, is that the issue?The solution, as I mentioned in original post, is that the Qt library needs a function that can set the 'range', 'tickInterval', and 'tickAnchor' in one AUTONOMOUS operation/function to eliminate the problem; i.e. Qt library needs a function like the following:
Then you have to report that at https://bugreports.qt.io/. It is a QtCharts issue.
-
@EricR said in problem using the Qt library function 'QValueAxis::setRange':
After several minutes of being "hung", the following message box appears:
That message is indicative of an invalid or null pointer. Attempting to allocate a huge block of memory, and failing to check success, could easily lead to this sort of problem. It seems odd that you call a method to change parameters on your Y axis and claim a crash in code called
ChartValueAxisX::calculateLayout()
@JonB said in problem using the Qt library function 'QValueAxis::setRange':
I don't follow how the fact you have two different graphs affects the problem? That is confusing me.
There is only one QChart. There are two push buttons labels (somewhat confusingly)
Graph 1
andGraph 2
. Both buttons trigger slots that notionally act on the Y axis of the single chart. -
@ChrisW67 said in problem using the Qt library function 'QValueAxis::setRange':
There are two push buttons labels (somewhat confusingly) Graph 1 and Graph 2.
Ah, I see! I looked at the names and thought we had two graphs, one affecting the other, somehow. I will have a look at the code....
-
@EricR said in problem using the Qt library function 'QValueAxis::setRange':
If I change the order as you suggested (setting the tickInterval BEFORE setting the 'range'), then I can make the same problem occur by doing the following:
m_axisY->setTickInterval(1.0e3); m_axisY->setRange(0, 1.0e4); m_axisY->setTickAnchor(0);
I do not get the behaviour/crash you say. If I copy & paste your two new orderings in
on_pushButton_1_clicked()
&on_pushButton_2_clicked()
from your last reply I can click either button and swap between them without problem, the axis getting drawn with appropriate ticks. (I do get your crash if I do not swap the order and use your original code.)And this would seem to correspond to the fact that the internal
calculateLayout()
you show callstickInterval()
when calculating for the new range, so that should be in place first. I admit it would look like a problem if it also calls this whensetTickInterval()
is called whilesetRange()
has not yet been called and is on an old range, but this did not seem to happen to me.I am using QT 5.15 supplied with Ubuntu 22.04 if it makes any difference..
-
@JonB Thanks for your awesome reply. I think you are right. It only causes a problem (in the original example) if in
on_pushButton_2_clicked
the code callssetRange
first and then callsetTickInterval
because (apparently) onlysetRange
callscalcuilateLayout
as you determined.I have submitted a bug report to Qt (QTBUG-124158). For now, I will mark this forum topic as 'solved' and then after the QTBUG is resolved I will come back to this forum topic and update it.
-