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