I'm just starting out experimenting with Matplotlib today. I've spent the last few hours trying to fix the positioning of the title and axis labels to no avail. I figured out to fix the spacing between the title and top of the chart and the axis labels and ticks using the padding parameter. I can't figure out how to make it so that the title is not crammed to top of the figure and the x/y axis labels aren't crammed to the left/bottom of the figure.
Below is an example that has nothing to do with my actual problem other than illustrating the formatting issue.
import matplotlib.pyplot as plt
import numpy as np
#create data
A = 5
f = 0.5
t = np.arange(0,10,0.01)
y = A * np.sin(2*np.pi*f*t)
#create plot
fig, ax = plt.subplots()
fig.set_size_inches(8*(16/9),8)
ax.plot(t,y)
#format plot
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
ax.set_title('Need this title to move down without moving into subplot so that it is not crammed on top',pad=20)
ax.set_ylabel('Need this label to move to the right',labelpad=20)
ax.set_xlabel('Need this label to move up',labelpad=20)
Any suggestions as to how to increase the margins between the outside of the title/labels and the edge of the figure would be greatly appreciated.
You can try something like that:
import matplotlib.pyplot as plt
import numpy as np
#create data
A = 5
f = 0.5
t = np.arange(0, 10, 0.01)
y = A * np.sin(2 * np.pi * f * t)
#create plot
fig, ax = plt.subplots()
fig.set_size_inches(8 * (16 / 9), 8)
ax.plot(t, y)
#format plot
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
ax.set_title("Title", y=-0.1)
ax.set_xlabel("x-label")
ax.xaxis.set_label_position("top")
ax.set_ylabel("y-label")
ax.yaxis.set_label_position("right")
If you want to move x/y-ticks on top/to the right as well, then use the following commands:
ax.xaxis.tick_top()
ax.yaxis.tick_right()
and then modify:
ax.spines.top.set_visible(False)
ax.spines.right.set_visible(False)
to
ax.spines.bottom.set_visible(False)
ax.spines.left.set_visible(False)
The code below is supposed to update the graph (change the colors of the bar) depending on the ydata of on_click event. Some how, the colors do not chnage as supposed. Also, I'm using 'ax.clear()' to refresh the redraw the bars and the line every time the graph is clicked. Any idea what's wrong with this code?
import numpy as np
import matplotlib.pyplot as plt
import matplotlib
from matplotlib import cm
import pandas as pd
# Use the following data for this assignment:
np.random.seed(12345)
df = pd.DataFrame([np.random.normal(32000,200000,3650),
np.random.normal(43000,100000,3650),
np.random.normal(43500,140000,3650),
np.random.normal(48000,70000,3650)],
index=[1992,1993,1994,1995])
fig, ax = plt.subplots()
#Plotting the Bar chart
mean = df.mean(axis = 1)
std = df.std(axis = 1)
n= df.shape[1]
yerr = 1.96*std/np.sqrt(3650)
plt.bar(range(df.shape[0]), mean, yerr = yerr, color = 'grey',capsize=10, alpha = 0.5)
plt.xticks(range(len(df.index)), df.index)
plt.title('Proportion of confidence interval lying below the threshold value')
plt.ylabel('Number of votes')
#Click on the graph to choose a value, the color of the bar change based on the yvalue
colourofbars = []
norm = None
cmap = plt.cm.get_cmap('RdYlBu')
dict = {mean[x]: yerr[x] for x in list(df.index)}
def onclick(event):
val = event.ydata
global colourofbars
global norm
#Defining the condition based on the ydata
for key,value in dict.items():
if val > (key+(value)):
colour = 0
colourofbars.append(colour)
elif val < (key-(value)):
colour = 1
colourofbars.append(colour)
elif ((key+(value))> val > (key-(value))):
colour = ((key+(value))-val)/((key+value)-(key-value))
colourofbars.append(colour)
ax.clear()
norm = matplotlib.colors.Normalize(vmin=min(colourofbars),vmax=max(colourofbars), clip=False)
#Plotting the colored bar chart
plt.bar(range(df.shape[0]), mean, yerr = yerr, capsize=10, alpha = 0.5, color=cmap(norm(colourofbars)))
plt.axhline(y=val,linewidth=1, color='k')
plt.gcf().canvas.draw_idle()
#Adding the colorbar legend
scalarmappaple = cm.ScalarMappable(norm=norm, cmap=cmap)
scalarmappaple.set_array(colourofbars)
plt.colorbar(scalarmappaple)
plt.gcf().canvas.mpl_connect('button_press_event', onclick)
fig.canvas.draw()
In Jupyter Notebook you have to add
%matplotlib notebook
in order to make the plot interactive (adding that line after import statements is fine).
With the above statement I get this plot:
If I click somewhere in the plot I get:
I have a 4D data set (for those who care, its an astronomical Position-Position-Temperature-Opacity image) in a numpy array, that I need to plot in an interactive way. While there are programs to do this, none of them can handle the unusual form that my data steps in (but I can worry about that, thats not part of the question).
I know how to get it plotting with one Slider, but really I need to plot the image with 2 Sliders, one for each of temperature and opacity.
My MWE of a 3D array code is below:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
array = np.random.rand(300,300,10)
axis = 2
s = [slice(0, 1) if i == axis else slice(None) for i in xrange(array.ndim)]
im = array[s].squeeze()
fig = plt.figure()
ax = plt.subplot(111)
l = ax.imshow(im, origin = 'lower')
axcolor = 'lightgoldenrodyellow'
ax = fig.add_axes([0.2, 0.95, 0.65, 0.03], axisbg=axcolor)
slider = Slider(ax, 'Temperature', 0, array.shape[axis] - 1,
valinit=0, valfmt='%i')
def update(val):
ind = int(slider.val)
s = [slice(ind, ind + 1) if i == axis else slice(None)
for i in xrange(array.ndim)]
im = array[s].squeeze()
l.set_data(im)
fig.canvas.draw()
slider.on_changed(update)
plt.show()
Any way to do it with 2 sliders?
EDIT: The problem I am having is I dont know how to expand to 2 sliders. Particularly how to adapt the line
s = [slice(0, 1) if i == axis else slice(None) for i in xrange(array.ndim)]
and how to modify the update function when I go from np.random.rand(300,300,10) to np.random.rand(300,300,10,10). I supposed I will need to declare both a T_axis = 2 and B_axis = 3 rather than simply an axis = 2, but beyond that, I am rather stuck as to how to modify it.
As I interprete the data structure, you have an array of shape (300,300,n,m), where n is the number of temperatures and m is the number of opacities. The image to show for the ith temperature and the jth opacity is hence, array[:,:,i,j].
You now need of course two different silders where one determines the value of i and the other of j.
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
import numpy as np
array = np.random.rand(300,300,10,9)
# assuming you have for each i=Temperature index and j =Opacity index
# an image array(:,:,i,j)
fig, ax = plt.subplots()
l = ax.imshow(array[:,:,0,0], origin = 'lower')
axT = fig.add_axes([0.2, 0.95, 0.65, 0.03])
axO = fig.add_axes([0.2, 0.90, 0.65, 0.03])
sliderT = Slider(axT, 'Temperature', 0, array.shape[2]-1, valinit=0, valfmt='%i')
sliderO = Slider(axO, 'Opacity', 0, array.shape[3]-1, valinit=0, valfmt='%i')
def update(val):
i = int(sliderT.val)
j = int(sliderO.val)
im = array[:,:,i,j]
l.set_data(im)
fig.canvas.draw_idle()
sliderT.on_changed(update)
sliderO.on_changed(update)
plt.show()
I want to 'simulate' the location of two objects in a plot. I wanted to do this with axvspan. I want to move one axvspan with a slider. I want to do something like I this:
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
t = 2
g0 = 10
filter_loc, = plt.axvspan((50-g0-t), (50-g0), color = "blue")
sensor_loc = plt.axvspan(50,80, color="red")
plt.axis([0, 1, -10, 10])
axgap = plt.axes([0.25, 0.1, 0.65, 0.03])
sgap = Slider(axgap, 'Gap', 0.01, 30.0, valinit=g0)
def update(val):
gap = sgap.val
filter_loc.set_xdata(50-gap-t,50-gap)
fig.canvas.draw_idle()
sgap.on_changed(update)
plt.show()
When try it in several ways I always get the error:
filter_loc, = plt.axvspan((50-g0-t), (50-g0))
TypeError: 'Polygon' object is not iterable
So it seems to me that the axvspan does not like the updating of the values by using the slider. Are there ways I can still achieve this by using axvspan?
You've an extra comma right after filter_loc.
Besides that, filter_loc doesn't have a set_xdata attribute. You may modify it by calling set_xy(), for example something like this will do:
def set_xvalues(polygon, x0, x1):
_ndarray = polygon.get_xy()
_ndarray[:, 0] = [x0, x0, x1, x1, x0]
polygon.set_xy(_ndarray)
def update(val):
gap = sgap.val
set_xvalues(filter_loc, 50-gap-t, 50-gap)
fig.canvas.draw_idle()
BTW the current plt.axis() values are preventing the blue square from being shown.
I'm trying to create a scrollable multiplot based on the answer to this question:
Creating a scrollable multiplot with python's pylab
Lines created using ax.plot() are updating correctly, however I'm unable to figure out how to update artists created using xvlines() and fill_between().
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider
#create dataframes
dfs={}
for x in range(100):
col1=np.random.normal(10,0.5,30)
col2=(np.repeat([5,8,7],np.round(np.random.dirichlet(np.ones(3),size=1)*31)[0].tolist()))[:30]
col3=np.random.randint(4,size=30)
dfs[x]=pd.DataFrame({'col1':col1,'col2':col2,'col3':col3})
#create figure,axis,subplot
fig = plt.figure()
gs = gridspec.GridSpec(1,1,hspace=0,wspace=0,left=0.1,bottom=0.1)
ax = plt.subplot(gs[0])
ax.set_ylim([0,12])
#slider
frame=0
axframe = plt.axes([0.13, 0.02, 0.75, 0.03])
sframe = Slider(axframe, 'frame', 0, 99, valinit=0,valfmt='%d')
#plots
ln1,=ax.plot(dfs[0].index,dfs[0]['col1'])
ln2,=ax.plot(dfs[0].index,dfs[0]['col2'],c='black')
#artists
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==8,facecolor='b',edgecolors='none',alpha=0.5)
ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==7,facecolor='g',edgecolors='none',alpha=0.5)
ax.vlines(x=dfs[0]['col3'].index,ymin=0,ymax=dfs[0]['col3'],color='black')
#update plots
def update(val):
frame = np.floor(sframe.val)
ln1.set_ydata(dfs[frame]['col1'])
ln2.set_ydata(dfs[frame]['col2'])
ax.set_title('Frame ' + str(int(frame)))
plt.draw()
#connect callback to slider
sframe.on_changed(update)
plt.show()
This is what it looks like at the moment
I can't apply the same approach as for plot(), since the following produces an error message:
ln3,=ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
TypeError: 'PolyCollection' object is not iterable
This is what it's meant to look like on each frame
fill_between returns a PolyCollection, which expects a list (or several lists) of vertices upon creation. Unfortunately I haven't found a way to retrieve the vertices that where used to create the given PolyCollection, but in your case it is easy enough to create the PolyCollection directly (thereby avoiding the use of fill_between) and then update its vertices upon frame change.
Below a version of your code that does what you are after:
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
from matplotlib.widgets import Slider
from matplotlib.collections import PolyCollection
#create dataframes
dfs={}
for x in range(100):
col1=np.random.normal(10,0.5,30)
col2=(np.repeat([5,8,7],np.round(np.random.dirichlet(np.ones(3),size=1)*31)[0].tolist()))[:30]
col3=np.random.randint(4,size=30)
dfs[x]=pd.DataFrame({'col1':col1,'col2':col2,'col3':col3})
#create figure,axis,subplot
fig = plt.figure()
gs = gridspec.GridSpec(1,1,hspace=0,wspace=0,left=0.1,bottom=0.1)
ax = plt.subplot(gs[0])
ax.set_ylim([0,12])
#slider
frame=0
axframe = plt.axes([0.13, 0.02, 0.75, 0.03])
sframe = Slider(axframe, 'frame', 0, 99, valinit=0,valfmt='%d')
#plots
ln1,=ax.plot(dfs[0].index,dfs[0]['col1'])
ln2,=ax.plot(dfs[0].index,dfs[0]['col2'],c='black')
##additional code to update the PolyCollections
val_r = 5
val_b = 8
val_g = 7
def update_collection(collection, value, frame = 0):
xs = np.array(dfs[frame].index)
ys = np.array(dfs[frame]['col2'])
##we need to catch the case where no points with y == value exist:
try:
minx = np.min(xs[ys == value])
maxx = np.max(xs[ys == value])
miny = value-0.5
maxy = value+0.5
verts = np.array([[minx,miny],[maxx,miny],[maxx,maxy],[minx,maxy]])
except ValueError:
verts = np.zeros((0,2))
finally:
collection.set_verts([verts])
#artists
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==5,facecolor='r',edgecolors='none',alpha=0.5)
reds = PolyCollection([],facecolors = ['r'], alpha = 0.5)
ax.add_collection(reds)
update_collection(reds,val_r)
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==8,facecolor='b',edgecolors='none',alpha=0.5)
blues = PolyCollection([],facecolors = ['b'], alpha = 0.5)
ax.add_collection(blues)
update_collection(blues, val_b)
##ax.fill_between(dfs[0].index,y1=dfs[0]['col2']-0.5,y2=dfs[0]['col2']+0.5,where=dfs[0]['col2']==7,facecolor='g',edgecolors='none',alpha=0.5)
greens = PolyCollection([],facecolors = ['g'], alpha = 0.5)
ax.add_collection(greens)
update_collection(greens, val_g)
ax.vlines(x=dfs[0]['col3'].index,ymin=0,ymax=dfs[0]['col3'],color='black')
#update plots
def update(val):
frame = np.floor(sframe.val)
ln1.set_ydata(dfs[frame]['col1'])
ln2.set_ydata(dfs[frame]['col2'])
ax.set_title('Frame ' + str(int(frame)))
##updating the PolyCollections:
update_collection(reds,val_r, frame)
update_collection(blues,val_b, frame)
update_collection(greens,val_g, frame)
plt.draw()
#connect callback to slider
sframe.on_changed(update)
plt.show()
Each of the three PolyCollections (reds, blues, and greens) has only four vertices (the edges of the rectangles), which are determined based on the given data (which is done in update_collections). The result looks like this:
Tested in Python 3.5
Your error
TypeError: 'PolyCollection' object is not iterable
can be avoided by removing the comma after l3:
l3 = ax.fill_between(xx, y1, y2, **kwargs)
The return value is a PolyCollection, you need to update its vertices during the update() function. An alternative to the other answer posted here is to make fill_between() give you a new PolyCollection, and then get its vertices and use them to update those of l3:
def update(val):
dummy_l3 = ax.fill_between(xx, y1, y2, **kwargs)
verts = [ path._vertices for path in dummy_l3.get_paths() ]
codes = [ path._codes for path in dummy_l3.get_paths() ]
dummy_l3.remove()
l3.set_verts_and_codes(verts, codes)
plt.draw()
The above code does not run for me; however, to refresh fill_between the following works for me
%matplotlib inline
import numpy as np
from IPython import display
import matplotlib.pyplot as plt
import time
hdisplay = display.display("", display_id=True)
fig = plt.figure()
ax = fig.add_subplot(1,1,1)
x = np.linspace(0,1,100)
ax.set_title("Test")
ax.set_xlim()
y = np.random.random(size=(100))
dy = 0.1
l = ax.plot(x,y,":",color="red")
b = ax.fill_between( x, y-dy, y+dy, color="red", alpha=0.2 )
hdisplay.update(fig)
for i in range(5):
time.sleep(1)
ax.set_title("Test %ld" % i)
y = np.random.random(size=(100))
l[0].set_ydata( y )
b.remove()
b = ax.fill_between( x, y-dy, y+dy, color="red", alpha=0.2 )
plt.draw()
hdisplay.update(fig)
plt.close(fig)