I am trying to plot part of an matplotlib.patches.FancyArrowPatch in a dashed style. Using this post pyplot: Dotted line with FancyArrowPatch, I managed to get quite close to it :
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
plt.figure()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k', connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
plt.gca().add_patch(arrow)
points = np.array([i[0] for i in arrow.get_path().iter_segments(curves = False)])
# arrow.remove()
a, = plt.plot(points[:-3,0], points[:-3,1])
plt.plot(points[-4:,0], points[-4:,1], linestyle = '--', color = a.get_color())
plt.tight_layout()
plt.show()
To my understanding, the blue line does not match the black one because iter_segments() converts curves into straight lines with a density of point too low.
How should I do to get a better result ?
You can evaluate the Bezier curve that is produced by the arrow manually.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.9" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
verts = arrow.get_path().vertices
curve1 = bezier(verts, t=[0.0, 0.5], num=100)
curve2 = bezier(verts, t=[0.5, 1.0], num=100)
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson")
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson")
plt.show()
As you notice the two curves, i.e. the original arrow and the manually created bezier curve, are not overlaying each other. This is because matplotlib evaluates the Bezier curve in screen space, while the manual version evaluates it in data space.
To obtain the same curve in both cases, we would need to do the evaluation in screen space, which is shown in the following (where we also plot the three bezier nodes, both in data and in pixel space).
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from scipy.special import binom
fig, ax = plt.subplots()
kw = dict(arrowstyle = '-', shrinkA = 0, shrinkB = 0, color = 'k',
connectionstyle = "arc3, rad = -0.4" )
arrow = mpatches.FancyArrowPatch((0, 0), (5, 3), **kw)
ax.add_patch(arrow)
ax.autoscale()
print(arrow.get_path().vertices)
bernstein = lambda n, k, t: binom(n,k)* t**k * (1.-t)**(n-k)
def bezier(points, t=[0,1], num=200):
N = len(points)
t = np.linspace(*t, num=num)
curve = np.zeros((num, 2))
for i in range(N):
curve += np.outer(bernstein(N - 1, i, t), points[i])
return curve
trans = ax.transData
trans_inv = trans.inverted()
verts = trans.transform(arrow.get_path().vertices)
curve1 = trans_inv.transform(bezier(verts, t=[0.0, 0.5], num=100))
curve2 = trans_inv.transform(bezier(verts, t=[0.5, 1.0], num=100))
ax.plot(curve1[:,0], curve1[:,1], lw=3, color="crimson", zorder=0)
ax.plot(curve2[:,0], curve2[:,1], lw=3, ls="--", color="crimson", zorder=0)
from matplotlib.transforms import IdentityTransform
ax.plot(*trans.transform(arrow.get_path().vertices).T, ls="", marker="o",
color="C1", ms=7, transform=IdentityTransform())
ax.plot(*arrow.get_path().vertices.T, ls="", marker="o", color="C0", ms=3)
plt.show()
Related
I'm trying to Add the slider in the plot similar to the slider demo example.
I'm plotting fill_between which gives PolyCollection object.
Although I tried with plot too which give Line2D object as shown picture below, but plot doesn't update as expected as in demo.
code
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
# t = plt.fill_between(*get_pdf(2, 1), alpha=0.7) # this gives ployCollection
t = ax.plot(*get_pdf(2, 1), label='treatment', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
x, y = get_pdf(val)
t[0].set_ydata(y)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
To update the line plot, t[0].set_xdata(x) needs to be set, as it is different for each call. In this particular case, get_pdf each time returns the same y.
Updating the coordinates of the polyCollection generated by fill_between doesn't seem to be possible. However, you can delete and recreate it at every update. Note that this is slower than just updating the coordinates.
import numpy as np
import scipy.stats as ss
import matplotlib.pyplot as plt
import matplotlib.widgets as widgets
def get_pdf(mu, sigma=1, offset=4):
o = sigma * offset
x = np.linspace(mu - o, mu + o, 100)
rv = ss.norm(mu, sigma)
return x, rv.pdf(x)
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
ax.fill_between(*get_pdf(0, 1), alpha=0.7)
t = ax.fill_between(*get_pdf(2), color='crimson', alpha=0.7)
a = plt.axes([0.25, 0.1, 0.5, 0.03])
slider = widgets.Slider(a, "shift", 0, 10, valinit=2, valstep=1)
def update(val):
global t
t.remove()
t = ax.fill_between(*get_pdf(val), color='crimson', alpha=0.7)
fig.canvas.draw_idle()
slider.on_changed(update)
plt.show()
I have an array of shape(512,512).
Looks like, (row=x, column=y, density=z=the number of the array)
[[0.012825 0.020408 0.022976 ... 0.015938 0.02165 0.024357]
[0.036332 0.031904 0.025462 ... 0.031095 0.019812 0.024523]
[0.015831 0.027392 0.031939 ... 0.016249 0.01697 0.028686]
...
[0.024545 0.011895 0.022235 ... 0.033226 0.03223 0.030235]]
I had already drawn it into a 2D density plot. My goal is to find the center of the circle and draw a vertical and horizontal cross-section in one figure.
Now, I have the trouble to find the center of the circle and combine two cross-sections in one figure.
Please help.
This is my code:
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import scipy.ndimage
data = pd.read_csv('D:/BFP.csv', header=None)
# create data
data = np.array(data)
print(data)
#plot data
side = np.linspace(-1.5,1.5,512)
x,y = np.meshgrid(side,side)
z = [[data[i][j] for i in range(len(data[0]))]for j in range(len(data))]
#-- Extract the line...
# Make a line with "num" points...
x0, y0 = 270, 0 # These are in _pixel_ coordinates!!
x1, y1 = 270, 500
num = 512
x_, y_ = np.linspace(x0, x1, num), np.linspace(y0, y1, num)
# Extract the values along the line, using cubic interpolation
zi = scipy.ndimage.map_coordinates(z, np.vstack((x_,y_)))
#-- Plot...
fig, axes = plt.subplots(nrows=2)
axes[0].imshow(z,origin='lower')
axes[0].plot([x0, x1], [y0, y1], 'ro-')
#axes[0].axis('image')
axes[1].plot(zi)
plt.savefig('D:/vertical.png')
plt.show()
image here:
I cannot help you with finding the center of the circle, but you can create a nice visualization of the cross section by creating 3 axes in a grid. Usually, I would use GridSpec for this, but imhsow has a tendency to mess up the relative size of the axes to maintain square pixels. Thankfully, the AxesGrid toolkit can help.
The base of the code is inspired by this matplotlib example.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
cur_x = 110
cur_y = 40
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(cur_x, color='r')
h_line = main_ax.axhline(cur_y, color='g')
v_prof, = right_ax.plot(z[:,int(cur_x)],np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),z[int(cur_y),:], 'g-')
plt.show()
Just for fun, you can even make it interactive
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
from scipy.stats import multivariate_normal
import scipy
fig, main_ax = plt.subplots(figsize=(5, 5))
divider = make_axes_locatable(main_ax)
top_ax = divider.append_axes("top", 1.05, pad=0.1, sharex=main_ax)
right_ax = divider.append_axes("right", 1.05, pad=0.1, sharey=main_ax)
# make some labels invisible
top_ax.xaxis.set_tick_params(labelbottom=False)
right_ax.yaxis.set_tick_params(labelleft=False)
main_ax.set_xlabel('dim 1')
main_ax.set_ylabel('dim 2')
top_ax.set_ylabel('Z profile')
right_ax.set_xlabel('Z profile')
x, y = np.mgrid[-1:1:.01, -1:1:.01]
pos = np.empty(x.shape + (2,))
pos[:, :, 0] = x; pos[:, :, 1] = y
rv = multivariate_normal([-0.2, 0.2], [[1, 1.5], [0.25, 0.25]])
z = rv.pdf(pos)
z_max = z.max()
main_ax.imshow(z, origin='lower')
main_ax.autoscale(enable=False)
right_ax.autoscale(enable=False)
top_ax.autoscale(enable=False)
right_ax.set_xlim(right=z_max)
top_ax.set_ylim(top=z_max)
v_line = main_ax.axvline(np.nan, color='r')
h_line = main_ax.axhline(np.nan, color='g')
v_prof, = right_ax.plot(np.zeros(x.shape[1]),np.arange(x.shape[1]), 'r-')
h_prof, = top_ax.plot(np.arange(x.shape[0]),np.zeros(x.shape[0]), 'g-')
def on_move(event):
if event.inaxes is main_ax:
cur_x = event.xdata
cur_y = event.ydata
v_line.set_xdata([cur_x,cur_x])
h_line.set_ydata([cur_y,cur_y])
v_prof.set_xdata(z[:,int(cur_x)])
h_prof.set_ydata(z[int(cur_y),:])
fig.canvas.draw_idle()
fig.canvas.mpl_connect('motion_notify_event', on_move)
plt.show()
NB: the lag is just due to the convertion in gif, the update is much smoother on my machine
When I use fill_between The colored patches are slightly angled vertically so there is white space at the top of the y axis, whereas the colors are nicely merged at the bottom of the yaxis. Anyone know how to prevent this/understand what is causing this?
The plot is showing a 'weather window': when weather parameters are below a certain threshold the time period is 'operational' and at other times it is 'non operational'. The code to generate this plot is:
figure = plt.figure(figsize=(8, 3 * 3))
gs = gridspec.GridSpec(3, 1)
gs.update(hspace=0.3)
ax0 = plt.subplot(gs[0])
df1.plot() # pandas DataSeries
ax0.set_xlabel('')
ax1 = plt.subplot(gs[1])
df2.plot() # pandas DataSeries
ax1.set_xlabel('')
ax2 = plt.subplot(gs[2])
trans = mtransforms.blended_transform_factory(ax2.transData, ax2.transAxes)
ax2.plot(xtime, y, color = 'green', alpha = 0.5, lw = 0.01)
ax2.set_xlim(xtime[0], xtime[-1])
ax2.fill_between(xtime2, 0, 1, where = yop > 0, facecolor = 'green', alpha = 0.5, interpolate = True, transform = trans)
# yop is numpy array of 0's and 1's
ax2.fill_between(xtime2, 0, 1, where = ynonop > 0, facecolor = 'red', alpha = 0.5, interpolate = True, transform = trans)
# ynonop has 0's and 1's opposite to yop
The interpolate = True plays some role is removing the white spaces between points.
Here is simpler code to test the issue:
import matplotlib.pyplot as plt
import numpy as np
fig, ax = plt.subplots()
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
ynonop = np.copy(yop)
# make 0's and 1's opposite to yop
ynonop[ynonop == 1] = 2
ynonop[ynonop == 0] = 1
ynonop[ynonop == 2] = 0
import matplotlib.transforms as mtransforms
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
ax.set_xlim(x[0], x[-1])
ax.fill_between(x, 0, 1, where=yop > 0, facecolor='green', alpha=0.5, interpolate = True, transform=trans)
ax.fill_between(x, 0, 1, where=ynonop > theta, facecolor='red', alpha=0.5, interpolate = True, transform=trans)
plt.show()
# plt.savefig('test.png', bbox_inches = 0)
To understand what is causing the white stripes, you may zoom into the plot.
Because fill_between fills between points that fulfil a certain condition, you get a sawtooth-like shape.
A possible solution might be to use a broken_barh plot. To this end one would need to rearange the data into a 2columns format of (position, width).
import matplotlib.pyplot as plt
import numpy as np
fig, (ax,ax2) = plt.subplots(nrows=2, sharex=True, sharey=True)
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
ynonop = np.copy(yop)
# make 0's and 1's opposite to yop
ynonop[ynonop == 1] = 2
ynonop[ynonop == 0] = 1
ynonop[ynonop == 2] = 0
trans = ax.get_xaxis_transform()
ax.set_xlim(x[0], x[-1])
ax.fill_between(x, 0, 1, where=yop > 0, facecolor='green',
alpha=0.5, interpolate = True, transform=trans)
ax.fill_between(x, 0, 1, where=ynonop > 0, facecolor='red',
alpha=0.5, interpolate = True, transform=trans)
trans2 = ax2.get_xaxis_transform()
xra = np.c_[x[:-1],np.diff(x)]
ax2.broken_barh(xra[yop[:-1] > 0,:], (0,1),
facecolors='green', alpha=0.5, transform=trans2)
ax2.broken_barh(xra[ynonop[:-1] > 0,:], (0,1),
facecolors='red', alpha=0.5, transform=trans2)
ax.set_title("fill_between")
ax2.set_title("broken_barh")
plt.show()
You can also do this using imshow
import matplotlib.pyplot as plt
import numpy as np
import matplotlib.colors as mcolors
import matplotlib.transforms as mtransforms
fig, ax = plt.subplots()
x = np.arange(0.0, 365, 1)
yop = np.random.randint(2, size=len(x))
trans = mtransforms.blended_transform_factory(ax.transData, ax.transAxes)
ax.set_xlim(x[0], x[-1])
lc = mcolors.ListedColormap(['r', 'g'], name='RWG')
ax.imshow(yop.reshape(1, -1),
extent=[0, len(yop), 0, 1],
transform=trans,
cmap=lc,
norm=mcolors.NoNorm(), alpha=.5)
ax.set_aspect('auto')
# debugging plotting
ax.step(x, yop, '.', where='post', linestyle='none')
ax.set_ylim([-.1, 1.1])
plt.show()
By tweaking x values in extent you can control exactly where the pixels fall in dataspace.
Code is:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
example_data = np.random.randint(4, size=(40,44))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
img = plt.imshow(example_data, interpolation = 'nearest', origin = 'lower',
cmap = cmap, norm = norm)
Which gets me roughly what I want. What I am looking for is if there is a way to get the shape of each tile to be hexagonal rather than square? I think imshow might not be the way to do it but if there is a way you can change the default tile it would be good.
Thanks.
Here is a solution using patches:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
import matplotlib.patches as mpatches
from matplotlib.collections import PatchCollection
nx = 40
ny = 44
example_data = np.random.randint(4, size=(nx,ny))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
dx = np.diff(x)[0]
dy = np.diff(y)[0]
ds = np.sqrt(dx**2 + dy**2)
patches = []
for i in x:
for n, j in enumerate(y):
if n%2:
polygon = mpatches.RegularPolygon([i-dx/2., j], 6, 0.6*dx)
else:
polygon = mpatches.RegularPolygon([i, j], 6, 0.6*dx)
patches.append(polygon)
collection = PatchCollection(patches, cmap=cmap, norm=norm, alpha=1.0)
fig, ax = plt.subplots(1,1)
ax.add_collection(collection)
collection.set_array(example_data.ravel())
plt.show()
which looks like this,
Previous solution, it doesn't tessellate nicely and the hexagons are poorly shaped but you could use a scatter plot with coloured hexagons,
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import colors
nx = 40
ny = 44
example_data = np.random.randint(4, size=(nx,ny))
cmap = colors.ListedColormap(['black', 'green', 'red', 'blue'])
bounds = [0,1,2,3,4]
norm = colors.BoundaryNorm(bounds, cmap.N)
x = np.linspace(0, 1, nx)
y = np.linspace(0, 1, ny)
X, Y = np.meshgrid(x, y)
img = plt.scatter(X.ravel(),Y.ravel(),c=example_data.ravel(), cmap=cmap, norm=norm, s=360, marker=(6, 0), alpha=0.4)
plt.colorbar(img)
plt.show()
which looks like,
is there a way i can decorate my polar/radial plots with 'notches' at locations r=0 or r=outer_edge? below is an example. By 'notches' i mean the red and blue lines at the origin and at the edge of the plot.
The code below can be used to generate a polar plot.
import numpy as np
import matplotlib.pyplot as plt
data = [-121,87,118,87,109,-139,-112,115,153,-109,-106,-92,75,-98,103,-89,
152,114,77,-109,77,107,-77,106,-158,-71,-166,97,144,-166,138,39,130,
-71,-76,-82,128,74,-47,94,-119,130,76,-86,-85,108,-78,-96,-113,82,
127,-168,72,83,-61,-99,-83,-130,-69,43]
r = np.arange(0, len(data))
ax = plt.subplot(111,polar=True)
ax.scatter(data,r)
plt.show()
You could use annotate:
plt.annotate(' ',xy=(0, 0), # theta, radius
xytext = (-np.pi/10,len(data)/6.),
textcoords='data',
arrowprops=dict(facecolor='red', shrink=0.05))
plt.annotate(' ',xy=(np.pi/4.2, 1.355*max(r)), # theta, radius
xytext = (np.pi/4.2, 1.2*max(r)),
textcoords='data',
arrowprops=dict(facecolor='blue', shrink=0.05))
Or you can use plot:
rmax = ax.get_rmax()
theta_startstop = [2*[-np.pi/10],2*[np.pi/4.2]]
r_startstop = [[0,0.1*rmax],[0.9*rmax,rmax]]
notchcolor = ['red', 'blue']
for i in range(len(r_startstop)):
ax.plot(np.array(theta_startstop[i]), np.array(r_startstop[i]),
lw=3, c=notchcolor[i])
ax.set_rmax(rmax)
The result will be: