figpack 0.4.0__tar.gz → 0.4.2__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 (88) hide show
  1. {figpack-0.4.0/figpack.egg-info → figpack-0.4.2}/PKG-INFO +1 -1
  2. {figpack-0.4.0 → figpack-0.4.2}/figpack/__init__.py +1 -1
  3. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_bundle_utils.py +13 -1
  4. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_figure_spec.py +13 -1
  5. figpack-0.4.2/figpack/core/_remix_patch.py +228 -0
  6. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_save_figure.py +11 -1
  7. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_show_view.py +14 -2
  8. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/figpack_view.py +24 -3
  9. figpack-0.4.0/figpack/figpack-figure-dist/assets/index-BKMp8GZO.js → figpack-0.4.2/figpack/figpack-figure-dist/assets/index-DCvZuZLF.js +84 -80
  10. figpack-0.4.0/figpack/figpack-figure-dist/assets/index-GPjx4QpG.css → figpack-0.4.2/figpack/figpack-figure-dist/assets/index-oS-16pKZ.css +1 -1
  11. {figpack-0.4.0 → figpack-0.4.2}/figpack/figpack-figure-dist/index.html +2 -2
  12. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/TimeseriesGraph.py +9 -0
  13. {figpack-0.4.0 → figpack-0.4.2/figpack.egg-info}/PKG-INFO +1 -1
  14. {figpack-0.4.0 → figpack-0.4.2}/figpack.egg-info/SOURCES.txt +4 -2
  15. {figpack-0.4.0 → figpack-0.4.2}/pyproject.toml +1 -1
  16. figpack-0.4.2/tests/test_remix_patch.py +128 -0
  17. {figpack-0.4.0 → figpack-0.4.2}/LICENSE +0 -0
  18. {figpack-0.4.0 → figpack-0.4.2}/MANIFEST.in +0 -0
  19. {figpack-0.4.0 → figpack-0.4.2}/README.md +0 -0
  20. {figpack-0.4.0 → figpack-0.4.2}/figpack/cli.py +0 -0
  21. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/__init__.py +0 -0
  22. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_figure_utils.py +0 -0
  23. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_file_handler.py +0 -0
  24. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_patch_figure.py +0 -0
  25. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_remix_figure.py +0 -0
  26. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_revert_patch_figure.py +0 -0
  27. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_server_manager.py +0 -0
  28. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_upload_bundle.py +0 -0
  29. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_view_figure.py +0 -0
  30. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/_zarr_consolidate.py +0 -0
  31. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/config.py +0 -0
  32. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/extension_view.py +0 -0
  33. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/figpack_extension.py +0 -0
  34. {figpack-0.4.0 → figpack-0.4.2}/figpack/core/zarr.py +0 -0
  35. {figpack-0.4.0 → figpack-0.4.2}/figpack/extensions.py +0 -0
  36. {figpack-0.4.0 → figpack-0.4.2}/figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  37. {figpack-0.4.0 → figpack-0.4.2}/figpack/utils/__init__.py +0 -0
  38. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Box.py +0 -0
  39. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/CaptionedView.py +0 -0
  40. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/DataFrame.py +0 -0
  41. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Gallery.py +0 -0
  42. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/GalleryItem.py +0 -0
  43. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Iframe.py +0 -0
  44. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Image.py +0 -0
  45. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/LayoutItem.py +0 -0
  46. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Markdown.py +0 -0
  47. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/MatplotlibFigure.py +0 -0
  48. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/MountainLayout.py +0 -0
  49. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/MountainLayoutItem.py +0 -0
  50. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/MultiChannelTimeseries.py +0 -0
  51. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/PlotlyExtension/PlotlyExtension.py +0 -0
  52. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/PlotlyExtension/__init__.py +0 -0
  53. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/PlotlyExtension/_plotly_extension.py +0 -0
  54. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/PlotlyExtension/plotly_view.js +0 -0
  55. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Spectrogram.py +0 -0
  56. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/Splitter.py +0 -0
  57. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/TabLayout.py +0 -0
  58. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/TabLayoutItem.py +0 -0
  59. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/VerticalLayout.py +0 -0
  60. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/VerticalLayoutItem.py +0 -0
  61. {figpack-0.4.0 → figpack-0.4.2}/figpack/views/__init__.py +0 -0
  62. {figpack-0.4.0 → figpack-0.4.2}/figpack.egg-info/dependency_links.txt +0 -0
  63. {figpack-0.4.0 → figpack-0.4.2}/figpack.egg-info/entry_points.txt +0 -0
  64. {figpack-0.4.0 → figpack-0.4.2}/figpack.egg-info/requires.txt +0 -0
  65. {figpack-0.4.0 → figpack-0.4.2}/figpack.egg-info/top_level.txt +0 -0
  66. {figpack-0.4.0 → figpack-0.4.2}/setup.cfg +0 -0
  67. {figpack-0.4.0 → figpack-0.4.2}/tests/test_box.py +0 -0
  68. {figpack-0.4.0 → figpack-0.4.2}/tests/test_cli.py +0 -0
  69. {figpack-0.4.0 → figpack-0.4.2}/tests/test_core.py +0 -0
  70. {figpack-0.4.0 → figpack-0.4.2}/tests/test_dataframe.py +0 -0
  71. {figpack-0.4.0 → figpack-0.4.2}/tests/test_extension_system.py +0 -0
  72. {figpack-0.4.0 → figpack-0.4.2}/tests/test_figpack_view.py +0 -0
  73. {figpack-0.4.0 → figpack-0.4.2}/tests/test_figure_spec.py +0 -0
  74. {figpack-0.4.0 → figpack-0.4.2}/tests/test_file_handler.py +0 -0
  75. {figpack-0.4.0 → figpack-0.4.2}/tests/test_gallery.py +0 -0
  76. {figpack-0.4.0 → figpack-0.4.2}/tests/test_image.py +0 -0
  77. {figpack-0.4.0 → figpack-0.4.2}/tests/test_markdown.py +0 -0
  78. {figpack-0.4.0 → figpack-0.4.2}/tests/test_matplotlib_figure.py +0 -0
  79. {figpack-0.4.0 → figpack-0.4.2}/tests/test_multichannel_timeseries.py +0 -0
  80. {figpack-0.4.0 → figpack-0.4.2}/tests/test_patch_pages.py +0 -0
  81. {figpack-0.4.0 → figpack-0.4.2}/tests/test_plotly_figure.py +0 -0
  82. {figpack-0.4.0 → figpack-0.4.2}/tests/test_server_manager.py +0 -0
  83. {figpack-0.4.0 → figpack-0.4.2}/tests/test_spectrogram.py +0 -0
  84. {figpack-0.4.0 → figpack-0.4.2}/tests/test_splitter.py +0 -0
  85. {figpack-0.4.0 → figpack-0.4.2}/tests/test_tablayout.py +0 -0
  86. {figpack-0.4.0 → figpack-0.4.2}/tests/test_timeseries_graph.py +0 -0
  87. {figpack-0.4.0 → figpack-0.4.2}/tests/test_upload_bundle.py +0 -0
  88. {figpack-0.4.0 → figpack-0.4.2}/tests/test_view_figure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figpack
