0

I am trying to improve my plotting function. I want to plot data using my plotGraph function coming from an EEG board in real-time, pulling samples from an LSL @ 250Hz. Previously, I had a functional version using the regular self.ax.plot(x,y), clearing the data with self.ax.clear() every time the plot needed to refresh. Nonetheless, some profiling showed that my code was taking way too much time to plot in comparison to the rest of it.

One of the suggestions I got was to use set_data instead of plot and clear. I have multiple lines of data that I want to plot simultaneously, so I tried following Matplotlib multiple animate multiple lines, which you can see below (adapted code). Also, I was told to use self.figure.canvas.draw_idle(), which I tried, but I'm not sure if I did it correctly.

Unfortunately, it didn't work, the graph is not updating and I can't seem to find why. I'm aware that the source I just mentioned uses animation.FuncAnimation but I'm not sure that would be the problem. Is it?

Any ideas of why none of my lines are showing in my canvas' graph?

import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
class AppWindow:
   def plotGraph(self, x, y):
        for lnum,line in enumerate(self.lines):
            line.set_data(x[:], y[:, lnum])
        self.figure.canvas.draw_idle()
        plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
        plt.xlabel('Freq', fontsize = 9, color = tx_color)
        self.figure.canvas.draw()

   def __init__(self):
      self.root = tk.Tk() #start of application
      self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = 
   bg_color, highlightthickness=0)
      self.canvas.pack(fill = 'both', expand = True)
      self.figure = plt.figure(figsize = (5,6), dpi = 100)
      self.figure.patch.set_facecolor(sc_color)
      self.ax = self.figure.add_subplot(111)
      self.ax.clear()
      self.line, = self.ax.plot([], [], lw=1, color = tx_color)
      self.line.set_data([],[])

      #place graph
      self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
      self.chart_type.get_tk_widget().pack()

      self.lines = []
      numchan = 8 #let's say I have 8 channels
      for index in range(numchan):
          lobj = self.ax.plot([],[], lw=2, color=tx_color)[0]
          self.lines.append(lobj)
      for line in self.lines:
      line.set_data([],[])
  
def start(self):
   self.root.mainloop()
Jeanne Pindar
  • 617
  • 7
  • 15
mgmussi
  • 520
  • 1
  • 4
  • 19
  • This can be because a `draw` statement is missing. I believe adding `self.figure.canvas.draw()` after one of the updates might do the trick! – zwep Aug 28 '20 at 16:42
  • Hey @zwep, thanks for your comment. I tried adding ```self.figure.canvas.draw()``` after the updates, but it still doesn't work. – mgmussi Aug 28 '20 at 16:56

1 Answers1

0

You chart is empty because you are plotting empty arrays:

line.set_data([],[])

If you fill in the line arrays, the chart plots correctly.

Try this code. It updates the chart with new random data every second.

import tkinter as tk
import matplotlib
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import numpy as np
import random

bg_color='grey'
tx_color='green'
sc_color='linen'

numchan = 8
chlen = 100
xvals=[(x-40)/20 for x in range(chlen)]  # X coordinates
chcolors= ['gold','blue','green','maroon','red','brown','purple','cyan']


class AppWindow:
   def plotGraph(self):
      self.figure.canvas.draw_idle()
      plt.ylabel('Magnitude', fontsize = 9, color = tx_color)
      plt.xlabel('Freq', fontsize = 9, color = tx_color)
      self.figure.canvas.draw()
        
   def UpdateChannelData(self):  # callback with new data
      # fake random data
      for i,ch in enumerate(self.chdata):
         for p in range(len(ch)):
            ch[p] += (random.random()-.5)/100
         self.lines[i].set_data(xvals, ch)
         
      self.plotGraph()
      self.root.after(100, self.UpdateChannelData)  # simulate next call

   def __init__(self):
      global chzero
      self.root = tk.Tk() #start of application
      self.canvas = tk.Canvas(self.root, height = 420, width = 780, bg = bg_color, highlightthickness=0)
      self.canvas.pack(fill = 'both', expand = True)
      self.figure = plt.figure(figsize = (5,6), dpi = 100)
      self.figure.patch.set_facecolor(sc_color)
      self.ax = self.figure.add_subplot(111)
      self.ax.clear()
      self.line, = self.ax.plot([], [], lw=1, color = tx_color)
      self.line.set_data([],[])

      #place graph
      self.chart_type = FigureCanvasTkAgg(self.figure, self.canvas)
      self.chart_type.get_tk_widget().pack()

      self.lines = []
      #numchan = 8 #let's say I have 8 channels
      for index in range(numchan):
          lobj = self.ax.plot([],[], lw=1, color=chcolors[index])[0]
          self.lines.append(lobj)
      # set flat data
      self.chdata = [[0 for x in range(chlen)] for ch in range(numchan)]
      self.root.after(1000, self.UpdateChannelData) # start data read
  
   def start(self):
       self.root.mainloop()
       
AppWindow().start()

Output:

EEG

Mike67
  • 11,175
  • 2
  • 7
  • 15
  • Helllo @Mike67, thanks for your answer. The problem is that in adjusting the way you did, I lose the ability to call my function recurrently to plot new samples. Is there a solution in which I can update the plot within the function? – mgmussi Aug 28 '20 at 19:08
  • You want to call `plotGraph`recursively or in a loop? Or should the data be set in a single call to `plotGraph` ? – Mike67 Aug 28 '20 at 19:21
  • In a while loop. As the samples come in through the LSL, I want to plot them. – mgmussi Aug 28 '20 at 19:50
  • Answer updated. You will need to adjust the scaling and plug in the EEG callback mechanism. – Mike67 Aug 28 '20 at 21:32
  • Thanks, Mike. But unfortunately, it still didn't work... – mgmussi Sep 02 '20 at 16:24