plot-io 0.12.0__py3-none-win_arm64.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.
- plot_io/__init__.py +73 -0
- plot_io/_methods.py +1691 -0
- plot_io/_native/__init__.py +199 -0
- plot_io/_native/__main__.py +197 -0
- plot_io/_native/_api.py +457 -0
- plot_io/_native/bin/windows-arm64.dll +0 -0
- plot_io/_native/kernels.py +335 -0
- plot_io/_native/manifest.json +49 -0
- plot_io/axis.py +1347 -0
- plot_io/boxes.py +1156 -0
- plot_io/canvas.py +389 -0
- plot_io/color/__init__.py +3 -0
- plot_io/color/data.py +1646 -0
- plot_io/color/maps.py +922 -0
- plot_io/color/values.py +295 -0
- plot_io/color/variants.py +1246 -0
- plot_io/colorbar.py +1426 -0
- plot_io/columns.py +614 -0
- plot_io/decorations.py +920 -0
- plot_io/defaults.py +809 -0
- plot_io/density.py +113 -0
- plot_io/ext.py +68 -0
- plot_io/facets.py +240 -0
- plot_io/figure.py +788 -0
- plot_io/formatters.py +97 -0
- plot_io/genome/__init__.py +17 -0
- plot_io/genome/adapters.py +356 -0
- plot_io/genome/combine.py +123 -0
- plot_io/genome/contacts.py +508 -0
- plot_io/genome/intervals.py +750 -0
- plot_io/genome/lanes.py +191 -0
- plot_io/genome/layout.py +184 -0
- plot_io/genome/links.py +364 -0
- plot_io/genome/pack.py +27 -0
- plot_io/genome/readers.py +145 -0
- plot_io/genome/regions.py +228 -0
- plot_io/genome/scale.py +349 -0
- plot_io/genome/series.py +344 -0
- plot_io/genome/signal.py +621 -0
- plot_io/genome/sites.py +782 -0
- plot_io/genome/sources.py +459 -0
- plot_io/genome/testing.py +161 -0
- plot_io/geometry.py +289 -0
- plot_io/groups.py +308 -0
- plot_io/guides.py +503 -0
- plot_io/helpers/__init__.py +17 -0
- plot_io/helpers/clustermap.py +202 -0
- plot_io/helpers/dendrogram.py +172 -0
- plot_io/helpers/grids.py +304 -0
- plot_io/helpers/qq.py +334 -0
- plot_io/helpers/raincloud.py +249 -0
- plot_io/helpers/tree.py +214 -0
- plot_io/helpers/volcano.py +505 -0
- plot_io/hints.py +125 -0
- plot_io/kernels.py +740 -0
- plot_io/labels.py +566 -0
- plot_io/layers/__init__.py +100 -0
- plot_io/layers/base.py +630 -0
- plot_io/layers/context.py +87 -0
- plot_io/layers/core.py +446 -0
- plot_io/layers/geoms.py +1362 -0
- plot_io/layers/grammar.py +586 -0
- plot_io/layers/paint.py +614 -0
- plot_io/layers/pipeline.py +170 -0
- plot_io/layers/positions.py +431 -0
- plot_io/layers/stats.py +1088 -0
- plot_io/layout.py +1381 -0
- plot_io/legend.py +605 -0
- plot_io/levels.py +509 -0
- plot_io/limits.py +124 -0
- plot_io/lines.py +42 -0
- plot_io/mapped.py +522 -0
- plot_io/markers.py +110 -0
- plot_io/markup.py +128 -0
- plot_io/numbers.py +354 -0
- plot_io/occupancy.py +295 -0
- plot_io/plot.py +597 -0
- plot_io/presets/__init__.py +51 -0
- plot_io/presets/_util.py +153 -0
- plot_io/presets/area.py +145 -0
- plot_io/presets/bar.py +261 -0
- plot_io/presets/box.py +400 -0
- plot_io/presets/catpoints.py +177 -0
- plot_io/presets/contour.py +353 -0
- plot_io/presets/curves.py +30 -0
- plot_io/presets/dots.py +355 -0
- plot_io/presets/ecdf.py +97 -0
- plot_io/presets/errorbar.py +106 -0
- plot_io/presets/errors.py +296 -0
- plot_io/presets/fill.py +74 -0
- plot_io/presets/heatmap.py +322 -0
- plot_io/presets/hexbin.py +198 -0
- plot_io/presets/hist.py +391 -0
- plot_io/presets/hist2d.py +87 -0
- plot_io/presets/kde.py +162 -0
- plot_io/presets/kde2d.py +147 -0
- plot_io/presets/line.py +143 -0
- plot_io/presets/pie.py +502 -0
- plot_io/presets/regression.py +115 -0
- plot_io/presets/scatter.py +194 -0
- plot_io/presets/stem.py +100 -0
- plot_io/presets/violin.py +323 -0
- plot_io/py.typed +0 -0
- plot_io/registry.py +82 -0
- plot_io/render.py +480 -0
- plot_io/rows.py +491 -0
- plot_io/scales.py +103 -0
- plot_io/seams.py +138 -0
- plot_io/settings.py +807 -0
- plot_io/shapes.py +66 -0
- plot_io/sharing.py +232 -0
- plot_io/sizemaps.py +97 -0
- plot_io/spec.py +240 -0
- plot_io/states.py +216 -0
- plot_io/styles/__init__.py +307 -0
- plot_io/styles/library.py +397 -0
- plot_io/tables.py +138 -0
- plot_io/text.py +412 -0
- plot_io/ticks.py +400 -0
- plot_io/times.py +287 -0
- plot_io/titles.py +225 -0
- plot_io/transform.py +114 -0
- plot_io/types.py +13604 -0
- plot_io/units.py +768 -0
- plot_io/validators.py +1029 -0
- plot_io/values.py +190 -0
- plot_io-0.12.0.dist-info/METADATA +215 -0
- plot_io-0.12.0.dist-info/RECORD +130 -0
- plot_io-0.12.0.dist-info/WHEEL +4 -0
- plot_io-0.12.0.dist-info/licenses/LICENSE +28 -0
plot_io/genome/lanes.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"""Lanes: a plot holding interval series is a vertical stack of lanes in its axes
|
|
2
|
+
rectangle.
|
|
3
|
+
|
|
4
|
+
The *value lane* holds the series with a y scale (signals, ordinary series, the Hi-C
|
|
5
|
+
triangle). Each interval series (a series with a ``lane`` setting; a signal drawn as a
|
|
6
|
+
heat strip) gets a lane of its own, measured in px (its rows × ``row_height``):
|
|
7
|
+
``lane="below"`` (the default) or ``"above"`` the value lane, each group top to bottom in
|
|
8
|
+
call order, ``lane_gap`` apart; or ``lane="over"``: drawn in the lane of the series called before it (over the value lane, its
|
|
9
|
+
rows span that lane; over an interval lane, the two share rows).
|
|
10
|
+
|
|
11
|
+
The y axis maps only the value lane: :func:`y_span` gives the px its state leaves at each
|
|
12
|
+
end (``AxisState.span``), which its mapper, ticks, grid and spine follow. Interval series
|
|
13
|
+
draw through a y mapper of their lane (``call.lane``, see :func:`lane_mapper`): rows from
|
|
14
|
+
the lane's top, no axis padding. With ``height="auto"`` a track is as high as its lanes
|
|
15
|
+
(:func:`natural_height`); with a fixed height the interval lanes keep their px and the
|
|
16
|
+
value lane takes the rest; without a value lane the lanes are scaled to fill the axes.
|
|
17
|
+
An interval lane's ``name`` is drawn left of the lane, centred on it (:func:`names`).
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
from typing import Any
|
|
21
|
+
|
|
22
|
+
from ..axis import Mapper
|
|
23
|
+
from ..settings import Setting, one_of
|
|
24
|
+
|
|
25
|
+
__all__ = ["lane_setting", "is_lane", "natural_height", "y_span", "value_height",
|
|
26
|
+
"lane_mapper", "names", "LANE_GAP"]
|
|
27
|
+
|
|
28
|
+
#: ``lane_gap`` of a plot without the setting (an ordinary plot), in em.
|
|
29
|
+
LANE_GAP = 0.3
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def lane_setting() -> Setting:
|
|
33
|
+
return Setting(one_of("below", "above", "over"),
|
|
34
|
+
doc="Where the series is drawn: in a lane of its own `below` or `above` "
|
|
35
|
+
"the track's value lane (signals, Hi-C; each group top to bottom "
|
|
36
|
+
"in call order), or `over` the lane of the series called before it "
|
|
37
|
+
"(over a signal, its rows span the signal's lane; over intervals, "
|
|
38
|
+
"the two share rows; over a Hi-C triangle, intervals are drawn as "
|
|
39
|
+
"TADs and links as loop marks).")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def is_lane(series: Any) -> bool:
|
|
43
|
+
"""Whether a series is drawn in a lane of its own: it has a ``lane`` setting and rows
|
|
44
|
+
(``lane_rows()``, their count; ``drawn_rows()``, the row range its features cover;
|
|
45
|
+
``row_px()``, the height of a row), and ``in_lane()`` when it has one (a signal is a
|
|
46
|
+
lane as a heat strip only)."""
|
|
47
|
+
if not (hasattr(series, "lane_rows") and "lane" in series.SETTINGS):
|
|
48
|
+
return False
|
|
49
|
+
in_lane = getattr(series, "in_lane", None)
|
|
50
|
+
return in_lane is None or bool(in_lane())
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Lane:
|
|
54
|
+
"""A lane: ``where`` (``"above"``, ``"value"`` or ``"below"``), the interval series
|
|
55
|
+
drawn in it, its top and height (px from the axes' top, once placed)."""
|
|
56
|
+
|
|
57
|
+
__slots__ = ("where", "members", "top", "height")
|
|
58
|
+
|
|
59
|
+
def __init__(self, where: str, members: list[Any]) -> None:
|
|
60
|
+
self.where, self.members = where, members
|
|
61
|
+
self.top = self.height = 0.0
|
|
62
|
+
|
|
63
|
+
def rows(self) -> int:
|
|
64
|
+
"""The rows the members share."""
|
|
65
|
+
return max((s.lane_rows() for s in self.members), default=1)
|
|
66
|
+
|
|
67
|
+
def px(self) -> float:
|
|
68
|
+
"""The lane's own height: its rows × its first member's row height
|
|
69
|
+
(``row_px()``: ``row_height``, half of it for squished rows), or the height a
|
|
70
|
+
member asks (``lane_px()``: links), the larger."""
|
|
71
|
+
rowed = [s for s in self.members if not hasattr(s, "lane_px")]
|
|
72
|
+
own = [s.lane_px() for s in self.members if hasattr(s, "lane_px")]
|
|
73
|
+
if rowed:
|
|
74
|
+
own.append(max(s.lane_rows() for s in rowed) * rowed[0].row_px())
|
|
75
|
+
return max(own)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _lanes(plot: Any) -> tuple[Lane | None, list[Lane]]:
|
|
79
|
+
"""The value lane (``None``: the plot has no series with a y scale) and every lane, top
|
|
80
|
+
to bottom."""
|
|
81
|
+
value = Lane("value", [])
|
|
82
|
+
has_value = any(t.series for t in getattr(plot, "twins", ()))
|
|
83
|
+
above: list[Lane] = []
|
|
84
|
+
below: list[Lane] = []
|
|
85
|
+
prev: Lane | None = None
|
|
86
|
+
for s in plot.series:
|
|
87
|
+
if not is_lane(s):
|
|
88
|
+
has_value = True
|
|
89
|
+
prev = value
|
|
90
|
+
continue
|
|
91
|
+
where = s.resolve("lane")
|
|
92
|
+
if where == "over" and prev is not None:
|
|
93
|
+
prev.members.append(s)
|
|
94
|
+
continue
|
|
95
|
+
lane = Lane("above" if where == "above" else "below", [s])
|
|
96
|
+
(above if where == "above" else below).append(lane)
|
|
97
|
+
prev = lane
|
|
98
|
+
if not has_value:
|
|
99
|
+
return None, above + below
|
|
100
|
+
return value, above + [value] + below
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def lane_gap(plot: Any) -> float:
|
|
104
|
+
if "lane_gap" in plot.SETTINGS:
|
|
105
|
+
return float(plot.resolve("lane_gap"))
|
|
106
|
+
return LANE_GAP * float(plot.resolve("font_size"))
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def natural_height(plot: Any, value_height: float) -> float:
|
|
110
|
+
"""The height of a plot's lanes: the interval lanes', the value lane
|
|
111
|
+
``value_height`` high (when the plot has one), and the gaps between them."""
|
|
112
|
+
value, lanes = _lanes(plot)
|
|
113
|
+
if not lanes:
|
|
114
|
+
return value_height
|
|
115
|
+
total = sum(value_height if la is value else la.px() for la in lanes)
|
|
116
|
+
return total + lane_gap(plot) * (len(lanes) - 1)
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _placed(plot: Any, h: float) -> tuple[Lane | None, list[Lane]]:
|
|
120
|
+
"""The lanes placed in axes ``h`` px high."""
|
|
121
|
+
value, lanes = _lanes(plot)
|
|
122
|
+
if not lanes:
|
|
123
|
+
return value, lanes
|
|
124
|
+
gaps = lane_gap(plot) * (len(lanes) - 1)
|
|
125
|
+
own = [la.px() for la in lanes if la is not value]
|
|
126
|
+
total = sum(own)
|
|
127
|
+
if value is not None:
|
|
128
|
+
rest = h - gaps - total
|
|
129
|
+
k = 1.0 if rest >= 1.0 or not total else max(0.0, h - gaps - 1.0) / total
|
|
130
|
+
rest = max(rest, min(1.0, h))
|
|
131
|
+
else:
|
|
132
|
+
rest = 0.0
|
|
133
|
+
k = max(0.0, h - gaps) / total if total else 1.0
|
|
134
|
+
y = 0.0
|
|
135
|
+
for la in lanes:
|
|
136
|
+
la.top = y
|
|
137
|
+
la.height = rest if la is value else la.px() * k
|
|
138
|
+
y += la.height + lane_gap(plot)
|
|
139
|
+
return value, lanes
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
def y_span(plot: Any, h: float) -> tuple[float, float]:
|
|
143
|
+
"""The px the y axis leaves below and above the value lane of axes ``h`` px high (0 and
|
|
144
|
+
0: the plot has no interval lane, or no value lane)."""
|
|
145
|
+
value, lanes = _placed(plot, h)
|
|
146
|
+
if value is None or len(lanes) < 2:
|
|
147
|
+
return 0.0, 0.0
|
|
148
|
+
return h - value.top - value.height, value.top
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def value_height(plot: Any, h: float) -> float:
|
|
152
|
+
"""The height of the value lane of axes ``h`` px high (``h``: no interval lane)."""
|
|
153
|
+
value, lanes = _placed(plot, h)
|
|
154
|
+
return h if value is None or len(lanes) < 2 else value.height
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def _lane_of(plot: Any, series: Any, h: float) -> Lane | None:
|
|
158
|
+
_, lanes = _placed(plot, h)
|
|
159
|
+
return next((la for la in lanes if any(s is series for s in la.members)), None)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def lane_mapper(series: Any) -> Any:
|
|
163
|
+
"""``call.lane`` of an interval series: the y mapper of its rows in its lane, from the
|
|
164
|
+
plot's drawing context (row 0 at the lane's top)."""
|
|
165
|
+
def mapper(ctx: Any) -> Mapper:
|
|
166
|
+
x0, y0, x1, y1 = ctx.rect
|
|
167
|
+
lane = _lane_of(series.plot, series, y1 - y0)
|
|
168
|
+
if lane is None:
|
|
169
|
+
top, height, lo, hi = y0, y1 - y0, 0.0, float(series.lane_rows())
|
|
170
|
+
elif lane.where == "value": # the features span the value lane
|
|
171
|
+
top, height = y0 + lane.top, lane.height
|
|
172
|
+
lo, hi = series.drawn_rows()
|
|
173
|
+
else:
|
|
174
|
+
top, height, lo, hi = y0 + lane.top, lane.height, 0.0, float(lane.rows())
|
|
175
|
+
return Mapper(None, lo, hi, top, top + height)
|
|
176
|
+
return mapper
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
def names(plot: Any, h: float) -> list[tuple[Any, float, float]]:
|
|
180
|
+
"""The names of the interval lanes of axes ``h`` px high: ``(name, top, bottom)``, px
|
|
181
|
+
from the axes' top (a lane's name: its first member's ``name``)."""
|
|
182
|
+
_, lanes = _placed(plot, h)
|
|
183
|
+
out = []
|
|
184
|
+
for la in lanes:
|
|
185
|
+
if la.where == "value":
|
|
186
|
+
continue
|
|
187
|
+
name = next((s.resolve("name") for s in la.members if s.resolve("name") is not None),
|
|
188
|
+
None)
|
|
189
|
+
if name is not None and name != "":
|
|
190
|
+
out.append((name, la.top, la.top + la.height))
|
|
191
|
+
return out
|
plot_io/genome/layout.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"""Genome tracks: a tracks figure (``plt.Tracks(region)``) whose rows are :class:`Track`
|
|
2
|
+
plots over a genome x axis.
|
|
3
|
+
|
|
4
|
+
``plt.Tracks(region, **settings)`` is a :class:`~plot_io.Figure` with ``layout="tracks"``:
|
|
5
|
+
one column of plots sharing x, each as high as its ``height``; its x axes take the genome
|
|
6
|
+
scale over ``region`` (``figure.x_axis``). A track is a plot preset: a height from its
|
|
7
|
+
content, space above it, the y axis with at most 4 ticks and its label left of the track
|
|
8
|
+
(``label_position="left"``; a ``band`` label longer than the track is drawn as ``left``),
|
|
9
|
+
axis lines left and bottom (Hi-C: bottom only). Its interval series are in lanes of their
|
|
10
|
+
own beside the value lane (:mod:`plot_io.genome.lanes`). Plot types called on the figure
|
|
11
|
+
(``fig.bigwig(…)``) add a track holding them; called on a track, they overlay in it.
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
from typing import Any
|
|
15
|
+
|
|
16
|
+
from .._methods import TrackMixin
|
|
17
|
+
from ..figure import Figure
|
|
18
|
+
from ..plot import Plot
|
|
19
|
+
from ..settings import Setting, either, literal, table
|
|
20
|
+
from ..styles import register_class
|
|
21
|
+
from ..validators import length
|
|
22
|
+
from .contacts import HiC
|
|
23
|
+
from .lanes import is_lane, natural_height
|
|
24
|
+
from .series import GenomeSeries, axes_width
|
|
25
|
+
from .signal import fill_mode
|
|
26
|
+
|
|
27
|
+
__all__ = ["Track", "Tracks"]
|
|
28
|
+
|
|
29
|
+
#: Keywords of :func:`Tracks` that go to the genome x axis.
|
|
30
|
+
AXIS_KEYS = ("unit", "unit_in", "tick_format", "origin", "region_widths", "region_gap",
|
|
31
|
+
"chr_sizes", "scale_bar")
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def _hangs(track: "Track") -> bool:
|
|
35
|
+
"""A Hi-C triangle hangs from the top (no x axis under it)."""
|
|
36
|
+
return any(isinstance(s, HiC) and s.resolve("direction") == "down" for s in track.series)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _spines(track: "Track") -> Any:
|
|
40
|
+
if any(isinstance(s, HiC) and not s.square() for s in track.series):
|
|
41
|
+
return "none" if _hangs(track) else ["bottom"]
|
|
42
|
+
return ["left", "bottom"]
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def check_height(v: Any) -> Any:
|
|
46
|
+
"""A track height: ``"auto"``, a length, or a share ``"2fr"`` (kept as ``("fr", 2.0)``:
|
|
47
|
+
the figure turns it into a row ratio)."""
|
|
48
|
+
if isinstance(v, tuple) and len(v) == 2 and v[0] == "fr":
|
|
49
|
+
return v
|
|
50
|
+
if isinstance(v, str) and v.strip().lower().endswith("fr"):
|
|
51
|
+
try:
|
|
52
|
+
n = float(v.strip()[:-2] or 1.0)
|
|
53
|
+
except ValueError:
|
|
54
|
+
raise ValueError(f"expected a share such as '1fr', got {v!r}") from None
|
|
55
|
+
if not n > 0:
|
|
56
|
+
raise ValueError(f"expected a positive share, got {v!r}")
|
|
57
|
+
return ("fr", n)
|
|
58
|
+
return either(literal("auto"), length(positive=True))(v)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def _auto_space(track: "Track") -> float:
|
|
62
|
+
"""``space_above="auto"``: none above the figure's first plot, else
|
|
63
|
+
``auto_space_above``."""
|
|
64
|
+
plots = track.figure.plots
|
|
65
|
+
if plots and plots[0] is track:
|
|
66
|
+
return 0.0
|
|
67
|
+
return float(track.resolve("auto_space_above"))
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
def _auto_height(track: "Track") -> float:
|
|
71
|
+
"""``height="auto"``: the track's lanes, its value lane as high as the largest height
|
|
72
|
+
a genome series asks (Hi-C from its depth), else ``auto_height``."""
|
|
73
|
+
width = axes_width(track)
|
|
74
|
+
asked = [a for a in (s.track_height(width) for s in track.series
|
|
75
|
+
if isinstance(s, GenomeSeries) and not is_lane(s)) if a is not None]
|
|
76
|
+
value = max(asked) if asked else float(track.resolve("auto_height"))
|
|
77
|
+
return natural_height(track, value)
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
class Track(TrackMixin, Plot):
|
|
81
|
+
"""A row of a tracks figure (:func:`Tracks`): a plot with a height and space above it.
|
|
82
|
+
Add series with its methods (``bigwig``, ``bed``, ``genes``, ``gtf``, ``hic``, or any
|
|
83
|
+
plot type).
|
|
84
|
+
|
|
85
|
+
A track has axis lines left and bottom (Hi-C: bottom only), at most 4 y ticks and its y
|
|
86
|
+
``label`` left of it. A plot holding interval series is a stack of lanes: the value
|
|
87
|
+
lane (signals, ordinary series, Hi-C) and one lane per interval series (``bed``,
|
|
88
|
+
``genes``, ``gtf``: rows × ``row_height``; ``links``: its arcs), ``lane="below"``
|
|
89
|
+
(default) or ``"above"`` it, ``lane_gap`` apart; ``lane="over"`` draws a series in the
|
|
90
|
+
lane of the one before it (over Hi-C: intervals as domains, links as loops). An
|
|
91
|
+
interval lane's ``name`` is written left of it. The y axis maps the value lane only;
|
|
92
|
+
interval rows are px. ``height``: a length, a share (``"1fr"``) or ``"auto"`` (the Hi-C
|
|
93
|
+
triangle's, capped by ``max_height``, or ``auto_height`` for signals, plus the lanes).
|
|
94
|
+
``bins`` of ``bigwig`` and ``hic``: a width on the axes (``"0.5px"``: two per px), a size
|
|
95
|
+
(``"10kb"``, read on the genome-wide grid) or a count. ``share_y=other_track``, or the
|
|
96
|
+
figure's ``share_y="bigwig"`` (a plot type's name), puts tracks on one y scale.
|
|
97
|
+
"""
|
|
98
|
+
|
|
99
|
+
SETTINGS = table(**{**Plot.SETTINGS, **dict(
|
|
100
|
+
height=Setting(check_height, auto=_auto_height,
|
|
101
|
+
doc="Height of the track's axes: a length, a share of the room the "
|
|
102
|
+
"fixed heights leave (`\"1fr\"`, with the figure's height given), "
|
|
103
|
+
"or `auto`: the lanes' heights, intervals rows × `row_height`, the "
|
|
104
|
+
"value lane from Hi-C's `depth` (square cells), else "
|
|
105
|
+
"`auto_height`."),
|
|
106
|
+
auto_height=Setting(length(positive=True, percent="em"),
|
|
107
|
+
doc="Height of the value lane (signals) and of empty tracks with "
|
|
108
|
+
"`height=\"auto\"`."),
|
|
109
|
+
space_above=Setting(either(literal("auto"), length(lo=0, percent="em")),
|
|
110
|
+
auto=_auto_space,
|
|
111
|
+
doc="Space above the track; `auto`: none for the first track, "
|
|
112
|
+
"else `auto_space_above`."),
|
|
113
|
+
auto_space_above=Setting(length(lo=0, percent="em"),
|
|
114
|
+
doc="The space `space_above=\"auto\"` leaves."),
|
|
115
|
+
lane_gap=Setting(length(lo=0, percent="em"),
|
|
116
|
+
doc="Space between the track's lanes (the value lane and each "
|
|
117
|
+
"interval series' lane)."),
|
|
118
|
+
fill_mode=Setting(fill_mode,
|
|
119
|
+
doc="Fill between the track's signals (replacing their `fill_to`): "
|
|
120
|
+
"`\"in_between(up)\"` (or `\"in_between\"`), each band between "
|
|
121
|
+
"two neighbouring signals in the fill color of the one above; "
|
|
122
|
+
"`(down)`, of the one below; `(max)`, one band from the lowest "
|
|
123
|
+
"signal to the highest in the color of the top one; `(min)`, of "
|
|
124
|
+
"the bottom one. `None`: each signal's `fill_to`."),
|
|
125
|
+
)})
|
|
126
|
+
RULES = {"spines": _spines}
|
|
127
|
+
#: a ``band`` label longer than the track is drawn as ``left`` (:mod:`plot_io.rows`)
|
|
128
|
+
BAND_FITS = True
|
|
129
|
+
|
|
130
|
+
def __init__(self, figure: Any = None, id: Any = None, **settings: Any) -> None:
|
|
131
|
+
own = {k: settings.pop(k) for k in list(settings)
|
|
132
|
+
if k in Track.SETTINGS and k not in Plot.SETTINGS}
|
|
133
|
+
y_axis = settings.pop("y_axis", None)
|
|
134
|
+
if figure is None:
|
|
135
|
+
figure = Tracks()
|
|
136
|
+
super().__init__(figure=figure, id=id, **settings)
|
|
137
|
+
self.y_axis.set(max_ticks=4, label_position="left", label_pad="0.5em")
|
|
138
|
+
self.set(**own)
|
|
139
|
+
if y_axis is not None:
|
|
140
|
+
self._set_axes({"y_axis": y_axis})
|
|
141
|
+
|
|
142
|
+
def __repr__(self) -> str:
|
|
143
|
+
label = self.y_axis.get("label")
|
|
144
|
+
return f"Track({label!r})" if label else f"Track({self.id!r})"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def Tracks(region: Any = None, **settings: Any) -> Figure:
|
|
148
|
+
"""A figure of genome tracks over ``region`` (``"chr2:143,120,001-143,780,000"``,
|
|
149
|
+
``(chr, start, end)`` or a mapping), or over a list of regions drawn side by side in
|
|
150
|
+
each track (segments of one genome axis: :mod:`plot_io.genome.scale`); a chromosome
|
|
151
|
+
name (``"chr1"``) is the whole chromosome: ``layout="tracks"``, its x axes on the
|
|
152
|
+
genome scale over the region (``unit``, ``unit_in``, ``tick_format``, ``origin``,
|
|
153
|
+
``region_widths``, ``region_gap``, ``chr_sizes`` and ``scale_bar`` go to
|
|
154
|
+
``figure.x_axis`` too); the other
|
|
155
|
+
settings are the figure's.
|
|
156
|
+
|
|
157
|
+
A region is a string, 1-based and inclusive (``"chr2:143,120,001-143,780,000"``;
|
|
158
|
+
positions may carry a unit: ``"chr1:1.2M-1.5M"``), a ``(chr, start, end)`` tuple
|
|
159
|
+
(0-based, half-open) or a mapping with ``chr``, ``start``, ``end``; it has no strand. A
|
|
160
|
+
chromosome name takes its size from the x axis' ``chr_sizes``, else the readers'. Data
|
|
161
|
+
are read when the figure is drawn, for the region drawn: ``save``, ``write``, ``draw`` and
|
|
162
|
+
``size`` take ``region=``.
|
|
163
|
+
|
|
164
|
+
The x axis: ticks under every track, tick labels and the label (``chr2 (Mb)``) under the
|
|
165
|
+
bottom one (``label_outer=False``: every track). ``unit`` (``auto``, ``bp``, ``kb``,
|
|
166
|
+
``Mb``) and ``unit_in`` (``"label"``, or ``"ticks"``: ``-500kb 0 500kb``) set the labels;
|
|
167
|
+
``tick_format="chr:pos"`` writes positions; in a ``label`` string ``{chr}``, ``{unit}``,
|
|
168
|
+
``{start}``, ``{end}`` are filled in. ``origin`` (a position, ``"center"``, ``"start"``,
|
|
169
|
+
``"end"``) is drawn at 0: limits, ticks and decorations are distances from it.
|
|
170
|
+
``scale_bar=True`` (or a length) draws a scale bar in place of ticks. Regions side by
|
|
171
|
+
side (a list) are segments of one x axis, ``region_widths`` (``"span"`` or ``"equal"``)
|
|
172
|
+
wide and ``region_gap`` apart, each with its ticks and label; each series reads each
|
|
173
|
+
region, and a track keeps one y scale across them.
|
|
174
|
+
"""
|
|
175
|
+
axis = {k: settings.pop(k) for k in AXIS_KEYS if k in settings}
|
|
176
|
+
fig = Figure(layout="tracks", **settings)
|
|
177
|
+
if region is not None:
|
|
178
|
+
axis["region"] = region
|
|
179
|
+
if axis:
|
|
180
|
+
fig.x_axis.set(scale="genome", **axis)
|
|
181
|
+
return fig
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
register_class(Track)
|