plot3 0.6.7__tar.gz → 0.6.8__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.
Files changed (81) hide show
  1. {plot3-0.6.7 → plot3-0.6.8}/PKG-INFO +1 -1
  2. plot3-0.6.8/plot3/__version__.py +1 -0
  3. {plot3-0.6.7 → plot3-0.6.8}/plot3/build.py +34 -5
  4. {plot3-0.6.7 → plot3-0.6.8}/plot3/viewer.py +35 -5
  5. {plot3-0.6.7 → plot3-0.6.8}/plot3.egg-info/PKG-INFO +1 -1
  6. {plot3-0.6.7 → plot3-0.6.8}/tests/test_math_layers.py +34 -0
  7. plot3-0.6.7/plot3/__version__.py +0 -1
  8. {plot3-0.6.7 → plot3-0.6.8}/LICENSE +0 -0
  9. {plot3-0.6.7 → plot3-0.6.8}/README.md +0 -0
  10. {plot3-0.6.7 → plot3-0.6.8}/plot3/__init__.py +0 -0
  11. {plot3-0.6.7 → plot3-0.6.8}/plot3/aesexpr.py +0 -0
  12. {plot3-0.6.7 → plot3-0.6.8}/plot3/calculus.py +0 -0
  13. {plot3-0.6.7 → plot3-0.6.8}/plot3/compose.py +0 -0
  14. {plot3-0.6.7 → plot3-0.6.8}/plot3/contour.py +0 -0
  15. {plot3-0.6.7 → plot3-0.6.8}/plot3/craft.py +0 -0
  16. {plot3-0.6.7 → plot3-0.6.8}/plot3/encode.py +0 -0
  17. {plot3-0.6.7 → plot3-0.6.8}/plot3/expr.py +0 -0
  18. {plot3-0.6.7 → plot3-0.6.8}/plot3/flip.py +0 -0
  19. {plot3-0.6.7 → plot3-0.6.8}/plot3/function.py +0 -0
  20. {plot3-0.6.7 → plot3-0.6.8}/plot3/geoms.py +0 -0
  21. {plot3-0.6.7 → plot3-0.6.8}/plot3/ggplot.py +0 -0
  22. {plot3-0.6.7 → plot3-0.6.8}/plot3/io.py +0 -0
  23. {plot3-0.6.7 → plot3-0.6.8}/plot3/jupyter.py +0 -0
  24. {plot3-0.6.7 → plot3-0.6.8}/plot3/latexin.py +0 -0
  25. {plot3-0.6.7 → plot3-0.6.8}/plot3/masking.py +0 -0
  26. {plot3-0.6.7 → plot3-0.6.8}/plot3/mathtext.py +0 -0
  27. {plot3-0.6.7 → plot3-0.6.8}/plot3/payload.py +0 -0
  28. {plot3-0.6.7 → plot3-0.6.8}/plot3/remote.py +0 -0
  29. {plot3-0.6.7 → plot3-0.6.8}/plot3/scales.py +0 -0
  30. {plot3-0.6.7 → plot3-0.6.8}/plot3/scaling.py +0 -0
  31. {plot3-0.6.7 → plot3-0.6.8}/plot3/special.py +0 -0
  32. {plot3-0.6.7 → plot3-0.6.8}/plot3/stat2d.py +0 -0
  33. {plot3-0.6.7 → plot3-0.6.8}/plot3/static.py +0 -0
  34. {plot3-0.6.7 → plot3-0.6.8}/plot3/stats3d.py +0 -0
  35. {plot3-0.6.7 → plot3-0.6.8}/plot3/table.py +0 -0
  36. {plot3-0.6.7 → plot3-0.6.8}/plot3/themes.py +0 -0
  37. {plot3-0.6.7 → plot3-0.6.8}/plot3.egg-info/SOURCES.txt +0 -0
  38. {plot3-0.6.7 → plot3-0.6.8}/plot3.egg-info/dependency_links.txt +0 -0
  39. {plot3-0.6.7 → plot3-0.6.8}/plot3.egg-info/requires.txt +0 -0
  40. {plot3-0.6.7 → plot3-0.6.8}/plot3.egg-info/top_level.txt +0 -0
  41. {plot3-0.6.7 → plot3-0.6.8}/pyproject.toml +0 -0
  42. {plot3-0.6.7 → plot3-0.6.8}/setup.cfg +0 -0
  43. {plot3-0.6.7 → plot3-0.6.8}/tests/test_2d_density.py +0 -0
  44. {plot3-0.6.7 → plot3-0.6.8}/tests/test_3d.py +0 -0
  45. {plot3-0.6.7 → plot3-0.6.8}/tests/test_animation.py +0 -0
  46. {plot3-0.6.7 → plot3-0.6.8}/tests/test_annotate.py +0 -0
  47. {plot3-0.6.7 → plot3-0.6.8}/tests/test_api_public.py +0 -0
  48. {plot3-0.6.7 → plot3-0.6.8}/tests/test_array_backend.py +0 -0
  49. {plot3-0.6.7 → plot3-0.6.8}/tests/test_arrow.py +0 -0
  50. {plot3-0.6.7 → plot3-0.6.8}/tests/test_build_contract.py +0 -0
  51. {plot3-0.6.7 → plot3-0.6.8}/tests/test_contour.py +0 -0
  52. {plot3-0.6.7 → plot3-0.6.8}/tests/test_everyday_ggplot2.py +0 -0
  53. {plot3-0.6.7 → plot3-0.6.8}/tests/test_expr.py +0 -0
  54. {plot3-0.6.7 → plot3-0.6.8}/tests/test_facets_compose.py +0 -0
  55. {plot3-0.6.7 → plot3-0.6.8}/tests/test_geom_function.py +0 -0
  56. {plot3-0.6.7 → plot3-0.6.8}/tests/test_geoms_ds.py +0 -0
  57. {plot3-0.6.7 → plot3-0.6.8}/tests/test_ggplot2_extras2.py +0 -0
  58. {plot3-0.6.7 → plot3-0.6.8}/tests/test_ggplot2_parity.py +0 -0
  59. {plot3-0.6.7 → plot3-0.6.8}/tests/test_ggplot_extras.py +0 -0
  60. {plot3-0.6.7 → plot3-0.6.8}/tests/test_grammar.py +0 -0
  61. {plot3-0.6.7 → plot3-0.6.8}/tests/test_latex_input.py +0 -0
  62. {plot3-0.6.7 → plot3-0.6.8}/tests/test_lidar_scene.py +0 -0
  63. {plot3-0.6.7 → plot3-0.6.8}/tests/test_masking.py +0 -0
  64. {plot3-0.6.7 → plot3-0.6.8}/tests/test_mathtext.py +0 -0
  65. {plot3-0.6.7 → plot3-0.6.8}/tests/test_payload.py +0 -0
  66. {plot3-0.6.7 → plot3-0.6.8}/tests/test_payload_display.py +0 -0
  67. {plot3-0.6.7 → plot3-0.6.8}/tests/test_point_cloud.py +0 -0
  68. {plot3-0.6.7 → plot3-0.6.8}/tests/test_r_oracle_parity.py +0 -0
  69. {plot3-0.6.7 → plot3-0.6.8}/tests/test_readme.py +0 -0
  70. {plot3-0.6.7 → plot3-0.6.8}/tests/test_remote_bridge.py +0 -0
  71. {plot3-0.6.7 → plot3-0.6.8}/tests/test_review_fixes.py +0 -0
  72. {plot3-0.6.7 → plot3-0.6.8}/tests/test_save_menu.py +0 -0
  73. {plot3-0.6.7 → plot3-0.6.8}/tests/test_scales_api.py +0 -0
  74. {plot3-0.6.7 → plot3-0.6.8}/tests/test_scales_themes.py +0 -0
  75. {plot3-0.6.7 → plot3-0.6.8}/tests/test_special.py +0 -0
  76. {plot3-0.6.7 → plot3-0.6.8}/tests/test_stat2d.py +0 -0
  77. {plot3-0.6.7 → plot3-0.6.8}/tests/test_static_save.py +0 -0
  78. {plot3-0.6.7 → plot3-0.6.8}/tests/test_stats_semantic.py +0 -0
  79. {plot3-0.6.7 → plot3-0.6.8}/tests/test_step3_geoms.py +0 -0
  80. {plot3-0.6.7 → plot3-0.6.8}/tests/test_table_backends.py +0 -0
  81. {plot3-0.6.7 → plot3-0.6.8}/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.7
