Matplotlib square major/minor grid for axes with different limits

巧了我就是萌 提交于 2021-02-05 08:15:07

问题


I have a plot with a background grid. I need grid cells to be square (both major grid and minor grid cells) even though the limits of X and Y axes are different.

My current code is as follows:

import matplotlib.pyplot as plt
import matplotlib.ticker as plticker
import numpy as np

data = [0.014,  0.84,  0.95, -0.42, -0.79,  0.84, 0.98,  1.10,   0.56, -0.49]


fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()

# Set major and minor grid lines on X
ax.set_xticks(np.arange(0, 10, 0.2))
ax.xaxis.set_minor_locator(plticker.MultipleLocator(base=0.2 / 5.))
for xmaj in ax.xaxis.get_majorticklocs():
        ax.axvline(x=xmaj, ls='-', color='red', linewidth=0.8)
for xmin in ax.xaxis.get_minorticklocs():
    ax.axvline(x=xmin, ls=':', color='red', linewidth=0.6)

# Set major and minor grid lines on Y
ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.5, 0.5)
ax.set_yticks(yticks)
ax.yaxis.set_minor_locator(plticker.MultipleLocator(base=0.5 / 5.))
for ymaj in ax.yaxis.get_majorticklocs():
        ax.axhline(y=ymaj, ls='-', color='red', linewidth=0.8)
for ymin in ax.yaxis.get_minorticklocs():
    ax.axhline(y=ymin, ls=':', color='red', linewidth=0.6)

ax.axis([0, 10, -ylim, ylim])
fig.tight_layout()

# Plot
ax.plot(data)

# Set equal aspect ratio NOT WORKING
plt.gca().set_aspect('equal', adjustable='box')
plt.show()

Which generates the following plot:

Large grid cells contain 5 smaller cells each. However, the aspect ratio of large grid is not 1. Question: How can I make sure that large grid is square?

EDIT Current approach is to set same tick locations as suggested by @ImportanceOfBeingErnest, but change Y labels:

ylim = int(np.ceil(max(abs(min(data)), max(data))))
yticks = np.arange(-ylim, ylim + 0.2, 0.2)

ax.set_yticks(yticks)

labels = ['{:.1f}'.format(v if abs(v) < 1e-3 else (1 if v > 0 else -1)*((0.5 - abs(v)%0.5) + abs(v))) 
          if i%2==0 else "" for i, v in enumerate(np.arange(-ylim, ylim, 0.2))]
ax.set_yticklabels(labels)

Result: seems too hacky.


回答1:


When using equal aspect ratio and aiming for a square grid you would need to use the same tickspacing for both axes. This can be achieved with a MultipleLocator where the interval needs to be the same for x and y axis.

In general, grids can be created with the grid command.

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np

data = [0.014,  0.84,  0.95, -0.42, -0.79,  0.84, 0.98,  1.10,   0.56, -0.49]


fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()

# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))

ax.yaxis.set_major_locator(mticker.MultipleLocator(base=.5))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=0.5 / 5.))

ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)

## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect('equal', adjustable='box')
fig.tight_layout()

# Plot
ax.plot(data)

plt.show()

If you instead want to have different tick spacings with square major cells in the grid, you would need to give up the equal aspect ratio and instead set it to the quotient of the tick spacings.

import matplotlib.pyplot as plt
import matplotlib.ticker as mticker
import numpy as np

data = [0.014,  0.84,  0.95, -0.42, -0.79,  0.84, 0.98,  1.10,   0.56, -0.49]


fig, ax = plt.subplots(figsize=(20, 5))
ax.minorticks_on()

xm = 0.2
ym = 0.25

# Set major and minor grid lines on X
ax.xaxis.set_major_locator(mticker.MultipleLocator(base=xm))
ax.xaxis.set_minor_locator(mticker.MultipleLocator(base=xm / 5.))

ax.yaxis.set_major_locator(mticker.MultipleLocator(base=ym))
ax.yaxis.set_minor_locator(mticker.MultipleLocator(base=ym / 5.))

ax.grid(ls='-', color='red', linewidth=0.8)
ax.grid(which="minor", ls=':', color='red', linewidth=0.6)

## Set limits
ylim = int(np.ceil(max(abs(min(data)), max(data))))
ax.axis([0, 10, -ylim, ylim])
plt.gca().set_aspect(xm/ym, adjustable='box')
fig.tight_layout()


# Plot
ax.plot(data)

plt.show()

To then get rid of every second ticklabel, an option is

fmt = lambda x,p: "%.2f" % x if not x%(2*ym) else ""
ax.yaxis.set_major_formatter(mticker.FuncFormatter(fmt))



回答2:


You should be able to achieve this by using the same locator for the both axis. However matplotlib has a limitation currently, so here's a workaround:

# matplotlib doesnt (currently) allow two axis to share the same locator
# so make two wrapper locators and combine their view intervals
def share_locator(locator):
    class _SharedLocator(matplotlib.ticker.Locator):
        def tick_values(self, vmin, vmax):
            return locator.tick_values(vmin, vmax)

        def __call__(self):
            min0, max0 = shared_locators[0].axis.get_view_interval()
            min1, max1 = shared_locators[1].axis.get_view_interval()
            return self.tick_values(min(min0, min1), max(max0, max1))

    shared_locators = (_SharedLocator(), _SharedLocator())
    return shared_locators

Use like:

lx, ly = share_locator(matplotlib.ticker.AutoLocator())  # or any other locator
ax.xaxis.set_major_locator(lx)
ax.yaxis.set_major_locator(ly)


来源:https://stackoverflow.com/questions/51632072/matplotlib-square-major-minor-grid-for-axes-with-different-limits

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!