I am trying to put multiple matplotlib subplots into a big axis, where tick labels on the big axis correspond to some parameter values for which the data in each subplot has been obtained. Here's an example,
import matplotlib.pyplot as plt
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
# x and y coordinates for the big plot
x_coords = list(set([k[0] for k in data.keys()]))
y_coords = list(set([k[1] for k in data.keys()]))
labels = ['Frogs', 'Hogs', 'Dogs']
explode = (0.05, 0.05, 0.05) #
colors = ['gold', 'beige', 'lightcoral']
fig, axes = plt.subplots(len(y_coords), len(x_coords))
for row_topToDown in range(len(y_coords)):
row = (len(y_coords)-1) - row_topToDown
for col in range(len(x_coords)):
axes[row][col].pie(data[(x_coords[col], y_coords[row_topToDown])], explode=explode, colors = colors, \
autopct=None, pctdistance = 1.4, \
shadow=True, startangle=90, radius=0.7, \
wedgeprops = {'linewidth':1, 'edgecolor':'Black'}
)
axes[row][col].axis('equal') # Equal aspect ratio ensures that pie is drawn as a circle.
axes[row][col].set_title('(' + str(x_coords[col]) + ', ' + str(y_coords[row_topToDown]) + ')')
fig.tight_layout()
plt.show()
and here's how I'd like the output to look like:
I see two options:
A. use a single axes
You may plot all pie charts to the same axes. Use the center and radius argument to scale the pies in data coordinates. This could look as follows.
import matplotlib.pyplot as plt
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
labels = ['Frogs', 'Hogs', 'Dogs']
explode = [.2]*3
colors = ['gold', 'beige', 'lightcoral']
radius = 4
margin = 2
fig, ax = plt.subplots()
for x,y in data.keys():
d = data[(x,y)]
ax.pie(d, explode=explode, colors = colors, center=(x,y),
shadow=True, startangle=90, radius=radius,
wedgeprops = {'linewidth':1, 'edgecolor':'Black'})
ax.annotate("({},{})".format(x,y), xy = (x, y+radius),
xytext = (0,5), textcoords="offset points", ha="center")
ax.set_frame_on(True)
xaxis = list(set([x for x,y in data.keys()]))
yaxis = list(set([y for x,y in data.keys()]))
ax.set(aspect="equal",
xlim=(min(xaxis)-radius-margin,max(xaxis)+radius+margin),
ylim=(min(yaxis)-radius-margin,max(yaxis)+radius+margin),
xticks=xaxis, yticks=yaxis)
fig.tight_layout()
plt.show()
B. use inset axes
You can put each pie in its own axes and position the axes in data coordinates. This is facilitated by using mpl_toolkits.axes_grid1.inset_locator.inset_axes. The main difference to the above is that you may use a non-equal aspect of the parent axes, and that it's not possible to use tight_layout.
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
data = {}
data[(10, 10)] = [0.45, 0.30, 0.25]
data[(10, 20)] = [0.2, 0.5, 0.3]
data[(20, 10)] = [0.1, 0.3, 0.6]
data[(20, 20)] = [0.6, 0.15, 0.25]
data[(30, 10)] = [0.4, 0.35, 0.25]
data[(30, 20)] = [0.5, 0.1, 0.4]
labels = ['Frogs', 'Hogs', 'Dogs']
explode = [.05]*3
colors = ['gold', 'beige', 'lightcoral']
radius = 4
margin = 2
fig, axes = plt.subplots()
for x,y in data.keys():
d = data[(x,y)]
ax = inset_axes(axes, "100%", "100%",
bbox_to_anchor=(x-radius, y-radius, radius*2, radius*2),
bbox_transform=axes.transData, loc="center")
ax.pie(d, explode=explode, colors = colors,
shadow=True, startangle=90,
wedgeprops = {'linewidth':1, 'edgecolor':'Black'})
ax.set_title("({},{})".format(x,y))
xaxis = list(set([x for x,y in data.keys()]))
yaxis = list(set([y for x,y in data.keys()]))
axes.set(aspect="equal",
xlim=(min(xaxis)-radius-margin,max(xaxis)+radius+margin),
ylim=(min(yaxis)-radius-margin,max(yaxis)+radius+margin),
xticks=xaxis, yticks=yaxis)
plt.show()
For how to put a legend outside the plot, I would refer you to How to put the legend out of the plot. And for how to create a legend for a pie chart to How to add a legend to matplotlib pie chart?
Also Python - Legend overlaps with the pie chart may be of interest.
Related
I am new to matplotlib and I am asking for your help to solve my little problem. I am sharing the graph below, here are the questions:
1- I want x-axis and y-axis replace
2- And most important for me is that errorbars should be horizontal (in graph below these are vertical).
Some errorbars in the graph is overlapping and I tried to avoid this problem using transform command. As I said before if I can manage the replacement of X and Y axis I would be happy.
Below I am sharing the code I wrote:
import ax as ax
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.transforms import Affine2D
y_values = ['a', 'b', 'c', 'd', 'e', 'f', 'g']
p1 = [1, 0.77, 0.67, 0.85, 0.78, 1.05, 0.63]
p2 = [3, 2, 1.5, 1.20, 1.10, 1.40, 1.10]
x_err = [0.1, 0.2, 0.4, 0.5, 0.3, 0.2, 0.3]
y_err = [0.6, 0.2, 0.4, 0.5, 0.3, 0.2, 0.3]
fig, ax = plt.subplots()
trans1 = Affine2D().translate(-0.1, 0.0) + ax.transData
trans2 = Affine2D().translate(+0.1, 0.0) + ax.transData
er1 = ax.errorbar(y_values, p1, x_err, marker="o", linestyle="none", transform=trans1)
er2 = ax.errorbar(y_values, p2, y_err, marker="o", linestyle="none", transform=trans2)
errorbar plot
I don't know why but I am really struggling to get widgets working well in python. I try to look at examples about how to use them but I don't know how to extrapolate that to get it to work with my code. I am trying to get a figure to display widgets such that the type, frequency, phase, and other variables adjust the graph itself.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
from scipy import signal
from matplotlib.widgets import RadioButtons
A = 1
ang_f = 5
t = np.linspace(0, 4*np.pi, 1000)
phase = 0
s0 = A*np.sin(ang_f*t + phase)
s2 = A*signal.sawtooth(ang_f*t + phase)
s1 = A*signal.square(ang_f*t + phase)
fig, ax = plt.subplots()
l, = ax.plot(t, s1, lw=2, color='red')
plt.subplots_adjust(left=0.4)
def sinf(x, omega):
return np.sin(omega*x)
def sliderCallback(val):
# """ 'val' is the current value selected by the slider
# Recalculate sine values with val as the frequency """
axesHandle.set_ydata(sinf(x, val))
plt.draw() # Redraw the axes
def clickcallback(val):
# 'val' is the current value selected by the slider
# Recalculate sine values with val as the frequency
axesHandle.set_ydata(sinf(x, val))
plt.draw() # Redraw the axes
def closeCallback(event):
plt.close('all') # Close all open figure windows
fig = plt.figure(figsize=(7, 5))
ax = plt.axes([0.1, 0.2, 0.6, 0.7])
axesHandle, = plt.plot(x, sinf(x, 1), lw=2, color='red')
# Add axis to contain the slider
fax = plt.axes([0.1, 0.04, 0.35, 0.03]) # Frequency
tax = plt.axes([0.1, 0.12, 0.35, 0.03]) # Time
sax_3 = plt.axes([0.60, 0.1, 0.35, 0.03]) # Number of points
pax = plt.axes([0.60, 0.05, 0.35, 0.03]) # Phase
rax = plt.axes([0.85, 0.65, 0.12, 0.15]) # Type
bax = plt.axes([0.85, 0.85, 0.1, 0.1]) # Close
pointshandle = widgets.Slider(sax_3, 'Number of points', 1, 200,
valfmt='%0.0f')
pointshandle.on_changed(sliderCallback)
graphchoice = widgets.RadioButtons(rax, ('Sine', 'Squarewave', 'Sawtooth'))
graphchoice.on_clicked(clickcallback)
freqhandle = widgets.Slider(fax, 'Frequancy (Hz)', 0, 5, valinit=1)
freqhandle.on_changed(sliderCallback)
phasehandle = widgets.Slider(pax, 'Phase', 0, 0*np.pi, valinit=0)
phasehandle.on_changed(sliderCallback)
timehandle = widgets.Slider(tax, 'Time (s)', 1, 10, valinit=1)
timehandle.on_changed(sliderCallback)
buttonHandle = widgets.Button(bax, 'Close')
buttonHandle.on_clicked(closeCallback)
def hzfunc(label):
hzdict = {'Sine': s0, 'Squarewave': s1, 'Sawtooth': s2}
ydata = hzdict[label]
l.set_ydata(ydata)
plt.draw()
graphchoice.on_clicked(hzfunc)
I'm really lost so any tips to put me on the right path would be much appreciated, im just so confused atm.
I'm trying to create a scatter plot with x and y errors that have different marker and errorbar colors in four sections (e.g. red for x=0 to x=2, blue for x=2 to c=5, etc.). I have used a colormap with bounds for the markers, but I haven't been able to do something similar for the errorbars. I've tried to set the markers, errorbars, and caps as the same color in the scatter colormap using this answer to a similar question, but I wasn't able to get it to work for my code (comes up with an error about lengths of data not matching or unable to convert to tuple). I think I haven't been able to correctly modify it for the colormap I use for the markers, or this isn't the best way to go about getting the right result.
This is an example with some made up data:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
bounds = [0,1.5,3,4.5,5]
colors = ["r", "b", "g", "y"]
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(bounds, len(colors))
x = np.array([0.0, 0.0, 1.0, 2.0, 2.0, 3.0, 4.0, 4.0, 5.0, 5.0])
y = np.array([0.0, 0.1, 0.8, 0.9, 0.7, 0.1, -0.8, -0.5, -1.0, -0.7])
x_err = np.array([0.05, 0.06, 0.04, 0.045, 0.04, 0.06, 0.05, 0.055, 0.02, 0.05])
y_err = np.array([0.04, 0.05, 0.03, 0.055, 0.145, 0.065, 0.045, 0.15, 0.015, 0.17])
plt.scatter(x, y, marker='D', c=x, cmap=cmap, norm=norm)
plt.errorbar(x, y, xerr=x_err, yerr=y_err, fmt='.', lw=2, capsize=3, alpha=0.7, zorder=0)
plt.show()
which gives
.
How can I get the errorbars to have the same colormap as the one used in the scatter plot?
This is certainly not the fastest method but it works: get the colors for each x-value using to_rgba and then plot the error bars pointwise (probably slow for large data arrays):
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors
import matplotlib.cm
bounds = [0,1.5,3,4.5,5]
colors = ["r", "b", "g", "y"]
cmap = matplotlib.colors.ListedColormap(colors)
norm = matplotlib.colors.BoundaryNorm(bounds, len(colors))
x = np.array([0.0, 0.0, 1.0, 2.0, 2.0, 3.0, 4.0, 4.0, 5.0, 5.0])
y = np.array([0.0, 0.1, 0.8, 0.9, 0.7, 0.1, -0.8, -0.5, -1.0, -0.7])
x_err = np.array([0.05, 0.06, 0.04, 0.045, 0.04, 0.06, 0.05, 0.055, 0.02, 0.05])
y_err = np.array([0.04, 0.05, 0.03, 0.055, 0.145, 0.065, 0.045, 0.15, 0.015, 0.17])
plt.scatter(x, y, marker='D', c=x, cmap=cmap, norm=norm)
colors = matplotlib.cm.ScalarMappable(norm,cmap).to_rgba(x)
for i,_ in enumerate(x):
plt.errorbar(x[i], y[i], xerr=x_err[i], yerr=y_err[i], fmt='.', lw=2, capsize=3, alpha=0.7, zorder=0, ecolor=colors[i])
plt.show()
I was using this code to create an interactive plot (2d), and it works.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.widgets import Slider, Button, RadioButtons
fig, ax = plt.subplots()
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axcolor = 'lightgoldenrodyellow'
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg=axcolor)
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg=axcolor)
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
l.set_ydata(amp*np.sin(2*np.pi*freq*t))
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()
I then tried to modify it to create an interactive 3d plot by simply changing the axes to axes3d. I added the import statement shown below and replaced the definition of "fig" and "ax" with those shown below to become 3d.
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
The plot no longer updates, and I can't figure out why. It seems that the function fig.canvas.draw_idle() doesn't work on 3d graphs, but I don't have another way of updating the graph.
Any help would be appreciated,
Thanks!
You can see that the function fig.canvas.draw_idle() does exist and works as expected by looking at the colors which become updated.
The problem lies in the set_ydata function, which works differently in 3d space.
Assuming that you want the y coordinate to update and the z-coordinate to be constant, set_data will be given the constant values, while an additional property set_3d_properties() needs to be set to control the y-coordinate.
Here is the working example code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.widgets import Slider, Button, RadioButtons
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
plt.subplots_adjust(left=0.25, bottom=0.25)
t = np.arange(0.0, 1.0, 0.001)
### create constant z-coordinate
z = np.zeros_like(t) # <------------ here
a0 = 5
f0 = 3
s = a0*np.sin(2*np.pi*f0*t)
l, = plt.plot(t, s, lw=2, color='red')
plt.axis([0, 1, -10, 10])
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], axisbg="w")
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], axisbg="w")
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0)
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
def update(val):
amp = samp.val
freq = sfreq.val
#set constant z coordinate
l.set_data(t, z) # <------------ here
# set values to y-coordinate
l.set_3d_properties(amp*np.sin(2*np.pi*freq*t), zdir="y") #<------------ here
fig.canvas.draw_idle()
sfreq.on_changed(update)
samp.on_changed(update)
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
def reset(event):
sfreq.reset()
samp.reset()
button.on_clicked(reset)
rax = plt.axes([0.025, 0.5, 0.15, 0.15], axisbg=axcolor)
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
def colorfunc(label):
l.set_color(label)
fig.canvas.draw_idle()
radio.on_clicked(colorfunc)
plt.show()
I have the following code:
import matplotlib.pyplot as plt
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
plt.clf()
plt.pcolor(X, Y, v, cmap=cm)
plt.loglog()
plt.xlabel('X Axis')
plt.ylabel('Y Axis')
plt.colorbar()
plt.show()
So this produces a graph of the values 'v' on the axes X vs Y, using the specified colormap. The X and Y axes are perfect, but the colormap spreads between the min and max of v. I would like to force the colormap to range between 0 and 1.
I thought of using:
plt.axis(...)
To set the ranges of the axes, but this only takes arguments for the min and max of X and Y, not the colormap.
Edit:
For clarity, let's say I have one graph whose values range (0 ... 0.3), and another graph whose values (0.2 ... 0.8).
In both graphs, I will want the range of the colorbar to be (0 ... 1). In both graphs, I want this range of colour to be identical using the full range of cdict above (so 0.25 in both graphs will be the same colour). In the first graph, all colours between 0.3 and 1.0 won't feature in the graph, but will in the colourbar key at the side. In the other, all colours between 0 and 0.2, and between 0.8 and 1 will not feature in the graph, but will in the colourbar at the side.
Using vmin and vmax forces the range for the colors. Here's an example:
import matplotlib as m
import matplotlib.pyplot as plt
import numpy as np
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
x = np.arange(0, 10, .1)
y = np.arange(0, 10, .1)
X, Y = np.meshgrid(x,y)
data = 2*( np.sin(X) + np.sin(3*Y) )
def do_plot(n, f, title):
#plt.clf()
plt.subplot(1, 3, n)
plt.pcolor(X, Y, f(data), cmap=cm, vmin=-4, vmax=4)
plt.title(title)
plt.colorbar()
plt.figure()
do_plot(1, lambda x:x, "all")
do_plot(2, lambda x:np.clip(x, -4, 0), "<0")
do_plot(3, lambda x:np.clip(x, 0, 4), ">0")
plt.show()
Use the CLIM function (equivalent to CAXIS function in MATLAB):
plt.pcolor(X, Y, v, cmap=cm)
plt.clim(-4,4) # identical to caxis([-4,4]) in MATLAB
plt.show()
Not sure if this is the most elegant solution (this is what I used), but you could scale your data to the range between 0 to 1 and then modify the colorbar:
import matplotlib as mpl
...
ax, _ = mpl.colorbar.make_axes(plt.gca(), shrink=0.5)
cbar = mpl.colorbar.ColorbarBase(ax, cmap=cm,
norm=mpl.colors.Normalize(vmin=-0.5, vmax=1.5))
cbar.set_clim(-2.0, 2.0)
With the two different limits you can control the range and legend of the colorbar. In this example only the range between -0.5 to 1.5 is show in the bar, while the colormap covers -2 to 2 (so this could be your data range, which you record before the scaling).
So instead of scaling the colormap you scale your data and fit the colorbar to that.
Using figure environment and .set_clim()
Could be easier and safer this alternative if you have multiple plots:
import matplotlib as m
import matplotlib.pyplot as plt
import numpy as np
cdict = {
'red' : ( (0.0, 0.25, .25), (0.02, .59, .59), (1., 1., 1.)),
'green': ( (0.0, 0.0, 0.0), (0.02, .45, .45), (1., .97, .97)),
'blue' : ( (0.0, 1.0, 1.0), (0.02, .75, .75), (1., 0.45, 0.45))
}
cm = m.colors.LinearSegmentedColormap('my_colormap', cdict, 1024)
x = np.arange(0, 10, .1)
y = np.arange(0, 10, .1)
X, Y = np.meshgrid(x,y)
data = 2*( np.sin(X) + np.sin(3*Y) )
data1 = np.clip(data,0,6)
data2 = np.clip(data,-6,0)
vmin = np.min(np.array([data,data1,data2]))
vmax = np.max(np.array([data,data1,data2]))
fig = plt.figure()
ax = fig.add_subplot(131)
mesh = ax.pcolormesh(data, cmap = cm)
mesh.set_clim(vmin,vmax)
ax1 = fig.add_subplot(132)
mesh1 = ax1.pcolormesh(data1, cmap = cm)
mesh1.set_clim(vmin,vmax)
ax2 = fig.add_subplot(133)
mesh2 = ax2.pcolormesh(data2, cmap = cm)
mesh2.set_clim(vmin,vmax)
# Visualizing colorbar part -start
fig.colorbar(mesh,ax=ax)
fig.colorbar(mesh1,ax=ax1)
fig.colorbar(mesh2,ax=ax2)
fig.tight_layout()
# Visualizing colorbar part -end
plt.show()
A single colorbar
The best alternative is then to use a single color bar for the entire plot. There are different ways to do that, this tutorial is very useful for understanding the best option. I prefer this solution that you can simply copy and paste instead of the previous visualizing colorbar part of the code.
fig.subplots_adjust(bottom=0.1, top=0.9, left=0.1, right=0.8,
wspace=0.4, hspace=0.1)
cb_ax = fig.add_axes([0.83, 0.1, 0.02, 0.8])
cbar = fig.colorbar(mesh, cax=cb_ax)
P.S.
I would suggest using pcolormesh instead of pcolor because it is faster (more infos here ).