# Center origin in matplotlib

### Question

I need help customizing my plots. I want the canvas to look approximately like the default 2D-graph template from MacOS's Grapher (see screenshot). To clarify - I need

• a centered axis
• a grid (preferably with an additional darker grid every 1 unit)
• axislines with arrows
• only one zero at the origo (when I tried my best, I got one zero from the x-axis and a second one from the y-axis.), slightly moved to the left so it's not behind the y-axis

1
12
9/17/2015 3:48:56 AM

This definitely falls under the category of more trouble than it's worth with matplotlib, but here you go. Also, for the basic case, have a look at the centering spines demo in the documentation.

You can do this in a few different ways, but for the best visual effect, consider something along the lines of the following. It's far from perfect, but it's reasonably flexible:

``````import matplotlib.pyplot as plt
import matplotlib as mpl
import matplotlib.patheffects
import numpy as np

def center_spines(ax=None, centerx=0, centery=0):
"""Centers the axis spines at <centerx, centery> on the axis "ax", and
places arrows at the end of the axis spines."""
if ax is None:
ax = plt.gca()

# Set the axis's spines to be centered at the given point
# (Setting all 4 spines so that the tick marks go in both directions)
ax.spines['left'].set_position(('data', centerx))
ax.spines['bottom'].set_position(('data', centery))
ax.spines['right'].set_position(('data', centerx - 1))
ax.spines['top'].set_position(('data', centery - 1))

# Draw an arrow at the end of the spines
ax.spines['left'].set_path_effects([EndArrow()])
ax.spines['bottom'].set_path_effects([EndArrow()])

# Hide the line (but not ticks) for "extra" spines
for side in ['right', 'top']:
ax.spines[side].set_color('none')

# On both the x and y axes...
for axis, center in zip([ax.xaxis, ax.yaxis], [centerx, centery]):
# Turn on minor and major gridlines and ticks
axis.set_ticks_position('both')
axis.grid(True, 'major', ls='solid', lw=0.5, color='gray')
axis.grid(True, 'minor', ls='solid', lw=0.1, color='gray')
axis.set_minor_locator(mpl.ticker.AutoMinorLocator())

# Hide the ticklabels at <centerx, centery>
formatter = CenteredFormatter()
formatter.center = center
axis.set_major_formatter(formatter)

# Add offset ticklabels at <centerx, centery> using annotation
# (Should probably make these update when the plot is redrawn...)
xlabel, ylabel = map(formatter.format_data, [centerx, centery])
ax.annotate('(%s, %s)' % (xlabel, ylabel), (centerx, centery),
xytext=(-4, -4), textcoords='offset points',
ha='right', va='top')

# Note: I'm implementing the arrows as a path effect rather than a custom
#       Spines class. In the long run, a custom Spines class would be a better
#       way to go. One of the side effects of this is that the arrows aren't
#       reversed when the axes are reversed!

class EndArrow(mpl.patheffects._Base):
"""A matplotlib patheffect to add arrows at the end of a path."""
super(mpl.patheffects._Base, self).__init__()
self._gc_args = kwargs
self.facecolor = facecolor

self.trans = mpl.transforms.Affine2D()

self.arrowpath = mpl.path.Path(
np.array([[-0.5, -0.2], [0.0, 0.0], [0.5, -0.2],
[0.0, 1.0], [-0.5, -0.2]]),
np.array([1, 2, 2, 2, 79]))

def draw_path(self, renderer, gc, tpath, affine, rgbFace):
scalex = renderer.points_to_pixels(self.width)
scaley = renderer.points_to_pixels(self.height)

x0, y0 = tpath.vertices[-1]
dx, dy = tpath.vertices[-1] - tpath.vertices[-2]
azi =  np.arctan2(dy, dx) - np.pi / 2.0
trans = affine + self.trans.clear(
).scale(scalex, scaley
).rotate(azi
).translate(x0, y0)

gc0 = renderer.new_gc()
gc0.copy_properties(gc)
self._update_gc(gc0, self._gc_args)

if self.facecolor is None:
color = rgbFace
else:
color = self.facecolor

renderer.draw_path(gc0, self.arrowpath, trans, color)
renderer.draw_path(gc, tpath, affine, rgbFace)
gc0.restore()

class CenteredFormatter(mpl.ticker.ScalarFormatter):
"""Acts exactly like the default Scalar Formatter, but yields an empty
label for ticks at "center"."""
center = 0
def __call__(self, value, pos=None):
if value == self.center:
return ''
else:
return mpl.ticker.ScalarFormatter.__call__(self, value, pos)
``````

I deliberately didn't set the x and y major tick intervals to 1, but that's easy to do. `ax.xaxis.set_major_locator(MultipleLocator(1))`

Now you can just call `center_spines` to do something like this:

``````x = np.arange(-5, 5)
y = x

line, = plt.plot(x, y)
center_spines()
plt.axis('equal')
plt.show()
`````` 36
9/17/2015 3:51:23 AM

Centered axes, see an example here (look for "zeroed spines"): http://matplotlib.sourceforge.net/examples/pylab_examples/spine_placement_demo.html

grid: ax.grid(True)

Get rid of the zero at the origin: see set_ticks(...) here: http://matplotlib.sourceforge.net/api/axis_api.html

What else did you look for?