rsplotlib 0.3.5__cp39-cp39-win_amd64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- rsplotlib/__init__.py +101 -0
- rsplotlib/_font_resolver.py +167 -0
- rsplotlib/cm.py +15 -0
- rsplotlib/collections.py +35 -0
- rsplotlib/colors.py +186 -0
- rsplotlib/core/__init__.py +2 -0
- rsplotlib/core/api.py +771 -0
- rsplotlib/dates.py +85 -0
- rsplotlib/figure/__init__.py +2 -0
- rsplotlib/figure/_defaults.py +13 -0
- rsplotlib/gridspec.py +6 -0
- rsplotlib/layout/__init__.py +2 -0
- rsplotlib/layout/gridspec.py +161 -0
- rsplotlib/patches.py +54 -0
- rsplotlib/path.py +4 -0
- rsplotlib/pylab.py +22 -0
- rsplotlib/pyplot.py +3772 -0
- rsplotlib/rsplotlib.cp39-win_amd64.pyd +0 -0
- rsplotlib/text.py +18 -0
- rsplotlib/ticker.py +6 -0
- rsplotlib/ticks/__init__.py +2 -0
- rsplotlib/ticks/ticker.py +269 -0
- rsplotlib/utils/__init__.py +2 -0
- rsplotlib/utils/_font_resolver.py +34 -0
- rsplotlib/utils/_rcparams.py +103 -0
- rsplotlib/utils/style.py +103 -0
- rsplotlib-0.3.5.dist-info/METADATA +112 -0
- rsplotlib-0.3.5.dist-info/RECORD +31 -0
- rsplotlib-0.3.5.dist-info/WHEEL +4 -0
- rsplotlib-0.3.5.dist-info/licenses/LICENSE +21 -0
- rsplotlib-0.3.5.dist-info/sboms/rsplotlib.cyclonedx.json +2250 -0
rsplotlib/__init__.py
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"""rsplotlib 包顶层导出。建议通过导入自 `rsplotlib.api` 使用公开 API。"""
|
|
2
|
+
|
|
3
|
+
from .core.api import * # noqa: F403, F401
|
|
4
|
+
from .core.api import __all__ as _api_all
|
|
5
|
+
from .rsplotlib import register_sans_serif_font
|
|
6
|
+
from . import pyplot, pylab # noqa: F401
|
|
7
|
+
from .utils import _font_resolver, style # noqa: F401
|
|
8
|
+
from .layout import gridspec # noqa: F401
|
|
9
|
+
from .ticks import ticker # noqa: F401
|
|
10
|
+
import rsplotlib.text as text
|
|
11
|
+
|
|
12
|
+
GridSpec = gridspec.GridSpec
|
|
13
|
+
MaxNLocator = ticker.MaxNLocator
|
|
14
|
+
MultipleLocator = ticker.MultipleLocator
|
|
15
|
+
AutoMinorLocator = ticker.AutoMinorLocator
|
|
16
|
+
|
|
17
|
+
__version__ = "0.3.5"
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _round_float_for_display(value):
|
|
21
|
+
if isinstance(value, float):
|
|
22
|
+
rounded = round(value, 15)
|
|
23
|
+
rounded_int = round(rounded)
|
|
24
|
+
if abs(rounded - rounded_int) < 1e-10:
|
|
25
|
+
return rounded_int
|
|
26
|
+
return rounded
|
|
27
|
+
return value
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
def _patch_rsnumpy_repr():
|
|
31
|
+
try:
|
|
32
|
+
import rsnumpy as np
|
|
33
|
+
ndarray_cls = np.ndarray
|
|
34
|
+
|
|
35
|
+
original_repr = ndarray_cls.__repr__
|
|
36
|
+
original_str = ndarray_cls.__str__
|
|
37
|
+
|
|
38
|
+
def patched_repr(self):
|
|
39
|
+
try:
|
|
40
|
+
data = self.tolist()
|
|
41
|
+
|
|
42
|
+
def convert_to_python(obj):
|
|
43
|
+
if hasattr(obj, 'tolist'):
|
|
44
|
+
return convert_to_python(obj.tolist())
|
|
45
|
+
elif isinstance(obj, list):
|
|
46
|
+
return [convert_to_python(item) for item in obj]
|
|
47
|
+
elif isinstance(obj, complex):
|
|
48
|
+
real_part = _round_float_for_display(obj.real)
|
|
49
|
+
imag_part = _round_float_for_display(obj.imag)
|
|
50
|
+
return complex(real_part, imag_part)
|
|
51
|
+
else:
|
|
52
|
+
return _round_float_for_display(obj)
|
|
53
|
+
|
|
54
|
+
converted_data = convert_to_python(data)
|
|
55
|
+
|
|
56
|
+
if isinstance(converted_data, list) and len(converted_data) == 1:
|
|
57
|
+
converted_data = converted_data[0]
|
|
58
|
+
|
|
59
|
+
def format_list(lst):
|
|
60
|
+
if not lst:
|
|
61
|
+
return "[]"
|
|
62
|
+
first = lst[0]
|
|
63
|
+
if isinstance(first, list):
|
|
64
|
+
inner = ", ".join(format_list(item) for item in lst)
|
|
65
|
+
return f"[{inner}]"
|
|
66
|
+
elif isinstance(first, complex):
|
|
67
|
+
formatted = [f"({x.real}+{x.imag}j)" for x in lst]
|
|
68
|
+
return f"[{', '.join(formatted)}]"
|
|
69
|
+
else:
|
|
70
|
+
return str(lst)
|
|
71
|
+
|
|
72
|
+
if isinstance(converted_data, list):
|
|
73
|
+
return format_list(converted_data)
|
|
74
|
+
else:
|
|
75
|
+
return str(converted_data)
|
|
76
|
+
except Exception:
|
|
77
|
+
ndarray_cls.__str__ = original_str
|
|
78
|
+
result = original_repr(self)
|
|
79
|
+
ndarray_cls.__str__ = patched_str
|
|
80
|
+
return result
|
|
81
|
+
|
|
82
|
+
def patched_str(self):
|
|
83
|
+
try:
|
|
84
|
+
return patched_repr(self)
|
|
85
|
+
except Exception:
|
|
86
|
+
return original_str(self)
|
|
87
|
+
|
|
88
|
+
ndarray_cls.__repr__ = patched_repr
|
|
89
|
+
ndarray_cls.__str__ = patched_str
|
|
90
|
+
except ImportError:
|
|
91
|
+
pass
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
_patch_rsnumpy_repr()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
__all__ = list(_api_all) + [
|
|
98
|
+
'pyplot', 'style', 'gridspec', 'ticker', 'text',
|
|
99
|
+
'GridSpec', 'MaxNLocator', 'MultipleLocator',
|
|
100
|
+
'AutoMinorLocator', 'register_sans_serif_font',
|
|
101
|
+
]
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""rsplotlib._font_resolver - 字体族名 → 字体文件路径解析
|
|
2
|
+
|
|
3
|
+
将 matplotlib 风格的无衬线字体族名(如 "Arial Unicode MS"、"Helvetica Neue")
|
|
4
|
+
映射到本地字体文件路径。找不到时返回 None,由调用方决定回退到默认字体。
|
|
5
|
+
"""
|
|
6
|
+
import os
|
|
7
|
+
from typing import Optional, List
|
|
8
|
+
import sys
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
# ====== 字体族名 → 候选文件路径映射(按平台分别维护)======
|
|
12
|
+
|
|
13
|
+
_FONT_NAME_TO_PATHS = {
|
|
14
|
+
# macOS
|
|
15
|
+
"Arial Unicode MS": [
|
|
16
|
+
"/Library/Fonts/Arial Unicode.ttf",
|
|
17
|
+
"/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
|
|
18
|
+
],
|
|
19
|
+
"Arial": [
|
|
20
|
+
"/Library/Fonts/Arial.ttf",
|
|
21
|
+
"/System/Library/Fonts/Supplemental/Arial.ttf",
|
|
22
|
+
],
|
|
23
|
+
"Helvetica": [
|
|
24
|
+
"/System/Library/Fonts/Helvetica.ttc",
|
|
25
|
+
"/System/Library/Fonts/HelveticaNeue.ttc",
|
|
26
|
+
],
|
|
27
|
+
"Helvetica Neue": [
|
|
28
|
+
"/System/Library/Fonts/HelveticaNeue.ttc",
|
|
29
|
+
],
|
|
30
|
+
"PingFang SC": [
|
|
31
|
+
"/System/Library/Fonts/PingFang.ttc",
|
|
32
|
+
],
|
|
33
|
+
"Heiti SC": [
|
|
34
|
+
"/System/Library/Fonts/STHeiti Light.ttc",
|
|
35
|
+
"/System/Library/Fonts/STHeiti Medium.ttc",
|
|
36
|
+
],
|
|
37
|
+
"Hiragino Sans GB": [
|
|
38
|
+
"/System/Library/Fonts/Hiragino Sans GB W3.otf",
|
|
39
|
+
"/System/Library/Fonts/Hiragino Sans GB W6.otf",
|
|
40
|
+
],
|
|
41
|
+
# Linux
|
|
42
|
+
"DejaVu Sans": [
|
|
43
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
44
|
+
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
|
|
45
|
+
],
|
|
46
|
+
"Liberation Sans": [
|
|
47
|
+
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf",
|
|
48
|
+
],
|
|
49
|
+
"Noto Sans CJK SC": [
|
|
50
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
51
|
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
|
52
|
+
"/usr/share/fonts/noto-cjk/NotoSansCJK-Regular.ttc",
|
|
53
|
+
],
|
|
54
|
+
"WenQuanYi Micro Hei": [
|
|
55
|
+
"/usr/share/fonts/truetype/wqy/wqy-microhei.ttc",
|
|
56
|
+
],
|
|
57
|
+
# Windows
|
|
58
|
+
"Microsoft YaHei": [
|
|
59
|
+
"C:/Windows/Fonts/msyh.ttc",
|
|
60
|
+
"C:/Windows/Fonts/msyh.ttf",
|
|
61
|
+
"C:/Windows/Fonts/msyhbd.ttc",
|
|
62
|
+
],
|
|
63
|
+
"SimHei": [
|
|
64
|
+
"C:/Windows/Fonts/simhei.ttf",
|
|
65
|
+
],
|
|
66
|
+
"SimSun": [
|
|
67
|
+
"C:/Windows/Fonts/simsun.ttc",
|
|
68
|
+
],
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
# 跨平台按系统名归一化的额外兜底字体路径
|
|
73
|
+
|
|
74
|
+
def _system_fallback_paths() -> List[str]:
|
|
75
|
+
"""按当前操作系统返回一组"通用全功能字体"候选路径"""
|
|
76
|
+
system = sys.platform
|
|
77
|
+
if system == "darwin":
|
|
78
|
+
return [
|
|
79
|
+
"/Library/Fonts/Arial Unicode.ttf",
|
|
80
|
+
"/System/Library/Fonts/Supplemental/Arial Unicode.ttf",
|
|
81
|
+
]
|
|
82
|
+
elif system == "linux":
|
|
83
|
+
return [
|
|
84
|
+
"/usr/share/fonts/opentype/noto/NotoSansCJK-Regular.ttc",
|
|
85
|
+
"/usr/share/fonts/truetype/noto/NotoSansCJK-Regular.ttc",
|
|
86
|
+
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf",
|
|
87
|
+
"/usr/share/fonts/dejavu/DejaVuSans.ttf",
|
|
88
|
+
]
|
|
89
|
+
elif system == "win32":
|
|
90
|
+
return [
|
|
91
|
+
"C:/Windows/Fonts/msyh.ttc",
|
|
92
|
+
"C:/Windows/Fonts/msyh.ttf",
|
|
93
|
+
"C:/Windows/Fonts/msyhbd.ttc",
|
|
94
|
+
]
|
|
95
|
+
elif system == "cygwin":
|
|
96
|
+
return [
|
|
97
|
+
"C:/Windows/Fonts/msyh.ttc",
|
|
98
|
+
"C:/Windows/Fonts/arial.ttf",
|
|
99
|
+
]
|
|
100
|
+
return []
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def resolve_font_path(family: str) -> Optional[str]:
|
|
104
|
+
"""根据字体族名查找本地的字体文件路径。
|
|
105
|
+
|
|
106
|
+
找不到时返回 None。
|
|
107
|
+
"""
|
|
108
|
+
if not family:
|
|
109
|
+
return None
|
|
110
|
+
# 精确匹配
|
|
111
|
+
candidates = _FONT_NAME_TO_PATHS.get(family, [])
|
|
112
|
+
for path in candidates:
|
|
113
|
+
if os.path.isfile(path):
|
|
114
|
+
return path
|
|
115
|
+
# 大小写不敏感匹配
|
|
116
|
+
lower_map = {k.lower(): v for k, v in _FONT_NAME_TO_PATHS.items()}
|
|
117
|
+
candidates = lower_map.get(family.lower(), [])
|
|
118
|
+
for path in candidates:
|
|
119
|
+
if os.path.isfile(path):
|
|
120
|
+
return path
|
|
121
|
+
# 平台回退
|
|
122
|
+
for path in _system_fallback_paths():
|
|
123
|
+
if os.path.isfile(path):
|
|
124
|
+
return path
|
|
125
|
+
return None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
def apply_rcparams_font() -> Optional[str]:
|
|
129
|
+
"""读取 rcParams["font.sans-serif"],把第一个能解析到本地文件的字体注册到 plotters。
|
|
130
|
+
|
|
131
|
+
返回实际注册的字体文件路径,如果没找到任何字体则返回 None。
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
# 延迟导入避免循环依赖
|
|
135
|
+
from . import rsplotlib as _rsplotlib
|
|
136
|
+
from .pylab import mpl
|
|
137
|
+
except Exception:
|
|
138
|
+
return None
|
|
139
|
+
|
|
140
|
+
sans_serif = mpl.rcParams.get("font.sans-serif")
|
|
141
|
+
if not sans_serif:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
if isinstance(sans_serif, str):
|
|
145
|
+
candidates = [sans_serif]
|
|
146
|
+
else:
|
|
147
|
+
try:
|
|
148
|
+
candidates = list(sans_serif)
|
|
149
|
+
except TypeError:
|
|
150
|
+
candidates = [str(sans_serif)]
|
|
151
|
+
|
|
152
|
+
# "sans-serif" 关键字跳过:让 plotters 使用内部默认
|
|
153
|
+
candidates = [c for c in candidates if c and c.lower() != "sans-serif"]
|
|
154
|
+
|
|
155
|
+
for family in candidates:
|
|
156
|
+
path = resolve_font_path(family)
|
|
157
|
+
if path is None:
|
|
158
|
+
# 可能是直接的字体文件路径
|
|
159
|
+
if os.path.isfile(family):
|
|
160
|
+
path = family
|
|
161
|
+
if path is not None:
|
|
162
|
+
try:
|
|
163
|
+
_rsplotlib.register_sans_serif_font(path)
|
|
164
|
+
return path
|
|
165
|
+
except Exception:
|
|
166
|
+
continue
|
|
167
|
+
return None
|
rsplotlib/cm.py
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
class ScalarMappable:
|
|
2
|
+
def __init__(self, cmap=None, norm=None):
|
|
3
|
+
self.cmap = cmap
|
|
4
|
+
self.norm = norm
|
|
5
|
+
self.vmin = None
|
|
6
|
+
self.vmax = None
|
|
7
|
+
|
|
8
|
+
def set_clim(self, vmin=None, vmax=None):
|
|
9
|
+
self.vmin = vmin
|
|
10
|
+
self.vmax = vmax
|
|
11
|
+
|
|
12
|
+
def to_rgba(self, values):
|
|
13
|
+
if not isinstance(values, (list, tuple)):
|
|
14
|
+
values = [values]
|
|
15
|
+
return [[0.0, 0.0, 0.0, 1.0] for _ in values]
|
rsplotlib/collections.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
class PathCollection:
|
|
2
|
+
def __init__(self, paths):
|
|
3
|
+
self.paths = paths
|
|
4
|
+
self.zorder = None
|
|
5
|
+
|
|
6
|
+
def set_zorder(self, zorder):
|
|
7
|
+
self.zorder = zorder
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class LineCollection:
|
|
11
|
+
def __init__(self, segments, colors=None, linewidths=None, linestyle=None,
|
|
12
|
+
alpha=None, antialiaseds=None, zorder=None):
|
|
13
|
+
self.segments = segments
|
|
14
|
+
self.colors = colors
|
|
15
|
+
self.linewidths = linewidths
|
|
16
|
+
self.linestyle = linestyle
|
|
17
|
+
self.alpha = alpha
|
|
18
|
+
self.antialiaseds = antialiaseds
|
|
19
|
+
self.zorder = zorder
|
|
20
|
+
self.cmap = None
|
|
21
|
+
self.vmin = None
|
|
22
|
+
self.vmax = None
|
|
23
|
+
|
|
24
|
+
def set_cmap(self, cmap):
|
|
25
|
+
self.cmap = cmap
|
|
26
|
+
|
|
27
|
+
def set_clim(self, vmin, vmax):
|
|
28
|
+
self.vmin = vmin
|
|
29
|
+
self.vmax = vmax
|
|
30
|
+
|
|
31
|
+
def set_zorder(self, zorder):
|
|
32
|
+
self.zorder = zorder
|
|
33
|
+
|
|
34
|
+
def set_label(self, label):
|
|
35
|
+
self.label = label
|
rsplotlib/colors.py
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
"""rsplotlib.colors - Matplotlib colors 兼容接口 (子集)。
|
|
2
|
+
|
|
3
|
+
提供归一化类 Normalize / LogNorm,用于将数据映射到 [0, 1] 供 colormap 上色。
|
|
4
|
+
当前 imshow/pcolormesh 等接受 norm 参数,Normalize/LogNorm 在此处提供兼容 API。
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import math
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Normalize:
|
|
11
|
+
"""将数据线性归一化到 [0, 1],兼容 matplotlib.colors.Normalize。
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
vmin, vmax: 归一化值域端点,缺省时在首次调用时按数据自动推断。
|
|
15
|
+
clip: 是否将越界值裁剪到 [0, 1]。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
# 归一化类型标记:供 pyplot 把归一化方式(线性/对数)下沉给 Rust 上色与颜色条。
|
|
19
|
+
_norm_kind = 'linear'
|
|
20
|
+
|
|
21
|
+
def __init__(self, vmin=None, vmax=None, clip=False):
|
|
22
|
+
self.vmin = vmin
|
|
23
|
+
self.vmax = vmax
|
|
24
|
+
self.clip = clip
|
|
25
|
+
|
|
26
|
+
def autoscale_None(self, values):
|
|
27
|
+
"""当 vmin/vmax 未设置时,根据数据填充。"""
|
|
28
|
+
seq = _flatten(values)
|
|
29
|
+
if seq:
|
|
30
|
+
if self.vmin is None:
|
|
31
|
+
self.vmin = min(seq)
|
|
32
|
+
if self.vmax is None:
|
|
33
|
+
self.vmax = max(seq)
|
|
34
|
+
|
|
35
|
+
def _normalize_scalar(self, value):
|
|
36
|
+
vmin = 0.0 if self.vmin is None else float(self.vmin)
|
|
37
|
+
vmax = 1.0 if self.vmax is None else float(self.vmax)
|
|
38
|
+
if vmax == vmin:
|
|
39
|
+
return 0.0
|
|
40
|
+
t = (float(value) - vmin) / (vmax - vmin)
|
|
41
|
+
if self.clip:
|
|
42
|
+
t = max(0.0, min(1.0, t))
|
|
43
|
+
return t
|
|
44
|
+
|
|
45
|
+
def __call__(self, value, clip=None):
|
|
46
|
+
if clip is not None:
|
|
47
|
+
saved = self.clip
|
|
48
|
+
self.clip = clip
|
|
49
|
+
try:
|
|
50
|
+
return self._apply(value)
|
|
51
|
+
finally:
|
|
52
|
+
self.clip = saved
|
|
53
|
+
return self._apply(value)
|
|
54
|
+
|
|
55
|
+
def _apply(self, value):
|
|
56
|
+
if not _is_sequence(value):
|
|
57
|
+
return self._normalize_scalar(value)
|
|
58
|
+
# 循环不变量外提:vmin/vmax 的 float 转换与除数只算一次,再逐元素映射。
|
|
59
|
+
seq = _flatten(value)
|
|
60
|
+
vmin = 0.0 if self.vmin is None else float(self.vmin)
|
|
61
|
+
vmax = 1.0 if self.vmax is None else float(self.vmax)
|
|
62
|
+
if vmax == vmin:
|
|
63
|
+
return [0.0] * len(seq)
|
|
64
|
+
inv = 1.0 / (vmax - vmin)
|
|
65
|
+
if self.clip:
|
|
66
|
+
return [max(0.0, min(1.0, (v - vmin) * inv)) for v in seq]
|
|
67
|
+
return [(v - vmin) * inv for v in seq]
|
|
68
|
+
|
|
69
|
+
def __repr__(self):
|
|
70
|
+
return f'Normalize(vmin={self.vmin}, vmax={self.vmax})'
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class LogNorm(Normalize):
|
|
74
|
+
"""对数归一化,兼容 matplotlib.colors.LogNorm。
|
|
75
|
+
|
|
76
|
+
将数据按对数刻度映射到 [0, 1];非正值被视为越界。
|
|
77
|
+
"""
|
|
78
|
+
|
|
79
|
+
_norm_kind = 'log'
|
|
80
|
+
|
|
81
|
+
def autoscale_None(self, values):
|
|
82
|
+
"""当 vmin/vmax 未设置时,用数据的最小正值 / 最大值填充(对数刻度要求正值)。"""
|
|
83
|
+
seq = [v for v in _flatten(values) if v > 0]
|
|
84
|
+
if seq:
|
|
85
|
+
if self.vmin is None:
|
|
86
|
+
self.vmin = min(seq)
|
|
87
|
+
if self.vmax is None:
|
|
88
|
+
self.vmax = max(seq)
|
|
89
|
+
|
|
90
|
+
def _normalize_scalar(self, value):
|
|
91
|
+
vmin = self.vmin
|
|
92
|
+
vmax = self.vmax
|
|
93
|
+
if vmin is None or vmax is None or vmin <= 0 or vmax <= 0:
|
|
94
|
+
return 0.0
|
|
95
|
+
v = float(value)
|
|
96
|
+
if v <= 0:
|
|
97
|
+
return 0.0
|
|
98
|
+
t = (math.log(v) - math.log(vmin)) / (math.log(vmax) - math.log(vmin))
|
|
99
|
+
if self.clip:
|
|
100
|
+
t = max(0.0, min(1.0, t))
|
|
101
|
+
return t
|
|
102
|
+
|
|
103
|
+
def _apply(self, value):
|
|
104
|
+
if not _is_sequence(value):
|
|
105
|
+
return self._normalize_scalar(value)
|
|
106
|
+
# 循环不变量外提:log(vmin) 与对数跨度只算一次,避免逐元素重复求 log。
|
|
107
|
+
seq = _flatten(value)
|
|
108
|
+
vmin = self.vmin
|
|
109
|
+
vmax = self.vmax
|
|
110
|
+
if vmin is None or vmax is None or vmin <= 0 or vmax <= 0:
|
|
111
|
+
return [0.0] * len(seq)
|
|
112
|
+
log_vmin = math.log(vmin)
|
|
113
|
+
span = math.log(vmax) - log_vmin
|
|
114
|
+
if span == 0.0:
|
|
115
|
+
return [0.0] * len(seq)
|
|
116
|
+
inv = 1.0 / span
|
|
117
|
+
if self.clip:
|
|
118
|
+
return [max(0.0, min(1.0, (math.log(v) - log_vmin) * inv)) if v > 0 else 0.0
|
|
119
|
+
for v in seq]
|
|
120
|
+
return [(math.log(v) - log_vmin) * inv if v > 0 else 0.0 for v in seq]
|
|
121
|
+
|
|
122
|
+
def __repr__(self):
|
|
123
|
+
return f'LogNorm(vmin={self.vmin}, vmax={self.vmax})'
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def _is_sequence(obj):
|
|
127
|
+
if obj is None or isinstance(obj, str):
|
|
128
|
+
return False
|
|
129
|
+
return hasattr(obj, 'tolist') or hasattr(obj, '__iter__')
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
def _flatten(values):
|
|
133
|
+
"""递归展开嵌套序列 / 数组对象为一维浮点列表。"""
|
|
134
|
+
if values is None:
|
|
135
|
+
return []
|
|
136
|
+
if hasattr(values, 'tolist'):
|
|
137
|
+
values = values.tolist()
|
|
138
|
+
out = []
|
|
139
|
+
if isinstance(values, (list, tuple)):
|
|
140
|
+
for item in values:
|
|
141
|
+
if isinstance(item, (list, tuple)):
|
|
142
|
+
out.extend(_flatten(item))
|
|
143
|
+
else:
|
|
144
|
+
out.append(float(item))
|
|
145
|
+
else:
|
|
146
|
+
out.append(float(values))
|
|
147
|
+
return out
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
class _ColorConverter:
|
|
151
|
+
def to_rgba_array(self, colors, alpha=None):
|
|
152
|
+
if not isinstance(colors, (list, tuple)):
|
|
153
|
+
colors = [colors]
|
|
154
|
+
result = []
|
|
155
|
+
for color in colors:
|
|
156
|
+
if color is None or color == 'none':
|
|
157
|
+
rgba = [0.0, 0.0, 0.0, 0.0]
|
|
158
|
+
elif color == 'k' or color == 'black':
|
|
159
|
+
rgba = [0.0, 0.0, 0.0, 1.0]
|
|
160
|
+
elif color == 'w' or color == 'white':
|
|
161
|
+
rgba = [1.0, 1.0, 1.0, 1.0]
|
|
162
|
+
elif color == 'r' or color == 'red':
|
|
163
|
+
rgba = [1.0, 0.0, 0.0, 1.0]
|
|
164
|
+
elif color == 'g' or color == 'green':
|
|
165
|
+
rgba = [0.0, 1.0, 0.0, 1.0]
|
|
166
|
+
elif color == 'b' or color == 'blue':
|
|
167
|
+
rgba = [0.0, 0.0, 1.0, 1.0]
|
|
168
|
+
elif color == 'c' or color == 'cyan':
|
|
169
|
+
rgba = [0.0, 1.0, 1.0, 1.0]
|
|
170
|
+
elif color == 'm' or color == 'magenta':
|
|
171
|
+
rgba = [1.0, 0.0, 1.0, 1.0]
|
|
172
|
+
elif color == 'y' or color == 'yellow':
|
|
173
|
+
rgba = [1.0, 1.0, 0.0, 1.0]
|
|
174
|
+
elif isinstance(color, (list, tuple)) and len(color) in (3, 4):
|
|
175
|
+
rgba = list(color)
|
|
176
|
+
if len(rgba) == 3:
|
|
177
|
+
rgba.append(1.0)
|
|
178
|
+
else:
|
|
179
|
+
rgba = [0.0, 0.0, 0.0, 1.0]
|
|
180
|
+
if alpha is not None:
|
|
181
|
+
rgba[3] = alpha
|
|
182
|
+
result.append(rgba)
|
|
183
|
+
return result
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
colorConverter = _ColorConverter()
|