Revision: 4733
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=4733&view=rev
Author:   mdboom
Date:     2007-12-14 12:07:59 -0800 (Fri, 14 Dec 2007)

Log Message:
-----------
First pass at symmetrical log plots.  Expose xscale() and yscale()
through pyplot.

Modified Paths:
--------------
    branches/transforms/lib/matplotlib/axes.py
    branches/transforms/lib/matplotlib/pyplot.py
    branches/transforms/lib/matplotlib/scale.py
    branches/transforms/lib/matplotlib/ticker.py

Added Paths:
-----------
    branches/transforms/examples/symlog_demo.py

Added: branches/transforms/examples/symlog_demo.py
===================================================================
--- branches/transforms/examples/symlog_demo.py                         (rev 0)
+++ branches/transforms/examples/symlog_demo.py 2007-12-14 20:07:59 UTC (rev 
4733)
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+from pylab import *
+
+dt = 1.0
+x = arange(-50.0, 50.0, dt)
+y = arange(0, 100.0, dt)
+
+subplot(311)
+plot(x, y)
+xscale('symlog')
+ylabel('symlogx')
+grid(True)
+gca().xaxis.grid(True, which='minor')  # minor grid on too
+
+subplot(312)
+plot(y, x)
+yscale('symlog')
+ylabel('symlogy')
+
+
+subplot(313)
+plot(x, npy.sin(x / 3.0))
+xscale('symlog')
+yscale('symlog')
+grid(True)
+ylabel('symlog both')
+
+savefig('log_demo')
+show()


Property changes on: branches/transforms/examples/symlog_demo.py
___________________________________________________________________
Name: svn:executable
   + *

Modified: branches/transforms/lib/matplotlib/axes.py
===================================================================
--- branches/transforms/lib/matplotlib/axes.py  2007-12-14 16:28:34 UTC (rev 
4732)
+++ branches/transforms/lib/matplotlib/axes.py  2007-12-14 20:07:59 UTC (rev 
4733)
@@ -741,8 +741,6 @@
 
         self.xaxis.cla()
         self.yaxis.cla()
-        self.set_xscale('linear')
-        self.set_yscale('linear')
 
        self.ignore_existing_data_limits = True
         self.callbacks = cbook.CallbackRegistry(('xlim_changed', 
'ylim_changed'))
@@ -768,6 +766,8 @@
         self.collections = []  # collection.Collection instances
 
         self._autoscaleon = True
+        self.set_xscale('linear')
+        self.set_yscale('linear')
 
         self.grid(self._gridOn)
         props = font_manager.FontProperties(size=rcParams['axes.titlesize'])
@@ -1654,6 +1654,7 @@
             ", ".join(mscale.get_scale_names()))
        return self.xaxis.get_scale()
 
+    # MGDTODO: Update docstring
     def set_xscale(self, value, **kwargs):
         """
         SET_XSCALE(value)
@@ -1673,6 +1674,7 @@
         ACCEPTS: [%(scale)s]
         """ % {'scale': ' | '.join([repr(x) for x in 
mscale.get_scale_names()])}
         self.xaxis.set_scale(value, **kwargs)
+        self.autoscale_view()
         self._update_transScale()
 
     def get_xticks(self, minor=False):
