Sunday, September 11, 2016

Real-Time Plots with Python (Cont)

In the previous posts we've set the stage for some simple Python plotting.  First, we demonstrated the generation of a data set followed by plotting it thereafter.  In the second post, we demonstrated how we could plot 'live' data in real-time.  The astute reader would have observed that over time the 'live' plot will slow down which will result in eventually falling behind plotting the data we're interested in.  This is because an ever-increasing data set will greedily consume the available memory and an ever-increasing data set will eventually grow to an unmanageable size for plotting.
This post will focus on remedying such slow downs by constraining the data set to a manageable size.  We'll also demonstrate some of the multi-plot capabilities of MatlibPlot.

Let's get started;

Reviewing the functionality from the past post, we can plot a 'live', ever-growing data set by something of the following sort:
#!/usr/bin/python
from pylab import *;
import time;
import collections;
import re;

def log(M):
  print "__(log) " + M;

schedEventLoopTime=0.01;
MaxX=50.0;
MaxLen=10;

def test00():
  #--plots a dynamic, continuously growing 2D data set in an autoscaling simple graph
  plt.ion();
  fig=plt.figure(1);
  ax1=fig.add_subplot(111);
  l1,=ax1.plot(100,100,'r-');

  D=[];
  i=0.0;
  while (i < MaxX):
    D.append((i,sin(i)));
    T=[x[0] for x in D];
    L=[x[1] for x in D];
    l1.set_xdata(T);
    l1.set_ydata(L);
    ax1.relim();
    ax1.autoscale_view();
    plt.draw();
    i+=0.10;
    plt.pause(schedEventLoopTime);
  show(block=False);

  plt.close();
#---main---
log("main process initializing");
test00();
log("main process terminating");


A slight modification can increase the plotting performance by constraining the data set to a fixed size; note the use of the collection.
def test02():
  #--plots a dynamic, fixed-length 2D data set in an autoscaling simple graph
  plt.ion();
  fig=plt.figure(1);
  ax1=fig.add_subplot(111);
  l1,=ax1.plot(100,100,'r-');

  D = collections.deque(maxlen=MaxLen);

  i=0.0;
  while (i < MaxX):
    D.append((i,sin(i)));
    T=[x[0] for x in D];
    L=[x[1] for x in D];
    l1.set_xdata(T);
    l1.set_ydata(L);
    plt.ylim([min(L),max(L)]);
    plt.xlim([min(T),max(T)]);
    plt.draw();
    i+=0.10;
    plt.pause(schedEventLoopTime);
  show(block=False);

  plt.close();


The use of the collection allows pre-allocation of a fixed array, appending to a full array will simply push out the oldest value.  The 'live' plot is constrained to a fixed data size, resulting in a plot resembling this;
video


We'll use the same fixed-sizing concept in a multi-plot example;
def test03():
  #--plots a dynamic, fixed-length 2D data set in an autoscaling multi-plot graph
  plt.ion();
  fig=plt.figure(1);
  ax1=fig.add_subplot(311);
  ax2=fig.add_subplot(312);
  ax3=fig.add_subplot(313);
  l1,=ax1.plot(100,100,'r-');
  l2,=ax2.plot(100,100,'r-');
  l3,=ax3.plot(100,100,'r-');
  time.sleep(3);

  D = collections.deque(maxlen=MaxLen);
  i=0.0;
  while (i < MaxX):
    D.append((i,sin(i),cos(i),cos(i*2)));
    T1=[x[0] for x in D];
    L1=[x[1] for x in D];
    L2=[x[2] for x in D];
    L3=[x[3] for x in D];

    l1.set_xdata(T1);
    l1.set_ydata(L1);

    l2.set_xdata(T1);
    l2.set_ydata(L2);

    l3.set_xdata(T1);
    l3.set_ydata(L3);

    ax1.set_xlim([min(T1),max(T1)]);
    ax1.set_ylim([min(L1),max(L1)]);
    ax2.set_xlim([min(T1),max(T1)]);
    ax2.set_ylim([min(L2),max(L2)]);
    ax3.set_xlim([min(T1),max(T1)]);
    ax3.set_ylim([min(L3),max(L3)]);

    plt.draw();
    i+=0.10;

    plt.pause(schedEventLoopTime);
  show(block=False);
  plt.close();



The result is a 'live' multi-plot that looks like the following;
video


That's all for now, happy coding.

No comments:

Post a Comment