Asterisk
Asterisk

Reputation: 3574

Square major/minor grid for axes with different limits

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: enter image description here

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. enter image description here

Upvotes: 1

Views: 1239

Answers (2)

Giovanni Funchal
Giovanni Funchal

Reputation: 9190

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)

Upvotes: 0

ImportanceOfBeingErnest
ImportanceOfBeingErnest

Reputation: 339340

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()

enter image description here

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()

enter image description here

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))

Upvotes: 2

Related Questions