title: How to enable matplotlib blitting to create fast realtime graph in jupyterlab?
body: <p>I have the following code in jupyterlab, which updates a graph in realtime when I move a slider. If I disable blitting it updates, although at a very slow framerate (1 fps). If I enable blitting, it doesn't update at all - any idea why? The code uses the ipympl library to allow realtime update of matplotlib graphs in jupyterlab.</p>
  <pre><code>import pandas as pd, numpy as np
  import time
  import matplotlib.pyplot as plt
  import ipywidgets as widgets
  from itertools import count
  from ipywidgets import Button, Layout
  
  # next line enables ipympl
  %matplotlib widget 
  
  
  blit = True # False works, True doesn't.
  
  plt.close('all')
  plt.ioff()
  
  output = widgets.Output(layout={'width': '700px', 'height': '300px'})
  fig, axs = plt.subplots(3, 2, figsize=(10, 8), sharex=True)
  fig.canvas.header_visible = False
  fig.canvas.toolbar_visible = False
  
  
  for i in range(3):    
      axs[i,0].set_ylim(-1.5,1.5)
      axs[i,0].set_xlim(0,20)
      
  # index giver
  x_value = count()
  
  # expanding dataset
  x, y = [], []
  
  # initialise dummy data
  [x.append(next(x_value)) for i in range(2)]
  [y.append([1]*3) for i in range(2)]
  
  # setup desired and actual angle plots
  col_names = ['col1', 'col2', 'col3']
  ax_df = pd.DataFrame(index=x,columns=col_names, data=y).plot(subplots=True, ax=axs[:,0])
  
  if blit:
      bgs = []
      for ax in ax_df:
          # cache the background
          ax_background = fig.canvas.copy_from_bbox(ax.bbox)
          bgs.append(ax_background)
      
      fig.canvas.draw()  # initial draw required
  
  # monitor framerate
  t_start = time.time()   
  
  # event handler
  def on_value_changed(change):    
      with output:    
          next_x = next(x_value) # generate next x axis value
          x.append(next_x)
          y.append([change.new]*3) 
  
          for i in range(3):            
              if blit:              
                  # update data
                  line = ax_df[i].get_lines()[0]
                  line.set_data(x, pd.DataFrame(y).iloc[:,i])
                  
                  # restore background
                  fig.canvas.restore_region(bgs[i])
                  
                  # redraw just the points
                  ax_df[i].draw_artist(line)
  
                  # fill in the axes rectangle
                  fig.canvas.blit(ax_df[i].bbox)
                  
              else:
                  # update data
                  ax_df[i].get_lines()[0].set_data(x, pd.DataFrame(y).iloc[:,i])
  
                  # rescale view
                  ax_df[i].autoscale_view(None,'x',None)
                  ax_df[i].relim()
              
          fig.canvas.flush_events()
          
          if not blit:
              fig.canvas.draw() # this slows down framerate, not required for blit
  
          print(f&quot;FPS: {round(next_x/(time.time() - t_start),2)}&quot;, end=&quot;, &quot;)
  
  sliders = []
  int_slider = widgets.FloatSlider(description=&quot;test&quot;, 
                                   min=-1, max=1, 
                                   value = 0, continuous_update=True,
                                   orientation=&quot;horizontal&quot;,                                      
                                   layout=Layout(width=&quot;500px&quot;, height=&quot;20px&quot;))    
  int_slider.observe(on_value_changed, names=&quot;value&quot;)
  sliders = widgets.VBox([int_slider, fig.canvas, output])
  display(sliders)
  
  </code></pre>