Streaming events to a plot#

This notebook will illustrate how a plot can be continuously updated with new incoming data, without hogging the event loop, meaning that interacting with the plot (zooming, panning, …) is possible while the data is streaming in.

Working in other notebook cells is also possible while the plot is updating.

[1]:
import scipp as sc
import plopp as pp
import ipywidgets as ipw
%matplotlib widget

Create a data generator#

We create an object which generates new data points when its update method is called.

[2]:
import numpy as np

class DataGen:
    """A data generator which makes new data when asked"""
    def __init__(self):
        # Make an empty container with 300x300 bins
        nx, ny = 300, 300
        xmin, xmax = -10.0, 10.0
        ymin, ymax = -10.0, 10.0
        self.data = sc.DataArray(data=sc.zeros(sizes={'y': ny, 'x': nx}),
                                 coords={'x': sc.linspace('x', xmin, xmax, nx+1, unit='m'),
                                         'y': sc.linspace('y', ymin, ymax, ny+1, unit='m')})

    def __call__(self, iteration: int=0):
        # Generate new data when called
        npoints = 100
        x = sc.array(dims=['event'], values=np.random.normal(scale=2, size=npoints), unit='m')
        y = sc.array(dims=['event'], values=np.random.normal(scale=2, size=npoints), unit='m')
        new_events = sc.DataArray(
            data=sc.ones(sizes=x.sizes, unit=""),
            coords={'x': x, 'y': y})
        # Histogram and add to container
        self.data += new_events.hist({xy: self.data.coords[xy] for xy in "xy"})
        return self.data

data = DataGen()

Connecting the data generator to a Play widget#

We now use ipywidgets’s Play widget to continuously send updates at regular intervals.

[3]:
# Create a play widget that fires every 100 ms
play = ipw.Play(min=0, max=500, interval=100)

# Wrap the widget in a widget node
play_node = pp.widget_node(play)

# Connect the widget to the data generator.
# Note that `data` here is not a function, but our generator
# class that has a `__call__` method defined.
stream_node = pp.Node(data, iteration=play_node)

# Plot on a figure
fig = pp.imagefigure(stream_node, norm="log", cbar=True)
[5]:
# Display figure a play widget
ipw.VBox([fig, play])
[5]:
  • After pressing play, data starts collecting on the plot.

  • Zooming and panning is still possible while the image is updating

  • Working elsewhere in the notebook is also not blocked by the updating plot

[6]:
pp.show_graph(fig)
[6]:
../_images/gallery_streaming-plot_9_0.svg