@@ -1833,6 +1835,7 @@
         ACCEPTS: %(scale)s
         """ % {'scale': ' | '.join([repr(x) for x in 
mscale.get_scale_names()])}
         self.yaxis.set_scale(value, **kwargs)
+        self.autoscale_view()
         self._update_transScale()
 
     def get_yticks(self, minor=False):
@@ -1992,6 +1995,7 @@
             lim           = self.viewLim.frozen(),
             trans         = self.transData.frozen(),
             trans_inverse = self.transData.inverted().frozen(),
+            bbox          = self.bbox.frozen(),
             x             = x,
             y             = y
             )
@@ -2044,9 +2048,11 @@
         p = self._pan_start
         dx = x - p.x
         dy = y - p.y
+        if dx == 0 and dy == 0:
+            return
         if button == 1:
             dx, dy = format_deltas(key, dx, dy)
-            result = self.bbox.frozen().translated(-dx, -dy) \
+            result = p.bbox.translated(-dx, -dy) \
                 .transformed(p.trans_inverse)
         elif button == 3:
             try:

Modified: branches/transforms/lib/matplotlib/pyplot.py
===================================================================
--- branches/transforms/lib/matplotlib/pyplot.py        2007-12-14 16:28:34 UTC 
(rev 4732)
+++ branches/transforms/lib/matplotlib/pyplot.py        2007-12-14 20:07:59 UTC 
(rev 4733)
@@ -12,6 +12,7 @@
 from matplotlib.axes import Axes
 from matplotlib.projections import PolarAxes
 from matplotlib import mlab  # for csv2rec in plotfile
+from matplotlib.scale import get_scale_names
 
 from matplotlib import cm
 from matplotlib.cm import get_cmap
@@ -726,8 +727,32 @@
     return ret
 
 
+# MGDTODO: Update docstring
+def xscale(*args, **kwargs):
+    """
+    SET_XSCALE(value)
 
+    Set the xscaling: %(scale)s
+    """ % {'scale': ' | '.join([repr(x) for x in get_scale_names()])}
+    ax = gca()
+    ret = ax.set_xscale(*args, **kwargs)
+    draw_if_interactive()
+    return ret
 
+
+# MGDTODO: Update docstring
+def yscale(*args, **kwargs):
+    """
+    SET_YSCALE(value)
+
+    Set the yscaling: %(scale)s
+    """ % {'scale': ' | '.join([repr(x) for x in get_scale_names()])}
+    ax = gca()
+    ret = ax.set_yscale(*args, **kwargs)
+    draw_if_interactive()
+    return ret
+
+
 def xticks(*args, **kwargs):
     """
     Set/Get the xlimits of the current ticklocs, labels

Modified: branches/transforms/lib/matplotlib/scale.py
===================================================================
--- branches/transforms/lib/matplotlib/scale.py 2007-12-14 16:28:34 UTC (rev 
4732)
+++ branches/transforms/lib/matplotlib/scale.py 2007-12-14 20:07:59 UTC (rev 
4733)
@@ -3,7 +3,7 @@
 MaskedArray = ma.MaskedArray
 
 from ticker import NullFormatter, ScalarFormatter, LogFormatterMathtext
-from ticker import NullLocator, LogLocator, AutoLocator
+from ticker import NullLocator, LogLocator, AutoLocator, SymmetricalLogLocator
 from transforms import Transform, IdentityTransform
 
 class ScaleBase(object):
@@ -12,10 +12,10 @@
 
     def limit_range_for_scale(self, vmin, vmax, minpos):
         return vmin, vmax
-    
+
 class LinearScale(ScaleBase):
     name = 'linear'
-    
+
     def __init__(self, axis, **kwargs):
         pass
 
@@ -24,7 +24,7 @@
         axis.set_major_formatter(ScalarFormatter())
         axis.set_minor_locator(NullLocator())
         axis.set_minor_formatter(NullFormatter())
-    
+
     def get_transform(self):
         return IdentityTransform()
 
@@ -33,7 +33,7 @@
     if mask.any():
         return ma.MaskedArray(a, mask=mask)
     return a
-    
+
 class LogScale(ScaleBase):
     name = 'log'
 
@@ -41,13 +41,14 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-            
+        base = 10.0
+
         def transform(self, a):
             a = _mask_non_positives(a * 10.0)
             if isinstance(a, MaskedArray):
                 return ma.log10(a)
             return npy.log10(a)
-            
+
         def inverted(self):
             return LogScale.InvertedLog10Transform()
 
