plot3 0.4.0__py3-none-any.whl → 0.4.2__py3-none-any.whl

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/__version__.py CHANGED
@@ -1 +1 @@
1
- __version__ = "0.4.0"
1
+ __version__ = "0.4.2"
plot3/function.py CHANGED
@@ -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
- return parse_formula(source, params, defer_missing=True)
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:
plot3/ggplot.py CHANGED
@@ -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
- out_dir.mkdir(parents=True, exist_ok=True)
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)
plot3/viewer.py CHANGED
@@ -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.0
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 notest
382
- (ggplot(gapminder, aes(x="gdp", y="life", size="pop", colour="continent", group="country"))
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
 
@@ -1,5 +1,5 @@
1
1
  plot3/__init__.py,sha256=-jbV46XmzHZJzv7jrfi50on5Xxs1PJ8U3aV7bbSm1VA,6016
2
- plot3/__version__.py,sha256=42STGor_9nKYXumfeV5tiyD_M8VdcddX7CEexmibPBk,22
2
+ plot3/__version__.py,sha256=6hfVa12Q-nXyUEXr6SyKpqPEDJW6vlRHyPxlA27PfTs,22
3
3
  plot3/aesexpr.py,sha256=5GbKRX0FZFpDXCDzsZzZW32MKK3uI24Y9nabNJO5RRc,10449
4
4
  plot3/build.py,sha256=ilabpra3FkzL0vBMNx-yhQXOVwJxOlOwakWvWR3gqWM,167841
5
5
  plot3/calculus.py,sha256=QONvIZPi4QMAiTUUw3sHe8G3yicgxaMvZN3JLoLcOi0,42272
@@ -9,9 +9,9 @@ plot3/craft.py,sha256=gZMIrLyY0BJhLNJkBUuP7hEKnkvg7_8M5jITsIFwykA,4639
9
9
  plot3/encode.py,sha256=1t1XQwbPV-vKJtTAKkJzr2Kc8umfjsuvffVxyICwcpc,2309
10
10
  plot3/expr.py,sha256=HMePIjqT4kGfRNhYkSyAfKz_MuGLvQUlZlp_ZNffgqQ,54070
11
11
  plot3/flip.py,sha256=Ox9yxjYzbEpm96raN_qupKru__hEfSMkCZxv01vGOuk,9724
12
- plot3/function.py,sha256=xhU0UGTDlA9nkorOI3stkbHlMwmYWs69Dlrk_CKmqGQ,48377
12
+ plot3/function.py,sha256=LYKLPEIdoEp_SnoNYNEXMcV3Mkoh4NyMT1FWyERhZ3c,48749
13
13
  plot3/geoms.py,sha256=O36fCt2nQilbPc3BmD3lEEGFhZa7oX2m4B2cyEReORI,89491
14
- plot3/ggplot.py,sha256=HEoq5gAtJdy_E_hnOMZWwknKQVz43nTlXn_2Is4p4Tk,26234
14
+ plot3/ggplot.py,sha256=rC--7Dk0ULV95zzeqzQc_XOZZOT0NvYPO3kw8ovJLU8,26573
15
15
  plot3/io.py,sha256=FirkYHP4r1vaG10dmELCaa7JUwvlc1kwwKtp4zXfXHg,2649
16
16
  plot3/jupyter.py,sha256=6EgHacP3eBQ9JGM8nXCNgLosA7tTDFjR-gWij1-oaQM,17790
17
17
  plot3/latexin.py,sha256=RrXF6UClHLmsmRH8qCkFOCSgFJxrqPeyzik9t7Ce7iE,21007
@@ -27,9 +27,9 @@ plot3/static.py,sha256=KrAWbocyejKpi27VEKlJfSTZYcp5mC0IZc_1lDqeH-A,150660
27
27
  plot3/stats3d.py,sha256=mRp6n648_yIP7L1e4eh68lzqbT0rwaPOxcAucuEZX4c,16693
28
28
  plot3/table.py,sha256=v4G6NgbEPKv6RakSHLnvfdkWNAaApEgel9be0PzPUMA,26033
29
29
  plot3/themes.py,sha256=mK9zWm4nRLSDbH2xDJqdrRnnkb9bGUWByyI-x9ZupdU,5145
30
- plot3/viewer.py,sha256=LiIkh6lsGmm-veV7ZFCOlLqnCiuE_MxznJp8tbWA4fs,140117
31
- plot3-0.4.0.dist-info/licenses/LICENSE,sha256=OPphTrXUbMt4GNrsK8juVty13iThKFPIPnu_nh4UlAk,1081
32
- plot3-0.4.0.dist-info/METADATA,sha256=Mvr4DOm5vPhtjL1JuKZ7J6SUGTOjsKrpVdA9SpwsPo8,24408
33
- plot3-0.4.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
34
- plot3-0.4.0.dist-info/top_level.txt,sha256=wFHSJTzHoaJCrffpONuI8hayEblnTzRMi2y3qCcKzD0,6
35
- plot3-0.4.0.dist-info/RECORD,,
30
+ plot3/viewer.py,sha256=tKhBkO7IJpDVs2_NdM87F1O2EVtuAGst1P7bI8gtr_A,141046
31
+ plot3-0.4.2.dist-info/licenses/LICENSE,sha256=OPphTrXUbMt4GNrsK8juVty13iThKFPIPnu_nh4UlAk,1081
32
+ plot3-0.4.2.dist-info/METADATA,sha256=CpbQDHQN_LVTjRr60zTEpRIOSAUmLqigX-Jac-DAZ_o,25295
33
+ plot3-0.4.2.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
34
+ plot3-0.4.2.dist-info/top_level.txt,sha256=wFHSJTzHoaJCrffpONuI8hayEblnTzRMi2y3qCcKzD0,6
35
+ plot3-0.4.2.dist-info/RECORD,,
File without changes