trackplot 0.6.3__tar.gz → 0.6.5__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.
- {trackplot-0.6.3/trackplot.egg-info → trackplot-0.6.5}/PKG-INFO +1 -1
- {trackplot-0.6.3 → trackplot-0.6.5}/pyproject.toml +4 -1
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/cli.py +28 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/coord.py +40 -23
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/core.py +49 -17
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/limits.py +20 -16
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/render.py +110 -20
- {trackplot-0.6.3 → trackplot-0.6.5/trackplot.egg-info}/PKG-INFO +1 -1
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot.egg-info/SOURCES.txt +1 -0
- trackplot-0.6.5/trackplot.egg-info/entry_points.txt +2 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/LICENSE +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/README.md +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/setup.cfg +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/anno/AxLabel.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/anno/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/anno/theme.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/CoordinateMap.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/GenomicLoci.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/Junction.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/Protein.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/ReadDepth.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/Readder.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/Stroke.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/Transcript.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/base/pyUniprot.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/conf/DomainSetting.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/conf/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/conf/config.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/conf/drawing.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/conf/ui.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/ATAC.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Annotation.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Bam.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/BedGraph.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Bigwig.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Depth.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Fasta.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/File.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/HiCMatrixTrack.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Junction.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/Motif.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/ReadSegments.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/file/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/__init__.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/info.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot/utils.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/plot_func.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot/server.py +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot.egg-info/dependency_links.txt +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot.egg-info/requires.txt +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/trackplot.egg-info/top_level.txt +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/Home-B1EPE6Lq.js +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/Home-DuHqFHEh.css +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/Plot-CkylK0fA.css +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/Plot-jZAsK7Tq.js +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/_plugin-vue_export-helper-DLf8tVwz.css +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/_plugin-vue_export-helper-IgiCOkTJ.js +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/css-BDwM7dEv.css +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/css-CKLNJEB6.js +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/index-De-rfTdP.js +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/assets/index-Dlq_kUQH.css +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/index.html +0 -0
- {trackplot-0.6.3 → trackplot-0.6.5}/ui/vite.svg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trackplot
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: The trackplot is a tool for visualizing various next-generation sequencing (NGS) data, including DNA-seq, RNA-seq, single-cell RNA-seq and full-length sequencing datasets. https://sashimi.readthedocs.io/
|
|
5
5
|
Author-email: ygidtu <ygidtu@gmail.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "trackplot"
|
|
3
|
-
version = "0.6.
|
|
3
|
+
version = "0.6.5"
|
|
4
4
|
description = "The trackplot is a tool for visualizing various next-generation sequencing (NGS) data, including DNA-seq, RNA-seq, single-cell RNA-seq and full-length sequencing datasets. https://sashimi.readthedocs.io/"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "ygidtu", email = "ygidtu@gmail.com" }
|
|
@@ -29,6 +29,9 @@ dependencies = [
|
|
|
29
29
|
]
|
|
30
30
|
|
|
31
31
|
|
|
32
|
+
[project.scripts]
|
|
33
|
+
trackplot = "trackplot.cli:main" # 假设入口在 trackplot/cli.py 的 main 函数
|
|
34
|
+
|
|
32
35
|
[build-system]
|
|
33
36
|
requires = ["setuptools>=65.0", "wheel"]
|
|
34
37
|
build-backend = "setuptools.build_meta"
|
|
@@ -1214,6 +1214,30 @@ def _add_interval(p, f):
|
|
|
1214
1214
|
@optgroup.option(
|
|
1215
1215
|
"--title", type=click.STRING, default=None, help="Title", show_default=True
|
|
1216
1216
|
)
|
|
1217
|
+
@optgroup.option(
|
|
1218
|
+
"--no-title",
|
|
1219
|
+
type=click.BOOL,
|
|
1220
|
+
is_flag=True,
|
|
1221
|
+
default=False,
|
|
1222
|
+
help="Disable the plot title entirely",
|
|
1223
|
+
show_default=True,
|
|
1224
|
+
)
|
|
1225
|
+
@optgroup.option(
|
|
1226
|
+
"--no-region-text",
|
|
1227
|
+
type=click.BOOL,
|
|
1228
|
+
is_flag=True,
|
|
1229
|
+
default=False,
|
|
1230
|
+
help="Disable the --event region text on the x-axis ruler",
|
|
1231
|
+
show_default=True,
|
|
1232
|
+
)
|
|
1233
|
+
@optgroup.option(
|
|
1234
|
+
"--junctions-on-top",
|
|
1235
|
+
type=click.BOOL,
|
|
1236
|
+
is_flag=True,
|
|
1237
|
+
default=False,
|
|
1238
|
+
help="Force all junction arcs to be drawn above the density plot (only effective when --density-by-strand is not set)",
|
|
1239
|
+
show_default=True,
|
|
1240
|
+
)
|
|
1217
1241
|
@optgroup.option(
|
|
1218
1242
|
"--font", type=click.STRING, default=None, help="Fonts", show_default=True
|
|
1219
1243
|
)
|
|
@@ -1362,6 +1386,10 @@ def main(**kwargs):
|
|
|
1362
1386
|
normalize_format=kwargs.get("normalize_format"),
|
|
1363
1387
|
fill_step=kwargs.get("fill_step", "post"),
|
|
1364
1388
|
smooth_bin=kwargs["smooth_bin"],
|
|
1389
|
+
title=kwargs["title"],
|
|
1390
|
+
no_title=kwargs["no_title"],
|
|
1391
|
+
no_region_text=kwargs["no_region_text"],
|
|
1392
|
+
junctions_on_top=kwargs["junctions_on_top"],
|
|
1365
1393
|
)
|
|
1366
1394
|
|
|
1367
1395
|
|
|
@@ -40,31 +40,45 @@ def init_graph_coords(
|
|
|
40
40
|
|
|
41
41
|
if exons:
|
|
42
42
|
if intron_scale <= 1:
|
|
43
|
+
# 填充首个外显子前的内含子区域
|
|
43
44
|
for i in range(0, exons[0][0] - region.start):
|
|
44
45
|
graph_coords[i] = (i - 0) * intron_scale
|
|
45
46
|
exons = __merge_exons__(exons)
|
|
46
47
|
for i in range(0, len(exons)):
|
|
47
48
|
exon = exons[i]
|
|
49
|
+
|
|
50
|
+
# 外显子间内含子(从上一个外显子结束的下一碱基开始,避免重叠)
|
|
48
51
|
if i > 0:
|
|
49
|
-
|
|
50
|
-
|
|
52
|
+
intron_start = exons[i - 1][1] + 1
|
|
53
|
+
intron_end = exons[i][0]
|
|
54
|
+
for j in range(intron_start, intron_end):
|
|
51
55
|
if j >= region.start:
|
|
52
56
|
graph_coords[j - region.start] = (
|
|
53
|
-
graph_coords[
|
|
54
|
-
+ (j -
|
|
57
|
+
graph_coords[intron_start - region.start - 1]
|
|
58
|
+
+ (j - intron_start + 1) * intron_scale
|
|
55
59
|
)
|
|
60
|
+
|
|
61
|
+
# 填充外显子区域
|
|
56
62
|
for j in range(exon[0], exon[1] + 1):
|
|
57
63
|
if j >= region.start:
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
+
base_idx = exon[0] - region.start - 1
|
|
65
|
+
if base_idx < 0:
|
|
66
|
+
# 外显子起始位置在区域起点之前或等于起点,从 0 开始
|
|
67
|
+
graph_coords[j - region.start] = (j - region.start) * exon_scale
|
|
68
|
+
else:
|
|
69
|
+
graph_coords[j - region.start] = (
|
|
70
|
+
graph_coords[base_idx]
|
|
71
|
+
+ (j - exon[0] + 1) * exon_scale
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
# 填充最后一个外显子之后的内含子(从下一碱基开始,到 region.end 结束)
|
|
75
|
+
intron_start = exons[-1][-1] + 1
|
|
76
|
+
intron_end = region.end + 1 # +1 确保包含 region.end
|
|
77
|
+
for i in range(intron_start, intron_end):
|
|
64
78
|
if i >= region.start:
|
|
65
79
|
graph_coords[i - region.start] = (
|
|
66
|
-
graph_coords[
|
|
67
|
-
+ (i -
|
|
80
|
+
graph_coords[intron_start - region.start - 1]
|
|
81
|
+
+ (i - intron_start + 1) * intron_scale
|
|
68
82
|
)
|
|
69
83
|
else:
|
|
70
84
|
exons = __merge_exons__(exons)
|
|
@@ -85,14 +99,14 @@ def init_graph_coords(
|
|
|
85
99
|
for i in range(exons[0][0]):
|
|
86
100
|
steps[i] = step
|
|
87
101
|
for e in range(1, len(exons)):
|
|
88
|
-
interval = exons[e][0] - exons[e - 1][1] -
|
|
102
|
+
interval = exons[e][0] - exons[e - 1][1] - 1
|
|
89
103
|
step = intron_scale / interval
|
|
90
|
-
for i in range(exons[e - 1][1], exons[e][0]):
|
|
104
|
+
for i in range(exons[e - 1][1] + 1, exons[e][0]):
|
|
91
105
|
steps[i] = step
|
|
92
106
|
|
|
93
107
|
if last_interval := len(region) - exons[-1][1] - 1:
|
|
94
108
|
step = intron_scale / last_interval
|
|
95
|
-
for i in range(exons[1][1] + 1, len(region)):
|
|
109
|
+
for i in range(exons[-1][1] + 1, len(region)):
|
|
96
110
|
steps[i] = step
|
|
97
111
|
graph_coords = list(map(int, itertools.accumulate(steps)))
|
|
98
112
|
else:
|
|
@@ -134,14 +148,17 @@ def set_x_ticks(
|
|
|
134
148
|
x_label = f"{x_label}, y axis is {log_trans} transformed"
|
|
135
149
|
|
|
136
150
|
ax.hlines(y=0, xmin=0, xmax=max(graph_coords), color="black", lw=1)
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
|
|
152
|
+
no_region_text = kwargs.pop("no_region_text", False)
|
|
153
|
+
if not no_region_text:
|
|
154
|
+
ax.text(
|
|
155
|
+
x=graph_coords[len(graph_coords) // 2],
|
|
156
|
+
y=-2.8,
|
|
157
|
+
s=x_label,
|
|
158
|
+
fontsize=font_size,
|
|
159
|
+
ha="center",
|
|
160
|
+
va="top",
|
|
161
|
+
)
|
|
145
162
|
|
|
146
163
|
bk = 1
|
|
147
164
|
if not sequence and nx_ticks > 1:
|
|
@@ -866,20 +866,25 @@ class Plot(object):
|
|
|
866
866
|
return default_y
|
|
867
867
|
|
|
868
868
|
def _precompute_plot_y_limits(
|
|
869
|
-
self, p, max_used_y_dict, min_used_y_dict, same_y_by_groups, default_y, **kwargs
|
|
869
|
+
self, p, max_used_y_dict, min_used_y_dict, base_max_dict, same_y_by_groups, default_y, **kwargs
|
|
870
870
|
):
|
|
871
|
-
"""Compute and store y-limits for all objects in a plot.
|
|
871
|
+
"""Compute and store y-limits for all objects in a plot.
|
|
872
|
+
|
|
873
|
+
Also collects base_max (raw data maximum) per object path for use as
|
|
874
|
+
a global arc-height reference in --same-y mode.
|
|
875
|
+
"""
|
|
872
876
|
if p.type not in ("density", "site-plot", "line"):
|
|
873
877
|
return
|
|
874
878
|
|
|
875
879
|
for obj in p.obj:
|
|
876
|
-
_max, _min = precompute_y_limits(
|
|
880
|
+
_max, _min, _base = precompute_y_limits(
|
|
877
881
|
obj,
|
|
878
882
|
data=obj.data,
|
|
879
883
|
region=self.region,
|
|
880
884
|
graph_coords=self.graph_coords,
|
|
881
885
|
**kwargs,
|
|
882
886
|
)
|
|
887
|
+
base_max_dict[obj.path] = max(_base, base_max_dict.get(obj.path, 0))
|
|
883
888
|
|
|
884
889
|
if obj.label in same_y_by_groups:
|
|
885
890
|
key = same_y_by_groups[obj.label]
|
|
@@ -890,10 +895,11 @@ class Plot(object):
|
|
|
890
895
|
if obj.data.minus is None
|
|
891
896
|
else min_used_y_dict.get(obj.path, 0),
|
|
892
897
|
)
|
|
898
|
+
base_max_dict[key] = max(_base, base_max_dict.get(key, 0))
|
|
893
899
|
|
|
894
900
|
if isinstance(obj.data, dict):
|
|
895
901
|
for key, readDepth in obj.data.items():
|
|
896
|
-
_max, _min = precompute_y_limits(
|
|
902
|
+
_max, _min, _base = precompute_y_limits(
|
|
897
903
|
obj,
|
|
898
904
|
data=readDepth,
|
|
899
905
|
graph_coords=self.graph_coords,
|
|
@@ -907,6 +913,7 @@ class Plot(object):
|
|
|
907
913
|
if readDepth.minus is None
|
|
908
914
|
else min_used_y_dict.get(obj.path, 0),
|
|
909
915
|
)
|
|
916
|
+
base_max_dict[key] = max(_base, base_max_dict.get(key, 0))
|
|
910
917
|
|
|
911
918
|
continue
|
|
912
919
|
|
|
@@ -928,21 +935,29 @@ class Plot(object):
|
|
|
928
935
|
min_used_y_dict[key] = min_used_y_dict[obj.path]
|
|
929
936
|
|
|
930
937
|
def _resolve_plot_y_limits(
|
|
931
|
-
self, p, max_used_y_val, min_used_y_val, same_y_by_groups, default_y, **kwargs
|
|
938
|
+
self, p, max_used_y_val, min_used_y_val, base_max_val, same_y_by_groups, default_y, **kwargs
|
|
932
939
|
):
|
|
933
|
-
"""Determine y-limits for a single plot based on same-y / default-y settings.
|
|
940
|
+
"""Determine y-limits for a single plot based on same-y / default-y settings.
|
|
941
|
+
|
|
942
|
+
Returns (max_y, min_y, global_base_max).
|
|
943
|
+
global_base_max is the max of all base_max values (for --same-y mode),
|
|
944
|
+
used as the arc height reference so all panels share consistent arc heights.
|
|
945
|
+
"""
|
|
934
946
|
same_y_sc = kwargs.get("same_y_sc")
|
|
935
947
|
same_y = kwargs.get("same_y")
|
|
936
948
|
|
|
937
949
|
if same_y_sc and p.obj[0].is_single_cell:
|
|
938
|
-
|
|
950
|
+
_bm = base_max_val.get(p.obj[0].path) if base_max_val else None
|
|
951
|
+
return max_used_y_val.get(p.obj[0].path), min_used_y_val.get(p.obj[0].path), _bm
|
|
939
952
|
|
|
940
953
|
if same_y_by_groups and p.obj[0].label in same_y_by_groups:
|
|
941
954
|
key = same_y_by_groups[p.obj[0].label]
|
|
942
|
-
|
|
955
|
+
_bm = base_max_val.get(key) if base_max_val else None
|
|
956
|
+
return max_used_y_val.get(key), min_used_y_val.get(key), _bm
|
|
943
957
|
|
|
944
958
|
if same_y and max_used_y_val:
|
|
945
|
-
|
|
959
|
+
_bm = max(base_max_val.values()) if base_max_val else None
|
|
960
|
+
return max(max_used_y_val.values()), min(min_used_y_val.values()), _bm
|
|
946
961
|
|
|
947
962
|
if default_y and p.type in ("density", "site-plot", "line"):
|
|
948
963
|
for obj in p.obj:
|
|
@@ -956,8 +971,8 @@ class Plot(object):
|
|
|
956
971
|
if same_y_by_groups and obj.label in same_y_by_groups:
|
|
957
972
|
max_y = max_used_y_val.get(obj.path, max_y)
|
|
958
973
|
min_y = min_used_y_val.get(obj.path, min_y)
|
|
959
|
-
return max_y, min_y
|
|
960
|
-
return None, None
|
|
974
|
+
return max_y, min_y, None
|
|
975
|
+
return None, None, None
|
|
961
976
|
|
|
962
977
|
# ------------------------------------------------------------------
|
|
963
978
|
# Main plot method
|
|
@@ -986,6 +1001,11 @@ class Plot(object):
|
|
|
986
1001
|
|
|
987
1002
|
igv_height_scale = kwargs.get("igv_height_scale")
|
|
988
1003
|
|
|
1004
|
+
# Extract / pop custom params before they flow into downstream **kwargs
|
|
1005
|
+
title = kwargs.pop("title", None)
|
|
1006
|
+
no_title = kwargs.pop("no_title", False)
|
|
1007
|
+
junctions_on_top = kwargs.pop("junctions_on_top", False)
|
|
1008
|
+
|
|
989
1009
|
# ====== Phase 1: Load data ======
|
|
990
1010
|
self._load_plot_data(n_jobs, *args, **kwargs)
|
|
991
1011
|
|
|
@@ -1010,7 +1030,7 @@ class Plot(object):
|
|
|
1010
1030
|
)
|
|
1011
1031
|
|
|
1012
1032
|
# ====== Phase 4: Precompute global y-limits ======
|
|
1013
|
-
max_used_y_val, min_used_y_val = {}, {}
|
|
1033
|
+
max_used_y_val, min_used_y_val, base_max_val = {}, {}, {}
|
|
1014
1034
|
same_y_by_groups = {}
|
|
1015
1035
|
default_y = self._load_default_y(kwargs.get("y_limit"))
|
|
1016
1036
|
|
|
@@ -1034,6 +1054,7 @@ class Plot(object):
|
|
|
1034
1054
|
p,
|
|
1035
1055
|
max_used_y_val,
|
|
1036
1056
|
min_used_y_val,
|
|
1057
|
+
base_max_val,
|
|
1037
1058
|
same_y_by_groups,
|
|
1038
1059
|
default_y,
|
|
1039
1060
|
**kwargs,
|
|
@@ -1048,25 +1069,30 @@ class Plot(object):
|
|
|
1048
1069
|
else:
|
|
1049
1070
|
ax_var = plt.subplot(gs[curr_idx, 0])
|
|
1050
1071
|
|
|
1051
|
-
if curr_idx == 0:
|
|
1052
|
-
ax_var.set_title(str(self.region), loc="left")
|
|
1072
|
+
if curr_idx == 0 and not no_title:
|
|
1073
|
+
ax_var.set_title(title or str(self.region), loc="left")
|
|
1053
1074
|
|
|
1054
|
-
max_y_val_, min_y_val_ = self._resolve_plot_y_limits(
|
|
1075
|
+
max_y_val_, min_y_val_, base_max_ = self._resolve_plot_y_limits(
|
|
1055
1076
|
p,
|
|
1056
1077
|
max_used_y_val,
|
|
1057
1078
|
min_used_y_val,
|
|
1079
|
+
base_max_val,
|
|
1058
1080
|
same_y_by_groups,
|
|
1059
1081
|
default_y,
|
|
1060
1082
|
same_y_sc=kwargs.get("same_y_sc"),
|
|
1061
1083
|
same_y=kwargs.get("same_y"),
|
|
1062
1084
|
)
|
|
1063
|
-
|
|
1085
|
+
|
|
1064
1086
|
if max_y_val_ is not None:
|
|
1065
1087
|
max_y_val_ *= 1.1
|
|
1066
|
-
|
|
1088
|
+
|
|
1067
1089
|
if min_y_val_ is not None:
|
|
1068
1090
|
min_y_val_ *= 1.1
|
|
1069
1091
|
|
|
1092
|
+
# For --same-y mode: use global base_max as arc height reference
|
|
1093
|
+
# so all panels draw junctions at a consistent scale.
|
|
1094
|
+
global_arc_ref = base_max_
|
|
1095
|
+
|
|
1070
1096
|
logger.info(
|
|
1071
1097
|
f"plotting {p.type} at idx: {curr_idx} with height_ratio: {height_ratio[curr_idx]}"
|
|
1072
1098
|
)
|
|
@@ -1088,6 +1114,8 @@ class Plot(object):
|
|
|
1088
1114
|
distance_between_label_axis=distance_between_label_axis,
|
|
1089
1115
|
raster=raster,
|
|
1090
1116
|
fill_step=fill_step,
|
|
1117
|
+
junctions_on_top=junctions_on_top,
|
|
1118
|
+
global_arc_ref=global_arc_ref,
|
|
1091
1119
|
**temp_params,
|
|
1092
1120
|
)
|
|
1093
1121
|
curr_idx += 1
|
|
@@ -1103,6 +1131,8 @@ class Plot(object):
|
|
|
1103
1131
|
distance_between_label_axis=distance_between_label_axis,
|
|
1104
1132
|
raster=raster,
|
|
1105
1133
|
fill_step=fill_step,
|
|
1134
|
+
junctions_on_top=junctions_on_top,
|
|
1135
|
+
global_arc_ref=global_arc_ref,
|
|
1106
1136
|
**self.params.get(p, {}),
|
|
1107
1137
|
)
|
|
1108
1138
|
elif p.type == "hic":
|
|
@@ -1123,6 +1153,8 @@ class Plot(object):
|
|
|
1123
1153
|
min_used_y_val=min_y_val_,
|
|
1124
1154
|
distance_between_label_axis=distance_between_label_axis,
|
|
1125
1155
|
raster=raster,
|
|
1156
|
+
junctions_on_top=junctions_on_top,
|
|
1157
|
+
global_arc_ref=global_arc_ref,
|
|
1126
1158
|
**self.params.get(p, {}),
|
|
1127
1159
|
)
|
|
1128
1160
|
curr_idx += 1
|
|
@@ -26,14 +26,17 @@ def _compute_y_limits(
|
|
|
26
26
|
density_by_strand: bool = False,
|
|
27
27
|
fill_step: str = "post",
|
|
28
28
|
show_mean_jxn_number: bool = False,
|
|
29
|
-
|
|
29
|
+
junctions_on_top: bool = False,
|
|
30
|
+
) -> Tuple[float, float, float]:
|
|
30
31
|
"""
|
|
31
32
|
Compute y-axis limits from ReadDepth data, including junction arc extents.
|
|
32
33
|
|
|
33
34
|
This is the shared core used by both precompute_y_limits() and plot_density()
|
|
34
35
|
to avoid code duplication.
|
|
35
36
|
|
|
36
|
-
Returns (max_used_y_val, min_used_y_val).
|
|
37
|
+
Returns (max_used_y_val, min_used_y_val, base_max).
|
|
38
|
+
base_max is the raw data maximum before junction arc expansion, used as
|
|
39
|
+
arc height reference for --same-y cross-panel consistency.
|
|
37
40
|
"""
|
|
38
41
|
# --- Determine data-driven baseline values for arc height ---
|
|
39
42
|
# These baselines capture the "true" data range before junction expansion.
|
|
@@ -69,19 +72,15 @@ def _compute_y_limits(
|
|
|
69
72
|
if min_used_y_val is None:
|
|
70
73
|
min_used_y_val = base_min
|
|
71
74
|
|
|
72
|
-
# --- Arc heights:
|
|
73
|
-
#
|
|
74
|
-
#
|
|
75
|
-
#
|
|
75
|
+
# --- Arc heights: always based on the data baseline ---
|
|
76
|
+
# Previously this used max(base_max, max_used_y_val) which caused
|
|
77
|
+
# arc height inflation when max_used_y_val was already expanded by
|
|
78
|
+
# the precompute step. The inflated arc peak then exceeded the
|
|
79
|
+
# y-axis limit and overflowed the panel.
|
|
80
|
+
# Use base_max / base_min exclusively — the limit expansion logic
|
|
81
|
+
# below still ensures the axis range grows to accommodate the arcs.
|
|
76
82
|
_arc_ref_max = base_max
|
|
77
|
-
if max_used_y_val is not None:
|
|
78
|
-
_arc_ref_max = max(base_max, max_used_y_val)
|
|
79
|
-
|
|
80
83
|
_arc_ref_min = base_min
|
|
81
|
-
if min_used_y_val is not None:
|
|
82
|
-
_arc_ref_min = min(
|
|
83
|
-
base_min, min_used_y_val
|
|
84
|
-
) # both negative → pick the more negative
|
|
85
84
|
|
|
86
85
|
top_arc_height = abs(3 * _arc_ref_max / 4)
|
|
87
86
|
bot_arc_height = abs(3 * _arc_ref_min / 4) if _arc_ref_min != 0 else top_arc_height
|
|
@@ -119,6 +118,8 @@ def _compute_y_limits(
|
|
|
119
118
|
|
|
120
119
|
if density_by_strand:
|
|
121
120
|
jxn_on_top = jxn.strand == "+"
|
|
121
|
+
elif junctions_on_top:
|
|
122
|
+
jxn_on_top = True
|
|
122
123
|
else:
|
|
123
124
|
jxn_on_top = jxn_idx % 2 == 0
|
|
124
125
|
if abs(min_used_y_val) < max_used_y_val:
|
|
@@ -163,7 +164,7 @@ def _compute_y_limits(
|
|
|
163
164
|
if density_by_strand:
|
|
164
165
|
max_used_y_val = max(abs(min_used_y_val), max_used_y_val)
|
|
165
166
|
min_used_y_val = -max_used_y_val
|
|
166
|
-
elif not density_by_strand and not jxns:
|
|
167
|
+
elif not density_by_strand and (junctions_on_top or not jxns):
|
|
167
168
|
min_used_y_val = 0
|
|
168
169
|
|
|
169
170
|
# Small expansion so the top / bottom arcs have visual breathing room
|
|
@@ -171,7 +172,7 @@ def _compute_y_limits(
|
|
|
171
172
|
max_used_y_val *= 1.1
|
|
172
173
|
min_used_y_val *= 1.1
|
|
173
174
|
|
|
174
|
-
return max_used_y_val, min_used_y_val
|
|
175
|
+
return max_used_y_val, min_used_y_val, base_max
|
|
175
176
|
|
|
176
177
|
|
|
177
178
|
def precompute_y_limits(
|
|
@@ -198,7 +199,7 @@ def precompute_y_limits(
|
|
|
198
199
|
if data is None:
|
|
199
200
|
data = obj.data
|
|
200
201
|
|
|
201
|
-
|
|
202
|
+
_max, _min, _base = _compute_y_limits(
|
|
202
203
|
data=data,
|
|
203
204
|
region=region,
|
|
204
205
|
graph_coords=graph_coords,
|
|
@@ -207,4 +208,7 @@ def precompute_y_limits(
|
|
|
207
208
|
density_by_strand=kwargs.get("density_by_strand", False),
|
|
208
209
|
fill_step=fill_step,
|
|
209
210
|
show_mean_jxn_number=False,
|
|
211
|
+
junctions_on_top=kwargs.get("junctions_on_top", False),
|
|
210
212
|
)
|
|
213
|
+
|
|
214
|
+
return _max, _min, _base
|
|
@@ -406,6 +406,87 @@ def _plot_local_domain(
|
|
|
406
406
|
# ============================================================================
|
|
407
407
|
|
|
408
408
|
|
|
409
|
+
def _auto_adjust_horizontal(
|
|
410
|
+
text_objs: List,
|
|
411
|
+
ax,
|
|
412
|
+
padding_px: float = 4.0,
|
|
413
|
+
max_iter: int = 20,
|
|
414
|
+
):
|
|
415
|
+
"""
|
|
416
|
+
水平碰撞检测与自动调整 —— 替代 adjust_text 的 2D 随意移动。
|
|
417
|
+
将所有文本沿弧线水平排列,通过贪婪算法确保相邻标签互不重叠。
|
|
418
|
+
|
|
419
|
+
原理:
|
|
420
|
+
1. 获取每个文本在显示坐标中的包围盒,计算其数据坐标宽度。
|
|
421
|
+
2. 按 x 坐标排序,从左到右逐对检查。
|
|
422
|
+
3. 如果前一个的右边缘 + 间距 > 后一个的左边缘,则把后一个向右推。
|
|
423
|
+
4. 重复迭代直到没有标签需要移动为止。
|
|
424
|
+
|
|
425
|
+
:param text_objs: matplotlib.text.Text 对象列表
|
|
426
|
+
:param ax: matplotlib Axes
|
|
427
|
+
:param padding_px: 标签之间的最小间距(像素)
|
|
428
|
+
:param max_iter: 最大迭代次数
|
|
429
|
+
"""
|
|
430
|
+
if len(text_objs) < 2:
|
|
431
|
+
return
|
|
432
|
+
|
|
433
|
+
try:
|
|
434
|
+
fig = ax.figure
|
|
435
|
+
fig.canvas.draw()
|
|
436
|
+
except Exception:
|
|
437
|
+
return # 无法渲染时直接跳过调整
|
|
438
|
+
|
|
439
|
+
inv_trans = ax.transData.inverted()
|
|
440
|
+
|
|
441
|
+
# ---- 收集每个标签的数据坐标宽度 ----
|
|
442
|
+
items = []
|
|
443
|
+
for t in text_objs:
|
|
444
|
+
x_data, y_data = t.get_position()
|
|
445
|
+
bbox = t.get_window_extent()
|
|
446
|
+
|
|
447
|
+
# 像素 → 数据坐标
|
|
448
|
+
left_data = inv_trans.transform((bbox.x0, bbox.y0))[0]
|
|
449
|
+
right_data = inv_trans.transform((bbox.x1, bbox.y0))[0]
|
|
450
|
+
half_w = (right_data - left_data) / 2.0
|
|
451
|
+
|
|
452
|
+
# 间距(像素 → 数据坐标)
|
|
453
|
+
p0 = inv_trans.transform((0, 0))[0]
|
|
454
|
+
p1 = inv_trans.transform((padding_px, 0))[0]
|
|
455
|
+
pad = abs(p1 - p0)
|
|
456
|
+
|
|
457
|
+
items.append({
|
|
458
|
+
"obj": t,
|
|
459
|
+
"x": x_data,
|
|
460
|
+
"y": y_data,
|
|
461
|
+
"half": half_w,
|
|
462
|
+
"pad": pad,
|
|
463
|
+
})
|
|
464
|
+
|
|
465
|
+
# 按 x 排序
|
|
466
|
+
items.sort(key=lambda d: d["x"])
|
|
467
|
+
|
|
468
|
+
# ---- 贪婪水平调整 ----
|
|
469
|
+
for _ in range(max_iter):
|
|
470
|
+
any_adjusted = False
|
|
471
|
+
|
|
472
|
+
for i in range(len(items) - 1):
|
|
473
|
+
cur = items[i]
|
|
474
|
+
nxt = items[i + 1]
|
|
475
|
+
|
|
476
|
+
cur_right = cur["x"] + cur["half"]
|
|
477
|
+
nxt_left = nxt["x"] - nxt["half"]
|
|
478
|
+
min_gap = max(cur["pad"], nxt["pad"])
|
|
479
|
+
|
|
480
|
+
need = cur_right + min_gap - nxt_left
|
|
481
|
+
if need > 0:
|
|
482
|
+
nxt["x"] += need
|
|
483
|
+
nxt["obj"].set_position((nxt["x"], nxt["y"]))
|
|
484
|
+
any_adjusted = True
|
|
485
|
+
|
|
486
|
+
if not any_adjusted:
|
|
487
|
+
break
|
|
488
|
+
|
|
489
|
+
|
|
409
490
|
def plot_density(
|
|
410
491
|
ax,
|
|
411
492
|
obj: Optional[File] = None,
|
|
@@ -460,6 +541,11 @@ def plot_density(
|
|
|
460
541
|
# drawn junction arcs have exactly the same height that was used to expand
|
|
461
542
|
# the y-axis limits. Using the already-expanded max_used_y_val for arc
|
|
462
543
|
# height causes non-convergent growth and truncated junction arcs.
|
|
544
|
+
|
|
545
|
+
# Determine whether y-limits were externally provided (e.g. from --same-y).
|
|
546
|
+
# Must be computed here BEFORE the arc-height block below uses it.
|
|
547
|
+
fixed_max_used_y = max_used_y_val is not None
|
|
548
|
+
|
|
463
549
|
if isinstance(data, dict):
|
|
464
550
|
_base_max = max(max(v.plus) if v.plus is not None else 0 for v in data.values())
|
|
465
551
|
_minus_maxes = [
|
|
@@ -473,17 +559,20 @@ def plot_density(
|
|
|
473
559
|
if _base_max % 2 == 1:
|
|
474
560
|
_base_max += 1
|
|
475
561
|
|
|
476
|
-
# Arc height reference: use
|
|
477
|
-
#
|
|
478
|
-
#
|
|
479
|
-
#
|
|
480
|
-
#
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
)
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
562
|
+
# Arc height reference: use global_arc_ref (from --same-y) when available
|
|
563
|
+
# and when y-limits were externally provided. This ensures panels with
|
|
564
|
+
# small data ranges don't get tiny arcs flattened against the x-axis when
|
|
565
|
+
# the panel's y-range is scaled up to match a larger panel.
|
|
566
|
+
#
|
|
567
|
+
# NOTE: we only use global_arc_ref when fixed_max_used_y is True so that
|
|
568
|
+
# standalone panels (no --same-y) preserve their original behavior.
|
|
569
|
+
global_arc_ref = kwargs.get("global_arc_ref")
|
|
570
|
+
if fixed_max_used_y and global_arc_ref is not None:
|
|
571
|
+
_arc_ref_max = max(_base_max, global_arc_ref)
|
|
572
|
+
_arc_ref_min = max(abs(_base_min), global_arc_ref) if _base_min != 0 else _arc_ref_max
|
|
573
|
+
else:
|
|
574
|
+
_arc_ref_max = _base_max
|
|
575
|
+
_arc_ref_min = _base_min
|
|
487
576
|
|
|
488
577
|
_top_arc_height = abs(3 * _arc_ref_max / 4)
|
|
489
578
|
_bot_arc_height = (
|
|
@@ -491,9 +580,9 @@ def plot_density(
|
|
|
491
580
|
)
|
|
492
581
|
|
|
493
582
|
# Compute y limits using shared logic
|
|
494
|
-
|
|
583
|
+
junctions_on_top = kwargs.get("junctions_on_top", False)
|
|
495
584
|
|
|
496
|
-
max_used_y_val, min_used_y_val = _compute_y_limits(
|
|
585
|
+
max_used_y_val, min_used_y_val, _base = _compute_y_limits(
|
|
497
586
|
data=data,
|
|
498
587
|
region=region,
|
|
499
588
|
graph_coords=graph_coords,
|
|
@@ -502,6 +591,7 @@ def plot_density(
|
|
|
502
591
|
density_by_strand=kwargs.get("density_by_strand", False),
|
|
503
592
|
fill_step=fill_step,
|
|
504
593
|
show_mean_jxn_number=show_mean_jxn_number,
|
|
594
|
+
junctions_on_top=junctions_on_top,
|
|
505
595
|
)
|
|
506
596
|
|
|
507
597
|
# Draw fill
|
|
@@ -557,6 +647,8 @@ def plot_density(
|
|
|
557
647
|
|
|
558
648
|
if kwargs.get("density_by_strand"):
|
|
559
649
|
jxn_on_top = jxn.strand == "+"
|
|
650
|
+
elif junctions_on_top:
|
|
651
|
+
jxn_on_top = True
|
|
560
652
|
else:
|
|
561
653
|
jxn_on_top = jxn_idx % 2 == 0
|
|
562
654
|
if abs(min_used_y_val) < max_used_y_val:
|
|
@@ -623,17 +715,15 @@ def plot_density(
|
|
|
623
715
|
va="center",
|
|
624
716
|
backgroundcolor="w",
|
|
625
717
|
)
|
|
626
|
-
t.set_bbox(dict(alpha=0))
|
|
718
|
+
t.set_bbox(dict(alpha=0, pad=0.5))
|
|
627
719
|
text_objs.append(t)
|
|
628
720
|
|
|
629
721
|
if show_junction_number and len(text_objs) > 1:
|
|
630
|
-
|
|
722
|
+
_auto_adjust_horizontal(
|
|
631
723
|
text_objs,
|
|
632
724
|
ax=ax,
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
arrowprops=None,
|
|
636
|
-
lim=100,
|
|
725
|
+
padding_px=4.0,
|
|
726
|
+
max_iter=20,
|
|
637
727
|
)
|
|
638
728
|
|
|
639
729
|
if obj and obj.title:
|
|
@@ -659,7 +749,7 @@ def plot_density(
|
|
|
659
749
|
):
|
|
660
750
|
max_used_y_val = max(abs(min_used_y_val), max_used_y_val)
|
|
661
751
|
min_used_y_val = -max_used_y_val
|
|
662
|
-
elif not kwargs.get("density_by_strand") and not jxns:
|
|
752
|
+
elif not kwargs.get("density_by_strand") and (junctions_on_top or not jxns):
|
|
663
753
|
min_used_y_val = 0
|
|
664
754
|
|
|
665
755
|
set_y_ticks(
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: trackplot
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.5
|
|
4
4
|
Summary: The trackplot is a tool for visualizing various next-generation sequencing (NGS) data, including DNA-seq, RNA-seq, single-cell RNA-seq and full-length sequencing datasets. https://sashimi.readthedocs.io/
|
|
5
5
|
Author-email: ygidtu <ygidtu@gmail.com>
|
|
6
6
|
License-Expression: BSD-3-Clause
|
|
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
|
|
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
|
|
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
|