7
\$\begingroup\$

I would like to share a matplotlib real-time monitor plot that I needed for another application. The basic trick is to reverse the time, so that t=0 is now and t=20 is twenty seconds in the past and to plot the signal from 0 at the right towards the left. The core method is update that reverses time using the deque appendleft and applying a reverse axis by self.ax_monitor.set_xlim(self.monitor_time_window_s, 0), i.e., from high to low, rather than the typical low to high.

The following code is a running example (requires matplotlib and numpy):

from collections import deque
import time
import numpy as np
import matplotlib.pyplot as plt
fps = 24
seconds_per_frame = 1 / fps
class Monitor():
 ''' class to monitor value versus time
 '''
 def __init__(self, figsize=(5, 2), monitor_window=10, update_interval=0.1,
 ymin=-1, ymax=1, ticks=None):
 self.monitor_time_window_s = monitor_window
 self.update_monitor_interval_s = update_interval
 self.fig_monitor, self.ax_monitor = plt.subplots(
 1, 1, figsize=figsize)
 self.ax_monitor.set_ylim(ymin, ymax)
 if ticks is None:
 ticks = [ymin, 0, ymax]
 self.ax_monitor.set_yticks(ticks)
 self.ax_monitor.tick_params(axis='y', which='major', labelsize=6)
 self.ax_monitor.tick_params(axis='x', which='major', labelsize=6)
 self.ax_monitor.grid(True)
 self.fig_monitor.tight_layout()
 self.monitor_plot, = self.ax_monitor.plot(
 [0], [0], color='black', linewidth=0.5)
 self.ax_monitor.set_xlim(self.monitor_time_window_s, 0)
 @property
 def update_time_s(self):
 return self.update_monitor_interval_s
 @property
 def monitor_fig(self):
 return self.fig_monitor
 @property
 def monitor_ax(self):
 return self.ax_monitor
 def set_fig_title(self, title='Monitor Figure'):
 self.fig_monitor.canvas.manager.set_window_title(title)
 def set_ax_title(self, title='Monitor'):
 self.ax_monitor.set_title(title)
 self.fig_monitor.tight_layout()
 def blit(self):
 self.fig_monitor.canvas.draw()
 self.fig_monitor.canvas.flush_events()
 def update(self, _time, value):
 ''' method to plot value in time_window_graphs, time is
 past time, so t=0 is now and t=20 is 20 seconds ago
 '''
 # reset when time is zero
 if _time < self.update_monitor_interval_s:
 self.monitor_values = []
 self.time_values_reversed = deque([])
 self.time_reversed = []
 # build the time_reversed list (in reversed order to represent
 # time passed) and trim the monitor_values so they keep the
 # a finite length
 self.monitor_values.append(value)
 if _time < self.monitor_time_window_s + self.update_monitor_interval_s:
 self.time_values_reversed.appendleft(_time)
 self.time_reversed = list(self.time_values_reversed)
 else:
 self.monitor_values.pop(0)
 self.monitor_plot.set_data(self.time_reversed, self.monitor_values)
def run_monitor(event, monitor):
 def current_time():
 return time.time()
 _time = 0
 actual_start_time = current_time()
 while True:
 value = np.sin(_time) - 0.5 + np.random.random_sample()
 if _time % monitor.update_time_s < seconds_per_frame:
 monitor.update(_time, value)
 monitor.blit()
 _time += seconds_per_frame
 # wait for actual time to catch up with model time _time
 running_time = current_time() - actual_start_time
 while running_time < _time:
 running_time = current_time() - actual_start_time
def main():
 monitor = Monitor(monitor_window=20, ymin=-1.6, ymax=1.6,
 ticks=[-1.5, -1.0, -0.5, 0.0, +0.5, 1.0, 1.5])
 monitor.set_fig_title('Press any key to start ...')
 monitor.set_ax_title()
 monitor.monitor_fig.canvas.mpl_connect(
 'key_press_event', lambda event: run_monitor(event, monitor))
 plt.show()
if __name__ == "__main__":
 main()
asked Jul 22, 2019 at 11:58
\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

UX

The GUI looks great, and the message at the top of the graph is helpful. However, exiting out of the GUI is a little awkward. When I click on the "x", the GUI window closes, but I still need to Ctrl-C in my shell to end the program. Perhaps using matplotlib close would be cleaner.

Naming

The PEP 8 style guide recommends using upper case for constants:

fps would be FPS (or spell it out as FRAMES_PER_SECOND)

seconds_per_frame would be SECONDS_PER_FRAME

There are many functions and variables with "time" in their name. However, the variable named _time is vague. If it represents the past time, perhaps past_time would be a better name.

Unused code

The event variable is not used in this function, and it should be removed:

def run_monitor(event, monitor):

DRY

In the run_monitor function, this line is repeated twice:

running_time = current_time() - actual_start_time

You could eliminate the running_time variable and use this loop instead:

while True:
 if current_time() - actual_start_time < _time:
 break

Documentation

You could add a docstring to the top of the code to summarize its purpose:

"""
Real-time monitor plot. 
The basic trick is to reverse the time, so that t=0 is now and t=20
is twenty seconds in the past and to plot the signal from 0 at 
the right towards the left. 
"""
answered Feb 23 at 12:14
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.