figpack 0.3.2__tar.gz → 0.3.5__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 (80) hide show
  1. {figpack-0.3.2/figpack.egg-info → figpack-0.3.5}/PKG-INFO +2 -2
  2. {figpack-0.3.2 → figpack-0.3.5}/README.md +1 -1
  3. {figpack-0.3.2 → figpack-0.3.5}/figpack/__init__.py +1 -1
  4. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_bundle_utils.py +11 -3
  5. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_save_figure.py +17 -3
  6. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_show_view.py +5 -2
  7. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_view_figure.py +1 -3
  8. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/figpack_view.py +14 -4
  9. figpack-0.3.5/figpack/core/zarr.py +129 -0
  10. figpack-0.3.5/figpack/figpack-figure-dist/assets/index-P3Ni9vtI.js +394 -0
  11. {figpack-0.3.2 → figpack-0.3.5}/figpack/figpack-figure-dist/index.html +1 -1
  12. figpack-0.3.5/figpack/utils/__init__.py +24 -0
  13. {figpack-0.3.2 → figpack-0.3.5/figpack.egg-info}/PKG-INFO +2 -2
  14. {figpack-0.3.2 → figpack-0.3.5}/figpack.egg-info/SOURCES.txt +2 -1
  15. {figpack-0.3.2 → figpack-0.3.5}/pyproject.toml +1 -1
  16. figpack-0.3.2/figpack/core/zarr.py +0 -61
  17. figpack-0.3.2/figpack/figpack-figure-dist/assets/index-ST_DU17U.js +0 -95
  18. {figpack-0.3.2 → figpack-0.3.5}/LICENSE +0 -0
  19. {figpack-0.3.2 → figpack-0.3.5}/MANIFEST.in +0 -0
  20. {figpack-0.3.2 → figpack-0.3.5}/figpack/cli.py +0 -0
  21. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/__init__.py +0 -0
  22. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_file_handler.py +0 -0
  23. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_server_manager.py +0 -0
  24. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_upload_bundle.py +0 -0
  25. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/_zarr_consolidate.py +0 -0
  26. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/config.py +0 -0
  27. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/extension_view.py +0 -0
  28. {figpack-0.3.2 → figpack-0.3.5}/figpack/core/figpack_extension.py +0 -0
  29. {figpack-0.3.2 → figpack-0.3.5}/figpack/extensions.py +0 -0
  30. {figpack-0.3.2 → figpack-0.3.5}/figpack/figpack-figure-dist/assets/index-V5m_wCvw.css +0 -0
  31. {figpack-0.3.2 → figpack-0.3.5}/figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  32. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Box.py +0 -0
  33. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/CaptionedView.py +0 -0
  34. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/DataFrame.py +0 -0
  35. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Gallery.py +0 -0
  36. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/GalleryItem.py +0 -0
  37. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Iframe.py +0 -0
  38. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Image.py +0 -0
  39. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/LayoutItem.py +0 -0
  40. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Markdown.py +0 -0
  41. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/MatplotlibFigure.py +0 -0
  42. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/MountainLayout.py +0 -0
  43. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/MountainLayoutItem.py +0 -0
  44. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/MultiChannelTimeseries.py +0 -0
  45. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/PlotlyExtension/PlotlyExtension.py +0 -0
  46. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/PlotlyExtension/__init__.py +0 -0
  47. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/PlotlyExtension/_plotly_extension.py +0 -0
  48. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/PlotlyExtension/plotly_view.js +0 -0
  49. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Spectrogram.py +0 -0
  50. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/Splitter.py +0 -0
  51. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/TabLayout.py +0 -0
  52. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/TabLayoutItem.py +0 -0
  53. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/TimeseriesGraph.py +0 -0
  54. {figpack-0.3.2 → figpack-0.3.5}/figpack/views/__init__.py +0 -0
  55. {figpack-0.3.2 → figpack-0.3.5}/figpack-serve/worker/container/app.py +0 -0
  56. {figpack-0.3.2 → figpack-0.3.5}/figpack.egg-info/dependency_links.txt +0 -0
  57. {figpack-0.3.2 → figpack-0.3.5}/figpack.egg-info/entry_points.txt +0 -0
  58. {figpack-0.3.2 → figpack-0.3.5}/figpack.egg-info/requires.txt +0 -0
  59. {figpack-0.3.2 → figpack-0.3.5}/figpack.egg-info/top_level.txt +0 -0
  60. {figpack-0.3.2 → figpack-0.3.5}/setup.cfg +0 -0
  61. {figpack-0.3.2 → figpack-0.3.5}/tests/test_box.py +0 -0
  62. {figpack-0.3.2 → figpack-0.3.5}/tests/test_cli.py +0 -0
  63. {figpack-0.3.2 → figpack-0.3.5}/tests/test_core.py +0 -0
  64. {figpack-0.3.2 → figpack-0.3.5}/tests/test_dataframe.py +0 -0
  65. {figpack-0.3.2 → figpack-0.3.5}/tests/test_extension_system.py +0 -0
  66. {figpack-0.3.2 → figpack-0.3.5}/tests/test_figpack_view.py +0 -0
  67. {figpack-0.3.2 → figpack-0.3.5}/tests/test_file_handler.py +0 -0
  68. {figpack-0.3.2 → figpack-0.3.5}/tests/test_gallery.py +0 -0
  69. {figpack-0.3.2 → figpack-0.3.5}/tests/test_image.py +0 -0
  70. {figpack-0.3.2 → figpack-0.3.5}/tests/test_markdown.py +0 -0
  71. {figpack-0.3.2 → figpack-0.3.5}/tests/test_matplotlib_figure.py +0 -0
  72. {figpack-0.3.2 → figpack-0.3.5}/tests/test_multichannel_timeseries.py +0 -0
  73. {figpack-0.3.2 → figpack-0.3.5}/tests/test_plotly_figure.py +0 -0
  74. {figpack-0.3.2 → figpack-0.3.5}/tests/test_server_manager.py +0 -0
  75. {figpack-0.3.2 → figpack-0.3.5}/tests/test_spectrogram.py +0 -0
  76. {figpack-0.3.2 → figpack-0.3.5}/tests/test_splitter.py +0 -0
  77. {figpack-0.3.2 → figpack-0.3.5}/tests/test_tablayout.py +0 -0
  78. {figpack-0.3.2 → figpack-0.3.5}/tests/test_timeseries_graph.py +0 -0
  79. {figpack-0.3.2 → figpack-0.3.5}/tests/test_upload_bundle.py +0 -0
  80. {figpack-0.3.2 → figpack-0.3.5}/tests/test_view_figure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figpack
