I'd like to create 2 animations and save them to one file, but get the error:
TypeError: 'FuncAnimation' object is not iterable
Separately each animation runs fine.
Here's a minimal-ish example that demonstrates the problem. The cosmetic stuff like ax.text and ax.axhline may help show what I'm trying to do, but is not essential to the problem. I'm running the code in a Jupyter notebook.
Thanks for any help.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import animation
xdata = np.array([0.2, 0.4, 0.6, 0.8])
ydata = np.array([0, 0, 0, 0])
fig = plt.figure(figsize=(8,1.25))
ax = fig.add_subplot(111)
ax.set_xlim(0, 1)
ax.set_ylim(-0.5, 0.5)
ax.barh(0, 1, align='center', height=0.7, color='.95', ec='None', zorder=-20)
ax.axhline(0, 0, 1, color='0.25', linewidth=.5)
ax.text(0, -0.45, "0", verticalalignment = "center", horizontalalignment="center", fontsize=10)
ax.text(1, -0.45, "1", verticalalignment = "center", horizontalalignment="center", fontsize=10)
ax.text(-0.01, 0, "0", verticalalignment = "center", horizontalalignment="center", fontsize=10)
ax.text(0.75, -0.45, "3/4", verticalalignment = "center", horizontalalignment="center", fontsize=10)
ax.axvline(0.75, 0.15, 0.85, color='.5', linestyle='dotted', linewidth=0.75)
line, = ax.plot(xdata, ydata, 'ro', markersize=7)
def init0(): # initialization function for first animation
line.set_data(xdata, ydata)
return line,
def init1(): # second animation
ax.set_xlim(0.5, 1)
ax.text(0.5, -0.45, "1/2", verticalalignment = "center", horizontalalignment="center", fontsize=10)
line.set_data(xdata, ydata)
return line,
def animate(i): # update function
ax.figure.canvas.draw()
return line,
# call animators
anim0 = animation.FuncAnimation(fig, animate, init_func=init0, frames=150, interval=20, blit=True)
anim1 = animation.FuncAnimation(fig, animate, init_func=init1, frames=150, interval=20, blit=True)
fig.set_size_inches(16, 2.5, True) # improve resolution in animation
plt.axis('off')
anim0.save('anims.mp4', fps=30, extra_args=['-vcodec', 'libx264'], extra_anim=anim1)
plt.show()
Related
I am learning every day and invest many hours in a simple looking project.
Now I am faced with the problem that kivy rotates backwards my windrose after input of values over 360 degree.
Example:
First input: 356 degree -> rotates forwards to next input 360 degree.
Third input: 10 degree -> rotates backwards from 360 degree all the way back to 10 degree instead of using the shortest way.
What can I do to teach kivy to rotate always the shortest way? Forward and backwards?
See the example picture here
import socket
from kivy.animation import Animation
from kivy.clock import Clock, mainthread
from kivy.lang import Builder
from kivy.uix.screenmanager import Screen, ScreenManager
from kivy.app import App
from kivy.config import Config
from kivy.core.window import Window
from kivy.properties import NumericProperty
Config.set('graphics', 'width', '600')
Config.set('graphics', 'height', '650')
Window.size = (550, 650)
Window.clearcolor = (0.219, 0.247, 0.282, 1)
icon = "meteo_icon.ico"
title = "AWOS 1.0 display"
UDP_IP = ""
UDP_PORT = 6999
sock=socket.socket(socket.AF_INET,
socket.SOCK_DGRAM)
sock.bind((UDP_IP, UDP_PORT))
class DirWindow(Screen):
pass
class WindowManager(ScreenManager):
pass
KV = """
WindowManager:
DirWindow:
<DirWindow>:
angle: 0
name: 'AWOS_windrose'
Image:
source: 'rwy_pic.png'
size_hint: None, None
size: 400, 400
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
canvas.before:
PushMatrix
Rotate:
angle: 110
axis: (0, 0, 1)
origin: (self.center_x, self.center_y , 0)
canvas.after:
PopMatrix
Image:
source: 'arrow_thin.png'
size_hint: None, None
size: 530, 530
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
# apply rotation matrix to this Image
canvas.before:
PushMatrix
Rotate:
angle: root.angle
axis: (0, 0, 1)
origin: (self.center_x, self.center_y , 0)
canvas.after:
PopMatrix
Image:
source: 'wind_dial.png'
size_hint: None, None
size: 500, 500
pos_hint: {'center_x': 0.5, 'center_y': 0.4}
"""
class MyMainApp(App):
angle = NumericProperty(0)
def build(self):
self.title = 'AWOS 1.0 - Wind Display'
Clock.schedule_interval(self.animate_needle, 2) # start the animation in 2 seconds
return Builder.load_string(KV)
def animate_needle(self, dt):
data, addr = sock.recvfrom(1024)
wind_direction = (data.decode()[35:38])
#wind_speed = (data.decode()[26:30])
wind_control = (data.decode()[20:24])
a = "WIND"
if wind_control == a:
self.anim = Animation(angle=(-int(wind_direction)), duration=2)
self.anim.repeat = True
windrose = self.root.get_screen('AWOS_windrose')
self.anim.start(windrose)
print(wind_direction)
def on_angle(self, item, angle):
if angle == 360:
item.angle = 0
I would like to know how to simply reverse the color order of a given colormap in order to use it with plot_surface.
The standard colormaps also all have reversed versions. They have the same names with _r tacked on to the end. (Documentation here.)
The solution is pretty straightforward. Suppose you want to use the "autumn" colormap scheme. The standard version:
cmap = matplotlib.cm.autumn
To reverse the colormap color spectrum, use get_cmap() function and append '_r' to the colormap title like this:
cmap_reversed = matplotlib.cm.get_cmap('autumn_r')
In matplotlib a color map isn't a list, but it contains the list of its colors as colormap.colors. And the module matplotlib.colors provides a function ListedColormap() to generate a color map from a list. So you can reverse any color map by doing
colormap_r = ListedColormap(colormap.colors[::-1])
As of Matplotlib 2.0, there is a reversed() method for ListedColormap and LinearSegmentedColorMap objects, so you can just do
cmap_reversed = cmap.reversed()
Here is the documentation.
As a LinearSegmentedColormaps is based on a dictionary of red, green and blue, it's necessary to reverse each item:
import matplotlib.pyplot as plt
import matplotlib as mpl
def reverse_colourmap(cmap, name = 'my_cmap_r'):
"""
In:
cmap, name
Out:
my_cmap_r
Explanation:
t[0] goes from 0 to 1
row i: x y0 y1 -> t[0] t[1] t[2]
/
/
row i+1: x y0 y1 -> t[n] t[1] t[2]
so the inverse should do the same:
row i+1: x y1 y0 -> 1-t[0] t[2] t[1]
/
/
row i: x y1 y0 -> 1-t[n] t[2] t[1]
"""
reverse = []
k = []
for key in cmap._segmentdata:
k.append(key)
channel = cmap._segmentdata[key]
data = []
for t in channel:
data.append((1-t[0],t[2],t[1]))
reverse.append(sorted(data))
LinearL = dict(zip(k,reverse))
my_cmap_r = mpl.colors.LinearSegmentedColormap(name, LinearL)
return my_cmap_r
See that it works:
my_cmap
<matplotlib.colors.LinearSegmentedColormap at 0xd5a0518>
my_cmap_r = reverse_colourmap(my_cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = my_cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = my_cmap_r, norm=norm, orientation='horizontal')
EDIT
I don't get the comment of user3445587. It works fine on the rainbow colormap:
cmap = mpl.cm.jet
cmap_r = reverse_colourmap(cmap)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = cmap, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = cmap_r, norm=norm, orientation='horizontal')
But it especially works nice for custom declared colormaps, as there is not a default _r for custom declared colormaps. Following example taken from http://matplotlib.org/examples/pylab_examples/custom_cmap.html:
cdict1 = {'red': ((0.0, 0.0, 0.0),
(0.5, 0.0, 0.1),
(1.0, 1.0, 1.0)),
'green': ((0.0, 0.0, 0.0),
(1.0, 0.0, 0.0)),
'blue': ((0.0, 0.0, 1.0),
(0.5, 0.1, 0.0),
(1.0, 0.0, 0.0))
}
blue_red1 = mpl.colors.LinearSegmentedColormap('BlueRed1', cdict1)
blue_red1_r = reverse_colourmap(blue_red1)
fig = plt.figure(figsize=(8, 2))
ax1 = fig.add_axes([0.05, 0.80, 0.9, 0.15])
ax2 = fig.add_axes([0.05, 0.475, 0.9, 0.15])
norm = mpl.colors.Normalize(vmin=0, vmax=1)
cb1 = mpl.colorbar.ColorbarBase(ax1, cmap = blue_red1, norm=norm,orientation='horizontal')
cb2 = mpl.colorbar.ColorbarBase(ax2, cmap = blue_red1_r, norm=norm, orientation='horizontal')
There is no built-in way (yet) of reversing arbitrary colormaps, but one simple solution is to actually not modify the colorbar but to create an inverting Normalize object:
from matplotlib.colors import Normalize
class InvertedNormalize(Normalize):
def __call__(self, *args, **kwargs):
return 1 - super(InvertedNormalize, self).__call__(*args, **kwargs)
You can then use this with plot_surface and other Matplotlib plotting functions by doing e.g.
inverted_norm = InvertedNormalize(vmin=10, vmax=100)
ax.plot_surface(..., cmap=<your colormap>, norm=inverted_norm)
This will work with any Matplotlib colormap.
There are two types of LinearSegmentedColormaps. In some, the _segmentdata is given explicitly, e.g., for jet:
>>> cm.jet._segmentdata
{'blue': ((0.0, 0.5, 0.5), (0.11, 1, 1), (0.34, 1, 1), (0.65, 0, 0), (1, 0, 0)), 'red': ((0.0, 0, 0), (0.35, 0, 0), (0.66, 1, 1), (0.89, 1, 1), (1, 0.5, 0.5)), 'green': ((0.0, 0, 0), (0.125, 0, 0), (0.375, 1, 1), (0.64, 1, 1), (0.91, 0, 0), (1, 0, 0))}
For rainbow, _segmentdata is given as follows:
>>> cm.rainbow._segmentdata
{'blue': <function <lambda> at 0x7fac32ac2b70>, 'red': <function <lambda> at 0x7fac32ac7840>, 'green': <function <lambda> at 0x7fac32ac2d08>}
We can find the functions in the source of matplotlib, where they are given as
_rainbow_data = {
'red': gfunc[33], # 33: lambda x: np.abs(2 * x - 0.5),
'green': gfunc[13], # 13: lambda x: np.sin(x * np.pi),
'blue': gfunc[10], # 10: lambda x: np.cos(x * np.pi / 2)
}
Everything you want is already done in matplotlib, just call cm.revcmap, which reverses both types of segmentdata, so
cm.revcmap(cm.rainbow._segmentdata)
should do the job - you can simply create a new LinearSegmentData from that. In revcmap, the reversal of function based SegmentData is done with
def _reverser(f):
def freversed(x):
return f(1 - x)
return freversed
while the other lists are reversed as usual
valnew = [(1.0 - x, y1, y0) for x, y0, y1 in reversed(val)]
So actually the whole thing you want, is
def reverse_colourmap(cmap, name = 'my_cmap_r'):
return mpl.colors.LinearSegmentedColormap(name, cm.revcmap(cmap._segmentdata))
I'm trying to figure out how to take a crop of an image determined dynamically in Tensorflow. Below is an example of what I am trying to accomplish, however I can't seem to make it work. Essentially, I want to feed images and the crop values for that image within the graph, and then continue on with other computations on those cropped pieces. My current attempt:
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
sess = tf.InteractiveSession()
img1 = np.random.random([400, 600, 3])
img2 = np.random.random([400, 600, 3])
img3 = np.random.random([400, 600, 3])
images = [img1, img2, img3]
img1_crop = [100, 100, 100, 100]
img2_crop = [200, 150, 100, 100]
img3_crop = [150, 200, 100, 100]
crop_values = [img1_crop, img2_crop, img3_crop]
def crop_image(img, crop):
tf.image.crop_to_bounding_box(img,
crop[0],
crop[1],
crop[2],
crop[3])
image_placeholder = tf.placeholder("float", [None, 400, 600, 3])
crop_placeholder = tf.placeholder(dtype=tf.int32)
sess.run(tf.global_variables_initializer())
cropped_image = tf.map_fn(lambda img, crop: crop_image(img, crop), elems=[image_placeholder, crop_placeholder])
result = sess.run(cropped_image, feed_dict={image_placeholder: images, crop_placeholder:crop_values})
plt.imshow(result)
plt.show()
/Users/p111/anaconda/bin/python /Users/p111/PycharmProjects/analysis_code/testing.py
Traceback (most recent call last):
File "/Users/p111/PycharmProjects/analysis_code/testing.py", line 31, in
cropped_image = tf.map_fn(lambda img, crop: crop_image(img, crop), elems=[image_placeholder, crop_placeholder])
File "/Users/p111/anaconda/lib/python3.5/site-packages/tensorflow/python/ops/functional_ops.py", line 390, in map_fn
swap_memory=swap_memory)
File "/Users/p111/anaconda/lib/python3.5/site-packages/tensorflow/python/ops/control_flow_ops.py", line 2636, in while_loop
result = context.BuildLoop(cond, body, loop_vars, shape_invariants)
File "/Users/p111/anaconda/lib/python3.5/site-packages/tensorflow/python/ops/control_flow_ops.py", line 2469, in BuildLoop
pred, body, original_loop_vars, loop_vars, shape_invariants)
File "/Users/p111/anaconda/lib/python3.5/site-packages/tensorflow/python/ops/control_flow_ops.py", line 2419, in _BuildLoop
body_result = body(*packed_vars_for_body)
File "/Users/p111/anaconda/lib/python3.5/site-packages/tensorflow/python/ops/functional_ops.py", line 380, in compute
packed_fn_values = fn(packed_values)
TypeError: () missing 1 required positional argument: 'crop'
Edit: It appears that elems will only accept a single tensor. Which means I would need to somehow combine my two tensors into one, and then unpack it in my function to get the values out. I'm not sure how I would perform that kind of tensor manipulation. I have found the glimpse method already and that does work, however I am wondering if the same can be done with this specific method. Mostly, I'm wondering how you would combine and then split a pair of tensors so it can be used in this method.
I saw this code from here.
elems = (np.array([1, 2, 3]), np.array([-1, 1, -1]))
alternate = map_fn(lambda x: x[0] * x[1], elems, dtype=tf.int64)
# alternate == [-1, 2, -3]
It is possible to use a tuple or a list to pack several elements into one so I tried this.
import tensorflow as tf
from matplotlib import pyplot as plt
import numpy as np
sess = tf.InteractiveSession()
img1 = np.random.random([400, 600, 3])
img2 = np.random.random([400, 600, 3])
img3 = np.random.random([400, 600, 3])
images = np.array([img1, img2, img3])
# images = tf.convert_to_tensor(images) # it can be uncommented.
img1_crop = [100, 100, 100, 100]
img2_crop = [200, 150, 100, 100]
img3_crop = [150, 200, 100, 100]
crop_values = np.array([img1_crop, img2_crop, img3_crop])
# crop_values = tf.convert_to_tensor(crop_values) # it can be uncommented.
def crop_image(img, crop):
return tf.image.crop_to_bounding_box(img,
crop[0],
crop[1],
crop[2],
crop[3])
fn = lambda x: crop_image(x[0], x[1])
elems = (images, crop_values)
cropped_image = tf.map_fn(fn, elems=elems, dtype=tf.float64)
result = sess.run(cropped_image)
print result.shape
plt.imshow(result[0])
plt.show()
It works on my machine with tf version 0.11 and python2. Hope this can help you.
Couple of things :
You do not have a return statement in the crop_image function.
map_fn accepts a single argument.
I strongly advise you to separate the graph definition and the session usage.
--
# Graph def
def crop_image(img, crop):
return tf.image.crop_to_bounding_box(img,
crop[0],
crop[1],
crop[2],
crop[3])
image_placeholder = tf.placeholder(tf.float32, [None, 400, 600, 3])
crop_placeholder = tf.placeholder(dtype=tf.int32)
cropped_image = tf.map_fn(lambda inputs: crop_image(*inputs), elems=[image_placeholder, crop_placeholder], dtype=tf.float32)
# Session
sess = tf.InteractiveSession()
img1 = np.random.random([400, 600, 3])
img2 = np.random.random([400, 600, 3])
img3 = np.random.random([400, 600, 3])
images = [img1, img2, img3]
img1_crop = [100, 100, 100, 100]
img2_crop = [200, 150, 100, 100]
img3_crop = [150, 200, 100, 100]
crop_values = [img1_crop, img2_crop, img3_crop]
sess.run(tf.global_variables_initializer())
result = sess.run(cropped_image, feed_dict={image_placeholder: images, crop_placeholder:crop_values})
plt.imshow(result[0])
plt.show()
tf.map_fn(f, l) runs function f for every tensor in list l. In your case, your function expects 2 arguments, but since you supply a flat list, map_fn() sends them one by one. According to docs, map_fn() supports variable arity, so what you should do is something like this
tf.map_fn(lambda img, crop: crop_image(img, crop),
elems=([image_placeholder, crop_placeholder], ))
so the list you pass to map_fn contains pairs of arguments.
I'm trying to create an animated polar plot that, where the radial view limit increases/decreases to accommodate the radius. The yaxis updates just fine if I set polar=False, but it doesn't work correctly for a polar axis.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def data_gen():
t = data_gen.t
cnt = 0
while cnt < 1000:
cnt+=1
t += 0.05
yield t, 1.1 + np.sin(2*np.pi*t) * np.exp(t/10.)
data_gen.t = 0
plt.rc ('grid', color='g', lw=1, ls='-')
plt.rc ('xtick', labelsize=15, color='b')
plt.rc ('ytick', labelsize=15, color='b')
fig = plt.figure(figsize=(8,8))
ax1 = fig.add_axes([.05, .90, .9, .08], polar=False, axisbg='#BFBFBF', xticks=[], yticks=[])
ax2 = fig.add_axes([.05, .05, .9, .8], polar=True, axisbg='k')
#ax = fig.add_axes([.1,.1,.8,.8], polar=False, axisbg='k')
line, = ax2.plot([], [], lw=2)
ax2.set_ylim(0, 2.2)
ax2.set_xlim(0, 140)
ax2.grid(1)
xdata, ydata = [], []
title = ax1.text (0.02, 0.5, '', fontsize=14, transform=ax1.transAxes)
def init():
line.set_data([], [])
title.set_text ('')
return line, title
def run(data):
# update the data
t,y = data
xdata.append(t)
ydata.append(y)
ymin, ymax = ax2.get_ylim()
if y >= ymax:
ax2.set_ylim (ymin, 2*ymax)
ax2.figure.canvas.draw()
title.set_text ("time = %.3f, y(t) = 1.1 + sin(2*pi*t) + exp(t/10) = %.3f" % (t, y))
line.set_data(xdata, ydata)
return line, title
ani = animation.FuncAnimation(fig, run, data_gen, init, blit=True, interval=100, repeat=False)
Actually, the view limit does adjust, but the tick labels stay the same. Inserting raw_input() after the canvas is redrawn reveals that everything is redrawn correctly, but then the tick labels revert back to what they were before. Stranger still, this doesn't occur until the update() function is called a second time and returns.
I didn't have this problem when I animated the plot the old way, by calling draw() repeatedly. But I'd rather do it the right way with the animation module, as the old way had performance problems (The above code block is only to demonstrate the problem, and isn't the actual program that I'm writing).
I should note that I'm still learning MPL, so I apologize if some of my terminology is wrong.
If you use blit, the background is saved in cache for fast draw, you can clear the cache when the axis range is changed, add ani._blit_cache.clear():
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
def data_gen():
t = data_gen.t
cnt = 0
while cnt < 1000:
cnt+=1
t += 0.05
yield t, 1.1 + np.sin(2*np.pi*t) * np.exp(t/10.)
data_gen.t = 0
plt.rc ('grid', color='g', lw=1, ls='-')
plt.rc ('xtick', labelsize=15, color='b')
plt.rc ('ytick', labelsize=15, color='b')
fig = plt.figure(figsize=(8,8))
ax1 = fig.add_axes([.05, .90, .9, .08], polar=False, axisbg='#BFBFBF', xticks=[], yticks=[])
ax2 = fig.add_axes([.05, .05, .9, .8], polar=True, axisbg='k')
#ax = fig.add_axes([.1,.1,.8,.8], polar=False, axisbg='k')
line, = ax2.plot([], [], lw=2)
ax2.set_ylim(0, 2.2)
ax2.set_xlim(0, 140)
ax2.grid(1)
xdata, ydata = [], []
title = ax1.text (0.02, 0.5, '', fontsize=14, transform=ax1.transAxes)
def init():
line.set_data([], [])
title.set_text ('')
return line, title
def run(data):
# update the data
t,y = data
xdata.append(t)
ydata.append(y)
ymin, ymax = ax2.get_ylim()
if y >= ymax:
ax2.set_ylim (ymin, 2*ymax)
ani._blit_cache.clear() # <- add to clear background from blit cache
title.set_text('') # <- eliminate text artifact in title
ax2.figure.canvas.draw()
title.set_text ("time = %.3f, y(t) = 1.1 + sin(2*pi*t) + exp(t/10) = %.3f" % (t, y))
line.set_data(xdata, ydata)
return line, title
ani = animation.FuncAnimation(fig, run, data_gen, init, blit=True, interval=100, repeat=False)
Hello All and Merry Christmas,
Could someone please explain me how the following sample of code works (http://matplotlib.sourceforge.net/examples/animation/random_data.html) ?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
timeline = [1,2,3,4,5,6,7,8,9,10] ;
metric = [10,20,30,40,50,60,70,80,90,100] ;
fig = plt.figure()
window = fig.add_subplot(111)
line, = window.plot(np.random.rand(10))
def update(data):
line.set_ydata(data)
return line,
def data_gen():
while True:
yield np.random.rand(10)
ani = animation.FuncAnimation(fig, update, data_gen, interval=5*1000)
plt.show()
In particular, I would like to use lists ("metric") in order to update the list.
Th problem is that FuncAnimation is using generators if I am not mistaken, but, how can I make it work ?
Thank you.
You can feed FuncAnimation with any iterable not just a generator.
From docs:
class matplotlib.animation.FuncAnimation(fig, func, frames=None,
init_func=None, fargs=None, save_count=None, **kwargs)
Makes an animation by repeatedly calling a function func, passing in
(optional) arguments in fargs. frames can be a generator, an iterable,
or a number of frames. init_func is a function used to draw a clear
frame. If not given, the results of drawing from the first item in the
frames sequence will be used.
Thus the equivalen code with lists could be:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
start = [1, 0.18, 0.63, 0.29, 0.03, 0.24, 0.86, 0.07, 0.58, 0]
metric =[[0.03, 0.86, 0.65, 0.34, 0.34, 0.02, 0.22, 0.74, 0.66, 0.65],
[0.43, 0.18, 0.63, 0.29, 0.03, 0.24, 0.86, 0.07, 0.58, 0.55],
[0.66, 0.75, 0.01, 0.94, 0.72, 0.77, 0.20, 0.66, 0.81, 0.52]
]
fig = plt.figure()
window = fig.add_subplot(111)
line, = window.plot(start)
def update(data):
line.set_ydata(data)
return line,
ani = animation.FuncAnimation(fig, update, metric, interval=2*1000)
plt.show()