trackplot 0.6.4__tar.gz → 0.6.6__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.
Files changed (65) hide show
  1. {trackplot-0.6.4/trackplot.egg-info → trackplot-0.6.6}/PKG-INFO +1 -1
  2. {trackplot-0.6.4 → trackplot-0.6.6}/pyproject.toml +4 -1
  3. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Bam.py +1 -4
  4. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/coord.py +29 -15
  5. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/core.py +39 -15
  6. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/limits.py +15 -15
  7. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/render.py +105 -20
  8. {trackplot-0.6.4 → trackplot-0.6.6/trackplot.egg-info}/PKG-INFO +1 -1
  9. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot.egg-info/SOURCES.txt +2 -13
  10. trackplot-0.6.6/trackplot.egg-info/entry_points.txt +2 -0
  11. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot.egg-info/top_level.txt +0 -1
  12. trackplot-0.6.4/ui/assets/Home-B1EPE6Lq.js +0 -1
  13. trackplot-0.6.4/ui/assets/Home-DuHqFHEh.css +0 -1
  14. trackplot-0.6.4/ui/assets/Plot-CkylK0fA.css +0 -1
  15. trackplot-0.6.4/ui/assets/Plot-jZAsK7Tq.js +0 -24
  16. trackplot-0.6.4/ui/assets/_plugin-vue_export-helper-DLf8tVwz.css +0 -1
  17. trackplot-0.6.4/ui/assets/_plugin-vue_export-helper-IgiCOkTJ.js +0 -1
  18. trackplot-0.6.4/ui/assets/css-BDwM7dEv.css +0 -1
  19. trackplot-0.6.4/ui/assets/css-CKLNJEB6.js +0 -4
  20. trackplot-0.6.4/ui/assets/index-De-rfTdP.js +0 -2
  21. trackplot-0.6.4/ui/assets/index-Dlq_kUQH.css +0 -1
  22. trackplot-0.6.4/ui/index.html +0 -16
  23. trackplot-0.6.4/ui/vite.svg +0 -1
  24. {trackplot-0.6.4 → trackplot-0.6.6}/LICENSE +0 -0
  25. {trackplot-0.6.4 → trackplot-0.6.6}/README.md +0 -0
  26. {trackplot-0.6.4 → trackplot-0.6.6}/setup.cfg +0 -0
  27. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/__init__.py +0 -0
  28. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/anno/AxLabel.py +0 -0
  29. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/anno/__init__.py +0 -0
  30. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/anno/theme.py +0 -0
  31. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/CoordinateMap.py +0 -0
  32. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/GenomicLoci.py +0 -0
  33. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/Junction.py +0 -0
  34. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/Protein.py +0 -0
  35. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/ReadDepth.py +0 -0
  36. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/Readder.py +0 -0
  37. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/Stroke.py +0 -0
  38. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/Transcript.py +0 -0
  39. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/__init__.py +0 -0
  40. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/base/pyUniprot.py +0 -0
  41. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/cli.py +0 -0
  42. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/conf/DomainSetting.py +0 -0
  43. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/conf/__init__.py +0 -0
  44. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/conf/config.py +0 -0
  45. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/conf/drawing.py +0 -0
  46. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/conf/ui.py +0 -0
  47. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/ATAC.py +0 -0
  48. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Annotation.py +0 -0
  49. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/BedGraph.py +0 -0
  50. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Bigwig.py +0 -0
  51. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Depth.py +0 -0
  52. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Fasta.py +0 -0
  53. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/File.py +0 -0
  54. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/HiCMatrixTrack.py +0 -0
  55. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Junction.py +0 -0
  56. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/Motif.py +0 -0
  57. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/ReadSegments.py +0 -0
  58. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/file/__init__.py +0 -0
  59. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/__init__.py +0 -0
  60. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/info.py +0 -0
  61. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot/utils.py +0 -0
  62. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/plot_func.py +0 -0
  63. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot/server.py +0 -0
  64. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot.egg-info/dependency_links.txt +0 -0
  65. {trackplot-0.6.4 → trackplot-0.6.6}/trackplot.egg-info/requires.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trackplot
3
- Version: 0.6.4
3
+ Version: 0.6.6
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.4"
3
+ version = "0.6.6"
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"
@@ -263,11 +263,8 @@ class Bam(SingleCell):
263
263
  ):
264
264
  continue
265
265
 
266
- if len(umis[barcode]) == 0:
266
+ if umi not in umis[barcode]:
267
267
  umis[barcode][umi] = hash(read.query_name)
268
- # else:
269
- # # There is no umi tag in atacdata, and we add it
270
- # continue
271
268
 
272
269
  start = read.reference_start
273
270
  if required_strand and strand != required_strand:
@@ -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
- intron = [exons[i - 1][1], exons[i][0]]
50
- for j in range(intron[0], intron[1]):
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[intron[0] - region.start - 1]
54
- + (j - intron[0] + 1) * intron_scale
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
- graph_coords[j - region.start] = (
59
- graph_coords[exon[0] - region.start - 1]
60
- + (j - exon[0] + 1) * exon_scale
61
- )
62
- intron = [exons[-1][-1], region.end]
63
- for i in range(intron[0], intron[1]):
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[intron[0] - region.start - 1]
67
- + (i - intron[0] + 1) * intron_scale
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] - 2
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:
@@ -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
- return max_used_y_val.get(p.obj[0].path), min_used_y_val.get(p.obj[0].path)
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
- return max_used_y_val.get(key), min_used_y_val.get(key)
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
- return max(max_used_y_val.values()), min(min_used_y_val.values())
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
@@ -1015,7 +1030,7 @@ class Plot(object):
1015
1030
  )
1016
1031
 
1017
1032
  # ====== Phase 4: Precompute global y-limits ======
1018
- max_used_y_val, min_used_y_val = {}, {}
1033
+ max_used_y_val, min_used_y_val, base_max_val = {}, {}, {}
1019
1034
  same_y_by_groups = {}
1020
1035
  default_y = self._load_default_y(kwargs.get("y_limit"))
1021
1036
 
@@ -1039,6 +1054,7 @@ class Plot(object):
1039
1054
  p,
1040
1055
  max_used_y_val,
1041
1056
  min_used_y_val,
1057
+ base_max_val,
1042
1058
  same_y_by_groups,
1043
1059
  default_y,
1044
1060
  **kwargs,
@@ -1056,22 +1072,27 @@ class Plot(object):
1056
1072
  if curr_idx == 0 and not no_title:
1057
1073
  ax_var.set_title(title or str(self.region), loc="left")
1058
1074
 
1059
- 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(
1060
1076
  p,
1061
1077
  max_used_y_val,
1062
1078
  min_used_y_val,
1079
+ base_max_val,
1063
1080
  same_y_by_groups,
1064
1081
  default_y,
1065
1082
  same_y_sc=kwargs.get("same_y_sc"),
1066
1083
  same_y=kwargs.get("same_y"),
1067
1084
  )
1068
-
1085
+
1069
1086
  if max_y_val_ is not None:
1070
1087
  max_y_val_ *= 1.1
1071
-
1088
+
1072
1089
  if min_y_val_ is not None:
1073
1090
  min_y_val_ *= 1.1
1074
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
+
1075
1096
  logger.info(
1076
1097
  f"plotting {p.type} at idx: {curr_idx} with height_ratio: {height_ratio[curr_idx]}"
1077
1098
  )
@@ -1094,6 +1115,7 @@ class Plot(object):
1094
1115
  raster=raster,
1095
1116
  fill_step=fill_step,
1096
1117
  junctions_on_top=junctions_on_top,
1118
+ global_arc_ref=global_arc_ref,
1097
1119
  **temp_params,
1098
1120
  )
1099
1121
  curr_idx += 1
@@ -1110,6 +1132,7 @@ class Plot(object):
1110
1132
  raster=raster,
1111
1133
  fill_step=fill_step,
1112
1134
  junctions_on_top=junctions_on_top,
1135
+ global_arc_ref=global_arc_ref,
1113
1136
  **self.params.get(p, {}),
1114
1137
  )
1115
1138
  elif p.type == "hic":
@@ -1131,6 +1154,7 @@ class Plot(object):
1131
1154
  distance_between_label_axis=distance_between_label_axis,
