pylocuszoom 0.3.0__py3-none-any.whl → 0.5.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 +52 -1
- pylocuszoom/backends/base.py +45 -0
- pylocuszoom/backends/bokeh_backend.py +138 -48
- pylocuszoom/backends/matplotlib_backend.py +104 -0
- pylocuszoom/backends/plotly_backend.py +212 -64
- pylocuszoom/colors.py +3 -1
- pylocuszoom/gene_track.py +1 -0
- pylocuszoom/loaders.py +862 -0
- pylocuszoom/plotter.py +84 -113
- pylocuszoom/py.typed +0 -0
- pylocuszoom/recombination.py +4 -4
- pylocuszoom/schemas.py +395 -0
- {pylocuszoom-0.3.0.dist-info → pylocuszoom-0.5.0.dist-info}/METADATA +104 -24
- pylocuszoom-0.5.0.dist-info/RECORD +24 -0
- pylocuszoom-0.3.0.dist-info/RECORD +0 -21
- {pylocuszoom-0.3.0.dist-info → pylocuszoom-0.5.0.dist-info}/WHEEL +0 -0
- {pylocuszoom-0.3.0.dist-info → pylocuszoom-0.5.0.dist-info}/licenses/LICENSE.md +0 -0
pylocuszoom/__init__.py
CHANGED
|
@@ -34,7 +34,7 @@ Species Support:
|
|
|
34
34
|
- Custom: User provides all reference data
|
|
35
35
|
"""
|
|
36
36
|
|
|
37
|
-
__version__ = "0.
|
|
37
|
+
__version__ = "0.3.0"
|
|
38
38
|
|
|
39
39
|
# Main plotter class
|
|
40
40
|
# Backend types
|
|
@@ -74,6 +74,31 @@ from .labels import add_snp_labels
|
|
|
74
74
|
# LD calculation
|
|
75
75
|
from .ld import calculate_ld
|
|
76
76
|
|
|
77
|
+
# File format loaders
|
|
78
|
+
from .loaders import (
|
|
79
|
+
load_bed,
|
|
80
|
+
load_bolt_lmm,
|
|
81
|
+
load_caviar,
|
|
82
|
+
load_ensembl_genes,
|
|
83
|
+
load_eqtl_catalogue,
|
|
84
|
+
load_finemap,
|
|
85
|
+
load_gemma,
|
|
86
|
+
# eQTL loaders
|
|
87
|
+
load_gtex_eqtl,
|
|
88
|
+
# Gene annotation loaders
|
|
89
|
+
load_gtf,
|
|
90
|
+
# GWAS loaders
|
|
91
|
+
load_gwas,
|
|
92
|
+
load_gwas_catalog,
|
|
93
|
+
load_matrixeqtl,
|
|
94
|
+
load_plink_assoc,
|
|
95
|
+
load_polyfun,
|
|
96
|
+
load_regenie,
|
|
97
|
+
load_saige,
|
|
98
|
+
# Fine-mapping loaders
|
|
99
|
+
load_susie,
|
|
100
|
+
)
|
|
101
|
+
|
|
77
102
|
# Logging configuration
|
|
78
103
|
from .logging import disable_logging, enable_logging
|
|
79
104
|
from .plotter import LocusZoomPlotter
|
|
@@ -86,6 +111,9 @@ from .recombination import (
|
|
|
86
111
|
load_recombination_map,
|
|
87
112
|
)
|
|
88
113
|
|
|
114
|
+
# Schema validation
|
|
115
|
+
from .schemas import LoaderValidationError
|
|
116
|
+
|
|
89
117
|
# Validation utilities
|
|
90
118
|
from .utils import ValidationError, to_pandas
|
|
91
119
|
|
|
@@ -136,4 +164,27 @@ __all__ = [
|
|
|
136
164
|
# Validation & Utils
|
|
137
165
|
"ValidationError",
|
|
138
166
|
"to_pandas",
|
|
167
|
+
# GWAS loaders
|
|
168
|
+
"load_gwas",
|
|
169
|
+
"load_plink_assoc",
|
|
170
|
+
"load_regenie",
|
|
171
|
+
"load_bolt_lmm",
|
|
172
|
+
"load_gemma",
|
|
173
|
+
"load_saige",
|
|
174
|
+
"load_gwas_catalog",
|
|
175
|
+
# eQTL loaders
|
|
176
|
+
"load_gtex_eqtl",
|
|
177
|
+
"load_eqtl_catalogue",
|
|
178
|
+
"load_matrixeqtl",
|
|
179
|
+
# Fine-mapping loaders
|
|
180
|
+
"load_susie",
|
|
181
|
+
"load_finemap",
|
|
182
|
+
"load_caviar",
|
|
183
|
+
"load_polyfun",
|
|
184
|
+
# Gene annotation loaders
|
|
185
|
+
"load_gtf",
|
|
186
|
+
"load_bed",
|
|
187
|
+
"load_ensembl_genes",
|
|
188
|
+
# Schema validation
|
|
189
|
+
"LoaderValidationError",
|
|
139
190
|
]
|
pylocuszoom/backends/base.py
CHANGED
|
@@ -341,3 +341,48 @@ class PlotBackend(Protocol):
|
|
|
341
341
|
fig: Figure object.
|
|
342
342
|
"""
|
|
343
343
|
...
|
|
344
|
+
|
|
345
|
+
def add_eqtl_legend(
|
|
346
|
+
self,
|
|
347
|
+
ax: Any,
|
|
348
|
+
eqtl_positive_bins: List[Tuple[float, float, str, str]],
|
|
349
|
+
eqtl_negative_bins: List[Tuple[float, float, str, str]],
|
|
350
|
+
) -> None:
|
|
351
|
+
"""Add eQTL effect size legend to the axes.
|
|
352
|
+
|
|
353
|
+
Args:
|
|
354
|
+
ax: Axes or panel.
|
|
355
|
+
eqtl_positive_bins: List of (min, max, label, color) for positive effects.
|
|
356
|
+
eqtl_negative_bins: List of (min, max, label, color) for negative effects.
|
|
357
|
+
"""
|
|
358
|
+
...
|
|
359
|
+
|
|
360
|
+
def add_finemapping_legend(
|
|
361
|
+
self,
|
|
362
|
+
ax: Any,
|
|
363
|
+
credible_sets: List[int],
|
|
364
|
+
get_color_func: Any,
|
|
365
|
+
) -> None:
|
|
366
|
+
"""Add fine-mapping credible set legend to the axes.
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
ax: Axes or panel.
|
|
370
|
+
credible_sets: List of credible set IDs to show.
|
|
371
|
+
get_color_func: Function that takes CS ID and returns color.
|
|
372
|
+
"""
|
|
373
|
+
...
|
|
374
|
+
|
|
375
|
+
def add_simple_legend(
|
|
376
|
+
self,
|
|
377
|
+
ax: Any,
|
|
378
|
+
label: str,
|
|
379
|
+
loc: str = "upper right",
|
|
380
|
+
) -> None:
|
|
381
|
+
"""Add a simple legend entry for labeled data already in the plot.
|
|
382
|
+
|
|
383
|
+
Args:
|
|
384
|
+
ax: Axes or panel.
|
|
385
|
+
label: Legend label for labeled scatter data.
|
|
386
|
+
loc: Legend location.
|
|
387
|
+
"""
|
|
388
|
+
...
|
|
@@ -67,9 +67,13 @@ class BokehBackend:
|
|
|
67
67
|
toolbar_location="above" if i == 0 else None,
|
|
68
68
|
)
|
|
69
69
|
|
|
70
|
-
# Style
|
|
71
|
-
p.grid.
|
|
70
|
+
# Style - no grid lines, black axes for clean LocusZoom appearance
|
|
71
|
+
p.grid.visible = False
|
|
72
72
|
p.outline_line_color = None
|
|
73
|
+
p.xaxis.axis_line_color = "black"
|
|
74
|
+
p.yaxis.axis_line_color = "black"
|
|
75
|
+
p.xaxis.minor_tick_line_color = None
|
|
76
|
+
p.yaxis.minor_tick_line_color = None
|
|
73
77
|
|
|
74
78
|
figures.append(p)
|
|
75
79
|
|
|
@@ -115,9 +119,9 @@ class BokehBackend:
|
|
|
115
119
|
for col in hover_data.columns:
|
|
116
120
|
data[col] = hover_data[col].values
|
|
117
121
|
col_lower = col.lower()
|
|
118
|
-
if col_lower
|
|
122
|
+
if col_lower in ("p-value", "pval", "p_value"):
|
|
119
123
|
tooltips.append((col, "@{" + col + "}{0.2e}"))
|
|
120
|
-
elif
|
|
124
|
+
elif any(x in col_lower for x in ("r2", "r²", "ld")):
|
|
121
125
|
tooltips.append((col, "@{" + col + "}{0.3f}"))
|
|
122
126
|
elif "pos" in col_lower:
|
|
123
127
|
tooltips.append((col, "@{" + col + "}{0,0}"))
|
|
@@ -351,6 +355,16 @@ class BokehBackend:
|
|
|
351
355
|
ax.yaxis.axis_label = label
|
|
352
356
|
ax.yaxis.axis_label_text_font_size = f"{fontsize}pt"
|
|
353
357
|
|
|
358
|
+
def _get_legend_location(self, loc: str, default: str = "top_left") -> str:
|
|
359
|
+
"""Map matplotlib-style legend location to Bokeh location."""
|
|
360
|
+
loc_map = {
|
|
361
|
+
"upper left": "top_left",
|
|
362
|
+
"upper right": "top_right",
|
|
363
|
+
"lower left": "bottom_left",
|
|
364
|
+
"lower right": "bottom_right",
|
|
365
|
+
}
|
|
366
|
+
return loc_map.get(loc, default)
|
|
367
|
+
|
|
354
368
|
def _convert_label(self, label: str) -> str:
|
|
355
369
|
"""Convert LaTeX-style labels to Unicode for Bokeh display."""
|
|
356
370
|
conversions = [
|
|
@@ -465,60 +479,90 @@ class BokehBackend:
|
|
|
465
479
|
label = self._convert_label(label)
|
|
466
480
|
# Find the secondary axis and update its label
|
|
467
481
|
for renderer in ax.right:
|
|
468
|
-
if
|
|
482
|
+
if (
|
|
483
|
+
hasattr(renderer, "y_range_name")
|
|
484
|
+
and renderer.y_range_name == yaxis_name
|
|
485
|
+
):
|
|
469
486
|
renderer.axis_label = label
|
|
470
487
|
renderer.axis_label_text_font_size = f"{fontsize}pt"
|
|
471
488
|
renderer.axis_label_text_color = color
|
|
472
489
|
renderer.major_label_text_color = color
|
|
473
490
|
break
|
|
474
491
|
|
|
475
|
-
def
|
|
476
|
-
|
|
477
|
-
ax: figure,
|
|
478
|
-
ld_bins: List[Tuple[float, str, str]],
|
|
479
|
-
lead_snp_color: str,
|
|
480
|
-
) -> None:
|
|
481
|
-
"""Add LD color legend using invisible dummy glyphs.
|
|
492
|
+
def _ensure_legend_range(self, ax: figure) -> Any:
|
|
493
|
+
"""Ensure legend range exists and return a dummy data source.
|
|
482
494
|
|
|
483
|
-
Creates
|
|
484
|
-
the
|
|
495
|
+
Creates a separate y-range for legend glyphs so they don't affect
|
|
496
|
+
the main plot's axis scaling.
|
|
485
497
|
"""
|
|
486
|
-
from bokeh.models import ColumnDataSource,
|
|
487
|
-
|
|
488
|
-
legend_items = []
|
|
498
|
+
from bokeh.models import ColumnDataSource, Range1d
|
|
489
499
|
|
|
490
|
-
# Create a separate range for legend glyphs that won't affect the main plot
|
|
491
500
|
if "legend_range" not in ax.extra_y_ranges:
|
|
492
501
|
ax.extra_y_ranges["legend_range"] = Range1d(start=0, end=1)
|
|
502
|
+
return ColumnDataSource(data={"x": [0], "y": [0]})
|
|
493
503
|
|
|
494
|
-
|
|
495
|
-
|
|
504
|
+
def _add_legend_item(
|
|
505
|
+
self,
|
|
506
|
+
ax: figure,
|
|
507
|
+
source: Any,
|
|
508
|
+
label: str,
|
|
509
|
+
color: str,
|
|
510
|
+
marker: str,
|
|
511
|
+
size: int = 10,
|
|
512
|
+
) -> Any:
|
|
513
|
+
"""Create an invisible scatter renderer for a legend entry."""
|
|
514
|
+
from bokeh.models import LegendItem
|
|
515
|
+
|
|
516
|
+
renderer = ax.scatter(
|
|
517
|
+
x="x",
|
|
518
|
+
y="y",
|
|
519
|
+
source=source,
|
|
520
|
+
marker=marker,
|
|
521
|
+
size=size,
|
|
522
|
+
fill_color=color,
|
|
523
|
+
line_color="black",
|
|
524
|
+
line_width=0.5,
|
|
525
|
+
y_range_name="legend_range",
|
|
526
|
+
visible=False,
|
|
527
|
+
)
|
|
528
|
+
return LegendItem(label=label, renderers=[renderer])
|
|
496
529
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
x="x",
|
|
501
|
-
y="y",
|
|
502
|
-
marker="square",
|
|
503
|
-
size=10,
|
|
504
|
-
fill_color=color,
|
|
505
|
-
line_color="black",
|
|
506
|
-
line_width=0.5,
|
|
507
|
-
)
|
|
508
|
-
renderer = ax.add_glyph(dummy_source, glyph)
|
|
509
|
-
renderer.y_range_name = "legend_range"
|
|
510
|
-
renderer.visible = False
|
|
511
|
-
legend_items.append(LegendItem(label=label, renderers=[renderer]))
|
|
530
|
+
def _create_legend(self, ax: figure, items: List[Any], title: str) -> None:
|
|
531
|
+
"""Create and add a styled legend to the figure."""
|
|
532
|
+
from bokeh.models import Legend
|
|
512
533
|
|
|
513
534
|
legend = Legend(
|
|
514
|
-
items=
|
|
535
|
+
items=items,
|
|
515
536
|
location="top_right",
|
|
516
|
-
title=
|
|
537
|
+
title=title,
|
|
517
538
|
background_fill_alpha=0.9,
|
|
518
539
|
border_line_color="black",
|
|
540
|
+
spacing=0,
|
|
541
|
+
padding=4,
|
|
542
|
+
label_height=12,
|
|
543
|
+
glyph_height=12,
|
|
519
544
|
)
|
|
520
545
|
ax.add_layout(legend)
|
|
521
546
|
|
|
547
|
+
def add_ld_legend(
|
|
548
|
+
self,
|
|
549
|
+
ax: figure,
|
|
550
|
+
ld_bins: List[Tuple[float, str, str]],
|
|
551
|
+
lead_snp_color: str,
|
|
552
|
+
) -> None:
|
|
553
|
+
"""Add LD color legend using invisible dummy glyphs.
|
|
554
|
+
|
|
555
|
+
Creates legend entries with dummy renderers that are excluded from
|
|
556
|
+
the data range calculation to avoid affecting axis scaling.
|
|
557
|
+
"""
|
|
558
|
+
source = self._ensure_legend_range(ax)
|
|
559
|
+
items = [
|
|
560
|
+
self._add_legend_item(ax, source, "Lead SNP", lead_snp_color, "diamond", 12)
|
|
561
|
+
]
|
|
562
|
+
for _, label, color in ld_bins:
|
|
563
|
+
items.append(self._add_legend_item(ax, source, label, color, "square"))
|
|
564
|
+
self._create_legend(ax, items, "r²")
|
|
565
|
+
|
|
522
566
|
def add_legend(
|
|
523
567
|
self,
|
|
524
568
|
ax: figure,
|
|
@@ -528,17 +572,7 @@ class BokehBackend:
|
|
|
528
572
|
title: Optional[str] = None,
|
|
529
573
|
) -> Any:
|
|
530
574
|
"""Configure legend on the figure."""
|
|
531
|
-
|
|
532
|
-
# Just configure position
|
|
533
|
-
|
|
534
|
-
loc_map = {
|
|
535
|
-
"upper left": "top_left",
|
|
536
|
-
"upper right": "top_right",
|
|
537
|
-
"lower left": "bottom_left",
|
|
538
|
-
"lower right": "bottom_right",
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
ax.legend.location = loc_map.get(loc, "top_left")
|
|
575
|
+
ax.legend.location = self._get_legend_location(loc, "top_left")
|
|
542
576
|
if title:
|
|
543
577
|
ax.legend.title = title
|
|
544
578
|
ax.legend.background_fill_alpha = 0.9
|
|
@@ -554,6 +588,11 @@ class BokehBackend:
|
|
|
554
588
|
"""
|
|
555
589
|
pass
|
|
556
590
|
|
|
591
|
+
def hide_yaxis(self, ax: figure) -> None:
|
|
592
|
+
"""Hide y-axis ticks, labels, line, and grid for gene track panels."""
|
|
593
|
+
ax.yaxis.visible = False
|
|
594
|
+
ax.ygrid.visible = False
|
|
595
|
+
|
|
557
596
|
def format_xaxis_mb(self, ax: figure) -> None:
|
|
558
597
|
"""Format x-axis to show megabase values."""
|
|
559
598
|
from bokeh.models import CustomJSTickFormatter
|
|
@@ -593,6 +632,57 @@ class BokehBackend:
|
|
|
593
632
|
"""Close the figure (no-op for bokeh)."""
|
|
594
633
|
pass
|
|
595
634
|
|
|
635
|
+
def add_eqtl_legend(
|
|
636
|
+
self,
|
|
637
|
+
ax: figure,
|
|
638
|
+
eqtl_positive_bins: List[Tuple[float, float, str, str]],
|
|
639
|
+
eqtl_negative_bins: List[Tuple[float, float, str, str]],
|
|
640
|
+
) -> None:
|
|
641
|
+
"""Add eQTL effect size legend using invisible dummy glyphs."""
|
|
642
|
+
source = self._ensure_legend_range(ax)
|
|
643
|
+
items = []
|
|
644
|
+
for _, _, label, color in eqtl_positive_bins:
|
|
645
|
+
items.append(self._add_legend_item(ax, source, label, color, "triangle"))
|
|
646
|
+
for _, _, label, color in eqtl_negative_bins:
|
|
647
|
+
items.append(
|
|
648
|
+
self._add_legend_item(ax, source, label, color, "inverted_triangle")
|
|
649
|
+
)
|
|
650
|
+
self._create_legend(ax, items, "eQTL effect")
|
|
651
|
+
|
|
652
|
+
def add_finemapping_legend(
|
|
653
|
+
self,
|
|
654
|
+
ax: figure,
|
|
655
|
+
credible_sets: List[int],
|
|
656
|
+
get_color_func: Any,
|
|
657
|
+
) -> None:
|
|
658
|
+
"""Add fine-mapping credible set legend using invisible dummy glyphs."""
|
|
659
|
+
if not credible_sets:
|
|
660
|
+
return
|
|
661
|
+
|
|
662
|
+
source = self._ensure_legend_range(ax)
|
|
663
|
+
items = [
|
|
664
|
+
self._add_legend_item(
|
|
665
|
+
ax, source, f"CS{cs_id}", get_color_func(cs_id), "circle"
|
|
666
|
+
)
|
|
667
|
+
for cs_id in credible_sets
|
|
668
|
+
]
|
|
669
|
+
self._create_legend(ax, items, "Credible sets")
|
|
670
|
+
|
|
671
|
+
def add_simple_legend(
|
|
672
|
+
self,
|
|
673
|
+
ax: figure,
|
|
674
|
+
label: str,
|
|
675
|
+
loc: str = "upper right",
|
|
676
|
+
) -> None:
|
|
677
|
+
"""Configure legend position.
|
|
678
|
+
|
|
679
|
+
Bokeh handles legends automatically from legend_label.
|
|
680
|
+
This just positions the legend.
|
|
681
|
+
"""
|
|
682
|
+
ax.legend.location = self._get_legend_location(loc, "top_right")
|
|
683
|
+
ax.legend.background_fill_alpha = 0.9
|
|
684
|
+
ax.legend.border_line_color = "black"
|
|
685
|
+
|
|
596
686
|
def finalize_layout(
|
|
597
687
|
self,
|
|
598
688
|
fig: Any,
|
|
@@ -290,6 +290,110 @@ class MatplotlibBackend:
|
|
|
290
290
|
"""Close the figure and free resources."""
|
|
291
291
|
plt.close(fig)
|
|
292
292
|
|
|
293
|
+
def add_eqtl_legend(
|
|
294
|
+
self,
|
|
295
|
+
ax: Axes,
|
|
296
|
+
eqtl_positive_bins: List[Tuple[float, float, str, str]],
|
|
297
|
+
eqtl_negative_bins: List[Tuple[float, float, str, str]],
|
|
298
|
+
) -> None:
|
|
299
|
+
"""Add eQTL effect size legend using matplotlib Line2D markers."""
|
|
300
|
+
from matplotlib.lines import Line2D
|
|
301
|
+
|
|
302
|
+
legend_elements = []
|
|
303
|
+
|
|
304
|
+
# Positive effects (upward triangles)
|
|
305
|
+
for _, _, label, color in eqtl_positive_bins:
|
|
306
|
+
legend_elements.append(
|
|
307
|
+
Line2D(
|
|
308
|
+
[0],
|
|
309
|
+
[0],
|
|
310
|
+
marker="^",
|
|
311
|
+
color="w",
|
|
312
|
+
markerfacecolor=color,
|
|
313
|
+
markeredgecolor="black",
|
|
314
|
+
markersize=7,
|
|
315
|
+
label=label,
|
|
316
|
+
)
|
|
317
|
+
)
|
|
318
|
+
|
|
319
|
+
# Negative effects (downward triangles)
|
|
320
|
+
for _, _, label, color in eqtl_negative_bins:
|
|
321
|
+
legend_elements.append(
|
|
322
|
+
Line2D(
|
|
323
|
+
[0],
|
|
324
|
+
[0],
|
|
325
|
+
marker="v",
|
|
326
|
+
color="w",
|
|
327
|
+
markerfacecolor=color,
|
|
328
|
+
markeredgecolor="black",
|
|
329
|
+
markersize=7,
|
|
330
|
+
label=label,
|
|
331
|
+
)
|
|
332
|
+
)
|
|
333
|
+
|
|
334
|
+
ax.legend(
|
|
335
|
+
handles=legend_elements,
|
|
336
|
+
loc="upper right",
|
|
337
|
+
fontsize=8,
|
|
338
|
+
frameon=True,
|
|
339
|
+
framealpha=0.9,
|
|
340
|
+
title="eQTL effect",
|
|
341
|
+
title_fontsize=9,
|
|
342
|
+
handlelength=1.2,
|
|
343
|
+
handleheight=1.0,
|
|
344
|
+
labelspacing=0.3,
|
|
345
|
+
)
|
|
346
|
+
|
|
347
|
+
def add_finemapping_legend(
|
|
348
|
+
self,
|
|
349
|
+
ax: Axes,
|
|
350
|
+
credible_sets: List[int],
|
|
351
|
+
get_color_func: Any,
|
|
352
|
+
) -> None:
|
|
353
|
+
"""Add fine-mapping credible set legend using matplotlib Line2D markers."""
|
|
354
|
+
from matplotlib.lines import Line2D
|
|
355
|
+
|
|
356
|
+
if not credible_sets:
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
legend_elements = []
|
|
360
|
+
for cs_id in credible_sets:
|
|
361
|
+
color = get_color_func(cs_id)
|
|
362
|
+
legend_elements.append(
|
|
363
|
+
Line2D(
|
|
364
|
+
[0],
|
|
365
|
+
[0],
|
|
366
|
+
marker="o",
|
|
367
|
+
color="w",
|
|
368
|
+
markerfacecolor=color,
|
|
369
|
+
markeredgecolor="black",
|
|
370
|
+
markersize=7,
|
|
371
|
+
label=f"CS{cs_id}",
|
|
372
|
+
)
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
ax.legend(
|
|
376
|
+
handles=legend_elements,
|
|
377
|
+
loc="upper right",
|
|
378
|
+
fontsize=8,
|
|
379
|
+
frameon=True,
|
|
380
|
+
framealpha=0.9,
|
|
381
|
+
title="Credible sets",
|
|
382
|
+
title_fontsize=9,
|
|
383
|
+
handlelength=1.2,
|
|
384
|
+
handleheight=1.0,
|
|
385
|
+
labelspacing=0.3,
|
|
386
|
+
)
|
|
387
|
+
|
|
388
|
+
def add_simple_legend(
|
|
389
|
+
self,
|
|
390
|
+
ax: Axes,
|
|
391
|
+
label: str,
|
|
392
|
+
loc: str = "upper right",
|
|
393
|
+
) -> None:
|
|
394
|
+
"""Add simple legend for labeled scatter data."""
|
|
395
|
+
ax.legend(loc=loc, fontsize=9)
|
|
396
|
+
|
|
293
397
|
def finalize_layout(
|
|
294
398
|
self,
|
|
295
399
|
fig: Figure,
|