Ticket #1431: trac_1431.patch

File trac_1431.patch, 13.4 KB (added by kcrisman, 12 years ago)

Based on 4.2

  • sage/plot/misc.py

    # HG changeset patch
    # User Karl-Dieter Crisman <kcrisman@gmail.com>
    # Date 1258040665 18000
    # Node ID 85754465ba10a3880f3f7c746b73de2692547246
    # Parent  f872a92f5f9afff4b4971afc82bf9270faec269b
    Trac 1431 - allows easier custom ticks and formatting, particularly for multiples of pi
    
    diff -r f872a92f5f9a -r 85754465ba10 sage/plot/misc.py
    a b  
    427427            # we probably have a constant
    428428            pass
    429429    return tuple(sorted(vars, key=lambda x: str(x))), tuple(sorted(free_variables, key=lambda x: str(x)))
     430
     431def multiple_of_pi(n,pos):
     432    """
     433    Function for internal use in formatting ticks on axes with
     434    nice-looking multiples of `\pi`.  Should only be used via
     435    keyword argument `tick_formatter='pi'` in :meth:`plot.show`.
     436    See documentation for the matplotlib.ticker module for more
     437    details.
     438
     439    EXAMPLES:
     440
     441    Here is the intended use::
     442
     443        sage: plot(sin(x), (x,0,2*pi), tick_locator=pi/3,tick_formatter='pi')
     444
     445    Here is an unintended use, which yields unexpected results::
     446
     447        sage: plot(x^2, (x, -2, 2), tick_formatter='pi')
     448
     449    """
     450    from sage.symbolic.constants import pi
     451    from sage.misc.latex import latex
     452    from sage.rings.arith import convergents
     453    c=[i for i in convergents(n/pi) if i.denominator()<12]
     454    return '$%s$'%latex(c[-1]*pi)
  • sage/plot/plot.py

    diff -r f872a92f5f9a -r 85754465ba10 sage/plot/plot.py
    a b  
    109109    sage: plot(x^2,(x,480,500))  # no scientific notation
    110110    sage: plot(x^2,(x,300,500))  # scientific notation on y-axis
    111111
     112But you can fix your own labels, if you know what to expect and
     113have a preference::
     114
     115    sage: plot(x^2,(x,300,500),tick_locator=[None,50000])
     116
    112117Next we construct the reflection of the above polygon about the
    113118`y`-axis by iterating over the list of first-coordinates of
    114119the first graphic element of `P` (which is the actual
     
    190195    sage: g2 = plot(2*exp(-30*x) - exp(-3*x), 0, 1)
    191196    sage: show(graphics_array([g1, g2], 2, 1), xmin=0)
    192197
    193 Pi Axis: In the PyX manual, the point of this example is to show
    194 labeling the X-axis using rational multiples of Pi. Sage currently
    195 has no support for controlling how the ticks on the x and y axes
    196 are labeled, so this is really a bad example::
     198Pi Axis::
    197199
    198200    sage: g1 = plot(sin(x), 0, 2*pi)
    199201    sage: g2 = plot(cos(x), 0, 2*pi, linestyle = "--")
    200     sage: g1 + g2    # show their sum
     202    sage: (g1+g2).show(tick_locator=pi/6, tick_formatter='pi')  # show their sum, nicely formatted
    201203
    202204An illustration of integration::
    203205
     
    11791181                        fontsize=None, aspect_ratio=None,
    11801182                        gridlines=None, gridlinesstyle=None,
    11811183                        vgridlinesstyle=None, hgridlinesstyle=None,transparent=False,
    1182                         axes_pad=.02)
     1184                        axes_pad=.02, tick_locator=None, tick_formatter=None)
    11831185
    11841186    def show(self, **kwds):
    11851187        """
     
    12541256          etc.  To get axes that are exactly the specified limits, set
    12551257          ``axes_pad`` to zero.
    12561258
     1259        - ``tick_locator`` - A matplotlib locator for the major ticks, or
     1260          a number. There are several options.  For more information about
     1261          locators, type ``from matplotlib import ticker`` and then
     1262          ``ticker?``.
     1263
     1264          - If this is a locator object, then it is the locator for
     1265            the horizontal axis.  A value of None means use the default
     1266            locator.
     1267
     1268          - If it is a list of two locators, then the first is for the
     1269            horizontal axis and one for the vertical axis.  A value of
     1270            None means use the default locator (so a value of
     1271            [None, my_locator] uses my_locator for the vertical axis and
     1272            the default for the horizontal axis).
     1273
     1274          - If in either case above one of the entries is a number `m`
     1275            (something which can be coerced to a float), it will be
     1276            replaced by a MultipleLocator which places major ticks at
     1277            integer multiples of `m`.  See examples.
     1278
     1279        - ``tick_formatter`` - A matplotlib formatter for the major
     1280          ticks. There are several options.  For more information about
     1281          formatters, type ``from matplotlib import ticker`` and then
     1282          ``ticker?``.
     1283
     1284          - If this is a formatter object, then it is the formatter for
     1285            the horizontal axis.  A value of None means use the default
     1286            locator.
     1287
     1288          - If it is a list of two formatters, then the first is for the
     1289            horizontal axis and one for the vertical axis.  A value of
     1290            None means use the default formatter (so a value of
     1291            [None, my_formatter] uses my_formatter for the vertical axis
     1292            and the default for the horizontal axis).
     1293
     1294          - If in either case above one of the entries is the string "pi",
     1295            ticks will be formatted nicely at rational multiples of `\pi`.
     1296            This should only be used with a ``tick_locator`` option using
     1297            nice rational multiples of `\pi`!
     1298
     1299
    12571300        EXAMPLES::
    12581301       
    12591302            sage: c = circle((1,1), 1, color='red')
     
    14091452            sage: plot(sin(x), (x, -pi, pi),thickness=2)+point((pi, -1), pointsize=15)
    14101453            sage: plot(sin(x), (x, -pi, pi),thickness=2,axes_pad=0)+point((pi, -1), pointsize=15)
    14111454       
     1455        Via matplotlib, Sage allows setting of custom ticks.  See
     1456        documentation for :meth:`show` for more details.
     1457
     1458        ::
     1459
     1460            sage: plot(sin(pi*x), (x, -8, 8)) # Labels not so helpful
     1461            sage: plot(sin(pi*x), (x, -8, 8), tick_locator=2) # Multiples of 2
     1462            sage: plot(1.5/(1+e^(-x)), (x, -10, 10)) # doesn't quite show value of inflection point
     1463            sage: plot(1.5/(1+e^(-x)), (x, -10, 10), tick_locator=[None, 1.5/4]) # It's right at f(x)=0.75!
     1464
     1465        But be careful to leave enough room for at least two major ticks, so that
     1466        the user can tell what the scale is.
     1467
     1468        ::
     1469
     1470            sage: plot(x^2,(x,1,8),tick_locator=6)
     1471            Traceback (most recent call last):
     1472            ...
     1473            ValueError: Expand the range of the independent variable to allow two multiples of your `tick_locator`.
     1474
     1475        We can also do custom formatting if you need it, with a built-in one for
     1476        multiples of `\pi`.  See documentation for :meth:`show` for more details.
     1477
     1478        ::
     1479
     1480            sage: plot(sin(x),x,0,2*pi,tick_locator=pi/3,tick_formatter='pi')
     1481
    14121482        """
    14131483
    14141484        # This option should not be passed on to save().
     
    15611631              axes=None, axes_labels=None, fontsize=None,
    15621632             frame=False, verify=True, aspect_ratio = None,
    15631633             gridlines=None, gridlinesstyle=None,
    1564              vgridlinesstyle=None, hgridlinesstyle=None,axes_pad=0.02):
     1634             vgridlinesstyle=None, hgridlinesstyle=None,axes_pad=0.02,
     1635             tick_formatter=None, tick_locator=None):
    15651636        r"""
    15661637        Return a matplotlib figure object representing the graphic
    15671638
     
    15841655        :meth:`show` method (this function accepts all except the
    15851656        transparent argument).
    15861657        """
     1658
     1659        if not isinstance(tick_locator, (list, tuple)):
     1660            tick_locator = (tick_locator, None)
     1661
     1662        if not isinstance(tick_formatter, (list, tuple)):
     1663            tick_formatter = (tick_formatter, None)
     1664           
    15871665        self.set_axes_range(xmin, xmax, ymin, ymax)
    15881666        d = self.get_axes_range()
    15891667        xmin = d['xmin']
     
    16541732            # For now, set the formatter to the old one, since that is
    16551733            # sort of what we are used to.  We should eventually look at
    16561734            # the default one to see if we like it better.
    1657             from matplotlib.ticker import OldScalarFormatter, MaxNLocator
    1658             subplot.xaxis.set_major_locator(MaxNLocator(nbins=9))
    1659             subplot.yaxis.set_major_locator(MaxNLocator(nbins=9))
    1660             subplot.xaxis.set_major_formatter(OldScalarFormatter())
    1661             subplot.yaxis.set_major_formatter(OldScalarFormatter())
     1735
     1736            from matplotlib.ticker import OldScalarFormatter, MaxNLocator, MultipleLocator, Locator
     1737            x_locator, y_locator = tick_locator
     1738            if x_locator is None:
     1739                x_locator = MaxNLocator(nbins=9)
     1740            elif isinstance(x_locator,Locator):
     1741                pass
     1742            else: # x_locator is a number which can be made a float
     1743                from sage.functions.other import ceil, floor
     1744                if floor(xmax/x_locator)-ceil(xmin/x_locator)>1:
     1745                    x_locator=MultipleLocator(float(x_locator))
     1746                else: # not enough room for two major ticks
     1747                    raise ValueError('Expand the range of the independent variable to allow two multiples of your `tick_locator`.')
     1748            if y_locator is None:
     1749                y_locator = MaxNLocator(nbins=9)
     1750            elif isinstance(y_locator,Locator):
     1751                pass
     1752            else: # y_locator is a number which can be made a float
     1753                from sage.functions.other import ceil, floor
     1754                if floor(ymax/y_locator)-ceil(ymin/y_locator)>1:
     1755                    y_locator=MultipleLocator(float(y_locator))
     1756                else: # not enough room for two major ticks
     1757                    raise ValueError('Expand the range of the dependent variable to allow two multiples of your `tick_locator`.')
     1758
     1759            x_formatter, y_formatter = tick_formatter
     1760            if x_formatter is None:
     1761                x_formatter = OldScalarFormatter()
     1762            elif x_formatter=='pi':
     1763                from misc import multiple_of_pi
     1764                from matplotlib.ticker import FuncFormatter
     1765                x_formatter = FuncFormatter(multiple_of_pi)
     1766            if y_formatter is None:
     1767                y_formatter = OldScalarFormatter()
     1768            elif y_formatter=='pi':
     1769                from misc import multiple_of_pi
     1770                from matplotlib.ticker import FuncFormatter
     1771                y_formatter = FuncFormatter(multiple_of_pi)
     1772
     1773            subplot.xaxis.set_major_locator(x_locator)
     1774            subplot.yaxis.set_major_locator(y_locator)
     1775            subplot.xaxis.set_major_formatter(x_formatter)
     1776            subplot.yaxis.set_major_formatter(y_formatter)
    16621777           
    16631778            subplot.set_frame_on(True)
    16641779            if axes:
     
    17151830            # For now, set the formatter to the old one, since that is
    17161831            # sort of what we are used to.  We should eventually look at
    17171832            # the default one to see if we like it better.
    1718             from matplotlib.ticker import OldScalarFormatter, MaxNLocator
    1719             subplot.xaxis.set_major_locator(MaxNLocator(nbins=10,steps=[1,2,5,10]))
    1720             subplot.yaxis.set_major_locator(MaxNLocator(nbins=10,steps=[1,2,5,10]))
    1721             subplot.xaxis.set_major_formatter(OldScalarFormatter())
    1722             subplot.yaxis.set_major_formatter(OldScalarFormatter())
     1833           
     1834            from matplotlib.ticker import OldScalarFormatter, MaxNLocator, MultipleLocator, Locator
     1835            x_locator, y_locator = tick_locator
     1836            if x_locator is None:
     1837                x_locator = MaxNLocator(nbins=9, steps=[1,2,5,10])
     1838            elif isinstance(x_locator,Locator):
     1839                pass
     1840            else: # x_locator is a number which can be made a float
     1841                from sage.functions.other import ceil, floor
     1842                if floor(xmax/x_locator)-ceil(xmin/x_locator)>1:
     1843                    x_locator=MultipleLocator(float(x_locator))
     1844                else: # not enough room for two major ticks
     1845                    raise ValueError('Expand the range of the independent variable to allow two multiples of your `tick_locator`.')
     1846            if y_locator is None:
     1847                y_locator = MaxNLocator(nbins=9, steps=[1,2,5,10])
     1848            elif isinstance(y_locator,Locator):
     1849                pass
     1850            else: # y_locator is a number which can be made a float
     1851                from sage.functions.other import ceil, floor
     1852                if floor(ymax/y_locator)-ceil(ymin/y_locator)>1:
     1853                    y_locator=MultipleLocator(float(y_locator))
     1854                else: # not enough room for two major ticks
     1855                    raise ValueError('Expand the range of the dependent variable to allow two multiples of your `tick_locator`.')
    17231856
     1857            x_formatter, y_formatter = tick_formatter
     1858            if x_formatter is None:
     1859                x_formatter = OldScalarFormatter()
     1860            elif x_formatter=='pi':
     1861                from misc import multiple_of_pi
     1862                from matplotlib.ticker import FuncFormatter
     1863                x_formatter = FuncFormatter(multiple_of_pi)
     1864            if y_formatter is None:
     1865                y_formatter = OldScalarFormatter()
     1866            elif y_formatter=='pi':
     1867                from misc import multiple_of_pi
     1868                from matplotlib.ticker import FuncFormatter
     1869                y_formatter = FuncFormatter(multiple_of_pi)
     1870           
     1871            subplot.xaxis.set_major_locator(x_locator)
     1872            subplot.yaxis.set_major_locator(y_locator)
     1873            subplot.xaxis.set_major_formatter(x_formatter)
     1874            subplot.yaxis.set_major_formatter(y_formatter)
    17241875
    17251876            # Make ticklines go on both sides of the axes
    17261877            #             if xmiddle: