I want to remove the last tick in a polar plot (the 2π). I've found a way for non-polar plots here, where it said:
yticks[-1].set_visible(False)
which results in:
AttributeError: 'PolarAxesSubplot' object has no attribute 'yticks'
I tried to write rticks instead of yticks but this produced the same error. I've attached an image at the end.
I'm looking for an equivalent method to remove the last tick entry like for a non-polar plot.
import numpy as np
import matplotlib.pyplot as plt
def multiple_formatter(denominator=2, number=np.pi, latex='\pi'):
# produces pi in the axis labels
# https://stackoverflow.com/a/53586826
def gcd(a, b):
while b:
a, b = b, a%b
return a
def _multiple_formatter(x, pos):
den = denominator
num = np.int(np.rint(den*x/number))
com = gcd(num,den)
(num,den) = (int(num/com),int(den/com))
if den==1:
if num==0:
return r'$0$'
if num==1:
return r'$%s$'%latex
elif num==-1:
return r'$-%s$'%latex
else:
return r'$%s%s$'%(num,latex)
else:
if num==1:
return r'$\frac{%s}{%s}$'%(latex,den)
elif num==-1:
return r'$\frac{-%s}{%s}$'%(latex,den)
else:
return r'$\frac{%s%s}{%s}$'%(num,latex,den)
return _multiple_formatter
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2]) # Less radial ticks
ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
ax.grid(True)
ax.set_title("A line plot on a polar axis", va='bottom')
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 4))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 12))
ax.xaxis.set_major_formatter(plt.FuncFormatter(multiple_formatter(4)))
plt.show()
The pi labeling comes from here.
Result:
If you change
ax.xaxis.set_major_locator(plt.MultipleLocator(np.pi / 4))
to
ax.xaxis.set_major_locator(plt.FixedLocator(np.arange(0,2*np.pi,np.pi/4)))
It should meet your requirements.
Full code:
import numpy as np
import matplotlib.pyplot as plt
def multiple_formatter(denominator=2, number=np.pi, latex='\pi'):
# produces pi in the axis labels
# https://stackoverflow.com/a/53586826
def gcd(a, b):
while b:
a, b = b, a%b
return a
def _multiple_formatter(x, pos):
den = denominator
num = np.int(np.rint(den*x/number))
com = gcd(num,den)
(num,den) = (int(num/com),int(den/com))
if den==1:
if num==0:
return r'$0$'
if num==1:
return r'$%s$'%latex
elif num==-1:
return r'$-%s$'%latex
else:
return r'$%s%s$'%(num,latex)
else:
if num==1:
return r'$\frac{%s}{%s}$'%(latex,den)
elif num==-1:
return r'$\frac{-%s}{%s}$'%(latex,den)
else:
return r'$\frac{%s%s}{%s}$'%(num,latex,den)
return _multiple_formatter
r = np.arange(0, 2, 0.01)
theta = 2 * np.pi * r
ax = plt.subplot(111, projection='polar')
ax.plot(theta, r)
ax.set_rticks([0.5, 1, 1.5, 2]) # Less radial ticks
ax.set_rlabel_position(-22.5) # Move radial labels away from plotted line
ax.grid(True)
ax.set_title("A line plot on a polar axis", va='bottom')
ax.xaxis.set_major_locator(plt.FixedLocator(np.arange(0,2*np.pi,np.pi/4)))
ax.xaxis.set_minor_locator(plt.MultipleLocator(np.pi / 12))
ax.xaxis.set_major_formatter(plt.FuncFormatter(multiple_formatter(4)))
plt.show()
Related
I'm plotting N plots in a grid without any decoration. The plots that have data are easily set to not show the axis by using axis('off'). But how can i do this for the "spillover" defaults from subplots()?
Here is a working example of the code in question:
import math
filenames = ['plot0', 'plot1', 'plot2', 'plot4', 'plot5', 'plot6', 'plot7', 'plot8', 'plot9', 'plotl0']
N = len(filenames)
Y = int(math.sqrt(N))
X = int(N / Y) + 1
fig, ax = plt.subplots(X, Y)
for i in range(N):
ax[divmod(i, Y)].text(0.5, 0.5, str('Test'))
ax[divmod(i, Y)].axis('off')
plt.show()
Use 'add_subplot()' to add 'subplot' to the 'fig' object with the required graph.
import matplotlib.pyplot as plt
import math
filenames = ['plot0', 'plot1', 'plot2', 'plot4', 'plot5', 'plot6', 'plot7', 'plot8', 'plot9', 'plotl0']
N = len(filenames)
Y = int(math.sqrt(N))
X = int(N / Y) + 1
fig = plt.figure()
for i in range(1,N+1):
axes.append(fig.add_subplot(X, Y, i))
axes[i-1].text(0.5, 0.5, str('Test'))
axes[i-1].axis('off')
plt.show()
I want to annotate a horizontal bar chart I created with Pandas with '⭑' characters. The number of stars per bar is determined by a rating system that ranges from zero to five stars and contains half-star ratings. My issue is that there is no 1/2 star text character, so I have to use images which contain half-stars to properly annotate the bars.
Here is the code to create a sample from the DataFrame I am working from:
df = pd.DataFrame(index=range(7),
data={'Scores': [79.0, 79.5, 81.8, 76.1, 72.8, 87.6, 79.3]})
df['Stars'] = df['Scores'].apply(real_stars)
And here is the function that is determining the star ratings:
def real_stars(x):
if x >=88:
return ('★★★★★')
elif x >=83:
return ('★★★★¹/₂')
elif x >=79:
return ('★★★★')
elif x >=75:
return ('★★★¹/₂')
elif x >=71:
return ('★★★')
elif x >=67:
return ('★★¹/₂')
elif x >=63:
return ('★★')
elif x >=59:
return ('★¹/₂')
elif x >=55:
return ('★')
elif x >=50:
return ('¹/₂★')
else:
return None
And this is the code I use to plot the bar chart and annotate the right side of each bar with a star rating:
fig = plt.figure(figsize=(7.5,5))
ax = plt.subplot()
plt.box(on=None)
df.plot.barh(ax=ax, width=.75, legend=False)
for i, p in zip(df['Stars'], ax.patches):
width, height = p.get_width(), p.get_height()
x, y = p.get_xy()
ax.annotate(i, (p.get_x()+1*width, p.get_y()+.45*height), fontsize=25,
fontweight='bold', color='white', ha='right', va='center')
I would like to annotate the bars the exact same way, but instead of expressing a half star rating as '1/2', I would instead like to include an image of a half star. I think the first step would be to incorporate the images in the real_stars function, display the images in the df['Stars'] column and then use the column for annotation.
Examples of images I would like to use:
.5 star
1 star
Half star characters were added into unicode version 11, for example:
import matplotlib.pyplot as plt
import matplotlib.font_manager as mfm
font_path = '<PATH>/Symbola.ttf'
prop = mfm.FontProperties(fname=font_path) # find this font
# Some examples of stars
uni_char = u"\u2605\U0001F7CA\u2BE8\u2BEA"
plt.annotate(uni_char, (0.5, 0.5), fontproperties=prop, fontsize=20)
plt.show()
Please see this, and note that you have to use fonts that support the newer standard in this list. The example above uses Symbola
To annotate bars with shapes one may use an AnchoredOffsetbox, which contains a DrawingArea. The shapes inside the DrawingArea are defined in display space, relative to the Offsetbox.
import numpy as np
import matplotlib.markers
from matplotlib.path import Path
from matplotlib.patches import PathPatch
from matplotlib.offsetbox import AnchoredOffsetbox, DrawingArea
import matplotlib.pyplot as plt
class AnchoredDrawingArea(AnchoredOffsetbox):
def __init__(self, width, height, xdescent, ydescent,
loc, pad=0.4, borderpad=0.5, **kwargs):
self.da = DrawingArea(width, height, xdescent, ydescent)
super().__init__(loc, pad=pad, borderpad=borderpad,
child=self.da, **kwargs)
def get_star(pos=0, size=10, half=False, **kwargs):
marker = matplotlib.markers.MarkerStyle("*")
p = marker.get_path()
v = np.round(list(p.vertices[:]), 4)
c = np.array(list(p.codes[:]))
v *= size
v[:,0] += pos*2*size
if half:
v = np.delete(v, np.s_[6:-1],0)
c = np.delete(c, np.s_[6:-1],0)
return PathPatch(Path(v, c), **kwargs)
def draw_star(x,y,s, ax, size=10, **kwargs):
# https://matplotlib.org/gallery/misc/anchored_artists.html
ada = AnchoredDrawingArea(np.ceil(s)*2*size-size, size, size/2, size/2, loc="lower center",
bbox_to_anchor=(x,y,0,0), bbox_transform=ax.transData,
frameon=False)
for i in range(int(s)):
star = get_star(i, size=size, **kwargs)
ada.da.add_artist(star)
h = s - int(s)
if h > 0:
star = get_star(int(s), size=size, half=True, **kwargs)
ada.da.add_artist(star)
return ada
def draw_stars(x, y, s, ax=None, size=10, **kwargs):
ax = ax or plt.gca()
for xi,yi,si in zip(x,y,s):
ada = draw_star(xi,yi,si, ax=ax, size=size, **kwargs)
ax.add_artist(ada)
x = np.arange(6)
y = np.array([4,5,3,4,2,4])
stars = np.array([4, 2.5, 1, 3.5, 4.5, 2])
fig, ax = plt.subplots()
ax.bar(x,y)
draw_stars(x,y,stars, ax=ax, size=5, linewidth=0.72,
facecolor="crimson", edgecolor="darkred")
ax.margins(y=0.1)
plt.show()
I want to pick (add) marker to the curve. The marker may change the position many times, however eventually I need to plot only the newest (updated) marker and remove the old.
Any ideas?
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)for i in range(10):
pt, = ax1.plot(t, s, picker=5)
def onpick(event):
if event.artist != pt:
return True
if not len(event.ind):
return True
ind = event.ind[0]
ax1.plot(t[ind], s[ind], '|r', markersize='20')
fig.canvas.draw()
return True
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
Instead of calling a new plot() and creating a new artist at every click, simply create an empty artist at initialization stage, and update its coordinates in onpick():
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots()
t = np.arange(0.0, 1.0, 0.01)
s = np.sin(2 * 2 * np.pi * t)
pt, = ax1.plot(t, s, picker=5)
mark, = ax1.plot([], [], '|r', markersize='20')
def onpick(event):
if event.artist != pt:
return True
if not len(event.ind):
return True
ind = event.ind[0]
mark.set_data(t[ind], s[ind])
fig.canvas.draw()
return True
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
EDIT: same principle using N curves and N markers
import matplotlib.pyplot as plt
import numpy as np
fig, ax1 = plt.subplots()
t = np.arange(0.0, 1.0, 0.01)
ss = [np.sin(2 * 2 * np.pi * t),
np.cos(3 * 2 * np.pi * t),
np.sin(0.5 * 2 * np.pi * t)]
cs = ['b','r', 'g']
ms = ['|','o','D']
lines = [ax1.plot(t,s,'-',color=c, picker=5)[0] for s,c in zip(ss,cs)]
markers = [ax1.plot([],[],lw=0, marker=m, ms=20, color=c)[0] for m,c in zip(ms,cs)]
def onpick(event):
point_idx = event.ind[0]
art_idx = None
for i,l in enumerate(lines):
if event.artist == l:
art_idx = i
break
if art_idx is not None:
markers[art_idx].set_data(t[point_idx], ss[art_idx][point_idx])
fig.canvas.draw()
return True
fig.canvas.mpl_connect('pick_event', onpick)
plt.show()
I am interested in math demonstrations. Currently I am working on visualizing numerical methods in python, in particular the bisection method. Below is the code I have written so far.
import matplotlib.pyplot as plt
import matplotlib.animation as animation
import numpy as np
def sgn(x):
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0
def bisect(f,a,b):
fa = f(a)
fb = f(b)
p = a+(b-a)/2
fp = f(p)
if sgn(fa) == sgn(fp):
return p, fp, b, fb
else:
return a, fa, p, fp
def f(x):
return x**2-3
a, b = 1, 2
plt.figure()
plt.subplot(111)
a, fa, b, fb = bisect(f,a,b)
vf = np.vectorize(f)
x = np.linspace(a,b)
y = vf(x)
plt.plot(x, y, color='blue')
plt.plot([a,a], [0,fa], color='red', linestyle="--")
plt.plot([b,b], [0,fb], color='red', linestyle="--")
plt.grid()
plt.show()
I have three problems I wish to solve. First, I want to be able to call the bisect function multiple times and each time I would like to redraw the plot with the new data. Second, I would like to restart the animation after applying the bisect function some specified number of times. Third, I would like to retain the original axes of the figure before the bisection method is called i.e. I would like to keep the x-range as [1,2] and the y-range as $[-2,1]$. Any help will be much appreciated.
I found a solution to my problems through much trial and error.
import matplotlib.pyplot as plt
from matplotlib import animation
import numpy as np
def sgn(x):
if x > 0:
return 1
elif x < 0:
return -1
else:
return 0
def bisect(f,a,b):
fa = f(a)
fb = f(b)
p = a+(b-a)/2
fp = f(p)
if sgn(fa) == sgn(fp):
return p, b
else:
return a, p
def bisection_method(f,a,b,n):
for i in range(n):
a,b = bisect(f,a,b)
return a,b
def f(x):
return x**2-3
xmin, xmax = 1, 2
yrange = f(xmin), f(xmax)
ymin, ymax = min(yrange), max(yrange)
vf = np.vectorize(f)
x = np.linspace(xmin,xmax)
y = vf(x)
epsilon = 0.1
# Initialize figure
fig = plt.figure()
ax = plt.axes(xlim=(xmin-epsilon,xmax+epsilon), ylim=(ymin,ymax))
curve, = ax.plot([],[], color='blue')
left, = ax.plot([],[],color='red')
right, = ax.plot([],[],color='red')
# Figure reset between frames
def init():
left.set_data([],[])
right.set_data([],[])
curve.set_data([],[])
return left, right, curve,
# Animation of bisection
def animate(i):
a, b = bisection_method(f,xmin,xmax,i)
left.set_data([a,a],[ymin,ymax])
right.set_data([b,b],[ymin,ymax])
curve.set_data(x,y)
return left, right, curve,
anim = animation.FuncAnimation(fig, animate, init_func=init, frames=15, interval=700, blit=True)
plt.grid()
plt.show()
You can simply change your code to:
plt.plot([a,a], [0,fa], color='red', linestyle="--",hold=TRUE) which would basically allow you to plot multiple points without resetting the plot and once you have plotted a number of times you can reset using hold=FALSE. Hope this makes sense.
I was wondering if it is possible to plot a curve in matplotlib with arrow ticks.
Something like:
from pylab import *
y = linspace(0,10,0.01)
x = cos(y)
plot(x, y, '->')
which should come out with a curve made like this --->---->----> when x increases and like this ---<----<----< whenit decreases (and for y as well, of course).
EDIT:
Furthermore, the arrows should be inclined in the curve's direction (for example, 45 degrees for the y=x function)
It is possible to use the same strategy as in matplotlib streamplot function. Based on the example already given by hitzg:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.lines as mlines
import matplotlib.patches as mpatches
def add_arrow_to_line2D(
axes, line, arrow_locs=[0.2, 0.4, 0.6, 0.8],
arrowstyle='-|>', arrowsize=1, transform=None):
"""
Add arrows to a matplotlib.lines.Line2D at selected locations.
Parameters:
-----------
axes:
line: Line2D object as returned by plot command
arrow_locs: list of locations where to insert arrows, % of total length
arrowstyle: style of the arrow
arrowsize: size of the arrow
transform: a matplotlib transform instance, default to data coordinates
Returns:
--------
arrows: list of arrows
"""
if not isinstance(line, mlines.Line2D):
raise ValueError("expected a matplotlib.lines.Line2D object")
x, y = line.get_xdata(), line.get_ydata()
arrow_kw = {
"arrowstyle": arrowstyle,
"mutation_scale": 10 * arrowsize,
}
color = line.get_color()
use_multicolor_lines = isinstance(color, np.ndarray)
if use_multicolor_lines:
raise NotImplementedError("multicolor lines not supported")
else:
arrow_kw['color'] = color
linewidth = line.get_linewidth()
if isinstance(linewidth, np.ndarray):
raise NotImplementedError("multiwidth lines not supported")
else:
arrow_kw['linewidth'] = linewidth
if transform is None:
transform = axes.transData
arrows = []
for loc in arrow_locs:
s = np.cumsum(np.sqrt(np.diff(x) ** 2 + np.diff(y) ** 2))
n = np.searchsorted(s, s[-1] * loc)
arrow_tail = (x[n], y[n])
arrow_head = (np.mean(x[n:n + 2]), np.mean(y[n:n + 2]))
p = mpatches.FancyArrowPatch(
arrow_tail, arrow_head, transform=transform,
**arrow_kw)
axes.add_patch(p)
arrows.append(p)
return arrows
y = np.linspace(0, 100, 200)
x = np.cos(y/5.)
fig, ax = plt.subplots(1, 1)
# print the line and the markers in seperate steps
line, = ax.plot(x, y, 'k-')
add_arrow_to_line2D(ax, line, arrow_locs=np.linspace(0., 1., 200),
arrowstyle='->')
plt.show()
Also refer to this answer.
Try this:
import numpy as np
import matplotlib.pyplot as plt
y = np.linspace(0,100,100)
x = np.cos(y/5.)
# use masked arrays
x1 = np.ma.masked_array(x[:-1], np.diff(x)>=0)
x2 = np.ma.masked_array(x[:-1], np.diff(x)<=0)
# print the line and the markers in seperate steps
plt.plot(x, y, 'k-')
plt.plot(x1, y[:-1], 'k<')
plt.plot(x2, y[:-1], 'k>')
plt.show()