I'm trying to get some "pretty" arrows for graphs and other uses in
Sage. One of the problems we've been having with the FancyArrow and
YAArrow is that the arrow is skewed when the aspect ratio is not 1:1 and
it is scaled along with the plot. I've written the attached ArrowLine
class which basically modifies the marker drawing code to draw an
arrowhead at the end of a Line2D. It doesn't suffer either of these
problems; it works beautifully.
However, in drawing (vertex and line) graphs, we have another problem.
The vertices of the graph are drawn using scatterplot, and I know the
corresponding vertex size (in whatever units scatterplot uses). I'd
like to draw an arrow between the boundaries of the vertices. Is there
a way to shorten a line that originally goes between the centers of two
circles so that the line instead goes between the two boundaries of the
circles? Note that clipping the line isn't an option since I want to
keep the arrowhead on the line instead of clipping it off. I presume
this shortening will have to be done in the drawing routine since it
needs to be independent of zooming since the circles are drawn the same
independent of zooming.
Another related issue is that width of the path used to draw the
arrowhead makes the arrow tip go beyond the endpoint; is there a way to
shorten a line by a certain number of points so that we can account for
that? Also, in drawing the arrowhead, the line pokes through the
arrowhead; I'd like to shorten the shaft to the beginning of the arrowhead.
I think all three of these shortening questions are similar; I'd like to
shorten an arrow in a scale-independent way (i.e., by a certain number
of points or something).
The code I have for the ArrowLine class is below. If people are
interested, I could (eventually, as I have time) incorporate this
functionality into the Line2D class (i.e., putting arrowheads on the
ends of lines).
r"""
A matplotlib subclass to draw an arrowhead on a line.
AUTHORS:
-- Jason Grout (2008-08-19): initial version
"""
############################################################################
# Copyright (C) 2008 Jason Grout <[EMAIL PROTECTED]>
# Released under the terms of the modified BSD License
############################################################################
import matplotlib
from matplotlib.path import Path
from matplotlib.lines import Line2D
import math
import matplotlib.cbook
class ArrowLine(Line2D):
"""
A matplotlib subclass to draw an arrowhead on a line.
EXAMPLE:
sage: import pylab
sage: fig = pylab.figure()
sage: ax = fig.add_subplot(111, autoscale_on=False)
sage: t = [-1,2]
sage: s = [0,-1]
sage: line = ArrowLine(t, s, color='b', ls='-', lw=2,
arrow='>', arrowsize=20)
sage: ax.add_line(line)
sage: ax.set_xlim(-3,3)
(-3, 3)
sage: ax.set_ylim(-3,3)
(-3, 3)
sage: pylab.show()
"""
arrows = {'>' : '_draw_triangle_arrow'}
def __init__(self, *args, **kwargs):
"""Initialize the line and arrow."""
self._arrow = kwargs.pop('arrow', None)
self._arrowsize = kwargs.pop('arrowsize', 2*4)
self._arrowedgecolor = kwargs.pop('arrowedgecolor', 'b')
self._arrowfacecolor = kwargs.pop('arrowfacecolor', 'b')
self._arrowedgewidth = kwargs.pop('arrowedgewidth', 4)
self._arrowheadwidth = kwargs.pop('arrowheadwidth',
self._arrowsize)
self._arrowheadlength = kwargs.pop('arrowheadlength',
self._arrowsize)
Line2D.__init__(self, *args, **kwargs)
def draw(self, renderer):
"""Draw the line and arrowhead using the passed renderer."""
if self._invalid:
self.recache()
renderer.open_group('arrowline2d')
if not self._visible: return
Line2D.draw(self, renderer)
if self._arrow is not None:
gc = renderer.new_gc()
self._set_gc_clip(gc)
gc.set_foreground(self._arrowedgecolor)
gc.set_linewidth(self._arrowedgewidth)
gc.set_alpha(self._alpha)
funcname = self.arrows.get(self._arrow, '_draw_nothing')
if funcname != '_draw_nothing':
tpath, affine =
self._transformed_path.get_transformed_points_and_affine()
arrowFunc = getattr(self, funcname)
arrowFunc(renderer, gc, tpath, affine.frozen())
renderer.close_group('arrowline2d')
_arrow_path = Path([[0.0, 0.0], [-1.0, 1.0], [-1.0, -1.0], [0.0,
0.0]], codes=[Path.MOVETO, Path.LINETO,Path.LINETO, Path.CLOSEPOLY])
def _draw_triangle_arrow(self, renderer, gc, path, path_trans):
"""Draw a triangular arrow."""
segment = [i[0] for i in path.iter_segments()][-2:]
startx,starty = path_trans.transform_point(segment[0])
endx,endy = path_trans.transform_point(segment[1])
angle = math.atan2(endy-starty, endx-startx)
halfwidth = 0.5*renderer.points_to_pixels(self._arrowheadwidth)
length = renderer.points_to_pixels(self._arrowheadlength)
transform =
matplotlib.transforms.Affine2D().scale(length,halfwidth).rotate(angle).translate(endx,endy)
rgbFace = self._get_rgb_arrowface()
renderer.draw_path(gc, self._arrow_path, transform, rgbFace)
def _get_rgb_arrowface(self):
facecolor = self._arrowfacecolor
if matplotlib.cbook.is_string_like(facecolor) and
facecolor.lower()=='none':
rgbFace = None
else:
rgbFace = matplotlib.colors.colorConverter.to_rgb(facecolor)
return rgbFace
-------------------------------------------------------------------------
This SF.Net email is sponsored by the Moblin Your Move Developer's challenge
Build the coolest Linux based applications with Moblin SDK & win great prizes
Grand prize is a trip for two to an Open Source event anywhere in the world
http://moblin-contest.org/redirect.php?banner_id=100&url=/
_______________________________________________
Matplotlib-users mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-users