2

I'm trying to continuously plot data received via network using matplotlib.

On the y-axis, I want to plot a particular entity, while the x-axis is the current time.

The x-axis should cover a fixed period of time, ending with the current time.

Here's my current test code, which simulates the data received via network with random numbers.

import threading
import random
import time
import signal
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as md

class NPData():
    def __init__(self, size):
        self.data = np.zeros((size,2)) # size rows, 2 cols
        self.size = size
        self.head = 0
    def __len__(self):
        return self.data.__len__()
    def __str__(self):
        return str(self.data)
    def append(self, data):
        self.data[self.head] = data
        self.head = (self.head + 1) % self.size
    def get_x_range(self):
        return (self.data.min(axis=0)[0], self.data.max(axis=0)[0])

class Producer(threading.Thread):
    def __init__(self):
        super().__init__()
        random.seed()
        self.running = True
        self.data = NPData(100)
    def get_data(self):
        return self.data.data
    def stop(self):
        self.running = False
    def run(self):
        while self.running:
            now_ms = md.date2num(int(time.time() * 1000)) # ms
            sample = np.array([now_ms, np.random.randint(0,999)])
            self.data.append(sample)
            time.sleep(0.1)

prog_do_run = True

def signal_handler(sig, frame):
    global prog_do_run
    prog_do_run = False

def main():
    signal.signal(signal.SIGINT, signal_handler)
    p = Producer()
    p.start()

    fig, ax = plt.subplots()
    xfmt = md.DateFormatter('%H:%M:%S.%f')
    ax.xaxis.set_major_formatter(xfmt)
    #ax.plot(p.get_data())
    #ax.set_ylim(0,999)
    plt.show(block=False)

    while prog_do_run:
        x_range = p.data.get_x_range()
        ax.set_xlim(x_range)
        #ax.set_ylim(0,999)
        print(p.get_data())
        #ax.plot(p.get_data())
        plt.draw()
        plt.pause(0.05)

    p.stop()

Notes:

The Producer class is supposed to emulate data received via network.

I've encountered two main issues:

  1. I'm struggling to find out what actually needs to be called inside an endless loop in order for matplotlib to continuously update a plot (efficiently). Is it draw(), plot(), pause() or a combination of those?

  2. I've been generating milliseconds timestamps and matplotlib seems to not like them at all. The official docs say to use date2num(), which does not work. If I just use int(time.time() * 1000) or round(time.time() * 1000), I get OverflowError: int too big to convert from the formatter.

3
  • if you need time in ms, why not time.time() * 1e3 -- i.e. make it a float Commented May 12 at 15:58
  • @tmdavison you're generally right, using 1e3 would explicitly make it a float. But I think the np.array() constructor makes it a float anyway. Commented May 12 at 17:05
  • np.array won't make it a float if everything you put in there is explicitly an int, it will preserve the datatype; your overflow error even tells you the items are still ints when they make it to the formatter. Commented May 13 at 10:55

1 Answer 1

3

Basically there are small errors. For example don't call ax.plot() in the loop because it adds a new line each time, which is inefficient and causes multiples lines to be drawn. I would suggest to use a single line2D object by creating it once and then update its data with set_data() insde your loop. Additionally, use fig.canvas.draw_idle() or plt.pause() to refresh; plt.pause() is the simplest for interactive updates. Another error is the date, matplotlib expects dates in "days since 0001-01-01 UTC" as floats. I would say to use datetime.datetime.now() and md.date2num() to convert to the correct format. Take in cosideration that milliseconds are not directly supported in the tick labels, but you can format them.

import threading
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.dates as md
import datetime
import time
import signal

class NPData():
    def __init__(self, size):
        self.data = np.zeros((size,2))
        self.size = size
        self.head = 0
        self.full = False

    def append(self, data):
        self.data[self.head] = data
        self.head = (self.head + 1) % self.size
        if self.head == 0:
            self.full = True

    def get_data(self):
        if self.full:
            return np.vstack((self.data[self.head:], self.data[:self.head]))
        else:
            return self.data[:self.head]

    def get_x_range(self, window_seconds=10):
        data = self.get_data()
        if len(data) == 0:
            now = md.date2num(datetime.datetime.now())
            return (now - window_seconds/86400, now)
        latest = data[-1,0]
        return (latest - window_seconds/86400, latest)

class Producer(threading.Thread):
    def __init__(self):
        super().__init__()
        self.running = True
        self.data = NPData(1000)

    def stop(self):
        self.running = False

    def run(self):
        while self.running:
            now = datetime.datetime.now()
            now_num = md.date2num(now)
            sample = np.array([now_num, np.random.randint(0,999)])
            self.data.append(sample)
            time.sleep(0.1)

prog_do_run = True

def signal_handler(sig, frame):
    global prog_do_run
    prog_do_run = False

def main():
    signal.signal(signal.SIGINT, signal_handler)
    p = Producer()
    p.start()

    fig, ax = plt.subplots()
    xfmt = md.DateFormatter('%H:%M:%S.%f')
    ax.xaxis.set_major_formatter(xfmt)
    line, = ax.plot([], [], 'b-')
    ax.set_ylim(0, 999)
    plt.show(block=False)

    while prog_do_run:
        data = p.data.get_data()
        if len(data) > 0:
            line.set_data(data[:,0], data[:,1])
            x_range = p.data.get_x_range(window_seconds=10)
            ax.set_xlim(x_range) #Keep window fixed to recent data 
            ax.figure.canvas.draw_idle()
        plt.pause(0.05)

    p.stop()
Sign up to request clarification or add additional context in comments.

1 Comment

Thanks, that works like a charm! Got it, only canvas.draw_idle() and plt.pause() in the loop. I like the change with the line object and then update it with set_data() in the loop. For the times, it seems using datetime is the way to go then. Thank you very much!

Your Answer

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.