tree-annotated-plot 0.2.1__py3-none-any.whl → 0.2.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- tree_annotated_plot/_plot.py +96 -5
- {tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/METADATA +1 -1
- {tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/RECORD +6 -6
- {tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/WHEEL +0 -0
- {tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/entry_points.txt +0 -0
- {tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/licenses/LICENSE +0 -0
tree_annotated_plot/_plot.py
CHANGED
|
@@ -233,6 +233,28 @@ def _build(
|
|
|
233
233
|
ch.axis = alt.Axis(labels=False, ticks=False, domain=False, title=None)
|
|
234
234
|
n_hits += 1
|
|
235
235
|
_check_walker_hits("strain-axis update", n_hits, len(axis_hits), axis)
|
|
236
|
+
|
|
237
|
+
# Pin line/trail/area connection order to tip order via Vega-Lite's
|
|
238
|
+
# `order` channel. Without this, an explicit categorical `color` scale
|
|
239
|
+
# `domain` (or other encoding choices) can shift Vega-Lite's
|
|
240
|
+
# connection-order heuristic away from the strain-axis sort, causing
|
|
241
|
+
# lines to crisscross. The order channel's `sort` only accepts
|
|
242
|
+
# ascending/descending, so we attach a `calculate` transform that
|
|
243
|
+
# computes a per-row tip rank and point `order` at that derived
|
|
244
|
+
# quantitative field. User-supplied `order` always wins.
|
|
245
|
+
rank_expr = (
|
|
246
|
+
f"indexof({json.dumps(list(tip_names))}, "
|
|
247
|
+
f"datum[{json.dumps(config.chart_strain_field)}])"
|
|
248
|
+
)
|
|
249
|
+
for node in _iter_connection_order_nodes(new_chart, config.chart_strain_field):
|
|
250
|
+
enc = _live_attr(node, "encoding")
|
|
251
|
+
if _live_attr(enc, "order") is not None:
|
|
252
|
+
continue
|
|
253
|
+
existing = _live_attr(node, "transform") or []
|
|
254
|
+
node.transform = list(existing) + [
|
|
255
|
+
{"calculate": rank_expr, "as": _TIP_ORDER_RANK_FIELD}
|
|
256
|
+
]
|
|
257
|
+
enc.order = alt.Order(f"{_TIP_ORDER_RANK_FIELD}:Q")
|
|
236
258
|
hoisted_config, hoisted_other = _pop_toplevel_only_attrs(new_chart)
|
|
237
259
|
|
|
238
260
|
combined = _concat_for_location(
|
|
@@ -276,22 +298,32 @@ def _concat_for_location(
|
|
|
276
298
|
user_chart: alt.TopLevelMixin,
|
|
277
299
|
location: TreeLocation,
|
|
278
300
|
) -> alt.HConcatChart | alt.VConcatChart:
|
|
279
|
-
"""Concat tree and chart in the order implied by the tree's location.
|
|
301
|
+
"""Concat tree and chart in the order implied by the tree's location.
|
|
302
|
+
|
|
303
|
+
The strain axis is resolved independent so the tree and chart can use
|
|
304
|
+
different scales on that axis (the tree's branch length vs. the chart's
|
|
305
|
+
measurement value), while still sharing the orthogonal strain axis. The
|
|
306
|
+
`color` scale is also resolved independent: when ``color_tree_by`` is set
|
|
307
|
+
the tree panel emits a `color_value:N` color scale with a tree-specific
|
|
308
|
+
domain, and Vega-Lite's default of sharing color across concat views
|
|
309
|
+
would merge it with any color encoding on the user's chart, hiding
|
|
310
|
+
user-chart marks whose color values aren't in the tree's domain.
|
|
311
|
+
"""
|
|
280
312
|
if location == "left":
|
|
281
313
|
return alt.hconcat(tree_chart, user_chart, spacing=0).resolve_scale(
|
|
282
|
-
y="independent"
|
|
314
|
+
y="independent", color="independent"
|
|
283
315
|
)
|
|
284
316
|
if location == "right":
|
|
285
317
|
return alt.hconcat(user_chart, tree_chart, spacing=0).resolve_scale(
|
|
286
|
-
y="independent"
|
|
318
|
+
y="independent", color="independent"
|
|
287
319
|
)
|
|
288
320
|
if location == "top":
|
|
289
321
|
return alt.vconcat(tree_chart, user_chart, spacing=0).resolve_scale(
|
|
290
|
-
x="independent"
|
|
322
|
+
x="independent", color="independent"
|
|
291
323
|
)
|
|
292
324
|
if location == "bottom":
|
|
293
325
|
return alt.vconcat(user_chart, tree_chart, spacing=0).resolve_scale(
|
|
294
|
-
x="independent"
|
|
326
|
+
x="independent", color="independent"
|
|
295
327
|
)
|
|
296
328
|
raise ValueError(f"unreachable: tree_location={location!r}")
|
|
297
329
|
|
|
@@ -999,6 +1031,65 @@ def _live_attr(obj: Any, name: str) -> Any:
|
|
|
999
1031
|
return v
|
|
1000
1032
|
|
|
1001
1033
|
|
|
1034
|
+
# Vega-Lite marks whose drawing order along the discrete axis is determined
|
|
1035
|
+
# by the order channel (rule 1 of the connection-order fallback chain). For
|
|
1036
|
+
# these marks pinning the order channel to a per-row tip-order rank ensures
|
|
1037
|
+
# the line / trail / area connects strains in tip order regardless of how
|
|
1038
|
+
# Vega-Lite would otherwise resolve the fallback (e.g. when an explicit
|
|
1039
|
+
# color-scale `domain` shifts it away from the y-axis sort).
|
|
1040
|
+
_CONNECTION_ORDER_MARKS = frozenset({"line", "trail", "area"})
|
|
1041
|
+
|
|
1042
|
+
# Derived field name appended to each connection-order mark's transform
|
|
1043
|
+
# pipeline. Chosen to be unlikely to collide with user data; private prefix
|
|
1044
|
+
# makes the intent clear if anyone inspects the rendered spec.
|
|
1045
|
+
_TIP_ORDER_RANK_FIELD = "_tap_strain_order_idx"
|
|
1046
|
+
|
|
1047
|
+
|
|
1048
|
+
def _mark_type(node: Any) -> str | None:
|
|
1049
|
+
"""Return the mark type string for a chart node, or None.
|
|
1050
|
+
|
|
1051
|
+
Handles altair's two mark forms: the plain string (e.g. ``"line"`` from
|
|
1052
|
+
``chart.mark_line()``) and the ``MarkDef`` object (e.g. from
|
|
1053
|
+
``chart.mark_line(point=True)``). Container nodes (LayerChart,
|
|
1054
|
+
HConcatChart, etc.) have no mark and return None.
|
|
1055
|
+
"""
|
|
1056
|
+
mark = _live_attr(node, "mark")
|
|
1057
|
+
if mark is None:
|
|
1058
|
+
return None
|
|
1059
|
+
if isinstance(mark, str):
|
|
1060
|
+
return mark
|
|
1061
|
+
type_ = _live_attr(mark, "type")
|
|
1062
|
+
return type_ if isinstance(type_, str) else None
|
|
1063
|
+
|
|
1064
|
+
|
|
1065
|
+
def _iter_connection_order_nodes(node: Any, chart_strain_field: str) -> Iterator[Any]:
|
|
1066
|
+
"""Yield the live chart node for every node whose mark is in
|
|
1067
|
+
``_CONNECTION_ORDER_MARKS`` and whose ``x``/``y`` strain encoding
|
|
1068
|
+
matches ``chart_strain_field``.
|
|
1069
|
+
|
|
1070
|
+
Mirrors `_iter_strain_axis_channels`'s traversal (hconcat / vconcat /
|
|
1071
|
+
concat / layer / spec descent). Yields the node itself (not just the
|
|
1072
|
+
encoding) so the caller can attach both a ``calculate`` transform and
|
|
1073
|
+
the ``order`` channel.
|
|
1074
|
+
"""
|
|
1075
|
+
if _mark_type(node) in _CONNECTION_ORDER_MARKS:
|
|
1076
|
+
enc = _live_attr(node, "encoding")
|
|
1077
|
+
if enc is not None:
|
|
1078
|
+
for channel in ("x", "y"):
|
|
1079
|
+
ch = _live_attr(enc, channel)
|
|
1080
|
+
if ch is not None and _channel_field(ch) == chart_strain_field:
|
|
1081
|
+
yield node
|
|
1082
|
+
break
|
|
1083
|
+
for attr in ("hconcat", "vconcat", "concat", "layer"):
|
|
1084
|
+
sub = _live_attr(node, attr)
|
|
1085
|
+
if isinstance(sub, list):
|
|
1086
|
+
for s in sub:
|
|
1087
|
+
yield from _iter_connection_order_nodes(s, chart_strain_field)
|
|
1088
|
+
spec = _live_attr(node, "spec")
|
|
1089
|
+
if spec is not None:
|
|
1090
|
+
yield from _iter_connection_order_nodes(spec, chart_strain_field)
|
|
1091
|
+
|
|
1092
|
+
|
|
1002
1093
|
def _pop_toplevel_only_attrs(
|
|
1003
1094
|
chart: alt.TopLevelMixin,
|
|
1004
1095
|
) -> tuple[Any, dict]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: tree-annotated-plot
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Annotate the axis of an Altair / Vega-Lite plot with a phylogenetic tree.
|
|
5
5
|
Project-URL: Documentation, https://jbloomlab.github.io/tree-annotated-plot/
|
|
6
6
|
Project-URL: Homepage, https://github.com/jbloomlab/tree-annotated-plot
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
tree_annotated_plot/__init__.py,sha256=R39gjVnl3K2VjLba6zwtwPGADFmtNOE2UYNk1y9_z6k,351
|
|
2
2
|
tree_annotated_plot/_color.py,sha256=nOZp-KIPy6s-6gBkzW36oHrwDk_SmvVNsMiIJ-dY1Io,26519
|
|
3
3
|
tree_annotated_plot/_config.py,sha256=XwokRKY-ytNXelxwuapRKedTBaEedDi_erUp3su7zUs,12498
|
|
4
|
-
tree_annotated_plot/_plot.py,sha256=
|
|
4
|
+
tree_annotated_plot/_plot.py,sha256=GCdcPyfTlem9YTlPkgdkJGPb5g4w2Vt0fevJcHEo9HI,72720
|
|
5
5
|
tree_annotated_plot/_tree.py,sha256=Bj-ABt8aooSwV7wAVTbwFGMmCnTPGOKweUWAdfb7aEI,13563
|
|
6
6
|
tree_annotated_plot/cli.py,sha256=yXI5fcLoIoLf6EjgPLmIQIAzDfkVoS5mSZ26_HSHVmk,9786
|
|
7
|
-
tree_annotated_plot-0.2.
|
|
8
|
-
tree_annotated_plot-0.2.
|
|
9
|
-
tree_annotated_plot-0.2.
|
|
10
|
-
tree_annotated_plot-0.2.
|
|
11
|
-
tree_annotated_plot-0.2.
|
|
7
|
+
tree_annotated_plot-0.2.2.dist-info/METADATA,sha256=8yAZMK69Tos2TmV-_Gb359Xm6OlmC8Elzzgzdp3yM04,11353
|
|
8
|
+
tree_annotated_plot-0.2.2.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
|
|
9
|
+
tree_annotated_plot-0.2.2.dist-info/entry_points.txt,sha256=K7edTocRbwmtM2OeZmCy3DLjmY3MR-q2EJmQQq4rj0E,69
|
|
10
|
+
tree_annotated_plot-0.2.2.dist-info/licenses/LICENSE,sha256=UIp30ue1HZit7tMmSK0SS3swJBqOcLLnLJn5LY5YdNE,1036
|
|
11
|
+
tree_annotated_plot-0.2.2.dist-info/RECORD,,
|
|
File without changes
|
{tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{tree_annotated_plot-0.2.1.dist-info → tree_annotated_plot-0.2.2.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|