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