Skip to content

Performance

Typically 1–10M points can be replaced and drawn every frame at 60 FPS; above that, frames stop being smooth. Where a plot lands in that range depends on point count, update rate, data conversion, range scans, series count, line style, effects, window size, graphics backend, and refresh rate. Measure a workload that matches your application, for example in the Performance Lab.

Select the data path

API Use it when Cost
setData(QList<QPointF>) Data originates in QML Allocation and interleaving
setData(xs, ys) C++ data is in separate double vectors Allocation and interleaving
setData(std::vector<double>&&, count) C++ data is interleaved doubles Moved, no copy
setDataF(const float*, count) Interleaved floats stay caller-owned One copy into a reusable buffer
setDataF(std::vector<float>&&, count) Interleaved floats, on the curve's thread Moved, no copy
postData() with a float or double vector A worker thread owns the completed buffer Queues the moved-buffer handoff

Start with setDataF(std::vector<float>&&, count) for large C++ data sets.

Use a setData() overload for Unix-epoch timestamps, large coordinates, or small differences at a large offset. These paths keep doubles and rebase the GPU buffer so nearby values stay distinct. The F APIs store floats.

The table shows LineCurve. Every series has setData(), setDataF(), and postData() overloads for its own record layout.

A LineCurve builds its own vertex buffer. Updates with the same point count reuse it, and a growing point count writes vertices only for the added points.

Data ranges are computed on demand

No setter scans the data for its range. After an update, a series scans its records the first time something reads the range:

Reader Scans
rescaleToData(), rescaleAllAxes(), a double-click Once per call
An axis with autoRescale On every data update
dataMin / dataMax, or a binding to them On every data update that is followed by a read
A LineCurve gradient with a DataRange bound On every data update of that curve

appendData() widens a computed range without a rescan.

To skip the scan when the extents are already known, pass them with the data:

const auto bounds = QAccelPlot::PlotSeries::DataBounds{xMin, xMax, yMin, yMax};
curve->setDataF(std::move(points), pointCount, bounds);

The bounds describe that update only. A change that affects which values are valid, such as switching an axis to a logarithmic scale, discards them.

Build buffers efficiently

  • For the F paths, store points as interleaved floats: [x0, y0, x1, y1, ...].
  • Reserve or resize the vector once before filling it.
  • Move the completed vector instead of copying it.
  • Publish no faster than the display presents frames.
  • For a fixed streaming window, pass the window's bounds with each frame, as in the realtime example.

Keep work off the UI thread

Generate data on a worker thread and hand off only completed buffers, using postData() or a queued call. Never write to a QQuickItem from the worker. If producers are faster than the display, keep at most one pending batch; see Background data production.

Optional rendering costs

Hover interactions

Hover cost per mouse move:

Series Cost
LineCurve Checks one bounding box per 512 points, then the segments and markers in the matching boxes. hoverRadius: 0 skips the segments.
PointCloud, RectangleSeries Tests every record, roughly 1–2 ms per million, until those scans have cost as much as a spatial index. The index is then built, and later moves test only nearby records. Data replaced every frame never builds one.
BarSeries Builds a grid on the first move after a data change, then tests nearby bars.

A RectangleSeries also tests every rectangle spanning more than 64 grid cells, so many long rectangles make its lookup O(N).

Set hoverEnabled: false to turn hover off for one series. Set the environment variable QACCELPLOT_HOVER_ENABLED=0 to turn it off for every series and axis, for example in streaming-only views.

Other rendering costs

  • Markers add geometry and fragment work per visible point. Fragment work grows with marker size; MarkerShape.Pixel markers shade one pixel per point and suit very dense scatter plots.
  • A PointCloud or HeatmapSeries whose Colormap leaves min or max unset scans every value on each data update to resolve the range. Set both when the bounds of streaming values are known.
  • Gradient effects add material and domain work.
  • Transitions update intermediate data while animating.
  • Many series add per-node and per-material overhead at the same total point count.
  • Larger windows and high-DPI displays add fragment work.

Changing line width, marker size, or color is cheaper than changing data, but still schedules a redraw.

Use a hardware backend

Curves require a hardware Qt Quick Scene Graph backend: OpenGL, OpenGL ES, Direct3D 11/12, Vulkan, or Metal. The software backend skips curve rendering and emits a warning. When a curve is blank or slow, check QSG_RHI_BACKEND, QT_QUICK_BACKEND, remote-desktop sessions, and GPU drivers.

Select the texture upload mode

Curve coordinates live in a GPU data texture. How live updates reach it depends on QACCELPLOT_USE_QT_PRIVATE_API:

Mode Upload Use when
ON (default when Qt6::QuickPrivate is found) QSGPlainTexture::setImage() replaces the texture contents in place Large data sets updated every frame
OFF QQuickWindow::createTextureFromImage() recreates the texture Private Qt dependencies are unacceptable for packaging

No private Qt types appear in public headers or ABI. Gradient textures always use the public path.

Compare both modes with your workload, or build the Performance Lab once with each value.

Performance Lab

examples/showcases/performance streams a LineCurve, a PointCloud, a RectangleSeries, a BarSeries, and a HeatmapSeries, one per tab, with options for record and series count, ingestion API, hover, and rendering. It presents without vsync, so the display's refresh rate does not cap display FPS. Change one option at a time. The lab reports:

  • Display FPS — frames presented to the screen.
  • Data Update Rate (Hz) — new datasets applied to the plot per second, including worker-side generation.
  • Peak update gap — the longest interval between two dataset applications in the last reporting period.
  • Throughput — points, rectangles, bars, or heatmap cells updated per second.