3
- Version: 0.4.0
3
+ Version: 0.4.2
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
@@ -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.4.0"
5
+ __version__ = "0.4.2"
6
6
 
7
7
  from .cli import view_figure
8
8
  from .core import FigpackView, FigpackExtension, ExtensionView
@@ -3,6 +3,8 @@ import pathlib
3
3
  import json
4
4
  from typing import Optional, List
5
5
 
6
+ from ._remix_patch import RemixArg, apply_remix_patch
7
+
6
8
  import zarr
7
9
 
8
10
  from .figpack_view import FigpackView
@@ -26,6 +28,7 @@ def prepare_figure_bundle(
26
28
  title: str,
27
29
  description: Optional[str] = None,
28
30
  script: Optional[str] = None,
31
+ remix: Optional[RemixArg] = None,
29
32
  ) -> None:
30
33
  """
31
34
  Prepare a figure bundle in the specified temporary directory.
@@ -33,7 +36,8 @@ def prepare_figure_bundle(
33
36
  This function:
34
37
  1. Copies all files from the figpack-figure-dist directory to tmpdir
35
38
  2. Writes the view data to a zarr group
36
- 3. Writes the figure spec (layout and settings) into index.html
39
+ 3. Writes the figure spec (layout and settings) into index.html, with a
40
+ remix patch merged in if given
37
41
  4. Discovers and writes extension JavaScript files
38
42
  5. Consolidates zarr metadata
39
43
 
@@ -43,6 +47,8 @@ def prepare_figure_bundle(
43
47
  title: Title for the figure (required)
44
48
  description: Optional description for the figure (markdown supported)
45
49
  script: Optional script text used to generate the figure
50
+ remix: Optional remix patch (a file path or a loaded dict) to merge
51
+ into the figure spec
46
52
  """