@@ -55,7 +56,8 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-            
+        base = 10.0
+
         def transform(self, a):
             return ma.power(10.0, a) / 10.0
 
@@ -66,13 +68,14 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-            
+        base = 2.0
+
         def transform(self, a):
             a = _mask_non_positives(a * 2.0)
             if isinstance(a, MaskedArray):
                 return ma.log2(a)
             return npy.log2(a)
-            
+
         def inverted(self):
             return LogScale.InvertedLog2Transform()
 
@@ -80,7 +83,8 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-            
+        base = 2.0
+
         def transform(self, a):
             return ma.power(2.0, a) / 2.0
 
@@ -91,13 +95,14 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-        
+        base = npy.e
+
         def transform(self, a):
             a = _mask_non_positives(a * npy.e)
             if isinstance(a, MaskedArray):
                 return ma.log(a)
             return npy.log(a)
-            
+
         def inverted(self):
             return LogScale.InvertedNaturalLogTransform()
 
@@ -105,54 +110,55 @@
         input_dims = 1
         output_dims = 1
         is_separable = True
-        
+        base = npy.e
+
         def transform(self, a):
             return ma.power(npy.e, a) / npy.e
 
         def inverted(self):
             return LogScale.Log2Transform()
-        
+
     class LogTransform(Transform):
         input_dims = 1
         output_dims = 1
         is_separable = True
-        
+
         def __init__(self, base):
             Transform.__init__(self)
-            self._base = base
-            
+            self.base = base
+
         def transform(self, a):
-            a = _mask_non_positives(a * self._base)
+            a = _mask_non_positives(a * self.base)
             if isinstance(a, MaskedArray):
-                return ma.log10(a) / npy.log(self._base)
-            return npy.log(a) / npy.log(self._base)
-            
+                return ma.log(a) / npy.log(self.base)
+            return npy.log(a) / npy.log(self.base)
+
         def inverted(self):
-            return LogScale.InvertedLogTransform(self._base)
+            return LogScale.InvertedLogTransform(self.base)
 
     class InvertedLogTransform(Transform):
         input_dims = 1
         output_dims = 1
         is_separable = True
-        
+
         def __init__(self, base):
             Transform.__init__(self)
-            self._base = base
+            self.base = base
 
         def transform(self, a):
-            return ma.power(self._base, a) / self._base
+            return ma.power(self.base, a) / self.base
 
         def inverted(self):
-            return LogScale.LogTransform(self._base)
+            return LogScale.LogTransform(self.base)
 
-        
+
     def __init__(self, axis, **kwargs):
         if axis.axis_name == 'x':
             base = kwargs.pop('basex', 10.0)
-            subs = kwargs.pop('subsx', [])
+            subs = kwargs.pop('subsx', None)
         else:
             base = kwargs.pop('basey', 10.0)
-            subs = kwargs.pop('subsy', [])
+            subs = kwargs.pop('subsy', None)
 
         if base == 10.0:
             self._transform = self.Log10Transform()
@@ -163,25 +169,104 @@
         else:
             self._transform = self.LogTransform(base)
 
-        self._base = base
-        self._subs = subs
+        self.base = base
+        self.subs = subs
 
     def set_default_locators_and_formatters(self, axis):
-        axis.set_major_locator(LogLocator(self._base))
-        axis.set_major_formatter(LogFormatterMathtext(self._base))
-        axis.set_minor_locator(LogLocator(self._base, self._subs))
+        axis.set_major_locator(LogLocator(self.base))
+        axis.set_major_formatter(LogFormatterMathtext(self.base))
+        axis.set_minor_locator(LogLocator(self.base, self.subs))
         axis.set_minor_formatter(NullFormatter())
-            
+
     def get_transform(self):
         return self._transform
 
     def limit_range_for_scale(self, vmin, vmax, minpos):
         return (vmin <= 0.0 and minpos or vmin,
                 vmax <= 0.0 and minpos or vmax)
