scistackplot 0.1.37__tar.gz → 0.1.39__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 (48) hide show
  1. {scistackplot-0.1.37 → scistackplot-0.1.39}/PKG-INFO +2 -2
  2. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/render/mpl.py +92 -29
  3. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/variants.py +1 -2
  4. {scistackplot-0.1.37 → scistackplot-0.1.39}/.gitignore +0 -0
  5. {scistackplot-0.1.37 → scistackplot-0.1.39}/README.md +0 -0
  6. {scistackplot-0.1.37 → scistackplot-0.1.39}/pyproject.toml +0 -0
  7. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/__init__.py +0 -0
  8. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/aliases.py +0 -0
  9. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/autosize.py +0 -0
  10. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/capability.py +0 -0
  11. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/cell.py +0 -0
  12. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/codegen.py +0 -0
  13. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/colors.py +0 -0
  14. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/compare.py +0 -0
  15. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/custom.py +0 -0
  16. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/dedup.py +0 -0
  17. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/diffbars.py +0 -0
  18. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/export.py +0 -0
  19. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/figsize.py +0 -0
  20. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/figure_file.py +0 -0
  21. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/framesize.py +0 -0
  22. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/groups.py +0 -0
  23. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/numeric.py +0 -0
  24. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/panels.py +0 -0
  25. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/paper.py +0 -0
  26. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/presets.py +0 -0
  27. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/reduce.py +0 -0
  28. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/reducer.py +0 -0
  29. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/render/__init__.py +0 -0
  30. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/render/base.py +0 -0
  31. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/render/plotly_.py +0 -0
  32. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/resolved.py +0 -0
  33. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/restore.py +0 -0
  34. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/roles.py +0 -0
  35. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/series_stats.py +0 -0
  36. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/shape.py +0 -0
  37. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/sources/__init__.py +0 -0
  38. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/sources/base.py +0 -0
  39. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/sources/csv.py +0 -0
  40. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/sources/frame.py +0 -0
  41. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/spaghetti.py +0 -0
  42. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/spec.py +0 -0
  43. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/table.py +0 -0
  44. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/textsize.py +0 -0
  45. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/ticklabels.py +0 -0
  46. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/weights.py +0 -0
  47. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/xaxis.py +0 -0
  48. {scistackplot-0.1.37 → scistackplot-0.1.39}/src/scistackplot/ylimits.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: scistackplot
3
- Version: 0.1.37
3
+ Version: 0.1.39
4
4
  Summary: Spec-driven plotting for long-format scientific data — standalone, GUI-friendly
5
5
  Author: SciStack Contributors
6
6
  License-Expression: MIT
@@ -19,7 +19,7 @@ Classifier: Typing :: Typed
19
19
  Requires-Python: >=3.10
20
20
  Requires-Dist: numpy>=1.21
21
21
  Requires-Dist: pandas>=1.5
22
- Requires-Dist: scistacklog==0.1.37
22
+ Requires-Dist: scistacklog==0.1.39
23
23
  Provides-Extra: dev
24
24
  Requires-Dist: matplotlib>=3.6; extra == 'dev'
25
25
  Requires-Dist: plotly>=5.0; extra == 'dev'
@@ -1900,6 +1900,14 @@ def layout_decisions(
1900
1900
  "width_in", "height_in"}``. The plotly preview
1901
1901
  applies these rather than estimating text of its own, so what the preview
1902
1902
  shows and what the file gets cannot come from two different rules.
