I think I'm doing something really dumb, but I can't quite figure it out. I want to create a class to display a set of images as subplots; the display should be updated manually from within a loop. Here's the class I've created to try to do this:
import matplotlib.pyplot as plt
import numpy as np
class tensor_plot:
def __init__(self, tensor_shape, nrows=1):
self.img_height, self.img_width, self.num_imgs = tensor_shape
self.nrows = nrows
self.ncols = self.num_imgs // nrows
assert(self.ncols*self.nrows == self.num_imgs)
self.fig, self.a = plt.subplots(self.nrows, self.ncols, sharex='col', sharey='row')
for (row, col) in zip(range(self.nrows), range(self.ncols)):
self.a[row, col] = plt.imshow(np.zeros([self.img_height, self.img_width]))
def update(self, tensor):
n=0
for row in range(self.nrows):
for col in range(self.ncols):
self.a[row,col].set_data(tensor[:,:,n].squeeze())
n += 1
plt.show()
When I try to pass a tensor in to update, it says no set_data attribute. But using dir there is such an attribute.
In [322]: tp = tensor_plot(l10.shape, 4)
In [323]: tp.update(l10)
AttributeError: 'AxesSubplot' object has no attribute 'set_data'
In [324]: dir(tp.a[0,0])
Out[324]:
['_A',
...
'set_data',
...
'update_from',
'write_png',
'zorder']
If I add a line print(dir(self.a[row,col])) in the loop, it is true that set_data isn't there though! Same comment applies to imshow.
Any ideas?
With many thanks to #ImportanceOfBeingEarnest, here's the final code that works for me (in case it's useful to others).
class tensor_plot:
def __init__(self, tensor_shape, nrows=1):
self.img_height, self.img_width, self.num_imgs = tensor_shape
self.nrows = nrows
self.ncols = self.num_imgs // nrows
assert(self.ncols*self.nrows == self.num_imgs)
self.fig, self.a = plt.subplots(self.nrows, self.ncols, sharex='col', sharey='row')
self.imgs = np.array( [ [ self.a[row, col].imshow(np.zeros([self.img_height, self.img_width])) for col in range(self.ncols) ] for row in range(self.nrows)])
plt.pause(0.1)
def update(self, tensor):
n=0
for row in range(self.nrows):
for col in range(self.ncols):
self.imgs[row,col].set_data(tensor[:,:,n].squeeze())
self.imgs[row,col].set_clim(vmin=0, vmax=255)
n += 1
self.fig.canvas.draw_idle()
plt.pause(0.01)
plt.draw_all()
Related
I would like to update python plot once new data arrive. Below is my example using timer to simulate data change.
index = 0
class RepeatTimer(Timer):
def run(self):
while not self.finished.wait(self.interval):
self.function(*self.args, **self.kwargs)
def LoopArray():
global index
if index >= 4: index =0
ArrayList = [
[85.0805, 85.0625, 85.094, 85.043, 84.9655, 84.9725, 84.93],
[0.896385, 0.89645, 0.896315, 0.89622, 0.89579, 0.896005, 0.896535],
[1.19046, 1.191145, 1.19125, 1.19071, 1.19086, 1.19122, 1.190185],
[162.1485, 162.2275, 162.285, 162.305, 162.3965, 162.562, 162.7135],
]
Array = np.array(ArrayList[index],float)
fig = plt.figure()
ax = fig.subplots()
ax.plot(Array)
ax.set_title('Symbol' )
ax.set_ylabel('Price')
ax.legend()
ax.grid()
plt.show()
index = index +1
timer_main = RepeatTimer(3, LoopArray)
timer_main.start()
But I got only first array is displayed. Any advice or guidance on this would be greatly appreciated, Thanks.
I try to implement a matplotlib figure that updates during the simulation of my environment.
The following Classes works fine in my test but doesn't update the figure when I use it in my environment. During the simulation of the environment, the graph is shown, but no lines are plotted.
My guess is that .draw() is not working how I think it does.
Can anyone figure out the issue here?
class Visualisation:
def __init__(self, graphs):
self.graphs_dict = {}
for graph in graphs:
fig = plt.figure()
ax = fig.add_subplot(111)
line, = ax.plot(graph.x, graph.y, 'r-')
self.graphs_dict[graph.title] = {"fig": fig, "ax": ax, "line": line, "graph": graph}
self.graphs_dict[graph.title]["fig"].canvas.draw()
plt.ion()
plt.show()
def update(self, graph):
graph = self.graphs_dict[graph.title]["graph"]
self.graphs_dict[graph.title]["line"].set_xdata(graph.x)
self.graphs_dict[graph.title]["line"].set_ydata(graph.y)
self.graphs_dict[graph.title]["fig"].canvas.flush_events()
x_lim, y_lim = self.get_lim(graph)
self.graphs_dict[graph.title]["ax"].set_xlim(x_lim)
self.graphs_dict[graph.title]["ax"].set_ylim(y_lim)
self.graphs_dict[graph.title]["fig"].canvas.draw()
#staticmethod
def get_lim(graph):
if graph.x_lim is None:
x = np.array(graph.x)
y = np.array(graph.y)
x_lim = [x.min(), x.max()]
y_lim = [y.min(), y.max()]
else:
x_lim = graph.x_lim
y_lim = graph.y_lim
return x_lim, y_lim
class Graph:
def __init__(self, title, x, y, x_label="", y_label=""):
"""
Sets up a graph for Matplotlib
Parameters
----------
title : String
Title of the plot
x : float
y : float
x_label : String
x Label
y_label : String
y Label
"""
self.title = title
self.x = x
self.y = y
self.x_label = x_label
self.y_label = y_label
self.x_lim, self.y_lim = None, None
def set_lim(self, x_lim, y_lim):
self.x_lim = x_lim
self.y_lim = y_lim
class Environment:
def __init__(self, [..], verbose=0):
"""verbose : int
0 - No Visualisation
1 - Visualisation
2 - Visualisation and Logging"""
self.vis = None
self.verbose = verbose
[......]
def simulate(self):
for _ in range(self.n_steps):
[...]
self.visualize()
def visualize(self):
if self.verbose == 1 or self.verbose == 2:
if self.vis is None:
graphs = [Graph(title="VariableY", x=[], y=[])]
graphs[0].set_lim(x_lim=[0, 100], y_lim=[0, 300])
self.vis = Visualisation(graphs=graphs)
else:
self.vis.graphs_dict["VariableY"]["graph"].x.append(self.internal_step)
self.vis.graphs_dict["VariableY"]["graph"].y.append(150)
self.vis.update(self.vis.graphs_dict["VariableY"]["graph"])
When I run the code I more or less just write: env.simulate().
The code runs fine here:
class TestSingularVisualisation(unittest.TestCase):
def setUp(self):
self.graph = Graph(title="Test", x=[0], y=[0])
self.vis = Visualisation(graphs=[self.graph])
class TestSingleUpdate(TestSingularVisualisation):
def test_repeated_update(self):
for i in range(5):
self.graph.x.append(i)
self.graph.y.append(np.sin(i))
self.vis.update(self.graph)
time.sleep(1)
Turns out your code works the way it is set up. Here is the sole problem with the code you provided:
self.vis.graphs_dict["VariableY"]["graph"].x.append(self.internal_step)
self.vis.graphs_dict["VariableY"]["graph"].y.append(150)
You are plotting a line and correctly updating the canvas, however, you keep appending the exact same (x, y) coordinate. So the simulation does update the line, but the line simplifies to a point. Your test case does not do this. You can run a dummy example with your code by simply adding a line like this:
self.internal_step += 5
before adding the new x point, and you will produce a horizontal line.
Let me know if this solves your problem.
Probably not the most elegant, but I use plt.pause(0.1) when I want to update plots during execution. It pauses for 0.1s and forces all plots to be actually displayed. (It work in %debug in ipython as a bonus)
So I've created several charts using the matplotlib library in python 3.5, but I want to be able to have the flexibility to utilize a button to alternate between the views I created within a single window. I've been trying to experiment with an example here, but have not succeeded in doing so. I was curious in how to have the flexibility to click through different views that I created.
My code is sort of organized like this:
def plot1(data1, 'name1'):
...
ax.plot(x,y)
plt.draw()
def plot2(data2, 'name2'):
...
ax2.plot(x,y)
plt.draw()
def plot3(data3, 'name3'):
...
ax3.plot(x,y)
plt.draw()
plot1(data1,'name1')
plot2(data2,'name2')
plot3(data3,'name3')
plt.show()
Currently it will show up in three different windows. Now when I try to make this all into one view accessible via buttons, I'm unable to do so because quite frankly I'm unfamiliar with how to pass on the variables in my methods to create my desired subplots with the callback function. Is there a way to sort of structure my code to have them all run under one matplotlib window?
The following would be a class that uses the functions that you create. Those would not actually plot anything, but provide the required data. They should be put in a list called funcs, and when you click next or prev the corresponding graph would pop up. This should get you started.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Button
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.2)
x = range(-50,50)
y = range(-50,50)
l, = plt.plot(x, y, lw=2)
ax.title.set_text('y = x')
class Index(object):
ind = 0
global funcs # used so yu can access local list, funcs, here
def next(self, event):
self.ind += 1
i = self.ind %(len(funcs))
x,y,name = funcs[i]() # unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) # set title of graph
plt.draw()
def prev(self, event):
self.ind -= 1
i = self.ind %(len(funcs))
x,y, name = funcs[i]() #unpack tuple data
l.set_xdata(x) #set x value data
l.set_ydata(y) #set y value data
ax.title.set_text(name) #set title of graph
plt.draw()
def plot1():
x = range(-20,20)
y = x
name = "y = x"
return (x,y, name)
def plot2():
x = range(-20,20)
y = np.power(x, 2)
name = "y = x^2"
return (x,y,name)
def plot3():
x = range(-20,20) # sample data
y = np.power(x, 3)
name = "y = x^3"
return (x,y, name)
funcs = [plot1, plot2, plot3] # functions in a list so you can interate over
callback = Index()
axprev = plt.axes([0.7, 0.05, 0.1, 0.075])
axnext = plt.axes([0.81, 0.05, 0.1, 0.075])
bnext = Button(axnext, 'Next')
bnext.on_clicked(callback.next)
bprev = Button(axprev, 'Previous')
bprev.on_clicked(callback.prev)
plt.show()
What I try is to plot output of the gfs weather model with matplotlib using pygrib to save the data, which is saved in grib files. Nearly everything works fine, the output looks like this:
It appears that the program isn't closing the gap between 359.5 and 360 degress by using the data of 0 degress. If the data would be in a regular list or something I would use the data of 0° and save it for 360° too by appending the list. I've seen people having the same problem with non-pygrib data.
If you know how to change the pygrib data (regular operations don't work on pygrib data unfortunately) or how to make matplotlib close the gap, you would really help me out of this problem. Maybe the function "addcyclic" could help, but I don't know how.
EDIT: I solved the problem, see my answer.
So here is the code producing the problem:
#!/usr/bin/python3
import os, sys, datetime, string
from abc import ABCMeta, abstractmethod
import numpy as np
import numpy.ma as ma
from scipy.ndimage.filters import minimum_filter, maximum_filter
import pygrib
from netCDF4 import Dataset
from pylab import *
import matplotlib.pyplot as plt
from mpl_toolkits.basemap import Basemap, addcyclic, shiftgrid
import laplaceFilter
import mpl_util
class Plot(Basemap):
def __init__(self, basemapParams):
super().__init__(**basemapParams)
self.layers = []
def addLayer(self, layer):
self.layers.append(layer)
def plot(self, data):
for layer in self.layers:
layer.plot(self, data)
plt.title('Plot')
plt.show()
class Layer(metaclass=ABCMeta):
def __init__(self):
pass
#abstractmethod
def plot(self, plot, data):
return NotImplemented
class BackgroundLayer(Layer):
def __init__(self, bgtype, coords):
#possible bgtype values: borders, topo, both
self.bgtype = bgtype
self.lonStart = coords[0]
self.lonEnd = coords[1]
self.latStart = coords[2]
self.latEnd = coords[3]
def plot(self, plot, data):
[...]
def findSubsetIndices(self,min_lat,max_lat,min_lon,max_lon,lats,lons):
[...]
class LegendLayer(Layer):
def __init__(self):
pass
class GribDataLayer(Layer, metaclass=ABCMeta):
def __init__(self, varname, level, clevs, cmap, factor):
self.varname = varname
self.level = level
self.clevs = clevs
self.cmap = cmap
self.factor = factor
def plot(self, plot, data):
#depending on the height we want to use, we have to change the index
indexes = {1000:0, 2000:1, 3000:2, 5000:3, 7000:4, 10000:5, 15000:6, 20000:7, 25000:8, 30000:9,
35000:10, 40000:11, 45000:12, 50000:13, 55000:14, 60000:15, 65000:16, 70000:17,
75000:18, 80000:19, 85000:20, 90000:21, 92500:22, 95000:23, 97500:24, 100000:25, 0:0}
selecteddata = data.select(name = self.varname)[indexes[self.level]]
lats, lons = selecteddata.latlons()
layerdata = selecteddata.values*self.factor
x, y = plot(lons, lats) # compute map proj coordinates.
self.fillLayer(plot, x, y, layerdata, self.clevs, self.cmap)
#abstractmethod
def fillLayer(self, plot, x, y, layerdata, clevs, cmap):
return NotImplemented
class ContourLayer(GribDataLayer):
def __init__(self, varname, level, clevs, cmap, factor, linewidth=1.5, fontsize=15,
fmt="%3.1f", inline=0,labelcolor = 'k'):
self.linewidth = linewidth
self.fontsize = fontsize
self.fmt = fmt
self.inline = inline
self.labelcolor = labelcolor
super().__init__(varname, level, clevs, cmap, factor)
def fillLayer(self, plot, x, y, layerdata, clevs, cmap):
# contour data over the map.
cs = plot.contour(x,y,layerdata,clevs,colors = cmap,linewidths = self.linewidth)
plt.clabel(cs, clevs, fontsize = self.fontsize, fmt = self.fmt,
inline = self.inline, colors = self.labelcolor)
if self.varname == "Pressure reduced to MSL":
self.plotHighsLows(plot,layerdata,x,y)
def plotHighsLows(self,plot,layerdata,x,y):
[...]
class ContourFilledLayer(GribDataLayer):
def __init__(self, varname, level, clevs, cmap, factor, extend="both"):
self.extend = extend
super().__init__(varname, level, clevs, cmap, factor)
def fillLayer(self, plot, x, y, layerdata, clevs, cmap):
# contourfilled data over the map.
cs = plot.contourf(x,y,layerdata,levels=clevs,cmap=cmap,extend=self.extend)
#cbar = plot.colorbar.ColorbarBase(cs)
[...]
ger_coords = [4.,17.,46.,56.]
eu_coords = [-25.,57.,22.,70.]
### Choose Data
data = pygrib.open('gfs.t12z.mastergrb2f03')
### 500hPa Europe
coords = eu_coords
plot1 = Plot({"projection":"lcc","resolution":"h","rsphere":(6378137.00,6356752.3142), "area_thresh": 1000.,
"llcrnrlon":coords[0],"llcrnrlat":coords[2],"urcrnrlon":coords[1],"urcrnrlat":coords[3],
"lon_0":(coords[0]+coords[1])/2.,"lat_0":(coords[2]+coords[3])/2.})
clevs = range(480,600,4)
cmap = plt.cm.nipy_spectral
factor = .1
extend = "both"
level = 50000
layer1 = ContourFilledLayer('Geopotential Height', level, clevs, cmap, factor, extend)
clevs = [480.,552.,600.]
linewidth = 2.
fontsize = 14
fmt = "%d"
inline = 0
labelcolor = 'k'
layer2 = ContourLayer('Geopotential Height', level, clevs, 'k', factor, linewidth, fontsize, fmt, inline, labelcolor)
level = 0
clevs = range(800,1100,5)
factor = .01
linewidth = 1.5
inline = 0
labelcolor = 'k'
layer3 = ContourLayer('Pressure reduced to MSL', level, clevs, 'w', factor, linewidth, fontsize, fmt, inline, labelcolor)
plot1.addLayer(BackgroundLayer('borders', coords))
plot1.addLayer(layer1)
plot1.addLayer(layer2)
plot1.addLayer(layer3)
plot1.plot(data)
I solved it myself 2 months later:
Matplotlib doesn't fill the area if your longitude range is from 0 to 359.75 because it ends there from matplotlibs point of view. I solved it by dividing up the data and then stacking it.
selecteddata_all = data.select(name = "Temperature")[0]
selecteddata1, lats1, lons1 = selecteddata_all.data(lat1=20,lat2=60,lon1=335,lon2=360)
selecteddata2, lats2, lons2 = selecteddata_all.data(lat1=20,lat2=60,lon1=0,lon2=30)
lons = np.hstack((lons1,lons2))
lats = np.hstack((lats1,lats2))
selecteddata = np.hstack((selecteddata1,selecteddata2))
No white area left of 0° anymore.
I don't know whether there is a fix if you wanna plot a whole hemisphere (0 to 359.75 deg).
I've run in to this myself a few times, and the addcyclic function of the basemap module actually works pretty well. The basemap docs lay out the syntax and use pretty well.
In terms of the variables in your code you can add the cyclic point either before or after you multiply by self.factor in your GribDataLayer class:
layerdata, lons = addcyclic(layerdata, lons)
You can also use np.append and write your own function to accomplish this same task. It would look something like this:
layerdata = np.append(layerdata,layerdata[...,0,None],axis=-1)
If your input data are 2D then the syntax above is equivalent to selecting all the data in the first longitude band (i.e. layerdata[:,0])
layerdata = np.append(layerdata,layerdata[:,0,None],axis=-1)
Hope this helps!
I am trying to put the matplotlib.animation set into a class function. Though I don't seem to be having much luck. I have tried both, FunctionAnimation() & ArtistAnimation(). For both I don't seem to be able to get them to work (though they are vastly different).
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,tvector):
self.data = tvector
def ani_init(self):
self.line.set_data([], [])
def ani_update(i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line,
def animate(self):
anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init,
frames=4, interval=20, blit=True)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()
I want to try get my head around it, rather than use someone else's code. Though I did use others as a starting point. Can anyone help?
(warning: Newbie here.)
I think the best way for "anim" to stick is actually to set it as instance variable, using self.anim:
self.anim = ...
You also need to add "self" here:
def ani_update(self, i)
I use Spyder 2.1.10 and it seems to be working, although the animation is a bit fast.
You can either set blit as False, or as True but make sure you replace the line return self.line by return self.line,.
#!/usr/bin/env python3
# ------------------------------ #
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
# ------------------------------ #
class AniPlot():
def __init__(self):
self.fig = plt.figure()
self.ax = plt.axes(xlim=(-3.5, 3.5), ylim=(-5, 2))
self.line, = self.ax.plot([], [], lw=2)
def set_data(self,data):
self.data = data
def ani_init(self):
self.line.set_data([], [])
return self.line
def ani_update(self, i):
x = self.data[i][0]
y = self.data[i][1]
self.line.set_data(x, y)
return self.line
def animate(self):
self.anim = animation.FuncAnimation(self.fig, self.ani_update, init_func=self.ani_init, frames=4, interval=20, blit=False)
plt.show()
# ------------------------------ #
data = [
[[0,0,1,0],[0,-1,-2,-3]],
[[0,0,0,0.1],[0,-1,-3,-4]],
[[0,0,0.5,0],[0,-1,-2.5,-3.5]],
[[0,0,1,2],[0,-1,-2,-2.5]]
]
myani = AniPlot()
myani.set_data(data)
myani.animate()