Valerie
Valerie

Reputation: 37

How to plot axes with arrows in matplotlib

I want to achieve the following three things:

My code on this moment:

x = [9, 8, 11, 11, 14, 13, 16, 14, 14]
y = [9, 16, 15, 11, 10, 11, 10, 8, 8]
fig = plt.figure(figsize=(7,7), dpi=300)
axes = fig.add_axes([0,1,1,1])

axes.set_xlim(0, 17)
axes.set_ylim(0, 17)

axes.invert_yaxis()

axes.scatter(x, y, color='green')
axes.vlines(x, 0, y, linestyle="dashed", color='green')
axes.hlines(y, 0, x, linestyle="dashed", color='green')
axes.spines.right.set_visible(False)
axes.spines.bottom.set_visible(False)

plt.show()

Visually:

enter image description here

And plot that I want to realize enter image description here

Upvotes: 3

Views: 2226

Answers (3)

Sam Gallagher
Sam Gallagher

Reputation: 157

Came across this when looking for a way to make arrows on axes, but I wanted to control the aspect ratio of the arrows, which you can do using the above answers, slightly modified.

You define (a) an Affine2D scale transform (mpl.transforms.Affine2D().scale(sx,sy)) and (b) use a MarkerStyle using that transform (mpl.markers.MarkerStyle()) . This transform is independent of the coordinate transform that uses axes coordinates (e.g. ax.get_xaxis_transform()). Do this once for each axis. See this page for example.

Example with 2:1 length:width arrowheads:

# Arrows on axes
# We'll stretch out the triangular markers
stretchxax = mpl.transforms.Affine2D().scale(sx=2.,sy=1.)
stretchyax = mpl.transforms.Affine2D().scale(sx=1.,sy=2.)
ax.plot(1,0,'k',marker=mpl.markers.MarkerStyle('>',transform=stretchxax),transform=ax.get_yaxis_transform(),clip_on=False)
ax.plot(0,1,'k',marker=mpl.markers.MarkerStyle('^',transform=stretchyax),transform=ax.get_xaxis_transform(),clip_on=False)

Upvotes: 0

Cameron Riddell
Cameron Riddell

Reputation: 13447

You can draw arrows by overlaying triangle shaped points over the ends of your spines.

You'll need to leverage some transforms, but you can also create your labels by manually adding text to your Axes objects as well.

Labelling each coordinate can be done via axes.annotate, but you'll need to manually specify the location of each annotation to ensure they don't overlap with lines or other annotations.

import matplotlib.pyplot as plt
from matplotlib.ticker import FixedLocator

x = [9, 8, 11, 11, 14, 13, 16, 14, 14]
y = [9, 16, 15, 11, 10, 11, 10, 8, 8]

fig = plt.figure(figsize=(7,7), dpi=300)
axes = fig.add_axes([.05,.05,.9,.9])

# Plots the data
axes.scatter(x, y, color='green')
axes.vlines(x, 0, y, linestyle="dashed", color='green')
axes.hlines(y, 0, x, linestyle="dashed", color='green')

axes.set_xlim(0, 17)
axes.set_ylim(0, 17)
axes.set_xticks(x)
axes.set_yticks(y)
axes.invert_yaxis()

# Move ticks to top side of plot
axes.xaxis.set_tick_params(
    length=0, bottom=False, labelbottom=False, top=True, labeltop=True
)
axes.xaxis.set_tick_params(length=0)

# Add arrows to the spines by drawing triangle shaped points over them
axes.plot(1, 1, '>k', transform=axes.transAxes, clip_on=False)
axes.plot(0, 0, 'vk', transform=axes.transAxes, clip_on=False)
axes.spines[['bottom', 'right']].set_visible(False)

# Add labels for 0, F_1 and F_2
from matplotlib.transforms import offset_copy
axes.text(
    0, 1, s='0', fontstyle='italic', ha='right', va='bottom',
    transform=offset_copy(axes.transAxes, x=-5, y=5, fig=fig, units='points'),
)
axes.text(
    1, 1, s='$F_1$', fontstyle='italic', ha='right', va='bottom',
    transform=offset_copy(axes.transAxes, x=0, y=5, fig=fig, units='points'),
)
axes.text(
    0, 0, s='$F_2$', fontstyle='italic', ha='right',
    transform=offset_copy(axes.transAxes, x=-5, y=0, fig=fig, units='points'),
)

# Add labels at each point. Leveraging the alignment of the text
# AND padded offset.
lc = ('top', 'center', 0, -5)
ll = ('top', 'right', -5, -5)
lr = ('top', 'left', 5, -5)
ur = ('bottom', 'left', 5, 5)
alignments = [lc, lc, lc, ll, lc, ll, lc, ur, lr]
for i, (xc, yc, (va, ha, padx, pady)) in enumerate(zip(x, y, alignments)):
    axes.annotate(
        xy=(xc, yc), xytext=(padx, pady),
        text=f'$F(x_{i})$', ha=ha, va=va, textcoords='offset points')

plt.show()

enter image description here

Upvotes: 3

Daraan
Daraan

Reputation: 3967

Adding

axes.plot(1, 0, ">k", transform=axes.get_yaxis_transform(), clip_on=False)  
axes.plot(0, 0, "vk", transform=axes.get_xaxis_transform(), clip_on=False)

Will do it for you. This is basically just a cheat plot of markers.


There are also

from mpl_toolkits.axisartist.axislines.AxesZero which allow

for direction in ["xzero", "yzero"]:
    # adds arrows at the ends of each axis
    ax.axis[direction].set_axisline_style("-|>")

but they can't handle the reversed y axis in your case with default settings.

Upvotes: 1

Related Questions