Matplotlib Radial Graph (polar plot) - sub axis control - python

I have a polygon radial graph, however I cannot figure out how to make the y-tick lines polygon like the rest of the graph.
radar.py:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
from matplotlib.path import Path
from matplotlib.spines import Spine
from matplotlib.projections.polar import PolarAxes
from matplotlib.projections import register_projection
def _radar_factory(num_vars):
theta = 2*np.pi * np.linspace(0, 1-1./num_vars, num_vars)
theta += np.pi/2
def unit_poly_verts(theta):
x0, y0, r = [0.5] * 3
verts = [(r*np.cos(t) + x0, r*np.sin(t) + y0) for t in theta]
return verts
class RadarAxes(PolarAxes):
name = 'radar'
RESOLUTION = 1
def fill(self, *args, **kwargs):
closed = kwargs.pop('closed', True)
return super(RadarAxes, self).fill(closed=closed, *args, **kwargs)
def plot(self, *args, **kwargs):
lines = super(RadarAxes, self).plot(*args, **kwargs)
for line in lines:
self._close_line(line)
def _close_line(self, line):
x, y = line.get_data()
# FIXME: markers at x[0], y[0] get doubled-up
if x[0] != x[-1]:
x = np.concatenate((x, [x[0]]))
y = np.concatenate((y, [y[0]]))
line.set_data(x, y)
def set_varlabels(self, labels):
self.set_thetagrids(theta * 180/np.pi, labels)
def _gen_axes_patch(self):
verts = unit_poly_verts(theta)
return plt.Polygon(verts, closed=True, edgecolor='k')
def _gen_axes_spines(self):
spine_type = 'circle'
verts = unit_poly_verts(theta)
verts.append(verts[0])
path = Path(verts)
spine = Spine(self, spine_type, path)
spine.set_transform(self.transAxes)
return {'polar': spine}
register_projection(RadarAxes)
return theta
def radar_graph(labels = [], values = []):
N = len(labels)
theta = _radar_factory(N)
fig = plt.figure()
ax = fig.add_subplot(1, 1, 1, projection='radar')
ax.set_ylim(0,100)
ax.set_yticks([20, 40, 60, 80, 100])
ax.set_yticklabels(['','','','',''])
ax.plot(theta, values, color='k', linestyle='dashed', linewidth=.2)
ax.fill(theta, values, color='#23B5BA')
ax.set_varlabels(labels)
plt.savefig("radar.png", dpi=100)
Here is how I call to radar.py to generate the graph:
from radar import radar_graph
labels = ['1', '2', '3', '4', '5']
values = [65, 66, 53, 54, 78]
radar_graph(labels, values)
As you can see in this image the y-axis are circles but the overall structure is a polygon.Radar Image:
Maybe there is a way to transform the yticks similar to the spines.

Solution thought of by ImportanceOfBeingErnest, instead of trying to change the default circle sub-grid we just hide it and plot the sub-grid we want as polygon's.
We can add the following to Radar.py
#Hide Circle Grid
ax.grid(alpha=0.0)
#Draw Sub-axis Grid
ax.plot(theta, [.5,.5,.5,.5,.5], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [20,20,20,20,20], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [40,40,40,40,40], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [60,60,60,60,60], color='grey', linestyle='solid', linewidth=.2, alpha=1)
ax.plot(theta, [80,80,80,80,80], color='grey', linestyle='solid', linewidth=.2, alpha=1)
While I am sure there is a more elegant solution, this works.

Related

Wrong matplotlib animation

I have the following code that should draw a cycloid with animation and save it to a gif
but after running the program, a white square appears that covers everything, I can't find the reason cycloid_animation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.axes().transAxes
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.axes().set_aspect('equal')
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')
ani
Each time you call plt.axis() you are creating a new axis on top of the figure. Since what you want is to get the current axis and then apply the transformations, after creating the figure you should call plt.gca() to get the current axis and use that instead.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.animation as animation
from matplotlib.animation import FuncAnimation, PillowWriter
plt.rcParams['animation.html'] = 'html5'
R = 1
def circle(a, b, r):
# (a,b): the center of the circle
# r: the radius of the circle
# T: The number of the segments
T = 100
x, y = [0]*T, [0]*T
for i,theta in enumerate(np.linspace(0,2*np.pi,T)):
x[i] = a + r*np.cos(theta)
y[i] = b + r*np.sin(theta)
return x, y
# Calculate the cycloid line
thetas = np.linspace(0,4*np.pi,100)
cycloid_x = R*(thetas-np.sin(thetas))
cycloid_y = R*(1-np.cos(thetas))
cycloid_c = R*thetas
fig = plt.figure()
lns = []
trans = plt.gca().transAxes #<=== HERE
for i in range(len(thetas)):
x,y = circle(cycloid_c[i], R, R)
ln1, = plt.plot(x, y, 'g-', lw=2)
ln2, = plt.plot(cycloid_x[:i+1] ,cycloid_y[:i+1], 'r-', lw=2)
ln3, = plt.plot(cycloid_x[i], cycloid_y[i], 'bo', markersize=4)
ln4, = plt.plot([cycloid_c[i], cycloid_x[i]], [R,cycloid_y[i]], 'y-', lw=2)
tx1 = plt.text(0.05, 0.8, r'$\theta$ = %.2f $\pi$' % (thetas[i]/np.pi), transform=trans)
lns.append([ln1,ln2,ln3,ln4,tx1])
plt.xlim(0,15)
plt.ylim(0,3)
plt.xlabel('x')
plt.ylabel('y')
plt.grid()
plt.gca().set_aspect('equal') #<=== And HERE
ani = animation.ArtistAnimation(fig, lns, interval=50)
#ani.save('cycloid_ArtistAnimation.mp4',writer='ffmpeg')
ani.save('cycloid_ArtistAnimation.gif',writer='pillow')

