plot3 0.6.1__tar.gz → 0.6.2__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.6.1 → plot3-0.6.2}/PKG-INFO +16 -7
- {plot3-0.6.1 → plot3-0.6.2}/README.md +15 -6
- plot3-0.6.2/plot3/__version__.py +1 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/build.py +45 -2
- {plot3-0.6.1 → plot3-0.6.2}/plot3/static.py +4 -2
- {plot3-0.6.1 → plot3-0.6.2}/plot3.egg-info/PKG-INFO +16 -7
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_grammar.py +42 -0
- plot3-0.6.1/plot3/__version__.py +0 -1
- {plot3-0.6.1 → plot3-0.6.2}/LICENSE +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/__init__.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/aesexpr.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/calculus.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/compose.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/contour.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/craft.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/encode.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/expr.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/flip.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/function.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/geoms.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/ggplot.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/io.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/jupyter.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/latexin.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/masking.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/mathtext.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/payload.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/remote.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/scales.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/scaling.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/special.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/stat2d.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/stats3d.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/table.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/themes.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3/viewer.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3.egg-info/SOURCES.txt +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3.egg-info/dependency_links.txt +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3.egg-info/requires.txt +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/plot3.egg-info/top_level.txt +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/pyproject.toml +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/setup.cfg +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_2d_density.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_3d.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_animation.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_annotate.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_api_public.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_array_backend.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_arrow.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_build_contract.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_contour.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_everyday_ggplot2.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_expr.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_facets_compose.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_geom_function.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_geoms_ds.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_ggplot2_extras2.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_ggplot2_parity.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_ggplot_extras.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_latex_input.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_lidar_scene.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_masking.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_math_layers.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_mathtext.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_payload.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_payload_display.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_point_cloud.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_r_oracle_parity.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_readme.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_remote_bridge.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_review_fixes.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_save_menu.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_scales_api.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_scales_themes.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_special.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_stat2d.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_static_save.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_stats_semantic.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_step3_geoms.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_table_backends.py +0 -0
- {plot3-0.6.1 → plot3-0.6.2}/tests/test_titles_theme.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
@@ -224,18 +224,27 @@ ggplot2's names and defaults.
|
|
|
224
224
|
|
|
225
225
|
```python
|
|
226
226
|
rng = np.random.default_rng(1)
|
|
227
|
-
|
|
227
|
+
arms = ["placebo", "low", "high"]
|
|
228
|
+
trial = pd.DataFrame({"arm": pd.Categorical(rng.choice(arms, 150), categories=arms),
|
|
228
229
|
"dose": rng.uniform(0, 10, 150)})
|
|
229
|
-
|
|
230
|
+
slope = trial.arm.map({"placebo": 0.05, "low": 0.35, "high": 0.7}).astype(float)
|
|
231
|
+
trial["response"] = 2 + slope * trial.dose + rng.normal(0, 0.9, 150)
|
|
230
232
|
|
|
231
233
|
a = (ggplot(trial, aes(x="dose", y="response", colour="arm"))
|
|
232
|
-
+ geom_point(alpha=0.7) + geom_smooth(method="lm")
|
|
234
|
+
+ geom_point(alpha=0.7) + geom_smooth(method="lm", se=False)
|
|
235
|
+
+ scale_colour_hue() + theme_grey() + theme(legend_position="bottom"))
|
|
233
236
|
b = (ggplot(trial, aes(x="arm", y="response", fill="arm"))
|
|
234
|
-
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
235
|
-
|
|
237
|
+
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
238
|
+
+ scale_fill_hue() + theme_grey() + theme(legend_position="none"))
|
|
239
|
+
fig = ((a | b) + plot_layout(widths=[3, 2])
|
|
240
|
+
+ plot_annotation(title="Response by dose and arm", tag_levels="A"))
|
|
236
241
|
```
|
|
237
242
|
|
|
238
|
-
|
|
243
|
+
`scale_colour_hue()` and `theme_grey()` are ggplot2's own defaults, so the
|
|
244
|
+
figure looks as it would from R. plot3's defaults are `theme_bw()` for saved
|
|
245
|
+
files and `theme_dark()` in the viewer.
|
|
246
|
+
|
|
247
|
+
<img src="https://raw.githubusercontent.com/rleyvasal/plot3/main/docs/img/ggplot2_look.png" alt="Two-panel figure in ggplot2's grey theme: response against dose with one fitted line per arm, and violins of response by arm">
|
|
239
248
|
|
|
240
249
|
Everything else you'd expect is there, including histograms, densities,
|
|
241
250
|
boxplots, bar positions, `stat_summary`, error bars, heatmaps, hexbins, 2D
|
|
@@ -191,18 +191,27 @@ ggplot2's names and defaults.
|
|
|
191
191
|
|
|
192
192
|
```python
|
|
193
193
|
rng = np.random.default_rng(1)
|
|
194
|
-
|
|
194
|
+
arms = ["placebo", "low", "high"]
|
|
195
|
+
trial = pd.DataFrame({"arm": pd.Categorical(rng.choice(arms, 150), categories=arms),
|
|
195
196
|
"dose": rng.uniform(0, 10, 150)})
|
|
196
|
-
|
|
197
|
+
slope = trial.arm.map({"placebo": 0.05, "low": 0.35, "high": 0.7}).astype(float)
|
|
198
|
+
trial["response"] = 2 + slope * trial.dose + rng.normal(0, 0.9, 150)
|
|
197
199
|
|
|
198
200
|
a = (ggplot(trial, aes(x="dose", y="response", colour="arm"))
|
|
199
|
-
+ geom_point(alpha=0.7) + geom_smooth(method="lm")
|
|
201
|
+
+ geom_point(alpha=0.7) + geom_smooth(method="lm", se=False)
|
|
202
|
+
+ scale_colour_hue() + theme_grey() + theme(legend_position="bottom"))
|
|
200
203
|
b = (ggplot(trial, aes(x="arm", y="response", fill="arm"))
|
|
201
|
-
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
202
|
-
|
|
204
|
+
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
205
|
+
+ scale_fill_hue() + theme_grey() + theme(legend_position="none"))
|
|
206
|
+
fig = ((a | b) + plot_layout(widths=[3, 2])
|
|
207
|
+
+ plot_annotation(title="Response by dose and arm", tag_levels="A"))
|
|
203
208
|
```
|
|
204
209
|
|
|
205
|
-
|
|
210
|
+
`scale_colour_hue()` and `theme_grey()` are ggplot2's own defaults, so the
|
|
211
|
+
figure looks as it would from R. plot3's defaults are `theme_bw()` for saved
|
|
212
|
+
files and `theme_dark()` in the viewer.
|
|
213
|
+
|
|
214
|
+
<img src="https://raw.githubusercontent.com/rleyvasal/plot3/main/docs/img/ggplot2_look.png" alt="Two-panel figure in ggplot2's grey theme: response against dose with one fitted line per arm, and violins of response by arm">
|
|
206
215
|
|
|
207
216
|
Everything else you'd expect is there, including histograms, densities,
|
|
208
217
|
boxplots, bar positions, `stat_summary`, error bars, heatmaps, hexbins, 2D
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.2"
|
|
@@ -762,7 +762,7 @@ def expand_stat_geom(
|
|
|
762
762
|
if len(group_cols) > 1:
|
|
763
763
|
out_row[colour_col] = key_tuple[1]
|
|
764
764
|
outlier_rows.append(out_row)
|
|
765
|
-
frame = pd.DataFrame(rows)
|
|
765
|
+
frame = _keep_categories(pd.DataFrame(rows), data, [colour_col])
|
|
766
766
|
box_width = float(getattr(geom, "width", 0.75))
|
|
767
767
|
dodge_levels = None
|
|
768
768
|
if len(group_cols) > 1 and not frame.empty:
|
|
@@ -920,7 +920,7 @@ def expand_stat_geom(
|
|
|
920
920
|
elif colour_col == xcol:
|
|
921
921
|
row[colour_col] = x_key
|
|
922
922
|
rows.append(row)
|
|
923
|
-
frame = pd.DataFrame(rows)
|
|
923
|
+
frame = _keep_categories(pd.DataFrame(rows), data, [colour_col])
|
|
924
924
|
if frame.empty:
|
|
925
925
|
frame = pd.DataFrame(columns=["x", "y", "group"])
|
|
926
926
|
# Force categorical x scale labels via a helper frame for domains:
|
|
@@ -1423,6 +1423,48 @@ def _legend_position_spec(value):
|
|
|
1423
1423
|
|
|
1424
1424
|
|
|
1425
1425
|
|
|
1426
|
+
def _categorical_order(g, resolved, levels) -> list:
|
|
1427
|
+
"""Colour levels in the order of a categorical colour column, as ggplot2.
|
|
1428
|
+
|
|
1429
|
+
Stats (density, histogram, smooth, summaries) rebuild their data, so
|
|
1430
|
+
their levels arrive sorted as text. When the plot's colour column is a
|
|
1431
|
+
``pd.Categorical``, its categories set the order (and so the colours);
|
|
1432
|
+
levels it does not list keep their place after them.
|
|
1433
|
+
"""
|
|
1434
|
+
data = getattr(g, "data", None)
|
|
1435
|
+
if data is None:
|
|
1436
|
+
return list(levels)
|
|
1437
|
+
columns = dict.fromkeys(m.get("color") for _geom, m in resolved if isinstance(m.get("color"), str))
|
|
1438
|
+
for column in columns:
|
|
1439
|
+
if not has_column(data, column):
|
|
1440
|
+
continue
|
|
1441
|
+
source = materialize_columns(data, [column])[column]
|
|
1442
|
+
if not isinstance(source.dtype, pd.CategoricalDtype):
|
|
1443
|
+
continue
|
|
1444
|
+
wanted = [str(c) for c in source.cat.categories]
|
|
1445
|
+
present = [str(level) for level in levels]
|
|
1446
|
+
first = [c for c in wanted if c in present]
|
|
1447
|
+
return first + [level for level in present if level not in first]
|
|
1448
|
+
return list(levels)
|
|
1449
|
+
|
|
1450
|
+
|
|
1451
|
+
def _keep_categories(frame: pd.DataFrame, data, columns) -> pd.DataFrame:
|
|
1452
|
+
"""Give a stat's output columns the category order of the source column.
|
|
1453
|
+
|
|
1454
|
+
Box and violin stats build their output from plain rows, which turns
|
|
1455
|
+
``pd.Categorical(..., categories=["placebo", "low", "high"])`` into
|
|
1456
|
+
text, sorted alphabetically: the fill colours then disagree with the
|
|
1457
|
+
points of the same column in another panel or layer.
|
|
1458
|
+
"""
|
|
1459
|
+
for column in dict.fromkeys(c for c in columns if c):
|
|
1460
|
+
if column not in frame.columns or not has_column(data, column):
|
|
1461
|
+
continue
|
|
1462
|
+
source = materialize_columns(data, [column])[column]
|
|
1463
|
+
if isinstance(source.dtype, pd.CategoricalDtype):
|
|
1464
|
+
frame[column] = pd.Categorical(frame[column], categories=list(source.cat.categories))
|
|
1465
|
+
return frame
|
|
1466
|
+
|
|
1467
|
+
|
|
1426
1468
|
def _forced_levels(g, ccats) -> list:
|
|
1427
1469
|
"""Colour levels shared by every facet panel, so a group keeps its colour."""
|
|
1428
1470
|
forced = getattr(g, "_force_color_levels", None)
|
|
@@ -2826,6 +2868,7 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
|
|
|
2826
2868
|
# viridis_d scale, else the theme's colours extended past eight groups.
|
|
2827
2869
|
cat_colours: list[str] = []
|
|
2828
2870
|
if color_scale is not None and color_scale[0] == "cat":
|
|
2871
|
+
color_scale[1] = _categorical_order(g, resolved, color_scale[1])
|
|
2829
2872
|
user_scale = getattr(g, "colour_scale", None)
|
|
2830
2873
|
if user_scale is not None and user_scale.kind == "continuous":
|
|
2831
2874
|
raise ValueError(
|
|
@@ -2196,13 +2196,15 @@ def _paint_legend(commands, origin, metrics, theme, fonts) -> None:
|
|
|
2196
2196
|
surface = theme.get("surface") or "#0b1020"
|
|
2197
2197
|
grid = theme.get("grid") or "#1c2742"
|
|
2198
2198
|
if _rgb(grid) == _rgb(surface):
|
|
2199
|
-
grid
|
|
2199
|
+
# theme_grey / theme_classic: white grid on a white page. ggplot2
|
|
2200
|
+
# draws no legend box, so neither do we (not a dark one instead).
|
|
2201
|
+
grid = None
|
|
2200
2202
|
tick = fonts[0]
|
|
2201
2203
|
row_h = metrics["row_h"]
|
|
2202
2204
|
lx, ly = origin
|
|
2203
2205
|
# Opaque, so a legend inside the panel does not fade the marks under it.
|
|
2204
2206
|
commands.append((
|
|
2205
|
-
"rect", lx, ly, metrics["w"], metrics["h"], surface, grid, 1, 1.0,
|
|
2207
|
+
"rect", lx, ly, metrics["w"], metrics["h"], surface, grid, 1 if grid else 0, 1.0,
|
|
2206
2208
|
))
|
|
2207
2209
|
cursor = ly + 6
|
|
2208
2210
|
for row in metrics["rows"]:
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
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
|
|
@@ -224,18 +224,27 @@ ggplot2's names and defaults.
|
|
|
224
224
|
|
|
225
225
|
```python
|
|
226
226
|
rng = np.random.default_rng(1)
|
|
227
|
-
|
|
227
|
+
arms = ["placebo", "low", "high"]
|
|
228
|
+
trial = pd.DataFrame({"arm": pd.Categorical(rng.choice(arms, 150), categories=arms),
|
|
228
229
|
"dose": rng.uniform(0, 10, 150)})
|
|
229
|
-
|
|
230
|
+
slope = trial.arm.map({"placebo": 0.05, "low": 0.35, "high": 0.7}).astype(float)
|
|
231
|
+
trial["response"] = 2 + slope * trial.dose + rng.normal(0, 0.9, 150)
|
|
230
232
|
|
|
231
233
|
a = (ggplot(trial, aes(x="dose", y="response", colour="arm"))
|
|
232
|
-
+ geom_point(alpha=0.7) + geom_smooth(method="lm")
|
|
234
|
+
+ geom_point(alpha=0.7) + geom_smooth(method="lm", se=False)
|
|
235
|
+
+ scale_colour_hue() + theme_grey() + theme(legend_position="bottom"))
|
|
233
236
|
b = (ggplot(trial, aes(x="arm", y="response", fill="arm"))
|
|
234
|
-
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
235
|
-
|
|
237
|
+
+ geom_violin() + geom_jitter(width=0.1, height=0)
|
|
238
|
+
+ scale_fill_hue() + theme_grey() + theme(legend_position="none"))
|
|
239
|
+
fig = ((a | b) + plot_layout(widths=[3, 2])
|
|
240
|
+
+ plot_annotation(title="Response by dose and arm", tag_levels="A"))
|
|
236
241
|
```
|
|
237
242
|
|
|
238
|
-
|
|
243
|
+
`scale_colour_hue()` and `theme_grey()` are ggplot2's own defaults, so the
|
|
244
|
+
figure looks as it would from R. plot3's defaults are `theme_bw()` for saved
|
|
245
|
+
files and `theme_dark()` in the viewer.
|
|
246
|
+
|
|
247
|
+
<img src="https://raw.githubusercontent.com/rleyvasal/plot3/main/docs/img/ggplot2_look.png" alt="Two-panel figure in ggplot2's grey theme: response against dose with one fitted line per arm, and violins of response by arm">
|
|
239
248
|
|
|
240
249
|
Everything else you'd expect is there, including histograms, densities,
|
|
241
250
|
boxplots, bar positions, `stat_summary`, error bars, heatmaps, hexbins, 2D
|
|
@@ -135,3 +135,45 @@ def test_geom_smooth_takes_a_linetype():
|
|
|
135
135
|
assert [layer.get("dash") for layer in spec["layers"] if layer["kind"] == "line"] == [[4.0, 4.0]]
|
|
136
136
|
with pytest.raises(ValueError):
|
|
137
137
|
geom_smooth(linetype="wavy")
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@pytest.mark.parametrize("kind", ["violin", "boxplot", "density", "histogram", "bar", "smooth", "summary"])
|
|
141
|
+
def test_categorical_colour_order_survives_stats(kind):
|
|
142
|
+
# pd.Categorical(categories=["placebo", "low", "high"]) sets the colour
|
|
143
|
+
# order for every geom, as in ggplot2; stats used to sort it as text, so
|
|
144
|
+
# a violin and a scatter of the same column disagreed on colours.
|
|
145
|
+
import numpy as np
|
|
146
|
+
import pandas as pd
|
|
147
|
+
|
|
148
|
+
from plot3 import (aes, geom_bar, geom_boxplot, geom_density, geom_histogram,
|
|
149
|
+
geom_smooth, geom_violin, ggplot, stat_summary)
|
|
150
|
+
from plot3.build import build_spec
|
|
151
|
+
|
|
152
|
+
rng = np.random.default_rng(0)
|
|
153
|
+
arms = ["placebo", "low", "high"]
|
|
154
|
+
df = pd.DataFrame({"arm": pd.Categorical(rng.choice(arms, 90), categories=arms),
|
|
155
|
+
"x": rng.uniform(0, 10, 90), "y": rng.normal(size=90),
|
|
156
|
+
"sex": rng.choice(["F", "M"], 90)})
|
|
157
|
+
layer = {
|
|
158
|
+
"violin": (aes(x="arm", y="y", fill="arm"), geom_violin()),
|
|
159
|
+
"boxplot": (aes(x="sex", y="y", fill="arm"), geom_boxplot()),
|
|
160
|
+
"density": (aes(x="y", colour="arm"), geom_density()),
|
|
161
|
+
"histogram": (aes(x="y", fill="arm"), geom_histogram(bins=10)),
|
|
162
|
+
"bar": (aes(x="sex", fill="arm"), geom_bar()),
|
|
163
|
+
"smooth": (aes(x="x", y="y", colour="arm"), geom_smooth(method="lm")),
|
|
164
|
+
"summary": (aes(x="sex", y="y", colour="arm"), stat_summary()),
|
|
165
|
+
}[kind]
|
|
166
|
+
spec, _ = build_spec(ggplot(df, layer[0]) + layer[1])
|
|
167
|
+
assert spec["color"]["cats"] == arms
|
|
168
|
+
assert [entry["label"] for entry in spec["legend"]] == arms
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_theme_grey_legend_has_no_dark_frame(tmp_path):
|
|
172
|
+
import pandas as pd
|
|
173
|
+
|
|
174
|
+
from plot3 import aes, geom_point, ggplot, ggsave, theme_grey
|
|
175
|
+
|
|
176
|
+
df = pd.DataFrame({"x": [1, 2], "y": [1, 2], "g": ["a", "b"]})
|
|
177
|
+
out = tmp_path / "g.svg"
|
|
178
|
+
ggsave(str(out), ggplot(df, aes(x="x", y="y", colour="g")) + geom_point() + theme_grey())
|
|
179
|
+
assert 'stroke="#4d4d4d"' not in out.read_text()
|
plot3-0.6.1/plot3/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|