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.
- {plot3-0.8.0 → plot3-0.9.0}/PKG-INFO +2 -2
- plot3-0.9.0/plot3/__version__.py +1 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/jupyter.py +57 -49
- {plot3-0.8.0 → plot3-0.9.0}/plot3/masking.py +34 -15
- {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/PKG-INFO +2 -2
- {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/SOURCES.txt +1 -0
- plot3-0.9.0/plot3.egg-info/entry_points.txt +2 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/requires.txt +1 -1
- {plot3-0.8.0 → plot3-0.9.0}/pyproject.toml +6 -1
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_masking.py +21 -11
- plot3-0.8.0/plot3/__version__.py +0 -1
- {plot3-0.8.0 → plot3-0.9.0}/LICENSE +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/README.md +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/__init__.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/aesexpr.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/build.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/calculus.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/compose.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/contour.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/craft.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/encode.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/expr.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/flip.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/function.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/geoms.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/ggplot.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/io.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/kde.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/latexin.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/mathtext.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/payload.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/remote.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/scales.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/scaling.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/special.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/stat2d.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/static.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/stats3d.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/table.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/themes.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3/viewer.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/dependency_links.txt +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/plot3.egg-info/top_level.txt +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/setup.cfg +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_2d_density.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_3d.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_animation.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_annotate.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_api_public.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_array_backend.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_arrow.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_build_contract.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_contour.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_everyday_ggplot2.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_expr.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_facets_compose.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_fast_paths.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_geom_function.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_geoms_ds.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot2_extras2.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot2_parity.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_ggplot_extras.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_grammar.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_latex_input.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_lidar_scene.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_math_layers.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_mathtext.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_payload.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_payload_display.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_point_cloud.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_r_oracle_parity.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_readme.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_remote_bridge.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_review_fixes.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_save_menu.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_scales_api.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_scales_themes.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_special.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_stat2d.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_static_save.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_stats_semantic.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_step3_geoms.py +0 -0
- {plot3-0.8.0 → plot3-0.9.0}/tests/test_table_backends.py +0 -0
- {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.
|
|
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:
|
|
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
|
-
#
|
|
67
|
-
#
|
|
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
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
-
|
|
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
|
-
|
|
489
|
-
|
|
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
|
-
|
|
497
|
-
|
|
498
|
-
|
|
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
|
|
40
|
-
from
|
|
41
|
-
from
|
|
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
|
-
|
|
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
|
-
# (
|
|
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 =
|
|
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
|
-
"""
|
|
151
|
-
if
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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.
|
|
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:
|
|
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
|
|
@@ -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
|
-
"
|
|
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
|
|
313
|
-
#
|
|
314
|
-
#
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
]
|
|
327
|
-
out = "".join(
|
|
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
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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')"
|
plot3-0.8.0/plot3/__version__.py
DELETED
|
@@ -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
|
|
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
|