-    
+
+class SymmetricalLogScale(ScaleBase):
+    name = 'symlog'
+
+    class SymmetricalLogTransform(Transform):
+        input_dims = 1
+        output_dims = 1
+        is_separable = True
+
+        def __init__(self, base, linthresh):
+            Transform.__init__(self)
+            self.base = base
+            self.linthresh = linthresh
+            self._log_base = npy.log(base)
+            self._linadjust = (npy.log(linthresh) / self._log_base) / linthresh
+
+        def transform(self, a):
+            sign = npy.sign(npy.asarray(a))
+            masked = ma.masked_inside(a, -self.linthresh, self.linthresh, 
copy=False)
+            log = sign * ma.log(npy.abs(masked)) / self._log_base
+            if masked.mask.any():
+                return npy.asarray(ma.where(masked.mask,
+                                            a * self._linadjust,
+                                            log))
+            else:
+                return npy.asarray(log)
+
+        def inverted(self):
+            return 
SymmetricalLogScale.InvertedSymmetricalLogTransform(self.base, self.linthresh)
+
+    class InvertedSymmetricalLogTransform(Transform):
+        input_dims = 1
+        output_dims = 1
+        is_separable = True
+
+        def __init__(self, base, linthresh):
+            Transform.__init__(self)
+            self.base = base
+            self.linthresh = linthresh
+            self._log_base = npy.log(base)
+            self._log_linthresh = npy.log(linthresh) / self._log_base
+            self._linadjust = linthresh / (npy.log(linthresh) / self._log_base)
+
+        def transform(self, a):
+            return npy.where(a <= self._log_linthresh,
+                             npy.where(a >= -self._log_linthresh,
+                                       a * self._linadjust,
+                                       -(npy.power(self.base, -a))),
+                             npy.power(self.base, a))
+
+        def inverted(self):
+            return SymmetricalLogScale.SymmetricalLogTransform(self.base)
+
+    def __init__(self, axis, **kwargs):
+        if axis.axis_name == 'x':
+            base = kwargs.pop('basex', 10.0)
+            linthresh = kwargs.pop('linthreshx', 2.0)
+            subs = kwargs.pop('subsx', None)
+        else:
+            base = kwargs.pop('basey', 10.0)
+            linthresh = kwargs.pop('linthreshy', 2.0)
+            subs = kwargs.pop('subsy', None)
+
+        self._transform = self.SymmetricalLogTransform(base, linthresh)
+
+        self.base = base
+        self.linthresh = linthresh
+        self.subs = subs
+
+    def set_default_locators_and_formatters(self, axis):
+        axis.set_major_locator(SymmetricalLogLocator(self.get_transform()))
+        axis.set_major_formatter(LogFormatterMathtext(self.base))
+        axis.set_minor_locator(SymmetricalLogLocator(self.get_transform(), 
self.subs))
+        axis.set_minor_formatter(NullFormatter())
+
+    def get_transform(self):
+        return self._transform
+
+
 _scale_mapping = {
-    'linear' : LinearScale,
-    'log'    : LogScale
+    'linear'    : LinearScale,
+    'log'       : LogScale,
+    'symlog'    : SymmetricalLogScale
     }
 def scale_factory(scale, axis, **kwargs):
     scale = scale.lower()
@@ -190,7 +275,7 @@
 
     if not _scale_mapping.has_key(scale):
         raise ValueError("Unknown scale type '%s'" % scale)
-    
+
     return _scale_mapping[scale](axis, **kwargs)
 
 def get_scale_names():

Modified: branches/transforms/lib/matplotlib/ticker.py
===================================================================
--- branches/transforms/lib/matplotlib/ticker.py        2007-12-14 16:28:34 UTC 
(rev 4732)
+++ branches/transforms/lib/matplotlib/ticker.py        2007-12-14 20:07:59 UTC 
(rev 4733)
@@ -131,14 +131,14 @@
 
         def set_data_interval(self, vmin, vmax):
             self.dataLim.intervalx = vmin, vmax
