Repository: pyqtgraph/pyqtgraph

Issue:
plots not showing with NaN
<!-- In the following, please describe your issue in detail! -->
<!-- If some of the sections do not apply, just remove them. -->

### Short description
<!-- This should summarize the issue. -->
Plotting data with np.nan results in an empty plot.

### Code to reproduce
<!-- Please provide a minimal working example that reproduces the issue in the code block below.
     Ideally, this should be a full example someone else could run without additional setup. -->
```python
import numpy as np
import pyqtgraph as pg

data = np.random.normal(size=1000)
data2 = np.random.normal(size=1000)
data2[0] = np.nan

pg.plot(data, title="no NaN")
pg.plot(data2, title="one NaN")

pg.QtGui.QApplication.exec_()
```

### Expected behavior
<!-- What should happen? -->
Two plots with random data.

### Real behavior
<!-- What happens? -->
First plot shows as normal.
Second plot is empty.
```
An error occurred?
Post the full traceback inside these 'code fences'!
```

### Tested environment(s)

 * PyQtGraph version: 0.11.0.dev0+ged6586c
 * Qt Python binding: PySide2 5.13.1 Qt 5.13.1
 * Python version: 3.7.3
 * NumPy version: 1.17.2
 * Operating system: Windows 10, x64
 * Installation method: python from python.org installer. numpy from pip. pyqtgraph from git and / "python setup.py install" in pyqtgraph directory

 * PyQtGraph version: 0.11.0.dev0+ged6586c
 * Qt Python binding: PySide2 5.13.1 Qt 5.13.1
 * Python version: 3.7.3
 * NumPy version: 1.17.2
 * Operating system: Centos 7
 * Installation method: python built from source. numpy from pip. pyqtgraph from git and / "python setup.py install" in pyqtgraph directory


### Additional context