47
53
  html_dir = thisdir / ".." / "figpack-figure-dist"
48
54
  if not os.path.exists(html_dir):
@@ -84,11 +90,17 @@ def prepare_figure_bundle(
84
90
  index_html_path = pathlib.Path(tmpdir) / "index.html"
85
91
  figure_spec = build_figure_spec(zarr_group._zarr_group)
86
92
  figure_spec_docs = build_figure_spec_docs(zarr_group._zarr_group)
93
+ remix_record = None
94
+ if remix is not None:
95
+ figure_spec, remix_record = apply_remix_patch(
96
+ figure_spec, remix, zarr_group._zarr_group
97
+ )
87
98
  index_html_path.write_text(
88
99
  insert_figure_spec_into_html(
89
100
  index_html_path.read_text(encoding="utf-8"),
90
101
  figure_spec,
91
102
  figure_spec_docs,
103
+ remix_record,
92
104
  ),
93
105
  encoding="utf-8",
94
106
  )
@@ -243,7 +243,10 @@ def _escape_for_script(text: str) -> str:
243
243
 
244
244
 
245
245
  def insert_figure_spec_into_html(
246
- html: str, spec: Dict[str, Any], docs: Optional[str] = None
246
+ html: str,
247
+ spec: Dict[str, Any],
248
+ docs: Optional[str] = None,
249
+ remix_record: Optional[Dict[str, Any]] = None,
247
250
  ) -> str:
248
251
  """
249
252
  Insert the spec reference and a figpack.render(spec) script at the end of
@@ -253,6 +256,9 @@ def insert_figure_spec_into_html(
253
256
  html: The contents of index.html
254
257
  spec: The figure spec
255
258
  docs: Optional markdown reference for the spec
259
+ remix_record: Optional record of a remix patch applied to the spec,
260
+ written as a <script type="application/json"
261
+ data-figpack-remix-patch> element
256
262
 
257
263
  Returns:
258
264
  The updated html
@@ -270,6 +276,12 @@ def insert_figure_spec_into_html(
270
276
  + _escape_for_script(docs)
271
277
  + " </script>\n"
272
278
  )
279
+ if remix_record is not None:
280
+ script += (
281
+ ' <script type="application/json" data-figpack-remix-patch>\n '
282
+ + _escape_for_script(format_spec_json(remix_record, indent=4))
283
+ + "\n </script>\n"
284
+ )
273
285
  script += (
274
286
  ' <script type="module" data-figpack-spec>\n'
275
287
  + f" figpack.render({spec_json}, {{ specVersion: {FIGURE_SPEC_VERSION} }});\n"
@@ -0,0 +1,228 @@
1
+ """
2
+ Remix patches: applying a remix's changes when a figure is made
3
+
4
+ A remix patch carries the changes a remix makes to a figure's spec, keyed by
5
+ the figure's structure (child names and props) rather than by the code that
6
+ built the figure. It is exported from the remix page (Export patch) and
7
+ applied with view.show(..., remix=...) or view.save(..., remix=...). Format
8
+ (version 1):
9
+
10
+ {
11
+ "figpack_remix_patch": 1,
12
+ "source": {"figureUrl": "...", "createdAt": "2026-10-23T12:00:00.000Z"},
13
+ "patch": <spec diff>
14
+ }
15
+
16
+ The spec diff has the shape of a spec node, {data?, view_type?, props?,
17
+ children?}, with only what the remix changed. It is merged into the figure's
18
+ spec with the same rules as the spec itself:
19
+
20
+ - props replace per key (whole values; lists and objects are not merged)
21
+ - children merge by name
22
+ - data and view_type replace; a data path must exist in data.zarr
23
+
24
+ Parts that do not match the figure are reported as warnings and skipped: a
25
+ child that is neither in the spec nor a subgroup in data.zarr (unless the
26
+ patch gives it its own data), a data path that does not exist, or a value of
27
+ the wrong type. The figpack-manage remix page applies patches with the same
28
+ rules (figpack-manage/src/pages/RemixPage/remixPatch.ts); both are tested
29
+ against the shared cases in tests/remix_patch_cases. See
30
+ docs/remix_patches.md.
31
+ """
32
+
33
+ import copy
34
+ import json
35
+ import os
36
+ import pathlib
37
+ from typing import Any, Dict, List, Optional, Set, Tuple, Union
38
+
39
+ REMIX_PATCH_VERSION = 1
40
+
41
+ _NODE_KEYS = {"data", "view_type", "props", "children"}
42
+
43
+ RemixArg = Union[str, os.PathLike, Dict[str, Any]]
44
+
45
+
46
+ def load_remix_patch(remix: RemixArg) -> Tuple[Dict[str, Any], Optional[str]]:
47
+ """
48
+ Load a remix patch from a file path, or check an already-loaded dict
49
+
50
+ Args:
51
+ remix: Path to a patch file, or the patch as a dict
52
+
53
+ Returns:
54
+ (patch, file name or None)
55
+ """
56
+ file_name = None
57
+ if isinstance(remix, dict):
58
+ patch = remix
59
+ else:
60
+ path = pathlib.Path(remix)
61
+ file_name = path.name
62
+ patch = json.loads(path.read_text(encoding="utf-8"))
63
+ if not isinstance(patch, dict) or "figpack_remix_patch" not in patch:
64
+ raise ValueError("Not a remix patch (missing figpack_remix_patch)")
65
+ version = patch["figpack_remix_patch"]
66
+ if not isinstance(version, int) or version > REMIX_PATCH_VERSION:
67
+ raise ValueError(
68
+ f"Remix patch version {version} is not supported by this figpack "
69
+ f"(supports {REMIX_PATCH_VERSION}); upgrade figpack"
70
+ )
71
+ if not isinstance(patch.get("patch"), dict):
72
+ raise ValueError("Remix patch has no 'patch' object")
73
+ return patch, file_name
74
+
75
+
76
+ def get_group_paths(zarr_group) -> Set[str]:
77
+ """The paths of all groups in a zarr tree, relative to its root ("" for the root)"""
78
+ paths = {""}
79
+
80
+ def visit(group, prefix):
81
+ for name, subgroup in group.groups():
82
+ path = prefix + name
83
+ paths.add(path)
84
+ visit(subgroup, path + "/")
85
+
86
+ visit(zarr_group, "")
87
+ return paths
88
+
89
+
90
+ def merge_remix_patch(
91
+ spec: Dict[str, Any], patch: Dict[str, Any], group_paths: Set[str]
92
+ ) -> Tuple[Dict[str, Any], List[str]]:
93
+ """
94
+ Merge a spec diff into a figure spec
95
+
96
+ Args:
97
+ spec: The figure spec (not modified)
98
+ patch: The spec diff (the "patch" field of a remix patch)
99
+ group_paths: Paths of the groups in data.zarr (see get_group_paths)
100
+
101
+ Returns:
102
+ (merged spec, warnings for the parts that were skipped)
103
+ """
104
+ warnings: List[str] = []
105
+ merged = _merge_node(spec, patch, "", "", group_paths, warnings)
106
+ return merged, warnings
107
+
108
+
109
+ def _normalize_path(path: str) -> str:
110
+ return path.strip("/")
111
+
112
+
113
+ def _where(spec_path: str) -> str:
114
+ return spec_path or "root"
115
+
116
+
117
+ def _merge_node(
118
+ node: Dict[str, Any],
119
+ patch: Dict[str, Any],
120
+ base: Optional[str],
121
+ spec_path: str,
122
+ group_paths: Set[str],
123
+ warnings: List[str],
124
+ ) -> Dict[str, Any]:
125
+ # base: path of the node's counterpart group in data.zarr, if any
126
+ where = _where(spec_path)
127
+ result = copy.deepcopy(node)
128
+ for key in patch:
129
+ if key not in _NODE_KEYS:
130
+ warnings.append(f"remix: unknown key '{key}' at '{where}'; skipped")
131
+ if "data" in patch:
132
+ data = patch["data"]
133
+ if data is None:
134
+ result["data"] = None
135
+ elif isinstance(data, str) and _normalize_path(data) in group_paths:
136
+ result["data"] = data
137
+ elif isinstance(data, str):
138
+ warnings.append(
139
+ f"remix: data path '{data}' at '{where}' is not in data.zarr; skipped"
140
+ )
141
+ else:
142
+ warnings.append(f"remix: data at '{where}' is not a string; skipped")
143
+ if "view_type" in patch:
144
+ if isinstance(patch["view_type"], str):
145
+ result["view_type"] = patch["view_type"]
146
+ else:
147
+ warnings.append(f"remix: view_type at '{where}' is not a string; skipped")
148
+ if "props" in patch:
149
+ if isinstance(patch["props"], dict):
150
+ result["props"] = {**result.get("props", {}), **patch["props"]}
151
+ else:
152
+ warnings.append(f"remix: props at '{where}' is not an object; skipped")
153
+ if "data" in result:
154
+ base = None if result["data"] is None else _normalize_path(result["data"])
155
+ if "children" in patch:
156
+ if isinstance(patch["children"], dict):
157
+ children = result.get("children", {})
158
+ for name, child_patch in patch["children"].items():
159
+ child_path = f"{spec_path}/{name}" if spec_path else name
160
+ if not isinstance(child_patch, dict):
161
+ warnings.append(
162
+ f"remix: child '{_where(child_path)}' is not an object; skipped"
163
+ )
164
+ continue
165
+ counterpart = None
166
+ if base is not None:
167
+ p = f"{base}/{name}" if base else name
168
+ counterpart = p if p in group_paths else None
169
+ if name not in children and counterpart is None:
170
+ if not _has_own_data(child_patch, group_paths):
171
+ warnings.append(
172
+ f"remix: no child '{name}' under '{where}'; skipped"
173
+ )
174
+ continue
175
+ children[name] = _merge_node(
176
+ children.get(name, {}),
177
+ child_patch,
178
+ counterpart,
179
+ child_path,
180
+ group_paths,
181
+ warnings,
182
+ )
183
+ if children:
184
+ result["children"] = children
185
+ else:
186
+ warnings.append(f"remix: children at '{where}' is not an object; skipped")
187
+ return result
188
+
189
+
190
+ def _has_own_data(patch: Dict[str, Any], group_paths: Set[str]) -> bool:
191
+ # Whether a patch node defines its own base: data null (a node defined
192
+ # only by the spec) or an existing data path
193
+ if "data" not in patch:
194
+ return False
195
+ data = patch["data"]
196
+ return data is None or (
197
+ isinstance(data, str) and _normalize_path(data) in group_paths
198
+ )
199
+
200
+
201
+ def apply_remix_patch(
202
+ spec: Dict[str, Any], remix: RemixArg, zarr_group
203
+ ) -> Tuple[Dict[str, Any], Dict[str, Any]]:
204
+ """
205
+ Apply a remix patch to a figure spec, printing a warning for each part
206
+ that was skipped
207
+
208
+ Args:
209
+ spec: The figure spec built from the zarr tree
210
+ remix: Path to a patch file, or the patch as a dict
211
+ zarr_group: The (raw) root zarr group of the figure
212
+
213
+ Returns:
214
+ (merged spec, record of the applied patch for index.html)
215
+ """
216
+ patch, file_name = load_remix_patch(remix)
217
+ merged, warnings = merge_remix_patch(
218
+ spec, patch["patch"], get_group_paths(zarr_group)
219
+ )
220
+ for w in warnings:
221
+ print(f"Warning: {w}")
222
+ record = {
223
+ "file": file_name,
224
+ "source": patch.get("source"),
225
+ "patch": patch["patch"],
226
+ "skipped": warnings,
227
+ }
228
+ return merged, record
@@ -1,7 +1,9 @@
1
1
  import pathlib
2
2
  import tempfile
3
+ from typing import Optional
3
4
 
4
5
  from ._bundle_utils import prepare_figure_bundle
6
+ from ._remix_patch import RemixArg
5
7
  from .figpack_view import FigpackView
6
8
 
7
9
 
@@ -12,6 +14,7 @@ def _save_figure(
12
14
  title: str,
13
15
  description: str = "",
14
16
  script: str = "",
17
+ remix: Optional[RemixArg] = None,
15
18
  ) -> None:
16
19
  """
17
20
  Save the figure to a folder or a .tar.gz file
@@ -22,6 +25,7 @@ def _save_figure(
22
25
  title: Title for the figure
23
26
  description: Description text with markdown support
24
27
  script: Optional script text used to generate the figure
28
+ remix: Optional remix patch to apply (a file path or a loaded dict)
25
29
  """
26
30
  output_path_2 = pathlib.Path(output_path)
27
31
  if (output_path_2.suffix == ".gz" and output_path_2.suffixes[-2] == ".tar") or (
@@ -30,7 +34,12 @@ def _save_figure(
30
34
  # It's a .tar.gz file
31
35
  with tempfile.TemporaryDirectory(prefix="figpack_save_") as tmpdir:
32
36
  prepare_figure_bundle(
33
- view, tmpdir, title=title, description=description, script=script
37
+ view,
38
+ tmpdir,
39
+ title=title,
40
+ description=description,
41
+ script=script,
42
+ remix=remix,
34
43
  )
35
44
  # Create tar.gz file
36
45
  import tarfile
@@ -46,4 +55,5 @@ def _save_figure(
46
55
  title=title,
47
56
  description=description,
48
57
  script=script,
58
+ remix=remix,
49
59
  )
@@ -5,6 +5,7 @@ import webbrowser
5
5
  from typing import Union
6
6
 
7
7
  from ._bundle_utils import prepare_figure_bundle
8
+ from ._remix_patch import RemixArg
8
9
  from ._server_manager import ProcessServerManager
9
10
  from ._upload_bundle import _upload_bundle
10
11
  from .figpack_view import FigpackView
@@ -92,6 +93,7 @@ def _show_view(
92
93
  title: str,
93
94
  description: Union[str, None],
94
95
  script: Union[str, None],
96
+ remix: Union[RemixArg, None],
95
97
  inline: bool,
96
98
  inline_height: int,
97
99
  wait_for_input: bool,
@@ -101,7 +103,12 @@ def _show_view(
101
103
  # Upload behavior: create temporary directory for this upload only
102
104
  with tempfile.TemporaryDirectory(prefix="figpack_upload_") as tmpdir:
103
105
  prepare_figure_bundle(
104
- view, tmpdir, title=title, description=description, script=script
106
+ view,
107
+ tmpdir,
108
+ title=title,
109
+ description=description,
110
+ script=script,
111
+ remix=remix,
105
112
  )
106
113
 
107
114
  # Check for API key - required for regular uploads, optional for ephemeral
@@ -146,7 +153,12 @@ def _show_view(
146
153
 
147
154
  # Prepare the figure bundle in the subdirectory
148
155
  prepare_figure_bundle(
149
- view, str(figure_dir), title=title, description=description, script=script
156
+ view,
157
+ str(figure_dir),
158
+ title=title,
159
+ description=description,
160
+ script=script,
161
+ remix=remix,
150
162
  )
151
163
 
152
164
  # Start or get existing server
@@ -5,10 +5,13 @@ Base view class for figpack visualization components
5
5
  import os
6
6
  import random
7
7
  import string
8
- from typing import Optional
8
+ from typing import TYPE_CHECKING, Optional
9
9
 
10
10
  from .zarr import Group
11
11
 
12
+ if TYPE_CHECKING:
13
+ from ._remix_patch import RemixArg
14
+
12
15
 
13
16
  class FigpackView:
14
17
  """
@@ -21,6 +24,7 @@ class FigpackView:
21
24
  title: str,
22
25
  description: Optional[str] = None,
23
26
  script: Optional[str] = None,
27
+ remix: Optional["RemixArg"] = None,
24
28
  port: Optional[int] = None,
25
29
  open_in_browser: Optional[bool] = None,
26
30
  upload: Optional[bool] = None,
@@ -43,6 +47,9 @@ class FigpackView:
43
47
  title: Title for browser tab and figure (required)
44
48
  description: Description text with markdown support (optional)
45
49
  script: Optional script text used to generate the figure
50
+ remix: Optional remix patch to apply to the figure spec: a path to a
51
+ patch file exported from the remix page, or the loaded dict (see
52
+ https://flatironinstitute.github.io/figpack/remix_patches.html)
46
53
  port: Local server port, random if None
47
54
  open_in_browser: Auto-open in browser, auto-detects by environment
48
55
  upload: Upload figure to figpack servers, auto-detects by environment
@@ -164,6 +171,7 @@ class FigpackView:
164
171
  title=title,
165
172
  description=description,
166
173
  script=script,
174
+ remix=remix,
167
175
  inline=inline,
168
176
  inline_height=inline_height,
169
177
  wait_for_input=wait_for_input,
@@ -171,7 +179,13 @@ class FigpackView:
171
179
  )
172
180
 
173
181
  def save(
174
- self, output_path: str, *, title: str, description: str = "", script: str = ""
182
+ self,
183
+ output_path: str,
184
+ *,
185
+ title: str,
186
+ description: str = "",
187
+ script: str = "",
188
+ remix: Optional["RemixArg"] = None,
175
189
  ) -> None:
176
190
  """
177
191
  Save as figure either to a folder or to a .tar.gz file
@@ -180,11 +194,18 @@ class FigpackView:
180
194
  title: Title for the figure
181
195
  description: Description text with markdown support
182
196
  script: Optional script text used to generate the figure
197
+ remix: Optional remix patch to apply to the figure spec: a path to a
198
+ patch file exported from the remix page, or the loaded dict
183
199
  """
184
200
  from ._save_figure import _save_figure
185
201
 
186
202
  _save_figure(
187
- self, output_path, title=title, description=description, script=script
203
+ self,
204
+ output_path,
205
+ title=title,
206
+ description=description,
207
+ script=script,
208
+ remix=remix,
188
209
  )
189
210
 
190
211
  def write_to_zarr_group(self, group: Group) -> None: