passagemath-plot 10.6.31rc3__cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of passagemath-plot might be problematic. Click here for more details.
- passagemath_plot-10.6.31rc3.dist-info/METADATA +172 -0
- passagemath_plot-10.6.31rc3.dist-info/RECORD +82 -0
- passagemath_plot-10.6.31rc3.dist-info/WHEEL +6 -0
- passagemath_plot-10.6.31rc3.dist-info/top_level.txt +2 -0
- passagemath_plot.libs/libgfortran-83c28eba.so.5.0.0 +0 -0
- passagemath_plot.libs/libgsl-cda90e79.so.28.0.0 +0 -0
- passagemath_plot.libs/libopenblasp-r0-6dcb67f9.3.29.so +0 -0
- passagemath_plot.libs/libquadmath-2284e583.so.0.0.0 +0 -0
- sage/all__sagemath_plot.py +15 -0
- sage/ext_data/threejs/animation.css +195 -0
- sage/ext_data/threejs/animation.html +85 -0
- sage/ext_data/threejs/animation.js +273 -0
- sage/ext_data/threejs/fat_lines.js +48 -0
- sage/ext_data/threejs/threejs-version.txt +1 -0
- sage/ext_data/threejs/threejs_template.html +597 -0
- sage/interfaces/all__sagemath_plot.py +1 -0
- sage/interfaces/gnuplot.py +196 -0
- sage/interfaces/jmoldata.py +208 -0
- sage/interfaces/povray.py +56 -0
- sage/plot/all.py +42 -0
- sage/plot/animate.py +1796 -0
- sage/plot/arc.py +504 -0
- sage/plot/arrow.py +671 -0
- sage/plot/bar_chart.py +205 -0
- sage/plot/bezier_path.py +400 -0
- sage/plot/circle.py +435 -0
- sage/plot/colors.py +1606 -0
- sage/plot/complex_plot.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/complex_plot.pyx +1446 -0
- sage/plot/contour_plot.py +1792 -0
- sage/plot/density_plot.py +318 -0
- sage/plot/disk.py +373 -0
- sage/plot/ellipse.py +375 -0
- sage/plot/graphics.py +3580 -0
- sage/plot/histogram.py +354 -0
- sage/plot/hyperbolic_arc.py +404 -0
- sage/plot/hyperbolic_polygon.py +416 -0
- sage/plot/hyperbolic_regular_polygon.py +296 -0
- sage/plot/line.py +626 -0
- sage/plot/matrix_plot.py +629 -0
- sage/plot/misc.py +509 -0
- sage/plot/multigraphics.py +1294 -0
- sage/plot/plot.py +4183 -0
- sage/plot/plot3d/all.py +23 -0
- sage/plot/plot3d/base.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/base.pxd +12 -0
- sage/plot/plot3d/base.pyx +3378 -0
- sage/plot/plot3d/implicit_plot3d.py +659 -0
- sage/plot/plot3d/implicit_surface.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/implicit_surface.pyx +1453 -0
- sage/plot/plot3d/index_face_set.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/index_face_set.pxd +32 -0
- sage/plot/plot3d/index_face_set.pyx +1873 -0
- sage/plot/plot3d/introduction.py +131 -0
- sage/plot/plot3d/list_plot3d.py +649 -0
- sage/plot/plot3d/parametric_plot3d.py +1130 -0
- sage/plot/plot3d/parametric_surface.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/parametric_surface.pxd +12 -0
- sage/plot/plot3d/parametric_surface.pyx +893 -0
- sage/plot/plot3d/platonic.py +601 -0
- sage/plot/plot3d/plot3d.py +1442 -0
- sage/plot/plot3d/plot_field3d.py +162 -0
- sage/plot/plot3d/point_c.pxi +148 -0
- sage/plot/plot3d/revolution_plot3d.py +309 -0
- sage/plot/plot3d/shapes.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/shapes.pxd +22 -0
- sage/plot/plot3d/shapes.pyx +1382 -0
- sage/plot/plot3d/shapes2.py +1512 -0
- sage/plot/plot3d/tachyon.py +1779 -0
- sage/plot/plot3d/texture.py +453 -0
- sage/plot/plot3d/transform.cpython-314-x86_64-linux-gnu.so +0 -0
- sage/plot/plot3d/transform.pxd +21 -0
- sage/plot/plot3d/transform.pyx +268 -0
- sage/plot/plot3d/tri_plot.py +589 -0
- sage/plot/plot_field.py +362 -0
- sage/plot/point.py +624 -0
- sage/plot/polygon.py +562 -0
- sage/plot/primitive.py +249 -0
- sage/plot/scatter_plot.py +199 -0
- sage/plot/step.py +85 -0
- sage/plot/streamline_plot.py +328 -0
- sage/plot/text.py +432 -0
sage/plot/graphics.py
ADDED
|
@@ -0,0 +1,3580 @@
|
|
|
1
|
+
# sage_setup: distribution = sagemath-plot
|
|
2
|
+
r"""
|
|
3
|
+
Graphics objects
|
|
4
|
+
|
|
5
|
+
This file contains the definition of the class :class:`Graphics`.
|
|
6
|
+
Usually, you don't call the constructor of this class directly
|
|
7
|
+
(although you can do it), you would use :func:`plot` instead.
|
|
8
|
+
|
|
9
|
+
AUTHORS:
|
|
10
|
+
|
|
11
|
+
- Jeroen Demeyer (2012-04-19): split off this file from plot.py (:issue:`12857`)
|
|
12
|
+
|
|
13
|
+
- Punarbasu Purkayastha (2012-05-20): Add logarithmic scale (:issue:`4529`)
|
|
14
|
+
|
|
15
|
+
- Emily Chen (2013-01-05): Add documentation for
|
|
16
|
+
:meth:`~sage.plot.graphics.Graphics.show` figsize parameter (:issue:`5956`)
|
|
17
|
+
|
|
18
|
+
- Eric Gourgoulhon (2015-03-19): Add parameter axes_labels_size (:issue:`18004`)
|
|
19
|
+
|
|
20
|
+
- Eric Gourgoulhon (2019-05-24): :class:`~sage.plot.multigraphics.GraphicsArray`
|
|
21
|
+
moved to new module :mod:`~sage.plot.multigraphics`; various improvements and
|
|
22
|
+
fixes in :meth:`Graphics.matplotlib` and ``Graphics._set_scale``; new method
|
|
23
|
+
:meth:`Graphics.inset`
|
|
24
|
+
"""
|
|
25
|
+
|
|
26
|
+
# ****************************************************************************
|
|
27
|
+
# Copyright (C) 2006 Alex Clemesha <clemesha@gmail.com>
|
|
28
|
+
# Copyright (C) 2006-2008 William Stein <wstein@gmail.com>
|
|
29
|
+
# Copyright (C) 2010 Jason Grout
|
|
30
|
+
#
|
|
31
|
+
# Distributed under the terms of the GNU General Public License (GPL)
|
|
32
|
+
# as published by the Free Software Foundation; either version 2 of
|
|
33
|
+
# the License, or (at your option) any later version.
|
|
34
|
+
# https://www.gnu.org/licenses/
|
|
35
|
+
# ****************************************************************************
|
|
36
|
+
|
|
37
|
+
import os
|
|
38
|
+
from numbers import Integral
|
|
39
|
+
from collections.abc import Iterable
|
|
40
|
+
from math import isnan
|
|
41
|
+
import sage.misc.verbose
|
|
42
|
+
from sage.misc.temporary_file import tmp_filename
|
|
43
|
+
from sage.misc.fast_methods import WithEqualityById
|
|
44
|
+
from sage.structure.sage_object import SageObject
|
|
45
|
+
from sage.misc.decorators import suboptions
|
|
46
|
+
from .colors import rgbcolor
|
|
47
|
+
|
|
48
|
+
ALLOWED_EXTENSIONS = ['.eps', '.pdf', '.pgf', '.png', '.ps', '.sobj', '.svg']
|
|
49
|
+
DEFAULT_DPI = 100
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
# If do_verify is True, options are checked when drawing a
|
|
53
|
+
# GraphicsPrimitive. See primitive.py
|
|
54
|
+
do_verify = True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def is_Graphics(x):
|
|
58
|
+
"""
|
|
59
|
+
Return ``True`` if `x` is a Graphics object.
|
|
60
|
+
|
|
61
|
+
EXAMPLES::
|
|
62
|
+
|
|
63
|
+
sage: from sage.plot.graphics import is_Graphics
|
|
64
|
+
sage: is_Graphics(1)
|
|
65
|
+
doctest:warning...
|
|
66
|
+
DeprecationWarning: The function is_Graphics is deprecated;
|
|
67
|
+
use 'isinstance(..., Graphics)' instead.
|
|
68
|
+
See https://github.com/sagemath/sage/issues/38184 for details.
|
|
69
|
+
False
|
|
70
|
+
sage: is_Graphics(disk((0.0, 0.0), 1, (0, pi/2))) # needs sage.symbolic
|
|
71
|
+
True
|
|
72
|
+
"""
|
|
73
|
+
from sage.misc.superseded import deprecation
|
|
74
|
+
deprecation(38184,
|
|
75
|
+
"The function is_Graphics is deprecated; "
|
|
76
|
+
"use 'isinstance(..., Graphics)' instead.")
|
|
77
|
+
return isinstance(x, Graphics)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _parse_figsize(figsize):
|
|
81
|
+
r"""
|
|
82
|
+
Helper function to get a figure size in matplotlib format.
|
|
83
|
+
|
|
84
|
+
INPUT:
|
|
85
|
+
|
|
86
|
+
- ``figsize`` -- width or [width, height] in inches; if only the width is
|
|
87
|
+
provided, the height is computed from matplotlib's default aspect ratio
|
|
88
|
+
|
|
89
|
+
OUTPUT:
|
|
90
|
+
|
|
91
|
+
- a pair of ``float``'s representing ``(width, height)``
|
|
92
|
+
|
|
93
|
+
EXAMPLES::
|
|
94
|
+
|
|
95
|
+
sage: from sage.plot.graphics import _parse_figsize
|
|
96
|
+
sage: _parse_figsize([5, 4])
|
|
97
|
+
(5.0, 4.0)
|
|
98
|
+
|
|
99
|
+
The default aspect ratio is 4/3::
|
|
100
|
+
|
|
101
|
+
sage: _parse_figsize(5) # tol 1.0e-13
|
|
102
|
+
(5.0, 3.75)
|
|
103
|
+
"""
|
|
104
|
+
from matplotlib import rcParams
|
|
105
|
+
if isinstance(figsize, (list, tuple)):
|
|
106
|
+
# figsize should be a pair of positive numbers
|
|
107
|
+
if len(figsize) != 2:
|
|
108
|
+
raise ValueError("figsize should be a positive number or a list "
|
|
109
|
+
f"of two positive numbers, not {figsize}")
|
|
110
|
+
figsize = (float(figsize[0]), float(figsize[1])) # floats for mpl
|
|
111
|
+
if not (figsize[0] > 0 and figsize[1] > 0):
|
|
112
|
+
raise ValueError("figsize should be positive numbers, "
|
|
113
|
+
f"not {figsize[0]} and {figsize[1]}")
|
|
114
|
+
else:
|
|
115
|
+
# in this case, figsize is a single number representing the width and
|
|
116
|
+
# should be positive
|
|
117
|
+
try:
|
|
118
|
+
figsize = float(figsize) # to pass to mpl
|
|
119
|
+
except TypeError:
|
|
120
|
+
raise TypeError(f"figsize should be a positive number, not {figsize}")
|
|
121
|
+
if figsize > 0:
|
|
122
|
+
default_width, default_height = rcParams['figure.figsize']
|
|
123
|
+
figsize = (figsize, default_height * figsize / default_width)
|
|
124
|
+
else:
|
|
125
|
+
raise ValueError(f"figsize should be positive, not {figsize}")
|
|
126
|
+
return figsize
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Graphics(WithEqualityById, SageObject):
|
|
130
|
+
"""
|
|
131
|
+
The Graphics object is an empty list of graphics objects. It is
|
|
132
|
+
useful to use this object when initializing a for loop where
|
|
133
|
+
different graphics object will be added to the empty object.
|
|
134
|
+
|
|
135
|
+
EXAMPLES::
|
|
136
|
+
|
|
137
|
+
sage: G = Graphics(); print(G)
|
|
138
|
+
Graphics object consisting of 0 graphics primitives
|
|
139
|
+
sage: c = circle((1,1), 1)
|
|
140
|
+
sage: G += c; print(G)
|
|
141
|
+
Graphics object consisting of 1 graphics primitive
|
|
142
|
+
|
|
143
|
+
Here we make a graphic of embedded isosceles triangles, coloring
|
|
144
|
+
each one with a different color as we go::
|
|
145
|
+
|
|
146
|
+
sage: h = 10; c = 0.4; p = 0.5
|
|
147
|
+
sage: G = Graphics()
|
|
148
|
+
sage: for x in srange(1, h+1): # needs sage.symbolic
|
|
149
|
+
....: l = [[0,x*sqrt(3)],[-x/2,-x*sqrt(3)/2],[x/2,-x*sqrt(3)/2],[0,x*sqrt(3)]]
|
|
150
|
+
....: G += line(l, color=hue(c + p*(x/h)))
|
|
151
|
+
sage: G.show(figsize=[5,5]) # needs sage.symbolic
|
|
152
|
+
|
|
153
|
+
We can change the scale of the axes in the graphics before displaying.::
|
|
154
|
+
|
|
155
|
+
sage: G = plot(exp, 1, 10) # long time # needs sage.symbolic
|
|
156
|
+
sage: G.show(scale='semilogy') # long time # needs sage.symbolic
|
|
157
|
+
|
|
158
|
+
TESTS:
|
|
159
|
+
|
|
160
|
+
From :issue:`4604`, ensure Graphics can handle 3d objects::
|
|
161
|
+
|
|
162
|
+
sage: g = Graphics()
|
|
163
|
+
sage: g += sphere((1, 1, 1), 2)
|
|
164
|
+
sage: g.show()
|
|
165
|
+
|
|
166
|
+
We check that graphics can be pickled (we can't use equality on
|
|
167
|
+
graphics so we just check that the load/dump cycle gives a
|
|
168
|
+
:class:`Graphics` instance)::
|
|
169
|
+
|
|
170
|
+
sage: g = Graphics()
|
|
171
|
+
sage: g2 = loads(dumps(g))
|
|
172
|
+
sage: g2.show()
|
|
173
|
+
|
|
174
|
+
::
|
|
175
|
+
|
|
176
|
+
sage: isinstance(g2, Graphics)
|
|
177
|
+
True
|
|
178
|
+
|
|
179
|
+
sage: hash(Graphics()) # random
|
|
180
|
+
42
|
|
181
|
+
|
|
182
|
+
.. automethod:: _rich_repr_
|
|
183
|
+
"""
|
|
184
|
+
|
|
185
|
+
def __init__(self):
|
|
186
|
+
"""
|
|
187
|
+
Create a new empty Graphics objects with all the defaults.
|
|
188
|
+
|
|
189
|
+
EXAMPLES::
|
|
190
|
+
|
|
191
|
+
sage: G = Graphics()
|
|
192
|
+
"""
|
|
193
|
+
self._axes_color = (0, 0, 0)
|
|
194
|
+
self._axes_label_color = (0, 0, 0)
|
|
195
|
+
self._axes_width = 0.8
|
|
196
|
+
self._bbox_extra_artists = []
|
|
197
|
+
self._extra_kwds = {}
|
|
198
|
+
self._fontsize = 10
|
|
199
|
+
self._axes_labels_size = 1.6
|
|
200
|
+
self._legend_colors = []
|
|
201
|
+
self._legend_opts = {}
|
|
202
|
+
self._objects = []
|
|
203
|
+
self._show_axes = True
|
|
204
|
+
self._show_legend = False
|
|
205
|
+
self._tick_label_color = (0, 0, 0)
|
|
206
|
+
|
|
207
|
+
def set_aspect_ratio(self, ratio):
|
|
208
|
+
"""
|
|
209
|
+
Set the aspect ratio, which is the ratio of height and width
|
|
210
|
+
of a unit square (i.e., height/width of a unit square), or
|
|
211
|
+
'automatic' (expand to fill the figure).
|
|
212
|
+
|
|
213
|
+
INPUT:
|
|
214
|
+
|
|
215
|
+
- ``ratio`` -- a positive real number or 'automatic'
|
|
216
|
+
|
|
217
|
+
EXAMPLES: We create a plot of the upper half of a circle, but it
|
|
218
|
+
doesn't look round because the aspect ratio is off::
|
|
219
|
+
|
|
220
|
+
sage: P = plot(sqrt(1-x^2),(x,-1,1)); P # needs sage.symbolic
|
|
221
|
+
Graphics object consisting of 1 graphics primitive
|
|
222
|
+
|
|
223
|
+
So we set the aspect ratio and now it is round::
|
|
224
|
+
|
|
225
|
+
sage: P.set_aspect_ratio(1) # needs sage.symbolic
|
|
226
|
+
sage: P.aspect_ratio() # needs sage.symbolic
|
|
227
|
+
1.0
|
|
228
|
+
sage: P # needs sage.symbolic
|
|
229
|
+
Graphics object consisting of 1 graphics primitive
|
|
230
|
+
|
|
231
|
+
Note that the aspect ratio is inherited upon addition (which takes
|
|
232
|
+
the max of aspect ratios of objects whose aspect ratio has been
|
|
233
|
+
set)::
|
|
234
|
+
|
|
235
|
+
sage: P + plot(sqrt(4-x^2),(x,-2,2)) # needs sage.symbolic
|
|
236
|
+
Graphics object consisting of 2 graphics primitives
|
|
237
|
+
|
|
238
|
+
In the following example, both plots produce a circle that looks
|
|
239
|
+
twice as tall as wide::
|
|
240
|
+
|
|
241
|
+
sage: Q = circle((0,0), 0.5); Q.set_aspect_ratio(2)
|
|
242
|
+
sage: (P + Q).aspect_ratio(); P + Q # needs sage.symbolic
|
|
243
|
+
2.0
|
|
244
|
+
Graphics object consisting of 2 graphics primitives
|
|
245
|
+
sage: (Q + P).aspect_ratio(); Q + P # needs sage.symbolic
|
|
246
|
+
2.0
|
|
247
|
+
Graphics object consisting of 2 graphics primitives
|
|
248
|
+
"""
|
|
249
|
+
if ratio != 'auto' and ratio != 'automatic':
|
|
250
|
+
ratio = float(ratio)
|
|
251
|
+
if ratio <= 0:
|
|
252
|
+
raise ValueError("the aspect ratio must be positive or 'automatic'")
|
|
253
|
+
else:
|
|
254
|
+
ratio = 'automatic'
|
|
255
|
+
self._extra_kwds['aspect_ratio'] = ratio
|
|
256
|
+
|
|
257
|
+
def aspect_ratio(self):
|
|
258
|
+
"""
|
|
259
|
+
Get the current aspect ratio, which is the ratio of height to
|
|
260
|
+
width of a unit square, or ``'automatic'``.
|
|
261
|
+
|
|
262
|
+
OUTPUT: a positive float (height/width of a unit square), or ``'automatic'``
|
|
263
|
+
(expand to fill the figure).
|
|
264
|
+
|
|
265
|
+
EXAMPLES:
|
|
266
|
+
|
|
267
|
+
The default aspect ratio for a new blank :class:`Graphics` object is ``'automatic'``::
|
|
268
|
+
|
|
269
|
+
sage: P = Graphics()
|
|
270
|
+
sage: P.aspect_ratio()
|
|
271
|
+
'automatic'
|
|
272
|
+
|
|
273
|
+
The aspect ratio can be explicitly set different from the object's default::
|
|
274
|
+
|
|
275
|
+
sage: P = circle((1,1), 1)
|
|
276
|
+
sage: P.aspect_ratio()
|
|
277
|
+
1.0
|
|
278
|
+
sage: P.set_aspect_ratio(2)
|
|
279
|
+
sage: P.aspect_ratio()
|
|
280
|
+
2.0
|
|
281
|
+
sage: P.set_aspect_ratio('automatic')
|
|
282
|
+
sage: P.aspect_ratio()
|
|
283
|
+
'automatic'
|
|
284
|
+
"""
|
|
285
|
+
return self._extra_kwds.get('aspect_ratio', 'automatic')
|
|
286
|
+
|
|
287
|
+
def legend(self, show=None):
|
|
288
|
+
r"""
|
|
289
|
+
Set whether or not the legend is shown by default.
|
|
290
|
+
|
|
291
|
+
INPUT:
|
|
292
|
+
|
|
293
|
+
- ``show`` -- (default: ``None``) a boolean
|
|
294
|
+
|
|
295
|
+
If called with no input, return the current legend setting.
|
|
296
|
+
|
|
297
|
+
EXAMPLES:
|
|
298
|
+
|
|
299
|
+
By default no legend is displayed::
|
|
300
|
+
|
|
301
|
+
sage: P = plot(sin) # needs sage.symbolic
|
|
302
|
+
sage: P.legend() # needs sage.symbolic
|
|
303
|
+
False
|
|
304
|
+
|
|
305
|
+
But if we put a label then the legend is shown::
|
|
306
|
+
|
|
307
|
+
sage: P = plot(sin, legend_label='sin') # needs sage.symbolic
|
|
308
|
+
sage: P.legend() # needs sage.symbolic
|
|
309
|
+
True
|
|
310
|
+
|
|
311
|
+
We can turn it on or off::
|
|
312
|
+
|
|
313
|
+
sage: # needs sage.symbolic
|
|
314
|
+
sage: P.legend(False)
|
|
315
|
+
sage: P.legend()
|
|
316
|
+
False
|
|
317
|
+
sage: P.legend(True)
|
|
318
|
+
sage: P # show with the legend
|
|
319
|
+
Graphics object consisting of 1 graphics primitive
|
|
320
|
+
"""
|
|
321
|
+
if show is None:
|
|
322
|
+
return self._show_legend
|
|
323
|
+
else:
|
|
324
|
+
self._show_legend = bool(show)
|
|
325
|
+
|
|
326
|
+
def set_legend_options(self, **kwds):
|
|
327
|
+
r"""
|
|
328
|
+
Set various legend options.
|
|
329
|
+
|
|
330
|
+
INPUT:
|
|
331
|
+
|
|
332
|
+
- ``title`` -- (default: ``None``) string, the legend title
|
|
333
|
+
|
|
334
|
+
- ``ncol`` -- (default: 1) positive integer, the number of columns
|
|
335
|
+
|
|
336
|
+
- ``columnspacing`` -- (default: ``None``) the spacing between columns
|
|
337
|
+
|
|
338
|
+
- ``borderaxespad`` -- (default: ``None``) float, length between the axes and the legend
|
|
339
|
+
|
|
340
|
+
- ``back_color`` -- (default: ``'white'``) this parameter can be a string
|
|
341
|
+
denoting a color or an RGB tuple. The string can be a color name
|
|
342
|
+
as in ('red', 'green', 'yellow', ...) or a floating point number
|
|
343
|
+
like '0.8' which gets expanded to (0.8, 0.8, 0.8). The
|
|
344
|
+
tuple form is just a floating point RGB tuple with all values ranging
|
|
345
|
+
from 0 to 1.
|
|
346
|
+
|
|
347
|
+
- ``handlelength`` -- (default: 0.05) float, the length of the legend handles
|
|
348
|
+
|
|
349
|
+
- ``handletextpad`` -- (default: 0.5) float, the pad between the legend handle and text
|
|
350
|
+
|
|
351
|
+
- ``labelspacing`` -- (default: 0.02) float, vertical space between legend entries
|
|
352
|
+
|
|
353
|
+
- ``loc`` -- (default: ``'best'``) may be a string, an integer or a tuple. String or
|
|
354
|
+
integer inputs must be one of the following:
|
|
355
|
+
|
|
356
|
+
- 0, 'best'
|
|
357
|
+
|
|
358
|
+
- 1, 'upper right'
|
|
359
|
+
|
|
360
|
+
- 2, 'upper left'
|
|
361
|
+
|
|
362
|
+
- 3, 'lower left'
|
|
363
|
+
|
|
364
|
+
- 4, 'lower right'
|
|
365
|
+
|
|
366
|
+
- 5, 'right'
|
|
367
|
+
|
|
368
|
+
- 6, 'center left'
|
|
369
|
+
|
|
370
|
+
- 7, 'center right'
|
|
371
|
+
|
|
372
|
+
- 8, 'lower center'
|
|
373
|
+
|
|
374
|
+
- 9, 'upper center'
|
|
375
|
+
|
|
376
|
+
- 10, 'center'
|
|
377
|
+
|
|
378
|
+
- Tuple arguments represent an absolute (x, y) position on the plot
|
|
379
|
+
in axes coordinates (meaning from 0 to 1 in each direction).
|
|
380
|
+
|
|
381
|
+
- ``markerscale`` -- (default: 0.6) float, how much to scale the markers in the legend
|
|
382
|
+
|
|
383
|
+
- ``numpoints`` -- (default: 2) integer, the number of points in the legend for line
|
|
384
|
+
|
|
385
|
+
- ``borderpad`` -- (default: 0.6) float, the fractional whitespace inside the legend border
|
|
386
|
+
(between 0 and 1)
|
|
387
|
+
|
|
388
|
+
- ``font_family`` -- (default: ``'sans-serif'``) string, one of
|
|
389
|
+
``'serif'``, ``'sans-serif'``, ``'cursive'``, ``'fantasy'``,
|
|
390
|
+
``'monospace'``
|
|
391
|
+
|
|
392
|
+
- ``font_style`` -- (default: ``'normal'``) string, one of
|
|
393
|
+
``'normal'``, ``'italic'``, ``'oblique'``
|
|
394
|
+
|
|
395
|
+
- ``font_variant`` -- (default: ``'normal'``) string, one of
|
|
396
|
+
``'normal'``, ``'small-caps'``
|
|
397
|
+
|
|
398
|
+
- ``font_weight`` -- (default: ``'medium'``) string, one of
|
|
399
|
+
``'black'``, ``'extra bold'``, ``'bold'``, ``'semibold'``,
|
|
400
|
+
``'medium'``, ``'normal'``, ``'light'``
|
|
401
|
+
|
|
402
|
+
- ``font_size`` -- (default: ``'medium'``) string, one of
|
|
403
|
+
``'xx-small'``, ``'x-small'``, ``'small'``, ``'medium'``,
|
|
404
|
+
``'large'``, ``'x-large'``, ``'xx-large'``, or an absolute font size
|
|
405
|
+
(e.g. 12)
|
|
406
|
+
|
|
407
|
+
- ``shadow`` -- boolean (default: ``True``); draw a shadow behind the legend
|
|
408
|
+
|
|
409
|
+
- ``fancybox`` -- boolean (default: ``False``); if
|
|
410
|
+
``True``, draws a frame with a round fancybox
|
|
411
|
+
|
|
412
|
+
These are all keyword arguments.
|
|
413
|
+
|
|
414
|
+
OUTPUT: a dictionary of all current legend options
|
|
415
|
+
|
|
416
|
+
EXAMPLES:
|
|
417
|
+
|
|
418
|
+
By default, no options are set::
|
|
419
|
+
|
|
420
|
+
sage: p = plot(tan, legend_label='tan') # needs sage.symbolic
|
|
421
|
+
sage: p.set_legend_options() # needs sage.symbolic
|
|
422
|
+
{}
|
|
423
|
+
|
|
424
|
+
We build a legend without a shadow::
|
|
425
|
+
|
|
426
|
+
sage: p.set_legend_options(shadow=False) # needs sage.symbolic
|
|
427
|
+
sage: p.set_legend_options()['shadow'] # needs sage.symbolic
|
|
428
|
+
False
|
|
429
|
+
|
|
430
|
+
To set the legend position to the center of the plot, all these
|
|
431
|
+
methods are roughly equivalent::
|
|
432
|
+
|
|
433
|
+
sage: p.set_legend_options(loc='center'); p # needs sage.symbolic
|
|
434
|
+
Graphics object consisting of 1 graphics primitive
|
|
435
|
+
|
|
436
|
+
::
|
|
437
|
+
|
|
438
|
+
sage: p.set_legend_options(loc=10); p # needs sage.symbolic
|
|
439
|
+
Graphics object consisting of 1 graphics primitive
|
|
440
|
+
|
|
441
|
+
::
|
|
442
|
+
|
|
443
|
+
sage: p.set_legend_options(loc=(0.5,0.5)); p # aligns the bottom of the box to the center # needs sage.symbolic
|
|
444
|
+
Graphics object consisting of 1 graphics primitive
|
|
445
|
+
|
|
446
|
+
The parameters ``loc`` and ``borderaxespad`` can be altered
|
|
447
|
+
in order to place the legend below the x-axis label or to
|
|
448
|
+
the left of the y-axis label::
|
|
449
|
+
|
|
450
|
+
sage: p = line([(0, 0), (1, 1)], legend_label='test')
|
|
451
|
+
sage: p.axes_labels(['X-Label', 'Y-Label']) # adding labels for axes
|
|
452
|
+
sage: p.set_legend_options(loc=8, borderaxespad=-7.5-0.01*p.fontsize())
|
|
453
|
+
sage: p
|
|
454
|
+
Graphics object consisting of 1 graphics primitive
|
|
455
|
+
"""
|
|
456
|
+
if len(kwds) == 0:
|
|
457
|
+
return self._legend_opts
|
|
458
|
+
else:
|
|
459
|
+
self._legend_opts.update(kwds)
|
|
460
|
+
|
|
461
|
+
def get_axes_range(self):
|
|
462
|
+
"""
|
|
463
|
+
Return a dictionary of the range of the axes for this graphics
|
|
464
|
+
object. This is fall back to the ranges in ``get_minmax_data()`` for
|
|
465
|
+
any value which the user has not explicitly set.
|
|
466
|
+
|
|
467
|
+
.. warning::
|
|
468
|
+
|
|
469
|
+
Changing the dictionary returned by this function does not
|
|
470
|
+
change the axes range for this object. To do that, use the
|
|
471
|
+
:meth:`set_axes_range` method.
|
|
472
|
+
|
|
473
|
+
EXAMPLES::
|
|
474
|
+
|
|
475
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
476
|
+
sage: list(sorted(L.get_axes_range().items()))
|
|
477
|
+
[('xmax', 3.0), ('xmin', 1.0), ('ymax', 5.0), ('ymin', -4.0)]
|
|
478
|
+
sage: L.set_axes_range(xmin=-1)
|
|
479
|
+
sage: list(sorted(L.get_axes_range().items()))
|
|
480
|
+
[('xmax', 3.0), ('xmin', -1.0), ('ymax', 5.0), ('ymin', -4.0)]
|
|
481
|
+
"""
|
|
482
|
+
axes_range = self.get_minmax_data()
|
|
483
|
+
axes_range.update(self._get_axes_range_dict())
|
|
484
|
+
return axes_range
|
|
485
|
+
|
|
486
|
+
def set_axes_range(self, xmin=None, xmax=None, ymin=None, ymax=None):
|
|
487
|
+
"""
|
|
488
|
+
Set the ranges of the `x` and `y` axes.
|
|
489
|
+
|
|
490
|
+
INPUT:
|
|
491
|
+
|
|
492
|
+
- ``xmin``, ``xmax``, ``ymin``, ``ymax`` -- floats
|
|
493
|
+
|
|
494
|
+
EXAMPLES::
|
|
495
|
+
|
|
496
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
497
|
+
sage: L.set_axes_range(-1, 20, 0, 2)
|
|
498
|
+
sage: d = L.get_axes_range()
|
|
499
|
+
sage: d['xmin'], d['xmax'], d['ymin'], d['ymax']
|
|
500
|
+
(-1.0, 20.0, 0.0, 2.0)
|
|
501
|
+
"""
|
|
502
|
+
l = locals()
|
|
503
|
+
axes_range = self._get_axes_range_dict()
|
|
504
|
+
for name in ['xmin', 'xmax', 'ymin', 'ymax']:
|
|
505
|
+
if l[name] is not None:
|
|
506
|
+
axes_range[name] = float(l[name])
|
|
507
|
+
|
|
508
|
+
axes_range = set_axes_range
|
|
509
|
+
|
|
510
|
+
def _get_axes_range_dict(self):
|
|
511
|
+
"""
|
|
512
|
+
Return the underlying dictionary used to store the user's
|
|
513
|
+
custom ranges for the axes on this object.
|
|
514
|
+
|
|
515
|
+
EXAMPLES::
|
|
516
|
+
|
|
517
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
518
|
+
sage: L._get_axes_range_dict()
|
|
519
|
+
{}
|
|
520
|
+
sage: L.set_axes_range(xmin=-1)
|
|
521
|
+
sage: L._get_axes_range_dict()
|
|
522
|
+
{'xmin': -1.0}
|
|
523
|
+
"""
|
|
524
|
+
try:
|
|
525
|
+
return self._axes_range
|
|
526
|
+
except AttributeError:
|
|
527
|
+
self._axes_range = {}
|
|
528
|
+
return self._axes_range
|
|
529
|
+
|
|
530
|
+
def set_flip(self, flip_x=None, flip_y=None):
|
|
531
|
+
"""
|
|
532
|
+
Set the flip options for this graphics object.
|
|
533
|
+
|
|
534
|
+
INPUT:
|
|
535
|
+
|
|
536
|
+
- ``flip_x`` -- boolean (default: ``None``); if not ``None``, set the
|
|
537
|
+
``flip_x`` option to this value
|
|
538
|
+
- ``flip_y`` -- boolean (default: ``None``); if not ``None``, set the
|
|
539
|
+
``flip_y`` option to this value
|
|
540
|
+
|
|
541
|
+
EXAMPLES::
|
|
542
|
+
|
|
543
|
+
sage: L = line([(1, 0), (2, 3)])
|
|
544
|
+
sage: L.set_flip(flip_y=True)
|
|
545
|
+
sage: L.flip()
|
|
546
|
+
(False, True)
|
|
547
|
+
sage: L.set_flip(True, False)
|
|
548
|
+
sage: L.flip()
|
|
549
|
+
(True, False)
|
|
550
|
+
"""
|
|
551
|
+
if flip_x is not None:
|
|
552
|
+
self._extra_kwds['flip_x'] = flip_x
|
|
553
|
+
if flip_y is not None:
|
|
554
|
+
self._extra_kwds['flip_y'] = flip_y
|
|
555
|
+
|
|
556
|
+
def flip(self, flip_x=False, flip_y=False):
|
|
557
|
+
"""
|
|
558
|
+
Get the flip options and optionally mirror this graphics object.
|
|
559
|
+
|
|
560
|
+
INPUT:
|
|
561
|
+
|
|
562
|
+
- ``flip_x`` -- boolean (default: ``False``); if ``True``, replace the
|
|
563
|
+
current ``flip_x`` option by its opposite
|
|
564
|
+
- ``flip_y`` -- boolean (default: ``False``); if ``True``, replace the
|
|
565
|
+
current ``flip_y`` option by its opposite
|
|
566
|
+
|
|
567
|
+
OUTPUT: a tuple containing the new flip options
|
|
568
|
+
|
|
569
|
+
EXAMPLES:
|
|
570
|
+
|
|
571
|
+
When called without arguments, this just returns the current flip
|
|
572
|
+
options::
|
|
573
|
+
|
|
574
|
+
sage: L = line([(1, 0), (2, 3)])
|
|
575
|
+
sage: L.flip()
|
|
576
|
+
(False, False)
|
|
577
|
+
|
|
578
|
+
Otherwise, the specified options are changed and the new options are
|
|
579
|
+
returned::
|
|
580
|
+
|
|
581
|
+
sage: L.flip(flip_y=True)
|
|
582
|
+
(False, True)
|
|
583
|
+
sage: L.flip(True, True)
|
|
584
|
+
(True, False)
|
|
585
|
+
"""
|
|
586
|
+
a = self._extra_kwds.get('flip_x', self.SHOW_OPTIONS['flip_x'])
|
|
587
|
+
b = self._extra_kwds.get('flip_y', self.SHOW_OPTIONS['flip_y'])
|
|
588
|
+
if flip_x:
|
|
589
|
+
a = not a
|
|
590
|
+
self._extra_kwds['flip_x'] = a
|
|
591
|
+
if flip_y:
|
|
592
|
+
b = not b
|
|
593
|
+
self._extra_kwds['flip_y'] = b
|
|
594
|
+
return (a, b)
|
|
595
|
+
|
|
596
|
+
def fontsize(self, s=None):
|
|
597
|
+
"""
|
|
598
|
+
Set the font size of axes labels and tick marks.
|
|
599
|
+
|
|
600
|
+
Note that the relative size of the axes labels font w.r.t. the tick
|
|
601
|
+
marks font can be adjusted via :meth:`axes_labels_size`.
|
|
602
|
+
|
|
603
|
+
INPUT:
|
|
604
|
+
|
|
605
|
+
- ``s`` -- integer, a font size in points
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
If called with no input, return the current fontsize.
|
|
609
|
+
|
|
610
|
+
EXAMPLES::
|
|
611
|
+
|
|
612
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
613
|
+
sage: L.fontsize()
|
|
614
|
+
10
|
|
615
|
+
sage: L.fontsize(20)
|
|
616
|
+
sage: L.fontsize()
|
|
617
|
+
20
|
|
618
|
+
|
|
619
|
+
All the numbers on the axes will be very large in this plot::
|
|
620
|
+
|
|
621
|
+
sage: L
|
|
622
|
+
Graphics object consisting of 1 graphics primitive
|
|
623
|
+
"""
|
|
624
|
+
if s is None:
|
|
625
|
+
try:
|
|
626
|
+
return self._fontsize
|
|
627
|
+
except AttributeError:
|
|
628
|
+
self._fontsize = 10
|
|
629
|
+
return self._fontsize
|
|
630
|
+
self._fontsize = int(s)
|
|
631
|
+
|
|
632
|
+
def axes_labels_size(self, s=None):
|
|
633
|
+
"""
|
|
634
|
+
Set the relative size of axes labels w.r.t. the axes tick marks.
|
|
635
|
+
|
|
636
|
+
INPUT:
|
|
637
|
+
|
|
638
|
+
- ``s`` -- float, relative size of axes labels w.r.t. to the tick marks,
|
|
639
|
+
the size of the tick marks being set by :meth:`fontsize`
|
|
640
|
+
|
|
641
|
+
If called with no input, return the current relative size.
|
|
642
|
+
|
|
643
|
+
EXAMPLES::
|
|
644
|
+
|
|
645
|
+
sage: # needs sage.symbolic
|
|
646
|
+
sage: p = plot(sin(x^2), (x, -3, 3), axes_labels=['$x$','$y$'])
|
|
647
|
+
sage: p.axes_labels_size() # default value
|
|
648
|
+
1.6
|
|
649
|
+
sage: p.axes_labels_size(2.5)
|
|
650
|
+
sage: p.axes_labels_size()
|
|
651
|
+
2.5
|
|
652
|
+
|
|
653
|
+
Now the axes labels are large w.r.t. the tick marks::
|
|
654
|
+
|
|
655
|
+
sage: p # needs sage.symbolic
|
|
656
|
+
Graphics object consisting of 1 graphics primitive
|
|
657
|
+
"""
|
|
658
|
+
if s is None:
|
|
659
|
+
try:
|
|
660
|
+
return self._axes_labels_size
|
|
661
|
+
except AttributeError:
|
|
662
|
+
self._axes_labels_size = 1.6
|
|
663
|
+
return self._axes_labels_size
|
|
664
|
+
self._axes_labels_size = float(s)
|
|
665
|
+
|
|
666
|
+
def axes(self, show=None):
|
|
667
|
+
"""
|
|
668
|
+
Set whether or not the `x` and `y` axes are shown
|
|
669
|
+
by default.
|
|
670
|
+
|
|
671
|
+
INPUT:
|
|
672
|
+
|
|
673
|
+
- ``show`` -- boolean
|
|
674
|
+
|
|
675
|
+
If called with no input, return the current axes setting.
|
|
676
|
+
|
|
677
|
+
EXAMPLES::
|
|
678
|
+
|
|
679
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
680
|
+
|
|
681
|
+
By default the axes are displayed.
|
|
682
|
+
|
|
683
|
+
::
|
|
684
|
+
|
|
685
|
+
sage: L.axes()
|
|
686
|
+
True
|
|
687
|
+
|
|
688
|
+
But we turn them off, and verify that they are off
|
|
689
|
+
|
|
690
|
+
::
|
|
691
|
+
|
|
692
|
+
sage: L.axes(False)
|
|
693
|
+
sage: L.axes()
|
|
694
|
+
False
|
|
695
|
+
|
|
696
|
+
Displaying L now shows a triangle but no axes.
|
|
697
|
+
|
|
698
|
+
::
|
|
699
|
+
|
|
700
|
+
sage: L
|
|
701
|
+
Graphics object consisting of 1 graphics primitive
|
|
702
|
+
"""
|
|
703
|
+
if show is None:
|
|
704
|
+
try:
|
|
705
|
+
return self._show_axes
|
|
706
|
+
except AttributeError:
|
|
707
|
+
self._show_axes = True
|
|
708
|
+
return self._show_axes
|
|
709
|
+
self._show_axes = bool(show)
|
|
710
|
+
|
|
711
|
+
def axes_color(self, c=None):
|
|
712
|
+
"""
|
|
713
|
+
Set the axes color.
|
|
714
|
+
|
|
715
|
+
If called with no input, return the current axes_color setting.
|
|
716
|
+
|
|
717
|
+
INPUT:
|
|
718
|
+
|
|
719
|
+
- ``c`` -- an RGB color 3-tuple, where each tuple entry
|
|
720
|
+
is a float between 0 and 1
|
|
721
|
+
|
|
722
|
+
EXAMPLES: We create a line, which has like everything a default
|
|
723
|
+
axes color of black.
|
|
724
|
+
|
|
725
|
+
::
|
|
726
|
+
|
|
727
|
+
sage: L = line([(1,2), (3,-4), (2, 5), (1,2)])
|
|
728
|
+
sage: L.axes_color()
|
|
729
|
+
(0, 0, 0)
|
|
730
|
+
|
|
731
|
+
We change the axes color to red and verify the change.
|
|
732
|
+
|
|
733
|
+
::
|
|
734
|
+
|
|
735
|
+
sage: L.axes_color((1,0,0))
|
|
736
|
+
sage: L.axes_color()
|
|
737
|
+
(1.0, 0.0, 0.0)
|
|
738
|
+
|
|
739
|
+
When we display the plot, we'll see a blue triangle and bright red
|
|
740
|
+
axes.
|
|
741
|
+
|
|
742
|
+
::
|
|
743
|
+
|
|
744
|
+
sage: L
|
|
745
|
+
Graphics object consisting of 1 graphics primitive
|
|
746
|
+
"""
|
|
747
|
+
if c is None:
|
|
748
|
+
try:
|
|
749
|
+
return self._axes_color
|
|
750
|
+
|
|
751
|
+
except AttributeError:
|
|
752
|
+
self._axes_color = (0.0, 0.0, 0.0)
|
|
753
|
+
return self._axes_color
|
|
754
|
+
self._axes_color = rgbcolor(c)
|
|
755
|
+
|
|
756
|
+
def axes_labels(self, l=None):
|
|
757
|
+
"""
|
|
758
|
+
Set the axes labels.
|
|
759
|
+
|
|
760
|
+
INPUT:
|
|
761
|
+
|
|
762
|
+
- ``l`` -- (default: ``None``) a list of two strings or
|
|
763
|
+
``None``
|
|
764
|
+
|
|
765
|
+
OUTPUT: a 2-tuple of strings
|
|
766
|
+
|
|
767
|
+
If l is ``None``, returns the current ``axes_labels``,
|
|
768
|
+
which is itself by default ``None``. The default labels are both
|
|
769
|
+
empty.
|
|
770
|
+
|
|
771
|
+
EXAMPLES: We create a plot and put x and y axes labels on it.
|
|
772
|
+
|
|
773
|
+
::
|
|
774
|
+
|
|
775
|
+
sage: p = plot(sin(x), (x, 0, 10)) # needs sage.symbolic
|
|
776
|
+
sage: p.axes_labels(['$x$','$y$']) # needs sage.symbolic
|
|
777
|
+
sage: p.axes_labels() # needs sage.symbolic
|
|
778
|
+
('$x$', '$y$')
|
|
779
|
+
|
|
780
|
+
Now when you plot p, you see x and y axes labels::
|
|
781
|
+
|
|
782
|
+
sage: p # needs sage.symbolic
|
|
783
|
+
Graphics object consisting of 1 graphics primitive
|
|
784
|
+
|
|
785
|
+
Notice that some may prefer axes labels which are not
|
|
786
|
+
typeset::
|
|
787
|
+
|
|
788
|
+
sage: plot(sin(x), (x, 0, 10), axes_labels=['x','y']) # needs sage.symbolic
|
|
789
|
+
Graphics object consisting of 1 graphics primitive
|
|
790
|
+
|
|
791
|
+
TESTS:
|
|
792
|
+
|
|
793
|
+
Unicode strings are acceptable; see :issue:`13161`. Note that
|
|
794
|
+
this does not guarantee that matplotlib will handle the strings
|
|
795
|
+
properly, although it should.
|
|
796
|
+
|
|
797
|
+
::
|
|
798
|
+
|
|
799
|
+
sage: c = circle((0,0), 1)
|
|
800
|
+
sage: c.axes_labels(['axe des abscisses', 'axe des ordonnées'])
|
|
801
|
+
sage: c._axes_labels
|
|
802
|
+
('axe des abscisses', 'axe des ordonnées')
|
|
803
|
+
"""
|
|
804
|
+
if l is None:
|
|
805
|
+
try:
|
|
806
|
+
return self._axes_labels
|
|
807
|
+
except AttributeError:
|
|
808
|
+
self._axes_labels = None
|
|
809
|
+
return self._axes_labels
|
|
810
|
+
if not isinstance(l, (list, tuple)):
|
|
811
|
+
raise TypeError("l must be a list or tuple")
|
|
812
|
+
if len(l) != 2:
|
|
813
|
+
raise ValueError("l must have length 2")
|
|
814
|
+
self._axes_labels = tuple(l)
|
|
815
|
+
|
|
816
|
+
def axes_label_color(self, c=None):
|
|
817
|
+
r"""
|
|
818
|
+
Set the color of the axes labels.
|
|
819
|
+
|
|
820
|
+
The axes labels are placed at the edge of the x and y axes, and are
|
|
821
|
+
not on by default (use the :meth:`axes_labels` command to
|
|
822
|
+
set them; see the example below). This function just changes their
|
|
823
|
+
color.
|
|
824
|
+
|
|
825
|
+
INPUT:
|
|
826
|
+
|
|
827
|
+
- ``c`` -- an RGB 3-tuple of numbers between 0 and 1
|
|
828
|
+
|
|
829
|
+
|
|
830
|
+
If called with no input, return the current axes_label_color
|
|
831
|
+
setting.
|
|
832
|
+
|
|
833
|
+
EXAMPLES: We create a plot, which by default has axes label color
|
|
834
|
+
black.
|
|
835
|
+
|
|
836
|
+
::
|
|
837
|
+
|
|
838
|
+
sage: p = plot(sin, (-1,1)) # needs sage.symbolic
|
|
839
|
+
sage: p.axes_label_color() # needs sage.symbolic
|
|
840
|
+
(0, 0, 0)
|
|
841
|
+
|
|
842
|
+
We change the labels to be red, and confirm this::
|
|
843
|
+
|
|
844
|
+
sage: p.axes_label_color((1,0,0)) # needs sage.symbolic
|
|
845
|
+
sage: p.axes_label_color() # needs sage.symbolic
|
|
846
|
+
(1.0, 0.0, 0.0)
|
|
847
|
+
|
|
848
|
+
We set labels, since otherwise we won't see anything.
|
|
849
|
+
|
|
850
|
+
::
|
|
851
|
+
|
|
852
|
+
sage: p.axes_labels(['$x$ axis', '$y$ axis']) # needs sage.symbolic
|
|
853
|
+
|
|
854
|
+
In the plot below, notice that the labels are red::
|
|
855
|
+
|
|
856
|
+
sage: p # needs sage.symbolic
|
|
857
|
+
Graphics object consisting of 1 graphics primitive
|
|
858
|
+
"""
|
|
859
|
+
if c is None:
|
|
860
|
+
try:
|
|
861
|
+
return self._axes_label_color
|
|
862
|
+
except AttributeError:
|
|
863
|
+
self._axes_label_color = (0, 0, 0)
|
|
864
|
+
return self._axes_label_color
|
|
865
|
+
self._axes_label_color = rgbcolor(c)
|
|
866
|
+
|
|
867
|
+
def axes_width(self, w=None):
|
|
868
|
+
r"""
|
|
869
|
+
Set the axes width. Use this to draw a plot with really fat or
|
|
870
|
+
really thin axes.
|
|
871
|
+
|
|
872
|
+
INPUT:
|
|
873
|
+
|
|
874
|
+
- ``w`` -- a float
|
|
875
|
+
|
|
876
|
+
|
|
877
|
+
If called with no input, return the current
|
|
878
|
+
``axes_width`` setting.
|
|
879
|
+
|
|
880
|
+
EXAMPLES: We create a plot, see the default axes width (with funny
|
|
881
|
+
Python float rounding), then reset the width to 10 (very fat).
|
|
882
|
+
|
|
883
|
+
::
|
|
884
|
+
|
|
885
|
+
sage: # needs sage.symbolic
|
|
886
|
+
sage: p = plot(cos, (-3,3))
|
|
887
|
+
sage: p.axes_width()
|
|
888
|
+
0.8
|
|
889
|
+
sage: p.axes_width(10)
|
|
890
|
+
sage: p.axes_width()
|
|
891
|
+
10.0
|
|
892
|
+
|
|
893
|
+
Finally we plot the result, which is a graph with very fat axes.
|
|
894
|
+
|
|
895
|
+
::
|
|
896
|
+
|
|
897
|
+
sage: p # needs sage.symbolic
|
|
898
|
+
Graphics object consisting of 1 graphics primitive
|
|
899
|
+
"""
|
|
900
|
+
if w is None:
|
|
901
|
+
try:
|
|
902
|
+
return self._axes_width
|
|
903
|
+
except AttributeError:
|
|
904
|
+
self._axes_width = True
|
|
905
|
+
return self._axes_width
|
|
906
|
+
self._axes_width = float(w)
|
|
907
|
+
|
|
908
|
+
def tick_label_color(self, c=None):
|
|
909
|
+
"""
|
|
910
|
+
Set the color of the axes tick labels.
|
|
911
|
+
|
|
912
|
+
INPUT:
|
|
913
|
+
|
|
914
|
+
- ``c`` -- an RGB 3-tuple of numbers between 0 and 1
|
|
915
|
+
|
|
916
|
+
|
|
917
|
+
If called with no input, return the current ``tick_label_color``
|
|
918
|
+
setting.
|
|
919
|
+
|
|
920
|
+
EXAMPLES::
|
|
921
|
+
|
|
922
|
+
sage: # needs sage.symbolic
|
|
923
|
+
sage: p = plot(cos, (-3,3))
|
|
924
|
+
sage: p.tick_label_color()
|
|
925
|
+
(0, 0, 0)
|
|
926
|
+
sage: p.tick_label_color((1,0,0))
|
|
927
|
+
sage: p.tick_label_color()
|
|
928
|
+
(1.0, 0.0, 0.0)
|
|
929
|
+
sage: p
|
|
930
|
+
Graphics object consisting of 1 graphics primitive
|
|
931
|
+
"""
|
|
932
|
+
if c is None:
|
|
933
|
+
try:
|
|
934
|
+
return self._tick_label_color
|
|
935
|
+
except AttributeError:
|
|
936
|
+
self._tick_label_color = (0, 0, 0)
|
|
937
|
+
return self._tick_label_color
|
|
938
|
+
self._tick_label_color = rgbcolor(c)
|
|
939
|
+
|
|
940
|
+
def _repr_(self):
|
|
941
|
+
r"""
|
|
942
|
+
Return a string representation of the graphics objects.
|
|
943
|
+
|
|
944
|
+
OUTPUT: string
|
|
945
|
+
|
|
946
|
+
EXAMPLES:
|
|
947
|
+
|
|
948
|
+
We create a plot and call :meth:`show` on it, which causes it
|
|
949
|
+
to be displayed as a plot::
|
|
950
|
+
|
|
951
|
+
sage: P = plot(cos, (-1,1)) # needs sage.symbolic
|
|
952
|
+
sage: P.show() # needs sage.symbolic
|
|
953
|
+
|
|
954
|
+
Just doing this also displays the plot::
|
|
955
|
+
|
|
956
|
+
sage: P # needs sage.symbolic
|
|
957
|
+
Graphics object consisting of 1 graphics primitive
|
|
958
|
+
|
|
959
|
+
Using the Python `repr` or `str` commands do not display the
|
|
960
|
+
plot::
|
|
961
|
+
|
|
962
|
+
sage: repr(P) # needs sage.symbolic
|
|
963
|
+
'Graphics object consisting of 1 graphics primitive'
|
|
964
|
+
sage: str(P) # needs sage.symbolic
|
|
965
|
+
'Graphics object consisting of 1 graphics primitive'
|
|
966
|
+
sage: print(P) # needs sage.symbolic
|
|
967
|
+
Graphics object consisting of 1 graphics primitive
|
|
968
|
+
|
|
969
|
+
TESTS::
|
|
970
|
+
|
|
971
|
+
sage: P._repr_() # needs sage.symbolic
|
|
972
|
+
'Graphics object consisting of 1 graphics primitive'
|
|
973
|
+
"""
|
|
974
|
+
return str(self)
|
|
975
|
+
|
|
976
|
+
def _rich_repr_(self, display_manager, **kwds):
|
|
977
|
+
"""
|
|
978
|
+
Rich Output Magic Method.
|
|
979
|
+
|
|
980
|
+
See :mod:`sage.repl.rich_output` for details.
|
|
981
|
+
|
|
982
|
+
EXAMPLES::
|
|
983
|
+
|
|
984
|
+
sage: from sage.repl.rich_output import get_display_manager
|
|
985
|
+
sage: dm = get_display_manager()
|
|
986
|
+
sage: g = Graphics()
|
|
987
|
+
sage: g._rich_repr_(dm)
|
|
988
|
+
OutputImagePng container
|
|
989
|
+
"""
|
|
990
|
+
types = display_manager.types
|
|
991
|
+
prefer_raster = (
|
|
992
|
+
('.png', types.OutputImagePng),
|
|
993
|
+
('.jpg', types.OutputImageJpg),
|
|
994
|
+
('.gif', types.OutputImageGif),
|
|
995
|
+
)
|
|
996
|
+
prefer_vector = (
|
|
997
|
+
('.svg', types.OutputImageSvg),
|
|
998
|
+
('.pdf', types.OutputImagePdf),
|
|
999
|
+
)
|
|
1000
|
+
graphics = display_manager.preferences.graphics
|
|
1001
|
+
if graphics == 'disable':
|
|
1002
|
+
return
|
|
1003
|
+
elif graphics == 'raster' or graphics is None:
|
|
1004
|
+
preferred = prefer_raster + prefer_vector
|
|
1005
|
+
elif graphics == 'vector':
|
|
1006
|
+
preferred = prefer_vector + prefer_raster
|
|
1007
|
+
else:
|
|
1008
|
+
raise ValueError('unknown graphics output preference')
|
|
1009
|
+
for file_ext, output_container in preferred:
|
|
1010
|
+
if output_container in display_manager.supported_output():
|
|
1011
|
+
return display_manager.graphics_from_save(
|
|
1012
|
+
self.save, kwds, file_ext, output_container)
|
|
1013
|
+
|
|
1014
|
+
def __str__(self):
|
|
1015
|
+
r"""
|
|
1016
|
+
Return string representation of this plot.
|
|
1017
|
+
|
|
1018
|
+
OUTPUT: string
|
|
1019
|
+
|
|
1020
|
+
EXAMPLES::
|
|
1021
|
+
|
|
1022
|
+
sage: S = circle((0,0), 2); S.__str__()
|
|
1023
|
+
'Graphics object consisting of 1 graphics primitive'
|
|
1024
|
+
sage: str(S)
|
|
1025
|
+
'Graphics object consisting of 1 graphics primitive'
|
|
1026
|
+
sage: print(S)
|
|
1027
|
+
Graphics object consisting of 1 graphics primitive
|
|
1028
|
+
"""
|
|
1029
|
+
s = "Graphics object consisting of %s graphics primitives" % (len(self))
|
|
1030
|
+
if len(self) == 1:
|
|
1031
|
+
s = s[:-1]
|
|
1032
|
+
return s
|
|
1033
|
+
|
|
1034
|
+
def __getitem__(self, i):
|
|
1035
|
+
"""
|
|
1036
|
+
Return the i-th graphics primitive object:
|
|
1037
|
+
|
|
1038
|
+
EXAMPLES::
|
|
1039
|
+
|
|
1040
|
+
sage: G = circle((1,1),2) + circle((2,2),5); print(G)
|
|
1041
|
+
Graphics object consisting of 2 graphics primitives
|
|
1042
|
+
sage: G[1]
|
|
1043
|
+
Circle defined by (2.0,2.0) with r=5.0
|
|
1044
|
+
"""
|
|
1045
|
+
return self._objects[i]
|
|
1046
|
+
|
|
1047
|
+
def __len__(self):
|
|
1048
|
+
"""
|
|
1049
|
+
If G is of type Graphics, then len(G) gives the number of distinct
|
|
1050
|
+
graphics primitives making up that object.
|
|
1051
|
+
|
|
1052
|
+
EXAMPLES::
|
|
1053
|
+
|
|
1054
|
+
sage: G = circle((1,1),1) + circle((1,2),1) + circle((1,2),5); print(G)
|
|
1055
|
+
Graphics object consisting of 3 graphics primitives
|
|
1056
|
+
sage: len(G)
|
|
1057
|
+
3
|
|
1058
|
+
"""
|
|
1059
|
+
return len(self._objects)
|
|
1060
|
+
|
|
1061
|
+
def __delitem__(self, i):
|
|
1062
|
+
"""
|
|
1063
|
+
If G is of type Graphics, then del(G[i]) removes the i-th distinct
|
|
1064
|
+
graphic primitive making up that object.
|
|
1065
|
+
|
|
1066
|
+
EXAMPLES::
|
|
1067
|
+
|
|
1068
|
+
sage: G = circle((1,1),1) + circle((1,2),1) + circle((1,2),5); print(G)
|
|
1069
|
+
Graphics object consisting of 3 graphics primitives
|
|
1070
|
+
sage: len(G)
|
|
1071
|
+
3
|
|
1072
|
+
sage: del(G[2])
|
|
1073
|
+
sage: print(G)
|
|
1074
|
+
Graphics object consisting of 2 graphics primitives
|
|
1075
|
+
sage: len(G)
|
|
1076
|
+
2
|
|
1077
|
+
"""
|
|
1078
|
+
del self._objects[int(i)]
|
|
1079
|
+
|
|
1080
|
+
def __setitem__(self, i, x):
|
|
1081
|
+
"""
|
|
1082
|
+
You can replace a :class:`GraphicPrimitive` (point, line, circle, etc...) in
|
|
1083
|
+
a :class:`Graphics` object G with any other :class:`GraphicPrimitive`
|
|
1084
|
+
|
|
1085
|
+
EXAMPLES::
|
|
1086
|
+
|
|
1087
|
+
sage: G = circle((1,1),1) + circle((1,2),1) + circle((1,2),5); print(G)
|
|
1088
|
+
Graphics object consisting of 3 graphics primitives
|
|
1089
|
+
|
|
1090
|
+
::
|
|
1091
|
+
|
|
1092
|
+
sage: p = polygon([[1,3],[2,-2],[1,1],[1,3]]); print(p)
|
|
1093
|
+
Graphics object consisting of 1 graphics primitive
|
|
1094
|
+
|
|
1095
|
+
::
|
|
1096
|
+
|
|
1097
|
+
sage: G[1] = p[0]
|
|
1098
|
+
sage: G # show the plot
|
|
1099
|
+
Graphics object consisting of 3 graphics primitives
|
|
1100
|
+
"""
|
|
1101
|
+
from sage.plot.primitive import GraphicPrimitive
|
|
1102
|
+
if not isinstance(x, GraphicPrimitive):
|
|
1103
|
+
raise TypeError("x must be a GraphicPrimitive")
|
|
1104
|
+
self._objects[int(i)] = x
|
|
1105
|
+
|
|
1106
|
+
def __radd__(self, other):
|
|
1107
|
+
"""
|
|
1108
|
+
Compute and return other + this graphics object.
|
|
1109
|
+
|
|
1110
|
+
This only works when other is a Python int equal to 0. In all other
|
|
1111
|
+
cases a :exc:`TypeError` is raised. The main reason for this
|
|
1112
|
+
function is to make summing a list of graphics objects easier.
|
|
1113
|
+
|
|
1114
|
+
EXAMPLES::
|
|
1115
|
+
|
|
1116
|
+
sage: S = circle((0,0), 2)
|
|
1117
|
+
sage: print(int(0) + S)
|
|
1118
|
+
Graphics object consisting of 1 graphics primitive
|
|
1119
|
+
sage: print(S + int(0))
|
|
1120
|
+
Graphics object consisting of 1 graphics primitive
|
|
1121
|
+
|
|
1122
|
+
The following would fail were it not for this function::
|
|
1123
|
+
|
|
1124
|
+
sage: v = [circle((0,0), 2), circle((2,3), 1)]
|
|
1125
|
+
sage: print(sum(v))
|
|
1126
|
+
Graphics object consisting of 2 graphics primitives
|
|
1127
|
+
"""
|
|
1128
|
+
if isinstance(other, int) and other == 0:
|
|
1129
|
+
return self
|
|
1130
|
+
raise TypeError
|
|
1131
|
+
|
|
1132
|
+
def __add__(self, other):
|
|
1133
|
+
"""
|
|
1134
|
+
If you have any Graphics object G1, you can always add any other
|
|
1135
|
+
amount of Graphics objects G2,G3,... to form a new Graphics object:
|
|
1136
|
+
``G4 = G1 + G2 + G3``.
|
|
1137
|
+
|
|
1138
|
+
The xmin, xmax, ymin, and ymax properties of the graphics objects
|
|
1139
|
+
are expanded to include all objects in both scenes. If the aspect
|
|
1140
|
+
ratio property of either or both objects are set, then the larger
|
|
1141
|
+
aspect ratio is chosen, with 'automatic' being overridden by a
|
|
1142
|
+
numeric aspect ratio.
|
|
1143
|
+
|
|
1144
|
+
If one of the graphics object is set to show a legend, then
|
|
1145
|
+
the resulting object will also be set to show a legend. Legend
|
|
1146
|
+
options are propagated if set. If the same legend option is
|
|
1147
|
+
present in both arguments, the latter value is used.
|
|
1148
|
+
|
|
1149
|
+
EXAMPLES::
|
|
1150
|
+
|
|
1151
|
+
sage: g1 = plot(abs(sqrt(x^3-1)), (x,1,5), frame=True) # needs sage.symbolic
|
|
1152
|
+
sage: g2 = plot(-abs(sqrt(x^3-1)), (x,1,5), color='red') # needs sage.symbolic
|
|
1153
|
+
sage: g1 + g2 # displays the plot # needs sage.symbolic
|
|
1154
|
+
Graphics object consisting of 2 graphics primitives
|
|
1155
|
+
|
|
1156
|
+
TESTS:
|
|
1157
|
+
|
|
1158
|
+
Extra keywords to show are propagated::
|
|
1159
|
+
|
|
1160
|
+
sage: # needs sage.symbolic
|
|
1161
|
+
sage: (g1 + g2)._extra_kwds=={'aspect_ratio': 'automatic', 'frame': True}
|
|
1162
|
+
True
|
|
1163
|
+
sage: g1.set_aspect_ratio(2)
|
|
1164
|
+
sage: (g1+g2).aspect_ratio()
|
|
1165
|
+
2.0
|
|
1166
|
+
sage: g2.set_aspect_ratio(3)
|
|
1167
|
+
sage: (g1+g2).aspect_ratio()
|
|
1168
|
+
3.0
|
|
1169
|
+
|
|
1170
|
+
As are legend options, :issue:`12936`::
|
|
1171
|
+
|
|
1172
|
+
sage: # needs sage.symbolic
|
|
1173
|
+
sage: p1 = plot(x, x, 0, 1)
|
|
1174
|
+
sage: p2 = p1
|
|
1175
|
+
sage: p1.set_legend_options(back_color='black')
|
|
1176
|
+
sage: p2.set_legend_options(shadow=False)
|
|
1177
|
+
sage: p3 = p1 + p2
|
|
1178
|
+
sage: p3._legend_opts
|
|
1179
|
+
{'back_color': 'black', 'shadow': False}
|
|
1180
|
+
|
|
1181
|
+
If the same legend option is specified more than once, the
|
|
1182
|
+
latter takes precedence::
|
|
1183
|
+
|
|
1184
|
+
sage: # needs sage.symbolic
|
|
1185
|
+
sage: p1 = plot(x, x, 0, 1)
|
|
1186
|
+
sage: p2 = p1
|
|
1187
|
+
sage: p1.set_legend_options(shadow=True)
|
|
1188
|
+
sage: p2.set_legend_options(shadow=False)
|
|
1189
|
+
sage: p3 = p1 + p2
|
|
1190
|
+
sage: p3._legend_opts
|
|
1191
|
+
{'shadow': False}
|
|
1192
|
+
|
|
1193
|
+
Flipped axes take precedence over non-flipped axes::
|
|
1194
|
+
|
|
1195
|
+
sage: p1 = plot(x, x, 0, 1, flip_x=True, flip_y=True) # needs sage.symbolic
|
|
1196
|
+
sage: p2 = plot(x^2, x, 0, 1) # needs sage.symbolic
|
|
1197
|
+
sage: [p._extra_kwds[k] for p in [p1 + p2, p2 + p1] # needs sage.symbolic
|
|
1198
|
+
....: for k in ['flip_x', 'flip_y']]
|
|
1199
|
+
[True, True, True, True]
|
|
1200
|
+
"""
|
|
1201
|
+
if isinstance(other, int) and other == 0:
|
|
1202
|
+
return self
|
|
1203
|
+
if not isinstance(other, Graphics):
|
|
1204
|
+
from sage.plot.plot3d.base import Graphics3d
|
|
1205
|
+
if isinstance(other, Graphics3d):
|
|
1206
|
+
return self.plot3d() + other
|
|
1207
|
+
raise TypeError("other (=%s) must be a Graphics objects" % other)
|
|
1208
|
+
g = Graphics()
|
|
1209
|
+
g._objects = self._objects + other._objects
|
|
1210
|
+
g._show_legend = self._show_legend or other._show_legend
|
|
1211
|
+
g._extra_kwds.update(self._extra_kwds)
|
|
1212
|
+
g._extra_kwds.update(other._extra_kwds)
|
|
1213
|
+
g._legend_colors = self._legend_colors + other._legend_colors
|
|
1214
|
+
g._legend_opts.update(self._legend_opts)
|
|
1215
|
+
g._legend_opts.update(other._legend_opts)
|
|
1216
|
+
if 'flip_x' in self._extra_kwds and 'flip_x' in other._extra_kwds:
|
|
1217
|
+
g._extra_kwds['flip_x'] = (self._extra_kwds['flip_x']
|
|
1218
|
+
or other._extra_kwds['flip_x'])
|
|
1219
|
+
if 'flip_y' in self._extra_kwds and 'flip_y' in other._extra_kwds:
|
|
1220
|
+
g._extra_kwds['flip_y'] = (self._extra_kwds['flip_y']
|
|
1221
|
+
or other._extra_kwds['flip_y'])
|
|
1222
|
+
if self.aspect_ratio() == 'automatic':
|
|
1223
|
+
g.set_aspect_ratio(other.aspect_ratio())
|
|
1224
|
+
elif other.aspect_ratio() == 'automatic':
|
|
1225
|
+
g.set_aspect_ratio(self.aspect_ratio())
|
|
1226
|
+
else:
|
|
1227
|
+
g.set_aspect_ratio(max(self.aspect_ratio(), other.aspect_ratio()))
|
|
1228
|
+
return g
|
|
1229
|
+
|
|
1230
|
+
def add_primitive(self, primitive):
|
|
1231
|
+
"""
|
|
1232
|
+
Add a primitive to this graphics object.
|
|
1233
|
+
|
|
1234
|
+
EXAMPLES:
|
|
1235
|
+
|
|
1236
|
+
We give a very explicit example::
|
|
1237
|
+
|
|
1238
|
+
sage: G = Graphics()
|
|
1239
|
+
sage: from math import e
|
|
1240
|
+
sage: from sage.plot.line import Line
|
|
1241
|
+
sage: from sage.plot.arrow import Arrow
|
|
1242
|
+
sage: L = Line([3,4,2,7,-2], [1,2,e,4,5.],
|
|
1243
|
+
....: {'alpha': 1, 'thickness': 2, 'rgbcolor': (0,1,1),
|
|
1244
|
+
....: 'legend_label': ''})
|
|
1245
|
+
sage: A = Arrow(2, -5, .1, .2,
|
|
1246
|
+
....: {'width': 3, 'head': 0, 'rgbcolor': (1,0,0),
|
|
1247
|
+
....: 'linestyle': 'dashed', 'zorder': 8, 'legend_label': ''})
|
|
1248
|
+
sage: G.add_primitive(L)
|
|
1249
|
+
sage: G.add_primitive(A)
|
|
1250
|
+
sage: G
|
|
1251
|
+
Graphics object consisting of 2 graphics primitives
|
|
1252
|
+
"""
|
|
1253
|
+
self._objects.append(primitive)
|
|
1254
|
+
|
|
1255
|
+
def plot(self):
|
|
1256
|
+
"""
|
|
1257
|
+
Draw a 2D plot of this graphics object, which just returns this
|
|
1258
|
+
object since this is already a 2D graphics object.
|
|
1259
|
+
|
|
1260
|
+
EXAMPLES::
|
|
1261
|
+
|
|
1262
|
+
sage: S = circle((0,0), 2)
|
|
1263
|
+
sage: S.plot() is S
|
|
1264
|
+
True
|
|
1265
|
+
|
|
1266
|
+
It does not accept any argument (:issue:`19539`)::
|
|
1267
|
+
|
|
1268
|
+
sage: S.plot(1)
|
|
1269
|
+
Traceback (most recent call last):
|
|
1270
|
+
...
|
|
1271
|
+
TypeError: ...plot() takes 1 positional argument but 2 were given
|
|
1272
|
+
|
|
1273
|
+
sage: S.plot(hey='hou')
|
|
1274
|
+
Traceback (most recent call last):
|
|
1275
|
+
...
|
|
1276
|
+
TypeError: ...plot() got an unexpected keyword argument 'hey'
|
|
1277
|
+
"""
|
|
1278
|
+
return self
|
|
1279
|
+
|
|
1280
|
+
def plot3d(self, z=0, **kwds):
|
|
1281
|
+
"""
|
|
1282
|
+
Return an embedding of this 2D plot into the xy-plane of 3D space,
|
|
1283
|
+
as a 3D plot object. An optional parameter ``z`` can be given to
|
|
1284
|
+
specify the z-coordinate.
|
|
1285
|
+
|
|
1286
|
+
EXAMPLES::
|
|
1287
|
+
|
|
1288
|
+
sage: sum(plot(z*sin(x), 0, 10).plot3d(z) # long time # needs sage.symbolic
|
|
1289
|
+
....: for z in range(6))
|
|
1290
|
+
Graphics3d Object
|
|
1291
|
+
"""
|
|
1292
|
+
from sage.plot.plot3d.base import Graphics3dGroup
|
|
1293
|
+
g = Graphics3dGroup([g.plot3d(**kwds) for g in self._objects])
|
|
1294
|
+
if z:
|
|
1295
|
+
g = g.translate(0, 0, z)
|
|
1296
|
+
return g
|
|
1297
|
+
|
|
1298
|
+
@classmethod
|
|
1299
|
+
def _extract_kwds_for_show(cls, kwds, ignore=[]):
|
|
1300
|
+
"""
|
|
1301
|
+
Extract keywords relevant to show() from the provided dictionary.
|
|
1302
|
+
|
|
1303
|
+
EXAMPLES::
|
|
1304
|
+
|
|
1305
|
+
sage: kwds = {'f': lambda x: x, 'xmin': 0, 'figsize': [1,1], 'plot_points': (40, 40)}
|
|
1306
|
+
sage: G_kwds = Graphics._extract_kwds_for_show(kwds, ignore='xmin')
|
|
1307
|
+
sage: kwds # Note how this action modifies the passed dictionary
|
|
1308
|
+
{'f': <function <lambda> at 0x...>,
|
|
1309
|
+
'plot_points': (40, 40),
|
|
1310
|
+
'xmin': 0}
|
|
1311
|
+
sage: G_kwds
|
|
1312
|
+
{'figsize': [1, 1]}
|
|
1313
|
+
|
|
1314
|
+
This method is intended to be used with _set_extra_kwds(). Here is an
|
|
1315
|
+
idiom to ensure the correct keywords will get passed on to show()::
|
|
1316
|
+
|
|
1317
|
+
sage: options = {} # Usually this will come from an argument
|
|
1318
|
+
sage: g = Graphics()
|
|
1319
|
+
sage: g._set_extra_kwds(Graphics._extract_kwds_for_show(options))
|
|
1320
|
+
"""
|
|
1321
|
+
result = {}
|
|
1322
|
+
for option in cls.SHOW_OPTIONS:
|
|
1323
|
+
if option not in ignore:
|
|
1324
|
+
try:
|
|
1325
|
+
result[option] = kwds.pop(option)
|
|
1326
|
+
except KeyError:
|
|
1327
|
+
pass
|
|
1328
|
+
return result
|
|
1329
|
+
|
|
1330
|
+
def _set_extra_kwds(self, kwds):
|
|
1331
|
+
"""
|
|
1332
|
+
Set a dictionary of keywords that will get passed on to show().
|
|
1333
|
+
|
|
1334
|
+
TESTS::
|
|
1335
|
+
|
|
1336
|
+
sage: g = Graphics()
|
|
1337
|
+
sage: g._extra_kwds
|
|
1338
|
+
{}
|
|
1339
|
+
sage: g._set_extra_kwds({'figsize': [10,10]})
|
|
1340
|
+
sage: g._extra_kwds
|
|
1341
|
+
{'figsize': [10, 10]}
|
|
1342
|
+
sage: g.show() # Now the (blank) plot will be extra large
|
|
1343
|
+
"""
|
|
1344
|
+
self._extra_kwds = kwds
|
|
1345
|
+
|
|
1346
|
+
def _set_scale(self, subplot, scale=None, base=None):
|
|
1347
|
+
"""
|
|
1348
|
+
Set the scale of the axes in the current subplot. This function is
|
|
1349
|
+
only for internal use.
|
|
1350
|
+
|
|
1351
|
+
INPUT:
|
|
1352
|
+
|
|
1353
|
+
- ``subplot`` -- matplotlib Axes instance
|
|
1354
|
+
- ``scale`` -- the scale of the figure. Values it can take are
|
|
1355
|
+
``'linear'``, ``'loglog'``, ``'semilogx'``, ``'semilogy'``. See
|
|
1356
|
+
:meth:`show` for other options it can take.
|
|
1357
|
+
- ``base`` -- the base of the logarithm if a logarithmic scale is
|
|
1358
|
+
set. See :meth:`show` for the options it can take
|
|
1359
|
+
|
|
1360
|
+
OUTPUT:
|
|
1361
|
+
|
|
1362
|
+
The scale in the form of a tuple: (xscale, yscale, basex, basey)
|
|
1363
|
+
|
|
1364
|
+
EXAMPLES::
|
|
1365
|
+
|
|
1366
|
+
sage: # needs sage.symbolic
|
|
1367
|
+
sage: p = plot(x, 1, 10)
|
|
1368
|
+
sage: fig = p.matplotlib()
|
|
1369
|
+
sage: ax = fig.get_axes()[0]
|
|
1370
|
+
sage: p._set_scale(ax, scale='linear', base=2)
|
|
1371
|
+
('linear', 'linear', 10, 10)
|
|
1372
|
+
sage: p._set_scale(ax, scale='semilogy', base=2)
|
|
1373
|
+
('linear', 'log', 10, 2)
|
|
1374
|
+
sage: p._set_scale(ax, scale=('loglog', 2, 3))
|
|
1375
|
+
('log', 'log', 2, 3)
|
|
1376
|
+
sage: p._set_scale(ax, scale=['semilogx', 2])
|
|
1377
|
+
('log', 'linear', 2, 10)
|
|
1378
|
+
|
|
1379
|
+
TESTS::
|
|
1380
|
+
|
|
1381
|
+
sage: # needs sage.symbolic
|
|
1382
|
+
sage: p._set_scale(ax, 'log')
|
|
1383
|
+
Traceback (most recent call last):
|
|
1384
|
+
...
|
|
1385
|
+
ValueError: The scale must be one of 'linear', 'loglog', 'semilogx' or 'semilogy' -- got 'log'
|
|
1386
|
+
sage: p._set_scale(ax, ('loglog', 1))
|
|
1387
|
+
Traceback (most recent call last):
|
|
1388
|
+
...
|
|
1389
|
+
ValueError: The base of the logarithm must be greater than 1
|
|
1390
|
+
"""
|
|
1391
|
+
if scale is None:
|
|
1392
|
+
return ('linear', 'linear', 10, 10)
|
|
1393
|
+
if isinstance(scale, (list, tuple)):
|
|
1394
|
+
if len(scale) != 2 and len(scale) != 3:
|
|
1395
|
+
raise ValueError("If the input is a tuple, it must be of "
|
|
1396
|
+
"the form (scale, base) or (scale, basex, basey)")
|
|
1397
|
+
if len(scale) == 2:
|
|
1398
|
+
base = scale[1]
|
|
1399
|
+
else:
|
|
1400
|
+
base = scale[1:]
|
|
1401
|
+
scale = scale[0]
|
|
1402
|
+
|
|
1403
|
+
if scale not in ('linear', 'loglog', 'semilogx', 'semilogy'):
|
|
1404
|
+
raise ValueError("The scale must be one of 'linear', 'loglog',"
|
|
1405
|
+
f" 'semilogx' or 'semilogy' -- got '{scale}'")
|
|
1406
|
+
|
|
1407
|
+
if isinstance(base, (list, tuple)):
|
|
1408
|
+
basex, basey = base
|
|
1409
|
+
elif base is None:
|
|
1410
|
+
basex = basey = 10
|
|
1411
|
+
else:
|
|
1412
|
+
basex = basey = base
|
|
1413
|
+
|
|
1414
|
+
if basex <= 1 or basey <= 1:
|
|
1415
|
+
raise ValueError("The base of the logarithm must be greater "
|
|
1416
|
+
"than 1")
|
|
1417
|
+
|
|
1418
|
+
xscale = yscale = 'linear'
|
|
1419
|
+
if scale == 'linear':
|
|
1420
|
+
basex = basey = 10
|
|
1421
|
+
elif scale == 'loglog':
|
|
1422
|
+
subplot.set_xscale('log', base=basex)
|
|
1423
|
+
subplot.set_yscale('log', base=basey)
|
|
1424
|
+
xscale = yscale = 'log'
|
|
1425
|
+
elif scale == 'semilogx':
|
|
1426
|
+
subplot.set_xscale('log', base=basex)
|
|
1427
|
+
basey = 10
|
|
1428
|
+
xscale = 'log'
|
|
1429
|
+
elif scale == 'semilogy':
|
|
1430
|
+
subplot.set_yscale('log', base=basey)
|
|
1431
|
+
basex = 10
|
|
1432
|
+
yscale = 'log'
|
|
1433
|
+
|
|
1434
|
+
return (xscale, yscale, basex, basey)
|
|
1435
|
+
|
|
1436
|
+
# This dictionary has the default values for the keywords to show(). When
|
|
1437
|
+
# show is invoked with keyword arguments, those arguments are merged with
|
|
1438
|
+
# this dictionary to create a set of keywords with the defaults filled in.
|
|
1439
|
+
# Then, those keywords are passed on to save().
|
|
1440
|
+
|
|
1441
|
+
# NOTE: If you intend to use a new parameter in show(), you should update
|
|
1442
|
+
# this dictionary to contain the default value for that parameter.
|
|
1443
|
+
|
|
1444
|
+
SHOW_OPTIONS = { # axes options
|
|
1445
|
+
'axes': None, 'axes_labels': None, 'axes_labels_size': None,
|
|
1446
|
+
'axes_pad': None, 'base': None, 'scale': None,
|
|
1447
|
+
'xmin': None, 'xmax': None, 'ymin': None, 'ymax': None,
|
|
1448
|
+
'flip_x': False, 'flip_y': False,
|
|
1449
|
+
# Figure options
|
|
1450
|
+
'aspect_ratio': None, 'dpi': DEFAULT_DPI, 'fig_tight': True,
|
|
1451
|
+
'figsize': None, 'fontsize': None, 'frame': False,
|
|
1452
|
+
'title': None, 'title_pos': None, 'transparent': False,
|
|
1453
|
+
# Grid options
|
|
1454
|
+
'gridlines': None, 'gridlinesstyle': None,
|
|
1455
|
+
'hgridlinesstyle': None, 'vgridlinesstyle': None,
|
|
1456
|
+
# Legend options
|
|
1457
|
+
'legend_options': {}, 'show_legend': None,
|
|
1458
|
+
# Ticks options
|
|
1459
|
+
'ticks': None, 'tick_formatter': None, 'ticks_integer': False,
|
|
1460
|
+
# Text options
|
|
1461
|
+
'typeset': 'default'}
|
|
1462
|
+
|
|
1463
|
+
# Default options for the legends:
|
|
1464
|
+
|
|
1465
|
+
LEGEND_OPTIONS = {'back_color': 'white', 'borderpad': 0.6,
|
|
1466
|
+
'borderaxespad': None,
|
|
1467
|
+
'columnspacing': None,
|
|
1468
|
+
'fancybox': False, 'font_family': 'sans-serif',
|
|
1469
|
+
'font_size': 'medium', 'font_style': 'normal',
|
|
1470
|
+
'font_variant': 'normal', 'font_weight': 'medium',
|
|
1471
|
+
'handlelength': 0.05, 'handletextpad': 0.5,
|
|
1472
|
+
'labelspacing': 0.02, 'loc': 'best',
|
|
1473
|
+
'markerscale': 0.6, 'ncol': 1, 'numpoints': 2,
|
|
1474
|
+
'shadow': True, 'title': None}
|
|
1475
|
+
|
|
1476
|
+
@suboptions('legend', **LEGEND_OPTIONS)
|
|
1477
|
+
def show(self, **kwds):
|
|
1478
|
+
r"""
|
|
1479
|
+
Show this graphics image immediately.
|
|
1480
|
+
|
|
1481
|
+
This method attempts to display the graphics immediately,
|
|
1482
|
+
without waiting for the currently running code (if any) to
|
|
1483
|
+
return to the command line. Be careful, calling it from within
|
|
1484
|
+
a loop will potentially launch a large number of external
|
|
1485
|
+
viewer programs.
|
|
1486
|
+
|
|
1487
|
+
OPTIONAL INPUT:
|
|
1488
|
+
|
|
1489
|
+
- ``dpi`` -- (default: 100) dots per inch
|
|
1490
|
+
|
|
1491
|
+
- ``figsize`` -- (default: [6.4, 4.8]) [width, height] inches. The
|
|
1492
|
+
maximum value of each of the width and the height can be 327
|
|
1493
|
+
inches, at the default ``dpi`` of 100 dpi, which is just shy of
|
|
1494
|
+
the maximum allowed value of 32768 dots (pixels).
|
|
1495
|
+
|
|
1496
|
+
- ``fig_tight`` -- boolean (default: ``True``); whether to clip the drawing
|
|
1497
|
+
tightly around drawn objects. If ``True``, then the resulting
|
|
1498
|
+
image will usually not have dimensions corresponding to
|
|
1499
|
+
``figsize``. If ``False``, the resulting image will have
|
|
1500
|
+
dimensions corresponding to ``figsize``.
|
|
1501
|
+
|
|
1502
|
+
- ``aspect_ratio`` -- the perceived height divided by the
|
|
1503
|
+
perceived width. For example, if the aspect ratio is set to ``1``, circles
|
|
1504
|
+
will look round and a unit square will appear to have sides
|
|
1505
|
+
of equal length, and if the aspect ratio is set ``2``, vertical units will be
|
|
1506
|
+
twice as long as horizontal units, so a unit square will be twice as
|
|
1507
|
+
high as it is wide. If set to ``'automatic'``, the aspect ratio
|
|
1508
|
+
is determined by ``figsize`` and the picture fills the figure.
|
|
1509
|
+
|
|
1510
|
+
- ``axes`` -- (default: ``True``)
|
|
1511
|
+
|
|
1512
|
+
- ``axes_labels`` -- (default: ``None``) list (or tuple) of two
|
|
1513
|
+
strings; the first is used as the label for the horizontal
|
|
1514
|
+
axis, and the second for the vertical axis.
|
|
1515
|
+
|
|
1516
|
+
- ``axes_labels_size`` -- (default: current setting -- 1.6) scale factor
|
|
1517
|
+
relating the size of the axes labels with respect to the size of the
|
|
1518
|
+
tick marks.
|
|
1519
|
+
|
|
1520
|
+
- ``fontsize`` -- (default: current setting -- 10) positive
|
|
1521
|
+
integer; used for axes labels; if you make this very large,
|
|
1522
|
+
you may have to increase figsize to see all labels.
|
|
1523
|
+
|
|
1524
|
+
- ``frame`` -- boolean (default: ``False``); draw a frame around the image
|
|
1525
|
+
|
|
1526
|
+
- ``gridlines`` -- (default: ``None``) can be any of the following:
|
|
1527
|
+
|
|
1528
|
+
- None, False: do not add grid lines.
|
|
1529
|
+
|
|
1530
|
+
- True, "automatic", "major": add grid lines at major ticks of the axes.
|
|
1531
|
+
|
|
1532
|
+
- "minor": add grid at major and minor ticks.
|
|
1533
|
+
|
|
1534
|
+
- [xlist,ylist]: a tuple or list containing
|
|
1535
|
+
two elements, where xlist (or ylist) can be
|
|
1536
|
+
any of the following.
|
|
1537
|
+
|
|
1538
|
+
|
|
1539
|
+
- None, False: don't add horizontal (or vertical) lines.
|
|
1540
|
+
|
|
1541
|
+
- True, "automatic", "major": add horizontal (or vertical) grid lines at
|
|
1542
|
+
the major ticks of the axes.
|
|
1543
|
+
|
|
1544
|
+
- "minor": add horizontal (or vertical) grid lines at major and minor ticks of
|
|
1545
|
+
axes.
|
|
1546
|
+
|
|
1547
|
+
- an iterable yielding numbers n or pairs (n,opts), where n
|
|
1548
|
+
is the coordinate of the line and opt is a dictionary of
|
|
1549
|
+
MATPLOTLIB options for rendering the line.
|
|
1550
|
+
|
|
1551
|
+
|
|
1552
|
+
- ``gridlinesstyle, hgridlinesstyle, vgridlinesstyle`` -
|
|
1553
|
+
(default: ``None``); a dictionary of MATPLOTLIB options for the
|
|
1554
|
+
rendering of the grid lines, the horizontal grid lines or the
|
|
1555
|
+
vertical grid lines, respectively.
|
|
1556
|
+
|
|
1557
|
+
- ``transparent`` -- boolean (default: ``False``); if True, make the
|
|
1558
|
+
background transparent
|
|
1559
|
+
|
|
1560
|
+
- ``axes_pad`` -- (default: 0.02 on ``'linear'`` scale, 1 on
|
|
1561
|
+
``'log'`` scale)
|
|
1562
|
+
|
|
1563
|
+
- In the ``'linear'`` scale, it determines the percentage of the
|
|
1564
|
+
axis range that is added to each end of each axis. This helps
|
|
1565
|
+
avoid problems like clipping lines because of line-width, etc.
|
|
1566
|
+
To get axes that are exactly the specified limits, set
|
|
1567
|
+
``axes_pad`` to zero.
|
|
1568
|
+
|
|
1569
|
+
- On the ``'log'`` scale, it determines the exponent of the
|
|
1570
|
+
fraction of the minimum (resp. maximum) that is subtracted from
|
|
1571
|
+
the minimum (resp. added to the maximum) value of the axis. For
|
|
1572
|
+
instance if the minimum is `m` and the base of the axis is `b`
|
|
1573
|
+
then the new minimum after padding the axis will be
|
|
1574
|
+
`m - m/b^{\mathrm{axes\_pad}}`.
|
|
1575
|
+
|
|
1576
|
+
- ``ticks_integer`` -- boolean (default: ``False``); guarantee that the ticks
|
|
1577
|
+
are integers (the ``ticks`` option, if specified, will
|
|
1578
|
+
override this)
|
|
1579
|
+
|
|
1580
|
+
- ``ticks`` -- a matplotlib locator for the major ticks, or
|
|
1581
|
+
a number. There are several options. For more information about
|
|
1582
|
+
locators, type ``from matplotlib import ticker`` and then
|
|
1583
|
+
``ticker?``.
|
|
1584
|
+
|
|
1585
|
+
- If this is a locator object, then it is the locator for
|
|
1586
|
+
the horizontal axis. A value of None means use the default
|
|
1587
|
+
locator.
|
|
1588
|
+
|
|
1589
|
+
- If it is a list of two locators, then the first is for the
|
|
1590
|
+
horizontal axis and one for the vertical axis. A value of
|
|
1591
|
+
None means use the default locator (so a value of
|
|
1592
|
+
[None, my_locator] uses my_locator for the vertical axis and
|
|
1593
|
+
the default for the horizontal axis).
|
|
1594
|
+
|
|
1595
|
+
- If in either case above one of the entries is a number `m`
|
|
1596
|
+
(something which can be coerced to a float), it will be
|
|
1597
|
+
replaced by a MultipleLocator which places major ticks at
|
|
1598
|
+
integer multiples of `m`. See examples.
|
|
1599
|
+
|
|
1600
|
+
- If in either case above one of the entries is a list of
|
|
1601
|
+
numbers, it will be replaced by a FixedLocator which places
|
|
1602
|
+
ticks at the locations specified. This includes the case of
|
|
1603
|
+
of the empty list, which will give no ticks. See examples.
|
|
1604
|
+
|
|
1605
|
+
- ``tick_formatter`` -- a matplotlib formatter for the major
|
|
1606
|
+
ticks. There are several options. For more information about
|
|
1607
|
+
formatters, type ``from matplotlib import ticker`` and then
|
|
1608
|
+
``ticker?``.
|
|
1609
|
+
|
|
1610
|
+
If the value of this keyword is a single item, then this will
|
|
1611
|
+
give the formatting for the horizontal axis *only* (except for
|
|
1612
|
+
the ``'latex'`` option). If it is a list or tuple, the first
|
|
1613
|
+
is for the horizontal axis, the second for the vertical axis.
|
|
1614
|
+
The options are below:
|
|
1615
|
+
|
|
1616
|
+
- If one of the entries is a formatter object, then it used.
|
|
1617
|
+
A value of None means to use the default locator (so using
|
|
1618
|
+
``tick_formatter=[None, my_formatter]`` uses my_formatter
|
|
1619
|
+
for the vertical axis and the default for the horizontal axis).
|
|
1620
|
+
|
|
1621
|
+
- If one of the entries is a symbolic constant such as `\pi`,
|
|
1622
|
+
`e`, or `sqrt(2)`, ticks will be formatted nicely at rational
|
|
1623
|
+
multiples of this constant.
|
|
1624
|
+
|
|
1625
|
+
.. warning::
|
|
1626
|
+
|
|
1627
|
+
This should only be used with the ``ticks`` option using nice
|
|
1628
|
+
rational multiples of that constant!
|
|
1629
|
+
|
|
1630
|
+
- If one of the entries is the string ``'latex'``, then the
|
|
1631
|
+
formatting will be nice typesetting of the ticks. This is
|
|
1632
|
+
intended to be used when the tick locator for at least one of
|
|
1633
|
+
the axes is a list including some symbolic elements. This uses
|
|
1634
|
+
matplotlib's internal LaTeX rendering engine. If you want to
|
|
1635
|
+
use an external LaTeX compiler, then set the keyword option
|
|
1636
|
+
``typeset``. See examples.
|
|
1637
|
+
|
|
1638
|
+
- ``title`` -- (default: ``None``) the title for the plot
|
|
1639
|
+
|
|
1640
|
+
- ``title_pos`` -- (default: ``None``) the position of the title for the
|
|
1641
|
+
plot. It must be a tuple or a list of two real numbers
|
|
1642
|
+
``(x_pos, y_pos)`` which indicate the relative position of the
|
|
1643
|
+
title within the plot. The plot itself can be considered to
|
|
1644
|
+
occupy, in relative terms, the region within a unit square
|
|
1645
|
+
`[0, 1] \times [0, 1]`. The title text is centered around the
|
|
1646
|
+
horizontal factor ``x_pos`` of the plot. The baseline of the
|
|
1647
|
+
title text is present at the vertical factor ``y_pos`` of the
|
|
1648
|
+
plot. Hence, ``title_pos=(0.5, 0.5)`` will center the title in
|
|
1649
|
+
the plot, whereas ``title_pos=(0.5, 1.1)`` will center the
|
|
1650
|
+
title along the horizontal direction, but will place the title
|
|
1651
|
+
a fraction `0.1` times above the plot.
|
|
1652
|
+
|
|
1653
|
+
- If the first entry is a list of strings (or numbers), then the
|
|
1654
|
+
formatting for the horizontal axis will be typeset with the strings
|
|
1655
|
+
present in the list. Each entry of the list of strings must be
|
|
1656
|
+
provided with a corresponding number in the first entry of
|
|
1657
|
+
``ticks`` to indicate its position on the axis. To typeset the
|
|
1658
|
+
strings with ``'latex'`` enclose them within ``'$'`` symbols. To
|
|
1659
|
+
have similar custom formatting of the labels along the vertical
|
|
1660
|
+
axis, the second entry must be a list of strings and the second
|
|
1661
|
+
entry of ``ticks`` must also be a list of numbers which give the
|
|
1662
|
+
positions of the labels. See the examples below.
|
|
1663
|
+
|
|
1664
|
+
- ``show_legend`` -- (default: ``None``) if ``True``, show the legend
|
|
1665
|
+
|
|
1666
|
+
- ``legend_*`` -- all the options valid for :meth:`set_legend_options`
|
|
1667
|
+
prefixed with ``legend_``
|
|
1668
|
+
|
|
1669
|
+
- ``base`` -- (default: 10) the base of the logarithm if
|
|
1670
|
+
a logarithmic scale is set. This must be greater than 1. The base
|
|
1671
|
+
can be also given as a list or tuple ``(basex, basey)``.
|
|
1672
|
+
``basex`` sets the base of the logarithm along the horizontal
|
|
1673
|
+
axis and ``basey`` sets the base along the vertical axis.
|
|
1674
|
+
|
|
1675
|
+
- ``scale`` -- (default: ``'linear'``) string. The scale of the axes.
|
|
1676
|
+
Possible values are
|
|
1677
|
+
|
|
1678
|
+
- ``'linear'`` -- linear scaling of both the axes
|
|
1679
|
+
- ``'loglog'`` -- sets both the horizontal and vertical axes to
|
|
1680
|
+
logarithmic scale
|
|
1681
|
+
- ``'semilogx'`` -- sets only the horizontal axis to logarithmic
|
|
1682
|
+
scale
|
|
1683
|
+
- ``'semilogy'`` -- sets only the vertical axis to logarithmic
|
|
1684
|
+
scale
|
|
1685
|
+
|
|
1686
|
+
The scale can be also be given as single argument that is a list
|
|
1687
|
+
or tuple ``(scale, base)`` or ``(scale, basex, basey)``.
|
|
1688
|
+
|
|
1689
|
+
.. NOTE::
|
|
1690
|
+
|
|
1691
|
+
- If the ``scale`` is ``'linear'``, then irrespective of what
|
|
1692
|
+
``base`` is set to, it will default to 10 and will remain
|
|
1693
|
+
unused.
|
|
1694
|
+
|
|
1695
|
+
- ``xmin`` -- starting x value in the rendered figure
|
|
1696
|
+
|
|
1697
|
+
- ``xmax`` -- ending x value in the rendered figure
|
|
1698
|
+
|
|
1699
|
+
- ``ymin`` -- starting y value in the rendered figure
|
|
1700
|
+
|
|
1701
|
+
- ``ymax`` -- ending y value in the rendered figure
|
|
1702
|
+
|
|
1703
|
+
- ``flip_x`` -- boolean (default: ``False``); if ``True``, flip the horizontal
|
|
1704
|
+
axis
|
|
1705
|
+
|
|
1706
|
+
- ``flip_y`` -- boolean (default: ``False``); if ``True``, flip the vertical
|
|
1707
|
+
axis
|
|
1708
|
+
|
|
1709
|
+
- ``typeset`` -- (default: ``'default'``) string. The type of
|
|
1710
|
+
font rendering that should be used for the text. The possible
|
|
1711
|
+
values are
|
|
1712
|
+
|
|
1713
|
+
- ``'default'`` -- uses matplotlib's internal text rendering
|
|
1714
|
+
engine called Mathtext ( see
|
|
1715
|
+
https://matplotlib.org/users/mathtext.html ). If you have
|
|
1716
|
+
modified the default matplotlib settings, for instance via
|
|
1717
|
+
a matplotlibrc file, then this option will not change any of
|
|
1718
|
+
those settings.
|
|
1719
|
+
- ``'latex'`` -- laTeX is used for rendering the fonts. This
|
|
1720
|
+
requires LaTeX, dvipng and Ghostscript to be installed
|
|
1721
|
+
- ``'type1'`` -- type 1 fonts are used by matplotlib in the text
|
|
1722
|
+
in the figure. This requires LaTeX, dvipng and Ghostscript to
|
|
1723
|
+
be installed.
|
|
1724
|
+
|
|
1725
|
+
OUTPUT:
|
|
1726
|
+
|
|
1727
|
+
This method does not return anything. Use :meth:`save` if you
|
|
1728
|
+
want to save the figure as an image.
|
|
1729
|
+
|
|
1730
|
+
EXAMPLES::
|
|
1731
|
+
|
|
1732
|
+
sage: c = circle((1,1), 1, color='red')
|
|
1733
|
+
sage: c.show(xmin=-1, xmax=3, ymin=-1, ymax=3)
|
|
1734
|
+
|
|
1735
|
+
You can make the picture larger by changing ``figsize`` with width,
|
|
1736
|
+
height each having a maximum value of 327 inches at default dpi::
|
|
1737
|
+
|
|
1738
|
+
sage: p = ellipse((0,0),4,1)
|
|
1739
|
+
sage: p.show(figsize=[327,10], dpi=100)
|
|
1740
|
+
sage: p.show(figsize=[328,10], dpi=80)
|
|
1741
|
+
|
|
1742
|
+
You can turn off the drawing of the axes::
|
|
1743
|
+
|
|
1744
|
+
sage: show(plot(sin,-4,4), axes=False) # needs sage.symbolic
|
|
1745
|
+
|
|
1746
|
+
You can also label the axes. Putting something in dollar
|
|
1747
|
+
signs formats it as a mathematical expression::
|
|
1748
|
+
|
|
1749
|
+
sage: show(plot(sin,-4,4), axes_labels=('$x$','$y$')) # needs sage.symbolic
|
|
1750
|
+
|
|
1751
|
+
You can add a title to a plot::
|
|
1752
|
+
|
|
1753
|
+
sage: show(plot(sin,-4,4), title=r'A plot of $\sin(x)$') # needs sage.symbolic
|
|
1754
|
+
|
|
1755
|
+
You can also provide the position for the title to the plot. In the
|
|
1756
|
+
plot below the title is placed on the bottom left of the figure.::
|
|
1757
|
+
|
|
1758
|
+
sage: plot(sin, -4, 4, title='Plot sin(x)', title_pos=(0.05,-0.05)) # needs sage.symbolic
|
|
1759
|
+
Graphics object consisting of 1 graphics primitive
|
|
1760
|
+
|
|
1761
|
+
If you want all the text to be rendered by using an external LaTeX
|
|
1762
|
+
installation then set the ``typeset`` to ``'latex'``. This
|
|
1763
|
+
requires that LaTeX, dvipng and Ghostscript be installed::
|
|
1764
|
+
|
|
1765
|
+
sage: plot(x, typeset='latex') # optional - latex, needs sage.symbolic
|
|
1766
|
+
Graphics object consisting of 1 graphics primitive
|
|
1767
|
+
|
|
1768
|
+
If you want all the text in your plot to use Type 1 fonts, then
|
|
1769
|
+
set the ``typeset`` option to ``'type1'``. This requires that
|
|
1770
|
+
LaTeX, dvipng and Ghostscript be installed::
|
|
1771
|
+
|
|
1772
|
+
sage: plot(x, typeset='type1') # optional - latex, needs sage.symbolic
|
|
1773
|
+
Graphics object consisting of 1 graphics primitive
|
|
1774
|
+
|
|
1775
|
+
You can turn on the drawing of a frame around the plots::
|
|
1776
|
+
|
|
1777
|
+
sage: show(plot(sin,-4,4), frame=True) # needs sage.symbolic
|
|
1778
|
+
|
|
1779
|
+
You can make the background transparent::
|
|
1780
|
+
|
|
1781
|
+
sage: plot(sin(x), (x, -4, 4), transparent=True) # needs sage.symbolic
|
|
1782
|
+
Graphics object consisting of 1 graphics primitive
|
|
1783
|
+
|
|
1784
|
+
Prior to :issue:`19485`, legends by default had a shadowless gray
|
|
1785
|
+
background. This behavior can be recovered by passing in certain
|
|
1786
|
+
``legend_options``::
|
|
1787
|
+
|
|
1788
|
+
sage: p = plot(sin(x), legend_label=r'$\sin(x)$') # needs sage.symbolic
|
|
1789
|
+
sage: p.show(legend_options={'back_color': (0.9,0.9,0.9), # needs sage.symbolic
|
|
1790
|
+
....: 'shadow': False})
|
|
1791
|
+
|
|
1792
|
+
We can change the scale of the axes in the graphics before
|
|
1793
|
+
displaying::
|
|
1794
|
+
|
|
1795
|
+
sage: G = plot(exp, 1, 10) # needs sage.symbolic
|
|
1796
|
+
sage: G.show(scale='semilogy') # needs sage.symbolic
|
|
1797
|
+
|
|
1798
|
+
We can change the base of the logarithm too. The following changes
|
|
1799
|
+
the vertical axis to be on log scale, and with base 2. Note that
|
|
1800
|
+
the ``base`` argument will ignore any changes to the axis which is
|
|
1801
|
+
in linear scale.::
|
|
1802
|
+
|
|
1803
|
+
sage: G.show(scale='semilogy', base=2) # y axis as powers of 2 # long time, needs sage.symbolic
|
|
1804
|
+
|
|
1805
|
+
::
|
|
1806
|
+
|
|
1807
|
+
sage: G.show(scale='semilogy', base=(3,2)) # base ignored for x-axis # needs sage.symbolic
|
|
1808
|
+
|
|
1809
|
+
The scale can be also given as a 2-tuple or a 3-tuple.::
|
|
1810
|
+
|
|
1811
|
+
sage: G.show(scale=('loglog', 2.1)) # both x and y axes in base 2.1 # long time, needs sage.symbolic
|
|
1812
|
+
|
|
1813
|
+
::
|
|
1814
|
+
|
|
1815
|
+
sage: G.show(scale=('loglog', 2, 3)) # x in base 2, y in base 3 # long time, needs sage.symbolic
|
|
1816
|
+
|
|
1817
|
+
The base need not be an integer, though it does have to be made
|
|
1818
|
+
a float.::
|
|
1819
|
+
|
|
1820
|
+
sage: G.show(scale='semilogx', base=float(e)) # base is e # needs sage.symbolic
|
|
1821
|
+
|
|
1822
|
+
Logarithmic scale can be used for various kinds of plots. Here are
|
|
1823
|
+
some examples.::
|
|
1824
|
+
|
|
1825
|
+
sage: G = list_plot([10**i for i in range(10)]) # long time, needs sage.symbolic
|
|
1826
|
+
sage: G.show(scale='semilogy') # long time, needs sage.symbolic
|
|
1827
|
+
|
|
1828
|
+
::
|
|
1829
|
+
|
|
1830
|
+
sage: G = parametric_plot((x, x**2), (x, 1, 10)) # needs sage.symbolic
|
|
1831
|
+
sage: G.show(scale='loglog') # needs sage.symbolic
|
|
1832
|
+
|
|
1833
|
+
::
|
|
1834
|
+
|
|
1835
|
+
sage: disk((5,5), 4, (0, 3*pi/2)).show(scale='loglog',base=2) # needs sage.symbolic
|
|
1836
|
+
|
|
1837
|
+
::
|
|
1838
|
+
|
|
1839
|
+
sage: x, y = var('x, y') # needs sage.symbolic
|
|
1840
|
+
sage: G = plot_vector_field((2^x,y^2), (x,1,10), (y,1,100)) # needs sage.symbolic
|
|
1841
|
+
sage: G.show(scale='semilogx',base=2) # needs sage.symbolic
|
|
1842
|
+
|
|
1843
|
+
Flip the horizontal or vertical axis.
|
|
1844
|
+
|
|
1845
|
+
::
|
|
1846
|
+
|
|
1847
|
+
sage: G = plot(x^3, -2, 3) # needs sage.symbolic
|
|
1848
|
+
sage: G.show(flip_x=True) # needs sage.symbolic
|
|
1849
|
+
sage: G.show(flip_y=True) # needs sage.symbolic
|
|
1850
|
+
|
|
1851
|
+
Add grid lines at the major ticks of the axes.
|
|
1852
|
+
|
|
1853
|
+
::
|
|
1854
|
+
|
|
1855
|
+
sage: c = circle((0,0), 1)
|
|
1856
|
+
sage: c.show(gridlines=True)
|
|
1857
|
+
sage: c.show(gridlines='automatic')
|
|
1858
|
+
sage: c.show(gridlines='major')
|
|
1859
|
+
|
|
1860
|
+
Add grid lines at the major and minor ticks of the axes.
|
|
1861
|
+
|
|
1862
|
+
::
|
|
1863
|
+
|
|
1864
|
+
sage: # needs sage.symbolic
|
|
1865
|
+
sage: u,v = var('u v')
|
|
1866
|
+
sage: f = exp(-(u^2+v^2))
|
|
1867
|
+
sage: p = plot_vector_field(f.gradient(), (u,-2,2), (v,-2,2))
|
|
1868
|
+
sage: p.show(gridlines='minor')
|
|
1869
|
+
|
|
1870
|
+
Add only horizontal or vertical grid lines.
|
|
1871
|
+
|
|
1872
|
+
::
|
|
1873
|
+
|
|
1874
|
+
sage: p = plot(sin, -10, 20) # needs sage.symbolic
|
|
1875
|
+
sage: p.show(gridlines=[None, "automatic"]) # needs sage.symbolic
|
|
1876
|
+
sage: p.show(gridlines=["minor", False]) # needs sage.symbolic
|
|
1877
|
+
|
|
1878
|
+
Add grid lines at specific positions (using lists/tuples).
|
|
1879
|
+
|
|
1880
|
+
::
|
|
1881
|
+
|
|
1882
|
+
sage: x, y = var('x, y') # needs sage.symbolic
|
|
1883
|
+
sage: p = implicit_plot((y^2-x^2)*(x-1)*(2*x-3) - 4*(x^2+y^2-2*x)^2, # needs sage.symbolic
|
|
1884
|
+
....: (x,-2,2), (y,-2,2), plot_points=1000)
|
|
1885
|
+
sage: p.show(gridlines=[[1,0],[-1,0,1]]) # needs sage.symbolic
|
|
1886
|
+
|
|
1887
|
+
Add grid lines at specific positions (using iterators).
|
|
1888
|
+
|
|
1889
|
+
::
|
|
1890
|
+
|
|
1891
|
+
sage: def maple_leaf(t):
|
|
1892
|
+
....: return (100/(100+(t-pi/2)^8))*(2-sin(7*t)-cos(30*t)/2)
|
|
1893
|
+
sage: p = polar_plot(maple_leaf, -pi/4, 3*pi/2, # long time, needs sage.symbolic
|
|
1894
|
+
....: color='red',plot_points=1000)
|
|
1895
|
+
sage: p.show(gridlines=([-3,-2.75,..,3], range(-1,5,2))) # long time, needs sage.symbolic
|
|
1896
|
+
|
|
1897
|
+
Add grid lines at specific positions (using functions).
|
|
1898
|
+
|
|
1899
|
+
::
|
|
1900
|
+
|
|
1901
|
+
sage: # needs sage.symbolic
|
|
1902
|
+
sage: y = x^5 + 4*x^4 - 10*x^3 - 40*x^2 + 9*x + 36
|
|
1903
|
+
sage: p = plot(y, -4.1, 1.1)
|
|
1904
|
+
sage: xlines = lambda a, b: [z for z, m in y.roots()]
|
|
1905
|
+
sage: p.show(gridlines=[xlines, [0]], frame=True, axes=False)
|
|
1906
|
+
|
|
1907
|
+
Change the style of all the grid lines.
|
|
1908
|
+
|
|
1909
|
+
::
|
|
1910
|
+
|
|
1911
|
+
sage: b = bar_chart([-3,5,-6,11], color='red')
|
|
1912
|
+
sage: b.show(gridlines=([-1,-0.5,..,4], True),
|
|
1913
|
+
....: gridlinesstyle=dict(color='blue', linestyle=':'))
|
|
1914
|
+
|
|
1915
|
+
Change the style of the horizontal or vertical grid lines
|
|
1916
|
+
separately.
|
|
1917
|
+
|
|
1918
|
+
::
|
|
1919
|
+
|
|
1920
|
+
sage: p = polar_plot(2 + 2*cos(x), 0, 2*pi, color=hue(0.3)) # needs sage.symbolic
|
|
1921
|
+
sage: p.show(gridlines=True, # needs sage.symbolic
|
|
1922
|
+
....: hgridlinesstyle=dict(color='orange', linewidth=1.0),
|
|
1923
|
+
....: vgridlinesstyle=dict(color='blue', linestyle=':'))
|
|
1924
|
+
|
|
1925
|
+
Change the style of each grid line individually.
|
|
1926
|
+
|
|
1927
|
+
::
|
|
1928
|
+
|
|
1929
|
+
sage: x, y = var('x, y') # needs sage.symbolic
|
|
1930
|
+
sage: p = implicit_plot((y^2-x^2)*(x-1)*(2*x-3) - 4*(x^2+y^2-2*x)^2, # needs sage.symbolic
|
|
1931
|
+
....: (x,-2,2), (y,-2,2), plot_points=1000)
|
|
1932
|
+
sage: p.show(gridlines=( # needs sage.symbolic
|
|
1933
|
+
....: [
|
|
1934
|
+
....: (1,{"color":"red","linestyle":":"}),
|
|
1935
|
+
....: (0,{"color":"blue","linestyle":"--"})
|
|
1936
|
+
....: ],
|
|
1937
|
+
....: [
|
|
1938
|
+
....: (-1,{"color":"red","linestyle":":"}),
|
|
1939
|
+
....: (0,{"color":"blue","linestyle":"--"}),
|
|
1940
|
+
....: (1,{"color":"red","linestyle":":"}),
|
|
1941
|
+
....: ]
|
|
1942
|
+
....: ),
|
|
1943
|
+
....: gridlinesstyle=dict(marker='x',color='black'))
|
|
1944
|
+
|
|
1945
|
+
Grid lines can be added to contour plots.
|
|
1946
|
+
|
|
1947
|
+
::
|
|
1948
|
+
|
|
1949
|
+
sage: f = sin(x^2 + y^2)*cos(x)*sin(y) # needs sage.symbolic
|
|
1950
|
+
sage: c = contour_plot(f, (x, -4, 4), (y, -4, 4), plot_points=100) # needs sage.symbolic
|
|
1951
|
+
sage: c.show(gridlines=True, # needs sage.symbolic
|
|
1952
|
+
....: gridlinesstyle={'linestyle': ':', 'linewidth': 1, 'color': 'red'})
|
|
1953
|
+
|
|
1954
|
+
Grid lines can be added to matrix plots.
|
|
1955
|
+
|
|
1956
|
+
::
|
|
1957
|
+
|
|
1958
|
+
sage: M = MatrixSpace(QQ,10).random_element()
|
|
1959
|
+
sage: matrix_plot(M).show(gridlines=True)
|
|
1960
|
+
|
|
1961
|
+
By default, Sage increases the horizontal and vertical axes
|
|
1962
|
+
limits by a certain percentage in all directions. This is
|
|
1963
|
+
controlled by the ``axes_pad`` parameter. Increasing the range
|
|
1964
|
+
of the axes helps avoid problems with lines and dots being
|
|
1965
|
+
clipped because the linewidth extends beyond the axes. To get
|
|
1966
|
+
axes limits that are exactly what is specified, set
|
|
1967
|
+
``axes_pad`` to zero. Compare the following two examples
|
|
1968
|
+
|
|
1969
|
+
::
|
|
1970
|
+
|
|
1971
|
+
sage: (plot(sin(x), (x, -pi, pi), thickness=2) # needs sage.symbolic
|
|
1972
|
+
....: + point((pi, -1), pointsize=15))
|
|
1973
|
+
Graphics object consisting of 2 graphics primitives
|
|
1974
|
+
sage: (plot(sin(x), (x, -pi, pi), thickness=2, axes_pad=0) # needs sage.symbolic
|
|
1975
|
+
....: + point((pi, -1), pointsize=15))
|
|
1976
|
+
Graphics object consisting of 2 graphics primitives
|
|
1977
|
+
|
|
1978
|
+
The behavior of the ``axes_pad`` parameter is different if the axis
|
|
1979
|
+
is in the ``'log'`` scale. If `b` is the base of the axis, the
|
|
1980
|
+
minimum value of the axis, is decreased by the factor
|
|
1981
|
+
`1/b^{\mathrm{axes\_pad}}` of the minimum and the maximum value of the axis
|
|
1982
|
+
is increased by the same factor of the maximum value. Compare the
|
|
1983
|
+
axes in the following two plots to see the difference.
|
|
1984
|
+
|
|
1985
|
+
::
|
|
1986
|
+
|
|
1987
|
+
sage: plot_loglog(x, (1.1*10**-2, 9990)) # needs sage.symbolic
|
|
1988
|
+
Graphics object consisting of 1 graphics primitive
|
|
1989
|
+
|
|
1990
|
+
sage: plot_loglog(x, (1.1*10**-2, 9990), axes_pad=0) # needs sage.symbolic
|
|
1991
|
+
Graphics object consisting of 1 graphics primitive
|
|
1992
|
+
|
|
1993
|
+
Via matplotlib, Sage allows setting of custom ticks. See above
|
|
1994
|
+
for more details.
|
|
1995
|
+
|
|
1996
|
+
Here the labels are not so useful::
|
|
1997
|
+
|
|
1998
|
+
sage: plot(sin(pi*x), (x, -8, 8)) # needs sage.symbolic
|
|
1999
|
+
Graphics object consisting of 1 graphics primitive
|
|
2000
|
+
|
|
2001
|
+
Now put ticks at multiples of 2::
|
|
2002
|
+
|
|
2003
|
+
sage: plot(sin(pi*x), (x, -8, 8), ticks=2) # needs sage.symbolic
|
|
2004
|
+
Graphics object consisting of 1 graphics primitive
|
|
2005
|
+
|
|
2006
|
+
Or just choose where you want the ticks::
|
|
2007
|
+
|
|
2008
|
+
sage: plot(sin(pi*x), (x, -8, 8), ticks=[[-7,-3,0,3,7], [-1/2,0,1/2]]) # needs sage.symbolic
|
|
2009
|
+
Graphics object consisting of 1 graphics primitive
|
|
2010
|
+
|
|
2011
|
+
Or no ticks at all::
|
|
2012
|
+
|
|
2013
|
+
sage: plot(sin(pi*x), (x, -8, 8), ticks=[[], []]) # needs sage.symbolic
|
|
2014
|
+
Graphics object consisting of 1 graphics primitive
|
|
2015
|
+
|
|
2016
|
+
This can be very helpful in showing certain features of plots. ::
|
|
2017
|
+
|
|
2018
|
+
sage: plot(1.5/(1+e^(-x)), (x, -10, 10)) # doesn't quite show value of inflection point # needs sage.symbolic
|
|
2019
|
+
Graphics object consisting of 1 graphics primitive
|
|
2020
|
+
|
|
2021
|
+
::
|
|
2022
|
+
|
|
2023
|
+
sage: plot(1.5/(1+e^(-x)), (x, -10, 10), # It's right at f(x)=0.75! # needs sage.symbolic
|
|
2024
|
+
....: ticks=[None, 1.5/4])
|
|
2025
|
+
Graphics object consisting of 1 graphics primitive
|
|
2026
|
+
|
|
2027
|
+
But be careful to leave enough room for at least two major ticks, so that
|
|
2028
|
+
the user can tell what the scale is::
|
|
2029
|
+
|
|
2030
|
+
sage: plot(x^2, (x,1,8), ticks=6).show() # needs sage.symbolic
|
|
2031
|
+
Traceback (most recent call last):
|
|
2032
|
+
...
|
|
2033
|
+
ValueError: Expand the range of the independent variable to
|
|
2034
|
+
allow two multiples of your tick locator (option `ticks`).
|
|
2035
|
+
|
|
2036
|
+
We can also do custom formatting if you need it. See above for full
|
|
2037
|
+
details::
|
|
2038
|
+
|
|
2039
|
+
sage: plot(2*x + 1, (x,0,5), # not tested (broken with matplotlib 3.6), needs sage.symbolic
|
|
2040
|
+
....: ticks=[[0,1,e,pi,sqrt(20)], 2],
|
|
2041
|
+
....: tick_formatter='latex')
|
|
2042
|
+
Graphics object consisting of 1 graphics primitive
|
|
2043
|
+
|
|
2044
|
+
This is particularly useful when setting custom ticks in multiples
|
|
2045
|
+
of `\pi`.
|
|
2046
|
+
|
|
2047
|
+
::
|
|
2048
|
+
|
|
2049
|
+
sage: plot(sin(x), (x,0,2*pi), ticks=pi/3, tick_formatter=pi) # needs sage.symbolic
|
|
2050
|
+
Graphics object consisting of 1 graphics primitive
|
|
2051
|
+
|
|
2052
|
+
But keep in mind that you will get exactly the formatting you asked
|
|
2053
|
+
for if you specify both formatters. The first syntax is recommended
|
|
2054
|
+
for best style in that case. ::
|
|
2055
|
+
|
|
2056
|
+
sage: plot(arcsin(x), (x,-1,1), ticks=[None, pi/6], # Nice-looking! # needs sage.symbolic
|
|
2057
|
+
....: tick_formatter=["latex", pi])
|
|
2058
|
+
Graphics object consisting of 1 graphics primitive
|
|
2059
|
+
|
|
2060
|
+
::
|
|
2061
|
+
|
|
2062
|
+
sage: plot(arcsin(x), (x,-1,1), ticks=[None, pi/6], # Not so nice-looking # needs sage.symbolic
|
|
2063
|
+
....: tick_formatter=[None, pi])
|
|
2064
|
+
Graphics object consisting of 1 graphics primitive
|
|
2065
|
+
|
|
2066
|
+
Custom tick labels can be provided by providing the keyword
|
|
2067
|
+
``tick_formatter`` with the list of labels, and simultaneously
|
|
2068
|
+
providing the keyword ``ticks`` with the positions of the labels. ::
|
|
2069
|
+
|
|
2070
|
+
sage: plot(x, (x,0,3), ticks=[[1,2.5], [0.5,1,2]], # needs sage.symbolic
|
|
2071
|
+
....: tick_formatter=[["$x_1$","$x_2$"], ["$y_1$","$y_2$","$y_3$"]])
|
|
2072
|
+
Graphics object consisting of 1 graphics primitive
|
|
2073
|
+
|
|
2074
|
+
The following sets the custom tick labels only along the horizontal
|
|
2075
|
+
axis. ::
|
|
2076
|
+
|
|
2077
|
+
sage: plot(x**2, (x,0,2), ticks=[[1,2], None], # needs sage.symbolic
|
|
2078
|
+
....: tick_formatter=[["$x_1$","$x_2$"], None])
|
|
2079
|
+
Graphics object consisting of 1 graphics primitive
|
|
2080
|
+
|
|
2081
|
+
If the number of tick labels do not match the number of positions of
|
|
2082
|
+
tick labels, then it results in an error.::
|
|
2083
|
+
|
|
2084
|
+
sage: plot(x**2, (x,0,2), ticks=[[2], None], # needs sage.symbolic
|
|
2085
|
+
....: tick_formatter=[["$x_1$","$x_2$"], None]).show()
|
|
2086
|
+
Traceback (most recent call last):
|
|
2087
|
+
...
|
|
2088
|
+
ValueError: If the first component of the list `tick_formatter` is a list
|
|
2089
|
+
then the first component of `ticks` must also be a list of equal length.
|
|
2090
|
+
|
|
2091
|
+
When using logarithmic scale along the axis, make sure to have
|
|
2092
|
+
enough room for two ticks so that the user can tell what the scale
|
|
2093
|
+
is. This can be effected by increasing the range of the independent
|
|
2094
|
+
variable, or by changing the ``base``, or by providing enough tick
|
|
2095
|
+
locations by using the ``ticks`` parameter.
|
|
2096
|
+
|
|
2097
|
+
By default, Sage will expand the variable range so that at least two
|
|
2098
|
+
ticks are included along the logarithmic axis. However, if you
|
|
2099
|
+
specify ``ticks`` manually, this safety measure can be defeated::
|
|
2100
|
+
|
|
2101
|
+
sage: list_plot_loglog([(1,2),(2,3)], plotjoined=True, ticks=[[1],[1]])
|
|
2102
|
+
doctest:...: UserWarning: The x-axis contains fewer than 2 ticks;
|
|
2103
|
+
the logarithmic scale of the plot may not be apparent to the reader.
|
|
2104
|
+
doctest:...: UserWarning: The y-axis contains fewer than 2 ticks;
|
|
2105
|
+
the logarithmic scale of the plot may not be apparent to the reader.
|
|
2106
|
+
Graphics object consisting of 1 graphics primitive
|
|
2107
|
+
|
|
2108
|
+
This one works, since the horizontal axis is automatically expanded
|
|
2109
|
+
to contain two ticks and the vertical axis is provided with two ticks::
|
|
2110
|
+
|
|
2111
|
+
sage: list_plot_loglog([(1,2),(2,3)], plotjoined=True, ticks=[None,[1,10]])
|
|
2112
|
+
Graphics object consisting of 1 graphics primitive
|
|
2113
|
+
|
|
2114
|
+
Another example in the log scale where both the axes are automatically
|
|
2115
|
+
expanded to show two major ticks::
|
|
2116
|
+
|
|
2117
|
+
sage: list_plot_loglog([(2,0.5), (3, 4)], plotjoined=True)
|
|
2118
|
+
Graphics object consisting of 1 graphics primitive
|
|
2119
|
+
|
|
2120
|
+
When using ``title_pos``, it must be ensured that a list or a tuple
|
|
2121
|
+
of length two is used. Otherwise, a warning is raised::
|
|
2122
|
+
|
|
2123
|
+
sage: plot(x, -4, 4, title='Plot x', title_pos=0.05) # needs sage.symbolic
|
|
2124
|
+
doctest:...: ...RichReprWarning: Exception in _rich_repr_ while displaying
|
|
2125
|
+
object: 'title_pos' must be a list or tuple of two real numbers.
|
|
2126
|
+
Graphics object consisting of 1 graphics primitive
|
|
2127
|
+
|
|
2128
|
+
TESTS:
|
|
2129
|
+
|
|
2130
|
+
The following tests result in a segmentation fault and should not
|
|
2131
|
+
be run or doctested::
|
|
2132
|
+
|
|
2133
|
+
sage: p = ellipse((0,0),4,1)
|
|
2134
|
+
sage: p.show(figsize=[232,232],dpi=100) # not tested
|
|
2135
|
+
------------------------------------------------------------------------
|
|
2136
|
+
Unhandled SIGSEGV: A segmentation fault occurred.
|
|
2137
|
+
This probably occurred because a *compiled* module has a bug
|
|
2138
|
+
in it and is not properly wrapped with sig_on(), sig_off().
|
|
2139
|
+
Python will now terminate.
|
|
2140
|
+
------------------------------------------------------------------------
|
|
2141
|
+
sage: p.show(figsize=[327,181],dpi=100) # not tested
|
|
2142
|
+
------------------------------------------------------------------------
|
|
2143
|
+
Unhandled SIGSEGV: A segmentation fault occurred.
|
|
2144
|
+
This probably occurred because a *compiled* module has a bug
|
|
2145
|
+
in it and is not properly wrapped with sig_on(), sig_off().
|
|
2146
|
+
Python will now terminate.
|
|
2147
|
+
------------------------------------------------------------------------
|
|
2148
|
+
|
|
2149
|
+
The following tests ensure we give a good error message for
|
|
2150
|
+
negative figsizes::
|
|
2151
|
+
|
|
2152
|
+
sage: # needs sage.symbolic
|
|
2153
|
+
sage: P = plot(x^2,(x,0,1))
|
|
2154
|
+
sage: P.show(figsize=[-1,1])
|
|
2155
|
+
Traceback (most recent call last):
|
|
2156
|
+
...
|
|
2157
|
+
ValueError: figsize should be positive numbers, not -1.0 and 1.0
|
|
2158
|
+
sage: P.show(figsize=-1)
|
|
2159
|
+
Traceback (most recent call last):
|
|
2160
|
+
...
|
|
2161
|
+
ValueError: figsize should be positive, not -1.0
|
|
2162
|
+
sage: P.show(figsize=x^2)
|
|
2163
|
+
Traceback (most recent call last):
|
|
2164
|
+
...
|
|
2165
|
+
TypeError: figsize should be a positive number, not x^2
|
|
2166
|
+
sage: P.show(figsize=[2,3,4])
|
|
2167
|
+
Traceback (most recent call last):
|
|
2168
|
+
...
|
|
2169
|
+
ValueError: figsize should be a positive number or
|
|
2170
|
+
a list of two positive numbers, not [2, 3, 4]
|
|
2171
|
+
sage: P.show(figsize=[sqrt(2),sqrt(3)])
|
|
2172
|
+
"""
|
|
2173
|
+
from sage.repl.rich_output import get_display_manager
|
|
2174
|
+
dm = get_display_manager()
|
|
2175
|
+
dm.display_immediately(self, **kwds)
|
|
2176
|
+
|
|
2177
|
+
def xmin(self, xmin=None):
|
|
2178
|
+
"""
|
|
2179
|
+
EXAMPLES::
|
|
2180
|
+
|
|
2181
|
+
sage: g = line([(-1,1), (3,2)])
|
|
2182
|
+
sage: g.xmin()
|
|
2183
|
+
-1.0
|
|
2184
|
+
sage: g.xmin(-3)
|
|
2185
|
+
sage: g.xmin()
|
|
2186
|
+
-3.0
|
|
2187
|
+
"""
|
|
2188
|
+
if xmin is None:
|
|
2189
|
+
return self.get_axes_range()['xmin']
|
|
2190
|
+
else:
|
|
2191
|
+
self.set_axes_range(xmin=xmin)
|
|
2192
|
+
|
|
2193
|
+
def xmax(self, xmax=None):
|
|
2194
|
+
"""
|
|
2195
|
+
EXAMPLES::
|
|
2196
|
+
|
|
2197
|
+
sage: g = line([(-1,1), (3,2)])
|
|
2198
|
+
sage: g.xmax()
|
|
2199
|
+
3.0
|
|
2200
|
+
sage: g.xmax(10)
|
|
2201
|
+
sage: g.xmax()
|
|
2202
|
+
10.0
|
|
2203
|
+
"""
|
|
2204
|
+
if xmax is None:
|
|
2205
|
+
return self.get_axes_range()['xmax']
|
|
2206
|
+
else:
|
|
2207
|
+
self.set_axes_range(xmax=xmax)
|
|
2208
|
+
|
|
2209
|
+
def ymin(self, ymin=None):
|
|
2210
|
+
"""
|
|
2211
|
+
EXAMPLES::
|
|
2212
|
+
|
|
2213
|
+
sage: g = line([(-1,1), (3,2)])
|
|
2214
|
+
sage: g.ymin()
|
|
2215
|
+
1.0
|
|
2216
|
+
sage: g.ymin(-3)
|
|
2217
|
+
sage: g.ymin()
|
|
2218
|
+
-3.0
|
|
2219
|
+
"""
|
|
2220
|
+
if ymin is None:
|
|
2221
|
+
return self.get_axes_range()['ymin']
|
|
2222
|
+
else:
|
|
2223
|
+
self.set_axes_range(ymin=ymin)
|
|
2224
|
+
|
|
2225
|
+
def ymax(self, ymax=None):
|
|
2226
|
+
"""
|
|
2227
|
+
EXAMPLES::
|
|
2228
|
+
|
|
2229
|
+
sage: g = line([(-1,1), (3,2)])
|
|
2230
|
+
sage: g.ymax()
|
|
2231
|
+
2.0
|
|
2232
|
+
sage: g.ymax(10)
|
|
2233
|
+
sage: g.ymax()
|
|
2234
|
+
10.0
|
|
2235
|
+
"""
|
|
2236
|
+
if ymax is None:
|
|
2237
|
+
return self.get_axes_range()['ymax']
|
|
2238
|
+
else:
|
|
2239
|
+
self.set_axes_range(ymax=ymax)
|
|
2240
|
+
|
|
2241
|
+
def get_minmax_data(self):
|
|
2242
|
+
r"""
|
|
2243
|
+
Return the x and y coordinate minimum and maximum.
|
|
2244
|
+
|
|
2245
|
+
.. warning::
|
|
2246
|
+
|
|
2247
|
+
The returned dictionary is mutable, but changing it does
|
|
2248
|
+
not change the xmin/xmax/ymin/ymax data. The minmax data is a function
|
|
2249
|
+
of the primitives which make up this Graphics object. To change the
|
|
2250
|
+
range of the axes, call methods :meth:`xmin`, :meth:`xmax`,
|
|
2251
|
+
:meth:`ymin`, :meth:`ymax`, or :meth:`set_axes_range`.
|
|
2252
|
+
|
|
2253
|
+
OUTPUT:
|
|
2254
|
+
|
|
2255
|
+
A dictionary whose keys give the xmin, xmax, ymin, and ymax
|
|
2256
|
+
data for this graphic.
|
|
2257
|
+
|
|
2258
|
+
EXAMPLES::
|
|
2259
|
+
|
|
2260
|
+
sage: g = line([(-1,1), (3,2)])
|
|
2261
|
+
sage: list(sorted(g.get_minmax_data().items()))
|
|
2262
|
+
[('xmax', 3.0), ('xmin', -1.0), ('ymax', 2.0), ('ymin', 1.0)]
|
|
2263
|
+
|
|
2264
|
+
Note that changing ymax doesn't change the output of get_minmax_data::
|
|
2265
|
+
|
|
2266
|
+
sage: g.ymax(10)
|
|
2267
|
+
sage: list(sorted(g.get_minmax_data().items()))
|
|
2268
|
+
[('xmax', 3.0), ('xmin', -1.0), ('ymax', 2.0), ('ymin', 1.0)]
|
|
2269
|
+
|
|
2270
|
+
The width/height ratio (in output units, after factoring in the
|
|
2271
|
+
chosen aspect ratio) of the plot is limited to `10^{-15}\dots
|
|
2272
|
+
10^{15}`, otherwise floating point errors cause problems in
|
|
2273
|
+
matplotlib::
|
|
2274
|
+
|
|
2275
|
+
sage: l = line([(1e-19,-1), (-1e-19,+1)], aspect_ratio=1.0)
|
|
2276
|
+
sage: l.get_minmax_data()
|
|
2277
|
+
{'xmax': 1.00010000000000e-15,
|
|
2278
|
+
'xmin': -9.99900000000000e-16,
|
|
2279
|
+
'ymax': 1.0,
|
|
2280
|
+
'ymin': -1.0}
|
|
2281
|
+
sage: l = line([(0,0), (1,1)], aspect_ratio=1e19)
|
|
2282
|
+
sage: l.get_minmax_data()
|
|
2283
|
+
{'xmax': 5000.50000000000, 'xmin': -4999.50000000000,
|
|
2284
|
+
'ymax': 1.0, 'ymin': 0.0}
|
|
2285
|
+
"""
|
|
2286
|
+
objects = self._objects
|
|
2287
|
+
if objects:
|
|
2288
|
+
minmax_data = [o.get_minmax_data() for o in objects]
|
|
2289
|
+
xmin = min(d['xmin'] for d in minmax_data)
|
|
2290
|
+
xmax = max(d['xmax'] for d in minmax_data)
|
|
2291
|
+
ymin = min(d['ymin'] for d in minmax_data)
|
|
2292
|
+
ymax = max(d['ymax'] for d in minmax_data)
|
|
2293
|
+
if isnan(xmin):
|
|
2294
|
+
xmin = 0
|
|
2295
|
+
sage.misc.verbose.verbose("xmin was NaN (setting to 0)", level=0)
|
|
2296
|
+
if isnan(xmax):
|
|
2297
|
+
xmax = 0
|
|
2298
|
+
sage.misc.verbose.verbose("xmax was NaN (setting to 0)", level=0)
|
|
2299
|
+
if isnan(ymin):
|
|
2300
|
+
ymin = 0
|
|
2301
|
+
sage.misc.verbose.verbose("ymin was NaN (setting to 0)", level=0)
|
|
2302
|
+
if isnan(ymax):
|
|
2303
|
+
ymax = 0
|
|
2304
|
+
sage.misc.verbose.verbose("ymax was NaN (setting to 0)", level=0)
|
|
2305
|
+
else:
|
|
2306
|
+
xmin = xmax = ymin = ymax = 0
|
|
2307
|
+
|
|
2308
|
+
if xmin == xmax:
|
|
2309
|
+
xmin -= 1
|
|
2310
|
+
xmax += 1
|
|
2311
|
+
if ymin == ymax:
|
|
2312
|
+
ymin -= 1
|
|
2313
|
+
ymax += 1
|
|
2314
|
+
return self._limit_output_aspect_ratio(xmin, xmax, ymin, ymax)
|
|
2315
|
+
|
|
2316
|
+
def _limit_output_aspect_ratio(self, xmin, xmax, ymin, ymax):
|
|
2317
|
+
r"""
|
|
2318
|
+
Private helper function for :meth:`get_minmax_data`.
|
|
2319
|
+
|
|
2320
|
+
INPUT:
|
|
2321
|
+
|
|
2322
|
+
- ``xmin``, ``xmax``, ``ymin``, ``ymax`` -- bounding box for
|
|
2323
|
+
the graphics
|
|
2324
|
+
|
|
2325
|
+
OUTPUT:
|
|
2326
|
+
|
|
2327
|
+
A dictionary whose keys give the xmin, xmax, ymin, and ymax
|
|
2328
|
+
data for this graphic. Possibly enlarged in order to keep the
|
|
2329
|
+
width/height ratio (in output units, after factoring in the
|
|
2330
|
+
chosen aspect ratio) of the plot is limited to `10^{-15}\dots
|
|
2331
|
+
10^{15}` to avoid floating point issues in matplotlib.
|
|
2332
|
+
|
|
2333
|
+
EXAMPLES::
|
|
2334
|
+
|
|
2335
|
+
sage: l = line([(0,0), (1,1)], aspect_ratio=1.0)
|
|
2336
|
+
sage: l._limit_output_aspect_ratio(1, 2, 1e19, 3)
|
|
2337
|
+
{'xmax': -4999.50000000000,
|
|
2338
|
+
'xmin': 5000.50000000000,
|
|
2339
|
+
'ymax': 3,
|
|
2340
|
+
'ymin': 1.00000000000000e19}
|
|
2341
|
+
sage: l._limit_output_aspect_ratio(1, 2, 3, 1e19)
|
|
2342
|
+
{'xmax': 5000.50000000000,
|
|
2343
|
+
'xmin': -4999.50000000000,
|
|
2344
|
+
'ymax': 1.00000000000000e19,
|
|
2345
|
+
'ymin': 3}
|
|
2346
|
+
sage: l = line([(0,0), (1,1)], aspect_ratio=1e16)
|
|
2347
|
+
sage: l._limit_output_aspect_ratio(0, 1, 2, 3)
|
|
2348
|
+
{'xmax': 5.50000000000000, 'xmin': -4.50000000000000, 'ymax': 3, 'ymin': 2}
|
|
2349
|
+
"""
|
|
2350
|
+
aspect_ratio = self.aspect_ratio()
|
|
2351
|
+
if aspect_ratio != 'automatic':
|
|
2352
|
+
width = xmax - xmin
|
|
2353
|
+
height = ymax - ymin
|
|
2354
|
+
output_aspect = abs(width / height / aspect_ratio)
|
|
2355
|
+
if output_aspect > 1e15:
|
|
2356
|
+
height = 1e15 * width / aspect_ratio
|
|
2357
|
+
ycenter = (ymax - ymin) / 2
|
|
2358
|
+
ymin = ycenter - height / 2
|
|
2359
|
+
ymax = ycenter + height / 2
|
|
2360
|
+
if output_aspect < 1e-15:
|
|
2361
|
+
width = 1e-15 * height * aspect_ratio
|
|
2362
|
+
xcenter = (xmax - xmin) / 2
|
|
2363
|
+
xmin = xcenter - width / 2
|
|
2364
|
+
xmax = xcenter + width / 2
|
|
2365
|
+
return {'xmin': xmin, 'xmax': xmax, 'ymin': ymin, 'ymax': ymax}
|
|
2366
|
+
|
|
2367
|
+
def _matplotlib_tick_formatter(self, subplot, base=(10, 10),
|
|
2368
|
+
locator_options={}, scale=('linear', 'linear'),
|
|
2369
|
+
tick_formatter=(None, None), ticks=(None, None),
|
|
2370
|
+
xmax=None, xmin=None, ymax=None, ymin=None):
|
|
2371
|
+
r"""
|
|
2372
|
+
Take a matplotlib subplot instance representing the graphic and set
|
|
2373
|
+
the ticks formatting. This function is only for internal use.
|
|
2374
|
+
|
|
2375
|
+
INPUT:
|
|
2376
|
+
|
|
2377
|
+
- ``subplot`` -- the subplot instance
|
|
2378
|
+
|
|
2379
|
+
EXAMPLES::
|
|
2380
|
+
|
|
2381
|
+
sage: # needs sage.symbolic
|
|
2382
|
+
sage: from matplotlib.figure import Figure
|
|
2383
|
+
sage: p = plot(x); d = p.get_minmax_data()
|
|
2384
|
+
sage: subplot = Figure().add_subplot(111)
|
|
2385
|
+
sage: p._objects[0]._render_on_subplot(subplot)
|
|
2386
|
+
sage: p._matplotlib_tick_formatter(subplot, **d)
|
|
2387
|
+
(<Axes...>,
|
|
2388
|
+
<matplotlib.ticker.MaxNLocator object at ...>,
|
|
2389
|
+
<matplotlib.ticker.MaxNLocator object at ...>,
|
|
2390
|
+
<matplotlib.ticker.ScalarFormatter object at ...>,
|
|
2391
|
+
<matplotlib.ticker.ScalarFormatter object at ...>)
|
|
2392
|
+
"""
|
|
2393
|
+
# This function is created to refactor some code that is repeated
|
|
2394
|
+
# in the matplotlib function
|
|
2395
|
+
from matplotlib.ticker import (FixedLocator, Locator,
|
|
2396
|
+
LogFormatterMathtext,
|
|
2397
|
+
LogLocator, MaxNLocator,
|
|
2398
|
+
MultipleLocator,
|
|
2399
|
+
NullLocator, ScalarFormatter)
|
|
2400
|
+
|
|
2401
|
+
x_locator, y_locator = ticks
|
|
2402
|
+
# ---------------------- Location of x-ticks ---------------------
|
|
2403
|
+
|
|
2404
|
+
if x_locator is None:
|
|
2405
|
+
if scale[0] == 'log':
|
|
2406
|
+
x_locator = LogLocator(base=base[0])
|
|
2407
|
+
else:
|
|
2408
|
+
x_locator = MaxNLocator(**locator_options)
|
|
2409
|
+
elif isinstance(x_locator, Locator):
|
|
2410
|
+
pass
|
|
2411
|
+
elif x_locator == []:
|
|
2412
|
+
x_locator = NullLocator()
|
|
2413
|
+
elif isinstance(x_locator, list):
|
|
2414
|
+
x_locator = FixedLocator([float(x) for x in x_locator])
|
|
2415
|
+
else: # x_locator is a number which can be made a float
|
|
2416
|
+
from sage.functions.other import ceil, floor
|
|
2417
|
+
if floor(xmax / x_locator) - ceil(xmin / x_locator) > 1:
|
|
2418
|
+
x_locator = MultipleLocator(float(x_locator))
|
|
2419
|
+
else: # not enough room for two major ticks
|
|
2420
|
+
raise ValueError('Expand the range of the independent '
|
|
2421
|
+
'variable to allow two multiples of your tick locator '
|
|
2422
|
+
'(option `ticks`).')
|
|
2423
|
+
|
|
2424
|
+
# ---------------------- Location of y-ticks ---------------------
|
|
2425
|
+
if y_locator is None:
|
|
2426
|
+
if scale[1] == 'log':
|
|
2427
|
+
y_locator = LogLocator(base=base[1])
|
|
2428
|
+
else:
|
|
2429
|
+
y_locator = MaxNLocator(**locator_options)
|
|
2430
|
+
elif isinstance(y_locator, Locator):
|
|
2431
|
+
pass
|
|
2432
|
+
elif y_locator == []:
|
|
2433
|
+
y_locator = NullLocator()
|
|
2434
|
+
elif isinstance(y_locator, list):
|
|
2435
|
+
y_locator = FixedLocator([float(y) for y in y_locator])
|
|
2436
|
+
else: # y_locator is a number which can be made a float
|
|
2437
|
+
from sage.functions.other import ceil, floor
|
|
2438
|
+
if floor(ymax / y_locator) - ceil(ymin / y_locator) > 1:
|
|
2439
|
+
y_locator = MultipleLocator(float(y_locator))
|
|
2440
|
+
else: # not enough room for two major ticks
|
|
2441
|
+
raise ValueError('Expand the range of the dependent '
|
|
2442
|
+
'variable to allow two multiples of your tick locator '
|
|
2443
|
+
'(option `ticks`).')
|
|
2444
|
+
|
|
2445
|
+
x_formatter, y_formatter = tick_formatter
|
|
2446
|
+
from matplotlib.ticker import FuncFormatter, FixedFormatter
|
|
2447
|
+
from sage.misc.latex import latex
|
|
2448
|
+
from sage.structure.element import Expression
|
|
2449
|
+
from .misc import _multiple_of_constant
|
|
2450
|
+
# ---------------------- Formatting x-ticks ----------------------
|
|
2451
|
+
if x_formatter is None:
|
|
2452
|
+
if scale[0] == 'log':
|
|
2453
|
+
x_formatter = LogFormatterMathtext(base=base[0])
|
|
2454
|
+
else:
|
|
2455
|
+
x_formatter = ScalarFormatter()
|
|
2456
|
+
elif isinstance(x_formatter, Expression):
|
|
2457
|
+
x_const = x_formatter
|
|
2458
|
+
x_formatter = FuncFormatter(lambda n, pos:
|
|
2459
|
+
_multiple_of_constant(n, pos, x_const))
|
|
2460
|
+
elif x_formatter == "latex":
|
|
2461
|
+
if scale[0] == 'log':
|
|
2462
|
+
# We need to strip out '\\mathdefault' from the string
|
|
2463
|
+
x_formatter = FuncFormatter(lambda n, pos:
|
|
2464
|
+
LogFormatterMathtext(base=base[0])(n, pos).replace(
|
|
2465
|
+
"\\mathdefault", ""))
|
|
2466
|
+
else:
|
|
2467
|
+
# circumvent the problem of symbolic tick values (trac #34693)
|
|
2468
|
+
if isinstance(x_locator, FixedLocator):
|
|
2469
|
+
x_formatter = FixedFormatter(['$%s$' % latex(n) for n in ticks[0]])
|
|
2470
|
+
else:
|
|
2471
|
+
x_formatter = FuncFormatter(lambda n, pos: '$%s$' % latex(n))
|
|
2472
|
+
elif isinstance(x_formatter, (list, tuple)):
|
|
2473
|
+
if (not isinstance(ticks[0], (list, tuple)) or
|
|
2474
|
+
len(ticks[0]) != len(x_formatter)):
|
|
2475
|
+
raise ValueError("If the first component of the list "
|
|
2476
|
+
"`tick_formatter` is a list then the first component "
|
|
2477
|
+
"of `ticks` must also be a list of equal length.")
|
|
2478
|
+
x_formatter = FixedFormatter(x_formatter)
|
|
2479
|
+
# ---------------------- Formatting y-ticks ----------------------
|
|
2480
|
+
if y_formatter is None:
|
|
2481
|
+
if scale[1] == 'log':
|
|
2482
|
+
y_formatter = LogFormatterMathtext(base=base[1])
|
|
2483
|
+
else:
|
|
2484
|
+
y_formatter = ScalarFormatter()
|
|
2485
|
+
elif isinstance(y_formatter, Expression):
|
|
2486
|
+
y_const = y_formatter
|
|
2487
|
+
y_formatter = FuncFormatter(lambda n, pos:
|
|
2488
|
+
_multiple_of_constant(n, pos, y_const))
|
|
2489
|
+
elif y_formatter == "latex":
|
|
2490
|
+
if scale[1] == 'log':
|
|
2491
|
+
# We need to strip out '\\mathdefault' from the string
|
|
2492
|
+
y_formatter = FuncFormatter(lambda n, pos:
|
|
2493
|
+
LogFormatterMathtext(base=base[1])(n, pos).replace(
|
|
2494
|
+
"\\mathdefault", ""))
|
|
2495
|
+
else:
|
|
2496
|
+
# circumvent the problem of symbolic tick values (trac #34693)
|
|
2497
|
+
if isinstance(y_locator, FixedLocator):
|
|
2498
|
+
y_formatter = FixedFormatter(['$%s$' % latex(n) for n in ticks[1]])
|
|
2499
|
+
else:
|
|
2500
|
+
y_formatter = FuncFormatter(lambda n, pos: '$%s$' % latex(n))
|
|
2501
|
+
elif isinstance(y_formatter, (list, tuple)):
|
|
2502
|
+
if (not isinstance(ticks[1], (list, tuple)) or
|
|
2503
|
+
len(ticks[1]) != len(y_formatter)):
|
|
2504
|
+
raise ValueError("If the second component of the list "
|
|
2505
|
+
"`tick_formatter` is a list then the second component "
|
|
2506
|
+
"of `ticks` must also be a list of equal length.")
|
|
2507
|
+
y_formatter = FixedFormatter(y_formatter)
|
|
2508
|
+
|
|
2509
|
+
subplot.xaxis.set_major_locator(x_locator)
|
|
2510
|
+
subplot.yaxis.set_major_locator(y_locator)
|
|
2511
|
+
subplot.xaxis.set_major_formatter(x_formatter)
|
|
2512
|
+
subplot.yaxis.set_major_formatter(y_formatter)
|
|
2513
|
+
|
|
2514
|
+
# Check for whether there will be too few ticks in the log scale case.
|
|
2515
|
+
# If there are not enough ticks (2 or more) to determine that the scale
|
|
2516
|
+
# is non-linear, we throw a warning.
|
|
2517
|
+
from warnings import warn
|
|
2518
|
+
tickwarnmsg = 'The %s-axis contains fewer than 2 ticks; '
|
|
2519
|
+
tickwarnmsg += 'the logarithmic scale of the plot may not be apparent '
|
|
2520
|
+
tickwarnmsg += 'to the reader.'
|
|
2521
|
+
|
|
2522
|
+
if (scale[0] == 'log' and not isinstance(x_locator, NullLocator) and
|
|
2523
|
+
len(subplot.xaxis.get_ticklocs()) < 2):
|
|
2524
|
+
warn(tickwarnmsg % 'x')
|
|
2525
|
+
|
|
2526
|
+
if (scale[1] == 'log' and not isinstance(y_locator, NullLocator) and
|
|
2527
|
+
len(subplot.yaxis.get_ticklocs()) < 2):
|
|
2528
|
+
warn(tickwarnmsg % 'y')
|
|
2529
|
+
|
|
2530
|
+
return (subplot, x_locator, y_locator, x_formatter, y_formatter)
|
|
2531
|
+
|
|
2532
|
+
def _get_vmin_vmax(self, vmin, vmax, basev, axes_pad):
|
|
2533
|
+
r"""
|
|
2534
|
+
Determine the min/max value for a variable plotted on a logarithmic
|
|
2535
|
+
scale. The motivation is that we desire at least two ticks for a log
|
|
2536
|
+
plot; otherwise the reader may assume that the scale is linear. For
|
|
2537
|
+
internal use only.
|
|
2538
|
+
|
|
2539
|
+
We check if this case occurs (for e.g. assuming xmin < xmax)::
|
|
2540
|
+
|
|
2541
|
+
floor(logxmin) ceil(logxmax)
|
|
2542
|
+
----|---------+----------+----------|----------------------|--
|
|
2543
|
+
logxmin logxmax
|
|
2544
|
+
|
|
2545
|
+
Or if this case occurs (assuming xmin < xmax)::
|
|
2546
|
+
|
|
2547
|
+
floor(logxmin) floor(logxmax) ceil(logxmax)
|
|
2548
|
+
----|---------+---------------------|-----+----------------|--
|
|
2549
|
+
logxmin logxmax
|
|
2550
|
+
|
|
2551
|
+
|
|
2552
|
+
INPUT:
|
|
2553
|
+
|
|
2554
|
+
- ``vmin`` -- the current min for this variable (e.g. xmin or ymin)
|
|
2555
|
+
|
|
2556
|
+
- ``vmax`` -- the current max for this variable (e.g. xmax or ymax)
|
|
2557
|
+
|
|
2558
|
+
- ``basev`` -- the base of the logarithmic scale for this variable
|
|
2559
|
+
|
|
2560
|
+
- ``axes_pad`` -- the padding for the axis. It determines the
|
|
2561
|
+
exponent of the fraction of the minimum (resp. maximum) that is
|
|
2562
|
+
subtracted from the minimum (resp. added to the maximum) value of
|
|
2563
|
+
the axis. For instance if the minimum is `m` and the base of the
|
|
2564
|
+
axis is `b` then the new minimum after padding the axis will be
|
|
2565
|
+
`m - m/b^{\mathrm{axes\_pad}}`.
|
|
2566
|
+
|
|
2567
|
+
OUTPUT:
|
|
2568
|
+
|
|
2569
|
+
A new (min,max) pair for this variable, suitable for its logarithmic
|
|
2570
|
+
scale.
|
|
2571
|
+
|
|
2572
|
+
EXAMPLES:
|
|
2573
|
+
|
|
2574
|
+
On a base-10 logarithmic scale, we should have ``vmin``/``vmax``
|
|
2575
|
+
at least 10 units apart::
|
|
2576
|
+
|
|
2577
|
+
sage: p = Graphics()
|
|
2578
|
+
sage: p._get_vmin_vmax(1, 2, 10, None) == (9/10, 10)
|
|
2579
|
+
True
|
|
2580
|
+
sage: p._get_vmin_vmax(1, 5, 10, None) == (9/10, 10)
|
|
2581
|
+
True
|
|
2582
|
+
sage: p._get_vmin_vmax(1, 10, 10, None)
|
|
2583
|
+
(9/10, 11)
|
|
2584
|
+
sage: p._get_vmin_vmax(1, 11, 10, None)
|
|
2585
|
+
(9/10, 121/10)
|
|
2586
|
+
sage: p._get_vmin_vmax(1, 50, 10, None)
|
|
2587
|
+
(9/10, 55)
|
|
2588
|
+
|
|
2589
|
+
We can set the ``axes_pad`` separately::
|
|
2590
|
+
|
|
2591
|
+
sage: p._get_vmin_vmax(1, 50, 2, 2)
|
|
2592
|
+
(0.75, 62.5)
|
|
2593
|
+
|
|
2594
|
+
Nonpositive values of ``vmin`` are not accepted due to the domain
|
|
2595
|
+
of the logarithm function::
|
|
2596
|
+
|
|
2597
|
+
sage: p = Graphics()
|
|
2598
|
+
sage: p._get_vmin_vmax(-1,2,10, None)
|
|
2599
|
+
Traceback (most recent call last):
|
|
2600
|
+
...
|
|
2601
|
+
ValueError: vmin must be positive
|
|
2602
|
+
|
|
2603
|
+
And ``vmax`` must be greater than ``vmin``::
|
|
2604
|
+
|
|
2605
|
+
sage: p._get_vmin_vmax(1,-2,10, None)
|
|
2606
|
+
Traceback (most recent call last):
|
|
2607
|
+
...
|
|
2608
|
+
ValueError: vmin must be less than vmax
|
|
2609
|
+
"""
|
|
2610
|
+
if vmin <= 0:
|
|
2611
|
+
raise ValueError('vmin must be positive')
|
|
2612
|
+
|
|
2613
|
+
if vmin >= vmax:
|
|
2614
|
+
raise ValueError('vmin must be less than vmax')
|
|
2615
|
+
|
|
2616
|
+
import math
|
|
2617
|
+
if axes_pad is None:
|
|
2618
|
+
axes_pad = 1
|
|
2619
|
+
else:
|
|
2620
|
+
axes_pad = float(abs(axes_pad))
|
|
2621
|
+
|
|
2622
|
+
logvmin = math.log(vmin) / math.log(basev)
|
|
2623
|
+
logvmax = math.log(vmax) / math.log(basev)
|
|
2624
|
+
|
|
2625
|
+
if math.floor(logvmax) - math.ceil(logvmin) < 0:
|
|
2626
|
+
vmax = basev**math.ceil(logvmax)
|
|
2627
|
+
vmin = basev**math.floor(logvmin)
|
|
2628
|
+
elif math.floor(logvmax) - math.ceil(logvmin) < 1:
|
|
2629
|
+
if logvmax - math.floor(logvmax) > math.ceil(logvmin) - logvmin:
|
|
2630
|
+
vmax = basev**math.ceil(logvmax)
|
|
2631
|
+
if axes_pad > 0:
|
|
2632
|
+
vmin -= vmin * basev**(-axes_pad)
|
|
2633
|
+
else:
|
|
2634
|
+
vmin = basev**math.floor(logvmin)
|
|
2635
|
+
if axes_pad > 0:
|
|
2636
|
+
vmax += vmax * basev**(-axes_pad)
|
|
2637
|
+
elif axes_pad > 0:
|
|
2638
|
+
# pad the axes if we haven't expanded the axes earlier.
|
|
2639
|
+
vmin -= vmin * basev**(-axes_pad)
|
|
2640
|
+
vmax += vmax * basev**(-axes_pad)
|
|
2641
|
+
|
|
2642
|
+
return vmin, vmax
|
|
2643
|
+
|
|
2644
|
+
def matplotlib(self, filename=None,
|
|
2645
|
+
xmin=None, xmax=None, ymin=None, ymax=None,
|
|
2646
|
+
figsize=None, figure=None, sub=None,
|
|
2647
|
+
axes=None, axes_labels=None, axes_labels_size=None,
|
|
2648
|
+
flip_x=False, flip_y=False,
|
|
2649
|
+
fontsize=None, frame=False, verify=True,
|
|
2650
|
+
aspect_ratio=None,
|
|
2651
|
+
gridlines=None, gridlinesstyle=None,
|
|
2652
|
+
vgridlinesstyle=None, hgridlinesstyle=None,
|
|
2653
|
+
show_legend=None, legend_options=None,
|
|
2654
|
+
axes_pad=None, ticks_integer=None,
|
|
2655
|
+
tick_formatter=None, ticks=None, title=None,
|
|
2656
|
+
title_pos=None, base=None, scale=None,
|
|
2657
|
+
stylesheet=None,
|
|
2658
|
+
typeset='default'):
|
|
2659
|
+
r"""
|
|
2660
|
+
Construct or modify a Matplotlib figure by drawing ``self`` on it.
|
|
2661
|
+
|
|
2662
|
+
INPUT (partial description, involving only Matplotlib objects; see
|
|
2663
|
+
:meth:`show` for the other arguments):
|
|
2664
|
+
|
|
2665
|
+
- ``figure`` -- (default: ``None``) Matplotlib figure (class
|
|
2666
|
+
``matplotlib.figure.Figure``) on which ``self`` is to be displayed;
|
|
2667
|
+
if ``None``, the figure will be created from the parameter
|
|
2668
|
+
``figsize``
|
|
2669
|
+
|
|
2670
|
+
- ``figsize`` -- (default: ``None``) width or [width, height] in inches
|
|
2671
|
+
of the Matplotlib figure in case ``figure`` is ``None``; if
|
|
2672
|
+
``figsize`` is ``None``, Matplotlib's default (6.4 x 4.8 inches) is
|
|
2673
|
+
used
|
|
2674
|
+
|
|
2675
|
+
- ``sub`` -- (default: ``None``) subpart of the figure, as an
|
|
2676
|
+
instance of Matplotlib "axes" (class ``matplotlib.axes.Axes``) on
|
|
2677
|
+
which ``self`` is to be drawn; if ``None``, the subpart will be
|
|
2678
|
+
created so as to cover the whole figure
|
|
2679
|
+
|
|
2680
|
+
OUTPUT:
|
|
2681
|
+
|
|
2682
|
+
- a ``matplotlib.figure.Figure`` object; if the argument ``figure`` is
|
|
2683
|
+
provided, this is the same object as ``figure``.
|
|
2684
|
+
|
|
2685
|
+
EXAMPLES::
|
|
2686
|
+
|
|
2687
|
+
sage: c = circle((1,1),1)
|
|
2688
|
+
sage: print(c.matplotlib())
|
|
2689
|
+
Figure(640x480)
|
|
2690
|
+
|
|
2691
|
+
To obtain the first Matplotlib ``Axes`` object inside of the
|
|
2692
|
+
figure, you can do something like the following.
|
|
2693
|
+
|
|
2694
|
+
::
|
|
2695
|
+
|
|
2696
|
+
sage: p = plot(sin(x), (x, -2*pi, 2*pi)) # needs sage.symbolic
|
|
2697
|
+
sage: figure = p.matplotlib() # needs sage.symbolic
|
|
2698
|
+
sage: axes = figure.axes[0] # needs sage.symbolic
|
|
2699
|
+
|
|
2700
|
+
TESTS:
|
|
2701
|
+
|
|
2702
|
+
We verify that :issue:`10291` is fixed::
|
|
2703
|
+
|
|
2704
|
+
sage: # needs sage.symbolic
|
|
2705
|
+
sage: p = plot(sin(x), (x, -2*pi, 2*pi))
|
|
2706
|
+
sage: figure = p.matplotlib()
|
|
2707
|
+
sage: axes_range = p.get_axes_range()
|
|
2708
|
+
sage: figure = p.matplotlib()
|
|
2709
|
+
sage: axes_range2 = p.get_axes_range()
|
|
2710
|
+
sage: axes_range == axes_range2
|
|
2711
|
+
True
|
|
2712
|
+
|
|
2713
|
+
We verify that legend options are properly handled (:issue:`12960`).
|
|
2714
|
+
First, we test with no options, and next with an incomplete set of
|
|
2715
|
+
options.::
|
|
2716
|
+
|
|
2717
|
+
sage: # needs sage.symbolic
|
|
2718
|
+
sage: p = plot(x, legend_label='aha')
|
|
2719
|
+
sage: p.legend(True)
|
|
2720
|
+
sage: pm = p.matplotlib()
|
|
2721
|
+
sage: pm = p.matplotlib(legend_options={'font_size': 'small'})
|
|
2722
|
+
|
|
2723
|
+
The title should not overlap with the axes labels nor the frame in
|
|
2724
|
+
the following plot (see :issue:`10512`)::
|
|
2725
|
+
|
|
2726
|
+
sage: plot(sin(x^2), (x, -3, 3), title='Plot of sin(x^2)', # needs sage.symbolic
|
|
2727
|
+
....: axes_labels=['x','y'], frame=True)
|
|
2728
|
+
Graphics object consisting of 1 graphics primitive
|
|
2729
|
+
|
|
2730
|
+
``typeset`` must not be set to an arbitrary string::
|
|
2731
|
+
|
|
2732
|
+
sage: plot(x, typeset='garbage') # needs sage.symbolic
|
|
2733
|
+
doctest:...: ...RichReprWarning: Exception in _rich_repr_ while
|
|
2734
|
+
displaying object: typeset must be set to one of 'default',
|
|
2735
|
+
'latex', or 'type1'; got 'garbage'.
|
|
2736
|
+
Graphics object consisting of 1 graphics primitive
|
|
2737
|
+
|
|
2738
|
+
We verify that numerical options are changed to float before saving (:issue:`14741`).
|
|
2739
|
+
By default, Sage 5.10 changes float objects to the `RealLiteral` type.
|
|
2740
|
+
The patch changes them to float before creating `matplotlib` objects.::
|
|
2741
|
+
|
|
2742
|
+
sage: # long time, needs sage.symbolic
|
|
2743
|
+
sage: f = lambda x, y: abs(cos((x + I * y) ** 4)) - 1
|
|
2744
|
+
sage: g = implicit_plot(f, (-4, 4), (-3, 3), linewidth=0.6)
|
|
2745
|
+
sage: gm = g.matplotlib()
|
|
2746
|
+
|
|
2747
|
+
If the axes are flipped, the limits of the axes get swapped::
|
|
2748
|
+
|
|
2749
|
+
sage: # needs sage.symbolic
|
|
2750
|
+
sage: p = plot(2*x, 1, 2)
|
|
2751
|
+
sage: sub, = p.matplotlib(flip_y=True, flip_x=True).axes
|
|
2752
|
+
sage: xmin, xmax = sub.get_xlim()
|
|
2753
|
+
sage: ymin, ymax = sub.get_ylim()
|
|
2754
|
+
sage: xmin > xmax, ymin > ymax
|
|
2755
|
+
(...True..., ...True...)
|
|
2756
|
+
"""
|
|
2757
|
+
if not isinstance(ticks, (list, tuple)):
|
|
2758
|
+
ticks = (ticks, None)
|
|
2759
|
+
if legend_options is None:
|
|
2760
|
+
legend_options = {}
|
|
2761
|
+
# as discussed in trac #25799 and #23696, Sage prefers the computer
|
|
2762
|
+
# modern fonts of TeX for math texts such as axes labels, but otherwise
|
|
2763
|
+
# adopts the default style of matplotlib
|
|
2764
|
+
from matplotlib import rcParams
|
|
2765
|
+
rcParams['mathtext.fontset'] = 'cm'
|
|
2766
|
+
rcParams['mathtext.rm'] = 'serif'
|
|
2767
|
+
|
|
2768
|
+
import matplotlib.pyplot as plt
|
|
2769
|
+
if stylesheet in plt.style.available:
|
|
2770
|
+
plt.style.use(stylesheet)
|
|
2771
|
+
|
|
2772
|
+
from sage.structure.element import Expression
|
|
2773
|
+
# make sure both formatters typeset or both don't
|
|
2774
|
+
if not isinstance(tick_formatter, (list, tuple)):
|
|
2775
|
+
if tick_formatter == "latex" or isinstance(tick_formatter, Expression):
|
|
2776
|
+
tick_formatter = (tick_formatter, "latex")
|
|
2777
|
+
else:
|
|
2778
|
+
tick_formatter = (tick_formatter, None)
|
|
2779
|
+
|
|
2780
|
+
global do_verify
|
|
2781
|
+
do_verify = verify
|
|
2782
|
+
|
|
2783
|
+
if axes is None:
|
|
2784
|
+
axes = self._show_axes
|
|
2785
|
+
|
|
2786
|
+
from matplotlib.figure import Figure
|
|
2787
|
+
if typeset == 'type1': # Requires LaTeX, dvipng, gs to be installed.
|
|
2788
|
+
rcParams['ps.useafm'] = True
|
|
2789
|
+
rcParams['pdf.use14corefonts'] = True
|
|
2790
|
+
rcParams['text.usetex'] = True
|
|
2791
|
+
elif typeset == 'latex': # Requires LaTeX, dvipng, gs to be installed.
|
|
2792
|
+
rcParams['ps.useafm'] = False
|
|
2793
|
+
rcParams['pdf.use14corefonts'] = False
|
|
2794
|
+
rcParams['text.usetex'] = True
|
|
2795
|
+
elif typeset != 'default': # We won't change (maybe user-set) defaults
|
|
2796
|
+
raise ValueError("typeset must be set to one of 'default', 'latex',"
|
|
2797
|
+
f" or 'type1'; got '{typeset}'.")
|
|
2798
|
+
|
|
2799
|
+
self.fontsize(fontsize)
|
|
2800
|
+
self.axes_labels(l=axes_labels)
|
|
2801
|
+
self.axes_labels_size(s=axes_labels_size)
|
|
2802
|
+
|
|
2803
|
+
# If no matplotlib figure is provided, it is created here:
|
|
2804
|
+
if figure is None:
|
|
2805
|
+
if figsize is not None:
|
|
2806
|
+
figsize = _parse_figsize(figsize)
|
|
2807
|
+
figure = Figure(figsize=figsize)
|
|
2808
|
+
|
|
2809
|
+
# The incoming subplot instance
|
|
2810
|
+
subplot = sub
|
|
2811
|
+
if not subplot:
|
|
2812
|
+
subplot = figure.add_subplot(111)
|
|
2813
|
+
# Add all the primitives to the subplot
|
|
2814
|
+
old_opts = {}
|
|
2815
|
+
for g in self._objects:
|
|
2816
|
+
opts, old_opts[g] = g.options(), g.options()
|
|
2817
|
+
for k, v in opts.items():
|
|
2818
|
+
try:
|
|
2819
|
+
if v.parent() in sage.categories.fields.Fields():
|
|
2820
|
+
opts[k] = float(v)
|
|
2821
|
+
except (AttributeError, TypeError):
|
|
2822
|
+
pass
|
|
2823
|
+
g.set_options(opts)
|
|
2824
|
+
g._render_on_subplot(subplot)
|
|
2825
|
+
if hasattr(g, '_bbox_extra_artists'):
|
|
2826
|
+
self._bbox_extra_artists.extend(g._bbox_extra_artists)
|
|
2827
|
+
# Set the aspect ratio
|
|
2828
|
+
if aspect_ratio is None:
|
|
2829
|
+
aspect_ratio = self.aspect_ratio()
|
|
2830
|
+
if aspect_ratio == 'automatic':
|
|
2831
|
+
subplot.set_aspect('auto', adjustable='box')
|
|
2832
|
+
else:
|
|
2833
|
+
subplot.set_aspect(aspect_ratio, adjustable='box')
|
|
2834
|
+
|
|
2835
|
+
# ---------------- Set the axes limits and scale ------------------
|
|
2836
|
+
self.set_axes_range(xmin, xmax, ymin, ymax)
|
|
2837
|
+
d = self.get_axes_range()
|
|
2838
|
+
xmin = d['xmax' if flip_x else 'xmin']
|
|
2839
|
+
xmax = d['xmin' if flip_x else 'xmax']
|
|
2840
|
+
ymin = d['ymax' if flip_y else 'ymin']
|
|
2841
|
+
ymax = d['ymin' if flip_y else 'ymax']
|
|
2842
|
+
|
|
2843
|
+
xscale, yscale, basex, basey = self._set_scale(subplot, scale=scale,
|
|
2844
|
+
base=base)
|
|
2845
|
+
|
|
2846
|
+
# If any of the x-data are negative, we leave the min/max alone.
|
|
2847
|
+
if xscale == 'log' and min(xmin, xmax) > 0:
|
|
2848
|
+
if xmin < xmax:
|
|
2849
|
+
xmin, xmax = self._get_vmin_vmax(xmin, xmax, basex, axes_pad)
|
|
2850
|
+
else:
|
|
2851
|
+
xmax, xmin = self._get_vmin_vmax(xmax, xmin, basex, axes_pad)
|
|
2852
|
+
else:
|
|
2853
|
+
xpad = 0.02 if axes_pad is None else axes_pad
|
|
2854
|
+
xpad = (xmax - xmin) * float(xpad)
|
|
2855
|
+
xmax += xpad
|
|
2856
|
+
xmin -= xpad
|
|
2857
|
+
|
|
2858
|
+
# Likewise for the y-data.
|
|
2859
|
+
if yscale == 'log' and min(ymin, ymax) > 0:
|
|
2860
|
+
if ymin < ymax:
|
|
2861
|
+
ymin, ymax = self._get_vmin_vmax(ymin, ymax, basey, axes_pad)
|
|
2862
|
+
else:
|
|
2863
|
+
ymax, ymin = self._get_vmin_vmax(ymax, ymin, basey, axes_pad)
|
|
2864
|
+
else:
|
|
2865
|
+
ypad = 0.02 if axes_pad is None else axes_pad
|
|
2866
|
+
ypad = (ymax - ymin) * float(ypad)
|
|
2867
|
+
ymax += ypad
|
|
2868
|
+
ymin -= ypad
|
|
2869
|
+
|
|
2870
|
+
# -------------------------- Set the legend -----------------------
|
|
2871
|
+
if show_legend is None:
|
|
2872
|
+
show_legend = self._show_legend
|
|
2873
|
+
|
|
2874
|
+
if show_legend:
|
|
2875
|
+
from matplotlib.font_manager import FontProperties
|
|
2876
|
+
lopts = {}
|
|
2877
|
+
lopts.update(legend_options)
|
|
2878
|
+
lopts.update(self._legend_opts)
|
|
2879
|
+
prop = FontProperties(
|
|
2880
|
+
family=lopts.pop('font_family', 'sans-serif'),
|
|
2881
|
+
size=lopts.pop('font_size', 'medium'),
|
|
2882
|
+
style=lopts.pop('font_style', 'normal'),
|
|
2883
|
+
weight=lopts.pop('font_weight', 'medium'),
|
|
2884
|
+
variant=lopts.pop('font_variant', 'normal'))
|
|
2885
|
+
color = lopts.pop('back_color', 'white')
|
|
2886
|
+
if 'loc' in lopts:
|
|
2887
|
+
loc = lopts['loc']
|
|
2888
|
+
if isinstance(loc, Integral):
|
|
2889
|
+
# matplotlib 3.8 doesn't support sage integers
|
|
2890
|
+
lopts['loc'] = int(loc)
|
|
2891
|
+
leg = subplot.legend(prop=prop, **lopts)
|
|
2892
|
+
if leg is None:
|
|
2893
|
+
from warnings import warn
|
|
2894
|
+
warn("legend requested but no items are labeled")
|
|
2895
|
+
else:
|
|
2896
|
+
# color
|
|
2897
|
+
lframe = leg.get_frame()
|
|
2898
|
+
lframe.set_facecolor(color)
|
|
2899
|
+
from sage.plot.colors import to_mpl_color
|
|
2900
|
+
for txt, color in zip(leg.get_texts(), self._legend_colors):
|
|
2901
|
+
if color is not None:
|
|
2902
|
+
txt.set_color(to_mpl_color(color))
|
|
2903
|
+
|
|
2904
|
+
subplot.set_xlim([xmin, xmax])
|
|
2905
|
+
subplot.set_ylim([ymin, ymax])
|
|
2906
|
+
|
|
2907
|
+
locator_options = {'nbins': 9, 'steps': [1, 2, 5, 10],
|
|
2908
|
+
'integer': ticks_integer}
|
|
2909
|
+
|
|
2910
|
+
if axes is None:
|
|
2911
|
+
axes = self._show_axes
|
|
2912
|
+
|
|
2913
|
+
for spine in subplot.spines.values():
|
|
2914
|
+
spine.set_color(self._axes_color)
|
|
2915
|
+
spine.set_linewidth(self._axes_width)
|
|
2916
|
+
|
|
2917
|
+
if frame:
|
|
2918
|
+
# For now, set the formatter to the old one, since that is
|
|
2919
|
+
# sort of what we are used to. We should eventually look at
|
|
2920
|
+
# the default one to see if we like it better.
|
|
2921
|
+
|
|
2922
|
+
(subplot, x_locator, y_locator,
|
|
2923
|
+
x_formatter, y_formatter) = self._matplotlib_tick_formatter(
|
|
2924
|
+
subplot, base=(basex, basey),
|
|
2925
|
+
locator_options=locator_options,
|
|
2926
|
+
scale=(xscale, yscale),
|
|
2927
|
+
tick_formatter=tick_formatter, ticks=ticks,
|
|
2928
|
+
xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin)
|
|
2929
|
+
|
|
2930
|
+
subplot.set_frame_on(True)
|
|
2931
|
+
if axes and xscale == 'linear' and yscale == 'linear':
|
|
2932
|
+
if (ymin <= 0 and ymax >= 0) or (ymax <= 0 and ymin >= 0):
|
|
2933
|
+
subplot.axhline(color=self._axes_color,
|
|
2934
|
+
linewidth=self._axes_width)
|
|
2935
|
+
if (xmin <= 0 and xmax >= 0) or (xmax <= 0 and xmin >= 0):
|
|
2936
|
+
subplot.axvline(color=self._axes_color,
|
|
2937
|
+
linewidth=self._axes_width)
|
|
2938
|
+
|
|
2939
|
+
elif axes:
|
|
2940
|
+
ymiddle = False
|
|
2941
|
+
xmiddle = False
|
|
2942
|
+
# Note that the user may specify a custom xmin and xmax which
|
|
2943
|
+
# flips the axis horizontally. Hence we need to check for both
|
|
2944
|
+
# the possibilities in the if statements below. Similar
|
|
2945
|
+
# comments hold for ymin and ymax.
|
|
2946
|
+
if xscale == 'log':
|
|
2947
|
+
if xmax > xmin:
|
|
2948
|
+
subplot.spines['right'].set_visible(False)
|
|
2949
|
+
subplot.spines['left'].set_position(('outward', 10))
|
|
2950
|
+
subplot.yaxis.set_ticks_position('left')
|
|
2951
|
+
subplot.yaxis.set_label_position('left')
|
|
2952
|
+
yaxis = 'left'
|
|
2953
|
+
elif xmax < xmin:
|
|
2954
|
+
subplot.spines['left'].set_visible(False)
|
|
2955
|
+
subplot.spines['right'].set_position(('outward', 10))
|
|
2956
|
+
subplot.yaxis.set_ticks_position('right')
|
|
2957
|
+
subplot.yaxis.set_label_position('right')
|
|
2958
|
+
yaxis = 'right'
|
|
2959
|
+
elif (xmin > 0 and xmax > xmin) or (xmax > 0 and xmin > xmax):
|
|
2960
|
+
subplot.spines['right'].set_visible(False)
|
|
2961
|
+
subplot.spines['left'].set_position(('outward', 10))
|
|
2962
|
+
subplot.yaxis.set_ticks_position('left')
|
|
2963
|
+
subplot.yaxis.set_label_position('left')
|
|
2964
|
+
yaxis = 'left'
|
|
2965
|
+
elif (xmax < 0 and xmax > xmin) or (xmin < 0 and xmin > xmax):
|
|
2966
|
+
subplot.spines['left'].set_visible(False)
|
|
2967
|
+
subplot.spines['right'].set_position(('outward', 10))
|
|
2968
|
+
subplot.yaxis.set_ticks_position('right')
|
|
2969
|
+
subplot.yaxis.set_label_position('right')
|
|
2970
|
+
yaxis = 'right'
|
|
2971
|
+
else:
|
|
2972
|
+
subplot.spines['left'].set_position('zero')
|
|
2973
|
+
subplot.yaxis.set_ticks_position('left')
|
|
2974
|
+
subplot.yaxis.set_label_position('left')
|
|
2975
|
+
subplot.spines['right'].set_visible(False)
|
|
2976
|
+
ymiddle = True
|
|
2977
|
+
yaxis = 'left'
|
|
2978
|
+
|
|
2979
|
+
if yscale == 'log':
|
|
2980
|
+
if ymax > ymin:
|
|
2981
|
+
subplot.spines['top'].set_visible(False)
|
|
2982
|
+
subplot.spines['bottom'].set_position(('outward', 10))
|
|
2983
|
+
subplot.xaxis.set_ticks_position('bottom')
|
|
2984
|
+
subplot.xaxis.set_label_position('bottom')
|
|
2985
|
+
xaxis = 'bottom'
|
|
2986
|
+
elif ymax < ymin:
|
|
2987
|
+
subplot.spines['bottom'].set_visible(False)
|
|
2988
|
+
subplot.spines['top'].set_position(('outward', 10))
|
|
2989
|
+
subplot.xaxis.set_ticks_position('top')
|
|
2990
|
+
subplot.xaxis.set_label_position('top')
|
|
2991
|
+
xaxis = 'top'
|
|
2992
|
+
elif (ymin > 0 and ymax > ymin) or (ymax > 0 and ymin > ymax):
|
|
2993
|
+
subplot.spines['top'].set_visible(False)
|
|
2994
|
+
subplot.spines['bottom'].set_position(('outward', 10))
|
|
2995
|
+
subplot.xaxis.set_ticks_position('bottom')
|
|
2996
|
+
subplot.xaxis.set_label_position('bottom')
|
|
2997
|
+
xaxis = 'bottom'
|
|
2998
|
+
elif (ymax < 0 and ymax > ymin) or (ymin < 0 and ymin > ymax):
|
|
2999
|
+
subplot.spines['bottom'].set_visible(False)
|
|
3000
|
+
subplot.spines['top'].set_position(('outward', 10))
|
|
3001
|
+
subplot.xaxis.set_ticks_position('top')
|
|
3002
|
+
subplot.xaxis.set_label_position('top')
|
|
3003
|
+
xaxis = 'top'
|
|
3004
|
+
else:
|
|
3005
|
+
subplot.spines['bottom'].set_position('zero')
|
|
3006
|
+
subplot.xaxis.set_ticks_position('bottom')
|
|
3007
|
+
subplot.xaxis.set_label_position('bottom')
|
|
3008
|
+
subplot.spines['top'].set_visible(False)
|
|
3009
|
+
xmiddle = True
|
|
3010
|
+
xaxis = 'bottom'
|
|
3011
|
+
|
|
3012
|
+
# For now, set the formatter to the old one, since that is
|
|
3013
|
+
# sort of what we are used to. We should eventually look at
|
|
3014
|
+
# the default one to see if we like it better.
|
|
3015
|
+
|
|
3016
|
+
(subplot, x_locator, y_locator,
|
|
3017
|
+
x_formatter, y_formatter) = self._matplotlib_tick_formatter(
|
|
3018
|
+
subplot, base=(basex, basey),
|
|
3019
|
+
locator_options=locator_options,
|
|
3020
|
+
scale=(xscale, yscale),
|
|
3021
|
+
tick_formatter=tick_formatter, ticks=ticks,
|
|
3022
|
+
xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin)
|
|
3023
|
+
|
|
3024
|
+
# Make ticklines go on both sides of the axes
|
|
3025
|
+
# if xmiddle:
|
|
3026
|
+
# for t in subplot.xaxis.get_majorticklines():
|
|
3027
|
+
# t.set_marker("|")
|
|
3028
|
+
# t.set_markersize(8)
|
|
3029
|
+
# for t in subplot.xaxis.get_minorticklines():
|
|
3030
|
+
# t.set_marker("|")
|
|
3031
|
+
# t.set_markersize(4)
|
|
3032
|
+
|
|
3033
|
+
# if ymiddle:
|
|
3034
|
+
# for t in subplot.yaxis.get_majorticklines():
|
|
3035
|
+
# t.set_marker("|")
|
|
3036
|
+
# t.set_markersize(8)
|
|
3037
|
+
# for t in subplot.yaxis.get_minorticklines():
|
|
3038
|
+
# t.set_marker("|")
|
|
3039
|
+
# t.set_markersize(4)
|
|
3040
|
+
|
|
3041
|
+
# Make the zero tick labels disappear if the axes cross
|
|
3042
|
+
# inside the picture, but only if log scale is not used
|
|
3043
|
+
if (xmiddle and ymiddle and xscale == 'linear' == yscale):
|
|
3044
|
+
from sage.plot.plot import SelectiveFormatter
|
|
3045
|
+
subplot.yaxis.set_major_formatter(SelectiveFormatter(
|
|
3046
|
+
subplot.yaxis.get_major_formatter(), skip_values=[0]))
|
|
3047
|
+
subplot.xaxis.set_major_formatter(SelectiveFormatter(
|
|
3048
|
+
subplot.xaxis.get_major_formatter(), skip_values=[0]))
|
|
3049
|
+
|
|
3050
|
+
else:
|
|
3051
|
+
for spine in subplot.spines.values():
|
|
3052
|
+
spine.set_visible(False)
|
|
3053
|
+
from matplotlib.ticker import NullFormatter, NullLocator
|
|
3054
|
+
subplot.xaxis.set_major_formatter(NullFormatter())
|
|
3055
|
+
subplot.yaxis.set_major_formatter(NullFormatter())
|
|
3056
|
+
subplot.xaxis.set_major_locator(NullLocator())
|
|
3057
|
+
subplot.yaxis.set_major_locator(NullLocator())
|
|
3058
|
+
|
|
3059
|
+
if frame or axes:
|
|
3060
|
+
# Make minor tickmarks, unless we specify fixed ticks or no ticks
|
|
3061
|
+
# We do this change only on linear scale, otherwise matplotlib
|
|
3062
|
+
# errors out with a memory error.
|
|
3063
|
+
from matplotlib.ticker import (AutoMinorLocator, FixedLocator,
|
|
3064
|
+
LogLocator, NullLocator)
|
|
3065
|
+
if isinstance(x_locator, (NullLocator, FixedLocator)):
|
|
3066
|
+
subplot.xaxis.set_minor_locator(NullLocator())
|
|
3067
|
+
elif xscale == 'linear':
|
|
3068
|
+
subplot.xaxis.set_minor_locator(AutoMinorLocator())
|
|
3069
|
+
else: # log scale
|
|
3070
|
+
from sage.arith.srange import srange
|
|
3071
|
+
base_inv = 1.0 / basex
|
|
3072
|
+
subs = [float(_) for _ in srange(2 * base_inv, 1, base_inv)]
|
|
3073
|
+
subplot.xaxis.set_minor_locator(LogLocator(base=basex,
|
|
3074
|
+
subs=subs))
|
|
3075
|
+
if isinstance(y_locator, (NullLocator, FixedLocator)):
|
|
3076
|
+
subplot.yaxis.set_minor_locator(NullLocator())
|
|
3077
|
+
elif yscale == 'linear':
|
|
3078
|
+
subplot.yaxis.set_minor_locator(AutoMinorLocator())
|
|
3079
|
+
else: # log scale
|
|
3080
|
+
from sage.arith.srange import srange
|
|
3081
|
+
base_inv = 1.0 / basey
|
|
3082
|
+
subs = [float(_) for _ in srange(2 * base_inv, 1, base_inv)]
|
|
3083
|
+
subplot.yaxis.set_minor_locator(LogLocator(base=basey,
|
|
3084
|
+
subs=subs))
|
|
3085
|
+
# Set the color and fontsize of ticks
|
|
3086
|
+
subplot.tick_params(color=self._axes_color,
|
|
3087
|
+
labelcolor=self._tick_label_color,
|
|
3088
|
+
labelsize=self._fontsize, which='both')
|
|
3089
|
+
|
|
3090
|
+
if gridlines is not None:
|
|
3091
|
+
if isinstance(gridlines, (list, tuple)):
|
|
3092
|
+
vgridlines, hgridlines = gridlines
|
|
3093
|
+
else:
|
|
3094
|
+
hgridlines = gridlines
|
|
3095
|
+
vgridlines = gridlines
|
|
3096
|
+
|
|
3097
|
+
if gridlinesstyle is None:
|
|
3098
|
+
# Set up the default grid style
|
|
3099
|
+
gridlinesstyle = {'color': 'black', 'linestyle': ':',
|
|
3100
|
+
'linewidth': 0.5}
|
|
3101
|
+
|
|
3102
|
+
vgridstyle = gridlinesstyle.copy()
|
|
3103
|
+
if vgridlinesstyle is not None:
|
|
3104
|
+
vgridstyle.update(vgridlinesstyle)
|
|
3105
|
+
|
|
3106
|
+
hgridstyle = gridlinesstyle.copy()
|
|
3107
|
+
if hgridlinesstyle is not None:
|
|
3108
|
+
hgridstyle.update(hgridlinesstyle)
|
|
3109
|
+
|
|
3110
|
+
if hgridlines == 'minor':
|
|
3111
|
+
hgridstyle['which'] = 'both'
|
|
3112
|
+
if vgridlines == 'minor':
|
|
3113
|
+
vgridstyle['which'] = 'both'
|
|
3114
|
+
|
|
3115
|
+
if not isinstance(hgridlines, str) and isinstance(hgridlines, Iterable):
|
|
3116
|
+
hlines = iter(hgridlines)
|
|
3117
|
+
hgridstyle.pop("minor", None)
|
|
3118
|
+
for hline in hlines:
|
|
3119
|
+
if isinstance(hline, (list, tuple)):
|
|
3120
|
+
hl, style = hline
|
|
3121
|
+
st = hgridstyle.copy()
|
|
3122
|
+
st.update(style)
|
|
3123
|
+
else:
|
|
3124
|
+
hl = hline
|
|
3125
|
+
st = hgridstyle
|
|
3126
|
+
subplot.axhline(hl, **st)
|
|
3127
|
+
else:
|
|
3128
|
+
if hgridlines not in (None, False):
|
|
3129
|
+
subplot.yaxis.grid(True, **hgridstyle)
|
|
3130
|
+
|
|
3131
|
+
if not isinstance(vgridlines, str) and isinstance(vgridlines, Iterable):
|
|
3132
|
+
vlines = iter(vgridlines)
|
|
3133
|
+
vgridstyle.pop("minor", None)
|
|
3134
|
+
for vline in vlines:
|
|
3135
|
+
if isinstance(vline, (list, tuple)):
|
|
3136
|
+
vl, style = vline
|
|
3137
|
+
st = vgridstyle.copy()
|
|
3138
|
+
st.update(style)
|
|
3139
|
+
else:
|
|
3140
|
+
vl = vline
|
|
3141
|
+
st = vgridstyle
|
|
3142
|
+
subplot.axvline(vl, **st)
|
|
3143
|
+
else:
|
|
3144
|
+
if vgridlines not in (None, False):
|
|
3145
|
+
subplot.xaxis.grid(True, **vgridstyle)
|
|
3146
|
+
|
|
3147
|
+
if self._axes_labels is not None:
|
|
3148
|
+
label_options = {}
|
|
3149
|
+
label_options['color'] = self._axes_label_color
|
|
3150
|
+
label_options['size'] = int(self._axes_labels_size * self._fontsize)
|
|
3151
|
+
subplot.set_xlabel(self._axes_labels[0], **label_options)
|
|
3152
|
+
subplot.set_ylabel(self._axes_labels[1], **label_options)
|
|
3153
|
+
|
|
3154
|
+
if axes is True and frame is False:
|
|
3155
|
+
# We set the label positions according to where we are
|
|
3156
|
+
# drawing the axes.
|
|
3157
|
+
if xaxis == 'bottom':
|
|
3158
|
+
yaxis_labely = subplot.get_ylim()[1]
|
|
3159
|
+
yaxis_labeloffset = 8
|
|
3160
|
+
yaxis_vert = 'bottom'
|
|
3161
|
+
xaxis_labely = 0
|
|
3162
|
+
xaxis_vert = 'baseline'
|
|
3163
|
+
else:
|
|
3164
|
+
yaxis_labely = subplot.get_ylim()[0]
|
|
3165
|
+
yaxis_labeloffset = -8
|
|
3166
|
+
yaxis_vert = 'top'
|
|
3167
|
+
xaxis_labely = 1
|
|
3168
|
+
xaxis_vert = 'top'
|
|
3169
|
+
|
|
3170
|
+
if yaxis == 'left':
|
|
3171
|
+
xaxis_labelx = subplot.get_xlim()[1]
|
|
3172
|
+
xaxis_labeloffset = 8
|
|
3173
|
+
xaxis_horiz = 'left'
|
|
3174
|
+
yaxis_labelx = 0
|
|
3175
|
+
else:
|
|
3176
|
+
xaxis_labelx = subplot.get_xlim()[0]
|
|
3177
|
+
xaxis_labeloffset = -8
|
|
3178
|
+
xaxis_horiz = 'right'
|
|
3179
|
+
yaxis_labelx = 1
|
|
3180
|
+
|
|
3181
|
+
from matplotlib.transforms import offset_copy
|
|
3182
|
+
xlabel = subplot.xaxis.get_label()
|
|
3183
|
+
xlabel.set_horizontalalignment(xaxis_horiz)
|
|
3184
|
+
xlabel.set_verticalalignment(xaxis_vert)
|
|
3185
|
+
trans = subplot.spines[xaxis].get_transform()
|
|
3186
|
+
labeltrans = offset_copy(trans, figure, x=xaxis_labeloffset,
|
|
3187
|
+
y=0, units='points')
|
|
3188
|
+
subplot.xaxis.set_label_coords(x=xaxis_labelx,
|
|
3189
|
+
y=xaxis_labely, transform=labeltrans)
|
|
3190
|
+
|
|
3191
|
+
ylabel = subplot.yaxis.get_label()
|
|
3192
|
+
ylabel.set_horizontalalignment('center')
|
|
3193
|
+
ylabel.set_verticalalignment(yaxis_vert)
|
|
3194
|
+
ylabel.set_rotation('horizontal')
|
|
3195
|
+
trans = subplot.spines[yaxis].get_transform()
|
|
3196
|
+
labeltrans = offset_copy(trans, figure, x=0,
|
|
3197
|
+
y=yaxis_labeloffset, units='points')
|
|
3198
|
+
subplot.yaxis.set_label_coords(x=yaxis_labelx,
|
|
3199
|
+
y=yaxis_labely, transform=labeltrans)
|
|
3200
|
+
|
|
3201
|
+
# This option makes the xlim and ylim limits not take effect
|
|
3202
|
+
# todo: figure out which limits were specified, and let the
|
|
3203
|
+
# free limits autoscale
|
|
3204
|
+
# subplot.autoscale_view(tight=True)
|
|
3205
|
+
if title is not None:
|
|
3206
|
+
if title_pos is not None:
|
|
3207
|
+
if (not isinstance(title_pos, (list, tuple)) or
|
|
3208
|
+
len(title_pos) != 2):
|
|
3209
|
+
raise ValueError("'title_pos' must be a list or tuple "
|
|
3210
|
+
"of two real numbers.")
|
|
3211
|
+
title_pos = (float(title_pos[0]), float(title_pos[1]))
|
|
3212
|
+
|
|
3213
|
+
if (frame) or (axes_labels is None):
|
|
3214
|
+
if title_pos is not None:
|
|
3215
|
+
subplot.set_title(title, fontsize=fontsize,
|
|
3216
|
+
position=title_pos)
|
|
3217
|
+
else:
|
|
3218
|
+
subplot.set_title(title, fontsize=fontsize)
|
|
3219
|
+
else:
|
|
3220
|
+
# frame is false axes is not None, and neither is axes_labels
|
|
3221
|
+
# Then, the title is moved up to avoid overlap with axes labels
|
|
3222
|
+
if title_pos is None:
|
|
3223
|
+
title_pos = (0.5, 1.05)
|
|
3224
|
+
subplot.set_title(title, fontsize=fontsize, position=title_pos)
|
|
3225
|
+
|
|
3226
|
+
for g in self._objects:
|
|
3227
|
+
g.set_options(old_opts[g])
|
|
3228
|
+
|
|
3229
|
+
return figure
|
|
3230
|
+
|
|
3231
|
+
def save_image(self, filename=None, *args, **kwds):
|
|
3232
|
+
r"""
|
|
3233
|
+
Save an image representation of ``self``.
|
|
3234
|
+
|
|
3235
|
+
The image type is determined by the extension of the filename.
|
|
3236
|
+
For example, this could be ``.png``, ``.jpg``, ``.gif``,
|
|
3237
|
+
``.pdf``, ``.svg``. Currently this is implemented by calling
|
|
3238
|
+
the :meth:`save` method of self, passing along all arguments
|
|
3239
|
+
and keywords.
|
|
3240
|
+
|
|
3241
|
+
.. NOTE::
|
|
3242
|
+
|
|
3243
|
+
Not all image types are necessarily implemented for all
|
|
3244
|
+
graphics types. See :meth:`save` for more details.
|
|
3245
|
+
|
|
3246
|
+
EXAMPLES::
|
|
3247
|
+
|
|
3248
|
+
sage: import tempfile
|
|
3249
|
+
sage: c = circle((1,1), 1, color='red')
|
|
3250
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.png') as f:
|
|
3251
|
+
....: c.save_image(f.name, xmin=-1, xmax=3,
|
|
3252
|
+
....: ymin=-1, ymax=3)
|
|
3253
|
+
"""
|
|
3254
|
+
self.save(filename, *args, **kwds)
|
|
3255
|
+
|
|
3256
|
+
# filename argument is written explicitly so that it can be used as a
|
|
3257
|
+
# positional one, which is a very likely usage for this function.
|
|
3258
|
+
|
|
3259
|
+
@suboptions('legend', **LEGEND_OPTIONS)
|
|
3260
|
+
def save(self, filename, **kwds):
|
|
3261
|
+
r"""
|
|
3262
|
+
Save the graphics to an image file.
|
|
3263
|
+
|
|
3264
|
+
INPUT:
|
|
3265
|
+
|
|
3266
|
+
- ``filename`` -- string. The filename and the image format
|
|
3267
|
+
given by the extension, which can be one of the following:
|
|
3268
|
+
|
|
3269
|
+
* ``.eps``,
|
|
3270
|
+
|
|
3271
|
+
* ``.pdf``,
|
|
3272
|
+
|
|
3273
|
+
* ``.pgf``,
|
|
3274
|
+
|
|
3275
|
+
* ``.png``,
|
|
3276
|
+
|
|
3277
|
+
* ``.ps``,
|
|
3278
|
+
|
|
3279
|
+
* ``.sobj`` (for a Sage object you can load later),
|
|
3280
|
+
|
|
3281
|
+
* ``.svg``,
|
|
3282
|
+
|
|
3283
|
+
* empty extension will be treated as ``.sobj``.
|
|
3284
|
+
|
|
3285
|
+
All other keyword arguments will be passed to the plotter.
|
|
3286
|
+
|
|
3287
|
+
OUTPUT: none
|
|
3288
|
+
|
|
3289
|
+
EXAMPLES::
|
|
3290
|
+
|
|
3291
|
+
sage: c = circle((1,1), 1, color='red')
|
|
3292
|
+
sage: from tempfile import NamedTemporaryFile
|
|
3293
|
+
sage: with NamedTemporaryFile(suffix='.png') as f:
|
|
3294
|
+
....: c.save(f.name, xmin=-1, xmax=3, ymin=-1, ymax=3)
|
|
3295
|
+
|
|
3296
|
+
To make a figure bigger or smaller, use ``figsize``::
|
|
3297
|
+
|
|
3298
|
+
sage: c.save(f.name, figsize=5, xmin=-1, xmax=3, ymin=-1, ymax=3)
|
|
3299
|
+
|
|
3300
|
+
By default, the figure grows to include all of the graphics and text,
|
|
3301
|
+
so the final image may not be exactly the figure size you specified.
|
|
3302
|
+
If you want a figure to be exactly a certain size, specify the keyword
|
|
3303
|
+
``fig_tight=False``::
|
|
3304
|
+
|
|
3305
|
+
sage: c.save(f.name, figsize=[8,4], fig_tight=False,
|
|
3306
|
+
....: xmin=-1, xmax=3, ymin=-1, ymax=3)
|
|
3307
|
+
|
|
3308
|
+
You can also pass extra options to the plot command instead of this
|
|
3309
|
+
method, e.g. ::
|
|
3310
|
+
|
|
3311
|
+
sage: plot(x^2 - 5, (x, 0, 5), ymin=0).save(tmp_filename(ext='.png')) # needs sage.symbolic
|
|
3312
|
+
|
|
3313
|
+
will save the same plot as the one shown by this command::
|
|
3314
|
+
|
|
3315
|
+
sage: plot(x^2 - 5, (x, 0, 5), ymin=0) # needs sage.symbolic
|
|
3316
|
+
Graphics object consisting of 1 graphics primitive
|
|
3317
|
+
|
|
3318
|
+
(This test verifies that :issue:`8632` is fixed.)
|
|
3319
|
+
|
|
3320
|
+
TESTS:
|
|
3321
|
+
|
|
3322
|
+
Legend labels should save correctly::
|
|
3323
|
+
|
|
3324
|
+
sage: # needs sage.symbolic
|
|
3325
|
+
sage: P = plot(x,(x,0,1),legend_label='$xyz$')
|
|
3326
|
+
sage: P.set_legend_options(back_color=(1,0,0))
|
|
3327
|
+
sage: P.set_legend_options(loc=7)
|
|
3328
|
+
sage: import tempfile
|
|
3329
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.png') as f:
|
|
3330
|
+
....: P.save(f.name)
|
|
3331
|
+
|
|
3332
|
+
This plot should save with the frame shown, showing :issue:`7524`
|
|
3333
|
+
is fixed (same issue as :issue:`7981` and :issue:`8632`)::
|
|
3334
|
+
|
|
3335
|
+
sage: var('x,y') # needs sage.symbolic
|
|
3336
|
+
(x, y)
|
|
3337
|
+
sage: a = plot_vector_field((x,-y),(x,-1,1),(y,-1,1)) # needs sage.symbolic
|
|
3338
|
+
sage: import tempfile
|
|
3339
|
+
sage: with tempfile.NamedTemporaryFile(suffix='.png') as f: # needs sage.symbolic
|
|
3340
|
+
....: a.save(f.name)
|
|
3341
|
+
|
|
3342
|
+
The following plot should show the axes; fixes :issue:`14782` ::
|
|
3343
|
+
|
|
3344
|
+
sage: plot(x^2, (x, 1, 2), ticks=[[], []]) # needs sage.symbolic
|
|
3345
|
+
Graphics object consisting of 1 graphics primitive
|
|
3346
|
+
"""
|
|
3347
|
+
options = {}
|
|
3348
|
+
options.update(self.SHOW_OPTIONS)
|
|
3349
|
+
options.update(self._extra_kwds)
|
|
3350
|
+
options.update(kwds)
|
|
3351
|
+
dpi = options.pop('dpi')
|
|
3352
|
+
transparent = options.pop('transparent')
|
|
3353
|
+
fig_tight = options.pop('fig_tight')
|
|
3354
|
+
|
|
3355
|
+
ext = os.path.splitext(filename)[1].lower()
|
|
3356
|
+
|
|
3357
|
+
if ext in ['', '.sobj']:
|
|
3358
|
+
SageObject.save(self, filename)
|
|
3359
|
+
elif ext not in ALLOWED_EXTENSIONS:
|
|
3360
|
+
raise ValueError("allowed file extensions for images are '" +
|
|
3361
|
+
"', '".join(ALLOWED_EXTENSIONS) + "'!")
|
|
3362
|
+
else:
|
|
3363
|
+
from matplotlib import rcParams
|
|
3364
|
+
rc_backup = (rcParams['ps.useafm'], rcParams['pdf.use14corefonts'],
|
|
3365
|
+
rcParams['text.usetex']) # save the rcParams
|
|
3366
|
+
figure = self.matplotlib(**options)
|
|
3367
|
+
# You can output in PNG, PS, EPS, PDF, PGF, or SVG format, depending
|
|
3368
|
+
# on the file extension.
|
|
3369
|
+
# PGF is handled by a different backend
|
|
3370
|
+
if ext == '.pgf':
|
|
3371
|
+
from sage.features.latex import xelatex, pdflatex, lualatex
|
|
3372
|
+
latex_implementations = []
|
|
3373
|
+
if xelatex().is_present():
|
|
3374
|
+
latex_implementations.append('xelatex')
|
|
3375
|
+
if pdflatex().is_present():
|
|
3376
|
+
latex_implementations.append('pdflatex')
|
|
3377
|
+
if lualatex().is_present():
|
|
3378
|
+
latex_implementations.append('lualatex')
|
|
3379
|
+
if not latex_implementations:
|
|
3380
|
+
raise ValueError("Matplotlib requires either xelatex, "
|
|
3381
|
+
"lualatex, or pdflatex.")
|
|
3382
|
+
if latex_implementations[0] == "pdflatex":
|
|
3383
|
+
# use pdflatex and set font encoding as per
|
|
3384
|
+
# matplotlib documentation:
|
|
3385
|
+
# https://matplotlib.org/users/pgf.html#pgf-tutorial
|
|
3386
|
+
pgf_options = {"pgf.texsystem": "pdflatex",
|
|
3387
|
+
"pgf.preamble": [
|
|
3388
|
+
r"\usepackage[utf8x]{inputenc}",
|
|
3389
|
+
r"\usepackage[T1]{fontenc}"]}
|
|
3390
|
+
else:
|
|
3391
|
+
pgf_options = {
|
|
3392
|
+
"pgf.texsystem": latex_implementations[0],
|
|
3393
|
+
}
|
|
3394
|
+
from matplotlib import rcParams
|
|
3395
|
+
rcParams.update(pgf_options)
|
|
3396
|
+
from matplotlib.backends.backend_pgf import FigureCanvasPgf
|
|
3397
|
+
figure.set_canvas(FigureCanvasPgf(figure))
|
|
3398
|
+
|
|
3399
|
+
# matplotlib looks at the file extension to see what the renderer should be.
|
|
3400
|
+
# The default is FigureCanvasAgg for PNG's because this is by far the most
|
|
3401
|
+
# common type of files rendered, like in the notebook, for example.
|
|
3402
|
+
# if the file extension is not '.png', then matplotlib will handle it.
|
|
3403
|
+
else:
|
|
3404
|
+
from matplotlib.backends.backend_agg import FigureCanvasAgg
|
|
3405
|
+
figure.set_canvas(FigureCanvasAgg(figure))
|
|
3406
|
+
# this messes up the aspect ratio!
|
|
3407
|
+
# figure.canvas.mpl_connect('draw_event', pad_for_tick_labels)
|
|
3408
|
+
|
|
3409
|
+
# tight_layout adjusts the *subplot* parameters so ticks aren't cut off, etc.
|
|
3410
|
+
figure.tight_layout()
|
|
3411
|
+
|
|
3412
|
+
opts = {'dpi': dpi, 'transparent': transparent}
|
|
3413
|
+
if fig_tight is True:
|
|
3414
|
+
opts['bbox_inches'] = 'tight'
|
|
3415
|
+
if self._bbox_extra_artists:
|
|
3416
|
+
opts['bbox_extra_artists'] = self._bbox_extra_artists
|
|
3417
|
+
|
|
3418
|
+
figure.savefig(filename, **opts)
|
|
3419
|
+
|
|
3420
|
+
# Restore the rcParams to the original, possibly user-set values
|
|
3421
|
+
(rcParams['ps.useafm'], rcParams['pdf.use14corefonts'],
|
|
3422
|
+
rcParams['text.usetex']) = rc_backup
|
|
3423
|
+
|
|
3424
|
+
def _latex_(self, **kwds):
|
|
3425
|
+
"""
|
|
3426
|
+
Return a string plotting ``self`` with PGF.
|
|
3427
|
+
|
|
3428
|
+
INPUT:
|
|
3429
|
+
|
|
3430
|
+
- ``**kwds`` -- all keyword arguments will be passed to the plotter
|
|
3431
|
+
|
|
3432
|
+
OUTPUT: string of PGF commands to plot ``self``
|
|
3433
|
+
|
|
3434
|
+
EXAMPLES::
|
|
3435
|
+
|
|
3436
|
+
sage: L = line([(0,0), (1,1)], axes=False)
|
|
3437
|
+
sage: L._latex_() # not tested
|
|
3438
|
+
'%% Creator: Matplotlib, PGF backend...
|
|
3439
|
+
"""
|
|
3440
|
+
tmpfilename = tmp_filename(ext='.pgf')
|
|
3441
|
+
self.save(filename=tmpfilename, **kwds)
|
|
3442
|
+
with open(tmpfilename) as tmpfile:
|
|
3443
|
+
latex_list = tmpfile.readlines()
|
|
3444
|
+
from sage.misc.latex import latex
|
|
3445
|
+
latex.add_package_to_preamble_if_available('pgf')
|
|
3446
|
+
return ''.join(latex_list)
|
|
3447
|
+
|
|
3448
|
+
def description(self):
|
|
3449
|
+
r"""
|
|
3450
|
+
Print a textual description to stdout.
|
|
3451
|
+
|
|
3452
|
+
This method is mostly used for doctests.
|
|
3453
|
+
|
|
3454
|
+
EXAMPLES::
|
|
3455
|
+
|
|
3456
|
+
sage: print(polytopes.hypercube(2).plot().description()) # needs sage.geometry.polyhedron
|
|
3457
|
+
Polygon defined by 4 points: [(-1.0, -1.0), (1.0, -1.0), (1.0, 1.0), (-1.0, 1.0)]
|
|
3458
|
+
Line defined by 2 points: [(-1.0, 1.0), (-1.0, -1.0)]
|
|
3459
|
+
Line defined by 2 points: [(1.0, -1.0), (-1.0, -1.0)]
|
|
3460
|
+
Line defined by 2 points: [(1.0, -1.0), (1.0, 1.0)]
|
|
3461
|
+
Line defined by 2 points: [(1.0, 1.0), (-1.0, 1.0)]
|
|
3462
|
+
Point set defined by 4 point(s): [(1.0, -1.0), (1.0, 1.0), (-1.0, 1.0), (-1.0, -1.0)]
|
|
3463
|
+
"""
|
|
3464
|
+
data = []
|
|
3465
|
+
for g in self:
|
|
3466
|
+
g_zorder = g.options().get('zorder', 0)
|
|
3467
|
+
if hasattr(g, 'xdata'):
|
|
3468
|
+
g_str = f'{g}:\t{list(zip(g.xdata, g.ydata))}'
|
|
3469
|
+
else:
|
|
3470
|
+
g_str = repr(g)
|
|
3471
|
+
data.append([g_zorder, g_str, g])
|
|
3472
|
+
data.sort()
|
|
3473
|
+
return '\n'.join(g[1] for g in data)
|
|
3474
|
+
|
|
3475
|
+
def inset(self, graphics, pos=None, fontsize=None):
|
|
3476
|
+
r"""
|
|
3477
|
+
Add a graphics object as an inset.
|
|
3478
|
+
|
|
3479
|
+
INPUT:
|
|
3480
|
+
|
|
3481
|
+
- ``graphics`` -- the graphics object (instance of :class:`Graphics`)
|
|
3482
|
+
to be added as an inset to the current graphics
|
|
3483
|
+
|
|
3484
|
+
- ``pos`` -- (default: ``None``) 4-tuple
|
|
3485
|
+
``(left, bottom, width, height)``
|
|
3486
|
+
specifying the location and size of the inset on the final figure,
|
|
3487
|
+
all quantities being in fractions of the figure width and height; if
|
|
3488
|
+
``None``, the value ``(0.7, 0.7, 0.2, 0.2)`` is used
|
|
3489
|
+
|
|
3490
|
+
- ``fontsize`` -- (default: ``None``) integer, font size (in points)
|
|
3491
|
+
for the inset; if ``None``, the value of 6 points is used, unless
|
|
3492
|
+
``fontsize`` has been explicitly set in the construction of
|
|
3493
|
+
``graphics`` (in this case, it is not overwritten here)
|
|
3494
|
+
|
|
3495
|
+
OUTPUT: instance of :class:`~sage.plot.multigraphics.MultiGraphics`
|
|
3496
|
+
|
|
3497
|
+
EXAMPLES::
|
|
3498
|
+
|
|
3499
|
+
sage: # needs sage.symbolic
|
|
3500
|
+
sage: f(x) = x^2*sin(1/x)
|
|
3501
|
+
sage: g1 = plot(f(x), (x, -2, 2), axes_labels=['$x$', '$y$'])
|
|
3502
|
+
sage: g2 = plot(f(x), (x, -0.3, 0.3), axes_labels=['$x$', '$y$'],
|
|
3503
|
+
....: frame=True)
|
|
3504
|
+
sage: g1.inset(g2)
|
|
3505
|
+
Multigraphics with 2 elements
|
|
3506
|
+
|
|
3507
|
+
.. PLOT::
|
|
3508
|
+
|
|
3509
|
+
f = (x**2*sin(1/x)).function(x)
|
|
3510
|
+
g1 = plot(f(x), (x, -2, 2), axes_labels=['$x$', '$y$'])
|
|
3511
|
+
g2 = plot(f(x), (x, -0.3, 0.3), axes_labels=['$x$', '$y$'], \
|
|
3512
|
+
frame=True)
|
|
3513
|
+
sphinx_plot(g1.inset(g2))
|
|
3514
|
+
|
|
3515
|
+
Using non-default values for the position/size and the font size::
|
|
3516
|
+
|
|
3517
|
+
sage: g1.inset(g2, pos=(0.15, 0.7, 0.25, 0.25), fontsize=8) # needs sage.symbolic
|
|
3518
|
+
Multigraphics with 2 elements
|
|
3519
|
+
|
|
3520
|
+
.. PLOT::
|
|
3521
|
+
|
|
3522
|
+
f = (x**2*sin(1/x)).function(x)
|
|
3523
|
+
g1 = plot(f(x), (x, -2, 2), axes_labels=['$x$', '$y$'])
|
|
3524
|
+
g2 = plot(f(x), (x, -0.3, 0.3), axes_labels=['$x$', '$y$'], \
|
|
3525
|
+
frame=True)
|
|
3526
|
+
sphinx_plot(g1.inset(g2, pos=(0.15, 0.7, 0.25, 0.25), fontsize=8))
|
|
3527
|
+
|
|
3528
|
+
We can add another inset by invoking ``inset`` on the last output::
|
|
3529
|
+
|
|
3530
|
+
sage: g1g2 = _ # needs sage.symbolic
|
|
3531
|
+
sage: g3 = plot(f(x), (x, -0.05, 0.05), axes_labels=['$x$', '$y$'], # needs sage.symbolic
|
|
3532
|
+
....: frame=True)
|
|
3533
|
+
sage: g1g2.inset(g3, pos=(0.65, 0.12, 0.25, 0.25)) # needs sage.symbolic
|
|
3534
|
+
Multigraphics with 3 elements
|
|
3535
|
+
|
|
3536
|
+
.. PLOT::
|
|
3537
|
+
|
|
3538
|
+
f = (x**2*sin(1/x)).function(x)
|
|
3539
|
+
g1 = plot(f(x), (x, -2, 2), axes_labels=['$x$', '$y$'])
|
|
3540
|
+
g2 = plot(f(x), (x, -0.3, 0.3), axes_labels=['$x$', '$y$'], \
|
|
3541
|
+
frame=True)
|
|
3542
|
+
g1g2 = g1.inset(g2, pos=(0.15, 0.7, 0.25, 0.25), fontsize=8)
|
|
3543
|
+
g3 = plot(f(x), (x, -0.05, 0.05), axes_labels=['$x$', '$y$'], \
|
|
3544
|
+
frame=True)
|
|
3545
|
+
sphinx_plot(g1g2.inset(g3, pos=(0.65, 0.12, 0.25, 0.25)))
|
|
3546
|
+
"""
|
|
3547
|
+
from .multigraphics import MultiGraphics
|
|
3548
|
+
if pos is None:
|
|
3549
|
+
pos = (0.7, 0.7, 0.2, 0.2)
|
|
3550
|
+
pos0 = (0.05, 0.05, 0.9, 0.9)
|
|
3551
|
+
if fontsize is not None:
|
|
3552
|
+
graphics._extra_kwds['fontsize'] = fontsize
|
|
3553
|
+
elif 'fontsize' not in graphics._extra_kwds:
|
|
3554
|
+
graphics._extra_kwds['fontsize'] = 6
|
|
3555
|
+
return MultiGraphics([(self, pos0), (graphics, pos)])
|
|
3556
|
+
|
|
3557
|
+
|
|
3558
|
+
# Deprecation notice for GraphicsArray import
|
|
3559
|
+
def GraphicsArray(*args, **kwargs):
|
|
3560
|
+
r"""
|
|
3561
|
+
This is deprecated (see :issue:`28675`).
|
|
3562
|
+
Use :class:`sage.plot.multigraphics.GraphicsArray` instead.
|
|
3563
|
+
|
|
3564
|
+
TESTS::
|
|
3565
|
+
|
|
3566
|
+
sage: from sage.plot.graphics import GraphicsArray
|
|
3567
|
+
sage: c = circle((0,0), 1)
|
|
3568
|
+
sage: G = GraphicsArray([c, c])
|
|
3569
|
+
doctest:...: DeprecationWarning: GraphicsArray must be imported from
|
|
3570
|
+
sage.plot.multigraphics and no longer from sage.plot.graphics.
|
|
3571
|
+
See https://github.com/sagemath/sage/issues/28675 for details.
|
|
3572
|
+
sage: G
|
|
3573
|
+
Graphics Array of size 1 x 2
|
|
3574
|
+
"""
|
|
3575
|
+
from .multigraphics import GraphicsArray as NewGraphicsArray
|
|
3576
|
+
from sage.misc.superseded import deprecation
|
|
3577
|
+
deprecation(28675, "GraphicsArray must be imported from "
|
|
3578
|
+
"sage.plot.multigraphics and no longer from "
|
|
3579
|
+
"sage.plot.graphics.")
|
|
3580
|
+
return NewGraphicsArray(*args, **kwargs)
|