plot3 0.4.0__tar.gz → 0.4.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.4.0 → plot3-0.4.2}/PKG-INFO +22 -5
- {plot3-0.4.0 → plot3-0.4.2}/README.md +21 -4
- plot3-0.4.2/plot3/__version__.py +1 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/function.py +10 -1
- {plot3-0.4.0 → plot3-0.4.2}/plot3/ggplot.py +8 -1
- {plot3-0.4.0 → plot3-0.4.2}/plot3/viewer.py +24 -2
- {plot3-0.4.0 → plot3-0.4.2}/plot3.egg-info/PKG-INFO +22 -5
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_animation.py +32 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_payload_display.py +26 -0
- plot3-0.4.0/plot3/__version__.py +0 -1
- {plot3-0.4.0 → plot3-0.4.2}/LICENSE +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/__init__.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/aesexpr.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/build.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/calculus.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/compose.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/contour.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/craft.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/encode.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/expr.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/flip.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/geoms.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/io.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/jupyter.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/latexin.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/masking.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/mathtext.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/payload.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/remote.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/scales.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/scaling.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/special.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/stat2d.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/static.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/stats3d.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/table.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3/themes.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3.egg-info/SOURCES.txt +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3.egg-info/dependency_links.txt +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3.egg-info/requires.txt +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/plot3.egg-info/top_level.txt +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/pyproject.toml +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/setup.cfg +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_2d_density.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_3d.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_annotate.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_api_public.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_array_backend.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_arrow.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_build_contract.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_contour.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_everyday_ggplot2.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_expr.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_facets_compose.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_geom_function.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_geoms_ds.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_ggplot2_extras2.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_ggplot2_parity.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_ggplot_extras.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_grammar.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_latex_input.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_lidar_scene.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_masking.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_math_layers.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_mathtext.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_payload.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_point_cloud.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_r_oracle_parity.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_readme.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_remote_bridge.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_review_fixes.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_save_menu.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_scales_api.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_scales_themes.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_special.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_stat2d.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_static_save.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_stats_semantic.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_step3_geoms.py +0 -0
- {plot3-0.4.0 → plot3-0.4.2}/tests/test_table_backends.py +0 -0
- {plot3-0.4.0 → plot3-0.4.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.4.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -376,13 +376,28 @@ ggplot() + geom_function("y = sin(x - t)") + transition_time(t=(0, 6.28)) # tr
|
|
|
376
376
|
ggplot() + geom_function("y = dbeta(x, a, b)") + slider(a=(0.5, 5), b=(0.5, 5))
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
Data animations follow `gganimate
|
|
379
|
+
Data animations follow `gganimate`. With a table shaped like gapminder
|
|
380
|
+
(made-up numbers here; the real data is
|
|
381
|
+
`pl.read_csv("https://raw.githubusercontent.com/kirenz/datasets/master/gapminder.csv")`):
|
|
380
382
|
|
|
381
|
-
```python
|
|
382
|
-
|
|
383
|
+
```python
|
|
384
|
+
countries = {"Brazil": "Americas", "China": "Asia", "Egypt": "Africa",
|
|
385
|
+
"France": "Europe", "India": "Asia", "Mexico": "Americas"}
|
|
386
|
+
gapminder = pd.DataFrame([
|
|
387
|
+
{"country": country, "continent": continent, "year": year,
|
|
388
|
+
"gdpPercap": 800 * (1.03 + 0.01 * i) ** (year - 1952),
|
|
389
|
+
"lifeExp": 45 + 0.35 * (year - 1952) + 2 * i,
|
|
390
|
+
"pop": 2e7 * (1.02 + 0.002 * i) ** (year - 1952)}
|
|
391
|
+
for i, (country, continent) in enumerate(countries.items())
|
|
392
|
+
for year in range(1952, 2008, 5)
|
|
393
|
+
])
|
|
394
|
+
|
|
395
|
+
(ggplot(gapminder, aes(x="gdpPercap", y="lifeExp", size="pop", colour="continent", group="country"))
|
|
383
396
|
+ geom_point() + scale_x_log10() + transition_time("year") + labs(title="{frame_time}"))
|
|
384
397
|
```
|
|
385
398
|
|
|
399
|
+
`{frame_time}` in a title shows the current year as the animation plays.
|
|
400
|
+
|
|
386
401
|
The viewer interpolates between frames in the browser, with play, pause,
|
|
387
402
|
scrubbing, speed, and video recording.
|
|
388
403
|
|
|
@@ -437,7 +452,9 @@ tree) is shortened to twice its width so it does not become a thin column;
|
|
|
437
452
|
- **SolveIt** draws figures inline and hides their HTML from the model's
|
|
438
453
|
context (`autohide(False)` to opt out).
|
|
439
454
|
- **VS Code notebooks** block WebGL in output cells, so plot3 opens figures
|
|
440
|
-
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode).
|
|
455
|
+
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode). The page
|
|
456
|
+
is written to `.plot3_preview/latest.html` in the notebook's folder, or to
|
|
457
|
+
the system temp folder when that folder is read-only.
|
|
441
458
|
- **CRAFT / `%gpu`**: the same `ggplot(...)` code runs on the remote kernel;
|
|
442
459
|
stats run where the data lives and only a compact payload comes back.
|
|
443
460
|
|
|
@@ -343,13 +343,28 @@ ggplot() + geom_function("y = sin(x - t)") + transition_time(t=(0, 6.28)) # tr
|
|
|
343
343
|
ggplot() + geom_function("y = dbeta(x, a, b)") + slider(a=(0.5, 5), b=(0.5, 5))
|
|
344
344
|
```
|
|
345
345
|
|
|
346
|
-
Data animations follow `gganimate
|
|
346
|
+
Data animations follow `gganimate`. With a table shaped like gapminder
|
|
347
|
+
(made-up numbers here; the real data is
|
|
348
|
+
`pl.read_csv("https://raw.githubusercontent.com/kirenz/datasets/master/gapminder.csv")`):
|
|
347
349
|
|
|
348
|
-
```python
|
|
349
|
-
|
|
350
|
+
```python
|
|
351
|
+
countries = {"Brazil": "Americas", "China": "Asia", "Egypt": "Africa",
|
|
352
|
+
"France": "Europe", "India": "Asia", "Mexico": "Americas"}
|
|
353
|
+
gapminder = pd.DataFrame([
|
|
354
|
+
{"country": country, "continent": continent, "year": year,
|
|
355
|
+
"gdpPercap": 800 * (1.03 + 0.01 * i) ** (year - 1952),
|
|
356
|
+
"lifeExp": 45 + 0.35 * (year - 1952) + 2 * i,
|
|
357
|
+
"pop": 2e7 * (1.02 + 0.002 * i) ** (year - 1952)}
|
|
358
|
+
for i, (country, continent) in enumerate(countries.items())
|
|
359
|
+
for year in range(1952, 2008, 5)
|
|
360
|
+
])
|
|
361
|
+
|
|
362
|
+
(ggplot(gapminder, aes(x="gdpPercap", y="lifeExp", size="pop", colour="continent", group="country"))
|
|
350
363
|
+ geom_point() + scale_x_log10() + transition_time("year") + labs(title="{frame_time}"))
|
|
351
364
|
```
|
|
352
365
|
|
|
366
|
+
`{frame_time}` in a title shows the current year as the animation plays.
|
|
367
|
+
|
|
353
368
|
The viewer interpolates between frames in the browser, with play, pause,
|
|
354
369
|
scrubbing, speed, and video recording.
|
|
355
370
|
|
|
@@ -404,7 +419,9 @@ tree) is shortened to twice its width so it does not become a thin column;
|
|
|
404
419
|
- **SolveIt** draws figures inline and hides their HTML from the model's
|
|
405
420
|
context (`autohide(False)` to opt out).
|
|
406
421
|
- **VS Code notebooks** block WebGL in output cells, so plot3 opens figures
|
|
407
|
-
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode).
|
|
422
|
+
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode). The page
|
|
423
|
+
is written to `.plot3_preview/latest.html` in the notebook's folder, or to
|
|
424
|
+
the system temp folder when that folder is read-only.
|
|
408
425
|
- **CRAFT / `%gpu`**: the same `ggplot(...)` code runs on the remote kernel;
|
|
409
426
|
stats run where the data lives and only a compact payload comes back.
|
|
410
427
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.4.2"
|
|
@@ -135,9 +135,18 @@ def _bind_swept_symbols(geom, formula: Formula, ranges: dict) -> Formula:
|
|
|
135
135
|
params = dict(getattr(geom, "params", None) or {})
|
|
136
136
|
for name in swept:
|
|
137
137
|
params[name] = float(ranges[name][0])
|
|
138
|
+
from dataclasses import replace
|
|
139
|
+
|
|
138
140
|
from plot3.expr import parse_formula
|
|
139
141
|
|
|
140
|
-
|
|
142
|
+
bound = parse_formula(source, params, defer_missing=True)
|
|
143
|
+
# Labels keep the swept symbol: the stand-in value would read "(t = 0)"
|
|
144
|
+
# in the legend and tooltip on every frame.
|
|
145
|
+
texts = (
|
|
146
|
+
"label", "latex", "pretty", "legend_latex", "legend_pretty",
|
|
147
|
+
"caption_latex", "caption_pretty",
|
|
148
|
+
)
|
|
149
|
+
return replace(bound, **{name: getattr(formula, name) for name in texts})
|
|
141
150
|
|
|
142
151
|
|
|
143
152
|
def _reject_curve_extras(geom, addons) -> None:
|
|
@@ -8,6 +8,7 @@ from plot3 import scaling as _scaling
|
|
|
8
8
|
import html as _htmlesc
|
|
9
9
|
import os
|
|
10
10
|
import sys
|
|
11
|
+
import tempfile
|
|
11
12
|
import webbrowser
|
|
12
13
|
from pathlib import Path
|
|
13
14
|
|
|
@@ -545,7 +546,13 @@ class ggplot:
|
|
|
545
546
|
) -> Path:
|
|
546
547
|
if path is None:
|
|
547
548
|
out_dir = Path.cwd() / ".plot3_preview"
|
|
548
|
-
|
|
549
|
+
try:
|
|
550
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
551
|
+
except OSError:
|
|
552
|
+
# A kernel started in "/" or another read-only folder (VS
|
|
553
|
+
# Code does this for notebooks outside a workspace).
|
|
554
|
+
out_dir = Path(tempfile.gettempdir()) / "plot3_preview"
|
|
555
|
+
out_dir.mkdir(parents=True, exist_ok=True)
|
|
549
556
|
path = out_dir / "latest.html"
|
|
550
557
|
else:
|
|
551
558
|
path = Path(path)
|
|
@@ -16,7 +16,8 @@ body{display:flex;flex-direction:column}
|
|
|
16
16
|
#canvas-host{position:absolute;z-index:1}
|
|
17
17
|
#year{display:none;position:absolute;left:50%;top:46%;transform:translate(-50%,-50%);
|
|
18
18
|
z-index:0;font-weight:700;line-height:1;letter-spacing:-0.04em;opacity:0.13;
|
|
19
|
-
pointer-events:none;user-select:none
|
|
19
|
+
pointer-events:none;user-select:none;font-variant-numeric:tabular-nums;
|
|
20
|
+
white-space:nowrap;text-align:right}
|
|
20
21
|
#axes{position:absolute;inset:0;pointer-events:none;z-index:2}
|
|
21
22
|
#grid{position:absolute;inset:0;pointer-events:none;z-index:0}
|
|
22
23
|
#legend{position:absolute;right:10px;top:36px;z-index:4;padding:6px 9px;
|
|
@@ -1214,6 +1215,27 @@ function fmtParam(v) {
|
|
|
1214
1215
|
if (a >= 1e6 || (a > 0 && a < 1e-3)) return v.toExponential(2);
|
|
1215
1216
|
return v.toFixed(2);
|
|
1216
1217
|
}
|
|
1218
|
+
// The big frame label must not move while it changes: pick one font size
|
|
1219
|
+
// for the whole animation and a box as wide as its widest label (in em, so
|
|
1220
|
+
// it scales with the window); digits are tabular and right-aligned in it.
|
|
1221
|
+
function steadyFrameLabel(el, nF) {
|
|
1222
|
+
const count = Math.min(Math.max(nF, 1), 240);
|
|
1223
|
+
const labels = new Set();
|
|
1224
|
+
for (let i = 0; i < count; i++) {
|
|
1225
|
+
labels.add(frameText(count <= 1 ? 0 : i * (nF - 1) / (count - 1)));
|
|
1226
|
+
}
|
|
1227
|
+
let longest = 0;
|
|
1228
|
+
for (const lab of labels) longest = Math.max(longest, lab.length);
|
|
1229
|
+
el.style.fontSize = longest > 8 ? '56px' : 'min(22vw, 148px)';
|
|
1230
|
+
el.style.width = 'auto';
|
|
1231
|
+
const size = parseFloat(getComputedStyle(el).fontSize) || 1;
|
|
1232
|
+
let widest = 0;
|
|
1233
|
+
for (const lab of labels) {
|
|
1234
|
+
el.textContent = lab;
|
|
1235
|
+
widest = Math.max(widest, el.getBoundingClientRect().width);
|
|
1236
|
+
}
|
|
1237
|
+
el.style.width = (widest / size + 0.05).toFixed(3) + 'em';
|
|
1238
|
+
}
|
|
1217
1239
|
function frameText(t) {
|
|
1218
1240
|
const tr = S.transition;
|
|
1219
1241
|
if (tr.params && tr.params.length) {
|
|
@@ -1349,6 +1371,7 @@ function installPlayer(renderFrame) {
|
|
|
1349
1371
|
btn.style.border = '1px solid ' + T.axis;
|
|
1350
1372
|
yearEl.style.display = 'block';
|
|
1351
1373
|
yearEl.style.color = T.ink;
|
|
1374
|
+
steadyFrameLabel(yearEl, nF);
|
|
1352
1375
|
const titleTemplate = (S.labs && S.labs.title) || '';
|
|
1353
1376
|
let shownLab = null;
|
|
1354
1377
|
function syncChrome() {
|
|
@@ -1356,7 +1379,6 @@ function installPlayer(renderFrame) {
|
|
|
1356
1379
|
frameLabel = lab;
|
|
1357
1380
|
readout.textContent = lab;
|
|
1358
1381
|
yearEl.textContent = lab;
|
|
1359
|
-
yearEl.style.fontSize = lab.length > 8 ? '56px' : 'min(22vw, 148px)';
|
|
1360
1382
|
range.value = (nF <= 1) ? '1000' : String(Math.round(playT / (nF - 1) * 1000));
|
|
1361
1383
|
btn.textContent = playing ? 'Pause' : 'Play';
|
|
1362
1384
|
btn.setAttribute('aria-label', playing ? 'Pause' : 'Play');
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plot3
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.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
|
|
@@ -376,13 +376,28 @@ ggplot() + geom_function("y = sin(x - t)") + transition_time(t=(0, 6.28)) # tr
|
|
|
376
376
|
ggplot() + geom_function("y = dbeta(x, a, b)") + slider(a=(0.5, 5), b=(0.5, 5))
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
Data animations follow `gganimate
|
|
379
|
+
Data animations follow `gganimate`. With a table shaped like gapminder
|
|
380
|
+
(made-up numbers here; the real data is
|
|
381
|
+
`pl.read_csv("https://raw.githubusercontent.com/kirenz/datasets/master/gapminder.csv")`):
|
|
380
382
|
|
|
381
|
-
```python
|
|
382
|
-
|
|
383
|
+
```python
|
|
384
|
+
countries = {"Brazil": "Americas", "China": "Asia", "Egypt": "Africa",
|
|
385
|
+
"France": "Europe", "India": "Asia", "Mexico": "Americas"}
|
|
386
|
+
gapminder = pd.DataFrame([
|
|
387
|
+
{"country": country, "continent": continent, "year": year,
|
|
388
|
+
"gdpPercap": 800 * (1.03 + 0.01 * i) ** (year - 1952),
|
|
389
|
+
"lifeExp": 45 + 0.35 * (year - 1952) + 2 * i,
|
|
390
|
+
"pop": 2e7 * (1.02 + 0.002 * i) ** (year - 1952)}
|
|
391
|
+
for i, (country, continent) in enumerate(countries.items())
|
|
392
|
+
for year in range(1952, 2008, 5)
|
|
393
|
+
])
|
|
394
|
+
|
|
395
|
+
(ggplot(gapminder, aes(x="gdpPercap", y="lifeExp", size="pop", colour="continent", group="country"))
|
|
383
396
|
+ geom_point() + scale_x_log10() + transition_time("year") + labs(title="{frame_time}"))
|
|
384
397
|
```
|
|
385
398
|
|
|
399
|
+
`{frame_time}` in a title shows the current year as the animation plays.
|
|
400
|
+
|
|
386
401
|
The viewer interpolates between frames in the browser, with play, pause,
|
|
387
402
|
scrubbing, speed, and video recording.
|
|
388
403
|
|
|
@@ -437,7 +452,9 @@ tree) is shortened to twice its width so it does not become a thin column;
|
|
|
437
452
|
- **SolveIt** draws figures inline and hides their HTML from the model's
|
|
438
453
|
context (`autohide(False)` to opt out).
|
|
439
454
|
- **VS Code notebooks** block WebGL in output cells, so plot3 opens figures
|
|
440
|
-
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode).
|
|
455
|
+
in your browser (`PLOT3_DISPLAY=browser|iframe` to force a mode). The page
|
|
456
|
+
is written to `.plot3_preview/latest.html` in the notebook's folder, or to
|
|
457
|
+
the system temp folder when that folder is read-only.
|
|
441
458
|
- **CRAFT / `%gpu`**: the same `ggplot(...)` code runs on the remote kernel;
|
|
442
459
|
stats run where the data lives and only a compact payload comes back.
|
|
443
460
|
|
|
@@ -639,3 +639,35 @@ def test_slider_refuses_a_grid_that_is_too_large():
|
|
|
639
639
|
+ geom_function("z = a sin(x) cos(y)")
|
|
640
640
|
+ slider(a=(0, 1), k=(1, 2), steps=25)
|
|
641
641
|
)
|
|
642
|
+
|
|
643
|
+
|
|
644
|
+
def test_frame_label_stays_in_place_while_it_changes():
|
|
645
|
+
"""The big frame label uses one size, a fixed box, and tabular digits."""
|
|
646
|
+
from plot3 import geom_function, ggplot, transition_time
|
|
647
|
+
|
|
648
|
+
doc = (
|
|
649
|
+
ggplot() + geom_function("y = sin(x - t)") + transition_time(t=(-5, 12))
|
|
650
|
+
).html()
|
|
651
|
+
assert "font-variant-numeric:tabular-nums" in doc.split("#year{", 1)[1].split("}", 1)[0]
|
|
652
|
+
assert "steadyFrameLabel(yearEl, nF)" in doc
|
|
653
|
+
# The size is chosen once per animation, not per frame.
|
|
654
|
+
assert "lab.length > 8" not in doc
|
|
655
|
+
|
|
656
|
+
|
|
657
|
+
def test_swept_parameter_is_not_frozen_into_labels():
|
|
658
|
+
"""transition_time(t=...) must not label curves with the first frame's t."""
|
|
659
|
+
import json
|
|
660
|
+
|
|
661
|
+
from plot3 import geom_function, ggplot, transition_time
|
|
662
|
+
from plot3.build import build_spec
|
|
663
|
+
|
|
664
|
+
spec = json.dumps(
|
|
665
|
+
build_spec(
|
|
666
|
+
ggplot()
|
|
667
|
+
+ geom_function("y = sin(x - t)")
|
|
668
|
+
+ geom_function("y = a cos(x - t)", a=2)
|
|
669
|
+
+ transition_time(t=(0, 6.28))
|
|
670
|
+
)
|
|
671
|
+
)
|
|
672
|
+
assert "t = 0" not in spec
|
|
673
|
+
assert "(a = 2)" in spec # fixed coefficients still show their value
|
|
@@ -114,3 +114,29 @@ def test_freeze_faceted_raises(cars):
|
|
|
114
114
|
fig = ggplot(cars, aes(x="wt", y="mpg")) + geom_point() + facet_wrap("cyl")
|
|
115
115
|
with pytest.raises(ValueError, match="facet"):
|
|
116
116
|
fig.freeze()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def test_browser_preview_falls_back_when_the_folder_is_read_only(tmp_path, monkeypatch):
|
|
120
|
+
"""A kernel started in a read-only folder ("/" in VS Code) still shows plots."""
|
|
121
|
+
import importlib
|
|
122
|
+
import os
|
|
123
|
+
import tempfile
|
|
124
|
+
|
|
125
|
+
gg = importlib.import_module("plot3.ggplot")
|
|
126
|
+
|
|
127
|
+
if os.name == "nt" or os.geteuid() == 0:
|
|
128
|
+
pytest.skip("needs a folder the test user cannot write to")
|
|
129
|
+
locked = tmp_path / "locked"
|
|
130
|
+
locked.mkdir()
|
|
131
|
+
locked.chmod(0o555)
|
|
132
|
+
monkeypatch.chdir(locked)
|
|
133
|
+
monkeypatch.setattr(gg.webbrowser, "open", lambda *_a, **_k: None)
|
|
134
|
+
monkeypatch.setattr(tempfile, "tempdir", str(tmp_path / "tmp"))
|
|
135
|
+
(tmp_path / "tmp").mkdir()
|
|
136
|
+
fig = ggplot({"x": [1, 2], "y": [3, 4]}, aes(x="x", y="y")) + geom_point()
|
|
137
|
+
try:
|
|
138
|
+
out = fig.show(browser=True)
|
|
139
|
+
finally:
|
|
140
|
+
locked.chmod(0o755)
|
|
141
|
+
assert out == (tmp_path / "tmp" / "plot3_preview" / "latest.html").resolve()
|
|
142
|
+
assert out.read_text(encoding="utf-8").startswith("<!")
|
plot3-0.4.0/plot3/__version__.py
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.4.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
|