I need to plot a large time series in PyQtGraph (millions of points). Plotting it as is is practically impossible and when turning on the optimization options (downsampling using setDownsampling
and clipping using setClipToView
) it is still barely usable when zoomed out (only when zoomed in it becomes fast thanks to clipping).
I have an idea though. I could pre-downsample my data since they're static. Then, I could use the cached downsampled data when zoomed out and the raw data when zoomed in.
How can I achieve that?
The answer by @three_pineapples describes a really nice improvement over the default downsampling in PyQtGraph, but it still requires performing the downsampling on the fly, which in my case is problematic.
Therefore, I decided to implement a different strategy, namely, pre-downsample the data and then select either the already downsampled data or the original data depending on the "zoom level".
I combine that approach with the default auto-downsample strategy employed natively by PyQtGraph to yield further speed improvements (which could be further improved with @three_pineapples suggestions).
This way, PyQtGraph always starts with data of much lower dimensionality, which makes zooming and panning instantaneous even with a really large amount of samples.
My approach is summarized in this code, which monkey patches the getData method of PlotDataItem
.
# Downsample data
downsampled_data = downsample(data, 100)
# Replacement for the default getData function
def getData(obj):
# Calculate the visible range
range = obj.viewRect()
if range is not None:
dx = float(data[-1, 0] - data[0, 0]) / (data.size[0] - 1)
x0 = (range.left() - data[0, 0]) / dx
x1 = (range.right() - data[0, 0]) / dx
# Decide whether to use downsampled or original data
if (x1 - x0) > 20000:
obj.xData = downsampled_data[:, 0]
obj.yData = downsampled_data[:, 1]
else:
obj.xData = data[:, 0]
obj.yData = data[:, 1]
# Run the original getData of PlotDataItem
return PlotDataItem.getData(obj)
# Replace the original getData with our getData
plot_data_item.getData = types.MethodType(getData, plot_data_item)