Matplotlib:将网格线保留在图形后面,但将y轴和x轴放在上方。

15

我在绘制图表时很难在图表下方绘制网格线,而不会影响主要的x轴和y轴zorder:

import matplotlib.pyplot as plt
import numpy as np


N = 5
menMeans = (20, 35, 30, 35, 27)
menStd =   (2, 3, 4, 1, 2)

ind = np.arange(N)  # the x locations for the groups
width = 0.35       # the width of the bars

fig, ax = plt.subplots()
rects1 = ax.bar(ind, menMeans, width, color='r', yerr=menStd, alpha=0.9, linewidth = 0,zorder=3)

womenMeans = (25, 32, 34, 20, 25)
womenStd =   (3, 5, 2, 3, 3)
rects2 = ax.bar(ind+width, womenMeans, width, color='y', yerr=womenStd, alpha=0.9, linewidth = 0,zorder=3)

# add some
ax.set_ylabel('Scores')
ax.set_title('Scores by group and gender')
ax.set_xticks(ind+width)
ax.set_xticklabels( ('G1', 'G2', 'G3', 'G4', 'G5') )

ax.legend( (rects1[0], rects2[0]), ('Men', 'Women') )

fig.gca().yaxis.grid(True, which='major', linestyle='-', color='#D9D9D9',zorder=2, alpha = .9)
[line.set_zorder(4) for line in ax.lines]

def autolabel(rects):
    # attach some text labels
    for rect in rects:
        height = rect.get_height()
        ax.text(rect.get_x()+rect.get_width()/2., 1.05*height, '%d'%int(height),
                ha='center', va='bottom')

autolabel(rects1)
autolabel(rects2)

plt.show()

这个示例来自matplotlib,我稍作修改以展示如何让问题显现。我无法发布图像,但是如果您运行该代码,您将看到条形图不仅绘制在水平网格线上方,而且还绘制在x和y轴上方。我不想让x和y轴被图形遮挡,特别是当刻度也被阻挡时。


为什么要使用 fig.gca() 而不是 ax - Francesco Montesano
只是出于好奇:你知道 ax.lines 中的线条是由什么创建的吗? - Francesco Montesano
你是对的,我只是迷失在尝试如何做它的过程中,而没有尝试理解一切是如何工作的... - luke_16
6个回答

12

当我在背景中有网格线时,我遇到了与坐标轴被绘制在图形线下的同样问题:

ax.yaxis.grid()  # grid lines
ax.set_axisbelow(True)  # grid lines are behind the rest

对我有用的解决方案是将plot()函数的zorder参数设置为1到2之间的值。虽然不是立即清楚,但zorder值可以是任何数字。根据matplotlib.artist.Artist类的文档:

set_zorder(level)

设置艺术家(图层)的zorder。具有较低zorder值的艺术家将首先绘制。

接受:任何数字

因此:

for i in range(5):
    ax.plot(range(10), np.random.randint(10, size=10), zorder=i / 100.0 + 1)

我没有检查过这个范围之外的值,也许它们也可以使用。


6

我尝试过使用matplotlib 1.2.1,1.3.1rc2和master(commit 06d014469fc5c79504a1b40e7d45bc33acc00773)。

要使轴脊位于柱形图的顶部,您可以执行以下操作:

for k, spine in ax.spines.items():  #ax.spines is a dictionary
    spine.set_zorder(10)

编辑

看起来我无法将勾线放在柱形图的上方。我已经尝试过:

1. ax.tick_params(direction='in', length=10, color='k', zorder=10)
   #This increases the size of the lines to 10 points, 
   #but the lines stays hidden behind  the bars
2. for l in ax.yaxis.get_ticklines():
       l.set_zorder(10)

还有其他一些方法,但没有结果。似乎在绘制条形图时,它们被放在顶部,并忽略了zorder。

一个解决方法是将刻度线向外绘制。

ax.tick_params(direction='out', length=4, color='k', zorder=10)

使用 direction='inout' 可以在内外两个方向上进行双向移动。

编辑2

在 @tcaswell 的评论后,我进行了一些测试。

如果在 ax.bar 函数中设置的 zorder 小于或等于2,则轴、刻度线和网格线都会显示在条形图之上。如果值大于2.01(默认值为轴),则条形图会画在轴、刻度线和网格线之上。此时可以将脊柱(如上所述)设为更大的值,但是任何尝试更改刻度线的 zorder 都将被忽略(虽然相应艺术家的值会更新)。

我尝试将 barzorder=1,将网格的 zorder=0,结果发现网格被绘制在条形图之上。因此,zorder 被忽略了。

总结

对我来说,看起来刻度线和网格的 zorder 被忽略了,并保持默认值。对我来说,这个问题与 bar 或一些 patches 有关。

顺便说一句,我记得在使用 imshow 时成功地更改了刻度线的 zorder


我会尝试并告诉你。 - Francesco Montesano
@tcaswell 我已经在1.3.1rc2和主分支(刚刚获取)上尝试了,但不起作用。那肯定是个bug。谁来报告呢?(如果我发现有关问题的任何提示,我可以尝试稍后查看源代码) - Francesco Montesano
实际上,我认为这更像是一种“特性”而不是错误,因为网格线会从轴对象继承它们的 Z 顺序。 - tacaswell
@tcaswell 继承自轴对象的zorder属性不应意味着我不能更改它,这就是我试图在ax.yaxis.grid(True, which='major', linestyle='-', color='#D9D9D9', zorder=2, alpha=.9)中做的。 - luke_16
1
@luke_16 如果我没记错的话,刻度线和坐标轴是由渲染器一次性绘制的,因此它们 不能 具有不同的 zorder。 - tacaswell
显示剩余3条评论

3

我知道这是一个相当古老的问题,但问题似乎本质上仍然存在,所以让我用Python 3.9.6和Matplotlib 3.4.2写下一个MRE和我的解决方案。

问题

正如OP所知,条形图的默认zorder相对较低。 以下代码会导致一个绘图,其中刻度线和网格线都在条形图上方。

import matplotlib.pyplot as plt

y = (20, 35, 30, 35, 27)
x = range(len(y))

fig, ax = plt.subplots()
ax.bar(x, y)
ax.grid(True)
ax.tick_params(direction="in", length=10)

plt.show()

First example

可以使用ax.set_axisbelow(True)将刻度线和网格线都移动到柱形图后面,但这样会导致x轴上的刻度被柱形图遮挡。

import matplotlib.pyplot as plt

y = (20, 35, 30, 35, 27)
x = range(len(y))

fig, ax = plt.subplots()
ax.bar(x, y)
ax.grid(True)
ax.tick_params(direction="in", length=10)
ax.set_axisbelow(True)  # This line added.

plt.show()

Second example

解决方案

在绘制完图中所有元素后重新绘制刻度线。 (我假设它们是不透明的,重新绘制是无害的。)

import matplotlib.artist
import matplotlib.backend_bases
import matplotlib.pyplot as plt


class TickRedrawer(matplotlib.artist.Artist):
    """Artist to redraw ticks."""

    __name__ = "ticks"

    zorder = 10

    @matplotlib.artist.allow_rasterization
    def draw(self, renderer: matplotlib.backend_bases.RendererBase) -> None:
        """Draw the ticks."""
        if not self.get_visible():
            self.stale = False
            return

        renderer.open_group(self.__name__, gid=self.get_gid())

        for axis in (self.axes.xaxis, self.axes.yaxis):
            loc_min, loc_max = axis.get_view_interval()

            for tick in axis.get_major_ticks() + axis.get_minor_ticks():
                if tick.get_visible() and loc_min <= tick.get_loc() <= loc_max:
                    for artist in (tick.tick1line, tick.tick2line):
                        artist.draw(renderer)

        renderer.close_group(self.__name__)
        self.stale = False


y = (20, 35, 30, 35, 27)
x = range(len(y))

fig, ax = plt.subplots()
ax.bar(x, y)
ax.grid(True)
ax.tick_params(direction="in", length=10)
ax.set_axisbelow(True)
ax.add_artist(TickRedrawer())  # This line added.

plt.show()

Solution


1
我遇到了与@luke_16相同的问题,即在轴上方绘制。 在我的情况下,当使用选项ax.set_axisbelow(True)将网格设置在图形后面时,出现了这个问题。
我的解决方法是不使用内置网格,而是模拟它:
def grid_selfmade(ax,minor=False):
    y_axis=ax.yaxis.get_view_interval()
    for xx in ax.xaxis.get_ticklocs():
        ax.plot([xx,xx],y_axis,linestyle=':',linewidth=0.5,zorder=0)
    if minor==True:
        for xx in ax.xaxis.get_ticklocs(minor=True):
            ax.plot([xx,xx],y_axis,linestyle=':',linewidth=0.5,zorder=0)
    x_axis=ax.xaxis.get_view_interval()
    for yy in ax.yaxis.get_ticklocs():
        ax.plot(x_axis,[yy,yy],linestyle=':',linewidth=0.5,zorder=0,)
    if minor==True:
        for yy in ax.yaxis.get_ticklocs(minor=True):
            ax.plot(x_axis,[yy,yy],linestyle=':',linewidth=0.5,zorder=0)

这个函数只需要当前的轴实例,然后在主要刻度处(可选择次要刻度)绘制一个类似于板载网格的背景。为了使轴和刻度显示在图形之上,必须将ax.set_axisbelow(False)保持为False,并且不使用zorder>2的绘图。我通过改变代码中绘图命令的顺序来管理绘图的zorder而不需要使用任何zorder选项。

1
一个更简单且更好的解决方案是复制网格命令中的行,并禁用网格,然后再次添加带有正确zorder的行:
ax.grid(True)
lines = ax.xaxis.get_gridlines().copy() + ax.yaxis.get_gridlines().copy()
ax.grid(False)
for l in lines:
    ax.add_line(l)
    l.set_zorder(0)

我不明白你解决方案中的ax1是什么。 - J_Scholz

0
从 @tueda 的 MRE 中借鉴...
这会导致网格线覆盖在柱状图上,正如问题所指出的那样,增加柱状图的 zorder 可以将柱状图放在坐标轴刻度之上。
import matplotlib.pyplot as plt

y = (20, 35, 30, 35, 27)
x = range(len(y))

fig, ax = plt.subplots()
ax.bar(x, y)
ax.grid(True)
ax.tick_params(direction="in", length=10)

enter image description here

解决方案:手动绘制每个刻度下方的水平和垂直线条,而不是使用grid()函数...
import matplotlib.pyplot as plt

y = (20, 35, 30, 35, 27)
x = range(len(y))

fig, ax = plt.subplots()
ax.bar(x, y)
ax.tick_params(direction="in", length=10)

for i in ax.get_xticks():
    ax.axvline(i, color="g", zorder=-10)
for i in ax.get_yticks():
    ax.axhline(i, color="r", zorder=-10)

enter image description here


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接