Advanced horizontal bar chart with Python?

I want to make a graph like the two below.
How can I achieve that with python? I am sorry that I can´t provide any implementation because I don´t have any idea at all. I think my question is something different to this.
https://matplotlib.org/gallery/lines_bars_and_markers/barh.html#sphx-glr-gallery-lines-bars-and-markers-barh-py
Could someone give me some suggestions with just some simple numbers?
The tutorial for vertical gradient bars can be adapted to draw horizontal bars with the darkest spot in the middle:
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
import matplotlib.colors as mcolors
import numpy as np
def hor_gradient_image(ax, extent, darkest, **kwargs):
'''
puts a horizontal gradient in the rectangle defined by extent (x0, x1, y0, y1)
darkest is a number between 0 (left) and 1 (right) setting the spot where the gradient will be darkest
'''
ax = ax or plt.gca()
img = np.interp(np.linspace(0, 1, 100), [0, darkest, 1], [0, 1, 0]).reshape(1, -1)
return ax.imshow(img, extent=extent, interpolation='bilinear', vmin=0, vmax=1, **kwargs)
def gradient_hbar(y, x0, x1, ax=None, height=0.8, darkest=0.5, cmap=plt.cm.PuBu):
hor_gradient_image(ax, extent=(x0, x1, y - height / 2, y + height / 2), cmap=cmap, darkest=darkest)
rect = mpatches.Rectangle((x0, y - height / 2), x1 - x0, height, edgecolor='black', facecolor='none')
ax.add_patch(rect)
# cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['paleturquoise', 'darkturquoise'])
cmap = mcolors.LinearSegmentedColormap.from_list('turq', ['#ACFAFA', '#3C9E9E'])
fig, ax = plt.subplots()
for y in range(1, 11):
x0, x1 = np.sort(np.random.uniform(1, 9, 2))
gradient_hbar(y, x0, x1, ax=ax, height=0.7, darkest=0.5, cmap=cmap)
ax.set_aspect('auto')
ax.use_sticky_edges = False
ax.autoscale(enable=True, tight=False)
ax.grid(axis='x')
plt.show()

Does the fill_between function in matplotlib have a gradient feature? [duplicate]

