plot3 0.5.1__tar.gz → 0.6.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.5.1 → plot3-0.6.0}/PKG-INFO +1 -1
- plot3-0.6.0/plot3/__version__.py +1 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/build.py +14 -3
- {plot3-0.5.1 → plot3-0.6.0}/plot3/geoms.py +18 -7
- {plot3-0.5.1 → plot3-0.6.0}/plot3/masking.py +26 -2
- {plot3-0.5.1 → plot3-0.6.0}/plot3/stat2d.py +1 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/static.py +1 -1
- {plot3-0.5.1 → plot3-0.6.0}/plot3.egg-info/PKG-INFO +1 -1
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_facets_compose.py +46 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_grammar.py +14 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_masking.py +14 -0
- plot3-0.5.1/plot3/__version__.py +0 -1
- {plot3-0.5.1 → plot3-0.6.0}/LICENSE +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/README.md +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/__init__.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/aesexpr.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/calculus.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/compose.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/contour.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/craft.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/encode.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/expr.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/flip.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/function.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/ggplot.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/io.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/jupyter.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/latexin.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/mathtext.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/payload.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/remote.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/scales.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/scaling.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/special.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/stats3d.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/table.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/themes.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3/viewer.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3.egg-info/SOURCES.txt +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3.egg-info/dependency_links.txt +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3.egg-info/requires.txt +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/plot3.egg-info/top_level.txt +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/pyproject.toml +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/setup.cfg +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_2d_density.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_3d.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_animation.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_annotate.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_api_public.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_array_backend.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_arrow.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_build_contract.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_contour.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_everyday_ggplot2.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_expr.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_geom_function.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_geoms_ds.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_ggplot2_extras2.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_ggplot2_parity.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_ggplot_extras.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_latex_input.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_lidar_scene.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_math_layers.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_mathtext.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_payload.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_payload_display.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_point_cloud.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_r_oracle_parity.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_readme.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_remote_bridge.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_review_fixes.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_save_menu.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_scales_api.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_scales_themes.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_special.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_stat2d.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_static_save.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_stats_semantic.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_step3_geoms.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_table_backends.py +0 -0
- {plot3-0.5.1 → plot3-0.6.0}/tests/test_titles_theme.py +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.0"
|
|
@@ -3731,7 +3731,13 @@ def facet_cells(g: ggplot) -> dict:
|
|
|
3731
3731
|
facet = g.facet
|
|
3732
3732
|
if g.data is None:
|
|
3733
3733
|
raise ValueError("ggplot has no data")
|
|
3734
|
-
|
|
3734
|
+
# ggplot2: "fixed" shares every axis, "free" none, "free_x" / "free_y"
|
|
3735
|
+
# all but that one.
|
|
3736
|
+
freed = {"fixed": set(), "free_x": {"x"}, "free_y": {"y"}}.get(facet.scales)
|
|
3737
|
+
force = (
|
|
3738
|
+
{ax: dom for ax, dom in _global_numeric_domains(g).items() if ax not in freed}
|
|
3739
|
+
if freed is not None else {}
|
|
3740
|
+
)
|
|
3735
3741
|
colour_levels = _facet_colour_levels(g)
|
|
3736
3742
|
|
|
3737
3743
|
def child(panel):
|
|
@@ -3745,6 +3751,9 @@ def facet_cells(g: ggplot) -> dict:
|
|
|
3745
3751
|
header = {
|
|
3746
3752
|
key: g.labs.get(key) for key in ("title", "subtitle", "caption", "tag") if g.labs.get(key)
|
|
3747
3753
|
}
|
|
3754
|
+
# Panels hide their own legends; the figure draws one unless
|
|
3755
|
+
# theme(legend_position="none") asks for none.
|
|
3756
|
+
legend = _legend_position_spec(getattr(g, "legend_position", None))
|
|
3748
3757
|
cells: list[dict] = []
|
|
3749
3758
|
if not isinstance(facet, _facet_grid):
|
|
3750
3759
|
column = facet.variable
|
|
@@ -3764,7 +3773,8 @@ def facet_cells(g: ggplot) -> dict:
|
|
|
3764
3773
|
row, col = divmod(index, ncol)
|
|
3765
3774
|
cells.append({"row": row, "col": col, "fig": panel, "strip": label})
|
|
3766
3775
|
return {"ncol": ncol, "nrow": nrow, "cells": cells, "col_strips": None,
|
|
3767
|
-
"row_strips": None, "header": header, "kind": "wrap"
|
|
3776
|
+
"row_strips": None, "header": header, "kind": "wrap",
|
|
3777
|
+
"legend": legend}
|
|
3768
3778
|
|
|
3769
3779
|
for column in (facet.rows, facet.cols):
|
|
3770
3780
|
if column is not None and not has_column(g.data, column):
|
|
@@ -3793,6 +3803,7 @@ def facet_cells(g: ggplot) -> dict:
|
|
|
3793
3803
|
"row_strips": [_strip_text(facet, facet.rows, v) for v in row_levels] if facet.rows else None,
|
|
3794
3804
|
"header": header,
|
|
3795
3805
|
"kind": "grid",
|
|
3806
|
+
"legend": legend,
|
|
3796
3807
|
}
|
|
3797
3808
|
|
|
3798
3809
|
|
|
@@ -3874,7 +3885,7 @@ def _build_doc_faceted(g: ggplot, facet) -> str:
|
|
|
3874
3885
|
first_spec, _pairs = build_spec(first)
|
|
3875
3886
|
shared_x = str((first_spec.get("labs") or {}).get("x") or "")
|
|
3876
3887
|
shared_y = str((first_spec.get("labs") or {}).get("y") or "")
|
|
3877
|
-
legend_html = _facet_legend_html(first_spec, theme)
|
|
3888
|
+
legend_html = "" if layout.get("legend") == "none" else _facet_legend_html(first_spec, theme)
|
|
3878
3889
|
cells: list[str] = []
|
|
3879
3890
|
total_kb = 0
|
|
3880
3891
|
count = 0
|
|
@@ -1184,13 +1184,14 @@ class geom_smooth(_Geom):
|
|
|
1184
1184
|
``method="loess"`` (default, local quadratic with ``span``) or ``"lm"``
|
|
1185
1185
|
(a straight line). ``se=True`` shades the ``level`` confidence band,
|
|
1186
1186
|
using Student's t like R. ``aes(colour=)`` fits each group.
|
|
1187
|
+
``linetype`` dashes the fitted line (``"dashed"``, ``"dotted"``, …).
|
|
1187
1188
|
"""
|
|
1188
1189
|
|
|
1189
1190
|
kind = "smooth"
|
|
1190
1191
|
|
|
1191
1192
|
def __init__(
|
|
1192
1193
|
self, mapping=None, *, method="loess", se=True, level=0.95,
|
|
1193
|
-
span=0.75, n=80, linewidth=2.0, **kw,
|
|
1194
|
+
span=0.75, n=80, linewidth=2.0, linetype=None, **kw,
|
|
1194
1195
|
):
|
|
1195
1196
|
super().__init__(mapping, **kw)
|
|
1196
1197
|
self.method = method
|
|
@@ -1199,6 +1200,8 @@ class geom_smooth(_Geom):
|
|
|
1199
1200
|
self.span = float(span)
|
|
1200
1201
|
self.n = int(n)
|
|
1201
1202
|
self.linewidth = float(linewidth)
|
|
1203
|
+
dash_pattern(linetype)
|
|
1204
|
+
self.linetype = linetype
|
|
1202
1205
|
|
|
1203
1206
|
|
|
1204
1207
|
class stat_summary(_Geom):
|
|
@@ -1751,6 +1754,14 @@ class geom_violin(_Geom):
|
|
|
1751
1754
|
self.linewidth = float(linewidth)
|
|
1752
1755
|
|
|
1753
1756
|
|
|
1757
|
+
_FACET_SCALES = ("fixed", "free", "free_x", "free_y")
|
|
1758
|
+
|
|
1759
|
+
|
|
1760
|
+
def _check_facet_scales(scales) -> None:
|
|
1761
|
+
if scales not in _FACET_SCALES:
|
|
1762
|
+
raise ValueError("scales must be 'fixed', 'free', 'free_x', or 'free_y'")
|
|
1763
|
+
|
|
1764
|
+
|
|
1754
1765
|
class facet_wrap:
|
|
1755
1766
|
"""Wrap panels by a discrete column (ggplot2 ``facet_wrap``).
|
|
1756
1767
|
|
|
@@ -1762,7 +1773,8 @@ class facet_wrap:
|
|
|
1762
1773
|
Panel grid size. If both are omitted, the grid is ggplot2's: 3 panels
|
|
1763
1774
|
in a row, 4 in 2 x 2, 5 or 6 in 2 rows of 3, 7 to 9 in 3 x 3.
|
|
1764
1775
|
scales:
|
|
1765
|
-
``"fixed"`` (shared domains across panels)
|
|
1776
|
+
``"fixed"`` (shared domains across panels), ``"free"`` (per-panel),
|
|
1777
|
+
or ``"free_x"`` / ``"free_y"`` (only that axis per-panel).
|
|
1766
1778
|
"""
|
|
1767
1779
|
|
|
1768
1780
|
def __init__(
|
|
@@ -1792,8 +1804,7 @@ class facet_wrap:
|
|
|
1792
1804
|
f"(got {facets!r})"
|
|
1793
1805
|
)
|
|
1794
1806
|
name = candidates[0]
|
|
1795
|
-
|
|
1796
|
-
raise ValueError("scales must be 'fixed' or 'free'")
|
|
1807
|
+
_check_facet_scales(scales)
|
|
1797
1808
|
if ncol is not None and ncol < 1:
|
|
1798
1809
|
raise ValueError("ncol must be positive")
|
|
1799
1810
|
if nrow is not None and nrow < 1:
|
|
@@ -1812,7 +1823,8 @@ class facet_grid:
|
|
|
1812
1823
|
``facet_grid(rows="sex", cols="day")`` or the formula ``"sex ~ day"``
|
|
1813
1824
|
(``". ~ day"`` for columns only). Column labels sit above the top row and
|
|
1814
1825
|
row labels to the right, as in ggplot2. ``scales="fixed"`` (default)
|
|
1815
|
-
shares axes across panels; ``"free"`` lets each panel fit its data
|
|
1826
|
+
shares axes across panels; ``"free"`` lets each panel fit its data, and
|
|
1827
|
+
``"free_x"`` / ``"free_y"`` free one axis only.
|
|
1816
1828
|
"""
|
|
1817
1829
|
|
|
1818
1830
|
def __init__(self, facets: str | None = None, *, rows=None, cols=None, scales: str = "fixed",
|
|
@@ -1839,8 +1851,7 @@ class facet_grid:
|
|
|
1839
1851
|
cols = _as_column_name(cols) if cols is not None else None
|
|
1840
1852
|
if rows is None and cols is None:
|
|
1841
1853
|
raise ValueError("facet_grid() needs rows=, cols=, or both")
|
|
1842
|
-
|
|
1843
|
-
raise ValueError("scales must be 'fixed' or 'free'")
|
|
1854
|
+
_check_facet_scales(scales)
|
|
1844
1855
|
self.rows = rows
|
|
1845
1856
|
self.cols = cols
|
|
1846
1857
|
self.scales = scales
|
|
@@ -263,6 +263,30 @@ class Plot3MaskTransformer(ast.NodeTransformer):
|
|
|
263
263
|
def _mask_selector(self, node: ast.AST) -> ast.AST:
|
|
264
264
|
return MaskSelectors(self._known()).visit(node)
|
|
265
265
|
|
|
266
|
+
def _mask_aes_value(self, node: ast.AST) -> ast.AST:
|
|
267
|
+
"""``colour = factor(cyl)`` → ``colour = "factor(cyl)"``.
|
|
268
|
+
|
|
269
|
+
aes() reads expressions over columns as strings (``"log10(pop)"``,
|
|
270
|
+
``"dose > 5"``). An expression whose bare names are all columns is
|
|
271
|
+
passed whole; quoting each name (``factor("cyl")``) breaks it.
|
|
272
|
+
Expressions over notebook variables or modules are left alone.
|
|
273
|
+
"""
|
|
274
|
+
if isinstance(node, (ast.Name, ast.Constant)) or _is_bt_call(node):
|
|
275
|
+
return self._mask_selector(node)
|
|
276
|
+
known = self._known()
|
|
277
|
+
called = {
|
|
278
|
+
id(call.func) for call in ast.walk(node)
|
|
279
|
+
if isinstance(call, ast.Call) and isinstance(call.func, ast.Name)
|
|
280
|
+
}
|
|
281
|
+
names = [
|
|
282
|
+
item for item in ast.walk(node)
|
|
283
|
+
if isinstance(item, ast.Name) and id(item) not in called
|
|
284
|
+
]
|
|
285
|
+
backticks = any(_is_bt_call(item) for item in ast.walk(node))
|
|
286
|
+
if names and not backticks and all(item.id not in known for item in names):
|
|
287
|
+
return ast.copy_location(ast.Constant(value=ast.unparse(node)), node)
|
|
288
|
+
return self._mask_selector(node)
|
|
289
|
+
|
|
266
290
|
def visit_Call(self, node: ast.Call) -> ast.AST:
|
|
267
291
|
# Always recurse so nested aes(...) inside geom_point(aes(...)) is seen.
|
|
268
292
|
if not isinstance(node.func, ast.Name):
|
|
@@ -276,9 +300,9 @@ class Plot3MaskTransformer(ast.NodeTransformer):
|
|
|
276
300
|
|
|
277
301
|
if name == "aes":
|
|
278
302
|
# All positional + keyword values are column selectors.
|
|
279
|
-
node.args = [self.
|
|
303
|
+
node.args = [self._mask_aes_value(a) for a in node.args]
|
|
280
304
|
node.keywords = [
|
|
281
|
-
ast.keyword(arg=kw.arg, value=self.
|
|
305
|
+
ast.keyword(arg=kw.arg, value=self._mask_aes_value(kw.value))
|
|
282
306
|
for kw in node.keywords
|
|
283
307
|
]
|
|
284
308
|
return node
|
|
@@ -609,6 +609,7 @@ def smooth(geom: _Geom, mapping: dict, data: Any):
|
|
|
609
609
|
curve = _layer(
|
|
610
610
|
"line", pd.concat(lines, ignore_index=True), line_map, geom,
|
|
611
611
|
_groups=starts, linewidth=float(getattr(geom, "linewidth", 2.0) or 2.0),
|
|
612
|
+
linetype=getattr(geom, "linetype", None),
|
|
612
613
|
)
|
|
613
614
|
_title(curve, "y", ycol)
|
|
614
615
|
_title(curve, "x", xcol)
|
|
@@ -659,7 +659,7 @@ def _draw_facets(panels, layout, first_spec, theme, width, height, base_pt, comm
|
|
|
659
659
|
# One legend for the figure, from the first panel (colour levels are
|
|
660
660
|
# shared, so every panel has the same keys).
|
|
661
661
|
legend_spec = dict(first_spec, legendPosition="right")
|
|
662
|
-
metrics = _legend_metrics(
|
|
662
|
+
metrics = None if layout.get("legend") == "none" else _legend_metrics(
|
|
663
663
|
legend_spec, theme, fonts, raw_labs.get("color") or "",
|
|
664
664
|
max_box=(width * 0.3, height - head - foot),
|
|
665
665
|
)
|
|
@@ -163,3 +163,49 @@ def test_composition_saves_static_and_html(tmp_path, plots):
|
|
|
163
163
|
assert doc.count("<iframe") == 3
|
|
164
164
|
assert "Overview" in doc
|
|
165
165
|
assert re.search(r"flex-direction:row", doc) and re.search(r"flex-direction:column", doc)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _facet_frame():
|
|
169
|
+
import pandas as pd
|
|
170
|
+
|
|
171
|
+
return pd.DataFrame({
|
|
172
|
+
"g": ["a"] * 3 + ["b"] * 3,
|
|
173
|
+
"x": [1, 2, 3, 10, 20, 30],
|
|
174
|
+
"y": [1.0, 2.0, 3.0, 100.0, 200.0, 300.0],
|
|
175
|
+
})
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_facet_free_y_frees_only_y():
|
|
179
|
+
from plot3 import aes, facet_wrap, geom_point, ggplot
|
|
180
|
+
from plot3.build import facet_cells
|
|
181
|
+
|
|
182
|
+
layout = facet_cells(ggplot(_facet_frame(), aes(x="x", y="y")) + geom_point() + facet_wrap("g", scales="free_y"))
|
|
183
|
+
forced = [cell["fig"]._force_scales for cell in layout["cells"]]
|
|
184
|
+
assert all(set(f) == {"x"} and f["x"] == (1.0, 30.0) for f in forced)
|
|
185
|
+
layout = facet_cells(ggplot(_facet_frame(), aes(x="x", y="y")) + geom_point() + facet_wrap("g", scales="free_x"))
|
|
186
|
+
assert all(set(cell["fig"]._force_scales) == {"y"} for cell in layout["cells"])
|
|
187
|
+
|
|
188
|
+
|
|
189
|
+
def test_facet_scales_rejects_unknown_values():
|
|
190
|
+
import pytest
|
|
191
|
+
|
|
192
|
+
from plot3 import facet_grid, facet_wrap
|
|
193
|
+
|
|
194
|
+
with pytest.raises(ValueError, match="free_y"):
|
|
195
|
+
facet_wrap("g", scales="loose")
|
|
196
|
+
facet_grid("g ~ .", scales="free_x")
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_facet_legend_position_none_hides_the_figure_legend(tmp_path):
|
|
200
|
+
from plot3 import aes, facet_wrap, geom_point, ggplot, ggsave, theme
|
|
201
|
+
from plot3.build import build_doc
|
|
202
|
+
|
|
203
|
+
p = (ggplot(_facet_frame(), aes(x="x", y="y", colour="g")) + geom_point()
|
|
204
|
+
+ facet_wrap("g") + theme(legend_position="none"))
|
|
205
|
+
assert "id='flegend'" not in build_doc(p)
|
|
206
|
+
out = tmp_path / "f.svg"
|
|
207
|
+
ggsave(str(out), p)
|
|
208
|
+
assert ">a<" in out.read_text() # strip label
|
|
209
|
+
shown = tmp_path / "shown.svg"
|
|
210
|
+
ggsave(str(shown), ggplot(_facet_frame(), aes(x="x", y="y", colour="g")) + geom_point() + facet_wrap("g"))
|
|
211
|
+
assert shown.read_text().count(">a<") > out.read_text().count(">a<") # legend key too
|
|
@@ -121,3 +121,17 @@ def test_set_colour_replaces_inherited_colour_mapping():
|
|
|
121
121
|
ggplot(df, aes(x="g", y="y", fill="g")) + geom_boxplot(colour="black")
|
|
122
122
|
)
|
|
123
123
|
assert spec["layers"][0].get("color") is not None
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
def test_geom_smooth_takes_a_linetype():
|
|
127
|
+
import pandas as pd
|
|
128
|
+
import pytest
|
|
129
|
+
|
|
130
|
+
from plot3 import aes, geom_smooth, ggplot
|
|
131
|
+
from plot3.build import build_spec
|
|
132
|
+
|
|
133
|
+
df = pd.DataFrame({"x": [1, 2, 3, 4], "y": [1.0, 2.5, 2.9, 4.2]})
|
|
134
|
+
spec, _ = build_spec(ggplot(df, aes(x="x", y="y")) + geom_smooth(method="lm", se=False, linetype="dashed"))
|
|
135
|
+
assert [layer.get("dash") for layer in spec["layers"] if layer["kind"] == "line"] == [[4.0, 4.0]]
|
|
136
|
+
with pytest.raises(ValueError):
|
|
137
|
+
geom_smooth(linetype="wavy")
|
|
@@ -183,3 +183,17 @@ def test_facet_wrap_masked_end_to_end(cars):
|
|
|
183
183
|
fig = ggplot(cars, aes(x="wt", y="mpg")) + geom_point() + fac
|
|
184
184
|
html = fig._repr_html_()
|
|
185
185
|
assert "iframe" in html or "three" in html.lower() or len(html) > 100
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def test_aes_expression_over_columns_passes_whole():
|
|
189
|
+
# aes(colour = factor(cyl)) in a notebook: aes() reads "factor(cyl)";
|
|
190
|
+
# quoting each name (factor("cyl")) would break it.
|
|
191
|
+
from plot3.masking import apply_masking, default_known_names
|
|
192
|
+
|
|
193
|
+
known = default_known_names({"np", "scale"})
|
|
194
|
+
assert apply_masking("aes(colour=factor(cyl))", known=known) == "aes(colour='factor(cyl)')"
|
|
195
|
+
assert apply_masking("aes(x=log10(pop), y=lifeExp)", known=known) == "aes(x='log10(pop)', y='lifeExp')"
|
|
196
|
+
assert apply_masking("aes(colour=cyl > 4)", known=known) == "aes(colour='cyl > 4')"
|
|
197
|
+
# Notebook variables and modules keep the old behaviour.
|
|
198
|
+
assert apply_masking("aes(x=np.log(x))", known=known) == "aes(x=np.log('x'))"
|
|
199
|
+
assert apply_masking("aes(x=wt)", known=known) == "aes(x='wt')"
|
plot3-0.5.1/plot3/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.5.1"
|
|
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
|