1903
+
1904
+ One layout, not two: the auto text size search already lays the figure
1905
+ out at every reference it tries, and a layout depends on the auto result
1906
+ only through its ``reference`` (``textsize.sizes_for``). So the trial at
1907
+ the chosen reference IS the final layout, and its decisions are kept
1908
+ rather than drawn again (scidb.log 2026-10-16: the redraw was ~0.4 s of
1909
+ every ~0.85 s next/previous click). A custom kind's trial skips the
1910
+ user's function (``custom.measuring``), so it is always drawn again.
1903
1911
  """
1904
1912
  import matplotlib.pyplot as plt
1905
1913
 
@@ -1911,37 +1919,87 @@ def layout_decisions(
1911
1919
  with _one_figure_at_a_time("layout_decisions"), Log.timer(
1912
1920
  "layout_decisions", layer=LAYER, extra=f"{width:.2f}x{height:.2f}in"
1913
1921
  ):
1922
+ # {reference: decisions} of each auto-size trial laid out here.
1923
+ trials: dict[float, dict] = {}
1924
+
1925
+ def measure(candidate: ResolvedPlot):
1926
+ decisions: dict = {}
1927
+ report = _layout_report(candidate, decisions_out=decisions)
1928
+ trials[candidate.auto_text.reference] = decisions
1929
+ return report
1930
+
1931
+ reusable = resolved.kind is not PlotKind.CUSTOM
1914
1932
  # Auto text is chosen at the size being decided (the pane's, in Fit
1915
1933
  # pane), keyed on the caller's object so the preview's later
1916
1934
  # render_plotly and a Save of the same resolve reuse it.
1917
- target = at_size(settle(resolved, width_in=width, height_in=height), width, height)
1918
- fig = render(target)
1919
- try:
1920
- fits = getattr(fig, LABEL_FIT_ATTR, None) or {}
1921
- axis_text = getattr(fig, AXIS_TEXT_ATTR, None) or ()
1922
- return {
1923
- "ticks": fits.get("ticks"),
1924
- "brackets": fits.get("brackets"),
1925
- "bracket_geometry": fits.get("bracket_geometry"),
1926
- "grid_reach": getattr(fig, GRID_REACH_ATTR, None),
1927
- # tuple[base.AxisText, ...]: the tick values/labels and title
1928
- # distance the export drew, which the preview copies so the
1929
- # y text it puts in a column gap is the text measured for it,
1930
- # and a numeric x axis shows the ticks the auto size judged.
1931
- "y_axes": tuple(a for a in axis_text if a.axis == "y"),
1932
- "x_axes": tuple(a for a in axis_text if a.axis == "x"),
1933
- "legend": getattr(fig, LEGEND_ATTR, None),
1934
- # diffbars.FigurePlacement, or None: the preview draws these
1935
- # bars and ranges rather than placing its own.
1936
- "difference_bars": getattr(fig, DIFF_BARS_ATTR, None),
1937
- # autosize.AutoTextSize, or None when the size is fixed: the
1938
- # preview draws at these sizes rather than searching again.
1939
- "auto_text": target.auto_text,
1940
- "width_in": width,
1941
- "height_in": height,
1942
- }
1943
- finally:
1944
- plt.close(fig)
1935
+ target = at_size(
1936
+ settle(
1937
+ resolved,
1938
+ width_in=width,
1939
+ height_in=height,
1940
+ measure=measure if reusable else None,
1941
+ ),
1942
+ width,
1943
+ height,
1944
+ )
1945
+ auto = target.auto_text
1946
+ decisions = trials.get(auto.reference) if auto is not None else None
1947
+ if decisions is not None:
1948
+ Log.info(
1949
+ "layout_decisions%s: kept the %gpt auto-size trial's layout "
1950
+ "(%d trial(s)); no second matplotlib layout",
1951
+ f" ({resolved.figure_label})" if resolved.figure_label else "",
1952
+ auto.reference,
1953
+ len(trials),
1954
+ layer=LAYER,
1955
+ )
1956
+ else:
1957
+ # Why a full draw: a fixed text size, a memo hit (the search did
1958
+ # not run here), or a custom kind.
1959
+ Log.debug(
1960
+ "layout_decisions: drawing (%s)",
1961
+ "custom kind" if not reusable
1962
+ else "fixed text size" if auto is None
1963
+ else "auto size reused, no trial here",
1964
+ layer=LAYER,
1965
+ )
1966
+ fig = render(target)
1967
+ try:
1968
+ decisions = _figure_decisions(fig)
1969
+ finally:
1970
+ plt.close(fig)
1971
+ return {
1972
+ **decisions,
1973
+ # autosize.AutoTextSize, or None when the size is fixed: the
1974
+ # preview draws at these sizes rather than searching again.
1975
+ "auto_text": auto,
1976
+ "width_in": width,
1977
+ "height_in": height,
1978
+ }
1979
+
1980
+
1981
+ def _figure_decisions(fig) -> dict:
1982
+ """What a drawn export decided, as ``layout_decisions`` returns it (less
1983
+ the size and ``auto_text``). The one reader of the figure's records, for
1984
+ both the final draw and a kept auto-size trial."""
1985
+ fits = getattr(fig, LABEL_FIT_ATTR, None) or {}
1986
+ axis_text = getattr(fig, AXIS_TEXT_ATTR, None) or ()
1987
+ return {
1988
+ "ticks": fits.get("ticks"),
1989
+ "brackets": fits.get("brackets"),
1990
+ "bracket_geometry": fits.get("bracket_geometry"),
1991
+ "grid_reach": getattr(fig, GRID_REACH_ATTR, None),
1992
+ # tuple[base.AxisText, ...]: the tick values/labels and title
1993
+ # distance the export drew, which the preview copies so the
1994
+ # y text it puts in a column gap is the text measured for it,
1995
+ # and a numeric x axis shows the ticks the auto size judged.
1996
+ "y_axes": tuple(a for a in axis_text if a.axis == "y"),
1997
+ "x_axes": tuple(a for a in axis_text if a.axis == "x"),
1998
+ "legend": getattr(fig, LEGEND_ATTR, None),
1999
+ # diffbars.FigurePlacement, or None: the preview draws these
2000
+ # bars and ranges rather than placing its own.
2001
+ "difference_bars": getattr(fig, DIFF_BARS_ATTR, None),
2002
+ }
1945
2003
 
1946
2004
 
1947
2005
  def layout_report(resolved: ResolvedPlot):
@@ -1953,7 +2011,10 @@ def layout_report(resolved: ResolvedPlot):
1953
2011
  return _layout_report(resolved)
1954
2012
 
1955
2013
 
1956
- def _layout_report(resolved: ResolvedPlot):
2014
+ def _layout_report(resolved: ResolvedPlot, *, decisions_out: dict | None = None):
2015
+ """``layout_report``'s body. ``decisions_out``, when given, is filled
2016
+ with the figure's ``_figure_decisions`` before it closes, so a trial at
2017
+ the chosen reference serves as the final layout too."""
1957
2018
  import matplotlib.pyplot as plt
1958
2019
 
1959
2020
  from ..autosize import LayoutReport
@@ -1964,6 +2025,8 @@ def _layout_report(resolved: ResolvedPlot):
1964
2025
  with measuring():
1965
2026
  fig = render(resolved)
1966
2027
  try:
2028
+ if decisions_out is not None:
2029
+ decisions_out.update(_figure_decisions(fig))
1967
2030
  fits = getattr(fig, LABEL_FIT_ATTR, None) or {}
1968
2031
  ticks, brackets = fits.get("ticks"), fits.get("brackets")
1969
2032
  legend = getattr(fig, LEGEND_ATTR, None) or {}
@@ -390,8 +390,7 @@ def export_selection(
390
390
  ``latest_column`` and ``default_pin`` are the TABLE's (the same facts
391
391
  :func:`effective_selection_with_source` reads), so the export adds the
392
392
  latest flag exactly when the preview does. A table with no default — a
393
- stacked multi-variable table, a single-generation variable — exports the
394
- row as written, as the preview draws it. A freeze comes back as the frozen
393
+ single-generation variable, a CSV — exports the row as written, as the preview draws it. A freeze comes back as the frozen
395
394
  ordinal(s) on their ``Code:`` columns.
396
395
 
397
396
  Known limit: a freeze of SEVERAL ordinals is "per location, the highest of
File without changes
File without changes