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.
@@ -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.1
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=l0GP66kLsVl1UjBmHIpnFcRGbFfk6qztxgi8ebYyxnM,68334
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.1.dist-info/METADATA,sha256=EWRbJmbr3W_Rrm3-ggkTB_mhA06czQDaBO91RM_NdBQ,11353
8
- tree_annotated_plot-0.2.1.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
9
- tree_annotated_plot-0.2.1.dist-info/entry_points.txt,sha256=K7edTocRbwmtM2OeZmCy3DLjmY3MR-q2EJmQQq4rj0E,69
10
- tree_annotated_plot-0.2.1.dist-info/licenses/LICENSE,sha256=UIp30ue1HZit7tMmSK0SS3swJBqOcLLnLJn5LY5YdNE,1036
11
- tree_annotated_plot-0.2.1.dist-info/RECORD,,
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,,