Skip to content

Instantly share code, notes, and snippets.

@rldotai
Created July 11, 2020 04:44
Show Gist options
  • Save rldotai/ecf0ed53ec6396cdfe9f5a7d3888d234 to your computer and use it in GitHub Desktop.
Save rldotai/ecf0ed53ec6396cdfe9f5a7d3888d234 to your computer and use it in GitHub Desktop.
A Matplotlib Norm with a Fixed Midpoint
import numpy as np
from matplotlib.colors import Normalize
class FixedMidpointNorm(Normalize):
def __init__(self, vcenter):
"""
Normalize data with a set center.
For plotting diverging colormaps around a particular value, at the
possible cost of some dynamic range.
Parameters
----------
vcenter : float
The data value that defines `0.5` in the normalization.
Examples
--------
This maps values such that zero is mapped to 0.5, with the other values
scaled accordingly::
>>> norm = FixedMidpointNorm(0.0)
>>> data = [-100. -75. -50. -25. 0. 25.]
>>> norm(data)
array([0. , 0.125, 0.25 , 0.375, 0.5 , 0.625])
"""
self.vcenter = vcenter
self.vmin = None
self.vmax = None
def autoscale_None(self, A):
"""
Get vmin and vmax, and then clip at vcenter
"""
super().autoscale_None(A)
# Distance between center and the max/min of the data
lo = max(self.vcenter - self.vmin, 0)
hi = max(self.vmax - self.vcenter, 0)
if hi > lo:
self._lower = self.vcenter - hi
self._upper = self.vcenter + hi
else:
self._lower = self.vcenter - lo
self._upper = self.vcenter + lo
def __call__(self, value, clip=None):
"""
Map value to the interval [0, 1]. The clip argument is unused.
"""
result, is_scalar = self.process_value(value)
self.autoscale_None(result) # sets self.vmin, self.vmax if None
if not self.vmin <= self.vcenter <= self.vmax:
raise ValueError("vmin, vcenter, vmax must increase monotonically")
result = np.ma.masked_array(
np.interp(result, [self._lower, self.vcenter, self._upper], [0, 0.5, 1.0]),
mask=np.ma.getmask(result),
)
if is_scalar:
result = np.atleast_1d(result)[0]
return result
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment