phykit 2.1.38__tar.gz → 2.1.40__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.
- {phykit-2.1.38 → phykit-2.1.40}/PKG-INFO +1 -1
- {phykit-2.1.38 → phykit-2.1.40}/phykit/cli_registry.py +3 -0
- phykit-2.1.40/phykit/helpers/plot_config.py +207 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/phykit.py +1115 -27
- {phykit-2.1.38 → phykit-2.1.40}/phykit/service_factories.py +1 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/alignment_entropy.py +19 -5
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/compositional_bias_per_site.py +24 -7
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/create_concatenation_matrix.py +38 -14
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/evolutionary_rate_per_site.py +19 -5
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/pairwise_identity.py +27 -8
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/plot_alignment_qc.py +3 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/rcvt.py +21 -5
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/__init__.py +1 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/ancestral_reconstruction.py +21 -6
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/concordance_asr.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/consensus_network.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/cont_map.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/cophylo.py +10 -4
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/covarying_evolutionary_rates.py +20 -6
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/density_map.py +9 -5
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/discordance_asymmetry.py +11 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/evo_tempo_map.py +13 -4
- phykit-2.1.40/phykit/services/tree/kf_distance.py +101 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/ltt.py +17 -7
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phenogram.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phylogenetic_ordination.py +11 -4
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phylomorphospace.py +7 -2
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/quartet_network.py +9 -2
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/rate_heterogeneity.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/relative_rate_test.py +15 -4
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/saturation.py +20 -6
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/spectral_discordance.py +23 -6
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/stochastic_character_map.py +9 -3
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/threshold_model.py +11 -6
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/tip_to_tip_distance.py +27 -8
- phykit-2.1.40/phykit/version.py +1 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/PKG-INFO +1 -1
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/SOURCES.txt +2 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/entry_points.txt +4 -0
- {phykit-2.1.38 → phykit-2.1.40}/setup.py +4 -0
- phykit-2.1.38/phykit/version.py +0 -1
- {phykit-2.1.38 → phykit-2.1.40}/LICENSE.md +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/README.md +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/__init__.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/__main__.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/errors.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/__init__.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/boolean_argument_parsing.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/caching.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/files.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/json_output.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/parallel.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/stats_summary.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/helpers/streaming.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/__init__.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/__init__.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/alignment_length.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/alignment_length_no_gaps.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/alignment_outlier_taxa.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/alignment_recoding.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/base.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/column_score.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/composition_per_taxon.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/dna_threader.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/faidx.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/gc_content.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/mask_alignment.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/occupancy_per_taxon.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/parsimony_informative_sites.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/rcv.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/rename_fasta_entries.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/sum_of_pairs_score.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/alignment/variable_sites.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/base.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/base.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/bipartition_support_stats.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/branch_length_multiplier.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/collapse_branches.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/consensus_tree.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/dvmc.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/evolutionary_rate.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/fit_continuous.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/hidden_paralogy_check.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/internal_branch_stats.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/internode_labeler.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/last_common_ancestor_subtree.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/lb_score.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/monophyly_check.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/nearest_neighbor_interchange.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/network_signal.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/ou_shift_detection.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/ouwie.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/patristic_distances.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phylogenetic_glm.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phylogenetic_regression.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/phylogenetic_signal.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/polytomy_test.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/print_tree.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/prune_tree.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/rename_tree_tips.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/rf_distance.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/root_tree.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/spurious_sequence.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/terminal_branch_stats.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/tip_labels.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/tip_to_tip_node_distance.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/total_tree_length.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/treeness.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/treeness_over_rcv.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit/services/tree/vcv_utils.py +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/dependency_links.txt +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/requires.txt +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/phykit.egg-info/top_level.txt +0 -0
- {phykit-2.1.38 → phykit-2.1.40}/setup.cfg +0 -0
|
@@ -129,6 +129,9 @@ ALIAS_TO_HANDLER: Dict[str, str] = {
|
|
|
129
129
|
"prune": "prune_tree",
|
|
130
130
|
"rename_tree": "rename_tree_tips",
|
|
131
131
|
"rename_tips": "rename_tree_tips",
|
|
132
|
+
"kuhner_felsenstein_distance": "kf_distance",
|
|
133
|
+
"kf_dist": "kf_distance",
|
|
134
|
+
"kf": "kf_distance",
|
|
132
135
|
"robinson_foulds_distance": "rf_distance",
|
|
133
136
|
"rf_dist": "rf_distance",
|
|
134
137
|
"rf": "rf_distance",
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import List, Optional
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
VALID_LEGEND_POSITIONS = frozenset([
|
|
7
|
+
"best", "upper right", "upper left", "lower left", "lower right",
|
|
8
|
+
"right", "center left", "center right", "lower center", "upper center",
|
|
9
|
+
"center", "none",
|
|
10
|
+
])
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class PlotConfig:
|
|
15
|
+
fig_width: Optional[float] = None
|
|
16
|
+
fig_height: Optional[float] = None
|
|
17
|
+
dpi: int = 300
|
|
18
|
+
show_title: bool = True
|
|
19
|
+
title: Optional[str] = None
|
|
20
|
+
legend_position: Optional[str] = None
|
|
21
|
+
ylabel_fontsize: Optional[float] = None
|
|
22
|
+
xlabel_fontsize: Optional[float] = None
|
|
23
|
+
title_fontsize: Optional[float] = None
|
|
24
|
+
axis_fontsize: Optional[float] = None
|
|
25
|
+
colors: Optional[List[str]] = None
|
|
26
|
+
|
|
27
|
+
def validate(self) -> None:
|
|
28
|
+
if self.fig_width is not None and self.fig_width <= 0:
|
|
29
|
+
print(f"Error: --fig-width must be positive, got {self.fig_width}")
|
|
30
|
+
sys.exit(2)
|
|
31
|
+
if self.fig_height is not None and self.fig_height <= 0:
|
|
32
|
+
print(f"Error: --fig-height must be positive, got {self.fig_height}")
|
|
33
|
+
sys.exit(2)
|
|
34
|
+
if self.dpi <= 0:
|
|
35
|
+
print(f"Error: --dpi must be positive, got {self.dpi}")
|
|
36
|
+
sys.exit(2)
|
|
37
|
+
for name, val in [
|
|
38
|
+
("--ylabel-fontsize", self.ylabel_fontsize),
|
|
39
|
+
("--xlabel-fontsize", self.xlabel_fontsize),
|
|
40
|
+
("--title-fontsize", self.title_fontsize),
|
|
41
|
+
("--axis-fontsize", self.axis_fontsize),
|
|
42
|
+
]:
|
|
43
|
+
if val is not None and val < 0:
|
|
44
|
+
print(f"Error: {name} must be non-negative, got {val}")
|
|
45
|
+
sys.exit(2)
|
|
46
|
+
if (
|
|
47
|
+
self.legend_position is not None
|
|
48
|
+
and self.legend_position not in VALID_LEGEND_POSITIONS
|
|
49
|
+
):
|
|
50
|
+
print(
|
|
51
|
+
f"Error: --legend-position '{self.legend_position}' is not valid. "
|
|
52
|
+
f"Choose from: {', '.join(sorted(VALID_LEGEND_POSITIONS))}"
|
|
53
|
+
)
|
|
54
|
+
sys.exit(2)
|
|
55
|
+
|
|
56
|
+
@classmethod
|
|
57
|
+
def auto_scale(cls, n_rows=None, n_cols=None) -> "PlotConfig":
|
|
58
|
+
# fig_height
|
|
59
|
+
if n_rows is not None:
|
|
60
|
+
raw_height = 3.0 + n_rows * 0.18
|
|
61
|
+
fig_height = max(5.0, min(200.0, raw_height))
|
|
62
|
+
if raw_height > 200.0:
|
|
63
|
+
print(
|
|
64
|
+
f"Warning: computed figure height ({raw_height:.0f} in) "
|
|
65
|
+
f"exceeds 200 in; capping at 200. Use --fig-height to override.",
|
|
66
|
+
file=sys.stderr,
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
fig_height = 8.0
|
|
70
|
+
|
|
71
|
+
# fig_width
|
|
72
|
+
if n_cols is not None:
|
|
73
|
+
fig_width = max(10.0, min(20.0, 8.0 + n_cols * 0.15))
|
|
74
|
+
else:
|
|
75
|
+
fig_width = 14.0
|
|
76
|
+
|
|
77
|
+
# ylabel_fontsize
|
|
78
|
+
if n_rows is not None:
|
|
79
|
+
if n_rows > 800:
|
|
80
|
+
ylabel_fontsize = 0.0
|
|
81
|
+
else:
|
|
82
|
+
ylabel_fontsize = max(3.0, min(7.0, 7.0 - (n_rows - 50) * 0.008))
|
|
83
|
+
else:
|
|
84
|
+
ylabel_fontsize = 7.0
|
|
85
|
+
|
|
86
|
+
# xlabel_fontsize
|
|
87
|
+
if n_cols is not None:
|
|
88
|
+
if n_cols > 60:
|
|
89
|
+
xlabel_fontsize = 0.0
|
|
90
|
+
else:
|
|
91
|
+
xlabel_fontsize = max(3.0, min(7.0, 7.0 - (n_cols - 20) * 0.1))
|
|
92
|
+
else:
|
|
93
|
+
xlabel_fontsize = 7.0
|
|
94
|
+
|
|
95
|
+
return cls(
|
|
96
|
+
fig_width=fig_width,
|
|
97
|
+
fig_height=fig_height,
|
|
98
|
+
ylabel_fontsize=ylabel_fontsize,
|
|
99
|
+
xlabel_fontsize=xlabel_fontsize,
|
|
100
|
+
title_fontsize=12.0,
|
|
101
|
+
axis_fontsize=10.0,
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
def resolve(self, n_rows=None, n_cols=None) -> "PlotConfig":
|
|
105
|
+
defaults = PlotConfig.auto_scale(n_rows, n_cols)
|
|
106
|
+
for fld in [
|
|
107
|
+
"fig_width", "fig_height", "ylabel_fontsize", "xlabel_fontsize",
|
|
108
|
+
"title_fontsize", "axis_fontsize",
|
|
109
|
+
]:
|
|
110
|
+
if getattr(self, fld) is None:
|
|
111
|
+
setattr(self, fld, getattr(defaults, fld))
|
|
112
|
+
return self
|
|
113
|
+
|
|
114
|
+
def apply_to_figure(self, fig, ax, default_title, default_colors):
|
|
115
|
+
# Title
|
|
116
|
+
if self.show_title:
|
|
117
|
+
title_text = self.title if self.title is not None else default_title
|
|
118
|
+
ax.set_title(title_text, fontsize=self.title_fontsize)
|
|
119
|
+
else:
|
|
120
|
+
ax.set_title("")
|
|
121
|
+
|
|
122
|
+
# Legend
|
|
123
|
+
if self.legend_position is not None:
|
|
124
|
+
if self.legend_position == "none":
|
|
125
|
+
legend = ax.get_legend()
|
|
126
|
+
if legend is not None:
|
|
127
|
+
legend.set_visible(False)
|
|
128
|
+
else:
|
|
129
|
+
legend = ax.get_legend()
|
|
130
|
+
if legend is not None:
|
|
131
|
+
handles = legend.legend_handles
|
|
132
|
+
labels = [t.get_text() for t in legend.get_texts()]
|
|
133
|
+
legend.remove()
|
|
134
|
+
ax.legend(handles=handles, labels=labels, loc=self.legend_position)
|
|
135
|
+
|
|
136
|
+
# Axis label font sizes
|
|
137
|
+
if self.axis_fontsize is not None:
|
|
138
|
+
ax.xaxis.label.set_fontsize(self.axis_fontsize)
|
|
139
|
+
ax.yaxis.label.set_fontsize(self.axis_fontsize)
|
|
140
|
+
|
|
141
|
+
# Y-axis tick labels
|
|
142
|
+
if self.ylabel_fontsize is not None:
|
|
143
|
+
if self.ylabel_fontsize == 0.0:
|
|
144
|
+
ax.set_yticklabels([])
|
|
145
|
+
else:
|
|
146
|
+
for label in ax.get_yticklabels():
|
|
147
|
+
label.set_fontsize(self.ylabel_fontsize)
|
|
148
|
+
|
|
149
|
+
# X-axis tick labels
|
|
150
|
+
if self.xlabel_fontsize is not None:
|
|
151
|
+
if self.xlabel_fontsize == 0.0:
|
|
152
|
+
ax.set_xticklabels([])
|
|
153
|
+
else:
|
|
154
|
+
for label in ax.get_xticklabels():
|
|
155
|
+
label.set_fontsize(self.xlabel_fontsize)
|
|
156
|
+
|
|
157
|
+
# Colors
|
|
158
|
+
return self.merge_colors(default_colors)
|
|
159
|
+
|
|
160
|
+
def merge_colors(self, defaults: List[str]) -> List[str]:
|
|
161
|
+
if self.colors is None:
|
|
162
|
+
return list(defaults)
|
|
163
|
+
result = list(defaults)
|
|
164
|
+
for i, user_color in enumerate(self.colors[:len(defaults)]):
|
|
165
|
+
if user_color: # non-empty string overrides
|
|
166
|
+
result[i] = user_color
|
|
167
|
+
return result
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def from_args(cls, args) -> "PlotConfig":
|
|
171
|
+
colors_str = getattr(args, "colors", None)
|
|
172
|
+
colors = None
|
|
173
|
+
if colors_str is not None:
|
|
174
|
+
colors = [c.strip() for c in colors_str.split(",")]
|
|
175
|
+
|
|
176
|
+
no_title = getattr(args, "no_title", False)
|
|
177
|
+
|
|
178
|
+
config = cls(
|
|
179
|
+
fig_width=getattr(args, "fig_width", None),
|
|
180
|
+
fig_height=getattr(args, "fig_height", None),
|
|
181
|
+
dpi=getattr(args, "dpi", 300),
|
|
182
|
+
show_title=not no_title,
|
|
183
|
+
title=getattr(args, "title", None),
|
|
184
|
+
legend_position=getattr(args, "legend_position", None),
|
|
185
|
+
ylabel_fontsize=getattr(args, "ylabel_fontsize", None),
|
|
186
|
+
xlabel_fontsize=getattr(args, "xlabel_fontsize", None),
|
|
187
|
+
title_fontsize=getattr(args, "title_fontsize", None),
|
|
188
|
+
axis_fontsize=getattr(args, "axis_fontsize", None),
|
|
189
|
+
colors=colors,
|
|
190
|
+
)
|
|
191
|
+
config.validate()
|
|
192
|
+
return config
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def add_plot_arguments(parser) -> None:
|
|
196
|
+
group = parser.add_argument_group("plot options")
|
|
197
|
+
group.add_argument("--fig-width", type=float, default=None, help="Figure width in inches (auto-scaled if omitted)")
|
|
198
|
+
group.add_argument("--fig-height", type=float, default=None, help="Figure height in inches (auto-scaled if omitted)")
|
|
199
|
+
group.add_argument("--dpi", type=int, default=300, help="Resolution in DPI (default: 300)")
|
|
200
|
+
group.add_argument("--no-title", action="store_true", default=False, help="Hide the plot title")
|
|
201
|
+
group.add_argument("--title", type=str, default=None, help="Custom title text")
|
|
202
|
+
group.add_argument("--legend-position", type=str, default=None, help="Legend location (e.g., 'upper right', 'none' to hide)")
|
|
203
|
+
group.add_argument("--ylabel-fontsize", type=float, default=None, help="Font size for y-axis labels; 0 to hide")
|
|
204
|
+
group.add_argument("--xlabel-fontsize", type=float, default=None, help="Font size for x-axis labels; 0 to hide")
|
|
205
|
+
group.add_argument("--title-fontsize", type=float, default=None, help="Font size for the title")
|
|
206
|
+
group.add_argument("--axis-fontsize", type=float, default=None, help="Font size for axis labels")
|
|
207
|
+
group.add_argument("--colors", type=str, default=None, help="Comma-separated colors (hex or named)")
|