Related
I am trying to create an image from a matrix z2 over a raster defined by np.meshgrid(grid_x, grid_y) such that the value of the image at vx=grid_x[i], vy=grid_y[j] is z2[i, j]. On top of this image, I am trying to add a scatter plot of a number of points obtained by three vectors x, y, z such that the i-th point has the coordinate (x[k], y[k]) and the value z[k]. All of these scattered points lies within the region of the aforementioned raster.
Here's an example of the aforementioned data I am trying to plot.
import numpy as np
np.random.seed(1)
z2 = np.ones((1000, 1000)) * 0.66
z2[0, 0] = 0
z2[-1, -1] = 1
x = np.random.rand(1000) * 1000
y = np.random.rand(1000) * 1000
z = np.random.rand(1000)
grid_x = np.linspace(0, 999, 1000)
grid_y = np.linspace(0, 999, 1000)
In order to do this, I am using a 2D plot where the x and y values are used to define the position of the points and z is indicated by a color drawn from a colormap.
What is required of this image is that 1) there should be no white space between the actual plot and the edge of the figure; 2) the unit length on the x and y axis should be equal; 3) the image should not be too large. In order to achieve these, I am using the following code for plotting.
import matplotlib.pyplot as plt
from matplotlib import cm
def plot_img(x, y, z, grid_x, grid_y, z2, set_fig_size=True):
# determine the figure size
if set_fig_size:
height, width = np.array(z2.shape, dtype=float)
dpi = max(max(640 // height, 640 // width), 1)
width, height = width * dpi, height * dpi
plt.gcf().set_size_inches(width, height)
plt.gcf().set_dpi(dpi)
# plot the figure
plt.gca().axis('off')
plt.gca().axis('equal')
plt.gca().set_position([0, 0, 1, 1])
plt.xlim((grid_x[0], grid_x[-1]))
plt.ylim((grid_y[0], grid_y[-1]))
# the raster
cmap = cm.get_cmap('gray')
cmap.set_bad(color='red', alpha=0.5)
plt.imshow(z2, cmap=cmap, interpolation='none', origin='lower',
extent=(grid_x[0], grid_x[-1], grid_y[0], grid_y[-1]))
# the scatter plot
min_z, max_z = np.min(z), np.max(z)
c = (z - min_z) / (max_z - min_z)
plt.scatter(x, y, marker='o', c=c, cmap='Greens')
plt.show()
Strangely, when I run plot_img(x, y, z, grid_x, grid_y, z2) using the aforementioned example data, the following image shows up.
Essentially, only the raster data got plotted, while the scattered data is not.
I then tried plot_img(x, y, z, grid_x, grid_y, z2, set_fig_size=False). The result is
Note that here to clearly show the white spaces in the figure, I kept the background of PyCharm surrounding it. Essentially, there are white spaces that I do not wish included in this figure.
I wonder why this is happening, and how I can fix the code to get the correct output, which is essentially the second result without the white spaces. Thanks!
Replace your dpi and figsize code by
# determine the figure size
height, width = np.array(z2.shape, dtype=float)
dpi = 200
# get size in inches:
width, height = height / dpi, width / dpi
plt.gcf().set_size_inches(width, height)
plt.gcf().set_dpi(dpi)
and you will have a 1000x1000 pixel figure, which at 200 dpi is 5"x5".
I have several plots and one of these showed below:
Example plot
Problem is I have many plots and I need to put the legend differently according to the position where x=0 and line of x=0 may vary in different plots.
How can I achieve this?
besides, bbox_to_anchor just allow me locate relatively to the fig, but have no idea of the inside (x,y) coordinate.
This is the part plotting:
ax.errorbar(x=x, y=y_erd, yerr=e_erd, fmt='-o',ecolor='orange',elinewidth=1,ms=5,mfc='wheat',mec='salmon',capsize=3)
ax.errorbar(x=x, y=y_ers, yerr=e_ers, fmt='-o',ecolor='blue',elinewidth=1,ms=5,mfc='wheat',mec='salmon',capsize=3)
ax.legend(['ERD', 'ERS'], loc="upper left", bbox_to_anchor=(1, 0.85),fontsize='x-small')
ax.axhline(y=0, color='r', linestyle='--')
We have created a code to calculate the zero position of the x and y axes using a simple sample as an example. First, get the tick values for each axis. Then, use the obtained value to get the index of zero. The next step is to calculate the position of the tick marks for the difference between the minimum and maximum values. From the array, we obtain the coordinates based on the zero index we obtained earlier. Set the obtained coordinates to bbox_to_anchor=[].
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(-10, 10, 500)
y = np.sin(x)
fig, ax = plt.subplots()
ax.plot(x, y, label='x=0,y=0')
xticks, yticks = ax.get_xticks(), ax.get_yticks()
xpos, ypos = 0, 0
for i,(x,y) in enumerate(zip(xticks, yticks)):
if x == 0:
xpos = i
if y == 0:
ypos = i
print(xpos, ypos)
x_min, x_max = ax.get_xlim()
xticks = [(tick - x_min)/(x_max - x_min) for tick in xticks]
y_min, y_max = ax.get_ylim()
yticks = [(tick - y_min)/(y_max - y_min) for tick in yticks]
print(xticks[xpos], yticks[ypos])
ax.legend(bbox_to_anchor=[xticks[xpos], yticks[ypos]], loc='center')
plt.show()
Does anyone know how to implement easily colormaps to 3d bar plots in matplotlib?
Consider this example, how do I change each bar according to a colormap? For example, short bars should be mainly blue, while taller bars graduate their colors from blue towards the red...
In the physical sciences, it's common to want a so-called LEGO plot, which is I think what the original user is going for. Kevin G's answer is good and got me to the final result. Here's a more advanced histogram, for x-y scatter data, colored by height:
xAmplitudes = np.random.exponential(10,10000) #your data here
yAmplitudes = np.random.normal(50,10,10000) #your other data here - must be same array length
x = np.array(xAmplitudes) #turn x,y data into numpy arrays
y = np.array(yAmplitudes) #useful for regular matplotlib arrays
fig = plt.figure() #create a canvas, tell matplotlib it's 3d
ax = fig.add_subplot(111, projection='3d')
#make histogram stuff - set bins - I choose 20x20 because I have a lot of data
hist, xedges, yedges = np.histogram2d(x, y, bins=(20,20))
xpos, ypos = np.meshgrid(xedges[:-1]+xedges[1:], yedges[:-1]+yedges[1:])
xpos = xpos.flatten()/2.
ypos = ypos.flatten()/2.
zpos = np.zeros_like (xpos)
dx = xedges [1] - xedges [0]
dy = yedges [1] - yedges [0]
dz = hist.flatten()
cmap = cm.get_cmap('jet') # Get desired colormap - you can change this!
max_height = np.max(dz) # get range of colorbars so we can normalize
min_height = np.min(dz)
# scale each z to [0,1], and get their rgb values
rgba = [cmap((k-min_height)/max_height) for k in dz]
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average')
plt.title("X vs. Y Amplitudes for ____ Data")
plt.xlabel("My X data source")
plt.ylabel("My Y data source")
plt.savefig("Your_title_goes_here")
plt.show()
Note: results will vary depending on how many bins you choose and how much data you use. This code needs you to insert some data or generate a random linear array. Resulting plots are below, with two different perspectives:
So maybe not exactly what you're looking for (perhaps a good starting point for you), but using
Getting individual colors from a color map in matplotlib
can give varying solid colors for the bars:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.cm as cm # import colormap stuff!
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x, y = np.random.rand(2, 100) * 4
hist, xedges, yedges = np.histogram2d(x, y, bins=4, range=[[0, 4], [0, 4]])
# Construct arrays for the anchor positions of the 16 bars.
# Note: np.meshgrid gives arrays in (ny, nx) so we use 'F' to flatten xpos,
# ypos in column-major order. For numpy >= 1.7, we could instead call meshgrid
# with indexing='ij'.
xpos, ypos = np.meshgrid(xedges[:-1] + 0.25, yedges[:-1] + 0.25)
xpos = xpos.flatten('F')
ypos = ypos.flatten('F')
zpos = np.zeros_like(xpos)
# Construct arrays with the dimensions for the 16 bars.
dx = 0.5 * np.ones_like(zpos)
dy = dx.copy()
dz = hist.flatten()
cmap = cm.get_cmap('jet') # Get desired colormap
max_height = np.max(dz) # get range of colorbars
min_height = np.min(dz)
# scale each z to [0,1], and get their rgb values
rgba = [cmap((k-min_height)/max_height) for k in dz]
ax.bar3d(xpos, ypos, zpos, dx, dy, dz, color=rgba, zsort='average')
plt.show()
Personally, I find that ugly as sin! But it probably won't look too bad with a sequential colormap - https://matplotlib.org/examples/color/colormaps_reference.html
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)
I'm drawing a scatter plot in which I'm specifying the color of each point:
ax.scatter(x, y, c=z)
The problem is that some values of z are -inf, and these points simply aren't plotted. I'd like to set them to some color. I tried this:
cm = mpl.cm.get_cmap()
cm.set_under('k',1.0)
cm.set_bad('purple',1.0)
ax.scatter(x, y, c=z, cmap=cm)
but there's still no points shown for the -inf values.
I think it's easiest to overplot the points at infinity:
cm = mpl.cm.get_cmap()
cm.set_under('k',1.0)
cm.set_bad('purple',1.0)
ax.scatter(x, y, c=z)
ax.scatter(x[numpy.isinf(z)], y[numpy.isinf(z)], c='b')
If you want to exclude positive infinity, this seems to work:
cm = mpl.cm.get_cmap()
cm.set_under('k',1.0)
cm.set_bad('purple',1.0)
ax.scatter(x, y, c=z)
indices = numpy.isinf(z) & (z < 0)
ax.scatter(x[indices], y[indices], c='b')
All code untested though.