-            
+
     def set_axis(self, axis):
         self.axis = axis
 
     def create_dummy_axis(self):
         if self.axis is None:
             self.axis = self.DummyAxis()
-        
+
     def set_view_interval(self, vmin, vmax):
         self.axis.set_view_interval(vmin, vmax)
 
@@ -149,7 +149,7 @@
         self.set_view_interval(vmin, vmax)
         self.set_data_interval(vmin, vmax)
 
-        
+
 class Formatter(TickHelper):
     """
     Convert the tick location to a string
@@ -459,13 +459,18 @@
         vmin, vmax = self.axis.get_view_interval()
         d = abs(vmax - vmin)
         b=self._base
+        if x == 0.0:
+            return '0'
+        sign = npy.sign(x)
         # only label the decades
-        fx = math.log(x)/math.log(b)
+        fx = math.log(abs(x))/math.log(b)
         isDecade = self.is_decade(fx)
         if not isDecade and self.labelOnlyBase: s = ''
         elif x>10000: s= '%1.0e'%x
         elif x<1: s =  '%1.0e'%x
         else        : s =  self.pprint_val(x,d)
+        if sign == -1:
+            return '-%s' % s
         return s
 
     def format_data(self,value):
@@ -516,8 +521,11 @@
         self.verify_intervals()
         d = abs(self.viewInterval.span())
         b=self._base
+        if x == 0:
+            return '0'
+        sign = npy.sign(x)
         # only label the decades
-        fx = math.log(x)/math.log(b)
+        fx = math.log(abs(x))/math.log(b)
         isDecade = self.is_decade(fx)
         if not isDecade and self.labelOnlyBase: s = ''
         #if 0: pass
@@ -526,6 +534,8 @@
         #elif x<1: s =  '10^%d'%fx
         elif fx<1: s =  '%1.0e'%fx
         else        : s =  self.pprint_val(fx,d)
+        if sign == -1:
+            return '-%s' % s
         return s
 
 
@@ -538,22 +548,30 @@
         'Return the format for tick val x at position pos'
         b = self._base
         # only label the decades
-        fx = math.log(x)/math.log(b)
+        if x == 0:
+            return '$0$'
+        sign = npy.sign(x)
+        fx = math.log(abs(x))/math.log(b)
         isDecade = self.is_decade(fx)
 
         usetex = rcParams['text.usetex']
 
+        if sign == -1:
+            sign_string = '-'
+        else:
+            sign_string = ''
+
         if not isDecade and self.labelOnlyBase: s = ''
         elif not isDecade:
             if usetex:
-                s = r'$%d^{%.2f}$'% (b, fx)
+                s = r'$%s%d^{%.2f}$'% (sign_string, b, fx)
             else:
-                s = '$\mathdefault{%d^{%.2f}}$'% (b, fx)
+                s = '$\mathdefault{%s%d^{%.2f}}$'% (sign_string, b, fx)
         else:
             if usetex:
-                s = r'$%d^{%d}$'% (b, self.nearest_long(fx))
+                s = r'$%s%d^{%d}$'% (sign_string, b, self.nearest_long(fx))
             else:
-                s = r'$\mathdefault{%d^{%d}}$'% (b, self.nearest_long(fx))
+                s = r'$\mathdefault{%s%d^{%d}}$'% (sign_string, b, 
self.nearest_long(fx))
 
         return s
 
@@ -928,13 +946,12 @@
             if vmin <= 0.0:
                 raise ValueError(
                     "Data has no positive values, and therefore can not be 
log-scaled.")
-            
+
         vmin = math.log(vmin)/math.log(b)
         vmax = math.log(vmax)/math.log(b)
 
         if vmax<vmin:
             vmin, vmax = vmax, vmin
-        ticklocs = []
 
         numdec = math.floor(vmax)-math.ceil(vmin)
 
@@ -949,9 +966,14 @@
         while numdec/stride+1 > self.numticks:
             stride += 1
 
-        for decadeStart in b**npy.arange(math.floor(vmin),
-                                         math.ceil(vmax)+stride, stride):
-            ticklocs.extend( subs*decadeStart )
+        decades = npy.arange(math.floor(vmin),
+                             math.ceil(vmax)+stride, stride)
+        if len(subs) > 1 or subs[0] != 1.0:
+            ticklocs = []
+            for decadeStart in b**decades:
+                ticklocs.extend( subs*decadeStart )
+        else:
+            ticklocs = b**decades
 
         return npy.array(ticklocs)
 
@@ -979,6 +1001,80 @@
         result = mtransforms.nonsingular(vmin, vmax)
         return result
 
+class SymmetricalLogLocator(Locator):
+    """
+    Determine the tick locations for log axes
+    """
+
+    def __init__(self, transform, subs=[1.0]):
+        """
+        place ticks on the location= base**i*subs[j]
+        """
+        self._transform = transform
+        self._subs = subs
+        self.numticks = 15
+
+    def _set_numticks(self):
+        self.numticks = 15  # todo; be smart here; this is just for dev
+
+    def __call__(self):
+        'Return the locations of the ticks'
+        b = self._transform.base
+
+        vmin, vmax = self.axis.get_view_interval()
+        vmin, vmax = self._transform.transform_point((vmin, vmax))
+        if vmax<vmin:
+            vmin, vmax = vmax, vmin
+        numdec = math.floor(vmax)-math.ceil(vmin)
+
+        if self._subs is None:
+            if numdec>10: subs = npy.array([1.0])
+            elif numdec>6: subs = npy.arange(2.0, b, 2.0)
+            else: subs = npy.arange(2.0, b)
+        else:
+            subs = npy.asarray(self._subs)
+
+        stride = 1
+        while numdec/stride+1 > self.numticks:
+            stride += 1
+
+        decades = npy.arange(math.floor(vmin), math.ceil(vmax)+stride, stride)
+        if len(subs) > 1 or subs[0] != 1.0:
+            ticklocs = []
+            for decade in decades:
+                ticklocs.extend(subs * (npy.sign(decade) * b ** 
npy.abs(decade)))
+        else:
+            ticklocs = npy.sign(decades) * b ** npy.abs(decades)
+        return npy.array(ticklocs)
+
+    def autoscale(self):
+        'Try to choose the view limits intelligently'
+        b = self._transform.base
+        vmin, vmax = self.axis.get_data_interval()
+        if vmax<vmin:
+            vmin, vmax = vmax, vmin
+
+        if not is_decade(abs(vmin), b):
+            if vmin < 0:
+                vmin = -decade_up(-vmin, b)
+            else:
+                vmin = decade_down(vmin, b)
+        if not is_decade(abs(vmax), b):
+            if vmax < 0:
+                vmax = -decade_down(-vmax, b)
+            else:
+                vmax = decade_up(vmax, b)
+
+        if vmin == vmax:
+            if vmin < 0:
+                vmin = -decade_up(-vmin, b)
+                vmax = -decade_down(-vmax, b)
+            else:
+                vmin = decade_down(vmin, b)
+                vmax = decade_up(vmax, b)
+        result = mtransforms.nonsingular(vmin, vmax)
+        return result
+
 class AutoLocator(MaxNLocator):
     def __init__(self):
         MaxNLocator.__init__(self, nbins=9, steps=[1, 2, 5, 10])


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

-------------------------------------------------------------------------
SF.Net email is sponsored by:
Check out the new SourceForge.net Marketplace.
It's the best place to buy or sell services
for just about anything Open Source.
http://ad.doubleclick.net/clk;164216239;13503038;w?http://sf.net/marketplace
_______________________________________________
Matplotlib-checkins mailing list
[email protected]
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to