plot3 0.8.0__tar.gz → 0.9.0__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 (84) hide show
  1. {plot3-0.8.0 → plot3-0.9.0}/PKG-INFO +2 -2
  2. plot3-0.9.0/plot3/__version__.py +1 -0
  3. {plot3-0.8.0 → plot3-0.9.0}/plot3/jupyter.py +57 -49
  4. {plot3-0.8.0 → plot3-0.9.0}/plot3/masking.py +34 -15
  5. {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/PKG-INFO +2 -2
  6. {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/SOURCES.txt +1 -0
  7. plot3-0.9.0/plot3.egg-info/entry_points.txt +2 -0
  8. {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/requires.txt +1 -1
  9. {plot3-0.8.0 → plot3-0.9.0}/pyproject.toml +6 -1
  10. {plot3-0.8.0 → plot3-0.9.0}/tests/test_masking.py +21 -11
  11. plot3-0.8.0/plot3/__version__.py +0 -1
  12. {plot3-0.8.0 → plot3-0.9.0}/LICENSE +0 -0
  13. {plot3-0.8.0 → plot3-0.9.0}/README.md +0 -0
  14. {plot3-0.8.0 → plot3-0.9.0}/plot3/__init__.py +0 -0
  15. {plot3-0.8.0 → plot3-0.9.0}/plot3/aesexpr.py +0 -0
  16. {plot3-0.8.0 → plot3-0.9.0}/plot3/build.py +0 -0
  17. {plot3-0.8.0 → plot3-0.9.0}/plot3/calculus.py +0 -0
  18. {plot3-0.8.0 → plot3-0.9.0}/plot3/compose.py +0 -0
  19. {plot3-0.8.0 → plot3-0.9.0}/plot3/contour.py +0 -0
  20. {plot3-0.8.0 → plot3-0.9.0}/plot3/craft.py +0 -0
  21. {plot3-0.8.0 → plot3-0.9.0}/plot3/encode.py +0 -0
  22. {plot3-0.8.0 → plot3-0.9.0}/plot3/expr.py +0 -0
  23. {plot3-0.8.0 → plot3-0.9.0}/plot3/flip.py +0 -0
  24. {plot3-0.8.0 → plot3-0.9.0}/plot3/function.py +0 -0
  25. {plot3-0.8.0 → plot3-0.9.0}/plot3/geoms.py +0 -0
  26. {plot3-0.8.0 → plot3-0.9.0}/plot3/ggplot.py +0 -0
  27. {plot3-0.8.0 → plot3-0.9.0}/plot3/io.py +0 -0
  28. {plot3-0.8.0 → plot3-0.9.0}/plot3/kde.py +0 -0
  29. {plot3-0.8.0 → plot3-0.9.0}/plot3/latexin.py +0 -0
  30. {plot3-0.8.0 → plot3-0.9.0}/plot3/mathtext.py +0 -0
  31. {plot3-0.8.0 → plot3-0.9.0}/plot3/payload.py +0 -0
  32. {plot3-0.8.0 → plot3-0.9.0}/plot3/remote.py +0 -0
  33. {plot3-0.8.0 → plot3-0.9.0}/plot3/scales.py +0 -0
  34. {plot3-0.8.0 → plot3-0.9.0}/plot3/scaling.py +0 -0
  35. {plot3-0.8.0 → plot3-0.9.0}/plot3/special.py +0 -0
  36. {plot3-0.8.0 → plot3-0.9.0}/plot3/stat2d.py +0 -0
  37. {plot3-0.8.0 → plot3-0.9.0}/plot3/static.py +0 -0
  38. {plot3-0.8.0 → plot3-0.9.0}/plot3/stats3d.py +0 -0
  39. {plot3-0.8.0 → plot3-0.9.0}/plot3/table.py +0 -0
  40. {plot3-0.8.0 → plot3-0.9.0}/plot3/themes.py +0 -0
  41. {plot3-0.8.0 → plot3-0.9.0}/plot3/viewer.py +0 -0
  42. {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/dependency_links.txt +0 -0
  43. {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/top_level.txt +0 -0
  44. {plot3-0.8.0 → plot3-0.9.0}/setup.cfg +0 -0
  45. {plot3-0.8.0 → plot3-0.9.0}/tests/test_2d_density.py +0 -0
  46. {plot3-0.8.0 → plot3-0.9.0}/tests/test_3d.py +0 -0
  47. {plot3-0.8.0 → plot3-0.9.0}/tests/test_animation.py +0 -0
  48. {plot3-0.8.0 → plot3-0.9.0}/tests/test_annotate.py +0 -0
  49. {plot3-0.8.0 → plot3-0.9.0}/tests/test_api_public.py +0 -0
  50. {plot3-0.8.0 → plot3-0.9.0}/tests/test_array_backend.py +0 -0
  51. {plot3-0.8.0 → plot3-0.9.0}/tests/test_arrow.py +0 -0
  52. {plot3-0.8.0 → plot3-0.9.0}/tests/test_build_contract.py +0 -0
  53. {plot3-0.8.0 → plot3-0.9.0}/tests/test_contour.py +0 -0
  54. {plot3-0.8.0 → plot3-0.9.0}/tests/test_everyday_ggplot2.py +0 -0
  55. {plot3-0.8.0 → plot3-0.9.0}/tests/test_expr.py +0 -0
  56. {plot3-0.8.0 → plot3-0.9.0}/tests/test_facets_compose.py +0 -0
  57. {plot3-0.8.0 → plot3-0.9.0}/tests/test_fast_paths.py +0 -0
  58. {plot3-0.8.0 → plot3-0.9.0}/tests/test_geom_function.py +0 -0
  59. {plot3-0.8.0 → plot3-0.9.0}/tests/test_geoms_ds.py +0 -0
  60. {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot2_extras2.py +0 -0
  61. {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot2_parity.py +0 -0
  62. {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot_extras.py +0 -0
  63. {plot3-0.8.0 → plot3-0.9.0}/tests/test_grammar.py +0 -0
  64. {plot3-0.8.0 → plot3-0.9.0}/tests/test_latex_input.py +0 -0
  65. {plot3-0.8.0 → plot3-0.9.0}/tests/test_lidar_scene.py +0 -0
  66. {plot3-0.8.0 → plot3-0.9.0}/tests/test_math_layers.py +0 -0
  67. {plot3-0.8.0 → plot3-0.9.0}/tests/test_mathtext.py +0 -0
  68. {plot3-0.8.0 → plot3-0.9.0}/tests/test_payload.py +0 -0
  69. {plot3-0.8.0 → plot3-0.9.0}/tests/test_payload_display.py +0 -0
  70. {plot3-0.8.0 → plot3-0.9.0}/tests/test_point_cloud.py +0 -0
  71. {plot3-0.8.0 → plot3-0.9.0}/tests/test_r_oracle_parity.py +0 -0
  72. {plot3-0.8.0 → plot3-0.9.0}/tests/test_readme.py +0 -0
  73. {plot3-0.8.0 → plot3-0.9.0}/tests/test_remote_bridge.py +0 -0
  74. {plot3-0.8.0 → plot3-0.9.0}/tests/test_review_fixes.py +0 -0
  75. {plot3-0.8.0 → plot3-0.9.0}/tests/test_save_menu.py +0 -0
  76. {plot3-0.8.0 → plot3-0.9.0}/tests/test_scales_api.py +0 -0
  77. {plot3-0.8.0 → plot3-0.9.0}/tests/test_scales_themes.py +0 -0
  78. {plot3-0.8.0 → plot3-0.9.0}/tests/test_special.py +0 -0
  79. {plot3-0.8.0 → plot3-0.9.0}/tests/test_stat2d.py +0 -0
  80. {plot3-0.8.0 → plot3-0.9.0}/tests/test_static_save.py +0 -0
  81. {plot3-0.8.0 → plot3-0.9.0}/tests/test_stats_semantic.py +0 -0
  82. {plot3-0.8.0 → plot3-0.9.0}/tests/test_step3_geoms.py +0 -0
  83. {plot3-0.8.0 → plot3-0.9.0}/tests/test_table_backends.py +0 -0
  84. {plot3-0.8.0 → plot3-0.9.0}/tests/test_titles_theme.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
5
5
  Author: Rigoberto Leyva Salmeron
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: numpy>=1.24
19
19
  Requires-Dist: pandas>=2.0
20
- Requires-Dist: tidysyntax<0.2,>=0.1
20
+ Requires-Dist: tidy3syntax<0.2,>=0.1.2
21
21
  Provides-Extra: pdf
22
22
  Requires-Dist: cairosvg>=2.7; extra == "pdf"
23
23
  Provides-Extra: dev
@@ -0,0 +1 @@
1
+ __version__ = "0.9.0"
@@ -4,10 +4,12 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
  import shlex
7
+ import dataclasses
7
8
  from typing import Any
8
9
 
9
10
  import numpy as np
10
11
  import pandas as pd
12
+ import tidy3syntax.ipython
11
13
 
12
14
  try:
13
15
  from IPython import get_ipython
@@ -17,13 +19,11 @@ except Exception: # pragma: no cover
17
19
  from plot3.io import ssh_bytes
18
20
  from plot3.masking import (
19
21
  BT_NAME,
22
+ DIALECT,
20
23
  Plot3MaskTransformer,
24
+ _plot3_names,
21
25
  is_backtick_transformer,
22
26
  is_mask_transformer,
23
- is_tidy3_backtick_transformer,
24
- plot3_backtick_transform,
25
- plot3_formula_transform,
26
- plot3_layer_transform,
27
27
  )
28
28
 
29
29
  # R-style bare-name / backtick masking for aes() / facet_wrap().
@@ -60,34 +60,39 @@ def enable_r_style(ipython: Any | None = None) -> bool:
60
60
  if ns is not None:
61
61
  # Escape hatch if AST missed a sentinel (still a valid column string).
62
62
  ns[BT_NAME] = _bt_fallback
63
- # When tidy3 is not loaded, leave tidy3 sentinel unset; plot3 AST
64
- # still rewrites __tidy3_bt__ if tidy3's preparser ran first.
65
63
 
66
- # Source preparser: when tidy3 already rewrites backticks, run only
67
- # plot3's own formula rewrites (^ in geom_function, ~ in facets), after it.
64
+ # The source rewrites (backticks, facet formulas, ^ in geom_function,
65
+ # + layer lines) go through tidy3syntax's coordinator, one transformer
66
+ # shared with tidy3, tidy3a and stats3; the AST pass masks aes() names.
67
+ _remove_own(ipython)
68
+ tidy3syntax.ipython.register(ipython, _dialect(), ast_transformer=Plot3MaskTransformer())
69
+ return True
70
+
71
+
72
+ def _dialect():
73
+ """plot3's dialect, with every public plot3 name as a layer a "+" line may add."""
74
+ return dataclasses.replace(DIALECT, layer_calls=frozenset(_plot3_names()))
75
+
76
+
77
+ def _layers_only():
78
+ """Only the "+" layer lines, for R-style masking turned off."""
79
+ return dataclasses.replace(
80
+ _dialect(), formula_calls=(), formula_keywords=(), power_calls=(), unary_formula_calls=()
81
+ )
82
+
83
+
84
+ def _remove_own(ipython: Any) -> None:
85
+ """An earlier plot3's own transformers, from before the coordinator."""
68
86
  for attr in ("input_transformers_cleanup", "input_transformers_post"):
69
87
  transformers = getattr(ipython, attr, None)
70
- if not isinstance(transformers, list):
71
- continue
72
- transformers[:] = [
73
- t for t in transformers if not is_backtick_transformer(t)
74
- ]
75
- tidy3_at = [
76
- i for i, t in enumerate(transformers) if is_tidy3_backtick_transformer(t)
77
- ]
78
- if tidy3_at:
79
- transformers.insert(tidy3_at[-1] + 1, plot3_formula_transform)
80
- else:
81
- transformers.insert(0, plot3_backtick_transform)
82
-
83
- # AST pass for bare names inside aes / facet_wrap.
88
+ if isinstance(transformers, list):
89
+ transformers[:] = [
90
+ t for t in transformers
91
+ if not is_backtick_transformer(t) and getattr(t, "__name__", "") != "plot3_layer_transform"
92
+ ]
84
93
  ast_transformers = getattr(ipython, "ast_transformers", None)
85
94
  if isinstance(ast_transformers, list):
86
- ast_transformers[:] = [
87
- t for t in ast_transformers if not is_mask_transformer(t)
88
- ]
89
- ast_transformers.append(Plot3MaskTransformer())
90
- return True
95
+ ast_transformers[:] = [t for t in ast_transformers if not is_mask_transformer(t)]
91
96
 
92
97
 
93
98
  def disable_r_style(ipython: Any | None = None) -> None:
@@ -101,17 +106,9 @@ def disable_r_style(ipython: Any | None = None) -> None:
101
106
  ipython = None
102
107
  if ipython is None:
103
108
  return
104
- for attr in ("input_transformers_cleanup", "input_transformers_post"):
105
- transformers = getattr(ipython, attr, None)
106
- if isinstance(transformers, list):
107
- transformers[:] = [
108
- t for t in transformers if not is_backtick_transformer(t)
109
- ]
110
- ast_transformers = getattr(ipython, "ast_transformers", None)
111
- if isinstance(ast_transformers, list):
112
- ast_transformers[:] = [
113
- t for t in ast_transformers if not is_mask_transformer(t)
114
- ]
109
+ _remove_own(ipython)
110
+ # The "+" layer lines stay, as before: only the R-style masking goes.
111
+ tidy3syntax.ipython.register(ipython, _layers_only())
115
112
 
116
113
 
117
114
  def r_style_enabled() -> bool:
@@ -482,20 +479,20 @@ def enable_layer_lines(ipython: Any | None = None) -> bool:
482
479
  ipython = get_ipython() if get_ipython is not None else None
483
480
  except Exception:
484
481
  ipython = None
485
- transformers = getattr(ipython, "input_transformers_cleanup", None)
486
- if not isinstance(transformers, list):
482
+ if ipython is None or not isinstance(getattr(ipython, "input_transformers_cleanup", None), list):
487
483
  return False
488
- transformers[:] = [
489
- t for t in transformers if getattr(t, "__name__", "") != "plot3_layer_transform"
490
- ]
491
- transformers.append(plot3_layer_transform)
484
+ # The layer lines are part of plot3's dialect in tidy3syntax's coordinator.
485
+ if "plot3" not in tidy3syntax.ipython.registered(ipython):
486
+ tidy3syntax.ipython.register(ipython, _layers_only())
492
487
  return True
493
488
 
494
489
 
495
- def register_plot3(*, quiet=True, r_style: bool = True) -> bool:
496
- if get_ipython is None:
497
- return False
498
- ip = get_ipython()
490
+ def register_plot3(*, quiet=True, r_style: bool = True, ipython: Any | None = None) -> bool:
491
+ ip = ipython
492
+ if ip is None:
493
+ if get_ipython is None:
494
+ return False
495
+ ip = get_ipython()
499
496
  if ip is None:
500
497
  return False
501
498
  ok = False
@@ -546,4 +543,15 @@ def register_plot3(*, quiet=True, r_style: bool = True) -> bool:
546
543
 
547
544
 
548
545
  def load_ipython_extension(ip=None):
549
- register_plot3(quiet=True)
546
+ register_plot3(quiet=True, ipython=ip)
547
+
548
+
549
+ def dialect():
550
+ """plot3's dialect for tidy3syntax (its ``tidy3syntax.dialects`` entry point): the
551
+ notebook loader, and plot3's bare-name masking for exports."""
552
+ from plot3.masking import apply_masking, default_known_names
553
+
554
+ def mask(source: str, known: frozenset[str]) -> str:
555
+ return apply_masking(source, known=set(known) | default_known_names(), backticks=False)
556
+
557
+ return dataclasses.replace(_dialect(), extension="plot3.jupyter:load_ipython_extension", mask=mask)
@@ -36,14 +36,15 @@ import re
36
36
  import tokenize
37
37
  from typing import Any, Iterable
38
38
 
39
- import tidysyntax
40
- from tidysyntax import repairs
41
- from tidysyntax.scanner import Spans, apply
39
+ import tidy3syntax
40
+ from tidy3syntax import repairs
41
+ from tidy3syntax.scanner import Spans, apply
42
42
 
43
43
  # Sentinels (plot3 own + tidy3 compatibility).
44
44
  BT_NAME = "__plot3_bt__"
45
45
  TIDY3_BT_NAME = "__tidy3_bt__"
46
- _BT_SENTINELS = frozenset({BT_NAME, TIDY3_BT_NAME})
46
+ # tidy3syntax's neutral name too: another package's pass (tidy3a's) may rewrite a backtick in aes() first.
47
+ _BT_SENTINELS = frozenset({BT_NAME, TIDY3_BT_NAME, tidy3syntax.NAME})
47
48
 
48
49
  # Calls whose arguments / keywords are column *selectors* (→ strings).
49
50
  _SELECTOR_FUNCS = frozenset(
@@ -116,11 +117,11 @@ _FORMULA_CALLS = frozenset({"geom_function", "geom_vector_field"})
116
117
  _FACETS = frozenset({"facet_wrap", "facet_grid"})
117
118
 
118
119
  # plot3's part of the notebook syntax tidy3, tidy3a, plot3 and stats3 share
119
- # (tidysyntax). ``drv ~ cyl`` and ``~`my col``` are not Python, so they are
120
+ # (tidy3syntax). ``drv ~ cyl`` and ``~`my col``` are not Python, so they are
120
121
  # quoted in any facet call; ``^`` in geom_function (XOR in Python) and ``~cyl``
121
122
  # in a facet (bitwise not) are valid Python, so they are rewritten only where
122
123
  # the call is plot3's: a notebook's own geom_function keeps XOR.
123
- DIALECT = tidysyntax.Dialect(
124
+ DIALECT = tidy3syntax.Dialect(
124
125
  "plot3",
125
126
  formula_calls=_FACETS,
126
127
  formula_keywords={"facets"},
@@ -147,13 +148,13 @@ def _session_namespace(namespace: Any) -> dict[str, Any] | None:
147
148
 
148
149
 
149
150
  def _plot3_bt(source: str) -> str:
150
- """tidysyntax's ``__r_name__`` placeholder as plot3's ``__plot3_bt__``, outside strings and comments."""
151
- if tidysyntax.NAME not in source:
151
+ """tidy3syntax's ``__r_name__`` placeholder as plot3's ``__plot3_bt__``, outside strings and comments."""
152
+ if tidy3syntax.NAME not in source:
152
153
  return source
153
154
  spans = Spans.of(source)
154
155
  edits = [
155
156
  (m.start(), m.end(), BT_NAME)
156
- for m in re.finditer(re.escape(tidysyntax.NAME) + r"(?=\s*\()", source)
157
+ for m in re.finditer(re.escape(tidy3syntax.NAME) + r"(?=\s*\()", source)
157
158
  if m.start() in spans
158
159
  ]
159
160
  return apply(source, edits)
@@ -178,7 +179,7 @@ def rewrite_facet_formulas(source: str, *, unary: str = "owned", namespace: Any
178
179
  source = repairs.quote_formulas(source, [DIALECT])
179
180
  if unary == "never":
180
181
  return source
181
- return tidysyntax.reinterpret(source, [_FACETS_ONLY], _session_namespace(namespace))
182
+ return tidy3syntax.reinterpret(source, [_FACETS_ONLY], _session_namespace(namespace))
182
183
 
183
184
 
184
185
  def rewrite_formulas(source: str, namespace: Any = _SESSION) -> str:
@@ -188,7 +189,7 @@ def rewrite_formulas(source: str, namespace: Any = _SESSION) -> str:
188
189
  ``drv ~ cyl`` is quoted first, since the cell must parse before the
189
190
  ownership of ``geom_function`` and ``facet_wrap`` can be read."""
190
191
  source = repairs.quote_formulas(source, [DIALECT])
191
- return tidysyntax.reinterpret(source, [DIALECT], _session_namespace(namespace))
192
+ return tidy3syntax.reinterpret(source, [DIALECT], _session_namespace(namespace))
192
193
 
193
194
 
194
195
  def rewrite_formula_carets(source: str, namespace: Any = _SESSION) -> str:
@@ -199,7 +200,7 @@ def rewrite_formula_carets(source: str, namespace: Any = _SESSION) -> str:
199
200
  ``geom_function`` is rewritten: in a notebook's own function of that
200
201
  name, ``^`` stays XOR.
201
202
  """
202
- return tidysyntax.reinterpret(source, [_POWER_ONLY], _session_namespace(namespace))
203
+ return tidy3syntax.reinterpret(source, [_POWER_ONLY], _session_namespace(namespace))
203
204
 
204
205
 
205
206
  def rewrite_backticks(source: str, namespace: Any = _SESSION) -> str:
@@ -211,7 +212,7 @@ def rewrite_backticks(source: str, namespace: Any = _SESSION) -> str:
211
212
  not Python goes first, so the cell parses when the ownership of
212
213
  ``geom_function`` and ``facet_wrap`` is read.
213
214
  """
214
- return tidysyntax.reinterpret(_repair(source), [DIALECT], _session_namespace(namespace))
215
+ return tidy3syntax.reinterpret(_repair(source), [DIALECT], _session_namespace(namespace))
215
216
 
216
217
 
217
218
  def _parse_dedented(source: str) -> tuple[ast.Module, list[int]] | None:
@@ -339,7 +340,7 @@ def join_layer_lines(source: str, namespace: Any = _SESSION) -> str | None:
339
340
  if not logical:
340
341
  return None
341
342
  parsed = _parse_dedented(_repair(source))
342
- owners = tidysyntax.Owners(parsed[0], _session_namespace(namespace), [DIALECT]) if parsed is not None else None
343
+ owners = tidy3syntax.Owners(parsed[0], _session_namespace(namespace), [DIALECT]) if parsed is not None else None
343
344
 
344
345
  def plot3_name(name: str, row: int) -> bool:
345
346
  return owners is not None and DIALECT.owns_module(owners.module(name, (row, 0)))
@@ -359,7 +360,7 @@ def join_layer_lines(source: str, namespace: Any = _SESSION) -> str | None:
359
360
  if bound:
360
361
  return bound[-1] is None or DIALECT.owns_module(bound[-1])
361
362
  if owners.context is not None and name in owners.context:
362
- return DIALECT.owns_module(tidysyntax.module_of(owners.context[name]))
363
+ return DIALECT.owns_module(tidy3syntax.module_of(owners.context[name]))
363
364
  return True
364
365
 
365
366
  def may_join(tokens: list[tokenize.TokenInfo], head: list[tokenize.TokenInfo]) -> bool:
@@ -493,6 +494,22 @@ class Plot3MaskTransformer(ast.NodeTransformer):
493
494
 
494
495
  def __init__(self, known: set[str] | None = None):
495
496
  self._known_static = known
497
+ self._owners: tidy3syntax.Owners | None = None
498
+
499
+ def visit_Module(self, node: ast.Module) -> ast.AST:
500
+ # Whose aes() or facet_wrap() each call is: a notebook's own function of that name keeps
501
+ # its arguments as written (tidy3syntax's rule, as for the source rewrites).
502
+ namespace = None if self._known_static is not None else _session_namespace(_SESSION)
503
+ self._owners = tidy3syntax.Owners(node, namespace, [DIALECT])
504
+ try:
505
+ return self.generic_visit(node)
506
+ finally:
507
+ self._owners = None
508
+
509
+ def _plot3s(self, func: ast.AST) -> bool:
510
+ if self._owners is None:
511
+ return True
512
+ return self._owners.owner(func, (func.lineno, func.col_offset)) == "plot3"
496
513
 
497
514
  def _known(self) -> set[str]:
498
515
  if self._known_static is not None:
@@ -541,6 +558,8 @@ class Plot3MaskTransformer(ast.NodeTransformer):
541
558
  return self.generic_visit(node)
542
559
 
543
560
  name = node.func.id
561
+ if (name in _FORMULA_CALLS or name in _SELECTOR_FUNCS) and not self._plot3s(node.func):
562
+ return self.generic_visit(node)
544
563
  if name in _FORMULA_CALLS:
545
564
  return self._visit_geom_function(node)
546
565
  if name not in _SELECTOR_FUNCS:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.8.0
3
+ Version: 0.9.0
4
4
  Summary: ggplot2 grammar of graphics for Python: interactive WebGL figures and journal-ready PNG, SVG, and PDF
5
5
  Author: Rigoberto Leyva Salmeron
6
6
  License-Expression: MIT
@@ -17,7 +17,7 @@ Description-Content-Type: text/markdown
17
17
  License-File: LICENSE
18
18
  Requires-Dist: numpy>=1.24
19
19
  Requires-Dist: pandas>=2.0
20
- Requires-Dist: tidysyntax<0.2,>=0.1
20
+ Requires-Dist: tidy3syntax<0.2,>=0.1.2
21
21
  Provides-Extra: pdf
22
22
  Requires-Dist: cairosvg>=2.7; extra == "pdf"
23
23
  Provides-Extra: dev
@@ -35,6 +35,7 @@ plot3/viewer.py
35
35
  plot3.egg-info/PKG-INFO
36
36
  plot3.egg-info/SOURCES.txt
37
37
  plot3.egg-info/dependency_links.txt
38
+ plot3.egg-info/entry_points.txt
38
39
  plot3.egg-info/requires.txt
39
40
  plot3.egg-info/top_level.txt
40
41
  tests/test_2d_density.py
@@ -0,0 +1,2 @@
1
+ [tidy3syntax.dialects]
2
+ plot3 = plot3.jupyter:dialect
@@ -1,6 +1,6 @@
1
1
  numpy>=1.24
2
2
  pandas>=2.0
3
- tidysyntax<0.2,>=0.1
3
+ tidy3syntax<0.2,>=0.1.2
4
4
 
5
5
  [dev]
6
6
  pytest>=7.0
@@ -24,13 +24,18 @@ dependencies = [
24
24
  "pandas>=2.0",
25
25
  # The notebook syntax tidy3, tidy3a, plot3 and stats3 share (facet
26
26
  # formulas, ^ in geom_function, backtick names). Standard library only.
27
- "tidysyntax>=0.1,<0.2",
27
+ "tidy3syntax>=0.1.2,<0.2",
28
28
  ]
29
29
 
30
30
  [project.optional-dependencies]
31
31
  pdf = ["cairosvg>=2.7"]
32
32
  dev = ["pytest>=7.0", "polars>=1.0", "contourpy>=1.0"]
33
33
 
34
+ # A notebook cell that imports plot3 gets its syntax in that same cell
35
+ # (its dialect names the notebook loader tidy3syntax's coordinator calls).
36
+ [project.entry-points."tidy3syntax.dialects"]
37
+ plot3 = "plot3.jupyter:dialect"
38
+
34
39
  [project.urls]
35
40
  Homepage = "https://github.com/rleyvasal/plot3"
36
41
  Changelog = "https://github.com/rleyvasal/plot3/blob/main/CHANGELOG.md"
@@ -309,11 +309,10 @@ _fake_tidy3_transform.__module__ = "tidy3.masking"
309
309
  _fake_tidy3_transform.__name__ = "tidy3_backtick_transform"
310
310
 
311
311
 
312
- def test_formula_rewrites_run_after_tidy3_preparser():
313
- # tidy3 imported first: plot3 still rewrites ^ in geom_function and ~ in
314
- # facets, after tidy3's backticks, and installs no second backtick pass.
312
+ def test_one_coordinator_ahead_of_other_transformers():
313
+ # plot3's source rewrites run in tidy3syntax's coordinator, one transformer
314
+ # for every package, put first; other packages' transformers stay.
315
315
  from plot3.jupyter import enable_r_style
316
- from plot3.masking import plot3_formula_transform
317
316
 
318
317
  def other(lines):
319
318
  return lines
@@ -321,15 +320,21 @@ def test_formula_rewrites_run_after_tidy3_preparser():
321
320
  ip = _shell(_fake_tidy3_transform, other)
322
321
  enable_r_style(ip)
323
322
  enable_r_style(ip) # idempotent
324
- assert ip.input_transformers_cleanup == [
325
- _fake_tidy3_transform, plot3_formula_transform, other,
326
- ]
327
- out = "".join(plot3_formula_transform(["p = geom_function(y = x^3) + facet_wrap(~cyl)\n"]))
323
+ coordinator, *rest = ip.input_transformers_cleanup
324
+ assert getattr(coordinator, "tidy3syntax", False)
325
+ assert rest == [_fake_tidy3_transform, other]
326
+ out = "".join(coordinator(["p = geom_function(y = x^3) + facet_wrap(~cyl)\n"]))
328
327
  assert out == "p = geom_function(y = x**3) + facet_wrap('~cyl')\n"
328
+ assert out == "".join(coordinator([out])) # a second pass changes nothing
329
329
 
330
- alone = _shell(other)
331
- enable_r_style(alone)
332
- assert alone.input_transformers_cleanup == [plot3_backtick_transform, other]
330
+
331
+ def test_a_notebooks_own_aes_keeps_its_arguments():
332
+ from plot3.masking import Plot3MaskTransformer
333
+
334
+ tree = ast.parse("def aes(**kw):\n return kw\nr = aes(x=wt)")
335
+ assert "x=wt" in ast.unparse(Plot3MaskTransformer(known=default_known_names()).visit(tree))
336
+ tree = ast.parse("r = aes(x=wt)")
337
+ assert "x='wt'" in ast.unparse(Plot3MaskTransformer(known=default_known_names()).visit(tree))
333
338
 
334
339
 
335
340
  # ── Valid Python keeps its meaning outside plot3's calls ─────────────────────
@@ -435,3 +440,8 @@ def test_plot3_calls_still_take_r_syntax_in_a_notebook():
435
440
  from IPython.core.interactiveshell import InteractiveShell
436
441
 
437
442
  InteractiveShell.clear_instance()
443
+
444
+
445
+ def test_the_shared_backtick_placeholder_is_a_column_name():
446
+ # tidy3a's notebook pass may rewrite `First Name` in aes() before plot3's runs.
447
+ assert _norm("aes(x=__r_name__('First Name'), y=mpg)") == "aes(x='First Name', y='mpg')"
@@ -1 +0,0 @@
1
- __version__ = "0.8.0"
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes