tlogo 0.1.2.dev0__tar.gz → 0.1.3.dev0__tar.gz
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.
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/PKG-INFO +2 -1
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/pyproject.toml +1 -0
- tlogo-0.1.3.dev0/src/tlogo/__init__.py +23 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/_version.py +3 -3
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/logo.py +65 -43
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/window.py +9 -21
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/PKG-INFO +2 -1
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/SOURCES.txt +1 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/requires.txt +1 -0
- tlogo-0.1.3.dev0/tests/test_draw_logo.py +143 -0
- tlogo-0.1.2.dev0/src/tlogo/__init__.py +0 -8
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.github/workflows/ci.yml +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.github/workflows/publish.yml +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/.gitignore +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/LICENSE +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/README.md +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/examples/example.fasta +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/examples/window_logo.png +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/setup.cfg +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/cli.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/constants.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/hxb2.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/io.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo/matrix.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/dependency_links.txt +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/entry_points.txt +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/src/tlogo.egg-info/top_level.txt +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/conftest.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_cli.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_hxb2.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_io.py +0 -0
- {tlogo-0.1.2.dev0 → tlogo-0.1.3.dev0}/tests/test_matrix.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tlogo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3.dev0
|
|
4
4
|
Summary: Sequence logo generator for aligned FASTA files
|
|
5
5
|
Author-email: Troy Sincomb <troysincomb@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,6 +24,7 @@ Requires-Dist: biopython>=1.80
|
|
|
24
24
|
Requires-Dist: matplotlib>=3.5
|
|
25
25
|
Requires-Dist: logomaker>=0.8
|
|
26
26
|
Requires-Dist: pandas>=1.3
|
|
27
|
+
Requires-Dist: patchworklib>=0.6
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
30
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""tlogo — Sequence logo generator for aligned FASTA files."""
|
|
2
|
+
|
|
3
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
4
|
+
|
|
5
|
+
from tlogo.hxb2 import build_hxb2_map, build_reverse_hxb2_map
|
|
6
|
+
from tlogo.io import parse_alignment
|
|
7
|
+
from tlogo.logo import apply_nature_style, draw_logo
|
|
8
|
+
from tlogo.matrix import build_logo_matrix
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
__version__ = version("tlogo")
|
|
12
|
+
except PackageNotFoundError:
|
|
13
|
+
__version__ = "0.0.0"
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"__version__",
|
|
17
|
+
"apply_nature_style",
|
|
18
|
+
"build_hxb2_map",
|
|
19
|
+
"build_logo_matrix",
|
|
20
|
+
"build_reverse_hxb2_map",
|
|
21
|
+
"draw_logo",
|
|
22
|
+
"parse_alignment",
|
|
23
|
+
]
|
|
@@ -28,7 +28,7 @@ version_tuple: VERSION_TUPLE
|
|
|
28
28
|
commit_id: COMMIT_ID
|
|
29
29
|
__commit_id__: COMMIT_ID
|
|
30
30
|
|
|
31
|
-
__version__ = version = '0.1.
|
|
32
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
31
|
+
__version__ = version = '0.1.3.dev0'
|
|
32
|
+
__version_tuple__ = version_tuple = (0, 1, 3, 'dev0')
|
|
33
33
|
|
|
34
|
-
__commit_id__ = commit_id = '
|
|
34
|
+
__commit_id__ = commit_id = 'g302e8747d'
|
|
@@ -60,6 +60,63 @@ def apply_nature_style() -> None:
|
|
|
60
60
|
})
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
def draw_logo(
|
|
64
|
+
ax: plt.Axes,
|
|
65
|
+
matrix: pd.DataFrame,
|
|
66
|
+
labels: list[str],
|
|
67
|
+
title: str = "",
|
|
68
|
+
color_scheme: str = "chemistry",
|
|
69
|
+
matrix_type: str = "information",
|
|
70
|
+
) -> logomaker.Logo:
|
|
71
|
+
"""Draw a sequence logo onto any matplotlib-compatible Axes.
|
|
72
|
+
|
|
73
|
+
This is the primary composable API for tlogo. It draws onto whatever
|
|
74
|
+
Axes it receives — including a ``patchworklib.Brick`` — without creating
|
|
75
|
+
a figure, calling ``tight_layout()``, saving, or closing. Those are the
|
|
76
|
+
caller's responsibility.
|
|
77
|
+
|
|
78
|
+
Args:
|
|
79
|
+
ax: Target matplotlib Axes (or patchworklib Brick).
|
|
80
|
+
matrix: Logomaker-compatible DataFrame (information, probability,
|
|
81
|
+
or counts).
|
|
82
|
+
labels: X-axis tick labels (e.g. HxB2 positions).
|
|
83
|
+
title: Panel title string. Pass ``""`` for no title.
|
|
84
|
+
color_scheme: Amino acid color scheme name for logomaker.
|
|
85
|
+
matrix_type: One of ``"information"``, ``"probability"``, or
|
|
86
|
+
``"counts"``. Controls the y-axis label.
|
|
87
|
+
|
|
88
|
+
Returns:
|
|
89
|
+
The ``logomaker.Logo`` instance for further customization.
|
|
90
|
+
"""
|
|
91
|
+
logo = logomaker.Logo(
|
|
92
|
+
matrix,
|
|
93
|
+
ax=ax,
|
|
94
|
+
color_scheme=color_scheme,
|
|
95
|
+
font_name="Arial",
|
|
96
|
+
stack_order="big_on_top",
|
|
97
|
+
vpad=0.04,
|
|
98
|
+
baseline_width=0.4,
|
|
99
|
+
)
|
|
100
|
+
logo.style_spines(spines=["top", "right"], visible=False)
|
|
101
|
+
logo.style_spines(spines=["left", "bottom"], visible=True)
|
|
102
|
+
|
|
103
|
+
n_positions = len(labels)
|
|
104
|
+
ax.set_xticks(range(n_positions))
|
|
105
|
+
ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
|
|
106
|
+
ax.set_ylabel(
|
|
107
|
+
_y_axis_label(matrix_type), fontsize=FONT_AXIS_LABEL, labelpad=4
|
|
108
|
+
)
|
|
109
|
+
if title:
|
|
110
|
+
ax.set_title(
|
|
111
|
+
title, fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left"
|
|
112
|
+
)
|
|
113
|
+
ax.yaxis.set_major_locator(
|
|
114
|
+
ticker.MaxNLocator(nbins=3, prune="both")
|
|
115
|
+
)
|
|
116
|
+
|
|
117
|
+
return logo
|
|
118
|
+
|
|
119
|
+
|
|
63
120
|
def _y_axis_label(matrix_type: str) -> str:
|
|
64
121
|
"""Return y-axis label for the given matrix type.
|
|
65
122
|
|
|
@@ -120,7 +177,6 @@ def render_logo_plot(
|
|
|
120
177
|
"""
|
|
121
178
|
apply_nature_style()
|
|
122
179
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
123
|
-
y_label = _y_axis_label(matrix_type)
|
|
124
180
|
n_positions = len(labels)
|
|
125
181
|
fmt = output_path.suffix.lstrip(".") or "pdf"
|
|
126
182
|
|
|
@@ -136,27 +192,11 @@ def render_logo_plot(
|
|
|
136
192
|
)
|
|
137
193
|
fig, ax = plt.subplots(1, 1, figsize=(fig_width, PANEL_HEIGHT))
|
|
138
194
|
|
|
139
|
-
|
|
140
|
-
matrix,
|
|
141
|
-
|
|
195
|
+
draw_logo(
|
|
196
|
+
ax, matrix, labels,
|
|
197
|
+
title=f"{title} \u2014 {n_positions} selected positions",
|
|
142
198
|
color_scheme=color_scheme,
|
|
143
|
-
|
|
144
|
-
stack_order="big_on_top",
|
|
145
|
-
vpad=0.04,
|
|
146
|
-
baseline_width=0.4,
|
|
147
|
-
)
|
|
148
|
-
logo.style_spines(spines=["top", "right"], visible=False)
|
|
149
|
-
logo.style_spines(spines=["left", "bottom"], visible=True)
|
|
150
|
-
|
|
151
|
-
ax.set_xticks(range(n_positions))
|
|
152
|
-
ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
|
|
153
|
-
ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
|
|
154
|
-
ax.set_title(
|
|
155
|
-
f"{title} \u2014 {n_positions} selected positions",
|
|
156
|
-
fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left",
|
|
157
|
-
)
|
|
158
|
-
ax.yaxis.set_major_locator(
|
|
159
|
-
ticker.MaxNLocator(nbins=3, prune="both")
|
|
199
|
+
matrix_type=matrix_type,
|
|
160
200
|
)
|
|
161
201
|
|
|
162
202
|
plt.tight_layout(pad=0.4)
|
|
@@ -184,29 +224,11 @@ def render_logo_plot(
|
|
|
184
224
|
sub_matrix = matrix.iloc[row_indices, :].copy()
|
|
185
225
|
sub_matrix.index = range(len(row_indices))
|
|
186
226
|
|
|
187
|
-
|
|
188
|
-
sub_matrix,
|
|
189
|
-
|
|
227
|
+
draw_logo(
|
|
228
|
+
ax, sub_matrix, sub_labels,
|
|
229
|
+
title=f"{title} \u2014 {region_name}",
|
|
190
230
|
color_scheme=color_scheme,
|
|
191
|
-
|
|
192
|
-
stack_order="big_on_top",
|
|
193
|
-
vpad=0.04,
|
|
194
|
-
baseline_width=0.4,
|
|
195
|
-
)
|
|
196
|
-
logo.style_spines(spines=["top", "right"], visible=False)
|
|
197
|
-
logo.style_spines(spines=["left", "bottom"], visible=True)
|
|
198
|
-
|
|
199
|
-
ax.set_xticks(range(len(row_indices)))
|
|
200
|
-
ax.set_xticklabels(
|
|
201
|
-
sub_labels, rotation=90, fontsize=FONT_TICK_LABEL
|
|
202
|
-
)
|
|
203
|
-
ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
|
|
204
|
-
ax.set_title(
|
|
205
|
-
f"{title} \u2014 {region_name}",
|
|
206
|
-
fontsize=FONT_TITLE, fontweight="bold", pad=4, loc="left",
|
|
207
|
-
)
|
|
208
|
-
ax.yaxis.set_major_locator(
|
|
209
|
-
ticker.MaxNLocator(nbins=3, prune="both")
|
|
231
|
+
matrix_type=matrix_type,
|
|
210
232
|
)
|
|
211
233
|
|
|
212
234
|
plt.tight_layout(pad=0.4, h_pad=0.6)
|
|
@@ -5,21 +5,18 @@ from __future__ import annotations
|
|
|
5
5
|
from pathlib import Path
|
|
6
6
|
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
|
-
import matplotlib.ticker as ticker
|
|
9
8
|
import logomaker
|
|
10
9
|
|
|
11
10
|
from tlogo.constants import (
|
|
12
11
|
FONT_ANIMAL_LABEL,
|
|
13
|
-
FONT_AXIS_LABEL,
|
|
14
12
|
FONT_SUPTITLE,
|
|
15
|
-
FONT_TICK_LABEL,
|
|
16
13
|
NATURE_DOUBLE_COL,
|
|
17
14
|
NATURE_SINGLE_COL,
|
|
18
15
|
PANEL_HEIGHT,
|
|
19
16
|
)
|
|
20
17
|
from tlogo.hxb2 import HxB2Position, build_reverse_hxb2_map, get_env_region
|
|
21
18
|
from tlogo.io import sort_animal_groups
|
|
22
|
-
from tlogo.logo import _save_figure, _y_axis_label, apply_nature_style
|
|
19
|
+
from tlogo.logo import _save_figure, _y_axis_label, apply_nature_style, draw_logo
|
|
23
20
|
from tlogo.matrix import find_variant_positions, resolve_window_cols
|
|
24
21
|
|
|
25
22
|
|
|
@@ -95,24 +92,15 @@ def render_window_logo(
|
|
|
95
92
|
)
|
|
96
93
|
matrix.index = range(len(matrix))
|
|
97
94
|
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
logo.style_spines(spines=["top", "right"], visible=False)
|
|
104
|
-
logo.style_spines(spines=["left", "bottom"], visible=True)
|
|
105
|
-
|
|
106
|
-
ax.set_xticks(range(len(selected_cols)))
|
|
107
|
-
ax.set_xticklabels(labels, rotation=90, fontsize=FONT_TICK_LABEL)
|
|
108
|
-
ax.set_ylabel(y_label, fontsize=FONT_AXIS_LABEL, labelpad=4)
|
|
109
|
-
ax.set_title(
|
|
110
|
-
f"{animal_name} (n={n_seqs})",
|
|
111
|
-
fontsize=FONT_ANIMAL_LABEL, loc="left",
|
|
112
|
-
)
|
|
113
|
-
ax.yaxis.set_major_locator(
|
|
114
|
-
ticker.MaxNLocator(nbins=3, prune="both")
|
|
95
|
+
draw_logo(
|
|
96
|
+
ax, matrix, labels,
|
|
97
|
+
title=f"{animal_name} (n={n_seqs})",
|
|
98
|
+
color_scheme=color_scheme,
|
|
99
|
+
matrix_type=matrix_type,
|
|
115
100
|
)
|
|
101
|
+
# Window mode uses a smaller title font than the standard logo
|
|
102
|
+
ax.title.set_fontsize(FONT_ANIMAL_LABEL)
|
|
103
|
+
ax.title.set_fontweight("normal")
|
|
116
104
|
|
|
117
105
|
fig.tight_layout(rect=[0, 0, 1, 0.95])
|
|
118
106
|
_save_figure(fig, output_path, fmt)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tlogo
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.3.dev0
|
|
4
4
|
Summary: Sequence logo generator for aligned FASTA files
|
|
5
5
|
Author-email: Troy Sincomb <troysincomb@gmail.com>
|
|
6
6
|
License: MIT
|
|
@@ -24,6 +24,7 @@ Requires-Dist: biopython>=1.80
|
|
|
24
24
|
Requires-Dist: matplotlib>=3.5
|
|
25
25
|
Requires-Dist: logomaker>=0.8
|
|
26
26
|
Requires-Dist: pandas>=1.3
|
|
27
|
+
Requires-Dist: patchworklib>=0.6
|
|
27
28
|
Provides-Extra: dev
|
|
28
29
|
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
30
|
Requires-Dist: hypothesis>=6.0; extra == "dev"
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
"""Tests for the draw_logo() composable API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import matplotlib
|
|
6
|
+
import matplotlib.pyplot as plt
|
|
7
|
+
import logomaker
|
|
8
|
+
import pytest
|
|
9
|
+
|
|
10
|
+
from tlogo.logo import apply_nature_style, draw_logo
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
# ---------------------------------------------------------------------------
|
|
14
|
+
# Helpers
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
|
|
17
|
+
def _make_matrix() -> tuple:
|
|
18
|
+
"""Build a small information matrix for testing."""
|
|
19
|
+
subsequences = ["ACDEF", "ACDEY", "ACDEW", "ACDEK", "ACDEF"]
|
|
20
|
+
matrix = logomaker.alignment_to_matrix(
|
|
21
|
+
subsequences,
|
|
22
|
+
to_type="information",
|
|
23
|
+
characters_to_ignore=".-",
|
|
24
|
+
pseudocount=1.0,
|
|
25
|
+
)
|
|
26
|
+
matrix.index = range(len(matrix))
|
|
27
|
+
labels = [str(i + 1) for i in range(len(matrix))]
|
|
28
|
+
return matrix, labels
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------------------------------
|
|
32
|
+
# Tests
|
|
33
|
+
# ---------------------------------------------------------------------------
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class TestDrawLogo:
|
|
37
|
+
"""Tests for draw_logo on plain matplotlib Axes."""
|
|
38
|
+
|
|
39
|
+
def setup_method(self) -> None:
|
|
40
|
+
matplotlib.use("Agg")
|
|
41
|
+
|
|
42
|
+
def test_returns_logomaker_logo(self) -> None:
|
|
43
|
+
matrix, labels = _make_matrix()
|
|
44
|
+
_fig, ax = plt.subplots()
|
|
45
|
+
result = draw_logo(ax, matrix, labels)
|
|
46
|
+
assert isinstance(result, logomaker.Logo)
|
|
47
|
+
plt.close("all")
|
|
48
|
+
|
|
49
|
+
def test_sets_xtick_labels(self) -> None:
|
|
50
|
+
matrix, labels = _make_matrix()
|
|
51
|
+
_fig, ax = plt.subplots()
|
|
52
|
+
draw_logo(ax, matrix, labels)
|
|
53
|
+
tick_labels = [t.get_text() for t in ax.get_xticklabels()]
|
|
54
|
+
assert tick_labels == labels
|
|
55
|
+
plt.close("all")
|
|
56
|
+
|
|
57
|
+
def test_sets_title(self) -> None:
|
|
58
|
+
matrix, labels = _make_matrix()
|
|
59
|
+
_fig, ax = plt.subplots()
|
|
60
|
+
draw_logo(ax, matrix, labels, title="My Title")
|
|
61
|
+
# draw_logo sets title with loc="left"
|
|
62
|
+
assert ax.get_title(loc="left") == "My Title"
|
|
63
|
+
plt.close("all")
|
|
64
|
+
|
|
65
|
+
def test_empty_title_produces_no_title(self) -> None:
|
|
66
|
+
matrix, labels = _make_matrix()
|
|
67
|
+
_fig, ax = plt.subplots()
|
|
68
|
+
draw_logo(ax, matrix, labels, title="")
|
|
69
|
+
assert ax.get_title(loc="left") == ""
|
|
70
|
+
plt.close("all")
|
|
71
|
+
|
|
72
|
+
@pytest.mark.parametrize(
|
|
73
|
+
"matrix_type, expected_ylabel",
|
|
74
|
+
[
|
|
75
|
+
("information", "Information (bits)"),
|
|
76
|
+
("probability", "Frequency"),
|
|
77
|
+
("counts", "Count"),
|
|
78
|
+
],
|
|
79
|
+
)
|
|
80
|
+
def test_ylabel_matches_matrix_type(
|
|
81
|
+
self, matrix_type: str, expected_ylabel: str
|
|
82
|
+
) -> None:
|
|
83
|
+
matrix, labels = _make_matrix()
|
|
84
|
+
_fig, ax = plt.subplots()
|
|
85
|
+
draw_logo(ax, matrix, labels, matrix_type=matrix_type)
|
|
86
|
+
assert ax.get_ylabel() == expected_ylabel
|
|
87
|
+
plt.close("all")
|
|
88
|
+
|
|
89
|
+
def test_does_not_create_or_close_figure(self) -> None:
|
|
90
|
+
"""draw_logo must not call plt.close or create its own figure."""
|
|
91
|
+
fig, ax = plt.subplots()
|
|
92
|
+
matrix, labels = _make_matrix()
|
|
93
|
+
draw_logo(ax, matrix, labels)
|
|
94
|
+
# Figure should still be open
|
|
95
|
+
assert fig.number in plt.get_fignums()
|
|
96
|
+
plt.close("all")
|
|
97
|
+
|
|
98
|
+
def test_nature_style_applied(self) -> None:
|
|
99
|
+
apply_nature_style()
|
|
100
|
+
assert matplotlib.rcParams["axes.spines.top"] is False
|
|
101
|
+
assert matplotlib.rcParams["axes.spines.right"] is False
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
# ---------------------------------------------------------------------------
|
|
105
|
+
# Patchworklib integration (skip if not installed)
|
|
106
|
+
# ---------------------------------------------------------------------------
|
|
107
|
+
|
|
108
|
+
try:
|
|
109
|
+
import patchworklib as pw
|
|
110
|
+
_has_pw = True
|
|
111
|
+
except ImportError:
|
|
112
|
+
_has_pw = False
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
@pytest.mark.skipif(not _has_pw, reason="patchworklib not installed")
|
|
116
|
+
class TestPatchworkIntegration:
|
|
117
|
+
"""Verify draw_logo works with patchworklib Bricks."""
|
|
118
|
+
|
|
119
|
+
def setup_method(self) -> None:
|
|
120
|
+
matplotlib.use("Agg")
|
|
121
|
+
|
|
122
|
+
def test_draw_on_brick(self) -> None:
|
|
123
|
+
matrix, labels = _make_matrix()
|
|
124
|
+
brick = pw.Brick("test_logo", figsize=(4, 2))
|
|
125
|
+
result = draw_logo(brick, matrix, labels, title="Brick Logo")
|
|
126
|
+
assert isinstance(result, logomaker.Logo)
|
|
127
|
+
assert brick.get_title(loc="left") == "Brick Logo"
|
|
128
|
+
|
|
129
|
+
def test_compose_two_bricks(self, output_dir) -> None:
|
|
130
|
+
m1, l1 = _make_matrix()
|
|
131
|
+
m2, l2 = _make_matrix()
|
|
132
|
+
|
|
133
|
+
b1 = pw.Brick("logo_left", figsize=(4, 2))
|
|
134
|
+
b2 = pw.Brick("logo_right", figsize=(4, 2))
|
|
135
|
+
|
|
136
|
+
draw_logo(b1, m1, l1, title="Left")
|
|
137
|
+
draw_logo(b2, m2, l2, title="Right")
|
|
138
|
+
|
|
139
|
+
composed = b1 | b2
|
|
140
|
+
out = output_dir / "patchwork_composed.png"
|
|
141
|
+
composed.savefig(str(out))
|
|
142
|
+
assert out.exists()
|
|
143
|
+
assert out.stat().st_size > 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|