Skip to content

Commit 1e8bfef

Browse files
committed
feat: shared fontsize constants, display_label, and xlabel on 1D panels
- Add FS_TICK/FS_AXLABEL/FS_AXTITLE/FS_TITLE/FS_LEGEND and use them in the 1D scatter and genome-axis decoration. - Add AXIS_LABELS and display_label() so log2RDR is shown as "RDR" on 1D and 2D axes, matching the heatmap colorbar. - plot_scatter_1d and plot_scatter_1d_multisample take an xlabel; the multisample figure draws it above the profile's top chromosome ticks. - 1D legend moves to the lower right. - plot_heatmap_cnp scales profile_hspace with figure height so the chromosome-label band keeps its size on short pages.
1 parent b5a4a6a commit 1e8bfef

5 files changed

Lines changed: 84 additions & 13 deletions

File tree

‎src/cnplot/__init__.py‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,15 @@
5050
from .cnplot_intcnp import plot_cnv_profile
5151
from .cnplot_io_utils import read_bed, read_chr_sizes
5252
from .cnplot_utils import (
53+
AXIS_LABELS,
5354
BAF_LIM,
5455
CN_PREFIX,
5556
EXP_PREFIX,
57+
FS_AXLABEL,
58+
FS_AXTITLE,
59+
FS_LEGEND,
60+
FS_TICK,
61+
FS_TITLE,
5662
MARKER_SIZE_LARGE,
5763
MARKER_SIZE_SMALL,
5864
MAX_NDOTS,
@@ -63,6 +69,7 @@
6369
FigureSaver,
6470
adaptive_dot_size,
6571
decorate_genome_axis,
72+
display_label,
6673
draw_chr_boundaries,
6774
draw_segment_boundaries,
6875
get_clone_ylabels,
@@ -97,13 +104,19 @@ def _read_version() -> str:
97104
__version__ = _read_version()
98105

99106
__all__ = [
107+
"AXIS_LABELS",
100108
"BAF_COLORS",
101109
"BAF_LIM",
102110
"CELLTYPE_CMAP",
103111
"CN_PREFIX",
104112
"DATASET_CMAP",
105113
"DEFAULT_EXCLUDED_CHROMS",
106114
"EXP_PREFIX",
115+
"FS_AXLABEL",
116+
"FS_AXTITLE",
117+
"FS_LEGEND",
118+
"FS_TICK",
119+
"FS_TITLE",
107120
"MARKER_SIZE_LARGE",
108121
"MARKER_SIZE_SMALL",
109122
"MAX_NDOTS",
@@ -126,6 +139,7 @@ def _read_version() -> str:
126139
"adaptive_dot_size",
127140
"annotate_landmarks",
128141
"decorate_genome_axis",
142+
"display_label",
129143
"draw_chr_boundaries",
130144
"draw_segment_boundaries",
131145
"get_ascn_cmap",

‎src/cnplot/cnplot_1d.py‎

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
from .cnplot_genome_axis import GenomeAxis
2525
from .cnplot_utils import (
2626
EXP_PREFIX,
27+
FS_AXLABEL,
28+
FS_AXTITLE,
29+
FS_LEGEND,
2730
decorate_genome_axis,
31+
display_label,
2832
draw_chr_boundaries,
2933
draw_segment_boundaries,
3034
resolve_colors,
@@ -168,6 +172,7 @@ def plot_scatter_1d(
168172
edgewidth: float = 0.0,
169173
ylim: tuple | None = None,
170174
ylabel: str | None = None,
175+
xlabel: str | None = None,
171176
title: str | None = None,
172177
href: float | None = None,
173178
reverse_y: bool = False,
@@ -210,8 +215,13 @@ def plot_scatter_1d(
210215
edgecolor: Marker edge color.
211216
edgewidth: Marker edge width.
212217
ylim: Fixed (lo, hi), or None to leave the autoscale alone.
213-
ylabel: Y-axis label.
214-
title: Axes title, drawn left-aligned.
218+
ylabel: Y-axis label, passed through
219+
:func:`~cnplot.cnplot_utils.display_label`.
220+
xlabel: X-axis label, drawn under the chromosome ticks and passed through
221+
:func:`~cnplot.cnplot_utils.display_label`. None leaves the axis
222+
unnamed, which suits a stacked panel whose bottom row carries it.
223+
title: Axes title, drawn left-aligned at FS_AXTITLE -- it labels one row
224+
of a stack, so it stays below the y-axis label that names the quantity.
215225
href: Y value for a dotted grey reference line, e.g. 0.5 on a BAF row.
216226
reverse_y: Invert the y-axis.
217227
plot_chrname: Draw chromosome labels under the axes.
@@ -273,7 +283,8 @@ def plot_scatter_1d(
273283
label=label,
274284
)
275285
if keep_col is not None and show_legend:
276-
ax.legend(loc="upper right", fontsize=8, markerscale=2, framealpha=0.7)
286+
# NB: lower right -- a legend at the top is the first thing the eye reaches.
287+
ax.legend(loc="lower right", fontsize=FS_LEGEND, markerscale=2, framealpha=0.7)
277288
if rasterized:
278289
for coll in ax.collections:
279290
coll.set_rasterized(True)
@@ -295,15 +306,17 @@ def plot_scatter_1d(
295306
if ylim is not None:
296307
ax.set_ylim(*ylim)
297308
if ylabel is not None:
298-
ax.set_ylabel(ylabel, fontsize=12, fontweight="bold")
309+
ax.set_ylabel(display_label(ylabel), fontsize=FS_AXLABEL)
299310
if title is not None:
300-
ax.set_title(title, fontsize=12, fontweight="bold", loc="left")
311+
ax.set_title(title, fontsize=FS_AXTITLE, fontweight="bold", loc="left", pad=4.0)
301312
decorate_genome_axis(
302313
ax,
303314
genome_axis,
304315
plot_chrname=plot_chrname,
305316
mb_ticks=mb_ticks,
306317
mb_tick_step=mb_tick_step,
307318
)
319+
if xlabel is not None:
320+
ax.set_xlabel(display_label(xlabel), fontsize=FS_AXLABEL)
308321
if reverse_y:
309322
ax.invert_yaxis()

‎src/cnplot/cnplot_2d.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
EXP_PREFIX,
3333
NORMAL_CLONE,
3434
SAMPLE_COL,
35+
display_label,
3536
resolve_colors,
3637
resolve_marker_size,
3738
)
@@ -418,8 +419,8 @@ def plot_scatter_2d(
418419
)
419420

420421
grid.set_axis_labels(
421-
xlabel=xcol if xlabel is None else xlabel,
422-
ylabel=ycol if ylabel is None else ylabel,
422+
xlabel=display_label(xcol if xlabel is None else xlabel),
423+
ylabel=display_label(ycol if ylabel is None else ylabel),
423424
fontsize=12,
424425
)
425426
if title is not None:

‎src/cnplot/cnplot_figures.py‎

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,10 @@
3030
select_sample,
3131
)
3232
from .cnplot_utils import (
33+
FS_AXLABEL,
3334
NORMAL_CLONE,
3435
SAMPLE_COL,
36+
display_label,
3537
resolve_marker_size,
3638
)
3739

@@ -158,6 +160,7 @@ def plot_scatter_1d_multisample(
158160
markersize: float | None = None,
159161
mb_ticks: bool = False,
160162
mb_tick_step: float = 50_000_000,
163+
xlabel: str | None = None,
161164
intra_group_hspace: float = 0.25,
162165
inter_group_hspace: float = 0.15,
163166
profile_hspace: float = 0.2,
@@ -212,7 +215,9 @@ def plot_scatter_1d_multisample(
212215
inter_group_hspace: Space between groups, as a fraction of a group's
213216
height; a group is several rows tall, so keep this small.
214217
profile_hspace: Space between the last row and the profile, which needs
215-
room for the chromosome labels it draws along its top.
218+
room for the chromosome labels it draws along its top. Raised to a
219+
floor of 0.2 when ``xlabel`` is set, since the name is drawn above
220+
those labels.
216221
rasterized: Rasterize the point collections.
217222
**profile_kwargs: Passed to
218223
:func:`~cnplot.cnplot_intcnp.plot_cnv_profile`.
@@ -263,13 +268,15 @@ def plot_scatter_1d_multisample(
263268

264269
# Nested so the three gaps stay independent: rows within a group, group to
265270
# group, and rows to profile. One flat grid would tie the last two together.
271+
# NB: a top-side xlabel sits in the rows-to-profile gap; 0.2 clears the row above.
272+
gap = max(profile_hspace, 0.2) if xlabel is not None else profile_hspace
266273
if has_profile:
267274
outer = GridSpec(
268275
2,
269276
1,
270277
figure=fig,
271278
height_ratios=[rows_h, profile_h],
272-
hspace=profile_hspace,
279+
hspace=gap,
273280
top=0.97,
274281
)
275282
groups_gs = GridSpecFromSubplotSpec(
@@ -323,6 +330,9 @@ def plot_scatter_1d_multisample(
323330
href=spec["href"],
324331
reverse_y=spec["reverse_y"],
325332
plot_chrname=last_row and last_group and not has_profile,
333+
xlabel=(
334+
xlabel if (last_row and last_group and not has_profile) else None
335+
),
326336
mb_ticks=mb_ticks,
327337
mb_tick_step=mb_tick_step,
328338
rasterized=rasterized,
@@ -342,6 +352,10 @@ def plot_scatter_1d_multisample(
342352
plot_cnv_profile(
343353
ax_profile, seg_df, genome_axis, ax_leg=ax_legend, **profile_kwargs
344354
)
355+
if xlabel is not None:
356+
# NB: the profile ticks its chromosomes on top, so the name goes there too.
357+
ax_profile.set_xlabel(display_label(xlabel), fontsize=FS_AXLABEL)
358+
ax_profile.xaxis.set_label_position("top")
345359
return fig
346360

347361

@@ -388,7 +402,8 @@ def plot_heatmap_cnp(
388402
profile_hspace: Space between the heatmap and the profile below it; holds
389403
the chromosome labels drawn on top of the profile. None picks 0.35,
390404
or 0.45 under ``mb_ticks``, whose rotated Mb labels take the same
391-
band.
405+
band, and raises that floor on a short page so the fixed-size labels
406+
keep the same gap in inches however tall the figure is.
392407
top: Top of the axes block in figure fraction; the space above holds the
393408
title.
394409
profile: "cnv" draws the joint integer-CN profile
@@ -409,7 +424,10 @@ def plot_heatmap_cnp(
409424
if profile not in ("cnv", "ascn"):
410425
raise ValueError("profile must be 'cnv' or 'ascn'")
411426
if profile_hspace is None:
412-
profile_hspace = 0.45 if heatmap_kwargs.get("mb_ticks") else 0.35
427+
base = 0.45 if heatmap_kwargs.get("mb_ticks") else 0.35
428+
# NB: hspace is a fraction of axes height; the labels are not, so short pages
429+
# need a larger fraction to hold the same band.
430+
profile_hspace = max(base, 4.5 / figsize[1])
413431
heatmap_kwargs.setdefault("show_colorbar", True)
414432
heatmap_kwargs.setdefault("show_block_labels", False)
415433
heatmap_kwargs["plot_chrname"] = False

‎src/cnplot/cnplot_utils.py‎

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,31 @@
7272
# off the spine; ticks still run 0.0 to 1.0, the pad being too small to label.
7373
BAF_LIM = (-0.01, 1.01)
7474

75+
# Fontsizes
76+
FS_TICK = 14
77+
FS_AXLABEL = 18
78+
FS_AXTITLE = 16
79+
FS_TITLE = 20
80+
FS_LEGEND = 12
81+
82+
# Display names for plotted quantities. A log2 ratio still reads as "RDR" on an
83+
# axis, which is how the heatmap already labels its colorbar; the 1D rows follow
84+
# so the two figures name the same quantity the same way.
85+
AXIS_LABELS = {"log2RDR": "RDR"}
86+
87+
88+
def display_label(name: str | None) -> str | None:
89+
"""Canonical axis text for a quantity name.
90+
91+
Args:
92+
name: Column or label name, or None.
93+
94+
Returns:
95+
The display name from :data:`AXIS_LABELS`, or ``name`` unchanged when it
96+
has no canonical form. None passes through.
97+
"""
98+
return name if name is None else AXIS_LABELS.get(name, name)
99+
75100

76101
# =============================================================================
77102
# Axis decoration
@@ -192,7 +217,7 @@ def decorate_genome_axis(
192217
plot_chrname: bool = True,
193218
label_pos: str = "bottom",
194219
rotation: float = 60,
195-
fontsize: int = 8,
220+
fontsize: int = FS_TICK,
196221
hide_spines: bool = False,
197222
pad_axis: bool = False,
198223
mb_ticks: bool = False,
@@ -215,7 +240,7 @@ def decorate_genome_axis(
215240
rotation: Tick label rotation in degrees, applied to the chromosome names
216241
or, with ``mb_ticks``, to the Mb labels; rotating keeps the dense Mb
217242
labels from overlapping. Chromosome names stay upright in that mode.
218-
fontsize: Label font size.
243+
fontsize: Label font size; defaults to the module-level FS_TICK.
219244
hide_spines: Hide all four spines.
220245
pad_axis: Use the padded limits instead of tight genome ends.
221246
mb_ticks: Mark position in Mb every ``mb_tick_step`` bp within each

0 commit comments

Comments
 (0)