我编写了以下自定义Scale类:
import numpy as np
from matplotlib import scale as mscale
from matplotlib import transforms as mtransforms
from matplotlib.ticker import Formatter
class PiecewiseLinearScale(mscale.ScaleBase):
name = 'piecewise'
def __init__(self, axis, *, thresholds=None, true_values=None, **kwargs):
mscale.ScaleBase.__init__(self)
if thresholds is None:
thresholds = [0, 1, 3, 5, 10]
if true_values is None:
true_values = [0, 1, 2, 3, 4]
if len(thresholds) != len(true_values):
raise ValueError("thresholds and true_values should have the same length")
tp = thresholds[0]
for t in thresholds[1:]:
if t <= tp:
raise ValueError("Values in thresholds array should be ascending")
t = tp
tp = true_values[0]
for t in true_values[1:]:
if t <= tp:
raise ValueError("Values in true_values array should be ascending")
t = tp
self.thresholds = thresholds
self.true_values = true_values
def get_transform(self):
return self.PiecewiseLinearLatitudeTransform(self.thresholds, self.true_values)
def set_default_locators_and_formatters(self, axis):
class DegreeFormatter(Formatter):
def __call__(self, x, pos=None):
return "{}".format(x)
axis.set_major_formatter(DegreeFormatter())
axis.set_minor_formatter(DegreeFormatter())
def limit_range_for_scale(self, vmin, vmax, minpos):
return max(vmin, self.true_values[0]), min(vmax, self.true_values[-1])
class PiecewiseLinearLatitudeTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
has_inverse = True
def __init__(self, thresholds = None, true_values = None):
mtransforms.Transform.__init__(self)
if thresholds is None:
thresholds = [0, 1, 3, 5, 10]
if true_values is None:
true_values = [0, 1, 2, 3, 4]
self.true_values = true_values
self.thresholds = thresholds
def transform_non_affine(self, a):
masked = np.ma.masked_where((a < self.true_values[0]) | (a > self.true_values[-1]), a)
# val = None
proper_stuff = []
for val in masked:
if isinstance(val, np.ma.core.MaskedConstant):
proper_stuff.append(np.ma.core.MaskedConstant())
continue
for i, t_v in enumerate(self.true_values[1:]):
if t_v >= val:
proper_true_ind = i
break
else:
proper_true_ind = i
unbiased = val-self.true_values[proper_true_ind]
true_values_raise = self.true_values[proper_true_ind+1] - self.true_values[proper_true_ind]
thresholds_raise = self.thresholds[proper_true_ind+1] - self.thresholds[proper_true_ind]
first_part = unbiased/true_values_raise
unbiased_scaled = first_part*thresholds_raise
proper_stuff.append(self.thresholds[proper_true_ind] + unbiased_scaled)
return np.ma.array(proper_stuff)
def inverted(self):
return PiecewiseLinearScale.InvertedPiecewiseLinearLatitudeTransform(
self.thresholds, self.true_values)
class InvertedPiecewiseLinearLatitudeTransform(mtransforms.Transform):
input_dims = 1
output_dims = 1
is_separable = True
has_inverse = True
def __init__(self, thresholds = None, true_values = None):
mtransforms.Transform.__init__(self)
if thresholds is None:
thresholds = [0, 1, 3, 5, 10]
if true_values is None:
true_values = [0, 1, 2, 3, 4]
self.true_values = true_values
self.thresholds = thresholds
def transform_non_affine(self, a):
masked = np.ma.masked_where((a < self.thresholds[0]) | (a > self.thresholds[-1]), a)
proper_stuff = []
for val in masked:
if isinstance(val, np.ma.core.MaskedConstant):
proper_stuff.append(np.ma.core.MaskedConstant())
continue
for i, t_v in enumerate(self.thresholds[1:]):
if t_v >= val:
proper_true_ind = i
break
else:
proper_true_ind = i
true_values_raise = self.true_values[proper_true_ind+1] - self.true_values[proper_true_ind]
thresholds_raise = self.thresholds[proper_true_ind+1] - self.thresholds[proper_true_ind]
unbiased = val-self.thresholds[proper_true_ind]
proper_stuff.append(self.true_values[proper_true_ind] + unbiased*true_values_raise/thresholds_raise)
return np.ma.array(proper_stuff)
def inverted(self):
return PiecewiseLinearScale.PiecewiseLinearLatitudeTransform(self.thresholds, self.true_values)
它正在按照我希望的方式进行转换。问题是,一旦在轴上应用了此比例尺,刻度就会消失。不幸的是,暂时不能使用最新的Matplotlib版本,但是非常相似的代码可以正常使用-区别仅在于调用PiecewiseLinearScale超类的构造函数。由于某些原因,matplotlib page with custom scale中的代码可以正常工作。
预期行为与实际行为的比较:
Desired plot(obtained with matplotlib 3.3.1)
我用来生成这些图像的代码:
t = np.arange(0, 4.05, 0.1)
plt.plot(t, t)
plt.gca().set_yscale('piecewise', thresholds = [0, 1, 4, 9, 16], true_values = [0, 1, 2, 3, 4])
plt.xlabel('x')
plt.ylabel('y')
plt.title('Piecewise')
plt.grid(True)
plt.yticks([1, 2, 3, 4])
plt.ylim(0, 4)
plt.xlim(0, 4)
plt.show()
我希望使用解决方案/建议,该解决方案/建议仅更改第一段代码。
答案 0 :(得分:0)
因此,经过一些调试
我发现,将掩码值放在transform_non_affine
的方法InvertedPiecewiseLinearLatitudeTransform
返回的数组中是错误的想法-我应该使用np.nan
来代替。更改后的代码现在看起来像这样:
def transform_non_affine(self, a):
masked = a.copy()
masked[(a < self.thresholds[0]) | (a > self.thresholds[-1])] = np.nan
proper_stuff = []
for val in masked:
if isinstance(val, np.ma.core.MaskedConstant):
proper_stuff.append(np.ma.core.MaskedConstant())
continue
for i, t_v in enumerate(self.thresholds[1:]):
if t_v >= val:
proper_true_ind = i
break
else:
proper_true_ind = i
true_values_raise = self.true_values[proper_true_ind+1] - self.true_values[proper_true_ind]
thresholds_raise = self.thresholds[proper_true_ind+1] - self.thresholds[proper_true_ind]
unbiased = val-self.thresholds[proper_true_ind]
proper_stuff.append(self.true_values[proper_true_ind] + unbiased*true_values_raise/thresholds_raise)
res = np.array(proper_stuff)
return res