pylocuszoom 0.1.0__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.
- pylocuszoom/__init__.py +120 -0
- pylocuszoom/backends/__init__.py +52 -0
- pylocuszoom/backends/base.py +341 -0
- pylocuszoom/backends/bokeh_backend.py +441 -0
- pylocuszoom/backends/matplotlib_backend.py +288 -0
- pylocuszoom/backends/plotly_backend.py +474 -0
- pylocuszoom/colors.py +107 -0
- pylocuszoom/eqtl.py +218 -0
- pylocuszoom/gene_track.py +311 -0
- pylocuszoom/labels.py +118 -0
- pylocuszoom/ld.py +209 -0
- pylocuszoom/logging.py +153 -0
- pylocuszoom/plotter.py +733 -0
- pylocuszoom/recombination.py +432 -0
- pylocuszoom/reference_data/__init__.py +4 -0
- pylocuszoom/utils.py +194 -0
- pylocuszoom-0.1.0.dist-info/METADATA +367 -0
- pylocuszoom-0.1.0.dist-info/RECORD +20 -0
- pylocuszoom-0.1.0.dist-info/WHEEL +4 -0
- pylocuszoom-0.1.0.dist-info/licenses/LICENSE.md +17 -0
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
"""Matplotlib backend for pyLocusZoom.
|
|
2
|
+
|
|
3
|
+
Default backend providing static publication-quality plots.
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Any, List, Optional, Tuple, Union
|
|
7
|
+
|
|
8
|
+
import matplotlib.pyplot as plt
|
|
9
|
+
import pandas as pd
|
|
10
|
+
from matplotlib.axes import Axes
|
|
11
|
+
from matplotlib.figure import Figure
|
|
12
|
+
from matplotlib.patches import Rectangle
|
|
13
|
+
from matplotlib.ticker import FuncFormatter, MaxNLocator
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class MatplotlibBackend:
|
|
17
|
+
"""Matplotlib backend for static plot generation.
|
|
18
|
+
|
|
19
|
+
This is the default backend, producing publication-quality static plots
|
|
20
|
+
suitable for papers and presentations.
|
|
21
|
+
"""
|
|
22
|
+
|
|
23
|
+
def __init__(self) -> None:
|
|
24
|
+
"""Initialize the matplotlib backend."""
|
|
25
|
+
pass
|
|
26
|
+
|
|
27
|
+
def create_figure(
|
|
28
|
+
self,
|
|
29
|
+
n_panels: int,
|
|
30
|
+
height_ratios: List[float],
|
|
31
|
+
figsize: Tuple[float, float],
|
|
32
|
+
sharex: bool = True,
|
|
33
|
+
) -> Tuple[Figure, List[Axes]]:
|
|
34
|
+
"""Create a figure with multiple panels.
|
|
35
|
+
|
|
36
|
+
Args:
|
|
37
|
+
n_panels: Number of vertical panels.
|
|
38
|
+
height_ratios: Relative heights for each panel.
|
|
39
|
+
figsize: Figure size as (width, height).
|
|
40
|
+
sharex: Whether panels share the x-axis.
|
|
41
|
+
|
|
42
|
+
Returns:
|
|
43
|
+
Tuple of (figure, list of axes).
|
|
44
|
+
"""
|
|
45
|
+
# Prevent auto-display in interactive environments
|
|
46
|
+
plt.ioff()
|
|
47
|
+
|
|
48
|
+
if n_panels == 1:
|
|
49
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
50
|
+
return fig, [ax]
|
|
51
|
+
|
|
52
|
+
fig, axes = plt.subplots(
|
|
53
|
+
n_panels,
|
|
54
|
+
1,
|
|
55
|
+
figsize=figsize,
|
|
56
|
+
height_ratios=height_ratios,
|
|
57
|
+
sharex=sharex,
|
|
58
|
+
gridspec_kw={"hspace": 0},
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
# Ensure axes is always a list
|
|
62
|
+
if n_panels == 1:
|
|
63
|
+
axes = [axes]
|
|
64
|
+
|
|
65
|
+
return fig, list(axes)
|
|
66
|
+
|
|
67
|
+
def scatter(
|
|
68
|
+
self,
|
|
69
|
+
ax: Axes,
|
|
70
|
+
x: pd.Series,
|
|
71
|
+
y: pd.Series,
|
|
72
|
+
colors: Union[str, List[str], pd.Series],
|
|
73
|
+
sizes: Union[float, List[float], pd.Series] = 60,
|
|
74
|
+
marker: str = "o",
|
|
75
|
+
edgecolor: str = "black",
|
|
76
|
+
linewidth: float = 0.5,
|
|
77
|
+
zorder: int = 2,
|
|
78
|
+
hover_data: Optional[pd.DataFrame] = None,
|
|
79
|
+
label: Optional[str] = None,
|
|
80
|
+
) -> Any:
|
|
81
|
+
"""Create a scatter plot on the given axes.
|
|
82
|
+
|
|
83
|
+
Note: hover_data is ignored for matplotlib (static plots).
|
|
84
|
+
"""
|
|
85
|
+
return ax.scatter(
|
|
86
|
+
x,
|
|
87
|
+
y,
|
|
88
|
+
c=colors,
|
|
89
|
+
s=sizes,
|
|
90
|
+
marker=marker,
|
|
91
|
+
edgecolor=edgecolor,
|
|
92
|
+
linewidth=linewidth,
|
|
93
|
+
zorder=zorder,
|
|
94
|
+
label=label,
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
def line(
|
|
98
|
+
self,
|
|
99
|
+
ax: Axes,
|
|
100
|
+
x: pd.Series,
|
|
101
|
+
y: pd.Series,
|
|
102
|
+
color: str = "blue",
|
|
103
|
+
linewidth: float = 1.5,
|
|
104
|
+
alpha: float = 1.0,
|
|
105
|
+
linestyle: str = "-",
|
|
106
|
+
zorder: int = 1,
|
|
107
|
+
label: Optional[str] = None,
|
|
108
|
+
) -> Any:
|
|
109
|
+
"""Create a line plot on the given axes."""
|
|
110
|
+
(line,) = ax.plot(
|
|
111
|
+
x,
|
|
112
|
+
y,
|
|
113
|
+
color=color,
|
|
114
|
+
linewidth=linewidth,
|
|
115
|
+
alpha=alpha,
|
|
116
|
+
linestyle=linestyle,
|
|
117
|
+
zorder=zorder,
|
|
118
|
+
label=label,
|
|
119
|
+
)
|
|
120
|
+
return line
|
|
121
|
+
|
|
122
|
+
def fill_between(
|
|
123
|
+
self,
|
|
124
|
+
ax: Axes,
|
|
125
|
+
x: pd.Series,
|
|
126
|
+
y1: Union[float, pd.Series],
|
|
127
|
+
y2: Union[float, pd.Series],
|
|
128
|
+
color: str = "blue",
|
|
129
|
+
alpha: float = 0.3,
|
|
130
|
+
zorder: int = 0,
|
|
131
|
+
) -> Any:
|
|
132
|
+
"""Fill area between two y-values."""
|
|
133
|
+
return ax.fill_between(x, y1, y2, color=color, alpha=alpha, zorder=zorder)
|
|
134
|
+
|
|
135
|
+
def axhline(
|
|
136
|
+
self,
|
|
137
|
+
ax: Axes,
|
|
138
|
+
y: float,
|
|
139
|
+
color: str = "grey",
|
|
140
|
+
linestyle: str = "--",
|
|
141
|
+
linewidth: float = 1.0,
|
|
142
|
+
zorder: int = 1,
|
|
143
|
+
) -> Any:
|
|
144
|
+
"""Add a horizontal line across the axes."""
|
|
145
|
+
return ax.axhline(
|
|
146
|
+
y=y, color=color, linestyle=linestyle, linewidth=linewidth, zorder=zorder
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
def add_text(
|
|
150
|
+
self,
|
|
151
|
+
ax: Axes,
|
|
152
|
+
x: float,
|
|
153
|
+
y: float,
|
|
154
|
+
text: str,
|
|
155
|
+
fontsize: int = 10,
|
|
156
|
+
ha: str = "center",
|
|
157
|
+
va: str = "bottom",
|
|
158
|
+
rotation: float = 0,
|
|
159
|
+
color: str = "black",
|
|
160
|
+
) -> Any:
|
|
161
|
+
"""Add text annotation to axes."""
|
|
162
|
+
return ax.text(
|
|
163
|
+
x, y, text, fontsize=fontsize, ha=ha, va=va, rotation=rotation, color=color
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
def add_rectangle(
|
|
167
|
+
self,
|
|
168
|
+
ax: Axes,
|
|
169
|
+
xy: Tuple[float, float],
|
|
170
|
+
width: float,
|
|
171
|
+
height: float,
|
|
172
|
+
facecolor: str = "blue",
|
|
173
|
+
edgecolor: str = "black",
|
|
174
|
+
linewidth: float = 0.5,
|
|
175
|
+
zorder: int = 2,
|
|
176
|
+
) -> Any:
|
|
177
|
+
"""Add a rectangle patch to axes."""
|
|
178
|
+
rect = Rectangle(
|
|
179
|
+
xy,
|
|
180
|
+
width,
|
|
181
|
+
height,
|
|
182
|
+
facecolor=facecolor,
|
|
183
|
+
edgecolor=edgecolor,
|
|
184
|
+
linewidth=linewidth,
|
|
185
|
+
zorder=zorder,
|
|
186
|
+
)
|
|
187
|
+
ax.add_patch(rect)
|
|
188
|
+
return rect
|
|
189
|
+
|
|
190
|
+
def set_xlim(self, ax: Axes, left: float, right: float) -> None:
|
|
191
|
+
"""Set x-axis limits."""
|
|
192
|
+
ax.set_xlim(left, right)
|
|
193
|
+
|
|
194
|
+
def set_ylim(self, ax: Axes, bottom: float, top: float) -> None:
|
|
195
|
+
"""Set y-axis limits."""
|
|
196
|
+
ax.set_ylim(bottom, top)
|
|
197
|
+
|
|
198
|
+
def set_xlabel(self, ax: Axes, label: str, fontsize: int = 12) -> None:
|
|
199
|
+
"""Set x-axis label."""
|
|
200
|
+
ax.set_xlabel(label, fontsize=fontsize)
|
|
201
|
+
|
|
202
|
+
def set_ylabel(self, ax: Axes, label: str, fontsize: int = 12) -> None:
|
|
203
|
+
"""Set y-axis label."""
|
|
204
|
+
ax.set_ylabel(label, fontsize=fontsize)
|
|
205
|
+
|
|
206
|
+
def set_title(self, ax: Axes, title: str, fontsize: int = 14) -> None:
|
|
207
|
+
"""Set panel title."""
|
|
208
|
+
ax.set_title(title, fontsize=fontsize)
|
|
209
|
+
|
|
210
|
+
def create_twin_axis(self, ax: Axes) -> Axes:
|
|
211
|
+
"""Create a secondary y-axis sharing the same x-axis."""
|
|
212
|
+
return ax.twinx()
|
|
213
|
+
|
|
214
|
+
def add_legend(
|
|
215
|
+
self,
|
|
216
|
+
ax: Axes,
|
|
217
|
+
handles: List[Any],
|
|
218
|
+
labels: List[str],
|
|
219
|
+
loc: str = "upper left",
|
|
220
|
+
title: Optional[str] = None,
|
|
221
|
+
) -> Any:
|
|
222
|
+
"""Add a legend to the axes."""
|
|
223
|
+
return ax.legend(
|
|
224
|
+
handles=handles,
|
|
225
|
+
labels=labels,
|
|
226
|
+
loc=loc,
|
|
227
|
+
title=title,
|
|
228
|
+
fontsize=9,
|
|
229
|
+
frameon=True,
|
|
230
|
+
framealpha=0.9,
|
|
231
|
+
title_fontsize=10,
|
|
232
|
+
handlelength=1.5,
|
|
233
|
+
handleheight=1.0,
|
|
234
|
+
labelspacing=0.4,
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
def hide_spines(self, ax: Axes, spines: List[str]) -> None:
|
|
238
|
+
"""Hide specified axis spines."""
|
|
239
|
+
for spine in spines:
|
|
240
|
+
ax.spines[spine].set_visible(False)
|
|
241
|
+
|
|
242
|
+
def format_xaxis_mb(self, ax: Axes) -> None:
|
|
243
|
+
"""Format x-axis to show megabase values."""
|
|
244
|
+
ax.xaxis.set_major_formatter(FuncFormatter(lambda x, _: f"{x / 1e6:.2f}"))
|
|
245
|
+
ax.xaxis.set_major_locator(MaxNLocator(nbins=6))
|
|
246
|
+
|
|
247
|
+
def save(
|
|
248
|
+
self,
|
|
249
|
+
fig: Figure,
|
|
250
|
+
path: str,
|
|
251
|
+
dpi: int = 150,
|
|
252
|
+
bbox_inches: str = "tight",
|
|
253
|
+
) -> None:
|
|
254
|
+
"""Save figure to file."""
|
|
255
|
+
fig.savefig(path, dpi=dpi, bbox_inches=bbox_inches)
|
|
256
|
+
|
|
257
|
+
def show(self, fig: Figure) -> None:
|
|
258
|
+
"""Display the figure."""
|
|
259
|
+
plt.ion()
|
|
260
|
+
plt.show()
|
|
261
|
+
|
|
262
|
+
def close(self, fig: Figure) -> None:
|
|
263
|
+
"""Close the figure and free resources."""
|
|
264
|
+
plt.close(fig)
|
|
265
|
+
|
|
266
|
+
def finalize_layout(
|
|
267
|
+
self,
|
|
268
|
+
fig: Figure,
|
|
269
|
+
left: float = 0.08,
|
|
270
|
+
right: float = 0.95,
|
|
271
|
+
top: float = 0.95,
|
|
272
|
+
bottom: float = 0.1,
|
|
273
|
+
hspace: float = 0.08,
|
|
274
|
+
) -> None:
|
|
275
|
+
"""Adjust subplot layout parameters.
|
|
276
|
+
|
|
277
|
+
Args:
|
|
278
|
+
fig: Figure object.
|
|
279
|
+
left: Left margin.
|
|
280
|
+
right: Right margin.
|
|
281
|
+
top: Top margin.
|
|
282
|
+
bottom: Bottom margin.
|
|
283
|
+
hspace: Height space between subplots.
|
|
284
|
+
"""
|
|
285
|
+
fig.subplots_adjust(
|
|
286
|
+
left=left, right=right, top=top, bottom=bottom, hspace=hspace
|
|
287
|
+
)
|
|
288
|
+
plt.ion()
|