mpl-spaceplot 0.1.1__py3-none-any.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.
- mpl_spaceplot-0.1.1.dist-info/METADATA +18 -0
- mpl_spaceplot-0.1.1.dist-info/RECORD +24 -0
- mpl_spaceplot-0.1.1.dist-info/WHEEL +5 -0
- mpl_spaceplot-0.1.1.dist-info/licenses/LICENSE +21 -0
- mpl_spaceplot-0.1.1.dist-info/top_level.txt +1 -0
- spaceplot/__init__.py +25 -0
- spaceplot/aligner.py +112 -0
- spaceplot/appearance/__init__.py +8 -0
- spaceplot/appearance/display.py +178 -0
- spaceplot/appearance/inline.py +45 -0
- spaceplot/appearance/layout.py +237 -0
- spaceplot/appearance/palettes.py +402 -0
- spaceplot/appearance/styles.py +58 -0
- spaceplot/appearance/tools.py +233 -0
- spaceplot/decorators/__init__.py +4 -0
- spaceplot/decorators/decorators.py +457 -0
- spaceplot/decorators/tools.py +88 -0
- spaceplot/montage_plot.py +295 -0
- spaceplot/plotting/__init__.py +7 -0
- spaceplot/plotting/plotting.py +224 -0
- spaceplot/plotting/tools.py +75 -0
- spaceplot/resources/font_loader.py +11 -0
- spaceplot/simulate_data.py +85 -0
- spaceplot/utils.py +48 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: mpl-spaceplot
|
|
3
|
+
Version: 0.1.1
|
|
4
|
+
Summary: layout wrappers for matplotlib
|
|
5
|
+
Author-email: Florian Raths <raths.f@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Keywords: matplotlib,plotting,wrappers
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Requires-Python: >=3.10
|
|
12
|
+
Description-Content-Type: text/markdown
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
Requires-Dist: matplotlib>=3.7
|
|
15
|
+
Requires-Dist: cmcrameri>=1.9
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# spaceplot
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
mpl_spaceplot-0.1.1.dist-info/licenses/LICENSE,sha256=_vaCZw15E27W2FRa7fXnfzoCvRPLfJdvjZYnW-JgDXQ,1070
|
|
2
|
+
spaceplot/__init__.py,sha256=j8LDw-mYSuYJaTGrHKdWA0kxypSb0XMViWUGAmUppfQ,535
|
|
3
|
+
spaceplot/aligner.py,sha256=JZu9bVtM3Z7NlVB6BxeKRpw_XZU0zFWvEG0hsIs9N8I,2871
|
|
4
|
+
spaceplot/montage_plot.py,sha256=vRwvjza81VAJzaqN6EKbBcta6wIT9yo8hiPTXySX_84,9717
|
|
5
|
+
spaceplot/simulate_data.py,sha256=ojgaA3Rj_nWZup-UXsYHptQxx5FmgFiTbU0x22fibIg,2556
|
|
6
|
+
spaceplot/utils.py,sha256=8yA2k9-juq-jSmAzbBgM3z_rd_OHYPHe3Mn_2_h8B6o,1332
|
|
7
|
+
spaceplot/appearance/__init__.py,sha256=r8WmWsdbrdVlwG6wA2E-lTUAjXPh4KWmQN8TRd-u5R0,237
|
|
8
|
+
spaceplot/appearance/display.py,sha256=aW-I-RmxDw4-_RSoY54HnSby6HVMf68xEO5NxZqNkUA,7328
|
|
9
|
+
spaceplot/appearance/inline.py,sha256=_czkKGmHHu3EXGIYd1r0UwDMcP__bt1-Qq-Ir3z6V_o,1186
|
|
10
|
+
spaceplot/appearance/layout.py,sha256=WMgt_nXz9_9rw-uTBbcn-i9bTS_Rc8uUWIs2Q3a5L_k,7781
|
|
11
|
+
spaceplot/appearance/palettes.py,sha256=uVu-9EmP8m07jcUVu-7JgDKJTLbUpOmkrQHrfizCuno,7288
|
|
12
|
+
spaceplot/appearance/styles.py,sha256=YXZiVQ9UZqo3XFqKrlmAsDli7ZFtPWmawru4hAq0NtU,1480
|
|
13
|
+
spaceplot/appearance/tools.py,sha256=S6FXhACX5PUnfIbZ1ILloWdv_zeCrKW9wJk_4FRl3Vk,7406
|
|
14
|
+
spaceplot/decorators/__init__.py,sha256=EY9yo6QBmq9fmbsd7k2-BaimBnF4ldglgKDfAKa9vpc,208
|
|
15
|
+
spaceplot/decorators/decorators.py,sha256=S8ekPl7Wq1fV97GA9v2e53kutGIcJv2vZx6QdpaTIzk,12610
|
|
16
|
+
spaceplot/decorators/tools.py,sha256=KjtBjhJ0v2xtU-Es2J6n4EkwTawhYMaSWagBi4oonV4,2876
|
|
17
|
+
spaceplot/plotting/__init__.py,sha256=8fUm0IVPZCWvd-QY7Os2xQldxFGv3UsjCCzHm_xJXG8,134
|
|
18
|
+
spaceplot/plotting/plotting.py,sha256=Pt2AcNOpMHPhjO8qZCYjx0i-085j-Rw-DnRr_lwfFvk,5315
|
|
19
|
+
spaceplot/plotting/tools.py,sha256=4x9ciSWgVCHbmBfOUi0JWEgV9O0SHplVwkqsAvO1fyM,2359
|
|
20
|
+
spaceplot/resources/font_loader.py,sha256=xFJk9DdyucFTwvoj1q2TLdp0Cw43vnXoa2lXxkgt-GQ,270
|
|
21
|
+
mpl_spaceplot-0.1.1.dist-info/METADATA,sha256=9TlN5ihQi5Yo6zexDnRJ84n2JY5LBLFwD6W-DIL2ZFM,525
|
|
22
|
+
mpl_spaceplot-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
23
|
+
mpl_spaceplot-0.1.1.dist-info/top_level.txt,sha256=p7OWPWO7oZw08wku3-XvgUCcPpiMfIKe_sRVqHE82r8,10
|
|
24
|
+
mpl_spaceplot-0.1.1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Florian Raths
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
spaceplot
|
spaceplot/__init__.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import matplotlib.pyplot as plt
|
|
2
|
+
|
|
3
|
+
from . import aligner
|
|
4
|
+
from . import decorators as decs
|
|
5
|
+
from . import utils as ut
|
|
6
|
+
from .appearance import palettes as plts
|
|
7
|
+
from .appearance.display import Theme, display
|
|
8
|
+
from .appearance.layout import layout
|
|
9
|
+
from .montage_plot import montage_plot
|
|
10
|
+
from .plotting import plt_category, plt_continous, plt_image
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'aligner',
|
|
14
|
+
'decs',
|
|
15
|
+
'ut',
|
|
16
|
+
'plts',
|
|
17
|
+
'Theme',
|
|
18
|
+
'display',
|
|
19
|
+
'layout',
|
|
20
|
+
'montage_plot',
|
|
21
|
+
'plt_category',
|
|
22
|
+
'plt_continous',
|
|
23
|
+
'plt_image',
|
|
24
|
+
'plt',
|
|
25
|
+
]
|
spaceplot/aligner.py
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
import numpy as np
|
|
4
|
+
|
|
5
|
+
aligns = Literal[
|
|
6
|
+
'top_left',
|
|
7
|
+
'top_right',
|
|
8
|
+
'bottom_left',
|
|
9
|
+
'bottom_right',
|
|
10
|
+
'center_left',
|
|
11
|
+
'center_right',
|
|
12
|
+
'center_top',
|
|
13
|
+
'center_bottom',
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
align_map = {
|
|
17
|
+
'c': 0.5,
|
|
18
|
+
'l': 0,
|
|
19
|
+
'r': 1,
|
|
20
|
+
'b': 0,
|
|
21
|
+
't': 1,
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
align_full_names = {
|
|
25
|
+
'c': 'center',
|
|
26
|
+
'l': 'left',
|
|
27
|
+
'r': 'right',
|
|
28
|
+
'b': 'bottom',
|
|
29
|
+
't': 'top',
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def translate_align(how: aligns, format: str = 'frac', xfact: float = None, yfact: float = None) -> tuple[float, float]:
|
|
34
|
+
if how is None and (xfact is None or yfact is None):
|
|
35
|
+
raise ValueError("Either 'how' must be provided or both 'xfact' and 'yfact' must be specified.")
|
|
36
|
+
|
|
37
|
+
if how is not None:
|
|
38
|
+
x_a, y_a = parse_alignment(how)
|
|
39
|
+
else:
|
|
40
|
+
x_a, y_a = 'c', 'c'
|
|
41
|
+
|
|
42
|
+
x = xfact if xfact else align_map[x_a]
|
|
43
|
+
y = yfact if yfact else align_map[y_a]
|
|
44
|
+
|
|
45
|
+
if format == 'name':
|
|
46
|
+
x = align_full_names[x_a] if xfact is None else xfact
|
|
47
|
+
y = align_full_names[y_a] if yfact is None else yfact
|
|
48
|
+
|
|
49
|
+
return x, y
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def parse_alignment(inpt: aligns) -> tuple[aligns, aligns]:
|
|
53
|
+
"""reutrns a tuple of (x, y) alignment based on the input string."""
|
|
54
|
+
|
|
55
|
+
def get_inpt_type(inpt):
|
|
56
|
+
if inpt in ['l', 'r']:
|
|
57
|
+
inpt_type = 'horizontal'
|
|
58
|
+
elif inpt in ['t', 'b']:
|
|
59
|
+
inpt_type = 'vertical'
|
|
60
|
+
else:
|
|
61
|
+
inpt_type = 'centered'
|
|
62
|
+
return inpt_type
|
|
63
|
+
|
|
64
|
+
if len(inpt) > 2:
|
|
65
|
+
if '_' in inpt:
|
|
66
|
+
parts = inpt.split('_')
|
|
67
|
+
|
|
68
|
+
if len(parts) == 2:
|
|
69
|
+
inpt = parts[0][0] + parts[1][0]
|
|
70
|
+
else:
|
|
71
|
+
raise ValueError('Input must be a two-part string separated by an underscore.')
|
|
72
|
+
else:
|
|
73
|
+
inpt = inpt[0]
|
|
74
|
+
|
|
75
|
+
x = y = None
|
|
76
|
+
if len(inpt) == 1:
|
|
77
|
+
in_type = get_inpt_type(inpt)
|
|
78
|
+
if in_type == 'horizontal':
|
|
79
|
+
x, y = inpt, 'c'
|
|
80
|
+
elif in_type == 'vertical':
|
|
81
|
+
x, y = 'c', inpt
|
|
82
|
+
else:
|
|
83
|
+
x = y = 'c'
|
|
84
|
+
|
|
85
|
+
elif len(inpt) == 2:
|
|
86
|
+
in_type_a = get_inpt_type(inpt[0])
|
|
87
|
+
in_type_b = get_inpt_type(inpt[1])
|
|
88
|
+
|
|
89
|
+
types = np.array([in_type_a, in_type_b])
|
|
90
|
+
|
|
91
|
+
if in_type_a == in_type_b:
|
|
92
|
+
raise ValueError('Both inputs cannot be of the same kind.')
|
|
93
|
+
|
|
94
|
+
h_idx = np.where(types == 'horizontal')[0]
|
|
95
|
+
v_idx = np.where(types == 'vertical')[0]
|
|
96
|
+
|
|
97
|
+
if len(h_idx) == 0:
|
|
98
|
+
x = 'c'
|
|
99
|
+
elif len(h_idx) == 1:
|
|
100
|
+
x = inpt[h_idx[0]]
|
|
101
|
+
else:
|
|
102
|
+
raise ValueError('More than one horizontal input provided.')
|
|
103
|
+
if len(v_idx) == 0:
|
|
104
|
+
y = 'c'
|
|
105
|
+
elif len(v_idx) == 1:
|
|
106
|
+
y = inpt[v_idx[0]]
|
|
107
|
+
else:
|
|
108
|
+
raise ValueError('More than one vertical input provided.')
|
|
109
|
+
else:
|
|
110
|
+
raise ValueError('Input must be a single character or a two-character string.')
|
|
111
|
+
|
|
112
|
+
return x, y
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
|
|
5
|
+
import matplotlib.pyplot as plt
|
|
6
|
+
|
|
7
|
+
from .. import utils
|
|
8
|
+
from . import inline, styles, tools
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def display(
|
|
12
|
+
theme: str | Theme = 'default',
|
|
13
|
+
**kwargs,
|
|
14
|
+
):
|
|
15
|
+
theme = Theme(source_theme=theme, **kwargs) if isinstance(theme, str) else theme
|
|
16
|
+
theme.apply()
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@dataclass
|
|
20
|
+
class Theme:
|
|
21
|
+
source_theme: str = None
|
|
22
|
+
explicit_rcParams: dict = None
|
|
23
|
+
retina: bool = False
|
|
24
|
+
transparent: bool = False
|
|
25
|
+
figsize: tuple[float, float] = None
|
|
26
|
+
dpi: int = None
|
|
27
|
+
palette: list[str] = None
|
|
28
|
+
cmap: str = None
|
|
29
|
+
text_color: str = None
|
|
30
|
+
line_color: str = None
|
|
31
|
+
ticks: list[str] = None
|
|
32
|
+
minor_visible: bool = None
|
|
33
|
+
spines: bool = None
|
|
34
|
+
margins: float = None
|
|
35
|
+
grid: bool = None
|
|
36
|
+
grid_color: str = None
|
|
37
|
+
grid_alpha: float = None
|
|
38
|
+
grid_linestyle: str = None
|
|
39
|
+
grid_linewidth: float = None
|
|
40
|
+
tick_linewidth: tuple[float, float] = (None, None)
|
|
41
|
+
tick_pad: tuple[float, float] = (None, None)
|
|
42
|
+
spine_linewidth: float = None
|
|
43
|
+
tick_size: tuple[float, float] = (None, None)
|
|
44
|
+
font_family: str = None
|
|
45
|
+
font_size: int = None
|
|
46
|
+
fig_facecolor: str = None
|
|
47
|
+
axes_facecolor: str = None
|
|
48
|
+
labelsize: dict = field(default_factory=lambda: {'axes': None, 'figure': None, 'ticks': None})
|
|
49
|
+
titlesize: dict = field(default_factory=lambda: {'axes': None, 'figure': None})
|
|
50
|
+
titleweight: dict = field(default_factory=lambda: {'axes': None, 'figure': None})
|
|
51
|
+
labelweight: dict = field(default_factory=lambda: {'axes': None, 'figure': None})
|
|
52
|
+
axes_labelpad: float = None
|
|
53
|
+
axes_titlepad: float = None
|
|
54
|
+
inline_config: dict = field(default_factory=dict)
|
|
55
|
+
|
|
56
|
+
def parse_source_theme(self):
|
|
57
|
+
if isinstance(self.source_theme, str):
|
|
58
|
+
base_theme = styles.themes.get(self.source_theme, {})
|
|
59
|
+
return Theme(source_theme=base_theme)
|
|
60
|
+
|
|
61
|
+
elif isinstance(self.source_theme, dict):
|
|
62
|
+
return Theme(**self.source_theme)
|
|
63
|
+
|
|
64
|
+
else:
|
|
65
|
+
return self.source_theme
|
|
66
|
+
|
|
67
|
+
def reset_defaults(self):
|
|
68
|
+
plt.rcdefaults()
|
|
69
|
+
|
|
70
|
+
@property
|
|
71
|
+
def rcDict(self):
|
|
72
|
+
from cycler import cycler
|
|
73
|
+
|
|
74
|
+
tick_unset_val = None if self.ticks is None else False
|
|
75
|
+
tick_t, tick_b, tick_l, tick_r = tools.set_position(positions=self.ticks, unset_value=tick_unset_val)
|
|
76
|
+
|
|
77
|
+
spine_unset_val = None if self.spines is None else False
|
|
78
|
+
spine_t, spine_b, spine_l, spine_r = tools.set_position(positions=self.spines, unset_value=spine_unset_val)
|
|
79
|
+
|
|
80
|
+
prop_cycle = None if self.palette is None else cycler('color', self.palette)
|
|
81
|
+
|
|
82
|
+
rc_dict = {
|
|
83
|
+
key: value
|
|
84
|
+
for key, value in {
|
|
85
|
+
'figure.figsize': self.figsize,
|
|
86
|
+
'figure.dpi': self.dpi,
|
|
87
|
+
'axes.prop_cycle': prop_cycle,
|
|
88
|
+
'image.cmap': self.cmap,
|
|
89
|
+
'xtick.color': self.line_color,
|
|
90
|
+
'ytick.color': self.line_color,
|
|
91
|
+
'axes.grid': self.grid,
|
|
92
|
+
'axes3d.grid': self.grid,
|
|
93
|
+
'polaraxes.grid': self.grid,
|
|
94
|
+
'grid.alpha': self.grid_alpha,
|
|
95
|
+
'grid.color': self.grid_color,
|
|
96
|
+
'grid.linestyle': self.grid_linestyle,
|
|
97
|
+
'grid.linewidth': self.grid_linewidth,
|
|
98
|
+
'text.color': self.text_color,
|
|
99
|
+
'axes.labelcolor': self.text_color,
|
|
100
|
+
'axes.titlecolor': self.text_color,
|
|
101
|
+
'ytick.labelcolor': self.text_color,
|
|
102
|
+
'xtick.labelcolor': self.text_color,
|
|
103
|
+
'axes.edgecolor': self.line_color,
|
|
104
|
+
'axes.facecolor': self.axes_facecolor,
|
|
105
|
+
'figure.facecolor': self.fig_facecolor,
|
|
106
|
+
'font.family': self.font_family,
|
|
107
|
+
'font.size': self.font_size,
|
|
108
|
+
'figure.titlesize': self.titlesize.get('figure', None),
|
|
109
|
+
'axes.titlesize': self.titlesize.get('axes', None),
|
|
110
|
+
'figure.titleweight': self.titleweight.get('figure', None),
|
|
111
|
+
'axes.titleweight': self.titleweight.get('axes', None),
|
|
112
|
+
'figure.labelsize': self.labelsize.get('figure', None),
|
|
113
|
+
'axes.labelsize': self.labelsize.get('axes', None),
|
|
114
|
+
'xtick.labelsize': self.labelsize.get('ticks', None),
|
|
115
|
+
'ytick.labelsize': self.labelsize.get('ticks', None),
|
|
116
|
+
'axes.labelpad': self.axes_labelpad,
|
|
117
|
+
'axes.titlepad': self.axes_titlepad,
|
|
118
|
+
'figure.labelweight': self.labelweight.get('figure', None),
|
|
119
|
+
'axes.labelweight': self.labelweight.get('axes', None),
|
|
120
|
+
'axes.linewidth': self.spine_linewidth,
|
|
121
|
+
'axes.xmargin': self.margins,
|
|
122
|
+
'axes.ymargin': self.margins,
|
|
123
|
+
'axes.zmargin': self.margins,
|
|
124
|
+
'axes.spines.top': spine_t,
|
|
125
|
+
'axes.spines.bottom': spine_b,
|
|
126
|
+
'axes.spines.left': spine_l,
|
|
127
|
+
'axes.spines.right': spine_r,
|
|
128
|
+
'xtick.top': tick_t,
|
|
129
|
+
'xtick.bottom': tick_b,
|
|
130
|
+
'ytick.left': tick_l,
|
|
131
|
+
'ytick.right': tick_r,
|
|
132
|
+
'ytick.major.left': tick_l,
|
|
133
|
+
'ytick.major.right': tick_r,
|
|
134
|
+
'xtick.major.top': tick_t,
|
|
135
|
+
'xtick.major.bottom': tick_b,
|
|
136
|
+
'ytick.minor.left': tick_l,
|
|
137
|
+
'ytick.minor.right': tick_r,
|
|
138
|
+
'xtick.minor.top': tick_t,
|
|
139
|
+
'xtick.minor.bottom': tick_b,
|
|
140
|
+
'ytick.labelleft': tick_l,
|
|
141
|
+
'ytick.labelright': tick_r,
|
|
142
|
+
'xtick.labeltop': tick_t,
|
|
143
|
+
'xtick.labelbottom': tick_b,
|
|
144
|
+
'xtick.major.pad': utils.maj_min_args(self.tick_pad)[0],
|
|
145
|
+
'xtick.minor.pad': utils.maj_min_args(self.tick_pad)[1],
|
|
146
|
+
'ytick.major.pad': utils.maj_min_args(self.tick_pad)[0],
|
|
147
|
+
'ytick.minor.pad': utils.maj_min_args(self.tick_pad)[1],
|
|
148
|
+
'xtick.major.size': utils.maj_min_args(self.tick_size)[0],
|
|
149
|
+
'xtick.minor.size': utils.maj_min_args(self.tick_size)[1],
|
|
150
|
+
'ytick.major.size': utils.maj_min_args(self.tick_size)[0],
|
|
151
|
+
'ytick.minor.size': utils.maj_min_args(self.tick_size)[1],
|
|
152
|
+
'xtick.major.width': utils.maj_min_args(self.tick_linewidth)[0],
|
|
153
|
+
'xtick.minor.width': utils.maj_min_args(self.tick_linewidth)[1],
|
|
154
|
+
'ytick.major.width': utils.maj_min_args(self.tick_linewidth)[0],
|
|
155
|
+
'ytick.minor.width': utils.maj_min_args(self.tick_linewidth)[1],
|
|
156
|
+
'ytick.minor.visible': self.minor_visible,
|
|
157
|
+
'xtick.minor.visible': self.minor_visible,
|
|
158
|
+
}.items()
|
|
159
|
+
if value is not None
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if self.explicit_rcParams is not None:
|
|
163
|
+
rc_dict.update(self.explicit_rcParams)
|
|
164
|
+
|
|
165
|
+
if self.source_theme is not None:
|
|
166
|
+
source = self.parse_source_theme()
|
|
167
|
+
rc_dict = {**source.rcDict, **rc_dict}
|
|
168
|
+
|
|
169
|
+
return rc_dict
|
|
170
|
+
|
|
171
|
+
def apply(self):
|
|
172
|
+
if self.source_theme == 'default':
|
|
173
|
+
print('Theme: resetting to matplotlib defaults')
|
|
174
|
+
plt.rcdefaults()
|
|
175
|
+
|
|
176
|
+
plt.rcParams.update(self.rcDict)
|
|
177
|
+
|
|
178
|
+
inline.inline_config(retina=self.retina, transparent=self.transparent, **self.inline_config)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import matplotlib_inline.backend_inline as mpl_inline
|
|
2
|
+
from matplotlib import rcParams
|
|
3
|
+
|
|
4
|
+
rc_mapping = {
|
|
5
|
+
'dpi': 'figure.dpi',
|
|
6
|
+
'pad_inches': 'savefig.pad_inches',
|
|
7
|
+
'facecolor': 'figure.facecolor',
|
|
8
|
+
'bbox_inches': 'savefig.bbox',
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def from_rc(key):
|
|
13
|
+
return rcParams[rc_mapping[key]] if key in rc_mapping else None
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def inline_config(
|
|
17
|
+
retina: bool = None,
|
|
18
|
+
facecolor: str = 'rc',
|
|
19
|
+
dpi: int | str = 'rc',
|
|
20
|
+
pad_inches: float | str = 'rc',
|
|
21
|
+
bbox_inches: float | str = 'tight',
|
|
22
|
+
transparent: bool = False,
|
|
23
|
+
**kwargs,
|
|
24
|
+
):
|
|
25
|
+
dpi = from_rc('dpi') if dpi == 'rc' else dpi
|
|
26
|
+
pad_inches = from_rc('pad_inches') if pad_inches == 'rc' else pad_inches
|
|
27
|
+
bbox_inches = from_rc('bbox_inches') if bbox_inches == 'rc' else bbox_inches
|
|
28
|
+
facecolor = from_rc('facecolor') if facecolor == 'rc' else facecolor
|
|
29
|
+
|
|
30
|
+
facecolor = 'none' if transparent else facecolor
|
|
31
|
+
|
|
32
|
+
if retina:
|
|
33
|
+
inl_format = 'retina'
|
|
34
|
+
dpi = dpi * 2
|
|
35
|
+
else:
|
|
36
|
+
inl_format = 'png'
|
|
37
|
+
|
|
38
|
+
mpl_inline.set_matplotlib_formats(
|
|
39
|
+
inl_format,
|
|
40
|
+
facecolor=facecolor,
|
|
41
|
+
bbox_inches=bbox_inches,
|
|
42
|
+
dpi=dpi,
|
|
43
|
+
pad_inches=pad_inches,
|
|
44
|
+
**kwargs,
|
|
45
|
+
)
|
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
from collections.abc import Iterable
|
|
2
|
+
from itertools import cycle
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .. import decorators as decs
|
|
7
|
+
from .. import utils
|
|
8
|
+
from . import tools
|
|
9
|
+
|
|
10
|
+
major_grid_style = 'solid'
|
|
11
|
+
minor_grid_style = (0, (1, 2))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def layout(
|
|
15
|
+
axs,
|
|
16
|
+
*,
|
|
17
|
+
axis: str = 'both',
|
|
18
|
+
title: str = None,
|
|
19
|
+
x_label: str = None,
|
|
20
|
+
y_label: str = None,
|
|
21
|
+
abc: str | bool = None,
|
|
22
|
+
make_square: bool = None,
|
|
23
|
+
margins: float = None,
|
|
24
|
+
aspect: str | float | tuple = None,
|
|
25
|
+
ticks: tools._tick_vis = None,
|
|
26
|
+
grid: tools._grid_vis = None,
|
|
27
|
+
minor: bool = None,
|
|
28
|
+
spines: tools._tick_vis = None,
|
|
29
|
+
x_breaks: list[float] = None,
|
|
30
|
+
y_breaks: list[float] = None,
|
|
31
|
+
x_lims: list[float] = None,
|
|
32
|
+
y_lims: list[float] = None,
|
|
33
|
+
x_scale: str = None,
|
|
34
|
+
y_scale: str = None,
|
|
35
|
+
x_tick_labels: list[str] = None,
|
|
36
|
+
y_tick_labels: list[str] = None,
|
|
37
|
+
**kwargs,
|
|
38
|
+
):
|
|
39
|
+
# decompose kwargs into title, label, tick, and grid settings
|
|
40
|
+
title_settings = utils.get_hook_dict(kwargs, 'title', remove_hook=True)
|
|
41
|
+
label_settings = utils.get_hook_dict(kwargs, 'label', remove_hook=True)
|
|
42
|
+
|
|
43
|
+
tick_settings = utils.get_hook_dict(kwargs, 'tick', remove_hook=True)
|
|
44
|
+
grid_settings = utils.get_hook_dict(kwargs, 'grid', remove_hook=False)
|
|
45
|
+
tick_settings.update(grid_settings)
|
|
46
|
+
|
|
47
|
+
# ensure axs is a list
|
|
48
|
+
if not isinstance(axs, Iterable):
|
|
49
|
+
axs = [axs]
|
|
50
|
+
if not isinstance(title, Iterable):
|
|
51
|
+
title = [title]
|
|
52
|
+
|
|
53
|
+
handle_abc_labels(axs, abc, **kwargs)
|
|
54
|
+
|
|
55
|
+
pairs = list(zip(axs, cycle(title)))
|
|
56
|
+
|
|
57
|
+
for ax, title in pairs:
|
|
58
|
+
# handle ticks, grid, and spine visibility
|
|
59
|
+
# NOTE when axis != 'both', ticks=True does weird things... seems to be a matplotlib issue
|
|
60
|
+
handle_tick_settings(ax, axis, ticks, minor, grid, tick_settings)
|
|
61
|
+
|
|
62
|
+
# handle other layout elements
|
|
63
|
+
handle_title(ax, title, title_settings)
|
|
64
|
+
handle_labels(ax, axis, x_label, y_label, label_settings)
|
|
65
|
+
handle_tick_labels(ax, x_tick_labels, y_tick_labels)
|
|
66
|
+
|
|
67
|
+
handle_spines(ax, spines)
|
|
68
|
+
handle_breaks(ax, x_breaks, y_breaks)
|
|
69
|
+
handle_scales(ax, x_scale, y_scale)
|
|
70
|
+
handle_lims(ax, x_lims, y_lims)
|
|
71
|
+
|
|
72
|
+
handle_aspect(ax, aspect)
|
|
73
|
+
|
|
74
|
+
# TODO when x_lim/y_lim are set, margins don't work as expected
|
|
75
|
+
handle_margins(ax, margins, make_square)
|
|
76
|
+
|
|
77
|
+
if make_square is True:
|
|
78
|
+
tools.axis_ratio(ax, yx_ratio=1, margins=margins, how='lims')
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def handle_abc_labels(axs, abc=None, **kwargs):
|
|
82
|
+
if abc:
|
|
83
|
+
ax_labels = np.arange(1, len(axs) + 1)
|
|
84
|
+
if abc == 'ABC':
|
|
85
|
+
ax_labels = [chr(64 + num) for num in ax_labels]
|
|
86
|
+
elif abc == 'abc':
|
|
87
|
+
ax_labels = [chr(96 + num) for num in ax_labels]
|
|
88
|
+
|
|
89
|
+
abc_params = utils.get_hook_dict(kwargs, 'abc')
|
|
90
|
+
abc_params['loc'] = abc_params['loc'] if 'loc' in abc_params else 'tl'
|
|
91
|
+
abc_params['size'] = abc_params['size'] if 'size' in abc_params else 18
|
|
92
|
+
for i, ax in enumerate(axs):
|
|
93
|
+
decs.place_abc_label(
|
|
94
|
+
ax,
|
|
95
|
+
label=ax_labels[i],
|
|
96
|
+
pad=0.2,
|
|
97
|
+
**abc_params,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def handle_tick_grid_vis(ax, axis, ticks, minor, grid, tick_settings):
|
|
102
|
+
tools.set_minor_ticks_by_axis(ax, axis=axis)
|
|
103
|
+
minor = False if minor is None else minor
|
|
104
|
+
|
|
105
|
+
tools.set_tick_visibility(ax, axis=axis, ticks=ticks, minor=minor)
|
|
106
|
+
|
|
107
|
+
# set axis below if no grid zorder is specified to make sure grid lines are below other plot elements
|
|
108
|
+
ax_below = False if 'grid_zorder' in tick_settings else True
|
|
109
|
+
ax.set_axisbelow(ax_below)
|
|
110
|
+
|
|
111
|
+
maj_grid, min_grid = tools.set_grid_visibility(ax, axis=axis, grid=grid, minor=minor, apply=False)
|
|
112
|
+
tick_settings['gridOn'] = [maj_grid, min_grid]
|
|
113
|
+
|
|
114
|
+
# Set default grid style, since rcParams don't offer minor grid style
|
|
115
|
+
if 'grid_linestyle' not in tick_settings:
|
|
116
|
+
tick_settings['grid_linestyle'] = [major_grid_style, minor_grid_style]
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def handle_text_element(getter, setter, text: str = None, params: dict = {}):
|
|
120
|
+
"""Generic helper to get current text if needed and set it with params."""
|
|
121
|
+
if text is None and len(params) == 0:
|
|
122
|
+
return
|
|
123
|
+
|
|
124
|
+
if text is None and len(params) > 0:
|
|
125
|
+
text = getter()
|
|
126
|
+
|
|
127
|
+
setter(text, **params)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def handle_tick_settings(ax, axis, ticks, minor, grid, tick_settings):
|
|
131
|
+
if ticks is None and minor is None and grid is None and len(tick_settings) == 0:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
# first all the visibility settings
|
|
135
|
+
handle_tick_grid_vis(ax, axis, ticks, minor, grid, tick_settings)
|
|
136
|
+
|
|
137
|
+
# tick (and grid) settings are applied separately for major and minor ticks
|
|
138
|
+
majmin_settings = {k: utils.maj_min_args(maj_min=v) for k, v in tick_settings.items()}
|
|
139
|
+
|
|
140
|
+
for i, which in enumerate(['major', 'minor']):
|
|
141
|
+
tick_settings_select = {k: v[i] for k, v in majmin_settings.items()}
|
|
142
|
+
ax.tick_params(axis=axis, which=which, **tick_settings_select)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def handle_spines(ax, spines):
|
|
146
|
+
if spines is not None:
|
|
147
|
+
tools.set_spine_visibility(ax, spines)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def handle_aspect(ax, aspect):
|
|
151
|
+
if aspect is not None:
|
|
152
|
+
aspect = [aspect] if not isinstance(aspect, (list, tuple)) else aspect
|
|
153
|
+
adjustable = None if len(aspect) < 2 else aspect[1]
|
|
154
|
+
aspect_params = {'aspect': aspect[0], 'adjustable': adjustable}
|
|
155
|
+
ax.set_aspect(**aspect_params)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def handle_breaks(ax, x_breaks, y_breaks):
|
|
159
|
+
if x_breaks is not None:
|
|
160
|
+
ax.set_xticks(x_breaks)
|
|
161
|
+
|
|
162
|
+
if y_breaks is not None:
|
|
163
|
+
ax.set_yticks(y_breaks)
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
def handle_scales(ax, x_scale, y_scale):
|
|
167
|
+
if y_scale is not None:
|
|
168
|
+
scale_params = tools.parse_scale(scale=y_scale)
|
|
169
|
+
ax.set_yscale(**scale_params)
|
|
170
|
+
|
|
171
|
+
if x_scale is not None:
|
|
172
|
+
scale_params = tools.parse_scale(scale=x_scale)
|
|
173
|
+
ax.set_xscale(**scale_params)
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def handle_lims(ax, x_lims, y_lims):
|
|
177
|
+
if y_lims is not None:
|
|
178
|
+
ax.set_ylim(y_lims)
|
|
179
|
+
|
|
180
|
+
if x_lims is not None:
|
|
181
|
+
ax.set_xlim(x_lims)
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def handle_title(ax, title, title_params):
|
|
185
|
+
if title is None and len(title_params) == 0:
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
if title is None:
|
|
189
|
+
title = ax.get_title()
|
|
190
|
+
title = None if len(title) == 0 else title
|
|
191
|
+
|
|
192
|
+
if title is not None or len(title_params) > 0:
|
|
193
|
+
handle_text_element(ax.get_title, ax.set_title, title, title_params)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def handle_labels(ax, axis, x_label, y_label, label_params):
|
|
197
|
+
if x_label is None and y_label is None and len(label_params) == 0:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
loc_lookup = {
|
|
201
|
+
'x': {'start': 'left', 'center': 'center', 'end': 'right'},
|
|
202
|
+
'y': {'start': 'bottom', 'center': 'center', 'end': 'top'},
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
def normalize_params(axis_key: str, params: dict | None) -> dict:
|
|
206
|
+
params = params or {}
|
|
207
|
+
loc = params.get('loc')
|
|
208
|
+
if loc is not None:
|
|
209
|
+
try:
|
|
210
|
+
params['loc'] = loc_lookup[axis_key][loc]
|
|
211
|
+
except KeyError:
|
|
212
|
+
raise ValueError(
|
|
213
|
+
f"Invalid {axis_key} label loc '{loc}'. Valid options are {list(loc_lookup[axis_key])}."
|
|
214
|
+
)
|
|
215
|
+
return params
|
|
216
|
+
|
|
217
|
+
x_label_params = normalize_params('x', label_params.copy()) if axis in ['x', 'both'] else {}
|
|
218
|
+
y_label_params = normalize_params('y', label_params.copy()) if axis in ['y', 'both'] else {}
|
|
219
|
+
|
|
220
|
+
handle_text_element(ax.get_xlabel, ax.set_xlabel, x_label, x_label_params)
|
|
221
|
+
handle_text_element(ax.get_ylabel, ax.set_ylabel, y_label, y_label_params)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def handle_tick_labels(ax, x_tick_labels, y_tick_labels):
|
|
225
|
+
if x_tick_labels is not None:
|
|
226
|
+
ax.set_xticklabels(x_tick_labels)
|
|
227
|
+
|
|
228
|
+
if y_tick_labels is not None:
|
|
229
|
+
ax.set_yticklabels(y_tick_labels)
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def handle_margins(ax, margins, make_square):
|
|
233
|
+
if margins is not None and not make_square:
|
|
234
|
+
xmargin, ymargin = utils.maj_min_args(margins)
|
|
235
|
+
|
|
236
|
+
ax.set_xmargin(xmargin)
|
|
237
|
+
ax.set_ymargin(ymargin)
|