3
- Version: 0.3.2
3
+ Version: 0.3.5
4
4
  Summary: A Python package for creating shareable, interactive visualizations in the browser
5
5
  Author-email: Jeremy Magland <jmagland@flatironinstitute.org>
6
6
  License: Apache-2.0
@@ -133,7 +133,7 @@ If you use figpack in your research, please cite it:
133
133
 
134
134
  Or in APA format:
135
135
 
136
- > Magland, J. (2025). figpack (Version 0.3.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17419621
136
+ > Magland, J. (2025). figpack (Version 0.3.5) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17419621
137
137
 
138
138
  ## Contributing
139
139
 
@@ -57,7 +57,7 @@ If you use figpack in your research, please cite it:
57
57
 
58
58
  Or in APA format:
59
59
 
60
- > Magland, J. (2025). figpack (Version 0.3.2) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17419621
60
+ > Magland, J. (2025). figpack (Version 0.3.5) [Computer software]. Zenodo. https://doi.org/10.5281/zenodo.17419621
61
61
 
62
62
  ## Contributing
63
63
 
@@ -2,7 +2,7 @@
2
2
  figpack - A Python package for creating shareable, interactive visualizations in the browser
3
3
  """
4
4
 
5
- __version__ = "0.3.2"
5
+ __version__ = "0.3.5"
6
6
 
7
7
  from .cli import view_figure
8
8
  from .core import FigpackView, FigpackExtension, ExtensionView
@@ -15,7 +15,12 @@ thisdir = pathlib.Path(__file__).parent.resolve()
15
15
 
16
16
 
17
17
  def prepare_figure_bundle(
18
- view: FigpackView, tmpdir: str, *, title: str, description: Optional[str] = None
18
+ view: FigpackView,
19
+ tmpdir: str,
20
+ *,
21
+ title: str,
22
+ description: Optional[str] = None,
23
+ script: Optional[str] = None,
19
24
  ) -> None:
20
25
  """
21
26
  Prepare a figure bundle in the specified temporary directory.
@@ -31,6 +36,7 @@ def prepare_figure_bundle(
31
36
  tmpdir: The temporary directory to prepare the bundle in
32
37
  title: Title for the figure (required)
33
38
  description: Optional description for the figure (markdown supported)
39
+ script: Optional script text used to generate the figure
34
40
  """
35
41
  html_dir = thisdir / ".." / "figpack-figure-dist"
36
42
  if not os.path.exists(html_dir):
@@ -61,10 +67,12 @@ def prepare_figure_bundle(
61
67
  zarr_group = Group(zarr_group)
62
68
  view.write_to_zarr_group(zarr_group)
63
69
 
64
- # Add title and description as attributes on the top-level zarr group
70
+ # Add title and description and script as attributes on the top-level zarr group
65
71
  zarr_group.attrs["title"] = title
66
- if description is not None:
72
+ if description:
67
73
  zarr_group.attrs["description"] = description
74
+ if script:
75
+ zarr_group.attrs["script"] = script
68
76
 
69
77
  # Discover and write extension JavaScript files
70
78
  required_extensions = _discover_required_extensions(view)
@@ -6,7 +6,12 @@ from .figpack_view import FigpackView
6
6
 
7
7
 
8
8
  def _save_figure(
9
- view: FigpackView, output_path: str, *, title: str, description: str = ""
9
+ view: FigpackView,
10
+ output_path: str,
11
+ *,
12
+ title: str,
13
+ description: str = "",
14
+ script: str = "",
10
15
  ) -> None:
11
16
  """
12
17
  Save the figure to a folder or a .tar.gz file
@@ -14,6 +19,9 @@ def _save_figure(
14
19
  Args:
15
20
  view: FigpackView instance to save
16
21
  output_path: Output path (destination folder or .tar.gz file path)
22
+ title: Title for the figure
23
+ description: Description text with markdown support
24
+ script: Optional script text used to generate the figure
17
25
  """
18
26
  output_path_2 = pathlib.Path(output_path)
19
27
  if (output_path_2.suffix == ".gz" and output_path_2.suffixes[-2] == ".tar") or (
@@ -21,7 +29,9 @@ def _save_figure(
21
29
  ):
22
30
  # It's a .tar.gz file
23
31
  with tempfile.TemporaryDirectory(prefix="figpack_save_") as tmpdir:
24
- prepare_figure_bundle(view, tmpdir, title=title, description=description)
32
+ prepare_figure_bundle(
33
+ view, tmpdir, title=title, description=description, script=script
34
+ )
25
35
  # Create tar.gz file
26
36
  import tarfile
27
37
 
@@ -31,5 +41,9 @@ def _save_figure(
31
41
  # It's a folder
32
42
  output_path_2.mkdir(parents=True, exist_ok=True)
33
43
  prepare_figure_bundle(
34
- view, str(output_path_2), title=title, description=description
44
+ view,
45
+ str(output_path_2),
46
+ title=title,
47
+ description=description,
48
+ script=script,
35
49
  )
@@ -91,6 +91,7 @@ def _show_view(
91
91
  ephemeral: bool,
92
92
  title: str,
93
93
  description: Union[str, None],
94
+ script: Union[str, None],
94
95
  inline: bool,
95
96
  inline_height: int,
96
97
  wait_for_input: bool,
@@ -99,7 +100,9 @@ def _show_view(
99
100
  if upload:
100
101
  # Upload behavior: create temporary directory for this upload only
101
102
  with tempfile.TemporaryDirectory(prefix="figpack_upload_") as tmpdir:
102
- prepare_figure_bundle(view, tmpdir, title=title, description=description)
103
+ prepare_figure_bundle(
104
+ view, tmpdir, title=title, description=description, script=script
105
+ )
103
106
 
104
107
  # Check for API key - required for regular uploads, optional for ephemeral
105
108
  api_key = os.environ.get("FIGPACK_API_KEY")
@@ -143,7 +146,7 @@ def _show_view(
143
146
 
144
147
  # Prepare the figure bundle in the subdirectory
145
148
  prepare_figure_bundle(
146
- view, str(figure_dir), title=title, description=description
149
+ view, str(figure_dir), title=title, description=description, script=script
147
150
  )
148
151
 
149
152
  # Start or get existing server
@@ -131,9 +131,7 @@ def view_figure(figure_path: str, port: Union[int, None] = None) -> None:
131
131
  )
132
132
  return
133
133
 
134
- if not figure_pathlib.suffix.lower() in [".gz", ".tgz"] or not str(
135
- figure_pathlib
136
- ).endswith(".tar.gz"):
134
+ if not figure_path.endswith(".tar.gz") and not figure_path.endswith(".tgz"):
137
135
  print(f"Error: Archive file must be a .tar.gz file: {figure_path}")
138
136
  sys.exit(1)
139
137
 
@@ -20,6 +20,7 @@ class FigpackView:
20
20
  *,
21
21
  title: str,
22
22
  description: Optional[str] = None,
23
+ script: Optional[str] = None,
23
24
  port: Optional[int] = None,
24
25
  open_in_browser: Optional[bool] = None,
25
26
  upload: Optional[bool] = None,
@@ -29,7 +30,7 @@ class FigpackView:
29
30
  allow_origin: Optional[str] = None,
30
31
  wait_for_input: Optional[bool] = None,
31
32
  _dev: Optional[bool] = None,
32
- ) -> None:
33
+ ):
33
34
  """
34
35
  Display a figpack view component with intelligent environment detection and flexible display options.
35
36
  See https://flatironinstitute.github.io/figpack/show_function.html for complete documentation.
@@ -41,6 +42,7 @@ class FigpackView:
41
42
  Args:
42
43
  title: Title for browser tab and figure (required)
43
44
  description: Description text with markdown support (optional)
45
+ script: Optional script text used to generate the figure
44
46
  port: Local server port, random if None
45
47
  open_in_browser: Auto-open in browser, auto-detects by environment
46
48
  upload: Upload figure to figpack servers, auto-detects by environment
@@ -161,7 +163,7 @@ class FigpackView:
161
163
  )
162
164
  print("")
163
165
 
164
- _show_view(
166
+ return _show_view(
165
167
  self,
166
168
  port=port,
167
169
  open_in_browser=open_in_browser,
@@ -170,21 +172,29 @@ class FigpackView:
170
172
  ephemeral=ephemeral,
171
173
  title=title,
172
174
  description=description,
175
+ script=script,
173
176
  inline=inline,
174
177
  inline_height=inline_height,
175
178
  wait_for_input=wait_for_input,
176
179
  _local_figure_name=_local_figure_name if _dev else None,
177
180
  )
178
181
 
179
- def save(self, output_path: str, *, title: str, description: str = "") -> None:
182
+ def save(
183
+ self, output_path: str, *, title: str, description: str = "", script: str = ""
184
+ ) -> None:
180
185
  """
181
186
  Save as figure either to a folder or to a .tar.gz file
182
187
  Args:
183
188
  output_path: Output path (destination folder or .tar.gz file path)
189
+ title: Title for the figure
190
+ description: Description text with markdown support
191
+ script: Optional script text used to generate the figure
184
192
  """
185
193
  from ._save_figure import _save_figure
186
194
 
187
- _save_figure(self, output_path, title=title, description=description)
195
+ _save_figure(
196
+ self, output_path, title=title, description=description, script=script
197
+ )
188
198
 
189
199
  def write_to_zarr_group(self, group: Group) -> None:
190
200
  """
@@ -0,0 +1,129 @@
1
+ from typing import Any, Dict
2
+ import numpy as np
3
+
4
+
5
+ _UNSPECIFIED = object()
6
+
7
+
8
+ class Group:
9
+ def __init__(self, zarr_group):
10
+ self._zarr_group = zarr_group
11
+
12
+ def create_group(self, name: str) -> "Group":
13
+ return Group(self._zarr_group.create_group(name))
14
+
15
+ def create_dataset(
16
+ self,
17
+ name: str,
18
+ *,
19
+ data=_UNSPECIFIED,
20
+ dtype=_UNSPECIFIED,
21
+ chunks=_UNSPECIFIED,
22
+ compressor=_UNSPECIFIED,
23
+ ) -> None:
24
+ kwargs = {}
25
+ if data is not _UNSPECIFIED:
26
+ kwargs["data"] = data
27
+ if dtype is not _UNSPECIFIED:
28
+ kwargs["dtype"] = dtype
29
+ if chunks is not _UNSPECIFIED:
30
+ kwargs["chunks"] = chunks
31
+ else:
32
+ if data is not _UNSPECIFIED:
33
+ chunks2 = _get_optimal_chunk_size(data.shape, data.dtype)
34
+ if chunks2 is not _UNSPECIFIED:
35
+ kwargs["chunks"] = chunks2
36
+ if compressor is not _UNSPECIFIED:
37
+ kwargs["compressor"] = compressor
38
+ if _check_zarr_version() == 2:
39
+ self._zarr_group.create_dataset(name, **kwargs)
40
+ elif _check_zarr_version() == 3:
41
+ self._zarr_group.create_array(name, **kwargs) # type: ignore
42
+ else:
43
+ raise RuntimeError("Unsupported Zarr version")
44
+
45
+ @property
46
+ def attrs(self) -> Dict[str, Any]:
47
+ return self._zarr_group.attrs # type: ignore
48
+
49
+ def __getitem__(self, key: str) -> Any:
50
+ return self._zarr_group[key]
51
+
52
+ # implement in operator
53
+ def __contains__(self, key: str) -> bool:
54
+ return key in self._zarr_group
55
+
56
+ def __iter__(self):
57
+ return iter(self._zarr_group)
58
+
59
+ def __reversed__(self):
60
+ return reversed(self._zarr_group)
61
+
62
+
63
+ def _check_zarr_version():
64
+ import zarr
65
+
66
+ version = zarr.__version__
67
+ major_version = int(version.split(".")[0])
68
+ return major_version
69
+
70
+
71
+ def _get_optimal_chunk_size(shape: tuple, dtype) -> tuple:
72
+ """Compute an optimal chunk shape targeting ~5 MB per chunk,
73
+ chunking along only one dimension and avoiding mostly empty chunks.
74
+ """
75
+ target_bytes = 5 * 1024 * 1024 # 5 MB
76
+
77
+ if np.prod(shape) <= 1:
78
+ # all dimensions are zero or one
79
+ # return shape of unspecified to let zarr choose on this edge case
80
+ return _UNSPECIFIED
81
+
82
+ # Ensure dtype is a numpy dtype to get itemsize
83
+ if isinstance(dtype, str):
84
+ dtype = np.dtype(dtype)
85
+
86
+ itemsize = np.dtype(dtype).itemsize
87
+
88
+ # Total array size in bytes
89
+ total_bytes = np.prod(shape) * itemsize
90
+ if total_bytes <= target_bytes:
91
+ # Entire array fits comfortably in one chunk
92
+ return shape
93
+
94
+ # Choose the first non-singleton dimension to chunk along
95
+ for axis, dim in enumerate(shape):
96
+ if dim > 1:
97
+ break
98
+ else:
99
+ return shape # all singleton
100
+
101
+ # Compute how many elements fit in target_bytes
102
+ target_elements = target_bytes // itemsize
103
+
104
+ # Compute product of all other dimensions (fixed)
105
+ other_size = np.prod(shape) // shape[axis]
106
+
107
+ # Determine chunk length along chosen axis
108
+ chunk_len = max(1, min(shape[axis], target_elements // other_size))
109
+
110
+ # Construct chunk shape
111
+ chunk_shape = list(shape)
112
+ chunk_shape[axis] = chunk_len
113
+
114
+ return tuple([int(c) for c in chunk_shape])
115
+
116
+
117
+ if __name__ == "__main__":
118
+ # Test the _get_optimal_chunk_size function for a variety of shapes and dtypes
119
+ test_cases = [
120
+ ((1000, 1000), np.float32),
121
+ ((5000, 2000), np.uint8),
122
+ ((100, 100, 100), np.float64),
123
+ ((10, 30, 30, 1500), np.int16),
124
+ ((1, 1, 1), np.float32),
125
+ ]
126
+
127
+ for shape, dtype in test_cases:
128
+ chunk_size = _get_optimal_chunk_size(shape, dtype)
129
+ print(f"Optimal chunk size for shape {shape} and dtype {dtype}: {chunk_size}")