1

I'm having trouble getting my line graph to animate.

Background: I'm working on a program that will handle simulating network latency and I'm trying to graph the latency so I can see how well my program is keeping up with the load of commands coming out of the controller.

I've setup my figure:

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0,2), ylim = (-2,2))
line, = ax.plot([], [], lw=2)

setup an init() and animate() function

# initialization function: plot the background of each frame
def init():
    line.set_data([], [])
    return line,

def animate(i):
    line.set_data(x[i], y[i])
    return line,

then in my DelayedTask.process() function (where I measure the time between the intended execution and actual execution) I append the values and index to my x,y lists.

delta =  self.time - datetime.now()
lock = threading.Lock()
lock.acquire()
x.append(len(x))
y.append(delta.total_seconds())
lock.release()

finally at the bottom of my program, I create the animation function.

anim = animation.FuncAnimation(fig, animate, init_func=init, frames=200, interval=20, blit=True)

Unfortunately, the graph shows up, but the numbers won't plot. I've put a breakpoint in the animate() function and in the deltas are filling in the list, but it won't show any lines on the graph.

Here is the full code:

import multiprocessing, requests, threading
import decimal
import random
import time
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation, rc

from queue import Queue
from multiprocessing.dummy import Pool as ThreadPool
from threading import Thread
from datetime import datetime, timedelta

class WorkQueue:
    def __init__(self, threads=6):
        self.threads = threads

    def process(self, work):
        pool = ThreadPool(self.threads)
        results = pool.map(DelayedTask.process, work)
        pool.close()
        pool.join()

class DelayedTask:
    def __init__(self, func, delay, message):
        print("DelayTask.__init__: {0}".format((func.__name__, delay, message)))
        self.func = func
        self.time = datetime.now() + timedelta(milliseconds=delay)
        self.message = message

    def process(self):
        delta =  self.time - datetime.now()
        lock = threading.Lock()
        lock.acquire()
        x.append(len(x))
        y.append(delta.total_seconds())
        lock.release()
        if delta.total_seconds() > 0.01:
            print('DelayTask.Process: Sleeping {0} milliseconds\n'.format(round(delta.total_seconds() * 1000)))
            time.sleep(delta.total_seconds())
            self.func(self.message)


        elif delta.total_seconds() < 0.01 and delta.total_seconds() > 0:
            print('DelayTask.Process: Processing with {0} milliseconds remaining\n'.format(round(delta.total_seconds() * 1000)))
            self.func(self.message)
        else:
            print("DelayTask.Process: Processing task: {0} milliseconds late\n".format(round(delta.total_seconds() * -1000)))
            self.func(self.message)
        return True

    def __str__(self):
        return str((self.func.__name__, self.time, self.message))

def get(url):

    print("Requesting {0}".format(url))

    r = requests.get(url=url)
    print("get(url): Received response for {0} with Status Code {1}".format(url, r.status_code))

aggregatorq = multiprocessing.Queue()

# First set up the figure, the axis, and the plot element we want to animate
fig = plt.figure()
ax = plt.axes(xlim=(0,2), ylim = (-2,2))
line, = ax.plot([], [], lw=2)

x = []
y = []

# initialization function: plot the background of each frame
def init():
    line.set_data([], [])
    return line,

def animate(i):
    line.set_data(x[i], y[i])
    return line,

def collector():
    bucket = []
    while len(bucket) <= 10:
        task = aggregatorq.get()
        print("collector: aggregating Tasks\n")
        bucket.append(DelayedTask(task['func'], task['delay'], task['message']))

        if(len(bucket) == 10):
            bucket.sort(key=lambda x: x.time, reverse=False)
            firsttask = bucket[0]
            firsttime =  firsttask.time - datetime.now()
            if firsttime.total_seconds() >= 0:
                print('collector: Sleeping {0} seconds until first task in bucket\n'.format(firsttime.total_seconds()))
                time.sleep(firsttime.total_seconds())
            queue = WorkQueue(10)
            queue.process(bucket)
            bucket.clear()

def controller():
    print("Starting Controller\n")
    finishtime = datetime.now() + timedelta(seconds=5)
    print("controller: Will finish at {0}\n".format(finishtime))

    sites = ["att", "google", "hulu", "msn", "yahoo", "gmail"]
    while True:
        if datetime.now() > finishtime:
            print("Controller Finished")
            return;
        else:
            pass
            print("{0} remaining in controller..".format(finishtime - datetime.now()))

        requestdelay = random.randint(1, 20)
        randomsite = random.randint(0, len(sites)-1)
        aggregatorq.put({'func': get, 'delay': requestdelay, 'message': 'http://www.{0}.com'.format(sites[randomsite])})

t = threading.Thread(target=controller)
t2 = threading.Thread(target=collector)

# call the animator.  blit=True means only re-draw the parts that have changed.
anim = animation.FuncAnimation(fig, animate, init_func=init,
                               frames=200, interval=20, blit=True)

def main():
    t.start()
    t2.start()
    plt.show()

if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        print('Interrupted')
        t.join()
        t2.join()
        try:
            sys.exit(0)
        except SystemExit:
            os._exit(0)
  • 1
    What is `x` and `y`? It's never defined. – ImportanceOfBeingErnest Jan 21 '18 at 17:18
  • They are lists. You're right, I edited the question and added them. I forgot to add them back after testing a few other things trying to get it working. – Bryan Davis Jan 21 '18 at 17:21
  • Do you get any kind of error message? – Thomas Kühn Jan 21 '18 at 17:21
  • @ThomasKühn no error. I only get the output of the print statements. I set the append to x and y in the `DelayedTask.process` function. – Bryan Davis Jan 21 '18 at 17:25
  • Ok, I'm guessing that your problem is the `i` in `line.set_data(x[i], y[i])` -- try `line.set_data(x, y)` instead, otherwise your line only has one point at any time, which would mean that you get no plot. – Thomas Kühn Jan 21 '18 at 17:27
  • I see a line now! Trying to get the scale right now. – Bryan Davis Jan 21 '18 at 17:32
  • Using line.set_data(x[i], y[i]) meant that you were only displaying one data point each time the animate method was called. – domochevski Jan 21 '18 at 17:34
  • So this is my first successful question, so do I post the updated working code now? – Bryan Davis Jan 21 '18 at 17:36
  • @ImportanceOfBeingErnest I tried that solution first and it didn't work for me. The solution to the other question was to grab the last value of the list in the loop. I'm aggregating the data differently so the solutions are different. – Bryan Davis Jan 21 '18 at 21:04
  • 1
    Since I don't think that we need a question for each possible value of `i`, I would still consider this a duplicate. The problem is the same: You get an empty graph, because you only plot a single point, instead of all points that are needed to form the line. – ImportanceOfBeingErnest Jan 21 '18 at 21:08
  • Seems you're right. – Bryan Davis Jan 21 '18 at 21:30

1 Answers1

1

Your problem is in the update function. Using the statement

line.set_data(x[i], y[i])

You assign exactly one data point to your line every time update is called. Therefore you cannot see any line, as lines are only plotted between data points. To fix the problem, leave out the indexing:

line.set_data(x, y)

This way all your collected data will be plotted.

Thomas Kühn
  • 9,412
  • 3
  • 47
  • 63