Discussion:
I can reproduce this bug after updating from PyQt5 5.12.2 to PyQt5 5.13.1 under Windows 10.
This is a duplicate of an issue discussed in the comments of issue #1011 with @jongra. I would suggest to continue the analysis from there over here.
The issue seems to be that previously, Qt drawed the part of a path that it could draw, and now it does not draw anything if one part of the path is invalid. The path is created using `PlotCurveItem.getPath` and `PlotCurveItem.generatePath`, so if anyone comes up with a neat way to filter the path for `nan` entries, a Pull Request would certainly be welcome! Also, it would probably be an idea to allow for multiple paths to be generated, so if the curve is split by a np.nan value in the middle, it actually creates two seperated paths that are rendered without connection. For that, then also `PlotCurveItem.paint` would have to be adapted.
Just feel free to experiment with this and create a Pull Request!
Plotting multiple paths, separated by np.nan values, can be achieved by the `pyqtgraph.PlotCurveItem` parameter `connect = "finite"`
See here: [pyqtgraph.PlotCurveItem.setData](http://www.pyqtgraph.org/documentation/graphicsItems/plotcurveitem.html#pyqtgraph.PlotCurveItem.setData)
@jongra That is true in theory. It would work this way: `functions.arrayToQPath(x, y, connect='finite')` would create a `QPainterPath` that contains `nan` values, but carries a connected=0 flag that prevents it from showing a connected line, so it is therefore invisible and effectively splits the path.
Now the issue is that `QPainterPath`s containing `nan` values are not painted at all, so a path that contains `nan` values that are not connected is still not drawn.
I have come up with a workaround for the connect='finite' case. It is probably not very efficient, but it seems to work. Basically NaN values are removed (set to 0) manually and a boolean array is constructed to define the connectivity and suppress the lines where the would have normally been suppressed.

```
import numpy as np
import pyqtgraph as pg

print(pg.__version__)
print(pg.Qt.VERSION_INFO)

data = np.array(range(1000), dtype=np.float)
data2 = np.array(range(1000), dtype=np.float)
data2[2:10] = np.nan
data2[100:300] = np.nan
data2[990:998] = np.nan

pg.plot(data, title="no NaN")

if pg.Qt.VERSION_INFO.split(' ')[0] in ['PySide2', 'PyQt5']:
    con = np.isfinite(data2)
    data2[~con] = 0
    pg.plot(data2, title="NaN", connect=np.logical_and(con, np.roll(con, -1)))
else:
    pg.plot(data2, title="NaN", connect='finite')

pg.QtGui.QApplication.exec_()

```

@summoningdark @jongra Could you please test if Pull Request #1058 fixes the issue?
I still see the issue with Pull Request #1058. I think the reason is that while the changes set connectivity flags for the offending points, the values are still NaN. Adding the following lines fixes it for the connect='finite', but would mess up the 'all', 'pairs' or connect=array cases:
```
arr[1:-1]['x'][non_finite_values] = 0
arr[1:-1]['y'][non_finite_values] = 0
```
@summoningdark Oh, I made a mistake. I did something similar to what you suggested, but my solution still fails if two non-finite values are side by side. As soon as I have time, I will have a look at how to fix that.
I am speaking about these lines in the PR:

```python3
    arr[1:-1]['x'][non_finite_values] = np.roll(x,-1)[non_finite_values]
    arr[1:-1]['y'][non_finite_values] = np.roll(y,-1)[non_finite_values]
```
I can fxi this bug after updating to PyQt5 5.13.0 under Windows 10.
@Fantasticer How are things coming along? I'm short on time or I would try to help.
> @highfestiva 
i think is just a bug in pyqt.Install the PyQt5 5.13.0  version is okay for nan
Any progress on this? Tested and observed on ubuntu 18.04 as well. The workaround (go back to PySide2 5.13.0) works for us for now.
 I think this is an upstream issue, afaik there is no pyqtgraph Dev effort going toward this issue for the time being.  I would love to know if the issue persists with 5.14. 

Edit: tagget the wrong person.
@j9ac9k For me it persists with PySide2==5.14.0. With the pyqtgraph PR branch https://github.com/pyqtgraph/pyqtgraph/pull/1058 (with failing tests) AND PySide2==5.14.0, however, everything works fine (except there is a lot of debug output about ignored exceptions).
Current tests are failing on 5.14.0 due to the changing of how PySide loads in .ui files, which is what our examples app uses (we use the examples app as part of the test suite).
@j9ac9k For me, the example from the **Code to reproduce** section above still fails. I think this issue exists since `Qt 5.13.1` - @Fantasticer, your solution of "updating to `Qt 5.13.0`" might actually work because you are using a version older than `Qt 5.13.1`. Could you test whether updating to a higher version for PyQt5 and Qt still works for you?

### Tested environment:

 * PyQtGraph version: branch `develop`, commit 6a2791e1c41ee5b08e860186c26613ddc4ef201d
 * Qt Python binding: PyQt5 5.14.1 Qt 5.14.0
 * Python version: 3.8.1
 * NumPy version: 1.18.1
 * Operating system: Manjaro Linux (5.4.12-1-MANJARO)
 * Installation method: `pyqtgraph via `setup.py`, rest via system packages
I am seeing the problem too. PyQt5 v5.14.1   I've been using `connect="finite"` for a while and just noticed after a recent update that my plots are failing to appear. I traced it back to this issue.

 
@raacampbell thanks for reporting in; I suspect this is an upstream issue that we won't be able to do anything about, but I'll try and take a look at it and see if something comes up.

Resolving patch:
diff --git a/pyqtgraph/functions.py b/pyqtgraph/functions.py
--- a/pyqtgraph/functions.py
+++ b/pyqtgraph/functions.py
@@ -13,7 +13,7 @@
 import sys, struct
 from .pgcollections import OrderedDict
 from .python2_3 import asUnicode, basestring
-from .Qt import QtGui, QtCore, QT_LIB
+from .Qt import QtGui, QtCore, QT_LIB, QtVersion
 from . import getConfigOption, setConfigOptions
 from . import debug, reload
 from .reload import getPreviousVersion 
@@ -1483,20 +1483,38 @@ def arrayToQPath(x, y, connect='all'):
 
     path = QtGui.QPainterPath()
 
-    #profiler = debug.Profiler()
     n = x.shape[0]
+
     # create empty array, pad with extra space on either end
     arr = np.empty(n+2, dtype=[('c', '>i4'), ('x', '>f8'), ('y', '>f8')])
+
     # write first two integers
-    #profiler('allocate empty')
     byteview = arr.view(dtype=np.ubyte)
     byteview[:16] = 0
     byteview.data[16:20] = struct.pack('>i', n)
-    #profiler('pack header')
+
     # Fill array with vertex values
     arr[1:-1]['x'] = x
     arr[1:-1]['y'] = y
 
+    # inf/nans completely prevent the plot from being displayed starting on 
+    # Qt version 5.12.3; these must now be manually cleaned out.
+    isfinite = None
+    qtver = [int(x) for x in QtVersion.split('.')]
+    if qtver >= [5, 12, 3]:
+        isfinite = np.isfinite(x) & np.isfinite(y)
+        if not np.all(isfinite):
+            # credit: Divakar https://stackoverflow.com/a/41191127/643629
+            mask = ~isfinite
+            idx = np.arange(len(x))
+            idx[mask] = -1
+            np.maximum.accumulate(idx, out=idx)
+            first = np.searchsorted(idx, 0)
+            if first < len(x):
+                # Replace all non-finite entries from beginning of arr with the first finite one
+                idx[:first] = first
+                arr[1:-1] = arr[1:-1][idx]
+
     # decide which points are connected by lines
     if eq(connect, 'all'):
         arr[1:-1]['c'] = 1
@@ -1508,7 +1526,9 @@ def arrayToQPath(x, y, connect='all'):
         # A point will anyway not connect to an invalid point regardless of the
         # 'c' value of the invalid point. Therefore, we should set 'c' to 0 for
         # the next point of an invalid point.
-        arr[2:]['c'] = np.isfinite(x) & np.isfinite(y)
+        if isfinite is None:
+            isfinite = np.isfinite(x) & np.isfinite(y)
+        arr[2:]['c'] = isfinite
     elif isinstance(connect, np.ndarray):
         arr[1:-1]['c'] = connect
     else:
@@ -1516,10 +1536,9 @@ def arrayToQPath(x, y, connect='all'):
 
     arr[1]['c'] = 0  # the first vertex has no previous vertex to connect
 
-    #profiler('fill array')
     byteview.data[-20:-16] = struct.pack('>i', 0)  # cStart
     byteview.data[-16:-12] = struct.pack('>i', 0)  # fillRule (Qt.OddEvenFill)
-    #profiler('footer')
+
     # create datastream object and stream into path
 
     ## Avoiding this method because QByteArray(str) leaks memory in PySide
@@ -1530,11 +1549,9 @@ def arrayToQPath(x, y, connect='all'):
         buf = QtCore.QByteArray.fromRawData(path.strn)
     except TypeError:
         buf = QtCore.QByteArray(bytes(path.strn))
-    #profiler('create buffer')
-    ds = QtCore.QDataStream(buf)
 
+    ds = QtCore.QDataStream(buf)
     ds >> path
-    #profiler('load')
 
     return path
 


Test patch: