def _render_on_subplot(self, subplot): """ Render this line on a matplotlib subplot. INPUT: - ``subplot`` -- a matplotlib subplot EXAMPLES: This implicitly calls this function:: sage: line([(1,2), (3,-4), (2, 5), (1,2)]) Graphics object consisting of 1 graphics primitive """ import matplotlib.lines as lines options = dict(self.options()) for o in ('alpha', 'legend_color', 'legend_label', 'linestyle', 'rgbcolor', 'thickness'): if o in options: del options[o] p = lines.Line2D(self.xdata, self.ydata, **options) options = self.options() a = float(options['alpha']) p.set_alpha(a) p.set_linewidth(float(options['thickness'])) p.set_color(to_mpl_color(options['rgbcolor'])) p.set_label(options['legend_label']) # we don't pass linestyle in directly since the drawstyles aren't # pulled off automatically. This (I think) is a bug in matplotlib 1.0.1 if 'linestyle' in options: from sage.plot.misc import get_matplotlib_linestyle p.set_linestyle(get_matplotlib_linestyle(options['linestyle'], return_type='short')) subplot.add_line(p)
def __init__(self, datalist, options): """ Initialize a ``Histogram`` primitive along with its options. EXAMPLES:: sage: from sage.plot.histogram import Histogram sage: Histogram([10,3,5], {'width':0.7}) Histogram defined by a data list of size 3 """ import numpy as np self.datalist = np.asarray(datalist, dtype=float) if 'normed' in options: from sage.misc.superseded import deprecation deprecation( 25260, "the 'normed' option is deprecated. Use 'density' instead.") if 'linestyle' in options: from sage.plot.misc import get_matplotlib_linestyle options['linestyle'] = get_matplotlib_linestyle( options['linestyle'], return_type='long') if options.get('range', None): # numpy.histogram performs type checks on "range" so this must be # actual floats options['range'] = [float(x) for x in options['range']] GraphicPrimitive.__init__(self, options)
def _render_on_subplot(self, subplot): """ TESTS:: sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = patches.Arc( (self.x,self.y), 2.*self.r1, 2.*self.r2, fmod(self.angle,2*pi)*(180./pi), self.s1*(180./pi), self.s2*(180./pi)) p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) z = int(options.pop('zorder',1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) p.set_edgecolor(c) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ TESTS:: sage: C = circle((2,pi), 2, edgecolor='black', facecolor='green', fill=True) """ import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = patches.Circle((float(self.x), float(self.y)), float(self.r), clip_on=options['clip']) if not options['clip']: self._bbox_extra_artists = [p] p.set_linewidth(float(options['thickness'])) p.set_fill(options['fill']) a = float(options['alpha']) p.set_alpha(a) ec = to_mpl_color(options['edgecolor']) fc = to_mpl_color(options['facecolor']) if 'rgbcolor' in options: ec = fc = to_mpl_color(options['rgbcolor']) p.set_edgecolor(ec) p.set_facecolor(fc) p.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_label(options['legend_label']) z = int(options.pop('zorder', 0)) p.set_zorder(z) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES:: This function implicitly ends up rendering this arrow on a matplotlib subplot: sage: arrow(path=[[(0,1), (2,-1), (4,5)]]) """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() width = float(options['width']) head = options.pop('head') if head == 0: style = '<|-' elif head == 1: style = '-|>' elif head == 2: style = '<|-|>' else: raise KeyError('head parameter must be one of 0 (start), 1 (end) or 2 (both).') arrowsize = float(options.get('arrowsize',5)) head_width=arrowsize head_length=arrowsize*2.0 color = to_mpl_color(options['rgbcolor']) from matplotlib.patches import FancyArrowPatch from matplotlib.path import Path bpath = Path(self.vertices, self.codes) p = FancyArrowPatch(path=bpath, lw=width, arrowstyle='%s,head_width=%s,head_length=%s'%(style,head_width, head_length), fc=color, ec=color) p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) p.set_zorder(options['zorder']) p.set_label(options['legend_label']) subplot.add_patch(p) return p
def _render_on_subplot(self, subplot): """ Render this line on a matplotlib subplot. INPUT: - ``subplot`` -- a matplotlib subplot EXAMPLES: This implicitly calls this function:: sage: line([(1,2), (3,-4), (2, 5), (1,2)]) """ import matplotlib.lines as lines options = dict(self.options()) for o in ("alpha", "legend_color", "legend_label", "linestyle", "rgbcolor", "thickness"): if o in options: del options[o] p = lines.Line2D(self.xdata, self.ydata, **options) options = self.options() a = float(options["alpha"]) p.set_alpha(a) p.set_linewidth(float(options["thickness"])) p.set_color(to_mpl_color(options["rgbcolor"])) p.set_label(options["legend_label"]) # we don't pass linestyle in directly since the drawstyles aren't # pulled off automatically. This (I think) is a bug in matplotlib 1.0.1 if "linestyle" in options: from sage.plot.misc import get_matplotlib_linestyle p.set_linestyle(get_matplotlib_linestyle(options["linestyle"], return_type="short")) subplot.add_line(p)
def _render_on_subplot(self, subplot): """ TESTS:: sage: C = circle((2,pi), 2, edgecolor='black', facecolor='green', fill=True) """ import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = patches.Circle((float(self.x), float(self.y)), float(self.r), clip_on=options['clip']) if not options['clip']: self._bbox_extra_artists=[p] p.set_linewidth(float(options['thickness'])) p.set_fill(options['fill']) a = float(options['alpha']) p.set_alpha(a) ec = to_mpl_color(options['edgecolor']) fc = to_mpl_color(options['facecolor']) if 'rgbcolor' in options: ec = fc = to_mpl_color(options['rgbcolor']) p.set_edgecolor(ec) p.set_facecolor(fc) p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) p.set_label(options['legend_label']) z = int(options.pop('zorder', 0)) p.set_zorder(z) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES: This function implicitly ends up rendering this arrow on a matplotlib subplot:: sage: arrow(path=[[(0,1), (2,-1), (4,5)]]) Graphics object consisting of 1 graphics primitive """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() width = float(options['width']) head = options.pop('head') if head == 0: style = '<|-' elif head == 1: style = '-|>' elif head == 2: style = '<|-|>' else: raise KeyError( 'head parameter must be one of 0 (start), 1 (end) or 2 (both).' ) arrowsize = float(options.get('arrowsize', 5)) head_width = arrowsize head_length = arrowsize * 2.0 color = to_mpl_color(options['rgbcolor']) from matplotlib.patches import FancyArrowPatch from matplotlib.path import Path bpath = Path(self.vertices, self.codes) p = FancyArrowPatch(path=bpath, lw=width, arrowstyle='%s,head_width=%s,head_length=%s' % (style, head_width, head_length), fc=color, ec=color, linestyle=get_matplotlib_linestyle( options['linestyle'], return_type='long')) p.set_zorder(options['zorder']) p.set_label(options['legend_label']) subplot.add_patch(p) return p
def _render_on_subplot(self, subplot): """ Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES:: This function implicitly ends up rendering this arrow on a matplotlib subplot: sage: arrow(path=[[(0,1), (2,-1), (4,5)]]) Graphics object consisting of 1 graphics primitive """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() width = float(options["width"]) head = options.pop("head") if head == 0: style = "<|-" elif head == 1: style = "-|>" elif head == 2: style = "<|-|>" else: raise KeyError("head parameter must be one of 0 (start), 1 (end) or 2 (both).") arrowsize = float(options.get("arrowsize", 5)) head_width = arrowsize head_length = arrowsize * 2.0 color = to_mpl_color(options["rgbcolor"]) from matplotlib.patches import FancyArrowPatch from matplotlib.path import Path bpath = Path(self.vertices, self.codes) p = FancyArrowPatch( path=bpath, lw=width, arrowstyle="%s,head_width=%s,head_length=%s" % (style, head_width, head_length), fc=color, ec=color, ) p.set_linestyle(get_matplotlib_linestyle(options["linestyle"], return_type="long")) p.set_zorder(options["zorder"]) p.set_label(options["legend_label"]) subplot.add_patch(p) return p
def __init__(self, datalist, options): """ Initialize a ``Histogram`` primitive along with its options. EXAMPLES:: sage: from sage.plot.histogram import Histogram sage: Histogram([10,3,5], {'width':0.7}) Histogram defined by a data list of size 3 """ import numpy as np self.datalist=np.asarray(datalist,dtype=float) if 'linestyle' in options: from sage.plot.misc import get_matplotlib_linestyle options['linestyle'] = get_matplotlib_linestyle( options['linestyle'], return_type='long') GraphicPrimitive.__init__(self, options)
def __init__(self, datalist, options): """ Initialize a ``Histogram`` primitive along with its options. EXAMPLES:: sage: from sage.plot.histogram import Histogram sage: Histogram([10,3,5], {'width':0.7}) Histogram defined by a data list of size 3 """ import numpy as np self.datalist = np.asarray(datalist, dtype=float) if 'linestyle' in options: from sage.plot.misc import get_matplotlib_linestyle options['linestyle'] = get_matplotlib_linestyle( options['linestyle'], return_type='long') GraphicPrimitive.__init__(self, options)
def _render_on_subplot(self, subplot): """ Render this Bezier path in a subplot. This is the key function that defines how this Bezier path graphics primitive is rendered in matplotlib's library. TESTS:: sage: bezier_path([[(0,1),(.5,0),(1,1)]]) Graphics object consisting of 1 graphics primitive :: sage: bezier_path([[(0,1),(.5,0),(1,1),(-3,5)]]) Graphics object consisting of 1 graphics primitive """ from matplotlib.patches import PathPatch from matplotlib.path import Path from sage.plot.misc import get_matplotlib_linestyle options = dict(self.options()) del options['alpha'] del options['thickness'] del options['rgbcolor'] del options['zorder'] del options['fill'] del options['linestyle'] bpath = Path(self.vertices, self.codes) bpatch = PathPatch(bpath, **options) options = self.options() bpatch.set_linewidth(float(options['thickness'])) bpatch.set_fill(options['fill']) bpatch.set_zorder(options['zorder']) a = float(options['alpha']) bpatch.set_alpha(a) c = to_mpl_color(options['rgbcolor']) bpatch.set_edgecolor(c) bpatch.set_facecolor(c) bpatch.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) subplot.add_patch(bpatch)
def _render_on_subplot(self, subplot): """ Render this Bezier path in a subplot. This is the key function that defines how this Bezier path graphics primitive is rendered in matplotlib's library. TESTS:: sage: bezier_path([[(0,1),(.5,0),(1,1)]]) Graphics object consisting of 1 graphics primitive :: sage: bezier_path([[(0,1),(.5,0),(1,1),(-3,5)]]) Graphics object consisting of 1 graphics primitive """ from matplotlib.patches import PathPatch from matplotlib.path import Path from sage.plot.misc import get_matplotlib_linestyle options = dict(self.options()) del options['alpha'] del options['thickness'] del options['rgbcolor'] del options['zorder'] del options['fill'] del options['linestyle'] bpath = Path(self.vertices, self.codes) bpatch = PathPatch(bpath, **options) options = self.options() bpatch.set_linewidth(float(options['thickness'])) bpatch.set_fill(options['fill']) bpatch.set_zorder(options['zorder']) a = float(options['alpha']) bpatch.set_alpha(a) c = to_mpl_color(options['rgbcolor']) bpatch.set_edgecolor(c) bpatch.set_facecolor(c) bpatch.set_linestyle(get_matplotlib_linestyle(options['linestyle'], return_type='long')) subplot.add_patch(bpatch)
def _render_on_subplot(self, subplot): """ TESTS:: sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = self._matplotlib_arc() p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) z = int(options.pop('zorder', 1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) p.set_linestyle(get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_edgecolor(c) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ TESTS:: sage: A = arc((1,1),3,4,pi/4,(pi,4*pi/3)); A Graphics object consisting of 1 graphics primitive """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = self._matplotlib_arc() p.set_linewidth(float(options['thickness'])) a = float(options['alpha']) p.set_alpha(a) z = int(options.pop('zorder', 1)) p.set_zorder(z) c = to_mpl_color(options['rgbcolor']) p.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_edgecolor(c) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ Render this ellipse in a subplot. This is the key function that defines how this ellipse graphics primitive is rendered in matplotlib's library. TESTS:: sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3) Graphics object consisting of 1 graphics primitive :: sage: ellipse((3,2),1,2) Graphics object consisting of 1 graphics primitive """ import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = patches.Ellipse((self.x, self.y), self.r1 * 2., self.r2 * 2., self.angle / pi * 180.) p.set_linewidth(float(options['thickness'])) p.set_fill(options['fill']) a = float(options['alpha']) p.set_alpha(a) ec = to_mpl_color(options['edgecolor']) fc = to_mpl_color(options['facecolor']) if 'rgbcolor' in options: ec = fc = to_mpl_color(options['rgbcolor']) p.set_edgecolor(ec) p.set_facecolor(fc) p.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_label(options['legend_label']) z = int(options.pop('zorder', 0)) p.set_zorder(z) subplot.add_patch(p)
def _render_on_subplot(self, subplot): """ Render this ellipse in a subplot. This is the key function that defines how this ellipse graphics primitive is rendered in matplotlib's library. TESTS:: sage: ellipse((0,0),3,1,pi/6,fill=True,alpha=0.3) Graphics object consisting of 1 graphics primitive :: sage: ellipse((3,2),1,2) Graphics object consisting of 1 graphics primitive """ import matplotlib.patches as patches from sage.plot.misc import get_matplotlib_linestyle options = self.options() p = patches.Ellipse( (self.x,self.y), self.r1*2.,self.r2*2.,self.angle/pi*180.) p.set_linewidth(float(options['thickness'])) p.set_fill(options['fill']) a = float(options['alpha']) p.set_alpha(a) ec = to_mpl_color(options['edgecolor']) fc = to_mpl_color(options['facecolor']) if 'rgbcolor' in options: ec = fc = to_mpl_color(options['rgbcolor']) p.set_edgecolor(ec) p.set_facecolor(fc) p.set_linestyle(get_matplotlib_linestyle(options['linestyle'],return_type='long')) p.set_label(options['legend_label']) z = int(options.pop('zorder', 0)) p.set_zorder(z) subplot.add_patch(p)
def _render_on_subplot(self, subplot): r""" Render this arrow in a subplot. This version of the method uses a narrower arrow head, which is not customizable by parameters in the Sage class. """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() head = options.pop('head') if head == 0: style = '<|-' elif head == 1: style = '-|>' elif head == 2: style = '<|-|>' else: raise KeyError('head parameter must be one of 0 (start), 1 (end) or 2 (both).') style='fancy' width = float(options['width']) arrowshorten_end = float(options.get('arrowshorten', 0)) / 2.0 arrowsize = float(options.get('arrowsize', 5)) #head_width = arrowsize * 0.5 head_width = arrowsize * 0.7 tail_width = arrowsize * 0.7 head_length = arrowsize * 2.0 color = to_mpl_color(options['rgbcolor']) from matplotlib.patches import FancyArrowPatch p = FancyArrowPatch((self.xtail, self.ytail), (self.xhead, self.yhead), lw=width, arrowstyle='%s,head_width=%s,head_length=%s,tail_width=%s' % (style, head_width, head_length, tail_width), shrinkA=arrowshorten_end, shrinkB=arrowshorten_end, fc=color, ec=color, linestyle=get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_zorder(options['zorder']) p.set_label(options['legend_label']) subplot.add_patch(p) return p
def _render_on_subplot(self, subplot): """ TESTS: A somewhat random plot, but fun to look at:: sage: x,y = var('x,y') sage: contour_plot(x^2-y^3+10*sin(x*y), (x, -4, 4), (y, -4, 4),plot_points=121,cmap='hsv') Graphics object consisting of 1 graphics primitive """ from sage.rings.integer import Integer options = self.options() fill = options['fill'] contours = options['contours'] if 'cmap' in options: cmap = get_cmap(options['cmap']) elif fill or contours is None: cmap = get_cmap('gray') else: if isinstance(contours, (int, Integer)): cmap = get_cmap([(i, i, i) for i in xsrange(0, 1, 1 / contours)]) else: l = Integer(len(contours)) cmap = get_cmap([(i, i, i) for i in xsrange(0, 1, 1 / l)]) x0, x1 = float(self.xrange[0]), float(self.xrange[1]) y0, y1 = float(self.yrange[0]), float(self.yrange[1]) if isinstance(contours, (int, Integer)): contours = int(contours) CSF = None if fill: if contours is None: CSF = subplot.contourf(self.xy_data_array, cmap=cmap, extent=(x0, x1, y0, y1), label=options['legend_label']) else: CSF = subplot.contourf(self.xy_data_array, contours, cmap=cmap, extent=(x0, x1, y0, y1), extend='both', label=options['legend_label']) linewidths = options.get('linewidths', None) if isinstance(linewidths, (int, Integer)): linewidths = int(linewidths) elif isinstance(linewidths, (list, tuple)): linewidths = tuple(int(x) for x in linewidths) from sage.plot.misc import get_matplotlib_linestyle linestyles = options.get('linestyles', None) if isinstance(linestyles, (list, tuple)): linestyles = [ get_matplotlib_linestyle(l, 'long') for l in linestyles ] else: linestyles = get_matplotlib_linestyle(linestyles, 'long') if contours is None: CS = subplot.contour(self.xy_data_array, cmap=cmap, extent=(x0, x1, y0, y1), linewidths=linewidths, linestyles=linestyles, label=options['legend_label']) else: CS = subplot.contour(self.xy_data_array, contours, cmap=cmap, extent=(x0, x1, y0, y1), linewidths=linewidths, linestyles=linestyles, label=options['legend_label']) if options.get('labels', False): label_options = options['label_options'] label_options['fontsize'] = int(label_options['fontsize']) if fill and label_options is None: label_options['inline'] = False subplot.clabel(CS, **label_options) if options.get('colorbar', False): colorbar_options = options['colorbar_options'] from matplotlib import colorbar cax, kwds = colorbar.make_axes_gridspec(subplot, **colorbar_options) if CSF is None: cb = colorbar.Colorbar(cax, CS, **kwds) else: cb = colorbar.Colorbar(cax, CSF, **kwds) cb.add_lines(CS)
def _render_on_subplot(self, subplot): """ TESTS: A somewhat random plot, but fun to look at:: sage: x,y = var('x,y') sage: contour_plot(x^2-y^3+10*sin(x*y), (x, -4, 4), (y, -4, 4),plot_points=121,cmap='hsv') """ from sage.rings.integer import Integer options = self.options() fill = options['fill'] contours = options['contours'] if options.has_key('cmap'): cmap = get_cmap(options['cmap']) elif fill or contours is None: cmap = get_cmap('gray') else: if isinstance(contours, (int, Integer)): cmap = get_cmap([(i,i,i) for i in xsrange(0,1,1/contours)]) else: l = Integer(len(contours)) cmap = get_cmap([(i,i,i) for i in xsrange(0,1,1/l)]) x0,x1 = float(self.xrange[0]), float(self.xrange[1]) y0,y1 = float(self.yrange[0]), float(self.yrange[1]) if isinstance(contours, (int, Integer)): contours = int(contours) CSF=None if fill: if contours is None: CSF=subplot.contourf(self.xy_data_array, cmap=cmap, extent=(x0,x1,y0,y1), label=options['legend_label']) else: CSF=subplot.contourf(self.xy_data_array, contours, cmap=cmap, extent=(x0,x1,y0,y1),extend='both', label=options['legend_label']) linewidths = options.get('linewidths',None) if isinstance(linewidths, (int, Integer)): linewidths = int(linewidths) elif isinstance(linewidths, (list, tuple)): linewidths = tuple(int(x) for x in linewidths) from sage.plot.misc import get_matplotlib_linestyle linestyles = options.get('linestyles', None) if isinstance(linestyles, (list, tuple)): linestyles = [get_matplotlib_linestyle(l, 'long') for l in linestyles] else: linestyles = get_matplotlib_linestyle(linestyles, 'long') if contours is None: CS = subplot.contour(self.xy_data_array, cmap=cmap, extent=(x0,x1,y0,y1), linewidths=linewidths, linestyles=linestyles, label=options['legend_label']) else: CS = subplot.contour(self.xy_data_array, contours, cmap=cmap, extent=(x0,x1,y0,y1), linewidths=linewidths, linestyles=linestyles, label=options['legend_label']) if options.get('labels', False): label_options = options['label_options'] label_options['fontsize'] = int(label_options['fontsize']) if fill and label_options is None: label_options['inline']=False subplot.clabel(CS, **label_options) if options.get('colorbar', False): colorbar_options = options['colorbar_options'] from matplotlib import colorbar cax,kwds=colorbar.make_axes_gridspec(subplot,**colorbar_options) if CSF is None: cb=colorbar.Colorbar(cax,CS, **kwds) else: cb=colorbar.Colorbar(cax,CSF, **kwds) cb.add_lines(CS)
def set_edges(self, **edge_options): """ Sets the edge (or arrow) plotting parameters for the ``GraphPlot`` object. This function is called by the constructor but can also be called to make updates to the vertex options of an existing ``GraphPlot`` object. Note that the changes are cumulative. EXAMPLES:: sage: g = Graph({}, loops=True, multiedges=True, sparse=True) sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 22 graphics primitives sage: GP.set_edges(edge_color='black') sage: GP.plot() Graphics object consisting of 22 graphics primitives sage: d = DiGraph({}, loops=True, multiedges=True, sparse=True) sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 24 graphics primitives sage: GP.set_edges(edge_color='black') sage: GP.plot() Graphics object consisting of 24 graphics primitives TESTS:: sage: G = Graph("Fooba") sage: G.show(edge_colors={'red':[(3,6),(2,5)]}) Verify that default edge labels are pretty close to being between the vertices in some cases where they weren't due to truncating division (:trac:`10124`):: sage: test_graphs = graphs.FruchtGraph(), graphs.BullGraph() sage: tol = 0.001 sage: for G in test_graphs: ... E=G.edges() ... for e0, e1, elab in E: ... G.set_edge_label(e0, e1, '%d %d' % (e0, e1)) ... gp = G.graphplot(save_pos=True,edge_labels=True) ... vx = gp._plot_components['vertices'][0].xdata ... vy = gp._plot_components['vertices'][0].ydata ... for elab in gp._plot_components['edge_labels']: ... textobj = elab[0] ... x, y, s = textobj.x, textobj.y, textobj.string ... v0, v1 = map(int, s.split()) ... vn = vector(((x-(vx[v0]+vx[v1])/2.),y-(vy[v0]+vy[v1])/2.)).norm() ... assert vn < tol """ for arg in edge_options: self._options[arg] = edge_options[arg] if 'edge_colors' in edge_options: self._options['color_by_label'] = False # Handle base edge options: thickness, linestyle eoptions = {} if 'edge_style' in self._options: from sage.plot.misc import get_matplotlib_linestyle eoptions['linestyle'] = get_matplotlib_linestyle( self._options['edge_style'], return_type='long') if 'thickness' in self._options: eoptions['thickness'] = self._options['thickness'] # Set labels param to add labels on the fly labels = False if self._options['edge_labels']: labels = True self._plot_components['edge_labels'] = [] # Make dict collection of all edges (keep label and edge color) edges_to_draw = {} if self._options['color_by_label'] or isinstance( self._options['edge_colors'], dict): if self._options['color_by_label']: edge_colors = self._graph._color_by_label( format=self._options['color_by_label']) else: edge_colors = self._options['edge_colors'] for color in edge_colors: for edge in edge_colors[color]: key = tuple(sorted([edge[0], edge[1]])) if key == (edge[0], edge[1]): head = 1 else: head = 0 if len(edge) < 3: label = self._graph.edge_label(edge[0], edge[1]) if isinstance(label, list): if key in edges_to_draw: edges_to_draw[key].append( (label[-1], color, head)) else: edges_to_draw[key] = [(label[-1], color, head)] for i in range(len(label) - 1): edges_to_draw[key].append( (label[-1], color, head)) else: label = edge[2] if key in edges_to_draw: edges_to_draw[key].append((label, color, head)) else: edges_to_draw[key] = [(label, color, head)] # add unspecified edges in (default color black) for edge in self._graph.edge_iterator(): key = tuple(sorted([edge[0], edge[1]])) label = edge[2] specified = False if key in edges_to_draw: for old_label, old_color, old_head in edges_to_draw[key]: if label == old_label: specified = True break if not specified: if key == (edge[0], edge[1]): head = 1 else: head = 0 edges_to_draw[key] = [(label, 'black', head)] else: for edge in self._graph.edges(sort=True): key = tuple(sorted([edge[0], edge[1]])) if key == (edge[0], edge[1]): head = 1 else: head = 0 if key in edges_to_draw: edges_to_draw[key].append( (edge[2], self._options['edge_color'], head)) else: edges_to_draw[key] = [(edge[2], self._options['edge_color'], head)] if edges_to_draw: self._plot_components['edges'] = [] else: return # Check for multi-edges or loops if self._arcs or self._loops: tmp = edges_to_draw.copy() dist = self._options['dist'] * 2. loop_size = self._options['loop_size'] max_dist = self._options['max_dist'] from sage.functions.all import sqrt for (a, b) in tmp: if a == b: # Loops distance = dist local_labels = edges_to_draw.pop((a, b)) if len(local_labels) * dist > max_dist: distance = float(max_dist) / len(local_labels) curr_loop_size = loop_size for i in range(len(local_labels)): self._plot_components['edges'].append( circle((self._pos[a][0], self._pos[a][1] - curr_loop_size), curr_loop_size, rgbcolor=local_labels[i][1], **eoptions)) if labels: self._plot_components['edge_labels'].append( text(local_labels[i][0], (self._pos[a][0], self._pos[a][1] - 2 * curr_loop_size))) curr_loop_size += distance / 4 elif len(edges_to_draw[(a, b)]) > 1: # Multi-edge local_labels = edges_to_draw.pop((a, b)) # Compute perpendicular bisector p1 = self._pos[a] p2 = self._pos[b] M = ( (p1[0] + p2[0]) / 2., (p1[1] + p2[1]) / 2.) # midpoint if not p1[1] == p2[1]: S = float(p1[0] - p2[0]) / (p2[1] - p1[1] ) # perp slope y = lambda x: S * x - S * M[0] + M[ 1] # perp bisector line # f,g are functions of distance d to determine x values # on line y at d from point M f = lambda d: sqrt(d**2 / (1. + S**2)) + M[0] g = lambda d: -sqrt(d**2 / (1. + S**2)) + M[0] odd_x = f even_x = g if p1[0] == p2[0]: odd_y = lambda d: M[1] even_y = odd_y else: odd_y = lambda x: y(f(x)) even_y = lambda x: y(g(x)) else: odd_x = lambda d: M[0] even_x = odd_x odd_y = lambda d: M[1] + d even_y = lambda d: M[1] - d # We now have the control points for each bezier curve # in terms of distance parameter d. # Also note that the label for each edge should be drawn at d/2. # (This is because we're using the perp bisectors). distance = dist if len(local_labels) * dist > max_dist: distance = float(max_dist) / len(local_labels) for i in range(len(local_labels) // 2): k = (i + 1.0) * distance if self._arcdigraph: odd_start = self._polar_hack_for_multidigraph( p1, [odd_x(k), odd_y(k)], self._vertex_radius)[0] odd_end = self._polar_hack_for_multidigraph( [odd_x(k), odd_y(k)], p2, self._vertex_radius)[1] even_start = self._polar_hack_for_multidigraph( p1, [even_x(k), even_y(k)], self._vertex_radius)[0] even_end = self._polar_hack_for_multidigraph( [even_x(k), even_y(k)], p2, self._vertex_radius)[1] self._plot_components['edges'].append( arrow(path=[[ odd_start, [odd_x(k), odd_y(k)], odd_end ]], head=local_labels[2 * i][2], zorder=1, rgbcolor=local_labels[2 * i][1], **eoptions)) self._plot_components['edges'].append( arrow(path=[[ even_start, [even_x(k), even_y(k)], even_end ]], head=local_labels[2 * i + 1][2], zorder=1, rgbcolor=local_labels[2 * i + 1][1], **eoptions)) else: self._plot_components['edges'].append( bezier_path( [[p1, [odd_x(k), odd_y(k)], p2]], zorder=1, rgbcolor=local_labels[2 * i][1], **eoptions)) self._plot_components['edges'].append( bezier_path( [[p1, [even_x(k), even_y(k)], p2]], zorder=1, rgbcolor=local_labels[2 * i + 1][1], **eoptions)) if labels: j = k / 2.0 self._plot_components['edge_labels'].append( text(local_labels[2 * i][0], [odd_x(j), odd_y(j)])) self._plot_components['edge_labels'].append( text(local_labels[2 * i + 1][0], [even_x(j), even_y(j)])) if len(local_labels) % 2 == 1: edges_to_draw[(a, b)] = [local_labels[-1] ] # draw line for last odd dir = self._graph.is_directed() for (a, b) in edges_to_draw: if self._arcdigraph: C, D = self._polar_hack_for_multidigraph( self._pos[a], self._pos[b], self._vertex_radius) self._plot_components['edges'].append( arrow(C, D, rgbcolor=edges_to_draw[(a, b)][0][1], head=edges_to_draw[(a, b)][0][2], **eoptions)) if labels: self._plot_components['edge_labels'].append( text(str(edges_to_draw[(a, b)][0][0]), [(C[0] + D[0]) / 2., (C[1] + D[1]) / 2.])) elif dir: self._plot_components['edges'].append( arrow(self._pos[a], self._pos[b], rgbcolor=edges_to_draw[(a, b)][0][1], arrowshorten=self._arrowshorten, head=edges_to_draw[(a, b)][0][2], **eoptions)) else: self._plot_components['edges'].append( line([self._pos[a], self._pos[b]], rgbcolor=edges_to_draw[(a, b)][0][1], **eoptions)) if labels and not self._arcdigraph: self._plot_components['edge_labels'].append( text(str(edges_to_draw[(a, b)][0][0]), [(self._pos[a][0] + self._pos[b][0]) / 2., (self._pos[a][1] + self._pos[b][1]) / 2.]))
def _render_on_subplot(self, subplot): r""" Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES: This function implicitly ends up rendering this arrow on a matplotlib subplot:: sage: arrow((0,1), (2,-1)) Graphics object consisting of 1 graphics primitive TESTS: The length of the ends (shrinkA and shrinkB) should not depend on the width of the arrow, because Matplotlib already takes this into account. See :trac:`12836`:: sage: fig = Graphics().matplotlib() sage: sp = fig.add_subplot(1,1,1) sage: a = arrow((0,0), (1,1)) sage: b = arrow((0,0), (1,1), width=20) sage: p1 = a[0]._render_on_subplot(sp) sage: p2 = b[0]._render_on_subplot(sp) sage: p1.shrinkA == p2.shrinkA True sage: p1.shrinkB == p2.shrinkB True Dashed arrows should have solid arrowheads, :trac:`12852`. This test saves the plot of a dashed arrow to an EPS file. Within the EPS file, ``stroke`` will be called twice: once to draw the line, and again to draw the arrowhead. We check that both calls do not occur while the dashed line style is enabled:: sage: a = arrow((0,0), (1,1), linestyle='dashed') sage: filename = tmp_filename(ext='.eps') sage: a.save(filename=filename) sage: with open(filename, 'r') as f: ....: contents = f.read().replace('\n', ' ') sage: two_stroke_pattern = r'setdash.*stroke.*stroke.*setdash' sage: import re sage: two_stroke_re = re.compile(two_stroke_pattern) sage: two_stroke_re.search(contents) is None True """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() head = options.pop('head') if head == 0: style = '<|-' elif head == 1: style = '-|>' elif head == 2: style = '<|-|>' else: raise KeyError( 'head parameter must be one of 0 (start), 1 (end) or 2 (both).' ) width = float(options['width']) arrowshorten_end = float(options.get('arrowshorten', 0)) / 2.0 arrowsize = float(options.get('arrowsize', 5)) head_width = arrowsize head_length = arrowsize * 2.0 color = to_mpl_color(options['rgbcolor']) from matplotlib.patches import FancyArrowPatch p = FancyArrowPatch((self.xtail, self.ytail), (self.xhead, self.yhead), lw=width, arrowstyle='%s,head_width=%s,head_length=%s' % (style, head_width, head_length), shrinkA=arrowshorten_end, shrinkB=arrowshorten_end, fc=color, ec=color) p.set_linestyle( get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_zorder(options['zorder']) p.set_label(options['legend_label']) if options['linestyle'] != 'solid': # The next few lines work around a design issue in matplotlib. Currently, the specified # linestyle is used to draw both the path and the arrowhead. If linestyle is 'dashed', this # looks really odd. This code is from Jae-Joon Lee in response to a post to the matplotlib mailing # list. See http://sourceforge.net/mailarchive/forum.php?thread_name=CAG%3DuJ%2Bnw2dE05P9TOXTz_zp-mGP3cY801vMH7yt6vgP9_WzU8w%40mail.gmail.com&forum_name=matplotlib-users import matplotlib.patheffects as pe class CheckNthSubPath(object): def __init__(self, patch, n): """ creates an callable object that returns True if the provided path is the n-th path from the patch. """ self._patch = patch self._n = n def get_paths(self, renderer): self._patch.set_dpi_cor(renderer.points_to_pixels(1.)) paths, fillables = self._patch.get_path_in_displaycoord() return paths def __call__(self, renderer, gc, tpath, affine, rgbFace): path = self.get_paths(renderer)[self._n] vert1, code1 = path.vertices, path.codes import numpy as np return np.array_equal(vert1, tpath.vertices) and np.array_equal( code1, tpath.codes) class ConditionalStroke(pe.RendererBase): def __init__(self, condition_func, pe_list): """ path effect that is only applied when the condition_func returns True. """ super(ConditionalStroke, self).__init__() self._pe_list = pe_list self._condition_func = condition_func def draw_path(self, renderer, gc, tpath, affine, rgbFace): if self._condition_func(renderer, gc, tpath, affine, rgbFace): for pe1 in self._pe_list: pe1.draw_path(renderer, gc, tpath, affine, rgbFace) pe1 = ConditionalStroke(CheckNthSubPath(p, 0), [pe.Stroke()]) pe2 = ConditionalStroke(CheckNthSubPath(p, 1), [pe.Stroke(linestyle="solid")]) p.set_path_effects([pe1, pe2]) subplot.add_patch(p) return p
def set_edges(self, **edge_options): """ Sets the edge (or arrow) plotting parameters for the ``GraphPlot`` object. This function is called by the constructor but can also be called to make updates to the vertex options of an existing ``GraphPlot`` object. Note that the changes are cumulative. EXAMPLES:: sage: g = Graph({}, loops=True, multiedges=True, sparse=True) sage: g.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: GP = g.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 22 graphics primitives sage: GP.set_edges(edge_color='black') sage: GP.plot() Graphics object consisting of 22 graphics primitives sage: d = DiGraph({}, loops=True, multiedges=True, sparse=True) sage: d.add_edges([(0,0,'a'),(0,0,'b'),(0,1,'c'),(0,1,'d'), ... (0,1,'e'),(0,1,'f'),(0,1,'f'),(2,1,'g'),(2,2,'h')]) sage: GP = d.graphplot(vertex_size=100, edge_labels=True, color_by_label=True, edge_style='dashed') sage: GP.set_edges(edge_style='solid') sage: GP.plot() Graphics object consisting of 24 graphics primitives sage: GP.set_edges(edge_color='black') sage: GP.plot() Graphics object consisting of 24 graphics primitives TESTS:: sage: G = Graph("Fooba") sage: G.show(edge_colors={'red':[(3,6),(2,5)]}) Verify that default edge labels are pretty close to being between the vertices in some cases where they weren't due to truncating division (:trac:`10124`):: sage: test_graphs = graphs.FruchtGraph(), graphs.BullGraph() sage: tol = 0.001 sage: for G in test_graphs: ... E=G.edges() ... for e0, e1, elab in E: ... G.set_edge_label(e0, e1, '%d %d' % (e0, e1)) ... gp = G.graphplot(save_pos=True,edge_labels=True) ... vx = gp._plot_components['vertices'][0].xdata ... vy = gp._plot_components['vertices'][0].ydata ... for elab in gp._plot_components['edge_labels']: ... textobj = elab[0] ... x, y, s = textobj.x, textobj.y, textobj.string ... v0, v1 = map(int, s.split()) ... vn = vector(((x-(vx[v0]+vx[v1])/2.),y-(vy[v0]+vy[v1])/2.)).norm() ... assert vn < tol """ for arg in edge_options: self._options[arg] = edge_options[arg] if 'edge_colors' in edge_options: self._options['color_by_label'] = False # Handle base edge options: thickness, linestyle eoptions={} if 'edge_style' in self._options: from sage.plot.misc import get_matplotlib_linestyle eoptions['linestyle'] = get_matplotlib_linestyle( self._options['edge_style'], return_type='long') if 'thickness' in self._options: eoptions['thickness'] = self._options['thickness'] # Set labels param to add labels on the fly labels = False if self._options['edge_labels']: labels = True self._plot_components['edge_labels'] = [] # Make dict collection of all edges (keep label and edge color) edges_to_draw = {} if self._options['color_by_label'] or isinstance(self._options['edge_colors'], dict): if self._options['color_by_label']: edge_colors = self._graph._color_by_label(format=self._options['color_by_label']) else: edge_colors = self._options['edge_colors'] for color in edge_colors: for edge in edge_colors[color]: key = tuple(sorted([edge[0],edge[1]])) if key == (edge[0],edge[1]): head = 1 else: head = 0 if len(edge) < 3: label = self._graph.edge_label(edge[0],edge[1]) if isinstance(label, list): if key in edges_to_draw: edges_to_draw[key].append((label[-1], color, head)) else: edges_to_draw[key] = [(label[-1], color, head)] for i in range(len(label)-1): edges_to_draw[key].append((label[-1], color, head)) else: label = edge[2] if key in edges_to_draw: edges_to_draw[key].append((label, color, head)) else: edges_to_draw[key] = [(label, color, head)] # add unspecified edges in (default color black) for edge in self._graph.edge_iterator(): key = tuple(sorted([edge[0],edge[1]])) label = edge[2] specified = False if key in edges_to_draw: for old_label, old_color, old_head in edges_to_draw[key]: if label == old_label: specified = True break if not specified: if key == (edge[0],edge[1]): head = 1 else: head = 0 edges_to_draw[key] = [(label, 'black', head)] else: for edge in self._graph.edges(sort=True): key = tuple(sorted([edge[0],edge[1]])) if key == (edge[0],edge[1]): head = 1 else: head = 0 if key in edges_to_draw: edges_to_draw[key].append((edge[2], self._options['edge_color'], head)) else: edges_to_draw[key] = [(edge[2], self._options['edge_color'], head)] if edges_to_draw: self._plot_components['edges'] = [] else: return # Check for multi-edges or loops if self._arcs or self._loops: tmp = edges_to_draw.copy() dist = self._options['dist']*2. loop_size = self._options['loop_size'] max_dist = self._options['max_dist'] from sage.functions.all import sqrt for (a,b) in tmp: if a == b: # Loops distance = dist local_labels = edges_to_draw.pop((a,b)) if len(local_labels)*dist > max_dist: distance = float(max_dist)/len(local_labels) curr_loop_size = loop_size for i in range(len(local_labels)): self._plot_components['edges'].append(circle((self._pos[a][0],self._pos[a][1]-curr_loop_size), curr_loop_size, rgbcolor=local_labels[i][1], **eoptions)) if labels: self._plot_components['edge_labels'].append(text(local_labels[i][0], (self._pos[a][0], self._pos[a][1]-2*curr_loop_size))) curr_loop_size += distance/4 elif len(edges_to_draw[(a,b)]) > 1: # Multi-edge local_labels = edges_to_draw.pop((a,b)) # Compute perpendicular bisector p1 = self._pos[a] p2 = self._pos[b] M = ((p1[0]+p2[0])/2., (p1[1]+p2[1])/2.) # midpoint if not p1[1] == p2[1]: S = float(p1[0]-p2[0])/(p2[1]-p1[1]) # perp slope y = lambda x : S*x-S*M[0]+M[1] # perp bisector line # f,g are functions of distance d to determine x values # on line y at d from point M f = lambda d : sqrt(d**2/(1.+S**2)) + M[0] g = lambda d : -sqrt(d**2/(1.+S**2)) + M[0] odd_x = f even_x = g if p1[0] == p2[0]: odd_y = lambda d : M[1] even_y = odd_y else: odd_y = lambda x : y(f(x)) even_y = lambda x : y(g(x)) else: odd_x = lambda d : M[0] even_x = odd_x odd_y = lambda d : M[1] + d even_y = lambda d : M[1] - d # We now have the control points for each bezier curve # in terms of distance parameter d. # Also note that the label for each edge should be drawn at d/2. # (This is because we're using the perp bisectors). distance = dist if len(local_labels)*dist > max_dist: distance = float(max_dist)/len(local_labels) for i in range(len(local_labels)//2): k = (i+1.0)*distance if self._arcdigraph: odd_start = self._polar_hack_for_multidigraph(p1, [odd_x(k),odd_y(k)], self._vertex_radius)[0] odd_end = self._polar_hack_for_multidigraph([odd_x(k),odd_y(k)], p2, self._vertex_radius)[1] even_start = self._polar_hack_for_multidigraph(p1, [even_x(k),even_y(k)], self._vertex_radius)[0] even_end = self._polar_hack_for_multidigraph([even_x(k),even_y(k)], p2, self._vertex_radius)[1] self._plot_components['edges'].append(arrow(path=[[odd_start,[odd_x(k),odd_y(k)],odd_end]], head=local_labels[2*i][2], zorder=1, rgbcolor=local_labels[2*i][1], **eoptions)) self._plot_components['edges'].append(arrow(path=[[even_start,[even_x(k),even_y(k)],even_end]], head=local_labels[2*i+1][2], zorder=1, rgbcolor=local_labels[2*i+1][1], **eoptions)) else: self._plot_components['edges'].append(bezier_path([[p1,[odd_x(k),odd_y(k)],p2]],zorder=1, rgbcolor=local_labels[2*i][1], **eoptions)) self._plot_components['edges'].append(bezier_path([[p1,[even_x(k),even_y(k)],p2]],zorder=1, rgbcolor=local_labels[2*i+1][1], **eoptions)) if labels: j = k/2.0 self._plot_components['edge_labels'].append(text(local_labels[2*i][0],[odd_x(j),odd_y(j)])) self._plot_components['edge_labels'].append(text(local_labels[2*i+1][0],[even_x(j),even_y(j)])) if len(local_labels)%2 == 1: edges_to_draw[(a,b)] = [local_labels[-1]] # draw line for last odd dir = self._graph.is_directed() for (a,b) in edges_to_draw: if self._arcdigraph: C,D = self._polar_hack_for_multidigraph(self._pos[a], self._pos[b], self._vertex_radius) self._plot_components['edges'].append(arrow(C,D, rgbcolor=edges_to_draw[(a,b)][0][1], head=edges_to_draw[(a,b)][0][2], **eoptions)) if labels: self._plot_components['edge_labels'].append(text(str(edges_to_draw[(a,b)][0][0]),[(C[0]+D[0])/2., (C[1]+D[1])/2.])) elif dir: self._plot_components['edges'].append(arrow(self._pos[a],self._pos[b], rgbcolor=edges_to_draw[(a,b)][0][1], arrowshorten=self._arrowshorten, head=edges_to_draw[(a,b)][0][2], **eoptions)) else: self._plot_components['edges'].append(line([self._pos[a],self._pos[b]], rgbcolor=edges_to_draw[(a,b)][0][1], **eoptions)) if labels and not self._arcdigraph: self._plot_components['edge_labels'].append(text(str(edges_to_draw[(a,b)][0][0]),[(self._pos[a][0]+self._pos[b][0])/2., (self._pos[a][1]+self._pos[b][1])/2.]))
def _render_on_subplot(self, subplot): r""" Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES: This function implicitly ends up rendering this arrow on a matplotlib subplot:: sage: arrow((0,1), (2,-1)) Graphics object consisting of 1 graphics primitive TESTS: The length of the ends (shrinkA and shrinkB) should not depend on the width of the arrow, because Matplotlib already takes this into account. See :trac:`12836`:: sage: fig = Graphics().matplotlib() sage: sp = fig.add_subplot(1,1,1, label='axis1') sage: a = arrow((0,0), (1,1)) sage: b = arrow((0,0), (1,1), width=20) sage: p1 = a[0]._render_on_subplot(sp) sage: p2 = b[0]._render_on_subplot(sp) sage: p1.shrinkA == p2.shrinkA True sage: p1.shrinkB == p2.shrinkB True Dashed arrows should have solid arrowheads, :trac:`12852`. We tried to make up a test for this, which turned out to be fragile and hence was removed. In general, robust testing of graphics seems basically need a human eye or AI. """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() head = options.pop('head') if head == 0: style = '<|-' elif head == 1: style = '-|>' elif head == 2: style = '<|-|>' else: raise KeyError( 'head parameter must be one of 0 (start), 1 (end) or 2 (both).' ) width = float(options['width']) arrowshorten_end = float(options.get('arrowshorten', 0)) / 2.0 arrowsize = float(options.get('arrowsize', 5)) head_width = arrowsize head_length = arrowsize * 2.0 color = to_mpl_color(options['rgbcolor']) from matplotlib.patches import FancyArrowPatch p = FancyArrowPatch( (self.xtail, self.ytail), (self.xhead, self.yhead), lw=width, arrowstyle='%s,head_width=%s,head_length=%s' % (style, head_width, head_length), shrinkA=arrowshorten_end, shrinkB=arrowshorten_end, fc=color, ec=color, linestyle=get_matplotlib_linestyle(options['linestyle'], return_type='long')) p.set_zorder(options['zorder']) p.set_label(options['legend_label']) if options['linestyle'] != 'solid': # The next few lines work around a design issue in matplotlib. # Currently, the specified linestyle is used to draw both the path # and the arrowhead. If linestyle is 'dashed', this looks really # odd. This code is from Jae-Joon Lee in response to a post to the # matplotlib mailing list. # See http://sourceforge.net/mailarchive/forum.php?thread_name=CAG%3DuJ%2Bnw2dE05P9TOXTz_zp-mGP3cY801vMH7yt6vgP9_WzU8w%40mail.gmail.com&forum_name=matplotlib-users import matplotlib.patheffects as pe class CheckNthSubPath(object): def __init__(self, patch, n): """ creates an callable object that returns True if the provided path is the n-th path from the patch. """ self._patch = patch self._n = n def get_paths(self, renderer): self._patch.set_dpi_cor(renderer.points_to_pixels(1.)) paths, fillables = self._patch.get_path_in_displaycoord() return paths def __call__(self, renderer, gc, tpath, affine, rgbFace): path = self.get_paths(renderer)[self._n] vert1, code1 = path.vertices, path.codes import numpy as np return np.array_equal(vert1, tpath.vertices) and np.array_equal( code1, tpath.codes) class ConditionalStroke(pe.RendererBase): def __init__(self, condition_func, pe_list): """ path effect that is only applied when the condition_func returns True. """ super(ConditionalStroke, self).__init__() self._pe_list = pe_list self._condition_func = condition_func def draw_path(self, renderer, gc, tpath, affine, rgbFace): if self._condition_func(renderer, gc, tpath, affine, rgbFace): for pe1 in self._pe_list: pe1.draw_path(renderer, gc, tpath, affine, rgbFace) pe1 = ConditionalStroke(CheckNthSubPath(p, 0), [pe.Stroke()]) pe2 = ConditionalStroke( CheckNthSubPath(p, 1), [pe.Stroke(dashes={ 'dash_offset': 0, 'dash_list': None })]) p.set_path_effects([pe1, pe2]) subplot.add_patch(p) return p
def _render_on_subplot(self, subplot): r""" Render this arrow in a subplot. This is the key function that defines how this arrow graphics primitive is rendered in matplotlib's library. EXAMPLES: This function implicitly ends up rendering this arrow on a matplotlib subplot:: sage: arrow((0,1), (2,-1)) TESTS: The length of the ends (shrinkA and shrinkB) should not depend on the width of the arrow, because Matplotlib already takes this into account. See :trac:`12836`:: sage: fig = Graphics().matplotlib() sage: sp = fig.add_subplot(1,1,1) sage: a = arrow((0,0), (1,1)) sage: b = arrow((0,0), (1,1), width=20) sage: p1 = a[0]._render_on_subplot(sp) sage: p2 = b[0]._render_on_subplot(sp) sage: p1.shrinkA == p2.shrinkA True sage: p1.shrinkB == p2.shrinkB True Dashed arrows should have solid arrowheads, :trac:`12852`. This test saves the plot of a dashed arrow to an EPS file. Within the EPS file, ``stroke`` will be called twice: once to draw the line, and again to draw the arrowhead. We check that both calls do not occur while the dashed line style is enabled:: sage: a = arrow((0,0), (1,1), linestyle='dashed') sage: filename = tmp_filename(ext='.eps') sage: a.save(filename=filename) sage: with open(filename, 'r') as f: ....: contents = f.read().replace('\n', ' ') sage: two_stroke_pattern = r'setdash.*stroke.*stroke.*setdash' sage: import re sage: two_stroke_re = re.compile(two_stroke_pattern) sage: two_stroke_re.search(contents) is None True """ from sage.plot.misc import get_matplotlib_linestyle options = self.options() head = options.pop("head") if head == 0: style = "<|-" elif head == 1: style = "-|>" elif head == 2: style = "<|-|>" else: raise KeyError("head parameter must be one of 0 (start), 1 (end) or 2 (both).") width = float(options["width"]) arrowshorten_end = float(options.get("arrowshorten", 0)) / 2.0 arrowsize = float(options.get("arrowsize", 5)) head_width = arrowsize head_length = arrowsize * 2.0 color = to_mpl_color(options["rgbcolor"]) from matplotlib.patches import FancyArrowPatch p = FancyArrowPatch( (self.xtail, self.ytail), (self.xhead, self.yhead), lw=width, arrowstyle="%s,head_width=%s,head_length=%s" % (style, head_width, head_length), shrinkA=arrowshorten_end, shrinkB=arrowshorten_end, fc=color, ec=color, ) p.set_linestyle(get_matplotlib_linestyle(options["linestyle"], return_type="long")) p.set_zorder(options["zorder"]) p.set_label(options["legend_label"]) if options["linestyle"] != "solid": # The next few lines work around a design issue in matplotlib. Currently, the specified # linestyle is used to draw both the path and the arrowhead. If linestyle is 'dashed', this # looks really odd. This code is from Jae-Joon Lee in response to a post to the matplotlib mailing # list. See http://sourceforge.net/mailarchive/forum.php?thread_name=CAG%3DuJ%2Bnw2dE05P9TOXTz_zp-mGP3cY801vMH7yt6vgP9_WzU8w%40mail.gmail.com&forum_name=matplotlib-users import matplotlib.patheffects as pe class CheckNthSubPath(object): def __init__(self, patch, n): """ creates an callable object that returns True if the provided path is the n-th path from the patch. """ self._patch = patch self._n = n def get_paths(self, renderer): self._patch.set_dpi_cor(renderer.points_to_pixels(1.0)) paths, fillables = self._patch.get_path_in_displaycoord() return paths def __call__(self, renderer, gc, tpath, affine, rgbFace): path = self.get_paths(renderer)[self._n] vert1, code1 = path.vertices, path.codes import numpy as np if np.all(vert1 == tpath.vertices) and np.all(code1 == tpath.codes): return True else: return False class ConditionalStroke(pe._Base): def __init__(self, condition_func, pe_list): """ path effect that is only applied when the condition_func returns True. """ super(ConditionalStroke, self).__init__() self._pe_list = pe_list self._condition_func = condition_func def draw_path(self, renderer, gc, tpath, affine, rgbFace): if self._condition_func(renderer, gc, tpath, affine, rgbFace): for pe1 in self._pe_list: pe1.draw_path(renderer, gc, tpath, affine, rgbFace) pe1 = ConditionalStroke(CheckNthSubPath(p, 0), [pe.Stroke()]) pe2 = ConditionalStroke(CheckNthSubPath(p, 1), [pe.Stroke(linestyle="solid")]) p.set_path_effects([pe1, pe2]) subplot.add_patch(p) return p