如何将图表的xtick标签设为简单图形?
我想在x轴的刻度标签上画一些简单的图形,比如线条和圆圈,而不是用文字或数字来标记。这样做可以吗?如果可以的话,使用matplotlib(一个绘图工具)最好的方法是什么呢?
2 个回答
之前的回答有一些缺点,因为它使用的是固定的坐标。这意味着当你改变图形的大小,或者放大、缩小和移动图表时,它就不管用了。
一个更好的方法是直接在你选择的坐标系统中定义位置。对于x轴,使用数据坐标来表示x位置,而使用轴坐标来表示y位置是比较合理的。
使用matplotlib.offsetbox
可以让这个过程变得简单。下面的代码会把一个圆形框和一个图片框分别放在坐标(-5,0)和(5,0)的位置,并稍微向下偏移一点,这样它们看起来就像是刻度标签一样。
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from matplotlib.offsetbox import (DrawingArea, OffsetImage,AnnotationBbox)
fig, ax = plt.subplots()
ax.plot([-10,10], [1,3])
# Annotate the 1st position with a circle patch
da = DrawingArea(20, 20, 10, 10)
p = mpatches.Circle((0, 0), 10)
da.add_artist(p)
ab = AnnotationBbox(da, (-5,0),
xybox=(0, -7),
xycoords=("data", "axes fraction"),
box_alignment=(.5, 1),
boxcoords="offset points",
bboxprops={"edgecolor" : "none"})
ax.add_artist(ab)
# Annotate the 2nd position with an image
arr_img = plt.imread("https://i.stack.imgur.com/FmX9n.png", format='png')
imagebox = OffsetImage(arr_img, zoom=0.2)
imagebox.image.axes = ax
ab = AnnotationBbox(imagebox, (5,0),
xybox=(0, -7),
xycoords=("data", "axes fraction"),
boxcoords="offset points",
box_alignment=(.5, 1),
bboxprops={"edgecolor" : "none"})
ax.add_artist(ab)
plt.show()
值得注意的是,很多形状可以用unicode符号表示,所以你可以直接用这些符号来设置刻度标签。如果你想了解这种方法,可以查看如何在matplotlib或seaborn中使用彩色形状作为y轴刻度?
我会去掉刻度标签,把文字换成补丁。下面是一个简单的例子,展示如何做到这一点:
import matplotlib.pyplot as plt
import matplotlib.patches as patches
# define where to put symbols vertically
TICKYPOS = -.6
fig = plt.figure()
ax = fig.add_subplot(111)
ax.plot(range(10))
# set ticks where your images will be
ax.get_xaxis().set_ticks([2,4,6,8])
# remove tick labels
ax.get_xaxis().set_ticklabels([])
# add a series of patches to serve as tick labels
ax.add_patch(patches.Circle((2,TICKYPOS),radius=.2,
fill=True,clip_on=False))
ax.add_patch(patches.Circle((4,TICKYPOS),radius=.2,
fill=False,clip_on=False))
ax.add_patch(patches.Rectangle((6-.1,TICKYPOS-.05),.2,.2,
fill=True,clip_on=False))
ax.add_patch(patches.Rectangle((8-.1,TICKYPOS-.05),.2,.2,
fill=False,clip_on=False))
这样就得到了下面的图:
关键是要把 clip_on
设置为 False
,否则在坐标轴外的 patches
就看不见了。补丁的坐标和大小(半径、宽度、高度等)会根据你的坐标轴在图中的位置而变化。例如,如果你打算在子图中使用这个功能,就需要注意补丁的位置,确保它们不会和其他坐标轴重叠。你可能需要花时间了解一下变换,并用其他单位(坐标轴、图形或显示)来定义位置和大小。
如果你有特定的图像文件想用作符号,可以使用 BboxImage
类来创建艺术对象,添加到坐标轴中,而不是使用补丁。例如,我用下面的脚本制作了一个简单的图标:
import matplotlib.pyplot as plt
fig = plt.figure(figsize=(1,1),dpi=400)
ax = fig.add_axes([0,0,1,1],frameon=False)
ax.set_axis_off()
ax.plot(range(10),linewidth=32)
ax.plot(range(9,-1,-1),linewidth=32)
fig.savefig('thumb.png')
生成了这个图像:
然后我在想要刻度标签的位置创建了一个 BboxImage,并设置了我想要的大小:
lowerCorner = ax.transData.transform((.8,TICKYPOS-.2))
upperCorner = ax.transData.transform((1.2,TICKYPOS+.2))
bbox_image = BboxImage(Bbox([lowerCorner[0],
lowerCorner[1],
upperCorner[0],
upperCorner[1],
]),
norm = None,
origin=None,
clip_on=False,
)
注意我使用了 transData
变换,将数据单位转换为显示单位,这在定义 Bbox
时是必须的。
现在我使用 imread
函数读取图像,并把结果(一个 numpy 数组)设置为 bbox_image
的数据,然后把这个艺术对象添加到坐标轴中:
bbox_image.set_data(imread('thumb.png'))
ax.add_artist(bbox_image)
这样就得到了更新后的图:
如果你直接使用图像,确保导入所需的类和方法:
from matplotlib.image import BboxImage,imread
from matplotlib.transforms import Bbox