So I have managed to create a new scale type in matplotlib using the ScaleBase class which is very useful and rather powerful. The scale subjects an axis to a squared transform, meaning that like a log plot, the x-values are spread out along the axis on a scale that is annotated with the actual values but visually and spatially scales as the square of those values. This is particularly useful for Guinier plots which are usually plotted as the square of Q versus the natural log of intensity.
At first the problem was that it was throwing errors all over the place when I attempted to move the view of the plot. Guessing that this was due to attempting to move across the axis and therefore taking the square root of a negative number on the inverse scale transform I first tried to modify the inverse transform to make sure it was multiplying negative numbers by minus one before taking the square root. Fiddling with this a variety of ways didn’t seem to help.
The next approach was to figure out how to set the scale defaults so that it stops you going to negative values on a squared scale. This seems fine, and now when re-scaling using the rectangle magnifier it no longer tries to expand the x-axis beyond zero. But now when using the cross hairs to move the data around in the plot it throws a segmentation fault when you attempt to move across the zero point on the x-axis. I’m not sure whether this is a real bug I’ve uncovered or whether I’m just doing something stupid but nonetheless, here is the code.
#!/usr/bin/env python
class SquaredScale(mscale.ScaleBase):
"""ScaleBase class for generating x axis of Guinier plots.
Uses the built in scalebase to generate a transformed axis type
called SquaredScale which can be called using ax.set_xscale('q_squared').
Currently uses the default ticker and scale setup which will need
to be changed in the future.
The class requires the import of pylab (AutoLocator, ScalarFormatter
NullLocator and NullFormatter) matplotlib.transforms (imported as
mtransforms, required for the mtransforms.Transform class) and
matplotlib.scale (imported as mscale, required for the mscale.ScaleBase
class for inheritance of the scale).
"""
name = 'q_squared'
def __init__(self, axis, **kwargs):
mscale.ScaleBase.__init__(self)
def set_default_locators_and_formatters(self, axis):
"""
Set the locators and formatters to reasonable defaults for
scaling. Not really too sure what these do at the moment.
"""
axis.set_major_locator(AutoLocator())
axis.set_major_formatter(ScalarFormatter())
axis.set_minor_locator(NullLocator())
axis.set_minor_formatter(NullFormatter())
def limit_range_for_scale(self, vmin, vmax, minpos):
return 0, vmax
class SquaredTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def transform(self, a): return a**2
def inverted(self):
return SquaredScale.InvertedSquaredTransform()
class InvertedSquaredTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
def transform(self, a):
return sqrt(a)
def inverted(self):
return SquaredScale.SquaredTransform()
def get_transform(self):
"""Set the actual transform for the axis coordinates.
"""
return self.SquaredTransform()
mscale.register_scale(SquaredScale)