I happened to see a beautiful graph on this page which is shown below:
Is it possible to get such color gradients in matplotlib?
There have been a handful of previous answers to similar questions (e.g. https://stackoverflow.com/a/22081678/325565), but they recommend a sub-optimal approach.
Most of the previous answers recommend plotting a white polygon over a pcolormesh fill. This is less than ideal for two reasons:
The background of the axes can't be transparent, as there's a filled polygon overlying it
pcolormesh is fairly slow to draw and isn't smoothly interpolated.
It's a touch more work, but there's a method that draws much faster and gives a better visual result: Set the clip path of an image plotted with imshow.
As an example:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
from matplotlib.patches import Polygon
np.random.seed(1977)
def main():
for _ in range(5):
gradient_fill(*generate_data(100))
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def gradient_fill(x, y, fill_color=None, ax=None, **kwargs):
"""
Plot a line with a linear alpha gradient filled beneath it.
Parameters
----------
x, y : array-like
The data values of the line.
fill_color : a matplotlib color specifier (string, tuple) or None
The color for the fill. If None, the color of the line will be used.
ax : a matplotlib Axes instance
The axes to plot on. If None, the current pyplot axes will be used.
Additional arguments are passed on to matplotlib's ``plot`` function.
Returns
-------
line : a Line2D instance
The line plotted.
im : an AxesImage instance
The transparent gradient clipped to just the area beneath the curve.
"""
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
z = np.empty((100, 1, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, 100)[:,None]
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
main()
Please note Joe Kington deserves the lion's share of the credit here; my sole contribution is zfunc.
His method opens to door to many gradient/blur/drop-shadow
effects. For example, to make the lines have an evenly blurred underside, you
could use PIL to build an alpha layer which is 1 near the line and 0 near the bottom edge.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import matplotlib.patches as patches
from PIL import Image
from PIL import ImageDraw
from PIL import ImageFilter
np.random.seed(1977)
def demo_blur_underside():
for _ in range(5):
# gradient_fill(*generate_data(100), zfunc=None) # original
gradient_fill(*generate_data(100), zfunc=zfunc)
plt.show()
def generate_data(num):
x = np.linspace(0, 100, num)
y = np.random.normal(0, 1, num).cumsum()
return x, y
def zfunc(x, y, fill_color='k', alpha=1.0):
scale = 10
x = (x*scale).astype(int)
y = (y*scale).astype(int)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
w, h = xmax-xmin, ymax-ymin
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
# Build a z-alpha array which is 1 near the line and 0 at the bottom.
img = Image.new('L', (w, h), 0)
draw = ImageDraw.Draw(img)
xy = np.column_stack([x, y])
xy -= xmin, ymin
# Draw a blurred line using PIL
draw.line(list(map(tuple, xy)), fill=255, width=15)
img = img.filter(ImageFilter.GaussianBlur(radius=100))
# Convert the PIL image to an array
zalpha = np.asarray(img).astype(float)
zalpha *= alpha/zalpha.max()
# make the alphas melt to zero at the bottom
n = zalpha.shape[0] // 4
zalpha[:n] *= np.linspace(0, 1, n)[:, None]
z[:,:,-1] = zalpha
return z
def gradient_fill(x, y, fill_color=None, ax=None, zfunc=None, **kwargs):
if ax is None:
ax = plt.gca()
line, = ax.plot(x, y, **kwargs)
if fill_color is None:
fill_color = line.get_color()
zorder = line.get_zorder()
alpha = line.get_alpha()
alpha = 1.0 if alpha is None else alpha
if zfunc is None:
h, w = 100, 1
z = np.empty((h, w, 4), dtype=float)
rgb = mcolors.colorConverter.to_rgb(fill_color)
z[:,:,:3] = rgb
z[:,:,-1] = np.linspace(0, alpha, h)[:,None]
else:
z = zfunc(x, y, fill_color=fill_color, alpha=alpha)
xmin, xmax, ymin, ymax = x.min(), x.max(), y.min(), y.max()
im = ax.imshow(z, aspect='auto', extent=[xmin, xmax, ymin, ymax],
origin='lower', zorder=zorder)
xy = np.column_stack([x, y])
xy = np.vstack([[xmin, ymin], xy, [xmax, ymin], [xmin, ymin]])
clip_path = patches.Polygon(xy, facecolor='none', edgecolor='none', closed=True)
ax.add_patch(clip_path)
im.set_clip_path(clip_path)
ax.autoscale(True)
return line, im
demo_blur_underside()
yields
I've tried something :
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
xData = range(100)
yData = range(100)
plt.plot(xData, yData)
NbData = len(xData)
MaxBL = [[MaxBL] * NbData for MaxBL in range(100)]
Max = [np.asarray(MaxBL[x]) for x in range(100)]
for x in range (50, 100):
plt.fill_between(xData, Max[x], yData, where=yData >Max[x], facecolor='red', alpha=0.02)
for x in range (0, 50):
plt.fill_between(xData, yData, Max[x], where=yData <Max[x], facecolor='green', alpha=0.02)
plt.fill_between([], [], [], facecolor='red', label="x > 50")
plt.fill_between([], [], [], facecolor='green', label="x < 50")
plt.legend(loc=4, fontsize=12)
plt.show()
fig.savefig('graph.png')
.. and the result:
Of course the gradient could go down to 0 by changing the range of feel_between function.

plotting the projection of 3D plot in three planes using contours

I have a three columns catalogue of data and I would like to make a 3D plot of them plus the projection of each axis as a projected contour in the the plane of the other two axises. So far I could make the 3D plot using matplotlib which still doesn't show anything from the properties of the data.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
from numpy import *
data=loadtxt('test.cat')
X=data[:,0]
Y=data[:,1]
Z=data[:,2]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X, Y, Z, c='r', marker='.')
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.show()
How could I plot the projection of the data in each plane with colorbar as well?
hmm, indeed, difficult data to display. Maybe creating some slices along one axis and creating certain number 2D plots would be best. However 3D plots are fancy. I played a bit with the data resulting in one 3D plot as you did and a separate plot with the projections.
The colors of the points are according the missing axis
Added transparency to give an idea of density
Kept axes of both plots the same
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
data = np.loadtxt('test.cat', skiprows=1)
X=data[:,0]
Y=data[:,1]
Z=data[:,2]
plt.figure()
ax1 = plt.subplot(111, projection='3d')
ax1.scatter(X, Y, Z, c='b', marker='.', alpha=0.2)
ax1.set_xlabel('X - axis')
ax1.set_ylabel('Y - axis')
ax1.set_zlabel('Z - axis')
plt.figure()
ax2 = plt.subplot(111, projection='3d')
plt.hot()
cx = np.ones_like(X) * ax1.get_xlim3d()[0]
cy = np.ones_like(X) * ax1.get_ylim3d()[1]
cz = np.ones_like(Z) * ax1.get_zlim3d()[0]
ax2.scatter(X, Y, cz, c=Z, marker='.', lw=0, alpha=0.2)
ax2.scatter(X, cy, Z, c=-Y, marker='.', lw=0, alpha=0.2)
ax2.scatter(cx, Y, Z, c=X, marker='.', lw=0, alpha=0.2)
ax2.set_xlim3d(ax1.get_xlim3d())
ax2.set_ylim3d(ax1.get_ylim3d())
ax2.set_zlim3d(ax1.get_zlim3d())
ax2.set_xlabel('X - axis')
ax2.set_ylabel('Y - axis')
ax2.set_zlabel('Z - axis')
According to what you want to do you need to use the zdir parameter for the contour and contourf functions. Here an example:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = Axes3D(fig)
X = np.arange(-4, 4, 0.25)
Y = np.arange(-4, 4, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X ** 2 + Y ** 2)
Z = np.sin(R)
ax.contourf(X, Y, Z, zdir='x', offset=-4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='x', offset=-4, colors='k')
ax.contourf(X, Y, Z, zdir='y', offset=4, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='y', offset=4, colors='k')
ax.contourf(X, Y, Z, zdir='z', offset=-1, cmap=plt.cm.hot)
ax.contour(X, Y, Z, zdir='z', offset=-1, colors='k')
plt.show()
With result:
Here's hoping I'm not shooting a mosquito with a [very ugly] cannon, I recently made a Binning_Array class for a school project and I think it might be able to help you:
import numpy as np
import matplotlib.pyplot as plt
import binning_array as ba
from mpl_toolkits.mplot3d import axes3d
data = np.loadtxt('test.cat')
X = data[:,0]
Y = data[:,1]
Z = data[:,2]
n_points = data.shape[0]
X_min = np.round(np.min(data[:,0])-0.5)
X_max = np.round(np.max(data[:,0])+0.5)
Y_min = np.round(np.min(data[:,1])-0.5)
Y_max = np.round(np.max(data[:,1])+0.5)
Z_min = np.round(np.min(data[:,2])-0.5)
Z_max = np.round(np.max(data[:,2])+0.5)
n_min_bins = 25
step = min([(X_max-X_min)/n_min_bins, (Y_max-Y_min)/n_min_bins, (Z_max-Z_min)/n_min_bins])
# Using three Binners
BinnerXY = ba.Binning_Array([[X_min, X_max, step],
[Y_min, Y_max, step]])
BinnerYZ = ba.Binning_Array([[Y_min, Y_max, step],
[Z_min, Z_max, step]])
BinnerXZ = ba.Binning_Array([[X_min, X_max, step],
[Z_min, Z_max, step]])
for point in data:
BinnerXY.add_value([point[0], point[1]])
BinnerXZ.add_value([point[0], point[2]])
BinnerYZ.add_value([point[1], point[2]])
fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
# Plot 2D projections on the 3D graph
vmin = np.min([BinnerXZ.bin_min(), BinnerYZ.bin_min(), BinnerXY.bin_min()])
vmax = np.max([BinnerXZ.bin_max(), BinnerYZ.bin_max(), BinnerXY.bin_max()])
levels = np.linspace(vmin,vmax,20)
xs_c = np.arange(*BinnerXZ.limits[0])
zs_c = np.arange(*BinnerXZ.limits[1])
ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
ax[0].contourf(X=XS_C, Y=BinnerXZ.bins, Z=ZS_C,
zdir='y', offset=Y_max,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
xs_c = np.arange(*BinnerXY.limits[0])
ys_c = np.arange(*BinnerXY.limits[1])
YS_C, XS_C = np.meshgrid(ys_c,xs_c)
ax[0].contourf(X=XS_C, Y=YS_C, Z=BinnerXY.bins,
zdir='z', offset=Z_min,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ys_c = np.arange(*BinnerYZ.limits[0])
zs_c = np.arange(*BinnerYZ.limits[1])
ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
ax[0].contourf(X=BinnerYZ.bins, Y=YS_C, Z=ZS_C,
zdir='x', offset=X_min,
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
# Plot scatter of all data
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
ax[0].set_xlabel(r"$x$")
ax[0].set_ylabel(r"$y$")
ax[0].set_zlabel(r"$z$")
max_range = max([X_max-X_min, Y_max-Y_min, Z_max-Z_min]) / 2.
pos = [(X_max+X_min)/2., (Y_max+Y_min)/2., (Z_max+Z_min)/2.]
ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)
# Plot 2D histograms
BinnerXZ.plot_2d_slice(fig=fig, ax=ax[1], xlabel=r"$x$", ylabel=r'$z$')
BinnerXY.plot_2d_slice(fig=fig, ax=ax[2], xlabel=r"$x$", ylabel=r'$y$')
BinnerYZ.plot_2d_slice(fig=fig, ax=ax[3], xlabel=r"$y$", ylabel=r'$z$')
plt.show()
You can also use only one Binner, but notice that you will get artifacts where the planes intersect:
# ...
# Using three Binners
# ...
# Using only one Binner (adds a small error! see comments!)
Binner = ba.Binning_Array([[X_min, X_max, step],
[Y_min, Y_max, step],
[Z_min, Z_max, step]])
for point in data:
Binner.add_value([point[0], point[1], Z_min])
Binner.add_value([point[0], Y_max-step, point[2]])
Binner.add_value([X_min, point[1], point[2]])
fig = plt.figure()
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
ax[0].scatter(X, Y, Z, c='g', marker='.', alpha=0.2)
Binner.plot_slices(others={0:X_min, 1:Y_max, 2:Z_min}, fig=fig, ax=ax)
plt.show()
The binning_array.py was made for a school project and is not entirely polished, but it's enough for what you want.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
class Binning_Array:
def __init__(self, limits=[[-1.,1.,1.],[-1.,1.,1.]]):
"""Create a new binning array.
The amount of given limits determines the dimension of the array,
although only 2 and 3D have been tested.
Each limit must be a list of start, stop and step for the
axis it represents (x, y or z)."""
self.limits = np.array(limits)
self._shape = []
for i in xrange(len(self.limits)):
self._shape.append((self.limits[i][1]-self.limits[i][0]) / \
float(self.limits[i][2]))
self._shape = tuple(self._shape)
self.dimensions = len(self._shape)
self.bins = np.zeros(self._shape)
self.outside = 0.
self._normalized = 1.
def __repr__(self):
"""Representation method. <<REVIEW>>"""
return "Binning Array! Hurray!"
def __getitem__(self, index):
"""Direct acess to read self.bins (use something like self[index])
Direct acess to write would be given by __setitem__
"""
return self.bins.__getitem__(index)
def position2index(self,position,axis=0):
"""Convert a given position to an index in axis.
If it is outside, it returns -1.
"""
if self.limits[axis][0] <= position < self.limits[axis][1]:
return int((position - self.limits[axis][0]) / self.limits[axis][2])
else: return -1
def index2position(self, index, axis=0):
"""Convert a given index to a position in axis.
If it is outisde, it returns -1.
"""
if 0 <= index < self._shape[axis]:
return self.limits[axis][0] + self.limits[axis][2] * index
else:
return -1
def add_value(self, position, value=1., verbose=False):
"""Add a given value to a specified position.
If verbose it returns a list with the axies at which the position
is outside the scope of this Binning_Array.
Not very efficient because of that (verbose was for debugging).
"""
indexs = []
outside = False
if verbose:
outs = []
for i in xrange(self.dimensions):
# using self.dimensions serves as a filter
# if position has non valid shape
index = self.position2index(position[i],i)
if index == -1:
if verbose:
outside = True
outs.append(i)
else:
self.outside += value / self._normalized
return None # nothing, as it is not verbose
else:
indexs.append(index)
if outside: # the only way to get here is if verbose is True...
self.outside += value / self._normalized
return outs # so I can just return outs...
else:
self.bins[tuple(indexs)] += value / self._normalized
if verbose:
return outs
def get_value(self, position, verbose=False):
"""Return the value at the specified position.
If verbose it alse returns a list with the axies at which the position
is outside the scope of this Binning_Array.
"""
indexs = []
outside = False
if verbose:
outs = []
for i in xrange(self.dimensions):
index = self.position2index(position[i],i)
if index == -1:
if verbose:
outside = True
outs.append[i]
else:
return self.outside
else:
indexs.append(index)
if outside: # the only way to get here is if verbose is True
return self.outside, outs # so I can just return outs...
else:
if verbose:
return self.bins[tuple(indexs)], outs
else:
return self.bins[tuple(indexs)]
def normalize(self, total=None):
"""Divide the entire array by the sum of its values (and outside).
Any value added after this will be normalized by the same factor.
"""
if total is None:
total = self.n_counts()
self.bins /= total
self.outside /= total
self.normalize *= total
def n_counts(self):
"""Return the number of counts."""
return np.sum(self.bins) + self.outside
def bin_max(self):
"""Return the value of the largest bin."""
return np.max(self.bins)
def bin_min(self):
"""Return the value of the largest bin."""
return np.min(self.bins)
def plot_2d_slice(self, cuts=[0,1], others={},
fig=None, ax=None, show=True, **kwargs):
"""Plot a 2D slice."""
x = min(cuts)
y = max(cuts)
xs = np.arange(self.limits[x][0],
self.limits[x][1] + self.limits[x][2],
self.limits[x][2])
ys = np.arange(self.limits[y][0],
self.limits[y][1] + self.limits[y][2],
self.limits[y][2])
index = []
title = ''
for i in xrange(self.dimensions):
if i in cuts:
appendix = slice(self._shape[i]+1)
else:
appendix = others.get(i,(self.limits[i][0]+
self.limits[i][1]) / 2.)
title += '%d:%.4e\t' % (i,appendix)
appendix = self.position2index(appendix,i)
index.append(appendix)
index = tuple(index)
if fig is None:
fig, ax = plt.subplots(1,1)
YS,XS = np.meshgrid(ys, xs)
graph = ax.pcolormesh (XS, YS, self.bins[index], cmap=plt.cm.coolwarm)
fig.colorbar(graph, ax=ax)
ax.axis('equal')
ax.set_xlim(self.limits[x][0], self.limits[x][1])
ax.set_ylim(self.limits[y][0], self.limits[y][1])
if 'xticks' in kwargs:
ax.set_xticks(kwargs['xticks'])
if 'yticks' in kwargs.keys():
ax.set_yticks(kwargs['yticks'])
if 'xlabel' in kwargs:
ax.set_xlabel(kwargs['xlabel'])
if 'ylabel' in kwargs:
ax.set_ylabel(kwargs['ylabel'])
if 'xlim' in kwargs:
ax.set_xlim(*kwargs['xlim'])
if 'ylim' in kwargs:
ax.set_ylim(*kwargs['ylim'])
if show:
fig.tight_layout()
fig.show()
def plot_slices(self, others={}, fig=None, ax=None,
show=True, projections=True):
index = []
pos = []
title = ''
for i in xrange(self.dimensions):
temp = others.get(i,(self.limits[i][0]+self.limits[i][1])/2.)
title += '%d:%.4e\t' % (i,temp)
pos.append(temp)
index.append(self.position2index(temp,i))
if self.dimensions == 3:
if fig is None:
fig = plt.figure()
if projections:
ax = [fig.add_subplot(221, projection='3d'),
fig.add_subplot(222),
fig.add_subplot(223),
fig.add_subplot(224)]
else:
ax = fig.add_subplot(111, projection='3d')
if projections:
xs = np.arange(self.limits[0][0],
self.limits[0][1] + self.limits[0][2],
self.limits[0][2])
ys = np.arange(self.limits[1][0],
self.limits[1][1] + self.limits[1][2],
self.limits[1][2])
zs = np.arange(self.limits[2][0],
self.limits[2][1] + self.limits[2][2],
self.limits[2][2])
xs_c = np.arange(*self.limits[0])
ys_c = np.arange(*self.limits[1])
zs_c = np.arange(*self.limits[2])
vmin = np.min(self.bins)
vmax = np.max(self.bins)
levels = np.linspace(vmin,vmax,20)
#graph 0 (3D)
ax[0].set_xlabel(r"$x$")
ax[0].set_ylabel(r"$y$")
ax[0].set_zlabel(r"$z$")
#ax[0].axis('equal') #not supported in 3D:
#http://stackoverflow.com/questions/13685386/\
#matplotlib-equal-unit-length-with-equal-aspect-ratio-z-axis-is-not-equal-to
max_range = max([xs[-1]-xs[0],ys[-1]-ys[0],zs[-1]-zs[0]]) / 2.
# x_mean = (xs[-1] + xs[0])/2.
# y_mean = (ys[-1] + ys[0])/2.
# z_mean = (zs[-1] +zs[0])/2.
ax[0].set_xlim(pos[0] - max_range, pos[0] + max_range)
ax[0].set_ylim(pos[1] - max_range, pos[1] + max_range)
ax[0].set_zlim(pos[2] - max_range, pos[2] + max_range)
# to understand holes in contour plot:
#http://stackoverflow.com/questions/18897950/\
#matplotlib-pyplot-contourf-function-introduces-holes-or-gaps-when-plotting-regul
# graph 1 (2D)
ZS, XS = np.meshgrid(zs,xs)
ZS_C, XS_C = np.meshgrid(zs_c,xs_c)
ax[1].pcolormesh(XS, ZS, self.bins[:,index[1],:],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=XS_C, Y=self.bins[:,index[1],:], Z=ZS_C,
zdir='y', offset=pos[1],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[1].set_xlabel(r"$x$")
ax[1].set_ylabel(r"$z$")
ax[1].set_xlim(xs[0],xs[-1])
ax[1].set_ylim(zs[0],zs[-1])
ax[1].axis('equal')
# graph 2 (2D)
YS, XS = np.meshgrid(ys,xs)
YS_C, XS_C = np.meshgrid(ys_c,xs_c)
ax[2].pcolormesh(XS, YS, self.bins[:,:,index[2]],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=XS_C, Y=YS_C, Z=self.bins[:,:,index[2]],
zdir='z', offset=pos[2],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[2].set_xlabel(r"$x$")
ax[2].set_ylabel(r"$y$")
ax[2].set_xlim(xs[0],xs[-1])
ax[2].set_ylim(ys[0],ys[-1])
ax[2].axis('equal')
# graph 3 (2D)
ZS, YS = np.meshgrid(zs, ys)
ZS_C, YS_C = np.meshgrid(zs_c, ys_c)
ax[3].pcolormesh(YS, ZS, self.bins[index[0],:,:],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm)
ax[0].contourf(X=self.bins[index[0],:,:], Y=YS_C, Z=ZS_C,
zdir='x', offset=pos[0],
vmin=vmin, vmax=vmax,
cmap=plt.cm.coolwarm, levels=levels,
alpha=0.5)
ax[3].set_xlabel(r"$y$")
ax[3].set_ylabel(r"$z$")
ax[3].set_xlim(ys[0],ys[-1])
ax[3].set_ylim(zs[0],zs[-1])
ax[3].axis('equal')
else:
# update to draw a given slice, use it to plot eaxh axes above!
ax.plot(self.XS,self.YS,self.ZS)
ax.set_zlabel(r"$z$")
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$")
ax.axis('equal')
else:
if fig is None:
fig, ax = plt.subplots(1)
xs = np.arange(self.limits[0][0],
self.limits[0][1] + self.limits[0][2],
self.limits[0][2])
ys = np.arange(self.limits[1][0],
self.limits[1][1] + self.limits[1][2],
self.limits[1][2],)
YS, XS = np.meshgrid(ys, xs)
graph = ax.pcolormesh(XS, YS, self.bins, cmap=plt.cm.coolwarm)
fig.colorbar(graph)
ax.set_xlim(self.limits[0][0], self.limits[0][1])
ax.set_ylim(self.limits[1][0], self.limits[1][1])
ax.set_title('Energy Distribution')
ax.set_xlabel(r"$x$")
ax.set_ylabel(r"$y$")
ax.axis('equal')
if show:
fig.tight_layout()
fig.show()
return fig, ax
If anything on the code is wrong or bugged please say so and I will edit the above (the school project was already graded, so you won't be doing my homework).
Also, if anything is less than clear please say so I add comments or explanations as needed.

How does one add a colorbar to a polar plot (rose diagram)?

In this example the color is correlative to the radius of each bar. How would one add a colorbar to this plot?
My code mimics a "rose diagram" projection which is essentially a bar chart on a polar projection.
here is a part of it:
angle = radians(10.)
patches = radians(360.)/angle
theta = np.arange(0,radians(360.),angle)
count = [0]*patches
for i, item in enumerate(some_array_of_azimuth_directions):
temp = int((item - item%angle)/angle)
count[temp] += 1
width = angle * np.ones(patches)
# force square figure and square axes looks better for polar, IMO
fig = plt.figure(figsize=(8,8))
ax = fig.add_axes([0.1, 0.1, 0.8, 0.8], polar=True)
rmax = max(count) + 1
ax.set_rlim(0,rmax)
ax.set_theta_offset(np.pi/2)
ax.set_thetagrids(np.arange(0,360,10))
ax.set_theta_direction(-1)
# project strike distribution as histogram bars
bars = ax.bar(theta, count, width=width)
r_values = []
colors = []
for r,bar in zip(count, bars):
r_values.append(r/float(max(count)))
colors.append(cm.jet(r_values[-1], alpha=0.5))
bar.set_facecolor(colors[-1])
bar.set_edgecolor('grey')
bar.set_alpha(0.5)
# Add colorbar, make sure to specify tick locations to match desired ticklabels
colorlist = []
r_values.sort()
values = []
for val in r_values:
if val not in values:
values.append(val*float(max(count)))
color = cm.jet(val, alpha=0.5)
if color not in colorlist:
colorlist.append(color)
cpt = mpl.colors.ListedColormap(colorlist)
bounds = range(max(count)+1)
norm = mpl.colors.BoundaryNorm(values, cpt.N-1)
cax = fig.add_axes([0.97, 0.3, 0.03, 0.4])
cb = mpl.colorbar.ColorbarBase(cax, cmap=cpt,
norm=norm,
boundaries=bounds,
# Make the length of each extension
# the same as the length of the
# interior colors:
extendfrac='auto',
ticks=[bounds[i] for i in range(0, len(bounds), 2)],
#ticks=bounds,
spacing='uniform')
and here is the resulting plot:
As you can see, the colorbar is not quite right. If you look closely, between 16 and 17, there is a color missing (darker orange) and according to the colorbar the yellows reach a value of 15, which is not true in the rose diagram (or the data).
I have played around with the code so much and I just can't figure out how to normalize the colorbar correctly.
The easiest way is to use a PatchCollection and pass in your "z" (i.e. the values you want to color by) as the array kwarg.
As a simple example:
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
import numpy as np
def main():
fig = plt.figure()
ax = fig.add_subplot(111, projection='polar')
x = np.radians(np.arange(0, 360, 10))
y = np.random.random(x.size)
z = np.random.random(y.size)
cmap = plt.get_cmap('cool')
coll = colored_bar(x, y, z, ax=ax, width=np.radians(10), cmap=cmap)
fig.colorbar(coll)
ax.set_yticks([0.5, 1.0])
plt.show()
def colored_bar(left, height, z=None, width=0.8, bottom=0, ax=None, **kwargs):
if ax is None:
ax = plt.gca()
width = itertools.cycle(np.atleast_1d(width))
bottom = itertools.cycle(np.atleast_1d(bottom))
rects = []
for x, y, w, h in zip(left, bottom, width, height):
rects.append(Rectangle((x,y), w, h))
coll = PatchCollection(rects, array=z, **kwargs)
ax.add_collection(coll)
ax.autoscale()
return coll
if __name__ == '__main__':
main()
If you want a discrete color map, it's easiest to just specify the number of intervals you'd like when you call plt.get_cmap. For example, in the code above, if you replace the line cmap = plt.get_cmap('cool') with:
cmap = plt.get_cmap('cool', 5)
Then you'll get a discrete colormap with 5 intervals. (Alternately, you could pass in the ListedColormap that you created in your example.)
If you want a "full-featured" rose diagram function, you might do something like this:
import itertools
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
from matplotlib.collections import PatchCollection
import numpy as np
def main():
azi = np.random.normal(20, 30, 100)
z = np.cos(np.radians(azi + 45))
plt.figure(figsize=(5,6))
plt.subplot(111, projection='polar')
coll = rose(azi, z=z, bidirectional=True)
plt.xticks(np.radians(range(0, 360, 45)),
['N', 'NE', 'E', 'SE', 'S', 'SW', 'W', 'NW'])
plt.colorbar(coll, orientation='horizontal')
plt.xlabel('A rose diagram colored by a second variable')
plt.rgrids(range(5, 20, 5), angle=290)
plt.show()
def rose(azimuths, z=None, ax=None, bins=30, bidirectional=False,
color_by=np.mean, **kwargs):
"""Create a "rose" diagram (a.k.a. circular histogram).
Parameters:
-----------
azimuths: sequence of numbers
The observed azimuths in degrees.
z: sequence of numbers (optional)
A second, co-located variable to color the plotted rectangles by.
ax: a matplotlib Axes (optional)
The axes to plot on. Defaults to the current axes.
bins: int or sequence of numbers (optional)
The number of bins or a sequence of bin edges to use.
bidirectional: boolean (optional)
Whether or not to treat the observed azimuths as bi-directional
measurements (i.e. if True, 0 and 180 are identical).
color_by: function or string (optional)
A function to reduce the binned z values with. Alternately, if the
string "count" is passed in, the displayed bars will be colored by
their y-value (the number of azimuths measurements in that bin).
Additional keyword arguments are passed on to PatchCollection.
Returns:
--------
A matplotlib PatchCollection
"""
azimuths = np.asanyarray(azimuths)
if color_by == 'count':
z = np.ones_like(azimuths)
color_by = np.sum
if ax is None:
ax = plt.gca()
ax.set_theta_direction(-1)
ax.set_theta_offset(np.radians(90))
if bidirectional:
other = azimuths + 180
azimuths = np.concatenate([azimuths, other])
if z is not None:
z = np.concatenate([z, z])
# Convert to 0-360, in case negative or >360 azimuths are passed in.
azimuths[azimuths > 360] -= 360
azimuths[azimuths < 0] += 360
counts, edges = np.histogram(azimuths, range=[0, 360], bins=bins)
if z is not None:
idx = np.digitize(azimuths, edges)
z = np.array([color_by(z[idx == i]) for i in range(1, idx.max() + 1)])
z = np.ma.masked_invalid(z)
edges = np.radians(edges)
coll = colored_bar(edges[:-1], counts, z=z, width=np.diff(edges),
ax=ax, **kwargs)
return coll
def colored_bar(left, height, z=None, width=0.8, bottom=0, ax=None, **kwargs):
"""A bar plot colored by a scalar sequence."""
if ax is None:
ax = plt.gca()
width = itertools.cycle(np.atleast_1d(width))
bottom = itertools.cycle(np.atleast_1d(bottom))
rects = []
for x, y, h, w in zip(left, bottom, height, width):
rects.append(Rectangle((x,y), w, h))
coll = PatchCollection(rects, array=z, **kwargs)
ax.add_collection(coll)
ax.autoscale()
return coll
if __name__ == '__main__':
main()

Categories

Resources