4

below is my code that plots a function, and I need to move the "X" and "Y" labels into the first quadrant, to where they are conventionally placed near the corresponding arrows. how is this done?

import pylab as p
import numpy as n

from mpl_toolkits.axes_grid import axislines


def cubic(x) :
    return x**3 + 6*x


def set_axes():
    fig = p.figure(1)
    ax = axislines.SubplotZero(fig, 111)
    fig.add_subplot(ax)

    for direction in ['xzero', 'yzero']:
        ax.axis[direction].set_axisline_style('->', size=2)
        ax.axis[direction].set_visible(True)

    for direction in ['right', 'top', 'left', 'bottom']:
        ax.axis[direction].set_visible(False)

    ax.axis['xzero'].set_label('X')
    ax.axis['yzero'].set_label('Y')

    ax.axis['yzero'].major_ticklabels.set_axis_direction('right')
    ax.axis['yzero'].set_axislabel_direction('+')
    ax.axis['yzero'].label.set_rotation(-90)
    ax.axis['yzero'].label.set_va('center')


set_axes()

X = n.linspace(-15,15,100)
Y = cubic(X)

p.plot(X, Y)

p.xlim(-5.0, 5.0)
p.ylim(-15.0, 15.0)

p.xticks(n.linspace(-5, 5, 11, endpoint=True))
p.grid(True)

p.show()
akonsu
  • 28,824
  • 33
  • 119
  • 194

1 Answers1

9

Normally, to change an axis's (e.g. ax.xaxis) label position, you'd do axis.label.set_position(xy). Or you can just set one coordinate, e.g. 'ax.xaxis.set_x(1)`.

In your case, it would be:

ax['xzero'].label.set_x(1)
ax['yzero'].label.set_y(1)

However, axislines (and anything else in axisartist or axes_grid) is a somewhat outdated module (which is why axes_grid1 exists). It doesn't subclass things properly in some cases. So, when we try to set the x and y positions of the labels, nothing changes!


A quick workaround would be to use ax.annotate to place labels at the ends of your arrows. However, let's try making the plot a different way first (after which we'll wind up coming back to annotate anyway).


These days, you're better off using the new spines functionality to do what you're trying to accomplish.

Setting the x and y axes to be "zeroed" is as simple as:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

for spine in ['left', 'bottom']:
    ax.spines[spine].set_position('zero')

# Hide the other spines...  
for spine in ['right', 'top']:
    ax.spines[spine].set_color('none')

ax.axis([-4, 10, -4, 10])
ax.grid()

plt.show()

enter image description here

However, we still need the nice arrow decoration. This is a bit more complex, but it's just two calls to annotate with the approriate arguments.

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

#-- Set axis spines at 0
for spine in ['left', 'bottom']:
    ax.spines[spine].set_position('zero')

# Hide the other spines...
for spine in ['right', 'top']:
    ax.spines[spine].set_color('none')

#-- Decorate the spins
arrow_length = 20 # In points

# X-axis arrow
ax.annotate('', xy=(1, 0), xycoords=('axes fraction', 'data'), 
            xytext=(arrow_length, 0), textcoords='offset points',
            arrowprops=dict(arrowstyle='<|-', fc='black'))

# Y-axis arrow
ax.annotate('', xy=(0, 1), xycoords=('data', 'axes fraction'), 
            xytext=(0, arrow_length), textcoords='offset points',
            arrowprops=dict(arrowstyle='<|-', fc='black'))

#-- Plot
ax.axis([-4, 10, -4, 10])
ax.grid()

plt.show()

enter image description here

(The width of the arrow is controlled by the text size (or an optional parameter to the arrowprops), so specifying something like size=16 to annotate will make the arrow a bit wider, if you'd like.)


At this point, it's easiest to just add the "X" and "Y" labels as a part of the annotation, though setting their positions would work as well.

If we just pass in a label as the first argument to annotate instead of an empty string (and change the alignment a bit) we'll get nice labels at the ends of the arrows:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()

#-- Set axis spines at 0
for spine in ['left', 'bottom']:
    ax.spines[spine].set_position('zero')

# Hide the other spines...
for spine in ['right', 'top']:
    ax.spines[spine].set_color('none')

#-- Decorate the spins
arrow_length = 20 # In points

# X-axis arrow
ax.annotate('X', xy=(1, 0), xycoords=('axes fraction', 'data'), 
            xytext=(arrow_length, 0), textcoords='offset points',
            ha='left', va='center',
            arrowprops=dict(arrowstyle='<|-', fc='black'))

# Y-axis arrow
ax.annotate('Y', xy=(0, 1), xycoords=('data', 'axes fraction'), 
            xytext=(0, arrow_length), textcoords='offset points',
            ha='center', va='bottom',
            arrowprops=dict(arrowstyle='<|-', fc='black'))

#-- Plot
ax.axis([-4, 10, -4, 10])
ax.grid()

plt.show()

enter image description here

With a only a tiny bit more work (directly accessing the spine's transform), you can generalize the use of annotate to work with any type of spine alignment (e.g. "dropped" spines, etc).

At any rate, hope that helps a bit. You can also get fancier with it, if you'd like.

Community
  • 1
  • 1
Joe Kington
  • 275,208
  • 71
  • 604
  • 463
  • thanks! one problem with using annotations to draw arrows is that the arrows are thicker a little than the axes. – akonsu Oct 10 '12 at 03:39