1132
1155
  raster=raster,
1133
1156
  junctions_on_top=junctions_on_top,
1157
+ global_arc_ref=global_arc_ref,
1134
1158
  **self.params.get(p, {}),
1135
1159
  )
1136
1160
  curr_idx += 1
@@ -27,14 +27,16 @@ def _compute_y_limits(
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]:
30
+ ) -> Tuple[float, float, float]:
31
31
  """
32
32
  Compute y-axis limits from ReadDepth data, including junction arc extents.
33
33
 
34
34
  This is the shared core used by both precompute_y_limits() and plot_density()
35
35
  to avoid code duplication.
36
36
 
37
- 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.
38
40
  """
39
41
  # --- Determine data-driven baseline values for arc height ---
40
42
  # These baselines capture the "true" data range before junction expansion.
@@ -70,19 +72,15 @@ def _compute_y_limits(
70
72
  if min_used_y_val is None:
71
73
  min_used_y_val = base_min
72
74
 
73
- # --- Arc heights: use the LARGER of (data baseline, incoming y-limit) ---
74
- # In --same-y mode, max_used_y_val may be the global limit (much larger than
75
- # this panel's own baseline). We want the arcs to look proportional to the
76
- # visible axis range, so we take max(baseline, incoming_limit).
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.
77
82
  _arc_ref_max = base_max
78
- if max_used_y_val is not None:
79
- _arc_ref_max = max(base_max, max_used_y_val)
80
-
81
83
  _arc_ref_min = base_min
82
- if min_used_y_val is not None:
83
- _arc_ref_min = min(
84
- base_min, min_used_y_val
85
- ) # both negative → pick the more negative
86
84
 
87
85
  top_arc_height = abs(3 * _arc_ref_max / 4)
88
86
  bot_arc_height = abs(3 * _arc_ref_min / 4) if _arc_ref_min != 0 else top_arc_height
@@ -174,7 +172,7 @@ def _compute_y_limits(
174
172
  max_used_y_val *= 1.1
175
173
  min_used_y_val *= 1.1
176
174
 
177
- return max_used_y_val, min_used_y_val
175
+ return max_used_y_val, min_used_y_val, base_max
178
176
 
179
177
 
180
178
  def precompute_y_limits(
@@ -201,7 +199,7 @@ def precompute_y_limits(
201
199
  if data is None:
202
200
  data = obj.data
203
201
 
204
- return _compute_y_limits(
202
+ _max, _min, _base = _compute_y_limits(
205
203
  data=data,
206
204
  region=region,
207
205
  graph_coords=graph_coords,
@@ -212,3 +210,5 @@ def precompute_y_limits(
212
210
  show_mean_jxn_number=False,
213
211
  junctions_on_top=kwargs.get("junctions_on_top", False),
214
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 the LARGER of (data baseline, incoming y-limit)
477
- # so arcs look proportional to the visible axis range (especially in
478
- # --same-y / --same-y-sc mode). If we only used the data baseline, panels
479
- # with small data would have tiny arcs hugging the x-axis when a large
480
- # global y-limit is shared across panels.
481
- _arc_ref_max = (
482
- max(_base_max, max_used_y_val) if max_used_y_val is not None else _base_max
483
- )
484
- _arc_ref_min = (
485
- min(_base_min, min_used_y_val) if min_used_y_val is not None else _base_min
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,11 +580,9 @@ def plot_density(
491
580
  )
492
581
 
493
582
  # Compute y limits using shared logic
494
- fixed_max_used_y = max_used_y_val is not None
495
-
496
583
  junctions_on_top = kwargs.get("junctions_on_top", False)
497
584
 
498
- max_used_y_val, min_used_y_val = _compute_y_limits(
585
+ max_used_y_val, min_used_y_val, _base = _compute_y_limits(
499
586
  data=data,
500
587
  region=region,
501
588
  graph_coords=graph_coords,
@@ -628,17 +715,15 @@ def plot_density(
628
715
  va="center",
629
716
  backgroundcolor="w",
630
717
  )
631
- t.set_bbox(dict(alpha=0))
718
+ t.set_bbox(dict(alpha=0, pad=0.5))
632
719
  text_objs.append(t)
633
720
 
634
721
  if show_junction_number and len(text_objs) > 1:
635
- adjust_text(
722
+ _auto_adjust_horizontal(
636
723
  text_objs,
637
724
  ax=ax,
638
- force_text=(0.5, 0.5),
639
- expand=(1.2, 1.2),
640
- arrowprops=None,
641
- lim=100,
725
+ padding_px=4.0,
726
+ max_iter=20,
642
727
  )
643
728
 
644
729
  if obj and obj.title:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: trackplot
3
- Version: 0.6.4
3
+ Version: 0.6.6
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
@@ -8,6 +8,7 @@ trackplot/server.py
8
8
  trackplot.egg-info/PKG-INFO
9
9
  trackplot.egg-info/SOURCES.txt
10
10
  trackplot.egg-info/dependency_links.txt
11
+ trackplot.egg-info/entry_points.txt
11
12
  trackplot.egg-info/requires.txt
12
13
  trackplot.egg-info/top_level.txt
13
14
  trackplot/anno/AxLabel.py
@@ -47,16 +48,4 @@ trackplot/plot/core.py
47
48
  trackplot/plot/info.py
48
49
  trackplot/plot/limits.py
49
50
  trackplot/plot/render.py
50
- trackplot/plot/utils.py
51
- ui/index.html
52
- ui/vite.svg
53
- ui/assets/Home-B1EPE6Lq.js
54
- ui/assets/Home-DuHqFHEh.css
55
- ui/assets/Plot-CkylK0fA.css
56
- ui/assets/Plot-jZAsK7Tq.js
57
- ui/assets/_plugin-vue_export-helper-DLf8tVwz.css
58
- ui/assets/_plugin-vue_export-helper-IgiCOkTJ.js
59
- ui/assets/css-BDwM7dEv.css
60
- ui/assets/css-CKLNJEB6.js
61
- ui/assets/index-De-rfTdP.js
62
- ui/assets/index-Dlq_kUQH.css
51
+ trackplot/plot/utils.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ trackplot = trackplot.cli:main
@@ -1 +0,0 @@
1
- import{Ct as e,St as t,Xt as n,gt as r,kn as i,mn as a,n as o,r as s,t as c,xt as l,yt as u,zt as d}from"./_plugin-vue_export-helper-IgiCOkTJ.js";import{A as f,D as p,n as m,r as h,t as g}from"./css-CKLNJEB6.js";var _=c(e({name:`Home`,data(){return{msg:`Welcome to trackplot App`,example:`https://trackplot.readthedocs.io/en/latest/imgs/diagram.png`}},setup(e){return(e,c)=>{let _=h,v=g,y=o,b=s,x=m;return d(),u(`div`,null,[r(`h1`,null,i(e.msg),1),t(_),t(b,null,{default:n(()=>[t(y,{span:12,offset:3},{default:n(()=>[t(v,{type:`primary`,href:`/#/plot`},{default:n(()=>[...c[0]||=[l(`Create your own plot`,-1)]]),_:1})]),_:1}),t(y,{span:6},{default:n(()=>[t(v,{type:`primary`,href:`https://github.com/ygidtu/trackplot/issues`,icon:a(p)},{default:n(()=>[...c[1]||=[l(`Report bug at Github`,-1)]]),_:1},8,[`icon`]),t(_,{direction:`vertical`}),t(v,{type:`primary`,icon:a(f),href:`https://trackplot.readthedocs.io/en/latest/web/`},{default:n(()=>[...c[2]||=[l(`Read the tutorial`,-1)]]),_:1},8,[`icon`])]),_:1}),t(y,{span:6})]),_:1}),t(_),t(b,null,{default:n(()=>[t(y,{span:20,offset:2},{default:n(()=>[t(x,{src:e.example,width:`100%`},null,8,[`src`])]),_:1})]),_:1})])}}}),[[`__scopeId`,`data-v-3d7de397`]]);export{_ as default};
@@ -1 +0,0 @@
1
- h3[data-v-3d7de397]{margin:40px 0 0}ul[data-v-3d7de397]{padding:0;list-style-type:none}li[data-v-3d7de397]{margin:0 10px;display:inline-block}a[data-v-3d7de397]{color:#42b983}