QML Charts - Data from C++ and Tickmarks
-
I am integrating C++ with QML, and need to plot data from the C++ class. Ideally, I'd like to separate the code so that all (or most) plotting functionality happens within QML.
However, for large sets of data the available functions in LineSeries within QML are slow. I did this in QML by iterating in a for() loop through the data from the C++ class and appending the LineSeries. This is extremely slow, either due to the item-by-item copy or due to the ChartView trying to render the plot after each individual append. Even for relatively small sets of data (1 set of 2000 points) it takes more than one second to plot one line using this method. In the example below, myData is the C++ class which is registered to QML.
/// main.qml - is there a faster way within QML only to plot data that is stored in a C++ class? myLineSeries.clear() for(var i = 0; i < myData.dataX.length; i++) { myLineSeries.append(myData.dataX[i], myData.dataY[i]) // point by point append - slow }
Doing this in C++, following the QML Oscillscope example is faster. But why doesn't the QML chart support the same bulk replace() function that the C++ QChart supports? This works, but violates the principle of keeping the data processing (should be in C++ class) separate from the UI manipulation (should be in QML).
// Much faster, but requires updating the LineSeries in C++. See all the code needed below.. // main.qml myData.update(myChartView.series(0)) // follows the QML Oscilloscope example // mydata.h #include <QAbstractSeries> //... QT_CHARTS_USE_NAMESPACE // Needed for QAbstractSeries (doesn't seem to be documented, is in the Oscilloscope demo) class MyData: public QObject { Q_OBJECT //... public slots: void update(QAbstractSeries *series); private: QVector<QPointF> m_data; // mydata.cpp #include <QXYSeries> //... // Update what is shown on the QML plot LineSeries void MyData::update(QAbstractSeries *series) { if (series) { QXYSeries *xySeries = static_cast<QXYSeries *>(series); QVector<QPointF> points = m_data; xySeries->replace(points); // much faster than the append() function above, operates on entire LineSeries } } // Make some example data to be plotted void MyData::generateData(int length) { // Remove previous data m_data.clear(); // Append the new data QVector<QPointF> points; points.reserve(length); qreal x(0); qreal y(0); for (int i=1; i < length+1; i++) { x = i; // make some data y = i; points.append(QPointF(x, y)); } m_data.append(points); }
This is a lot of code in order to facilitate the same goal as the QML-only code above, but in a faster way. If QML's ChartView would allow the entire LineSeries to be referenced, then this could be linked directly to a Q_PROPERTY in the C++ class to replace() the entire LineSeries. As of now this is only possible in the C+++ QChart object, not in the QML ChartView.
Second question. Tickmarks. On a LogValueAxis, I don't have enough control over where the tickmarks are placed. Common charts (Excel, Matlab, etc.) allow control over the grid spacing. In QML Charts we can only set the number of tickmarks. This code results in strange tick label locations.
// Ticks are at 20, 10010(??), 20000 LogValueAxis { id: xAxis tickCount: 3 min: 20 max: 20000 }
If following standard log plots these should be at 20, 10000, 20000. The center tick at 10010 is odd, and can't be dialed in to anything normally expected by using the available control of tickCount. Is there a way of controlling the grid spacing manually? I encountered a similar problem using the linear ValueAxis but was able to resolve it. Still, it would be better to have a grid spacing control even there, in case the chart zoom (axis min/maxes) is changed.