QML ChartView Binding to C++ Data
-
I am using Qt 5.7 on Linux x86 and IMX6.
There are several examples on how to bind chart data from C++ to a QML ChartView.
My problem with the examples, is I need the following:
- Take advantage of the declarative nature of QML to do the layouts. I have many charts, about 60 in an application. I have all my UI laid out in QML thinking I could reference the charts in C++ easily. I don't want to have to to a major redesign. I little is OK.
- I need to load the data from C++ for speed. I don't want to render 1000 points in JavaScript 2 or 3 times a second, driving my CPU load high.
- I don't want a QML timer to pull the data and load it in JavaScript.
- I don't want a global datasource in C++ since I have 60 charts. That would mean 60 global variables.
What I would like is to get a reference to the QML ChartView in C++ by finding the object in C++ and manipulating the LineSeries in C++.
So what I have done is this:
- Set the objectName property on my QML chart to be "myChartView"
- In the C++ code, call
QObject* _chartView = _applicationWindow->findChild<QObject*>( "myChartView" ); - I assert to ensure on the _chartView object is not null and this passes. So far so good.
- I want to static cast the QObject into a ChartView or Chart but notice this always fails when I try to access properties of the chartView or chart object on the C++ side.
- So I examine the metaObject() data from the _chartView and print it out. I get the following:
Class QtCharts::DeclarativeChart
theme
animationOptions
animationDuration
animationEasingCurve
title
titleFont
titleColor
legend
count
backgroundColor
dropShadowEnabled
backgroundRoundness
margins
plotArea
plotAreaColor
axes
localizeNumbers
localeClass QQuickItem
parent
data
resources
children
x
y
z
width
height
opacity
enabled
visible
visibleChildren
states
transitions
state
childrenRect
anchors
left
right
horizontalCenter
top
bottom
verticalCenter
baseline
baselineOffset
clip
focus
activeFocus
activeFocusOnTab
rotation
scale
transformOrigin
transformOriginPoint
transform
smooth
antialiasing
implicitWidth
implicitHeight
layerClass QObject
objectName
-
So I attempt to #include <QtCharts/DeclarativeChart> and oh, oh. Qt doesn't expose this class to be included. So I can't static cast the QObject* into a DeclarativeChart*.
-
So I then tried to set object names on my LineSeries, exposing those so they can be manipulated. Finding those using _applicationWindow->findChild<QObject*>( "myLineSeries" ); came up null, which sort of makes sense. It's not a visual object.
My questions to Qt are,
A. "Is there an elegant solution where I can either get a reference to the QML based QtChart or QtChartView in C++ elegantly?"
B. "Another thought would be to subclass the QtChart and expose this as a new QML metatype. This seems elegant and I am willing to do this if this is the right way to go."Any advice?
Thank you for any help to anyone.
Brian
-
I want to static cast the QObject into a ChartView or Chart but notice this always fails when I try to access properties of the chartView or chart object on the C++ side.
If you are able to cast it to
QObject
then it is possible to modify chart properties using setProperty.
For eg.:QObject* _chartView = _applicationWindow->findChild<QObject*>( "myChartView" ); _chartView->setProperty("title", "New Title");
-
Yes. You can set properties on the chart like title.
However, when using the ChartView in C++, I am trying to set the data I have acquired in C++ on the "series" method, not property.
The Qt documentation provided at
http://doc.qt.io/qt-5/qchart.html
is
QList<QAbstractSeries *> QChart::series() const
Returns all series that are added to the chart.
See also addSeries(), removeSeries(), and removeAllSeries().So setting and getting properties will not suffice on "METHOD" invocations, unless I am missing something.
If I have a C++ reference to a series defined in qml, or if I can manually added them with addSeries in C++, things will work.
The methods are there, but I can't get a reference to the series objects I need, since I need to invoke methods.
What is the best way forward for accessing the series objects in C++ so I can set the data on them?
Thank you for your help...
Brian
-
As a reference, the source code for the DeclarativeChart is found at
https://code.woboq.org/qt5/qtcharts/src/chartsqml2/declarativechart.h.html
There are public methods that I need to access:
public:
176 Q_INVOKABLE QAbstractSeries *series(int index);
177 Q_INVOKABLE QAbstractSeries *series(QString seriesName);
178 Q_INVOKABLE QAbstractSeries *createSeries(int type, QString name = "", QAbstractAxis *axisX = 0, QAbstractAxis *axisY = 0);
179 Q_INVOKABLE void removeSeries(QAbstractSeries *series);
180 Q_INVOKABLE void removeAllSeries() { m_chart->removeAllSeries(); }Since I can't subclass DeclarativeChart (it's header file is not exposed), I can't create some pass through accessors.
One option, is to copy the source code for DeclarativeChart into my own codebase and rename it to MyDeclarativeChart, expose it to QML and add some property accessors for the things I need, like:
Q_PROPERTY(QQmlListProperty<QAbstractSeries> series READ series REVISION 2)
However, I don't want to get off the beaten path by creating my own copy of DeclarativeChart for maintenance reasons down the road.
Any ideas?
-
@Brian-Gyetko said in QML ChartView Binding to C++ Data:
Yes. You can set properties on the chart like title.
However, when using the ChartView in C++, I am trying to set the data I have acquired in C++ on the "series" method, not property.
The Qt documentation provided at
http://doc.qt.io/qt-5/qchart.html
is
QList<QAbstractSeries *> QChart::series() const
Returns all series that are added to the chart.
See also addSeries(), removeSeries(), and removeAllSeries().So setting and getting properties will not suffice on "METHOD" invocations, unless I am missing something.
If I have a C++ reference to a series defined in qml, or if I can manually added them with addSeries in C++, things will work.
The methods are there, but I can't get a reference to the series objects I need, since I need to invoke methods.
What is the best way forward for accessing the series objects in C++ so I can set the data on them?
Thank you for your help...
Brian
@Brian-Gyetko Once you get the chartview object on C++ side you can just invoke the method i.e for eg.
series
in your case usingQMetaObject::invokeMethod
. Explicit casting not required. This is how it works:QObject* chartview = viewer.rootObject()->findChild<QObject*>("myChartView"); //points to chartview object from QML QAbstractSeries* series; //will point to PieSeries or LineSeries in ChartView once invokeMethod returns it QMetaObject::invokeMethod(chartview, "series", Qt::AutoConnection, Q_RETURN_ARG(QAbstractSeries*, series), Q_ARG(int, 0)); //Get first series in ChartView; series method expect an integer
The last line calls series method and stores the pointer in
QAbstractSeries* series
using which you can call its methods.
In the same way as explained earlier you can call methods or properties for thisseries
. -
You can close this incident.
-
@Brian-Gyetko Sure. You can also let us know if this helped ? Or may be another possibilities that you tried. It would be of much more help for the community if you shared them too. For now I'll mark this as solved.
-
I found the Charting to be a problem if you are trying to use QML to lay it out and C++ to load the data. I ended up building my own charting.
First, forcing users to invoke Java like reflection on methods and properties is really cumbersome. Why? Because not all the methods are exposed in the DeclarativeChart C++ class. You need Q_INVOKABLE everywhere. The developers should do one thing on the qml ChartView's C++ representation DeclarativeChart. Expose a method called getChartView() or getChart() so users can get at the C++ class entirely. Or allow users to static cast the QObject findObject by objectName reference into a DeclarativeChart (DeclarativeChart's header file should be exposed to the end user). Make the C++ equivalent of what the QML maps to obvious to the user. Document it. Rather than shimming a proxy datasource into the example (Oscilloscope), give the end user of the API full access to the Visual component.
Second. If you load date time stamps that are very large in size (in ms since 1970) say for the year 2015, and set the delta time between points to something small, say 1 second or 1000 ms, set OpenGL to true on the series, you get jagged lines on the screen. Some values display 2 points directly above each other. (Bug) I can't use this.
Third, I was having problems defining a series in QML, adding a few points in the QML markup, and then clearing the points out of series in C++. This all worked. However, when the number of points changed, the points wouldn't render and the axes didn't update. I seemed to be hunting down how to fire signals on series count changed, or axes changed and spent a day doing this. Since I was invoking methods, in a reflection style, maybe some of the signals associated with the methods and properties were not automatically being fired. It was so frustrating that I gave up. I figured that the Charting tool would go through many modifications in the next few updates and I couldn't risk investing my IP in something that might change rapidly from a maintenance perspective, even if I could get things working now. They might get broken in a chart update.
Fourth. I built my own plot component. I subclass QQuickPaintedItem. I render all my data using a inline QPainter to an offscreen image (not attached to the SceneGraph) ON A BACKGROUND THREAD, then transfer the image from the background thread to a slot across the thread boundary and use the following one liner on the UI thread QQuickPaintedItem to render the image.
void MyChart::paint(QPainter *painter){
painter->drawImage(0,0,_qImage,0,0,_qImage.width(),_qImage.height());
}
Since the QPainter has a nice API that is simple to use, the code was easy and therefore straightforward to maintain.My CPU stays fairly flat no matter how many points I render, since the background thread is doing all the work, blowing away even the fastest OpenGL rendering performance. My UI is so snappy on an ARM processor, people wonder how I did it. The total time to build my plot component was 2 days. I have 1000 points in up to 7 or 8 series updating possibly 2 to 5 times per second.
I have done this in plotting 50000 points (scatter like chart) at a rate of 15 frames per second on an ARM processor using only 15 % of the CPU. OpenGL libraries are not consistent across X86, ARM and others. So you never know what you are going to get when you trust OpenGL. Painting to an offscreen buffer on a background thread always works consistently because it is simple.
It's always good to push as much of the rendering to the background thread as possible.
UI's only have one UI thread. Leave that thread to the user to mouse move, click, drag etc.
Its precious.
-
@Brian-Gyetko
I would suggest you to ask your questions on Qt Interst Mailing List. You will find a lot of Qt developers there.
Also usually accessing QML components from C++ is not recommended.