Is there any way to create a 'wavy' arrow in matplotlib / python please?
Ideally, I'd like to recreate something like the following:
To reproduce the wavy arrow from the question, you may use a line plot and a triangle
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
fig, ax = plt.subplots()
x = np.linspace(0,9*np.pi,151)
y = np.sin(x)
ax.plot(x,y, color="gray", lw="3")
verts = np.array([[0,1],[0,-1],[2,0],[0,1]]).astype(float)*1.3
verts[:,0] += 9*np.pi
path = mpath.Path(verts)
patch = mpatches.PathPatch(path, fc='gray', ec="gray")
ax.add_patch(patch)
ax.axis("off")
ax.set_aspect("equal",'datalim')
ax.relim()
ax.autoscale_view()
plt.show()
I made a general version (function that returns a Patch for any given start and end coordinates) from the previous post snippet.
def curly_arrow(start, end, arr_size = 1, n = 5, col='gray', linew=1., width = 0.1):
xmin, ymin = start
xmax, ymax = end
dist = np.sqrt((xmin - xmax)**2 + (ymin - ymax)**2)
n0 = dist / (2 * np.pi)
x = np.linspace(0, dist, 151) + xmin
y = width * np.sin(n * x / n0) + ymin
line = plt.Line2D(x,y, color=col, lw=linew)
del_x = xmax - xmin
del_y = ymax - ymin
ang = np.arctan2(del_y, del_x)
line.set_transform(mpl.transforms.Affine2D().rotate_around(xmin, ymin, ang) + ax.transData)
ax.add_line(line)
verts = np.array([[0,1],[0,-1],[2,0],[0,1]]).astype(float) * arr_size
verts[:,1] += ymax
verts[:,0] += xmax
path = mpath.Path(verts)
patch = mpatches.PathPatch(path, fc=col, ec=col)
patch.set_transform(mpl.transforms.Affine2D().rotate_around(xmax, ymax, ang) + ax.transData)
return patch
arr_size - size of the arrow, linew - linewidth of the arrow, n - number of wiggles, width - "vertical" (latitudinal) size of the wiggles.
Example of usage:
fig, ax = plt.subplots()
ax.add_patch(curly_arrow((20, 20), (2, 10), n=10, arr_size=2))
ax.set_xlim(0,30)
ax.set_ylim(0,30)
PS. You'll also need to import:
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.path as mpath
import matplotlib.patches as mpatches
import matplotlib as mpl
Related
I would like to adjust the bar value text position below each bar top with barwidth/5 offset.
text_y -= bar.get_width()/5 # <- not work
Full code:
#!/usr/bin/env python3
import numpy as np
import matplotlib
import matplotlib.pyplot as plt
import pandas as pd
import matplotlib.patheffects as PathEffects
import math
import numpy as np
def save_fig(fig,pngname):
fig.savefig(pngname, dpi=fig.dpi, bbox_inches="tight")
print("[[%s]]"%pngname)
return
def plot_bar(df):
xname = df.columns[0]
fig, ax = plt.subplots(figsize=(10, 5))
x = np.arange(len(df[xname]))
n = len(df.columns[1:])
bar_width = 0.95/n
fontsize = 20
colors = ['#5891ad','#004561','#ff6f31','#1c7685','#0f45a8','#4cdc8b','#0097a7']
dy = -bar_width/10
bars = []
# add bars
for i,colname in enumerate(df.columns[1:]):
bar = ax.bar(x+i*bar_width, df[colname], width=bar_width,color=colors[i])
bars.append(bar)
# add text on bars
for bar in ax.patches:
bar_value = bar.get_height()
text = f'{bar_value:,}'
text_x = bar.get_x() + bar.get_width() / 2
text_y = bar.get_y() + bar_value
text_y -= bar.get_width()/5 # <- not work
bar_color = bar.get_facecolor()
t = ax.text(text_x, text_y, text, ha='center', va='top', color=bar_color,
size=fontsize)
t.set_path_effects([PathEffects.withStroke(linewidth=bar_width*15, foreground='w')])
ax.set_xticks(x + 1/ 2 - bar_width/2)
ax.set_xticklabels(df[xname])
ax.legend()
ax.spines['top'].set_visible(False)
ax.spines['right'].set_visible(False)
#ax.spines['left'].set_visible(False)
#ax.spines['bottom'].set_color('#DDDDDD')
ax.tick_params(bottom=False, left=False)
ax.set_axisbelow(True)
ax.yaxis.grid(True, color='#EEEEEE')
ax.xaxis.grid(False)
ax.set_xlabel('x', labelpad=15)
ax.set_ylabel('y', labelpad=15)
ax.set_title('title', pad=15)
fig.tight_layout()
plt.show()
return
data = [['a',3,2,1],
['b',2,3,1],
['c',3,1,3],
['d',5,1,3],
]
df = pd.DataFrame(data,columns=['f1','f2','f3','f4'])
plot_bar(df)
I have the following plot
import numpy as np
import matplotlib.pyplot as plt
a = np.random.randn(4,4)
fig, ax = plt.subplots()
im = ax.imshow(a)
plt.axis('off')
plt.show()
output:
I am trying to add the following gridlines:
gridlines = np.array([
[0,2], [2,4],
[0,4],
[0,3], [3,4],
[0,1], [1,3],
])
such that the plot looks like this:
Finally, I want to add text in the center of each red rectangle:
Is there any documentation or tutorial for doing this with matplotlib?
Since you have define gridlines, it is easy to achieve that result by adding Rectangles and text annotations.
Note that in the following I have modified gridlines to be a dictionary, mapping each "row" to grids.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
gridlines = {
0: [[0,2], [2,4]],
1: [[0,4]],
2: [[0,3], [3,4]],
3: [[0,1], [1,3]],
}
a = np.random.randn(4,4)
fig, ax = plt.subplots()
im = ax.imshow(a)
plt.axis('off')
xmin, xmax = ax.get_xlim()
ymin, ymax = ax.get_ylim()
dx = (xmax - xmin) / 4
dy = (ymax - ymin) / 4
count = 1
for r, v in gridlines.items():
for (_xmin, _xmax) in v:
x = xmin + dx * _xmin
y = ymax - (r + 1) * dy
w = dx * (_xmax - _xmin)
ax.add_patch(Rectangle((x, y), w, dy, facecolor='none', edgecolor="r", linewidth=3))
ax.text(x + w / 2, ymax - (r + 1) * dy + dy / 2, "text%s" % count, ha="center", va="center", color="white")
count += 1
plt.show()
In order to create a 3d plot using plot_surface and wireframe I wrote this (looking here around)
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import rc
from matplotlib.ticker import MultipleLocator
import matplotlib.ticker as mticker
import numpy as np
from matplotlib.ticker import FormatStrFormatter
def log_tick_formatter(val, pos=None):
return f"10$^{{{int(val)}}}$"
data=np.genfromtxt('jpdfomegal2_90.dat')
x_len= len(np.unique(data[:, 0]))
y_len= len(np.unique(data[:, 1]))
X = data[:, 0].reshape(x_len, y_len)
Y = data[:, 1].reshape(x_len, y_len)
Z = data[:, 2].reshape(x_len, y_len)
#identify lowest non-negative Z value Zmin>0
Zmin = np.where(Z > 0, Z, np.inf).min()
Zmax = Z.max()
#and substitute zero with a slightly lower value than Zmin
Z[Z==0] = 0.9 * Zmin
#log transformation because the conversion in 3D
#does not work well in matplotlib
Zlog = np.log10(Z)
rc('font',family='palatino')
rc('font',size=18)
fig = plt.figure(figsize=(12,8))
#ax = fig.add_subplot(projection='3d')
ax = Axes3D(fig)
ax.set_xlim3d(0,15)
ax.set_zlim3d(np.floor(np.log10(Zmin))-1, np.ceil(np.log10(10)))
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
ax.zaxis.set_major_locator(mticker.MaxNLocator(integer=True))
rc('font',family='palatino')
rc('font',size=18)
tmp_planes = ax.zaxis._PLANES
ax.zaxis._PLANES = ( tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
ax.set_xlabel('$\omega^2 /<\omega^2>$')
ax.xaxis.labelpad = 10
ax.yaxis.labelpad = 10
ax.set_ylabel('cos$(\omega,\lambda^2)$')
ax.zaxis.set_rotate_label(False) # disable automatic rotation
ax.zaxis.labelpad = 10
ax.set_zlabel('')
ax.view_init(elev=17, azim=-60)
ax.grid(False)
ax.xaxis.pane.set_edgecolor('black')
ax.yaxis.pane.set_edgecolor('black')
ax.zaxis.pane.set_edgecolor('black')
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
ax.xaxis.set_major_locator(MultipleLocator(2))
ax.yaxis.set_major_locator(MultipleLocator(0.2))
ax.zaxis.set_major_locator(MultipleLocator(1))
#not sure this axis scaling routine is really necessary
scale_x = 1
scale_y = 1
scale_z = 0.8
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="--", offset=np.floor(np.log10(Zmin))-1)#-7)
surf = ax.plot_surface(X, Y, np.log10(Z), cmap="binary", lw=0.1,alpha=0.5)
ax.plot_wireframe(X, Y, np.log10(Z),linewidth=1,color='k')
ax.contour(X, Y, np.log10(Z), 4, lw=0.1, colors="k", linestyles="solid")
fig.colorbar(surf, shrink=0.5, aspect=20)
plt.tight_layout()
plt.savefig('jpdf_lambda2_90.png', bbox_inches='tight')
plt.show()
the problem is related to the "minorticks" along zaxis .. I obtain this :
but I would have this format and ticks in the axis
Does somebody clarify how to obtain it and as well I did not find a way to use the log scale in pyplot 3d
There's an open bug on log-scaling in 3D plots, and it looks like there won't be a fix any time soon.
You can use a matplotlib.ticker.FixedLocator to add the z-axis minor ticks, as shown below.
I didn't have your data, so I've plotted an arbitrary surface.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import rc
from matplotlib.ticker import MultipleLocator, FixedLocator
import matplotlib.ticker as mticker
import numpy as np
from matplotlib.ticker import FormatStrFormatter
def log_tick_formatter(val, pos=None):
return f"10$^{{{int(val)}}}$"
x = np.linspace(1,15,15)
y = np.linspace(0,1,15)
X, Y = np.meshgrid(x, y)
Z = 1 + X**2 * Y**2
#identify lowest non-negative Z value Zmin>0
Zmin = np.where(Z > 0, Z, np.inf).min()
Zmax = Z.max()
#and substitute zero with a slightly lower value than Zmin
Z[Z==0] = 0.9 * Zmin
rc('font',family='palatino')
rc('font',size=18)
fig = plt.figure(figsize=(12,8))
ax = Axes3D(fig, auto_add_to_figure=False)
fig.add_axes(ax)
ax.set_xlim3d(0,15)
ax.set_zlim3d(np.floor(np.log10(Zmin))-1, np.ceil(np.log10(Zmax)))
ax.zaxis.set_major_formatter(mticker.FuncFormatter(log_tick_formatter))
tmp_planes = ax.zaxis._PLANES
ax.zaxis._PLANES = ( tmp_planes[2], tmp_planes[3],
tmp_planes[0], tmp_planes[1],
tmp_planes[4], tmp_planes[5])
ax.set_xlabel('$\omega^2 /<\omega^2>$')
ax.xaxis.labelpad = 10
ax.yaxis.labelpad = 10
ax.set_ylabel('cos$(\omega,\lambda^2)$')
ax.zaxis.set_rotate_label(False) # disable automatic rotation
ax.zaxis.labelpad = 10
ax.set_zlabel('')
ax.view_init(elev=17, azim=-60)
ax.grid(False)
ax.xaxis.pane.set_edgecolor('black')
ax.yaxis.pane.set_edgecolor('black')
ax.zaxis.pane.set_edgecolor('black')
ax.xaxis.pane.fill = False
ax.yaxis.pane.fill = False
ax.zaxis.pane.fill = False
ax.xaxis.set_major_locator(MultipleLocator(2))
ax.yaxis.set_major_locator(MultipleLocator(0.2))
ax.zaxis.set_major_locator(MultipleLocator(1))
# Z minor ticks
zminorticks = []
zaxmin, zaxmax = ax.get_zlim()
for zorder in np.arange(np.floor(zaxmin),
np.ceil(zaxmax)):
zminorticks.extend(np.log10(np.linspace(2,9,8)) + zorder)
ax.zaxis.set_minor_locator(FixedLocator(zminorticks))
#not sure this axis scaling routine is really necessary
scale_x = 1
scale_y = 1
scale_z = 0.8
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([scale_x, scale_y, scale_z, 1]))
ax.contour(X, Y, np.log10(Z), 4, colors="k", linestyles="--", offset=np.floor(np.log10(Zmin))-1)#-7)
surf = ax.plot_surface(X, Y, np.log10(Z), cmap="binary", lw=0.1,alpha=0.5)
ax.plot_wireframe(X, Y, np.log10(Z),linewidth=1,color='k')
ax.contour(X, Y, np.log10(Z), 4, colors="k", linestyles="solid")
fig.colorbar(surf, shrink=0.5, aspect=20)
# get a warning that Axes3D is incompatible with tight_layout()
# plt.tight_layout()
# for saving
# fig.savefig('log3d.png')
plt.show()
How to colour the base on y = 0.3 by the same color as the middle part of the cylinder have, please?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d import proj3d
def data_for_cylinder_along_z(center_x,center_y,radius,height_z):
z = np.linspace(0, height_z, 200)
theta = np.linspace(0, 2*np.pi, 200)
theta_grid, z_grid=np.meshgrid(theta, z)
x_grid = radius*np.cos(theta_grid) + center_x
y_grid = radius*np.sin(theta_grid) + center_y
return x_grid,y_grid,z_grid
fig = plt.figure(figsize=[6,5])
ax = fig.add_subplot(111, projection='3d')
ax.azim = -39
ax.elev = 15
Xc,Zc,Yc = data_for_cylinder_along_z(0,0,0.05,0.3)
ax.plot_surface(Xc, Yc, Zc, alpha=0.4, color = 'grey')
plt.show()
How to make a circle and lines outwards, please? Shape like:
I have a circle, but I do not know how to continue with lines.
import matplotlib.pyplot as plt
import matplotlib.patches as patches
fig, ax1 = plt.subplots(figsize=(10,10))
circle = patches.Circle((0.45, 0.5), radius=0.13, transform=ax1.transData, clip_on=False, zorder=10, linewidth=2,
edgecolor='black', facecolor=(0, 0, 0, .0125))
ax1.patches.append(circle)
plt.show()
Sine and cosine of 16 angles can be used to create the lines:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import numpy as np
fig, ax1 = plt.subplots(figsize=(10, 10))
rad_circ = 0.13
rad_line_start = 0.17
rad_line_end = 0.21
xc, yc = 0.45, 0.5
circle = patches.Circle((xc, yc), radius=rad_circ, transform=ax1.transData, clip_on=False, zorder=10, linewidth=2,
edgecolor='black', facecolor='gold')
ax1.patches.append(circle)
theta = np.linspace(0, 2 * np.pi, 16, endpoint=False)
for th in theta:
ax1.plot([xc + rad_line_start * np.cos(th), xc + rad_line_end * np.cos(th)],
[yc + rad_line_start * np.sin(th), yc + rad_line_end * np.sin(th)],
color='gold', lw=10)
ax1.set_aspect('equal')
ax1.axis('off')
plt.show()
PS: To create all the line segments as a "line collection":
from matplotlib.collections import LineCollection
# ...
line_interval = np.array([[rad_line_start], [rad_line_end]])
segments = np.array([xc + np.cos(theta) * line_interval,
yc + np.sin(theta) * line_interval]).T
lc = LineCollection(segments, colors='gold', lw=10)
ax1.add_collection(lc)