How to display streaming data with JupyterHub

Hi there,

I want to display fast moving data (similar to an oscilloscope) using JupyterHub.

Currently the incoming data is processed in JupyterLab cells and then displayed in a separate pyqtgraph window. This works fine, presumably because the data source, JupyterLabs and the pyqtgraph window all run in the same session.

Now with JupyterHub the browser, the data source and the pyqtgraph window would run in the same session, but JupyterLab is running in its own sudo session (ideally even on another backend server/cloud).

Whatever the exact reason is, using a pyqtgraph window kills the kernel when using it with JupyterHub where it works with only running JupyterLab.

I am new to JupyterHub, so maybe the general approach is wrong?
If so, I am open to other solutions:

  • The display could be inside a cell or not.
  • I am not limited to python (although preferred)
  • Using pyqtgraph is not a given, I could switch to something else

To make it more clear what I want to achieve, here is code that runs in JupyterLab but not JupyterHub:

import pyqtgraph as pg
from pyqtgraph.Qt import QtWidgets, QtCore
import numpy as np


class Graph(pg.GraphicsLayoutWidget):
    """ Main Window
    """

    def __init__(self):
        print('Graph start')
        super().__init__(title='My Plot', size=(1024, 400), show=True)

        self.update_ms = 20        # update interval
        self.steps = 0             # update steps
        self.freq = 6              # Hz for simulating data as a sine wave
        self.duration_ms = 5000    # time range to display in graph
        self.points = 500          # function points within duration

        self.app = QtCore.QCoreApplication.instance()
        if self.app is None:
            self.app = QtWidgets.QApplication([])

        self.init_plot()

        self.timer = QtCore.QTimer()
        self.timer.timeout.connect(self.update)
        self.timer.start(self.update_ms)

        self.app.exec_()
        print('Graph end')

    def closeEvent(self, event):
        event.accept()
        self.timer.stop()
        self.app.quit()

    def keyPressEvent(self, event):
        if event.key() == QtCore.Qt.Key_Q:
            event.accept()
            self.close()

    def init_plot(self):
        self.plot = self.addPlot(row=0, col=0)
        self.plot.showAxis('left', True)
        self.plot.setMenuEnabled('left', True)
        self.plot.showAxis('bottom', True)
        self.plot.setMenuEnabled('bottom', False)
        self.plot.setTitle('TimeSeries Plot')
        self.graph = self.plot.plot(pen=(255, 200, 0))

    def simulateData(self, t1, t2):
        T = np.linspace(start=t1, stop=t2, num=self.points)
        Y = np.sin(2 * np.pi * self.freq * T)
        return (T, Y)

    def update(self):
        now_ms = self.steps * self.update_ms  # time going forward in steps
        self.steps = self.steps + 1           # next time step
        T, Y = self.simulateData(now_ms/1000, (now_ms+self.duration_ms)/1000)
        self.graph.setData(T, Y)
        self.app.processEvents()


def main():
    try:
        print('main start')
        Graph()
    except BaseException as e:
        print('Exception', str(e))
    finally:
        print('main end')


if __name__ == '__main__':
    main()