3
+ Version: 0.6.8
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
@@ -0,0 +1 @@
1
+ __version__ = "0.6.8"
@@ -1726,7 +1726,8 @@ def _formula_legend(legend, formula_geoms, has_title: bool):
1726
1726
  """Tidy the legend rows that function curves add.
1727
1727
 
1728
1728
  * One curve under a title of your own: the title already names it, so
1729
- the curve gets no row (ggplot2 draws no legend for an unmapped layer).
1729
+ the curve gets no row (ggplot2 draws no legend for an unmapped layer),
1730
+ unless a tangent, derivative or other row sits beside it.
1730
1731
  * Several curves of one formula (a loop over ``a``): the formula moves
1731
1732
  to the legend title and each row lists only its values.
1732
1733
 
@@ -1744,7 +1745,7 @@ def _formula_legend(legend, formula_geoms, has_title: bool):
1744
1745
  return legend, None
1745
1746
  if has_title and len(formula_geoms) == 1:
1746
1747
  kept = [entry for entry in legend if not any(entry is p for p in primary)]
1747
- return kept or None, None
1748
+ return (legend if kept else None), None
1748
1749
  if len(primary) < 2 or len(primary) != len(formula_geoms):
1749
1750
  return legend, None
1750
1751
  geoms = [entry["_geom"] for entry in primary]
@@ -2871,6 +2872,28 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
2871
2872
  source = getattr(geom, "_inherit_from", None)
2872
2873
  if source and source in labeled:
2873
2874
  geom.const_color = labeled[source]
2875
+ # Clicking a legend row hides every layer it names: a tangent's line and
2876
+ # its point, a curve and the area shaded under it.
2877
+ legend_keys: dict[int, str] = {}
2878
+ key_of: dict[str, str] = {}
2879
+ for geom, _mapped in resolved:
2880
+ label = getattr(geom, "_legend_label", None)
2881
+ if not label or geom.const_color is None:
2882
+ continue
2883
+ color_key = getattr(geom, "_color_key", None)
2884
+ key = key_of.get(str(label)) or str(color_key or label)
2885
+ legend_keys[id(geom)] = key
2886
+ key_of.setdefault(str(label), key)
2887
+ if color_key:
2888
+ key_of.setdefault(str(color_key), key)
2889
+ for geom, _mapped in resolved:
2890
+ source = getattr(geom, "_inherit_from", None)
2891
+ if id(geom) not in legend_keys and source and str(source) in key_of:
2892
+ legend_keys[id(geom)] = key_of[str(source)]
2893
+ # Stable short keys, so the same figure always builds the same spec.
2894
+ stable: dict[str, str] = {}
2895
+ for gid, key in legend_keys.items():
2896
+ legend_keys[gid] = stable.setdefault(key, f"k{len(stable)}")
2874
2897
 
2875
2898
  payloads: list[tuple[str, str]] = []
2876
2899
  layer_specs = []
@@ -3362,6 +3385,8 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
3362
3385
  if tip_latex:
3363
3386
  tip["latex"] = str(tip_latex)
3364
3387
  spec_l["tip"] = tip
3388
+ if id(geom) in legend_keys:
3389
+ spec_l["lk"] = legend_keys[id(geom)]
3365
3390
  layer_specs.append(spec_l)
3366
3391
 
3367
3392
  # color spec + legend
@@ -3402,6 +3427,8 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
3402
3427
  if not label or not geom.const_color:
3403
3428
  continue
3404
3429
  entry = {"label": str(label), "color": geom.const_color}
3430
+ if id(geom) in legend_keys:
3431
+ entry["lk"] = legend_keys[id(geom)]
3405
3432
  if getattr(geom, "_is_formula", False):
3406
3433
  entry["formula"] = True
3407
3434
  entry["_geom"] = geom
@@ -3439,7 +3466,9 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
3439
3466
  raw_title = g.labs.get("title") or ""
3440
3467
  # One function and no title of your own: the formula is the title.
3441
3468
  # A shaded area, a tangent, or roots stay in the legend when they
3442
- # have a label of their own. The curve's row would only repeat the title.
3469
+ # have a label of their own. The curve keeps its row beside them, so the
3470
+ # legend says which line is the curve; alone, its row would only repeat
3471
+ # the title.
3443
3472
  if not str(raw_title).strip() and len(formula_geoms) == 1:
3444
3473
  shown = formula_geoms[0]
3445
3474
  # Symbolic formula, not the value list. That list stays in the legend
@@ -3456,11 +3485,11 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
3456
3485
  elif title_latex:
3457
3486
  labs_math["title"] = [{"text": title, "latex": str(title_latex)}]
3458
3487
  if legend:
3459
- legend = [
3488
+ others = [
3460
3489
  entry for entry in legend
3461
3490
  if not (entry.get("formula") and entry.get("label") == title)
3462
3491
  ]
3463
- if not legend:
3492
+ if not others:
3464
3493
  legend = None
3465
3494
  else:
3466
3495
  title = _take("title", raw_title)
@@ -338,8 +338,14 @@ function regCat(ci, obj) {
338
338
  if (!catObjs.has(ci)) catObjs.set(ci, []);
339
339
  catObjs.get(ci).push(obj);
340
340
  }
341
+ // legend click on a formula row (tangent, derivative, curve): row key ->
342
+ // every three.js object its layers drew, e.g. a tangent's line and its point
343
+ const hiddenKeys = new Set();
344
+ const keyObjs = new Map();
345
+ let currentLK = null; // the key of the layer being drawn, while it draws
346
+ function layerHidden(L) { return L.lk != null && hiddenKeys.has(L.lk); }
341
347
  let redraw = () => {}; // 2D assigns its draw(); 3D renders continuously
342
- window.__plot3 = { hiddenCats, catObjs };
348
+ window.__plot3 = { hiddenCats, catObjs, hiddenKeys, keyObjs };
343
349
  function showLegendBox() {
344
350
  // theme(legend_position="none"), and all but one panel of a facet_grid.
345
351
  if (S.legendPosition === 'none') return;
@@ -414,15 +420,23 @@ if (S.legend) {
414
420
  legEl.innerHTML = (S.labs.color ? '<b style="color:'+T.ink+'">' +
415
421
  richLabel('color', S.labs.color) + '</b>' : '') +
416
422
  S.legend.map((e, i) => '<div class="lg-e" data-ci="' + (e.ci != null ? e.ci : i) +
423
+ (e.lk != null ? '" data-lk="' + plot3Esc(e.lk) : '') +
417
424
  '">' + keyHTML(e, e.color) +
418
425
  legendLabel(e) + '</div>').join('') + barHTML() + szHTML;
419
426
  legEl.addEventListener('click', ev => {
420
427
  const row = ev.target.closest('.lg-e');
421
428
  if (!row) return;
422
- const ci = +row.dataset.ci;
423
- if (hiddenCats.has(ci)) hiddenCats.delete(ci); else hiddenCats.add(ci);
424
- row.style.opacity = hiddenCats.has(ci) ? 0.35 : 1;
425
- for (const o of (catObjs.get(ci) || [])) o.visible = !hiddenCats.has(ci);
429
+ const lk = row.dataset.lk;
430
+ if (lk != null) {
431
+ if (hiddenKeys.has(lk)) hiddenKeys.delete(lk); else hiddenKeys.add(lk);
432
+ row.style.opacity = hiddenKeys.has(lk) ? 0.35 : 1;
433
+ for (const o of (keyObjs.get(lk) || [])) o.visible = !hiddenKeys.has(lk);
434
+ } else {
435
+ const ci = +row.dataset.ci;
436
+ if (hiddenCats.has(ci)) hiddenCats.delete(ci); else hiddenCats.add(ci);
437
+ row.style.opacity = hiddenCats.has(ci) ? 0.35 : 1;
438
+ for (const o of (catObjs.get(ci) || [])) o.visible = !hiddenCats.has(ci);
439
+ }
426
440
  if (window.__plot3.afterLegend) window.__plot3.afterLegend();
427
441
  tip.style.display = 'none';
428
442
  redraw();
@@ -607,6 +621,16 @@ renderer.setPixelRatio(window.devicePixelRatio);
607
621
  renderer.setClearColor(0x000000, 0);
608
622
  host.appendChild(renderer.domElement);
609
623
  const scene = new THREE.Scene();
624
+ {
625
+ const add0 = scene.add.bind(scene);
626
+ scene.add = (...objs) => {
627
+ if (currentLK != null) {
628
+ if (!keyObjs.has(currentLK)) keyObjs.set(currentLK, []);
629
+ keyObjs.get(currentLK).push(...objs);
630
+ }
631
+ return add0(...objs);
632
+ };
633
+ }
610
634
  const lineMats = [];
611
635
 
612
636
  // Point symbols (aes(shape=) / geom_point(shape=)) as white sprites tinted
@@ -1614,6 +1638,7 @@ if (!S.is3d) {
1614
1638
  return addToScene(...objs);
1615
1639
  };
1616
1640
  for (const L of S.layers) {
1641
+ currentLK = L.lk != null ? L.lk : null;
1617
1642
  if (L.blank) continue; // geom_blank: on the scales only
1618
1643
  paintOrder += 1;
1619
1644
  const n = L.n;
@@ -1937,6 +1962,7 @@ if (!S.is3d) {
1937
1962
  });
1938
1963
  }
1939
1964
  }
1965
+ currentLK = null;
1940
1966
  scene.add = addToScene;
1941
1967
 
1942
1968
  function drawAxes() {
@@ -2267,6 +2293,7 @@ if (!S.is3d) {
2267
2293
  if (!best || score < best.score) best = { score, sx, sy, html, hold: !!hold };
2268
2294
  }
2269
2295
  for (const L of S.layers) {
2296
+ if (layerHidden(L)) continue;
2270
2297
  if (L.kind === 'col') {
2271
2298
  // Full bar rectangle (not just the top-center point).
2272
2299
  const hw = (L.width || 0.08) * 0.5;
@@ -2445,6 +2472,7 @@ if (!S.is3d) {
2445
2472
  }
2446
2473
 
2447
2474
  for (const L of S.layers) {
2475
+ currentLK = L.lk != null ? L.lk : null;
2448
2476
  if (L.blank) continue; // geom_blank: on the scales only
2449
2477
  const n = L.n;
2450
2478
  const cols = layerColors(L, hex2rgb(T.cat[0]));
@@ -2528,6 +2556,7 @@ if (!S.is3d) {
2528
2556
  }
2529
2557
  }
2530
2558
  }
2559
+ currentLK = null;
2531
2560
 
2532
2561
  // Axes box as matplotlib and plotly draw it: the three far walls with
2533
2562
  // grid lines, refreshed as the camera orbits. The edges at the near
@@ -2706,6 +2735,7 @@ if (!S.is3d) {
2706
2735
  let best = null, bestD = Infinity;
2707
2736
  const maxScan = 80000;
2708
2737
  for (const { L } of pickLayers) {
2738
+ if (layerHidden(L)) continue;
2709
2739
  const n = L._stepN != null ? L._stepN : L.n;
2710
2740
  const step = n > maxScan ? Math.ceil(n / maxScan) : 1;
2711
2741
  const xs = L._x || L.x.data;
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.6.7
3
+ Version: 0.6.8
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
@@ -13,6 +13,7 @@ from plot3 import (
13
13
  geom_vector_field,
14
14
  ggplot,
15
15
  ggsave,
16
+ labs,
16
17
  slider,
17
18
  tangent,
18
19
  )
@@ -134,6 +135,39 @@ def test_tangent_at_one_has_slope_two():
134
135
  assert any(entry["label"] == "tangent" for entry in spec["legend"])
135
136
 
136
137
 
138
+ def test_legend_rows_name_the_layers_a_click_hides():
139
+ # The viewer hides every layer whose key matches the clicked row: the
140
+ # tangent row covers its line and the point at x = 1.
141
+ spec, _payloads = build_spec(
142
+ ggplot() + geom_function("y = x^3 - 3x") + tangent(at=1) + derivative()
143
+ )
144
+ keys = {entry["label"]: entry["lk"] for entry in spec["legend"]}
145
+ tangent_layers = [layer for layer in spec["layers"] if layer.get("lk") == keys["tangent"]]
146
+ assert sorted(layer["kind"] for layer in tangent_layers) == ["line", "point"]
147
+ assert len(set(keys.values())) == 3
148
+ # Stable keys: the same figure builds the same spec.
149
+ again, _payloads = build_spec(
150
+ ggplot() + geom_function("y = x^3 - 3x") + tangent(at=1) + derivative()
151
+ )
152
+ assert [entry["lk"] for entry in again["legend"]] == list(keys.values())
153
+
154
+
155
+ def test_curve_keeps_its_row_beside_a_tangent_and_derivative():
156
+ spec, _payloads = build_spec(
157
+ ggplot() + geom_function("y = x^3 - 3x") + tangent(at=1) + derivative()
158
+ )
159
+ labels = [entry["label"] for entry in spec["legend"]]
160
+ assert labels[0] == spec["labs"]["title"]
161
+ assert "tangent" in labels and len(labels) == 3
162
+ titled, _payloads = build_spec(
163
+ ggplot() + geom_function("y = x^2") + tangent(at=1) + labs(title="Slope")
164
+ )
165
+ assert len(titled["legend"]) == 2
166
+ # Alone, the curve's row would only repeat the title.
167
+ alone, _payloads = build_spec(ggplot() + geom_function("y = x^2"))
168
+ assert alone["legend"] is None
169
+
170
+
137
171
  def test_derivative_is_the_slope_curve():
138
172
  layers = _layers(ggplot() + geom_function("y = x^2", xlim=(-2, 2), n=11) + derivative())
139
173
  curve = next(
@@ -1 +0,0 @@
1
- __version__ = "0.6.7"
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