Ticket #4529: trac_4529-patch1.patch

File trac_4529-patch1.patch, 22.9 KB (added by kcrisman, 9 years ago)
  • sage/plot/graphics.py

    # HG changeset patch
    # User Punarbasu Purkayastha <ppurka@gmail.com>
    # Date 1338111511 -28800
    # Node ID 35ef31cc28aa5c31bda29843638d8898ba4fc5e2
    # Parent  b122be2fae06f42c2e236cb63aba573d5abe42a9
    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  
    99AUTHORS:
    1010
    1111- 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`)
    1213
    1314"""
    1415
     
    117118        ...        G+=line(l,color=hue(c + p*(x/h)))
    118119        sage: G.show(figsize=[5,5])
    119120
     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
    120126    TESTS:
    121127
    122128    From :trac:`4604`, ensure Graphics can handle 3d objects::
     
    843849       
    844850            sage: show_default(True)
    845851        """
    846         pr, i = '', 0
    847         for x in self:
    848             pr += '\n\t%s -- %s'%(i, x)
    849             i += 1
    850852        s = "Graphics object consisting of %s graphics primitives"%(len(self))
    851853        if len(self) == 1:
    852854            s = s[:-1]
     
    10261028        elif other.aspect_ratio()=='automatic':
    10271029            g.set_aspect_ratio(self.aspect_ratio())
    10281030        else:
    1029             g.set_aspect_ratio( max(self.aspect_ratio(), other.aspect_ratio()))
     1031            g.set_aspect_ratio(max(self.aspect_ratio(), other.aspect_ratio()))
    10301032        return g
    10311033 
    10321034    def add_primitive(self, primitive):
     
    11231125        """
    11241126        self._extra_kwds = kwds
    11251127
     1128    def _set_scale(self, figure, scale=None, base=None):
     1129        """
     1130        Set the scale of the axes in the current figure. This function is
     1131        only for internal use.
     1132
     1133        INPUT:
     1134        - ``figure`` -- the matplotlib figure instance.
     1135        - ``scale`` -- the scale of the figure. Values it can take are
     1136          `"linear"`, `"loglog"`, `"semilogx"`, `"semilogy"`. See
     1137          :meth:`show` for other options it can take.
     1138        - ``base`` -- the base of the logarithm if a logarithmic scale is
     1139          set. See :meth:`show` for the options it can take.
     1140
     1141        OUTPUT:
     1142        The scale in the form of a tuple: (xscale, yscale, basex, basey)
     1143
     1144        EXAMPLES::
     1145
     1146            sage: p = plot(x,1,10)
     1147            sage: fig = p.matplotlib()
     1148            sage: p._set_scale(fig, scale='linear', base=2)
     1149            ('linear', 'linear', 10, 10)
     1150            sage: p._set_scale(fig, scale='semilogy', base=2)
     1151            ('linear', 'log', 10, 2)
     1152            sage: p._set_scale(fig, scale=('loglog', 2, 3))
     1153            ('log', 'log', 2, 3)
     1154            sage: p._set_scale(fig, scale=['semilogx', 2])
     1155            ('log', 'linear', 2, 10)
     1156
     1157        TESTS::
     1158
     1159            sage: p._set_scale(fig, 'log')
     1160            Traceback (most recent call last):
     1161            ...
     1162            ValueError: The scale must be one of 'linear', 'loglog', 'semilogx' or 'semilogy' -- got 'log'
     1163            sage: p._set_scale(fig, ('loglog', 1))
     1164            Traceback (most recent call last):
     1165            ...
     1166            ValueError: The base of the logarithm must be greater than 1
     1167        """
     1168        if scale is None:
     1169            return ('linear', 'linear', 10, 10)
     1170        if isinstance(scale, (list, tuple)):
     1171            if len(scale) != 2 and len(scale) != 3:
     1172                raise ValueError("If the input is a tuple, it must be of "
     1173                    "the form (scale, base) or (scale, basex, basey)")
     1174            if len(scale) == 2:
     1175                base = scale[1]
     1176            else:
     1177                base = scale[1:]
     1178            scale = scale[0]
     1179
     1180        if scale not in ('linear', 'loglog', 'semilogx', 'semilogy'):
     1181            raise ValueError("The scale must be one of 'linear', 'loglog',"
     1182                    " 'semilogx' or 'semilogy' -- got '{0}'".format(scale))
     1183
     1184        if isinstance(base, (list, tuple)):
     1185            basex, basey = base
     1186        elif base is None:
     1187            basex = basey = 10
     1188        else:
     1189            basex = basey = base
     1190
     1191        if basex <= 1 or basey <= 1:
     1192            raise ValueError("The base of the logarithm must be greater "
     1193                             "than 1")
     1194
     1195        ax = figure.get_axes()[0]
     1196        xscale = yscale = 'linear'
     1197        if scale == 'linear':
     1198            basex = basey = 10
     1199        elif scale == 'loglog':
     1200            ax.set_xscale('log', basex=basex)
     1201            ax.set_yscale('log', basey=basey)
     1202            xscale = yscale = 'log'
     1203        elif scale == 'semilogx':
     1204            ax.set_xscale('log', basex=basex)
     1205            basey = 10
     1206            xscale = 'log'
     1207        elif scale == 'semilogy':
     1208            ax.set_yscale('log', basey=basey)
     1209            basex = 10
     1210            yscale = 'log'
     1211
     1212        return (xscale, yscale, basex, basey)
     1213
     1214
    11261215    # This dictionary has the default values for the keywords to show(). When
    11271216    # show is invoked with keyword arguments, those arguments are merged with
    11281217    # this dictionary to create a set of keywords with the defaults filled in.
     
    11341223    SHOW_OPTIONS = dict(filename=None,
    11351224                        # axes options
    11361225                        axes=None, axes_labels=None, axes_pad=.02,
     1226                        base=None, scale=None,
    11371227                        xmin=None, xmax=None, ymin=None, ymax=None,
    11381228                        # Figure options
    11391229                        aspect_ratio=None, dpi=DEFAULT_DPI, fig_tight=True,
     
    12991389
    13001390        - ``legend_*`` - all the options valid for :meth:`set_legend_options` prefixed with ``legend_``
    13011391
     1392        - ``base`` - (default: 10) the base of the logarithm if
     1393          a logarithmic scale is set. This must be greater than 1. The base
     1394          can be also given as a list or tuple ``(basex, basey)``.
     1395          ``basex`` sets the base of the logarithm along the horizontal
     1396          axis and ``basey`` sets the base along the vertical axis.
     1397
     1398        - ``scale`` -- (default: `"linear"`) string. The scale of the axes.
     1399          Possible values are
     1400
     1401          - `"linear"` -- linear scaling of both the axes
     1402          - `"loglog"` -- sets both the horizontal and vertical axes to
     1403            logarithmic scale
     1404          - `"semilogx"` -- sets only the horizontal axis to logarithmic
     1405            scale.
     1406          - `"semilogy"` -- sets only the vertical axis to logarithmic
     1407            scale.
     1408
     1409          The scale can be also be given as single argument that is a list
     1410          or tuple ``(scale, base)`` or ``(scale, basex, basey)``.
     1411
     1412          Note: If the ``scale`` is `"linear"`, then irrespective of what
     1413          ``base`` is set to, it will default to 10 and will remain unused.
     1414
    13021415        EXAMPLES::
    13031416       
    13041417            sage: c = circle((1,1), 1, color='red')
     
    13251438
    13261439            sage: plot(sin(x), (x, -4, 4), transparent=True)
    13271440
     1441        We can change the scale of the axes in the graphics before
     1442        displaying::
     1443
     1444            sage: G = plot(exp, 1, 10)
     1445            sage: G.show(scale='semilogy')
     1446
     1447        We can change the base of the logarithm too. The following changes
     1448        the vertical axis to be on log scale, and with base 2. Note that
     1449        the ``base`` argument will ignore any changes to the axis which is
     1450        in linear scale.::
     1451
     1452            sage: G.show(scale='semilogy', base=2) # y axis as powers of 2
     1453
     1454            sage: G.show(scale='semilogy', base=(3,2)) # base ignored for x-axis
     1455
     1456        The scale can be also given as a 2-tuple or a 3-tuple.::
     1457
     1458            sage: G.show(scale=('loglog', 2)) # both x and y axes in base 2
     1459
     1460            sage: G.show(scale=('loglog', 2, 3)) # x in base 2, y in base 3
     1461
     1462        The base need not be an integer.::
     1463
     1464            sage: G.show(scale='semilogy', base=float(e)) # base is e
     1465
     1466        Logarithmic scale can be used for various kinds of plots. Here are
     1467        some examples.::
     1468
     1469            sage: G = list_plot(map(lambda i: 10**i, range(10)))
     1470            sage: G.show(scale='semilogy')
     1471
     1472            sage: G = parametric_plot((x, x**2), (x, 1, 10))
     1473            sage: G.show(scale='loglog')
     1474
     1475            sage: G = arc((2,3), 2, 1, angle=pi/2, sector=(0,pi/2))
     1476            sage: G.show(scale=('loglog', 2))
     1477
     1478            sage: disk((0.1,0.1), 1, (pi/3, pi/2)).show(scale='loglog')
     1479
     1480            sage: polygon([(1,1), (10,10), (10,1)]).show(scale='loglog')
     1481
     1482            sage: x, y = var('x, y')
     1483            sage: G =  plot_vector_field((sin(x),cos(y)),(x,0.1,3),(y,0.1,3))
     1484            sage: G.show(scale='loglog')
     1485
    13281486        Add grid lines at the major ticks of the axes.
    13291487       
    13301488        ::
     
    16441802            ymax += 1
    16451803        return {'xmin':xmin, 'xmax':xmax, 'ymin':ymin, 'ymax':ymax}
    16461804
    1647     def _matplotlib_tick_formatter(self, subplot, locator_options={},
     1805    def _matplotlib_tick_formatter(self, subplot, base=(10, 10),
     1806                            locator_options={}, scale=('linear', 'linear'),
    16481807                            tick_formatter=(None, None), ticks=(None, None),
    16491808                            xmax=None, xmin=None, ymax=None, ymin=None):
    16501809        r"""
     
    16691828        """
    16701829        # This function is created to refactor some code that is repeated
    16711830        # in the matplotlib function
    1672         from matplotlib.ticker import (FixedLocator, Locator, MaxNLocator,
     1831        from matplotlib.ticker import (FixedLocator, Locator,
     1832                LogFormatterMathtext, LogLocator, MaxNLocator,
    16731833                MultipleLocator, NullLocator, OldScalarFormatter)
    16741834
    16751835        x_locator, y_locator = ticks
    16761836        #---------------------- Location of x-ticks ---------------------#
     1837
    16771838        if x_locator is None:
    1678             x_locator = MaxNLocator(**locator_options)
     1839            if scale[0] == 'log':
     1840                x_locator = LogLocator(base=base[0])
     1841            else:
     1842                x_locator = MaxNLocator(**locator_options)
    16791843        elif isinstance(x_locator,Locator):
    16801844            pass
    16811845        elif x_locator == []:
     
    16931857
    16941858        #---------------------- Location of y-ticks ---------------------#
    16951859        if y_locator is None:
    1696             y_locator = MaxNLocator(**locator_options)
     1860            if scale[1] == 'log':
     1861                y_locator = LogLocator(base=base[1])
     1862            else:
     1863                y_locator = MaxNLocator(**locator_options)
    16971864        elif isinstance(y_locator,Locator):
    16981865            pass
    16991866        elif y_locator == []:
     
    17151882        from sage.symbolic.ring import SR
    17161883        #---------------------- Formatting x-ticks ----------------------#
    17171884        if x_formatter is None:
    1718             x_formatter = OldScalarFormatter()
     1885            if scale[0] == 'log':
     1886                x_formatter = LogFormatterMathtext(base=base[0])
     1887            else:
     1888                x_formatter = OldScalarFormatter()
    17191889        elif x_formatter in SR:
    17201890            from misc import _multiple_of_constant
    17211891            x_const = x_formatter
    17221892            x_formatter = FuncFormatter(lambda n,pos:
    17231893                                        _multiple_of_constant(n,pos,x_const))
    17241894        elif x_formatter == "latex":
    1725             x_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n))
     1895            if scale[0] == 'log':
     1896                # We need to strip out '\\mathdefault' from the string
     1897                x_formatter = FuncFormatter(lambda n,pos:
     1898                    LogFormatterMathtext(base=base[0])(n,pos).replace(
     1899                                                        "\\mathdefault",""))
     1900            else:
     1901                x_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n))
    17261902        #---------------------- Formatting y-ticks ----------------------#
    17271903        if y_formatter is None:
    1728             y_formatter = OldScalarFormatter()
     1904            if scale[1] == 'log':
     1905                y_formatter = LogFormatterMathtext(base=base[1])
     1906            else:
     1907                y_formatter = OldScalarFormatter()
    17291908        elif y_formatter in SR:
    17301909            from misc import _multiple_of_constant
    17311910            y_const = y_formatter
    17321911            y_formatter = FuncFormatter(lambda n,pos:
    17331912                                        _multiple_of_constant(n,pos,y_const))
    17341913        elif y_formatter == "latex":
    1735             y_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n))
     1914            if scale[1] == 'log':
     1915                # We need to strip out '\\mathdefault' from the string
     1916                y_formatter = FuncFormatter(lambda n,pos:
     1917                    LogFormatterMathtext(base=base[1])(n,pos).replace(
     1918                                                        "\\mathdefault",""))
     1919            else:
     1920                y_formatter = FuncFormatter(lambda n,pos: '$%s$'%latex(n))
    17361921
    17371922        subplot.xaxis.set_major_locator(x_locator)
    17381923        subplot.yaxis.set_major_locator(y_locator)
     
    17511936                   vgridlinesstyle=None, hgridlinesstyle=None,
    17521937                   show_legend=None, legend_options={},
    17531938                   axes_pad=0.02, ticks_integer=None,
    1754                    tick_formatter=None, ticks=None):
     1939                   tick_formatter=None, ticks=None,
     1940                   base=None, scale=None):
    17551941        r"""
    17561942        Return a matplotlib figure object representing the graphic
    17571943
     
    18532039            g._render_on_subplot(subplot)
    18542040            if hasattr(g, '_bbox_extra_artists'):
    18552041                self._bbox_extra_artists.extend(g._bbox_extra_artists)
    1856        
    1857         #add the legend if requested
     2042
     2043        #--------------------------- Set the scale -----------------------#
     2044        xscale, yscale, basex, basey = self._set_scale(figure, scale=scale,
     2045                                                       base=base)
     2046
     2047        #-------------------------- Set the legend -----------------------#
    18582048        if show_legend is None:
    18592049            show_legend = self._show_legend
    1860        
     2050
    18612051        if show_legend:
    18622052            from matplotlib.font_manager import FontProperties
    18632053            lopts = dict()
     
    18812071       
    18822072           
    18832073        subplot.set_xlim([xmin, xmax])
    1884         subplot.set_ylim([ymin,ymax])
     2074        subplot.set_ylim([ymin, ymax])
    18852075
    18862076        locator_options=dict(nbins=9,steps=[1,2,5,10],integer=ticks_integer)
    18872077
    1888 
    18892078        if axes is None:
    18902079            axes = self._show_axes
    18912080
     
    19002089            # the default one to see if we like it better.
    19012090
    19022091            (subplot, x_locator, y_locator,
    1903                     x_formatter, y_formatter) = self._matplotlib_tick_formatter(
    1904                             subplot, locator_options=locator_options,
     2092                x_formatter, y_formatter) = self._matplotlib_tick_formatter(
     2093                            subplot, base=(basex, basey),
     2094                            locator_options=locator_options,
     2095                            scale=(xscale, yscale),
    19052096                            tick_formatter=tick_formatter, ticks=ticks,
    19062097                            xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin)
    19072098           
    19082099            subplot.set_frame_on(True)
    1909             if axes:
    1910                 if ymin<=0 and ymax>=0:
     2100            if axes and xscale == 'linear' and yscale == 'linear':
     2101                if (ymin<=0 and ymax>=0) or (ymax<=0 and ymin>=0):
    19112102                    subplot.axhline(color=self._axes_color,
    19122103                                    linewidth=self._axes_width)
    1913                 if xmin<=0 and xmax>=0:
     2104                if (xmin<=0 and xmax>=0) or (xmax<=0 and xmin>=0):
    19142105                    subplot.axvline(color=self._axes_color,
    19152106                                    linewidth=self._axes_width)
    1916            
     2107
    19172108        elif axes:
    19182109            ymiddle=False
    19192110            xmiddle=False
    1920             if xmin>0:
     2111            # Note that the user may specify a custom xmin and xmax which
     2112            # flips the axis horizontally. Hence we need to check for both
     2113            # the possibilities in the if statements below. Similar
     2114            # comments hold for ymin and ymax.
     2115            if xscale == 'log':
     2116                if xmax > xmin:
     2117                    subplot.spines['right'].set_visible(False)
     2118                    subplot.spines['left'].set_position(('outward',10))
     2119                    subplot.yaxis.set_ticks_position('left')
     2120                    subplot.yaxis.set_label_position('left')
     2121                    yaxis='left'
     2122                elif xmax < xmin:
     2123                    subplot.spines['left'].set_visible(False)
     2124                    subplot.spines['right'].set_position(('outward',10))
     2125                    subplot.yaxis.set_ticks_position('right')
     2126                    subplot.yaxis.set_label_position('right')
     2127                    yaxis='right'
     2128            elif (xmin > 0 and xmax > xmin) or (xmax > 0 and xmin > xmax):
    19212129                subplot.spines['right'].set_visible(False)
    19222130                subplot.spines['left'].set_position(('outward',10))
    19232131                subplot.yaxis.set_ticks_position('left')
    19242132                subplot.yaxis.set_label_position('left')
    19252133                yaxis='left'
    1926             elif xmax<0:
     2134            elif (xmax < 0 and xmax > xmin) or (xmin < 0 and xmin > xmax):
    19272135                subplot.spines['left'].set_visible(False)
    19282136                subplot.spines['right'].set_position(('outward',10))
    19292137                subplot.yaxis.set_ticks_position('right')
     
    19372145                ymiddle=True
    19382146                yaxis='left'
    19392147
    1940             if ymin>0:
     2148            if yscale == 'log':
     2149                if ymax > ymin:
     2150                    subplot.spines['top'].set_visible(False)
     2151                    subplot.spines['bottom'].set_position(('outward',10))
     2152                    subplot.xaxis.set_ticks_position('bottom')
     2153                    subplot.xaxis.set_label_position('bottom')
     2154                    xaxis='bottom'
     2155                elif ymax < ymin:
     2156                    subplot.spines['bottom'].set_visible(False)
     2157                    subplot.spines['top'].set_position(('outward',10))
     2158                    subplot.xaxis.set_ticks_position('top')
     2159                    subplot.xaxis.set_label_position('top')
     2160                    xaxis='top'
     2161            elif (ymin > 0 and ymax > ymin) or (ymax > 0 and ymin > ymax):
    19412162                subplot.spines['top'].set_visible(False)
    19422163                subplot.spines['bottom'].set_position(('outward',10))
    19432164                subplot.xaxis.set_ticks_position('bottom')
    19442165                subplot.xaxis.set_label_position('bottom')
    19452166                xaxis='bottom'
    1946             elif ymax<0:
     2167            elif (ymax < 0 and ymax > ymin) or (ymin < 0 and ymin > ymax):
    19472168                subplot.spines['bottom'].set_visible(False)
    19482169                subplot.spines['top'].set_position(('outward',10))
    19492170                subplot.xaxis.set_ticks_position('top')
     
    19622183            # the default one to see if we like it better.
    19632184
    19642185            (subplot, x_locator, y_locator,
    1965                     x_formatter, y_formatter) = self._matplotlib_tick_formatter(
    1966                             subplot, locator_options=locator_options,
     2186                x_formatter, y_formatter) = self._matplotlib_tick_formatter(
     2187                            subplot, base=(basex, basey),
     2188                            locator_options=locator_options,
     2189                            scale=(xscale, yscale),
    19672190                            tick_formatter=tick_formatter, ticks=ticks,
    19682191                            xmax=xmax, xmin=xmin, ymax=ymax, ymin=ymin)
    19692192           
     
    19852208            #                     t.set_markersize(4)
    19862209           
    19872210            # Make the zero tick labels disappear if the axes cross
    1988             # inside the picture
    1989             if xmiddle and ymiddle:
     2211            # inside the picture, but only if log scale is not used
     2212            if (xmiddle and ymiddle and xscale == 'linear' and
     2213                yscale == 'linear'):
    19902214                from sage.plot.plot import SelectiveFormatter
    19912215                subplot.yaxis.set_major_formatter(SelectiveFormatter(
    1992                     subplot.yaxis.get_major_formatter(),skip_values=[0]))
     2216                    subplot.yaxis.get_major_formatter(), skip_values=[0]))
    19932217                subplot.xaxis.set_major_formatter(SelectiveFormatter(
    1994                     subplot.xaxis.get_major_formatter(),skip_values=[0]))
     2218                    subplot.xaxis.get_major_formatter(), skip_values=[0]))
    19952219
    19962220        else:
    19972221            for spine in subplot.spines.values():
     
    20042228
    20052229        if frame or axes:
    20062230            # Make minor tickmarks, unless we specify fixed ticks or no ticks
    2007             from matplotlib.ticker import AutoMinorLocator, FixedLocator, NullLocator
     2231            # We do this change only on linear scale, otherwise matplotlib
     2232            # errors out with a memory error.
     2233            from matplotlib.ticker import (AutoMinorLocator, FixedLocator,
     2234                    LogLocator, NullLocator)
    20082235            if isinstance(x_locator, (NullLocator, FixedLocator)):
    20092236                subplot.xaxis.set_minor_locator(NullLocator())
    2010             else:
     2237            elif xscale == 'linear':
    20112238                subplot.xaxis.set_minor_locator(AutoMinorLocator())
     2239            else: # log scale
     2240                from sage.misc.misc import srange
     2241                base_inv = 1.0/basex
     2242                subs = map(float, srange(2*base_inv, 1, base_inv))
     2243                subplot.xaxis.set_minor_locator(LogLocator(base=basex,
     2244                                                           subs=subs))
    20122245            if isinstance(y_locator, (NullLocator, FixedLocator)):
    20132246                subplot.yaxis.set_minor_locator(NullLocator())
    2014             else:
     2247            elif yscale == 'linear':
    20152248                subplot.yaxis.set_minor_locator(AutoMinorLocator())
    2016 
    2017             ticklabels=subplot.xaxis.get_majorticklabels() + \
    2018                 subplot.xaxis.get_minorticklabels() + \
    2019                 subplot.yaxis.get_majorticklabels() + \
    2020                 subplot.yaxis.get_minorticklabels()
    2021             for ticklabel in ticklabels:
    2022                 ticklabel.set_fontsize(self._fontsize)
    2023                 ticklabel.set_color(self._tick_label_color)
    2024 
    2025             ticklines=subplot.xaxis.get_majorticklines() + \
    2026                 subplot.xaxis.get_minorticklines() + \
    2027                 subplot.yaxis.get_majorticklines() + \
    2028                 subplot.yaxis.get_minorticklines()
    2029             for tickline in ticklines:
    2030                 tickline.set_color(self._axes_color)
    2031                
    2032            
     2249            else: # log scale
     2250                from sage.misc.misc import srange
     2251                base_inv = 1.0/basey
     2252                subs = map(float, srange(2*base_inv, 1, base_inv))
     2253                subplot.yaxis.set_minor_locator(LogLocator(base=basey,
     2254                                                           subs=subs))
     2255
     2256            # Set the color and fontsize of ticks
     2257            figure.get_axes()[0].tick_params(color=self._axes_color,
     2258                    labelcolor=self._tick_label_color,
     2259                    labelsize=self._fontsize, which='both')
     2260
     2261
    20332262        if gridlines is not None:
    20342263            if isinstance(gridlines, (list, tuple)):
    20352264                vgridlines,hgridlines=gridlines