Qt Graphs Data Handling with 3D
Series
A series combines a logically connected set of data items and visual properties that describe how the data items should be rendered, such as item meshes and colors. Each graph's type has its own series type. For example, bar graphs use QBar3DSeries. All graphs can have multiple series added simultaneously. Each series owns its data.
This code snippet shows how to use QBar3DSeries to render bars as cylinders and with a gradient instead of a uniform color:
QQuickWidget quickWidget; Q3DBarsWidgetItem graph; graph.setWidget(&quickWidget); QBar3DSeries series; QLinearGradient barGradient(0, 0, 1, 100); barGradient.setColorAt(1.0, Qt::white); barGradient.setColorAt(0.0, Qt::black); series.setBaseGradient(barGradient); series.setColorStyle(QGraphsTheme::ColorStyle::ObjectGradient); series.setMesh(QAbstract3DSeries::Mesh::Cylinder); graph.addSeries(&series);
Data Proxies
The data users wish to visualize comes in many formats, all of which cannot be directly supported. Therefore, Qt Graphs implements data proxies, into which user can feed their data in a known format. Each graph has a basic proxy type in its series, which takes data in a format suitable for that graph. With the proxy-series relationship, since data is stored in series, it's important to mention a few points. Firstly, with this implementation, users can access data through the series. If a user wants to perform operations on this data, such as adding or removing, they can still carry out these operations through the proxy. However, in order to perform these operations, it is necessary to create a series associated with this proxy.
For example, the basic proxy for QBar3DSeries is QBarDataProxy, which is used to add data to the series as QBarDataItem objects. Each QBarDataItem holds a single bar value. Additional typedefs are provided for QBarDataArray and QBarDataRow containers.
This code snippet shows how to use basic proxy when your data is stored in a hypothetical myData
object, which returns the data as QBarDataItems:
QQuickWidget quickWidget; Q3DBarsWidgetItem graph; graph.setWidget(&quickWidget); QBar3DSeries series; for (int i = 0; i < 10; ++i) { QBarDataRow dataRow; for (int j = 0; j < 5; ++j) dataRow.append(myData->getValue(i, j)); series.dataProxy()->addRow(dataRow); } graph.addSeries(&series);
Note: Series objects can own only a single proxy at a time. The existing proxy is deleted when another is set to the series. Graphs can contain multiple series, though. If you need to switch back and forth between two different sets of data, it is usually more efficient to store each set in a different series and just change the series, rather than reset the data in the series using a proxy every time you need to switch.
Item Models and Data Mapping
For common use cases, Qt Graphs offers specialized proxies. One such case is having data in an item model (QAbstractItemModel subclass), which is a common way to store data in Qt applications. Each of the graph's types offers a special proxy class for this purpose, for example, QItemModelBarDataProxy for QBar3DSeries. These proxies are simple to use: provide them a pointer to the item model containing the data, and specify the rules on how to map the data into a format the basic proxy can process.
Mapping works with item model roles. Each data item in the model can have different values for different roles. For example, with QItemModelBarDataProxy, you can specify which role is used to determine which row the item belongs to, which role does the same for columns, and which role specifies the value of the item. When the proxy resolves the data from the model, it uses these mappings to generate the rows and columns of the bar graph.
Often, the item models will have a single role that contains information you want to map to multiple values. A typical example of this is a timestamp field when generating a bar graph with two time-related axes, for example, years and months. To enable mapping a single item model role to more than one data field, item model proxies should implement a pattern matching and replacing mechanism. You can also use this mechanism to reformat data even in one-to-one mapping cases.
Depending on the graph's type, proxies may support other functionalities as well, such as QItemModelBarDataProxy optionally mapping QAbstractItemModel rows and columns directly into bar graph rows and columns.
See individual proxy classes for more information and examples about how to use them: QItemModelBarDataProxy, QItemModelScatterDataProxy, and QItemModelSurfaceDataProxy.
Other Custom Proxies
QHeightMapSurfaceDataProxy is a specialized proxy for generating a surface graph from a heightmap image. See the QHeightMapSurfaceDataProxy documentation for more information.
The Graph Gallery example, in the Bar Graph tab, shows how a custom proxy can be created. It defines a custom data set based on variant lists and an extension of the basic proxy to resolve that data with an associated mapper.
Dealing with Real-time Data
When you have a data set that updates rapidly, it is important to handle data properly to ensure good performance. Since memory allocation is a costly operation, always use QList::reserve() and QList::resize() where possible to avoid unnecessary reallocations when constructing the array to give to the proxy.
If you need to:
- change the entire data set for each frame, it is in most cases best to reuse the existing array, especially if the array dimensions do not change.
- add, insert, remove, or change several rows or items for each frame, it is always more efficient to do it with one method call instead of multiple calls affecting a single row or item each. For example, adding ten rows with a single QBarDataProxy::addRows() call is much more efficient than ten separate QBarDataProxy::addRow() calls.
Bars3D is optimized to access only data that is within the data window and thus should not suffer a noticeable slowdown even if more data is continually added to the series using the proxy.
Due to the unsorted nature of the scatter data, any change in the data window ranges requires all data points to be checked for visibility, which can cause an increasing slowdown if data is continually added to the proxy. For the best performance with the scatter graphs, only keep the data you need in the proxy.
Surface data, while on the item level similar to scatter data, is already assigned into rows and columns. Therefore, the surface renderer can optimize drawing by making the assumption that the data in the rows and columns is sorted along their respective axes. It is not quite as efficient as in the case of the bars, but nearly so.