Ticket #4529: trac_4529-add-log-scale.patch
File trac_4529-add-log-scale.patch, 22.5 KB (added by , 9 years ago) |
---|
-
sage/plot/graphics.py
# HG changeset patch # User Punarbasu Purkayastha <ppurka@gmail.com> # Date 1338111511 -28800 # Node ID 50485c4d4dd2d50839e508b720ba0121053e528f # Parent 2e78a1d2e09596753afb4ed9223be6c78c5c9e36 add logarithmic scale to Graphics(). Also fix a problem with user specified xmin,xmax,ymin,ymax diff --git a/sage/plot/graphics.py b/sage/plot/graphics.py
a b 9 9 AUTHORS: 10 10 11 11 - Jeroen Demeyer (2012-04-19): split off this file from plot.py (:trac:`12857`) 12 - Punarbasu Purkayastha (2012-05-20): Add logarithmic scale (:trac:`4529`) 12 13 13 14 """ 14 15 … … 117 118 ... G+=line(l,color=hue(c + p*(x/h))) 118 119 sage: G.show(figsize=[5,5]) 119 120 121 We can change the scale of the axes in the graphics before displaying.:: 122 123 sage: G = plot(exp, 1, 10) 124 sage: G.show(scale='semilogy') 125 120 126 TESTS: 121 127 122 128 From :trac:`4604`, ensure Graphics can handle 3d objects:: … … 843 849 844 850 sage: show_default(True) 845 851 """ 846 pr, i = '', 0847 for x in self:848 pr += '\n\t%s -- %s'%(i, x)849 i += 1850 852 s = "Graphics object consisting of %s graphics primitives"%(len(self)) 851 853 if len(self) == 1: 852 854 s = s[:-1] … … 1001 1003 elif other.aspect_ratio()=='automatic': 1002 1004 g.set_aspect_ratio(self.aspect_ratio()) 1003 1005 else: 1004 g.set_aspect_ratio( 1006 g.set_aspect_ratio(max(self.aspect_ratio(), other.aspect_ratio())) 1005 1007 return g 1006 1008 1007 1009 def add_primitive(self, primitive): … … 1098 1100 """ 1099 1101 self._extra_kwds = kwds 1100 1102 1103 def _set_scale(self, figure, scale=None, base=None): 1104 """ 1105 Set the scale of the axes in the current figure. This function is 1106 only for internal use. 1107 1108 INPUT: 1109 - ``figure`` -- the matplotlib figure instance. 1110 - ``scale`` -- the scale of the figure. Values it can take are 1111 `"linear"`, `"loglog"`, `"semilogx"`, `"semilogy"`. See 1112 :meth:`show` for other options it can take. 1113 - ``base`` -- the base of the logarithm if a logarithmic scale is 1114 set. See :meth:`show` for the options it can take. 1115 1116 OUTPUT: 1117 The scale in the form of a tuple: (xscale, yscale, basex, basey) 1118 1119 EXAMPLES:: 1120 1121 sage: p = plot(x,1,10) 1122 sage: fig = p.matplotlib() 1123 sage: p._set_scale(fig, scale='linear', base=2) 1124 ('linear', 'linear', 10, 10) 1125 sage: p._set_scale(fig, scale='semilogy', base=2) 1126 ('linear', 'log', 10, 2) 1127 sage: p._set_scale(fig, scale=('loglog', 2, 3)) 1128 ('log', 'log', 2, 3) 1129 sage: p._set_scale(fig, scale=['semilogx', 2]) 1130 ('log', 'linear', 2, 10) 1131 1132 TESTS:: 1133 1134 sage: p._set_scale(fig, 'log') 1135 Traceback (most recent call last): 1136 ... 1137 ValueError: The scale must be one of 'linear', 'loglog', 'semilogx' or 'semilogy' -- got 'log' 1138 sage: p._set_scale(fig, ('loglog', 1)) 1139 Traceback (most recent call last): 1140 ... 1141 ValueError: The base of the logarithm must be greater than 1 1142 """ 1143 if scale is None: 1144 return ('linear', 'linear', 10, 10) 1145 if isinstance(scale, (list, tuple)): 1146 if len(scale) != 2 and len(scale) != 3: 1147 raise ValueError("If the input is a tuple, it must be of " 1148 "the form (scale, base) or (scale, basex, basey)") 1149 if len(scale) == 2: 1150 base = scale[1] 1151 else: 1152 base = scale[1:] 1153 scale = scale[0] 1154 1155 if scale not in ('linear', 'loglog', 'semilogx', 'semilogy'): 1156 raise ValueError("The scale must be one of 'linear', 'loglog'," 1157 " 'semilogx' or 'semilogy' -- got '{0}'".format(scale)) 1158 1159 if isinstance(base, (list, tuple)): 1160 basex, basey = base 1161 elif base is None: 1162 basex = basey = 10 1163 else: 1164 basex = basey = base 1165 1166 if basex <= 1 or basey <= 1: 1167 raise ValueError("The base of the logarithm must be greater " 1168 "than 1") 1169 1170 ax = figure.get_axes()[0] 1171 xscale = yscale = 'linear' 1172 if scale == 'linear': 1173 basex = basey = 10 1174 elif scale == 'loglog': 1175 ax.set_xscale('log', basex=basex) 1176 ax.set_yscale('log', basey=basey) 1177 xscale = yscale = 'log' 1178 elif scale == 'semilogx': 1179 ax.set_xscale('log', basex=basex) 1180 basey = 10 1181 xscale = 'log' 1182 elif scale == 'semilogy': 1183 ax.set_yscale('log', basey=basey) 1184 basex = 10 1185 yscale = 'log' 1186 1187 return (xscale, yscale, basex, basey) 1188 1189 1101 1190 # This dictionary has the default values for the keywords to show(). When 1102 1191 # show is invoked with keyword arguments, those arguments are merged with 1103 1192 # this dictionary to create a set of keywords with the defaults filled in. … … 1109 1198 SHOW_OPTIONS = dict(filename=None, 1110 1199 # axes options 1111 1200 axes=None, axes_labels=None, axes_pad=.02, 1201 base=None, scale=None, 1112 1202 xmin=None, xmax=None, ymin=None, ymax=None, 1113 1203 # Figure options 1114 1204 aspect_ratio=None, dpi=DEFAULT_DPI, fig_tight=True, … … 1274 1364 1275 1365 - ``legend_*`` - all the options valid for :meth:`set_legend_options` prefixed with ``legend_`` 1276 1366 1367 - ``base`` - (default: 10) the base of the logarithm if 1368 a logarithmic scale is set. This must be greater than 1. The base 1369 can be also given as a list or tuple ``(basex, basey)``. 1370 ``basex`` sets the base of the logarithm along the horizontal 1371 axis and ``basey`` sets the base along the vertical axis. 1372 1373 - ``scale`` -- (default: `"linear"`) string. The scale of the axes. 1374 Possible values are 1375 1376 - `"linear"` -- linear scaling of both the axes 1377 - `"loglog"` -- sets both the horizontal and vertical axes to 1378 logarithmic scale 1379 - `"semilogx"` -- sets only the horizontal axis to logarithmic 1380 scale. 1381 - `"semilogy"` -- sets only the vertical axis to logarithmic 1382 scale. 1383 1384 The scale can be also be given as single argument that is a list 1385 or tuple ``(scale, base)`` or ``(scale, basex, basey)``. 1386 1387 Note: If the ``scale`` is `"linear"`, then irrespective of what 1388 ``base`` is set to, it will default to 10 and will remain unused. 1389 1277 1390 EXAMPLES:: 1278 1391 1279 1392 sage: c = circle((1,1), 1, color='red') … … 1300 1413 1301 1414 sage: plot(sin(x), (x, -4, 4), transparent=True) 1302 1415 1416 We can change the scale of the axes in the graphics before 1417 displaying:: 1418 1419 sage: G = plot(exp, 1, 10) 1420 sage: G.show(scale='semilogy') 1421 1422 We can change the base of the logarithm too. The following changes 1423 the vertical axis to be on log scale, and with base 2. Note that 1424 the ``base`` argument will ignore any changes to the axis which is 1425 in linear scale.:: 1426 1427 sage: G.show(scale='semilogy', base=2) # y axis as powers of 2 1428 1429 sage: G.show(scale='semilogy', base=(3,2)) # base ignored for x-axis 1430 1431 The scale can be also given as a 2-tuple or a 3-tuple.:: 1432 1433 sage: G.show(scale=('loglog', 2)) # both x and y axes in base 2 1434 1435 sage: G.show(scale=('loglog', 2, 3)) # x in base 2, y in base 3 1436 1437 The base need not be an integer.:: 1438 1439 sage: G.show(scale='semilogy', base=float(e)) # base is e 1440 1441 Logarithmic scale can be used for various kinds of plots. Here are 1442 some examples.:: 1443 1444 sage: G = list_plot(map(lambda i: 10**i, range(10))) 1445 sage: G.show(scale='semilogy') 1446 1447 sage: G = parametric_plot((x, x**2), (x, 1, 10)) 1448 sage: G.show(scale='loglog') 1449 1450 sage: G = arc((2,3), 2, 1, angle=pi/2, sector=(0,pi/2)) 1451 sage: G.show(scale=('loglog', 2)) 1452 1453 sage: disk((0.1,0.1), 1, (pi/3, pi/2)).show(scale='loglog') 1454 1455 sage: polygon([(1,1), (10,10), (10,1)]).show(scale='loglog') 1456 1457 sage: x, y = var('x, y') 1458 sage: G = plot_vector_field((sin(x),cos(y)),(x,0.1,3),(y,0.1,3)) 1459 sage: G.show(scale='loglog') 1460 1303 1461 Add grid lines at the major ticks of the axes. 1304 1462 1305 1463 :: … … 1610 1768 ymax += 1 1611 1769 return {'xmin':xmin, 'xmax':xmax, 'ymin':ymin, 'ymax':ymax} 1612 1770 1613 def _matplotlib_tick_formatter(self, subplot, locator_options={}, 1771 def _matplotlib_tick_formatter(self, subplot, base=(10, 10), 1772 locator_options={}, scale=('linear', 'linear'), 1614 1773 tick_formatter=(None, None), ticks=(None, None), 1615 1774 xmax=None, xmin=None, ymax=None, ymin=None): 1616 1775 r""" … … 1635 1794 """ 1636 1795 # This function is created to refactor some code that is repeated 1637 1796 # in the matplotlib function 1638 from matplotlib.ticker import (FixedLocator, Locator, MaxNLocator, 1797 from matplotlib.ticker import (FixedLocator, Locator, 1798 LogFormatterMathtext, LogLocator, MaxNLocator, 1639 1799 MultipleLocator, NullLocator, OldScalarFormatter) 1640 1800 1641 1801 x_locator, y_locator = ticks 1642 1802 #---------------------- Location of x-ticks ---------------------# 1803 1643 1804 if x_locator is None: 1644 x_locator = MaxNLocator(**locator_options) 1805 if scale[0] == 'log': 1806 x_locator = LogLocator(base=base[0]) 1807 else: 1808 x_locator = MaxNLocator(**locator_options) 1645 1809 elif isinstance(x_locator,Locator): 1646 1810 pass 1647 1811 elif x_locator == []: … … 1659 1823 1660 1824 #---------------------- Location of y-ticks ---------------------# 1661 1825 if y_locator is None: 1662 y_locator = MaxNLocator(**locator_options) 1826 if scale[1] == 'log': 1827 y_locator = LogLocator(base=base[1]) 1828 else: 1829 y_locator = MaxNLocator(**locator_options) 1663 1830 elif isinstance(y_locator,Locator): 1664 1831 pass 1665 1832 elif y_locator == []: … … 1681 1848 from sage.symbolic.ring import SR 1682 1849 #---------------------- Formatting x-ticks ----------------------# 1683 1850 if x_formatter is None: 1684 x_formatter = OldScalarFormatter() 1851 if scale[0] == 'log': 1852 x_formatter = LogFormatterMathtext(base=base[0]) 1853 else: 1854 x_formatter = OldScalarFormatter() 1685 1855 elif x_formatter in SR: 1686 1856 from misc import _multiple_of_constant 1687 1857 x_const = x_formatter 1688 1858 x_formatter = FuncFormatter(lambda n,pos: 1689 1859 _multiple_of_constant(n,pos,x_const)) 1690 1860 elif x_formatter == "latex": 1691 x_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n)) 1861 if scale[0] == 'log': 1862 # We need to strip out '\\mathdefault' from the string 1863 x_formatter = FuncFormatter(lambda n,pos: 1864 LogFormatterMathtext(base=base[0])(n,pos).replace( 1865 "\\mathdefault","")) 1866 else: 1867 x_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n)) 1692 1868 #---------------------- Formatting y-ticks ----------------------# 1693 1869 if y_formatter is None: 1694 y_formatter = OldScalarFormatter() 1870 if scale[1] == 'log': 1871 y_formatter = LogFormatterMathtext(base=base[1]) 1872 else: 1873 y_formatter = OldScalarFormatter() 1695 1874 elif y_formatter in SR: 1696 1875 from misc import _multiple_of_constant 1697 1876 y_const = y_formatter 1698 1877 y_formatter = FuncFormatter(lambda n,pos: 1699 1878 _multiple_of_constant(n,pos,y_const)) 1700 1879 elif y_formatter == "latex": 1701 y_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n)) 1880 if scale[1] == 'log': 1881 # We need to strip out '\\mathdefault' from the string 1882 y_formatter = FuncFormatter(lambda n,pos: 1883 LogFormatterMathtext(base=base[1])(n,pos).replace( 1884 "\\mathdefault","")) 1885 else: 1886 y_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n)) 1702 1887 1703 1888 subplot.xaxis.set_major_locator(x_locator) 1704 1889 subplot.yaxis.set_major_locator(y_locator) … … 1717 1902 vgridlinesstyle=None, hgridlinesstyle=None, 1718 1903 show_legend=None, legend_options={}, 1719 1904 axes_pad=0.02, ticks_integer=None, 1720 tick_formatter=None, ticks=None): 1905 tick_formatter=None, ticks=None, 1906 base=None, scale=None): 1721 1907 r""" 1722 1908 Return a matplotlib figure object representing the graphic 1723 1909 … … 1811 1997 if hasattr(g, '_bbox_extra_artists'): 1812 1998 self._bbox_extra_artists.extend(g._bbox_extra_artists) 1813 1999 1814 #add the legend if requested 2000 #--------------------------- Set the scale -----------------------# 2001 xscale, yscale, basex, basey = self._set_scale(figure, scale=scale, 2002 base=base) 2003 2004 #-------------------------- Set the legend -----------------------# 1815 2005 if show_legend is None: 1816 2006 show_legend = self._show_legend 1817 2007 … … 1833 2023 1834 2024 1835 2025 subplot.set_xlim([xmin, xmax]) 1836 subplot.set_ylim([ymin, ymax])2026 subplot.set_ylim([ymin, ymax]) 1837 2027 1838 2028 locator_options=dict(nbins=9,steps=[1,2,5,10],integer=ticks_integer) 1839 2029 1840 1841 2030 if axes is None: 1842 2031 axes = self._show_axes 1843 2032 … … 1852 2041 # the default one to see if we like it better. 1853 2042 1854 2043 (subplot, x_locator, y_locator, 1855 x_formatter, y_formatter) = self._matplotlib_tick_formatter( 1856 subplot, locator_options=locator_options, 2044 x_formatter, y_formatter) = self._matplotlib_tick_formatter( 2045 subplot, base=(basex, basey), 2046 locator_options=locator_options, 2047 scale=(xscale, yscale), 1857 2048 tick_formatter=tick_formatter, ticks=ticks, 1858 2049 xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin) 1859 2050 1860 2051 subplot.set_frame_on(True) 1861 if axes :1862 if ymin<=0 and ymax>=0:2052 if axes and xscale == 'linear' and yscale == 'linear': 2053 if (ymin<=0 and ymax>=0) or (ymax<=0 and ymin>=0): 1863 2054 subplot.axhline(color=self._axes_color, 1864 2055 linewidth=self._axes_width) 1865 if xmin<=0 and xmax>=0:2056 if (xmin<=0 and xmax>=0) or (xmax<=0 and xmin>=0): 1866 2057 subplot.axvline(color=self._axes_color, 1867 2058 linewidth=self._axes_width) 1868 2059 1869 2060 elif axes: 1870 2061 ymiddle=False 1871 2062 xmiddle=False 1872 if xmin>0: 2063 # Note that the user may specify a custom xmin and xmax which 2064 # flips the axis horizontally. Hence we need to check for both 2065 # the possibilities in the if statements below. Similar 2066 # comments hold for ymin and ymax. 2067 if xscale == 'log': 2068 if xmax > xmin: 2069 subplot.spines['right'].set_visible(False) 2070 subplot.spines['left'].set_position(('outward',10)) 2071 subplot.yaxis.set_ticks_position('left') 2072 subplot.yaxis.set_label_position('left') 2073 yaxis='left' 2074 elif xmax < xmin: 2075 subplot.spines['left'].set_visible(False) 2076 subplot.spines['right'].set_position(('outward',10)) 2077 subplot.yaxis.set_ticks_position('right') 2078 subplot.yaxis.set_label_position('right') 2079 yaxis='right' 2080 elif (xmin > 0 and xmax > xmin) or (xmax > 0 and xmin > xmax): 1873 2081 subplot.spines['right'].set_visible(False) 1874 2082 subplot.spines['left'].set_position(('outward',10)) 1875 2083 subplot.yaxis.set_ticks_position('left') 1876 2084 subplot.yaxis.set_label_position('left') 1877 2085 yaxis='left' 1878 elif xmax<0:2086 elif (xmax < 0 and xmax > xmin) or (xmin < 0 and xmin > xmax): 1879 2087 subplot.spines['left'].set_visible(False) 1880 2088 subplot.spines['right'].set_position(('outward',10)) 1881 2089 subplot.yaxis.set_ticks_position('right') … … 1889 2097 ymiddle=True 1890 2098 yaxis='left' 1891 2099 1892 if ymin>0: 2100 if yscale == 'log': 2101 if ymax > ymin: 2102 subplot.spines['top'].set_visible(False) 2103 subplot.spines['bottom'].set_position(('outward',10)) 2104 subplot.xaxis.set_ticks_position('bottom') 2105 subplot.xaxis.set_label_position('bottom') 2106 xaxis='bottom' 2107 elif ymax < ymin: 2108 subplot.spines['bottom'].set_visible(False) 2109 subplot.spines['top'].set_position(('outward',10)) 2110 subplot.xaxis.set_ticks_position('top') 2111 subplot.xaxis.set_label_position('top') 2112 xaxis='top' 2113 elif (ymin > 0 and ymax > ymin) or (ymax > 0 and ymin > ymax): 1893 2114 subplot.spines['top'].set_visible(False) 1894 2115 subplot.spines['bottom'].set_position(('outward',10)) 1895 2116 subplot.xaxis.set_ticks_position('bottom') 1896 2117 subplot.xaxis.set_label_position('bottom') 1897 2118 xaxis='bottom' 1898 elif ymax<0:2119 elif (ymax < 0 and ymax > ymin) or (ymin < 0 and ymin > ymax): 1899 2120 subplot.spines['bottom'].set_visible(False) 1900 2121 subplot.spines['top'].set_position(('outward',10)) 1901 2122 subplot.xaxis.set_ticks_position('top') … … 1914 2135 # the default one to see if we like it better. 1915 2136 1916 2137 (subplot, x_locator, y_locator, 1917 x_formatter, y_formatter) = self._matplotlib_tick_formatter( 1918 subplot, locator_options=locator_options, 2138 x_formatter, y_formatter) = self._matplotlib_tick_formatter( 2139 subplot, base=(basex, basey), 2140 locator_options=locator_options, 2141 scale=(xscale, yscale), 1919 2142 tick_formatter=tick_formatter, ticks=ticks, 1920 2143 xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin) 1921 2144 … … 1937 2160 # t.set_markersize(4) 1938 2161 1939 2162 # Make the zero tick labels disappear if the axes cross 1940 # inside the picture 1941 if xmiddle and ymiddle: 2163 # inside the picture, but only if log scale is not used 2164 if (xmiddle and ymiddle and xscale == 'linear' and 2165 yscale == 'linear'): 1942 2166 from sage.plot.plot import SelectiveFormatter 1943 2167 subplot.yaxis.set_major_formatter(SelectiveFormatter( 1944 subplot.yaxis.get_major_formatter(), skip_values=[0]))2168 subplot.yaxis.get_major_formatter(), skip_values=[0])) 1945 2169 subplot.xaxis.set_major_formatter(SelectiveFormatter( 1946 subplot.xaxis.get_major_formatter(), skip_values=[0]))2170 subplot.xaxis.get_major_formatter(), skip_values=[0])) 1947 2171 1948 2172 else: 1949 2173 for spine in subplot.spines.values(): … … 1956 2180 1957 2181 if frame or axes: 1958 2182 # Make minor tickmarks, unless we specify fixed ticks or no ticks 1959 from matplotlib.ticker import AutoMinorLocator, FixedLocator, NullLocator 2183 # We do this change only on linear scale, otherwise matplotlib 2184 # errors out with a memory error. 2185 from matplotlib.ticker import (AutoMinorLocator, FixedLocator, 2186 LogLocator, NullLocator) 1960 2187 if isinstance(x_locator, (NullLocator, FixedLocator)): 1961 2188 subplot.xaxis.set_minor_locator(NullLocator()) 1962 el se:2189 elif xscale == 'linear': 1963 2190 subplot.xaxis.set_minor_locator(AutoMinorLocator()) 2191 else: # log scale 2192 from sage.misc.misc import srange 2193 base_inv = 1.0/basex 2194 subs = map(float, srange(2*base_inv, 1, base_inv)) 2195 subplot.xaxis.set_minor_locator(LogLocator(base=basex, 2196 subs=subs)) 1964 2197 if isinstance(y_locator, (NullLocator, FixedLocator)): 1965 2198 subplot.yaxis.set_minor_locator(NullLocator()) 1966 el se:2199 elif yscale == 'linear': 1967 2200 subplot.yaxis.set_minor_locator(AutoMinorLocator()) 1968 1969 ticklabels=subplot.xaxis.get_majorticklabels() + \ 1970 subplot.xaxis.get_minorticklabels() + \ 1971 subplot.yaxis.get_majorticklabels() + \ 1972 subplot.yaxis.get_minorticklabels() 1973 for ticklabel in ticklabels: 1974 ticklabel.set_fontsize(self._fontsize) 1975 ticklabel.set_color(self._tick_label_color) 1976 1977 ticklines=subplot.xaxis.get_majorticklines() + \ 1978 subplot.xaxis.get_minorticklines() + \ 1979 subplot.yaxis.get_majorticklines() + \ 1980 subplot.yaxis.get_minorticklines() 1981 for tickline in ticklines: 1982 tickline.set_color(self._axes_color) 2201 else: # log scale 2202 from sage.misc.misc import srange 2203 base_inv = 1.0/basey 2204 subs = map(float, srange(2*base_inv, 1, base_inv)) 2205 subplot.yaxis.set_minor_locator(LogLocator(base=basey, 2206 subs=subs)) 2207 2208 # Set the color and fontsize of ticks 2209 figure.get_axes()[0].tick_params(color=self._axes_color, 2210 labelcolor=self._tick_label_color, 2211 labelsize=self._fontsize, which='both') 1983 2212 1984 2213 1985 2214 if gridlines is not None: