plot3 0.6.6__tar.gz → 0.6.7__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.6 → plot3-0.6.7}/PKG-INFO +1 -1
  2. plot3-0.6.7/plot3/__version__.py +1 -0
  3. {plot3-0.6.6 → plot3-0.6.7}/plot3/build.py +32 -21
  4. {plot3-0.6.6 → plot3-0.6.7}/plot3/function.py +10 -0
  5. {plot3-0.6.6 → plot3-0.6.7}/plot3/geoms.py +8 -5
  6. {plot3-0.6.6 → plot3-0.6.7}/plot3/ggplot.py +29 -2
  7. {plot3-0.6.6 → plot3-0.6.7}/plot3.egg-info/PKG-INFO +1 -1
  8. {plot3-0.6.6 → plot3-0.6.7}/tests/test_animation.py +30 -0
  9. plot3-0.6.6/plot3/__version__.py +0 -1
  10. {plot3-0.6.6 → plot3-0.6.7}/LICENSE +0 -0
  11. {plot3-0.6.6 → plot3-0.6.7}/README.md +0 -0
  12. {plot3-0.6.6 → plot3-0.6.7}/plot3/__init__.py +0 -0
  13. {plot3-0.6.6 → plot3-0.6.7}/plot3/aesexpr.py +0 -0
  14. {plot3-0.6.6 → plot3-0.6.7}/plot3/calculus.py +0 -0
  15. {plot3-0.6.6 → plot3-0.6.7}/plot3/compose.py +0 -0
  16. {plot3-0.6.6 → plot3-0.6.7}/plot3/contour.py +0 -0
  17. {plot3-0.6.6 → plot3-0.6.7}/plot3/craft.py +0 -0
  18. {plot3-0.6.6 → plot3-0.6.7}/plot3/encode.py +0 -0
  19. {plot3-0.6.6 → plot3-0.6.7}/plot3/expr.py +0 -0
  20. {plot3-0.6.6 → plot3-0.6.7}/plot3/flip.py +0 -0
  21. {plot3-0.6.6 → plot3-0.6.7}/plot3/io.py +0 -0
  22. {plot3-0.6.6 → plot3-0.6.7}/plot3/jupyter.py +0 -0
  23. {plot3-0.6.6 → plot3-0.6.7}/plot3/latexin.py +0 -0
  24. {plot3-0.6.6 → plot3-0.6.7}/plot3/masking.py +0 -0
  25. {plot3-0.6.6 → plot3-0.6.7}/plot3/mathtext.py +0 -0
  26. {plot3-0.6.6 → plot3-0.6.7}/plot3/payload.py +0 -0
  27. {plot3-0.6.6 → plot3-0.6.7}/plot3/remote.py +0 -0
  28. {plot3-0.6.6 → plot3-0.6.7}/plot3/scales.py +0 -0
  29. {plot3-0.6.6 → plot3-0.6.7}/plot3/scaling.py +0 -0
  30. {plot3-0.6.6 → plot3-0.6.7}/plot3/special.py +0 -0
  31. {plot3-0.6.6 → plot3-0.6.7}/plot3/stat2d.py +0 -0
  32. {plot3-0.6.6 → plot3-0.6.7}/plot3/static.py +0 -0
  33. {plot3-0.6.6 → plot3-0.6.7}/plot3/stats3d.py +0 -0
  34. {plot3-0.6.6 → plot3-0.6.7}/plot3/table.py +0 -0
  35. {plot3-0.6.6 → plot3-0.6.7}/plot3/themes.py +0 -0
  36. {plot3-0.6.6 → plot3-0.6.7}/plot3/viewer.py +0 -0
  37. {plot3-0.6.6 → plot3-0.6.7}/plot3.egg-info/SOURCES.txt +0 -0
  38. {plot3-0.6.6 → plot3-0.6.7}/plot3.egg-info/dependency_links.txt +0 -0
  39. {plot3-0.6.6 → plot3-0.6.7}/plot3.egg-info/requires.txt +0 -0
  40. {plot3-0.6.6 → plot3-0.6.7}/plot3.egg-info/top_level.txt +0 -0
  41. {plot3-0.6.6 → plot3-0.6.7}/pyproject.toml +0 -0
  42. {plot3-0.6.6 → plot3-0.6.7}/setup.cfg +0 -0
  43. {plot3-0.6.6 → plot3-0.6.7}/tests/test_2d_density.py +0 -0
  44. {plot3-0.6.6 → plot3-0.6.7}/tests/test_3d.py +0 -0
  45. {plot3-0.6.6 → plot3-0.6.7}/tests/test_annotate.py +0 -0
  46. {plot3-0.6.6 → plot3-0.6.7}/tests/test_api_public.py +0 -0
  47. {plot3-0.6.6 → plot3-0.6.7}/tests/test_array_backend.py +0 -0
  48. {plot3-0.6.6 → plot3-0.6.7}/tests/test_arrow.py +0 -0
  49. {plot3-0.6.6 → plot3-0.6.7}/tests/test_build_contract.py +0 -0
  50. {plot3-0.6.6 → plot3-0.6.7}/tests/test_contour.py +0 -0
  51. {plot3-0.6.6 → plot3-0.6.7}/tests/test_everyday_ggplot2.py +0 -0
  52. {plot3-0.6.6 → plot3-0.6.7}/tests/test_expr.py +0 -0
  53. {plot3-0.6.6 → plot3-0.6.7}/tests/test_facets_compose.py +0 -0
  54. {plot3-0.6.6 → plot3-0.6.7}/tests/test_geom_function.py +0 -0
  55. {plot3-0.6.6 → plot3-0.6.7}/tests/test_geoms_ds.py +0 -0
  56. {plot3-0.6.6 → plot3-0.6.7}/tests/test_ggplot2_extras2.py +0 -0
  57. {plot3-0.6.6 → plot3-0.6.7}/tests/test_ggplot2_parity.py +0 -0
  58. {plot3-0.6.6 → plot3-0.6.7}/tests/test_ggplot_extras.py +0 -0
  59. {plot3-0.6.6 → plot3-0.6.7}/tests/test_grammar.py +0 -0
  60. {plot3-0.6.6 → plot3-0.6.7}/tests/test_latex_input.py +0 -0
  61. {plot3-0.6.6 → plot3-0.6.7}/tests/test_lidar_scene.py +0 -0
  62. {plot3-0.6.6 → plot3-0.6.7}/tests/test_masking.py +0 -0
  63. {plot3-0.6.6 → plot3-0.6.7}/tests/test_math_layers.py +0 -0
  64. {plot3-0.6.6 → plot3-0.6.7}/tests/test_mathtext.py +0 -0
  65. {plot3-0.6.6 → plot3-0.6.7}/tests/test_payload.py +0 -0
  66. {plot3-0.6.6 → plot3-0.6.7}/tests/test_payload_display.py +0 -0
  67. {plot3-0.6.6 → plot3-0.6.7}/tests/test_point_cloud.py +0 -0
  68. {plot3-0.6.6 → plot3-0.6.7}/tests/test_r_oracle_parity.py +0 -0
  69. {plot3-0.6.6 → plot3-0.6.7}/tests/test_readme.py +0 -0
  70. {plot3-0.6.6 → plot3-0.6.7}/tests/test_remote_bridge.py +0 -0
  71. {plot3-0.6.6 → plot3-0.6.7}/tests/test_review_fixes.py +0 -0
  72. {plot3-0.6.6 → plot3-0.6.7}/tests/test_save_menu.py +0 -0
  73. {plot3-0.6.6 → plot3-0.6.7}/tests/test_scales_api.py +0 -0
  74. {plot3-0.6.6 → plot3-0.6.7}/tests/test_scales_themes.py +0 -0
  75. {plot3-0.6.6 → plot3-0.6.7}/tests/test_special.py +0 -0
  76. {plot3-0.6.6 → plot3-0.6.7}/tests/test_stat2d.py +0 -0
  77. {plot3-0.6.6 → plot3-0.6.7}/tests/test_static_save.py +0 -0
  78. {plot3-0.6.6 → plot3-0.6.7}/tests/test_stats_semantic.py +0 -0
  79. {plot3-0.6.6 → plot3-0.6.7}/tests/test_step3_geoms.py +0 -0
  80. {plot3-0.6.6 → plot3-0.6.7}/tests/test_table_backends.py +0 -0
  81. {plot3-0.6.6 → plot3-0.6.7}/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.6
3
+ Version: 0.6.7
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.7"
@@ -1970,6 +1970,9 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
1970
1970
  "slider() cannot be combined with transition_time() "
1971
1971
  "or transition_states()"
1972
1972
  )
1973
+ if slider is not None:
1974
+ # Default steps shrink to fit while layers expand; keep that per render.
1975
+ slider = copy.copy(slider)
1973
1976
  domains = None
1974
1977
  if any(getattr(layer, "kind", None) == "function" for layer in layers_in):
1975
1978
  from plot3.function import data_domains
@@ -1978,27 +1981,35 @@ def build_spec(g: ggplot) -> tuple[dict, list[tuple[str, str]]]:
1978
1981
  addon_map: dict[int, list] = {}
1979
1982
  for index, addon in getattr(g, "_addons", None) or []:
1980
1983
  addon_map.setdefault(int(index), []).append(addon)
1981
- expanded = []
1982
- for index, geom in enumerate(layers_in):
1983
- layer_data = getattr(geom, "layer_data", None)
1984
- if getattr(geom, "kind", None) == "box3d":
1985
- # Box classes take the figure theme's group colours.
1986
- geom = copy.copy(geom)
1987
- geom._palette = list(theme["cat"])
1988
- result = expand_stat_geom(
1989
- geom,
1990
- g.mapping,
1991
- data if layer_data is None else layer_data,
1992
- domains,
1993
- transition,
1994
- slider,
1995
- coord,
1996
- addon_map.get(index),
1997
- )
1998
- if isinstance(result, list):
1999
- expanded.extend(result)
2000
- else:
2001
- expanded.append(result)
1984
+ def expand_all() -> list:
1985
+ out = []
1986
+ for index, geom in enumerate(layers_in):
1987
+ layer_data = getattr(geom, "layer_data", None)
1988
+ if getattr(geom, "kind", None) == "box3d":
1989
+ # Box classes take the figure theme's group colours.
1990
+ geom = copy.copy(geom)
1991
+ geom._palette = list(theme["cat"])
1992
+ result = expand_stat_geom(
1993
+ geom,
1994
+ g.mapping,
1995
+ data if layer_data is None else layer_data,
1996
+ domains,
1997
+ transition,
1998
+ slider,
1999
+ coord,
2000
+ addon_map.get(index),
2001
+ )
2002
+ if isinstance(result, list):
2003
+ out.extend(result)
2004
+ else:
2005
+ out.append(result)
2006
+ return out
2007
+
2008
+ steps_before = getattr(slider, "steps", None)
2009
+ expanded = expand_all()
2010
+ if getattr(slider, "steps", None) != steps_before:
2011
+ # A later layer lowered the default steps: every layer shares one grid.
2012
+ expanded = expand_all()
2002
2013
  from plot3.calculus import mark_intersections
2003
2014
 
2004
2015
  expanded = mark_intersections(expanded)
@@ -938,6 +938,16 @@ def _guard_slider(source: Any, n_samples: int, what: str) -> None:
938
938
  cells = int(n_samples) * n_frames
939
939
  if cells <= _SLIDER_CELL_CAP:
940
940
  return
941
+ if getattr(source, "auto_steps", False):
942
+ # Default steps: take the largest count that fits. build_spec hands
943
+ # each render its own copy and re-expands if this lowered it.
944
+ count = len(source.ranges)
945
+ steps = int(source.steps)
946
+ while steps > 2 and int(n_samples) * steps**count > _SLIDER_CELL_CAP:
947
+ steps -= 1
948
+ if int(n_samples) * steps**count <= _SLIDER_CELL_CAP:
949
+ source.steps = steps
950
+ return
941
951
  raise ValueError(
942
952
  f"slider() would sample {n_frames} frames of {n_samples} {what} "
943
953
  f"({cells} values). Pass a smaller steps= or n=."
@@ -2181,20 +2181,23 @@ class slider:
2181
2181
 
2182
2182
  ggplot() + geom_function("y = a sin(k x)") + slider(a=(0, 3), k=(1, 5))
2183
2183
 
2184
- ``steps`` defaults to 25 per parameter. A surface on the default grid
2185
- is too large for several parameters at that count; pass a smaller
2186
- ``steps`` or ``n``. Do not combine with ``transition_time()``.
2184
+ ``steps`` defaults to 25 per parameter, lowered as far as needed for
2185
+ the whole grid to fit (three parameters on a curve get 9). An explicit
2186
+ ``steps`` is kept as given and refused if it is too large; pass a
2187
+ smaller ``steps`` or ``n``. Do not combine with ``transition_time()``.
2187
2188
  """
2188
2189
 
2189
2190
  kind = "slider"
2190
2191
 
2191
- def __init__(self, *, steps: int = 25, **ranges):
2192
+ def __init__(self, *, steps: int | None = None, **ranges):
2192
2193
  if not ranges:
2193
2194
  raise TypeError(
2194
2195
  "slider() needs a parameter range, for example slider(a=(0, 3))"
2195
2196
  )
2197
+ # Only the default shrinks to fit; an explicit count is the user's.
2198
+ self.auto_steps = steps is None
2196
2199
  try:
2197
- nsteps = int(steps)
2200
+ nsteps = 25 if steps is None else int(steps)
2198
2201
  except (TypeError, ValueError):
2199
2202
  raise TypeError(
2200
2203
  "slider() steps= must be an integer, for example steps=25"
@@ -42,6 +42,25 @@ from plot3.table import as_table, detect_backend
42
42
  from plot3.themes import _THEMES
43
43
 
44
44
 
45
+ def _error_text(exc: BaseException) -> str:
46
+ return f"plot3 could not draw this figure.\n{type(exc).__name__}: {exc}"
47
+
48
+
49
+ def _error_html(exc: BaseException) -> str:
50
+ import traceback
51
+
52
+ trace = "".join(traceback.format_exception(type(exc), exc, exc.__traceback__))
53
+ return (
54
+ '<div class="plot3-error" style="border-left:4px solid #d9534f;'
55
+ "padding:8px 12px;font-family:ui-monospace,Menlo,monospace;"
56
+ 'font-size:13px;white-space:pre-wrap">'
57
+ "<strong>plot3 could not draw this figure.</strong>\n"
58
+ f"{_htmlesc.escape(type(exc).__name__)}: {_htmlesc.escape(str(exc))}"
59
+ '<details style="margin-top:6px"><summary>Traceback</summary>'
60
+ f"{_htmlesc.escape(trace)}</details></div>"
61
+ )
62
+
63
+
45
64
  def _env_flag(name: str) -> bool | None:
46
65
  raw = os.environ.get(name)
47
66
  if raw is None or raw == "":
@@ -422,7 +441,10 @@ class ggplot:
422
441
  # Used by hosts that only understand HTML reprs (and by tests).
423
442
  # Prefer ``display(fig)`` / ``fig.show()`` so VS Code can open a browser.
424
443
  self._maybe_hide_from_ai()
425
- return self._iframe()
444
+ try:
445
+ return self._iframe()
446
+ except Exception as exc:
447
+ return _error_html(exc)
426
448
 
427
449
  def _repr_mimebundle_(self, include=None, exclude=None):
428
450
  """HTML plus optional PlotPayload mime for CRAFT host handoff.
@@ -435,7 +457,12 @@ class ggplot:
435
457
  from plot3.remote import MIME_PLOT3
436
458
 
437
459
  self._maybe_hide_from_ai()
438
- bundle: dict = {"text/html": self._iframe()}
460
+ try:
461
+ bundle: dict = {"text/html": self._iframe()}
462
+ except Exception as exc:
463
+ # Notebook hosts swallow repr errors and fall back to the bare
464
+ # object repr, so the reason a plot is missing must be the output.
465
+ return {"text/html": _error_html(exc), "text/plain": _error_text(exc)}
439
466
  try:
440
467
  if self.facet is None and (
441
468
  self._payload is not None or self.data is not None or self.layers
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plot3
3
- Version: 0.6.6
3
+ Version: 0.6.7
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
@@ -671,3 +671,33 @@ def test_swept_parameter_is_not_frozen_into_labels():
671
671
  )
672
672
  assert "t = 0" not in spec
673
673
  assert "(a = 2)" in spec # fixed coefficients still show their value
674
+
675
+
676
+ def test_default_slider_steps_shrink_to_fit():
677
+ from plot3 import geom_function
678
+
679
+ g = (
680
+ ggplot()
681
+ + geom_function("y = a*(x-h)^2 + k", xlim=(-5, 5))
682
+ + slider(a=(0.2, 3), h=(-3, 3), k=(-3, 3))
683
+ )
684
+ spec, _ = build_spec(g)
685
+ assert [p["n"] for p in spec["slider"]["params"]] == [9, 9, 9]
686
+ assert spec["slider"]["nFrames"] == 729
687
+ # The figure's own slider keeps its default for later renders.
688
+ assert g.slider.steps == 25
689
+
690
+
691
+ def test_render_error_is_shown_instead_of_the_bare_repr():
692
+ from plot3 import geom_function
693
+
694
+ g = (
695
+ ggplot()
696
+ + geom_function("y = a x + k")
697
+ + slider(a=(0, 1), k=(0, 1), steps=2000)
698
+ )
699
+ bundle = g._repr_mimebundle_()
700
+ assert "plot3-error" in bundle["text/html"]
701
+ assert "smaller steps" in bundle["text/html"]
702
+ assert "ValueError" in bundle["text/plain"]
703
+ assert "smaller steps" in g._repr_html_()
@@ -1 +0,0 @@
1
- __version__ = "0.6.6"
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