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