Related
The problem is the y_h = 1 is not aligned properly with the y = 0.3 and so on, how do i fix this? Below i left a picture of the problem im facing and the code.
yaxis's not aligned
x = 2
y = np.array([0.3, 0.11, 0.43])
y_h = np.array([1, 2, 3])
fig, host = plt.subplots(figsize=(8, 5))
fig.subplots_adjust(right=0.75)
colormap = plt.get_cmap("gist_rainbow")
colors = [colormap(i) for i in np.linspace(0, 1, y.size)]
line = host.twinx()
#plot the lines the give them colors and labels
for i in range(y.size):
p = line.plot([x - x, x], [y_h[i], y[i]], color=colors[i], label="p"+str(i))
#append the line labels to a list
'''for i in range(y.size):
pis.append('p'+str(i))'''
host.set_xlim(0, x)
host.set_ylim(0, y_h.size)
line.set_ylim(0, max(y))
host.set_xlabel("Rev_Count")
host.set_ylabel("Value")
line.set_ylabel('Value_Header')
plt.show()
I am using the following code to to generate this heat map:
dim = np.arange(1, 32, 1)
fig, ax = plt.subplots(figsize=(7,9))
heatmap = ax.imshow(h, aspect=1, cmap=plt.cm.get_cmap('Blues', 5), clim=[0,100])
ax.set_ylabel("Days", fontsize=15)
ax.set_xlabel("Months", fontsize=15)
ax.set_title("Percentage of records per day", fontsize=18)
ax.set_yticks(range(0,31))
ax.set_yticklabels(dim, ha='center', minor=False)
ax.set_xticks(range(0,13,1))
ax.set_xticklabels(ylabel[7:],rotation=45, ha='right')
ax.grid(which = 'minor', color = 'w')
ax.set_facecolor('gray')
ax.xaxis.set_minor_locator(MultipleLocator(.5))
ax.yaxis.set_minor_locator(MultipleLocator(.5))
cbaxes = fig.add_axes([.8, .35, .04, .3])
cbar = fig.colorbar(heatmap, ticks = [0, 20, 40, 60, 80 ,100], label = 'Percentage', cax = cbaxes)
fig.show()
I would like to highlight all of the cells with a value greater or equal to 60.
I tried adding this to my code:
highlight = (h> 60)
highlight = np.ma.masked_less(highlight, 1)
ax.pcolormesh(highlight, facecolor = 'None')
and got this:
I am almost there but the cells and the mesh are misaligned. How could I fix this?
The cells in a heatmap are centered on integers, this means for example that the cell with index 0,0 is in fact -0.5 to 0.5 on both axes. You have to subtract 0.5 to the coordinates of your highlights.
Thanks to mozway's comment I was able to fix my problem. I changed the beginning of my code to:
highlight = (h> 60)
highlight = np.ma.masked_less(highlight, 1)
x = np.arange(-0.5,12,1) # len = 10
y = np.arange(-0.5,30,1) # len = 6
X, Y = np.meshgrid(x, y)
and change the line plotting the color mesh to:
ax.pcolormesh(x,y,highlight, facecolor = 'None', edgecolors = 'w',shading='auto', zorder=2)
I also had to set the z-order of the color mesh to be greater than the grid lines (zorder=2 and zorder=1 respectively).
I am drawing different circles with Matplotlib. Each circle has a label, and each label has a colour. What can I do to have a colourmap legend for these different labels?
I have tried a lot of solutions online, including the most naive one by just adding plt.colorbar(), which I will get the error
RuntimeError('No mappable was found to use for colorbar')
Here is my complete code. It's a little bit long. Please note that the key part just starts from if labels is None:. I just include everything for completeness.
def plot_gaussian_circles(loc_list, scale_list, save_path=None, sigma_coe=3, num_to_plot=300, labels=None):
mu_x_max = -float('inf')
mu_y_max = -float('inf')
mu_x_min = float('inf')
mu_y_min = float('inf')
color_idx = 0
rvs = []
lim_loc_list = loc_list[:num_to_plot]
lim_scale_list = scale_list[:num_to_plot]
for a_mu_, a_sigma_ in zip(lim_loc_list, lim_scale_list):
a_mu = a_mu_.squeeze()
a_sigma_ = a_sigma_.squeeze()
if not type(a_sigma_) is np.ndarray:
a_sigma_ = a_sigma_.numpy()
radius = sigma_coe * np.max(a_sigma_)
a_mu_x = a_mu[0]
a_mu_y = a_mu[1]
if (a_mu_x + radius) >= mu_x_max:
mu_x_max = a_mu_x + radius
if (a_mu_x - radius) <= mu_x_min:
mu_x_min = a_mu_x - radius
if (a_mu_y + radius) >= mu_y_max:
mu_y_max = a_mu_y + radius
if (a_mu_y - radius) <= mu_y_min:
mu_y_min = a_mu_y - radius
if labels is None:
rv = plt.Circle(a_mu, radius, fill=False, clip_on=False)
else:
colors = cm.rainbow(np.linspace(0, 1, len(set(labels))))
rv = plt.Circle(a_mu, radius, color=colors[labels[color_idx]], fill=False, clip_on=False)
rvs.append(rv)
color_idx = (color_idx + 1)
fig, ax = plt.subplots()
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
axes = plt.gca()
axes.set_xlim([mu_x_min - 1, mu_x_max + 1])
axes.set_ylim([mu_y_min - 1, mu_y_max + 1])
for rv in rvs:
ax.add_artist(rv)
if not(labels is None):
# plt.legend(colors, list(range(len(set(labels)))))
plt.colorbar()
if save_path is None:
plt.plot()
plt.show()
# plt.savefig('plotcircles_test.png')
else:
plt.savefig(save_path, dpi=200)
The image here is currently what I am getting, while I wish to have a legend of the colormap.
I found doing this will have the colour for the circles. Thanks ImportanceOfBeingErnest's comment for suggesting PatchCollection.
p = PatchCollection(rvs, cmap=cm.jet, alpha=0.4)
p.set_array(labels)
ax.add_collection(p)
fig.colorbar(p, ax=ax)
I want to plot curves with different y-axis that share the same x-axis. I have used the twinx function before, but it plot them on different side of the figure. Is there a way to plot both of them on the left hand side. I am looking for something like the following
but with both the axis on the same side. The code for the above example is here.
On a different not, can one plot the curves in some particular order, as z-order do not work for twinx
Whats shown in red is the default twinx() behavior. The extra modification in the example applies to whats shown in green.
You can modify both new axes similar as the green one, but select the left spine and apply a negative offset. So add/change the example with:
par1.spines["left"].set_position(("axes", -0.4)) # red one
par2.spines["left"].set_position(("axes", -0.2)) # green one
make_patch_spines_invisible(par1)
make_patch_spines_invisible(par2)
par1.spines["left"].set_visible(True)
par1.yaxis.set_label_position('left')
par1.yaxis.set_ticks_position('left')
par2.spines["left"].set_visible(True)
par2.yaxis.set_label_position('left')
par2.yaxis.set_ticks_position('left')
The zorder from lines is only taken into account within the axes (or so it appears?), since you have separate axes on top of each other, you should modify the zorder of the axes:
host.set_zorder(1)
par1.set_zorder(2)
par2.set_zorder(3)
Note that the host has a white background, placing it on top will hide the other lines unless you set the background to be transparent.
Here a function to make it automatically for any of the sides in case someone need it.
import matplotlib.pyplot as plt
import numpy as np
def plotting_several_axis(variables, positions, colors, ylabels, xlabel, yaxislabels,
fontsize=12, y_axis_dist = 0.2, figsize=(7,5)):
"""
plotting_several_axis(variables, positions, colors, ylabels, xlabel, yaxislabels,
fontsize=12, y_axis_dist = 0.2, figsize=(7,5))
Example:
a1 = np.arange(1, 100, 1)
a2 = np.arange(1, 100, 1)
a = [a1, a2]
b = [i**2 for i in a]
c = [i/5 for i in b]
d = [i*8 for i in c]
e = [i+5 for i in d]
variables = [a, b, c, d, e]
positions = ['right', 'left', 'right', 'left', 'right']
colors = ['green', 'blue', 'red', 'magenta', 'brown']
ylabels = ['potatoes', 'rice', 'tomatoes', 'juice', 'cotton']
xlabel = 'price'
yaxislabels = ['item', 'kg', 'bunch', 'Liters', 'cm3']
"""
def make_patch_spines_invisible(ax):
ax.set_frame_on(True)
ax.patch.set_visible(False)
for sp in ax.spines.values():
sp.set_visible(False)
fig, host = plt.subplots(figsize=figsize)
fig.subplots_adjust(right=0.75)
###### HOST PLOTTING
tkw = dict(size=4, width=1.5, labelsize=fontsize)
p1, = host.plot(variables[0][0], variables[0][1], colors[0], label=ylabels[0])
host.set_xlabel(xlabel, fontsize=fontsize)
host.set_ylabel(yaxislabels[0], fontsize=fontsize)
host.yaxis.label.set_color(p1.get_color())
host.tick_params(axis='y', colors=p1.get_color(), **tkw)
host.tick_params(axis='x', **tkw)
# host.set_xlim(0, 2)
lines = [p1]
# y_axis_dist = 0.2
inc_r = 1
inc_l = -y_axis_dist
for ix, i in enumerate(variables):
if ix != 0:
par = host.twinx()
if positions[ix] == 'right':
par.spines[positions[ix]].set_position(("axes", inc_r))
inc_r += y_axis_dist
elif positions[ix] == 'left':
par.spines[positions[ix]].set_position(("axes", inc_l))
inc_l -= y_axis_dist
make_patch_spines_invisible(par)
par.spines[positions[ix]].set_visible(True)
par.yaxis.set_label_position(positions[ix])
par.yaxis.set_ticks_position(positions[ix])
p, = par.plot(variables[ix][0], variables[ix][1], colors[ix], label=ylabels[ix])
par.set_ylabel(yaxislabels[ix], fontsize=fontsize)
par.yaxis.label.set_color(p.get_color())
par.tick_params(axis='y', colors=p.get_color(), **tkw)
lines.append(p)
host.legend(lines, [l.get_label() for l in lines], fontsize=fontsize, loc='lower right')
plt.savefig("example.png", dpi=300, bbox_inches="tight")
plt.show()
a1 = np.arange(1, 100, 1)
a2 = np.arange(1, 100, 1)
a = [a1, a2]
b = [i**2 for i in a]
c = [i/5 for i in b]
d = [i*8 for i in c]
e = [i+5 for i in d]
variables = [a, b, c, d, e]
positions = ['right', 'left', 'right', 'left', 'right']
colors = ['green', 'blue', 'red', 'magenta', 'brown']
ylabels = ['potatoes', 'rice', 'tomatoes', 'juice', 'cotton']
xlabel = 'price'
yaxislabels = ['item', 'kg', 'bunch', 'Liters', 'cm3']
plotting_several_axis(variables, positions, colors, ylabels, xlabel, yaxislabels, y_axis_dist=0.2)
How can I annotate a range of my data? E.g., say the data from x = 5 to x = 10 is larger than some cut-off, how could I indicate that on the graph. If I was annotating by hand, I would just draw a large bracket above the range and write my annotation above the bracket.
The closest I've seen is using arrowstyle='<->' and connectionstyle='bar', to make two arrows pointing to the edges of your data with a line connecting their tails. But that doesn't quite do the right thing; the text that you enter for the annotation will end up under one of the arrows, rather than above the bar.
Here is my attempt, along with it's results:
annotate(' ', xy=(1,.5), xycoords='data',
xytext=(190, .5), textcoords='data',
arrowprops=dict(arrowstyle="<->",
connectionstyle="bar",
ec="k",
shrinkA=5, shrinkB=5,
)
)
Another problem with my attempted solution is that the squared shape of the annotating bracket does not really make it clear that I am highlighting a range (unlike, e.g., a curly brace). But I suppose that's just being nitpicky at this point.
As mentioned in this answer, you can construct curly brackets with sigmoidal functions. Below is a function that adds curly brackets just above the x-axis. The curly brackets it produces should look the same regardless of the axes limits, as long as the figure width and height don't vary.
import numpy as np
import matplotlib.pyplot as plt
def draw_brace(ax, xspan, text):
"""Draws an annotated brace on the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:resolution//2+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = ymin + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1)
ax.text((xmax+xmin)/2., ymin+.07*yspan, text, ha='center', va='bottom')
ax = plt.gca()
ax.plot(range(10))
draw_brace(ax, (0, 8), 'large brace')
draw_brace(ax, (8, 9), 'small brace')
Output:
I modified Joooeey's answer to allow to change the vertical position of braces:
def draw_brace(ax, xspan, yy, text):
"""Draws an annotated brace on the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1)
ax.text((xmax+xmin)/2., yy+.07*yspan, text, ha='center', va='bottom')
ax = plt.gca()
ax.plot(range(10))
draw_brace(ax, (0, 8), -0.5, 'large brace')
draw_brace(ax, (8, 9), 3, 'small brace')
Output:
Also note that in Joooeey's answer, line
x_half = x[:resolution/2+1]
should be
x_half = x[:int(resolution/2)+1]
Otherwise, the number that the script tries to use as index here is a float.
Finally, note that right now the brace will not show up if you move it out of bounds. You need to add parameter clip_on=False, like this:
ax.plot(x, y, color='black', lw=1, clip_on=False)
You can just wrap it all up in a function:
def add_range_annotation(ax, start, end, txt_str, y_height=.5, txt_kwargs=None, arrow_kwargs=None):
"""
Adds horizontal arrow annotation with text in the middle
Parameters
----------
ax : matplotlib.Axes
The axes to draw to
start : float
start of line
end : float
end of line
txt_str : string
The text to add
y_height : float
The height of the line
txt_kwargs : dict or None
Extra kwargs to pass to the text
arrow_kwargs : dict or None
Extra kwargs to pass to the annotate
Returns
-------
tuple
(annotation, text)
"""
if txt_kwargs is None:
txt_kwargs = {}
if arrow_kwargs is None:
# default to your arrowprops
arrow_kwargs = {'arrowprops':dict(arrowstyle="<->",
connectionstyle="bar",
ec="k",
shrinkA=5, shrinkB=5,
)}
trans = ax.get_xaxis_transform()
ann = ax.annotate('', xy=(start, y_height),
xytext=(end, y_height),
transform=trans,
**arrow_kwargs)
txt = ax.text((start + end) / 2,
y_height + .05,
txt_str,
**txt_kwargs)
if plt.isinteractive():
plt.draw()
return ann, txt
Alternately,
start, end = .6, .8
ax.axvspan(start, end, alpha=.2, color='r')
trans = ax.get_xaxis_transform()
ax.text((start + end) / 2, .5, 'test', transform=trans)
Here is a minor modification to guzey and jooeey's answer to plot the flower braces outside the axes.
def draw_brace(ax, xspan, yy, text):
"""Draws an annotated brace outside the axes."""
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
ax.plot(x, -y, color='black', lw=1, clip_on=False)
ax.text((xmax+xmin)/2., -yy-.17*yspan, text, ha='center', va='bottom')
# Sample code
fmax = 1
fstart = -100
fend = 0
frise = 50
ffall = 20
def S(x):
if x<=0:
return 0
elif x>=1:
return 1
else:
return 1/(1+np.exp((1/(x-1))+(1/x)))
x = np.linspace(700,1000,500)
lam = [fmax*(S((i-880)/60)-S(((i-1000)/25)+1)) for i in x]
fig = plt.figure(1)
ax = fig.add_subplot(111)
plt.plot(x,lam)
plt.xlim([850,1000])
ax.set_aspect(50,adjustable='box')
plt.ylabel('$\lambda$')
plt.xlabel('$x$')
ax.xaxis.set_label_coords(0.5, -0.35)
draw_brace(ax, (900,950),0.2, 'rise')
draw_brace(ax, (980,1000),0.2, 'fall')
plt.text(822,0.95,'$(\lambda_{\mathrm{max}})$')
Sample output
a minor modification of the draw_brace of #Joooeey and #guezy to have also the brace upside down
+argument upsidedown
def draw_brace(ax, xspan, yy, text, upsidedown=False):
"""Draws an annotated brace on the axes."""
# shamelessly copied from https://stackoverflow.com/questions/18386210/annotating-ranges-of-data-in-matplotlib
xmin, xmax = xspan
xspan = xmax - xmin
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
ymin, ymax = ax.get_ylim()
yspan = ymax - ymin
resolution = int(xspan/xax_span*100)*2+1 # guaranteed uneven
beta = 300./xax_span # the higher this is, the smaller the radius
x = np.linspace(xmin, xmax, resolution)
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
if upsidedown:
y = np.concatenate((y_half_brace[-2::-1], y_half_brace))
else:
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
y = yy + (.05*y - .01)*yspan # adjust vertical position
ax.autoscale(False)
line = ax.plot(x, y, color='black', lw=1)
if upsidedown:
text = ax.text((xmax+xmin)/2., yy+-.07*yspan, text, ha='center', va='bottom',fontsize=7)
else:
text = ax.text((xmax+xmin)/2., yy+.07*yspan, text, ha='center', va='bottom',fontsize=7)
return line, text
I updated the previous answers to have some of the features I wanted, like an option for a vertical brace, that I wanted to place in multi-plot figures. One still has to futz with the beta_scale parameter sometimes depending on the scale of the data that one is applying this to.
def rotate_point(x, y, angle_rad):
cos,sin = np.cos(angle_rad),np.sin(angle_rad)
return cos*x-sin*y,sin*x+cos*y
def draw_brace(ax, span, position, text, text_pos, brace_scale=1.0, beta_scale=300., rotate=False, rotate_text=False):
'''
all positions and sizes are in axes units
span: size of the curl
position: placement of the tip of the curl
text: label to place somewhere
text_pos: position for the label
beta_scale: scaling for the curl, higher makes a smaller radius
rotate: true rotates to place the curl vertically
rotate_text: true rotates the text vertically
'''
# get the total width to help scale the figure
ax_xmin, ax_xmax = ax.get_xlim()
xax_span = ax_xmax - ax_xmin
resolution = int(span/xax_span*100)*2+1 # guaranteed uneven
beta = beta_scale/xax_span # the higher this is, the smaller the radius
# center the shape at (0, 0)
x = np.linspace(-span/2., span/2., resolution)
# calculate the shape
x_half = x[:int(resolution/2)+1]
y_half_brace = (1/(1.+np.exp(-beta*(x_half-x_half[0])))
+ 1/(1.+np.exp(-beta*(x_half-x_half[-1]))))
y = np.concatenate((y_half_brace, y_half_brace[-2::-1]))
# put the tip of the curl at (0, 0)
max_y = np.max(y)
min_y = np.min(y)
y /= (max_y-min_y)
y *= brace_scale
y -= max_y
# rotate the trace before shifting
if rotate:
x,y = rotate_point(x, y, np.pi/2)
# shift to the user's spot
x += position[0]
y += position[1]
ax.autoscale(False)
ax.plot(x, y, color='black', lw=1, clip_on=False)
# put the text
ax.text(text_pos[0], text_pos[1], text, ha='center', va='bottom', rotation=90 if rotate_text else 0)