figpack 0.2.7__tar.gz → 0.2.9__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.

Potentially problematic release.


This version of figpack might be problematic. Click here for more details.

Files changed (95) hide show
  1. {figpack-0.2.7/figpack.egg-info → figpack-0.2.9}/PKG-INFO +1 -1
  2. figpack-0.2.9/figpack/__init__.py +16 -0
  3. figpack-0.2.9/figpack/core/__init__.py +5 -0
  4. figpack-0.2.9/figpack/core/_bundle_utils.py +180 -0
  5. figpack-0.2.9/figpack/core/extension_view.py +53 -0
  6. figpack-0.2.9/figpack/core/figpack_extension.py +137 -0
  7. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/figpack_view.py +1 -1
  8. figpack-0.2.7/figpack/figpack-figure-dist/assets/index-CTBd5_Gw.js → figpack-0.2.9/figpack/figpack-figure-dist/assets/index-BBJJgcTp.js +87 -87
  9. {figpack-0.2.7 → figpack-0.2.9}/figpack/figpack-figure-dist/index.html +1 -1
  10. figpack-0.2.9/figpack/spike_sorting/views/RasterPlot.py +288 -0
  11. figpack-0.2.9/figpack/spike_sorting/views/SpikeAmplitudes.py +374 -0
  12. figpack-0.2.9/figpack/views/Spectrogram.py +210 -0
  13. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/__init__.py +1 -0
  14. {figpack-0.2.7 → figpack-0.2.9/figpack.egg-info}/PKG-INFO +1 -1
  15. {figpack-0.2.7 → figpack-0.2.9}/figpack.egg-info/SOURCES.txt +6 -1
  16. {figpack-0.2.7 → figpack-0.2.9}/pyproject.toml +1 -1
  17. figpack-0.2.9/tests/test_extension_system.py +233 -0
  18. {figpack-0.2.7 → figpack-0.2.9}/tests/test_raster_plot.py +2 -19
  19. figpack-0.2.9/tests/test_spectrogram.py +162 -0
  20. figpack-0.2.9/tests/test_spike_amplitudes.py +202 -0
  21. figpack-0.2.7/figpack/__init__.py +0 -9
  22. figpack-0.2.7/figpack/core/__init__.py +0 -0
  23. figpack-0.2.7/figpack/core/_bundle_utils.py +0 -57
  24. figpack-0.2.7/figpack/spike_sorting/views/RasterPlot.py +0 -77
  25. figpack-0.2.7/figpack/spike_sorting/views/SpikeAmplitudes.py +0 -89
  26. figpack-0.2.7/tests/test_spike_amplitudes.py +0 -110
  27. {figpack-0.2.7 → figpack-0.2.9}/LICENSE +0 -0
  28. {figpack-0.2.7 → figpack-0.2.9}/MANIFEST.in +0 -0
  29. {figpack-0.2.7 → figpack-0.2.9}/README.md +0 -0
  30. {figpack-0.2.7 → figpack-0.2.9}/figpack/cli.py +0 -0
  31. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/_save_figure.py +0 -0
  32. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/_server_manager.py +0 -0
  33. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/_show_view.py +0 -0
  34. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/_upload_bundle.py +0 -0
  35. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/_view_figure.py +0 -0
  36. {figpack-0.2.7 → figpack-0.2.9}/figpack/core/config.py +0 -0
  37. {figpack-0.2.7 → figpack-0.2.9}/figpack/figpack-figure-dist/assets/index-Cmae55E4.css +0 -0
  38. {figpack-0.2.7 → figpack-0.2.9}/figpack/figpack-figure-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  39. {figpack-0.2.7 → figpack-0.2.9}/figpack/franklab/__init__.py +0 -0
  40. {figpack-0.2.7 → figpack-0.2.9}/figpack/franklab/views/TrackAnimation.py +0 -0
  41. {figpack-0.2.7 → figpack-0.2.9}/figpack/franklab/views/__init__.py +0 -0
  42. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/__init__.py +0 -0
  43. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/AutocorrelogramItem.py +0 -0
  44. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/Autocorrelograms.py +0 -0
  45. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/AverageWaveforms.py +0 -0
  46. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/CrossCorrelogramItem.py +0 -0
  47. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/CrossCorrelograms.py +0 -0
  48. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/RasterPlotItem.py +0 -0
  49. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/SpikeAmplitudesItem.py +0 -0
  50. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/UnitSimilarityScore.py +0 -0
  51. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/UnitsTable.py +0 -0
  52. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/UnitsTableColumn.py +0 -0
  53. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/UnitsTableRow.py +0 -0
  54. {figpack-0.2.7 → figpack-0.2.9}/figpack/spike_sorting/views/__init__.py +0 -0
  55. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/Box.py +0 -0
  56. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/DataFrame.py +0 -0
  57. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/Gallery.py +0 -0
  58. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/GalleryItem.py +0 -0
  59. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/Image.py +0 -0
  60. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/LayoutItem.py +0 -0
  61. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/Markdown.py +0 -0
  62. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/MatplotlibFigure.py +0 -0
  63. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/MultiChannelTimeseries.py +0 -0
  64. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/PlotlyFigure.py +0 -0
  65. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/Splitter.py +0 -0
  66. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/TabLayout.py +0 -0
  67. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/TabLayoutItem.py +0 -0
  68. {figpack-0.2.7 → figpack-0.2.9}/figpack/views/TimeseriesGraph.py +0 -0
  69. {figpack-0.2.7 → figpack-0.2.9}/figpack.egg-info/dependency_links.txt +0 -0
  70. {figpack-0.2.7 → figpack-0.2.9}/figpack.egg-info/entry_points.txt +0 -0
  71. {figpack-0.2.7 → figpack-0.2.9}/figpack.egg-info/requires.txt +0 -0
  72. {figpack-0.2.7 → figpack-0.2.9}/figpack.egg-info/top_level.txt +0 -0
  73. {figpack-0.2.7 → figpack-0.2.9}/setup.cfg +0 -0
  74. {figpack-0.2.7 → figpack-0.2.9}/tests/test_average_waveforms.py +0 -0
  75. {figpack-0.2.7 → figpack-0.2.9}/tests/test_box.py +0 -0
  76. {figpack-0.2.7 → figpack-0.2.9}/tests/test_cli.py +0 -0
  77. {figpack-0.2.7 → figpack-0.2.9}/tests/test_core.py +0 -0
  78. {figpack-0.2.7 → figpack-0.2.9}/tests/test_dataframe.py +0 -0
  79. {figpack-0.2.7 → figpack-0.2.9}/tests/test_figpack_view.py +0 -0
  80. {figpack-0.2.7 → figpack-0.2.9}/tests/test_gallery.py +0 -0
  81. {figpack-0.2.7 → figpack-0.2.9}/tests/test_image.py +0 -0
  82. {figpack-0.2.7 → figpack-0.2.9}/tests/test_markdown.py +0 -0
  83. {figpack-0.2.7 → figpack-0.2.9}/tests/test_matplotlib_figure.py +0 -0
  84. {figpack-0.2.7 → figpack-0.2.9}/tests/test_multichannel_timeseries.py +0 -0
  85. {figpack-0.2.7 → figpack-0.2.9}/tests/test_plotly_figure.py +0 -0
  86. {figpack-0.2.7 → figpack-0.2.9}/tests/test_server_manager.py +0 -0
  87. {figpack-0.2.7 → figpack-0.2.9}/tests/test_show_view.py +0 -0
  88. {figpack-0.2.7 → figpack-0.2.9}/tests/test_spike_sorting_correlograms.py +0 -0
  89. {figpack-0.2.7 → figpack-0.2.9}/tests/test_splitter.py +0 -0
  90. {figpack-0.2.7 → figpack-0.2.9}/tests/test_tablayout.py +0 -0
  91. {figpack-0.2.7 → figpack-0.2.9}/tests/test_timeseries_graph.py +0 -0
  92. {figpack-0.2.7 → figpack-0.2.9}/tests/test_track_animation.py +0 -0
  93. {figpack-0.2.7 → figpack-0.2.9}/tests/test_units_table.py +0 -0
  94. {figpack-0.2.7 → figpack-0.2.9}/tests/test_upload_bundle.py +0 -0
  95. {figpack-0.2.7 → figpack-0.2.9}/tests/test_view_figure.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: figpack
3
- Version: 0.2.7
3
+ Version: 0.2.9
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
@@ -0,0 +1,16 @@
1
+ """
2
+ figpack - A Python package for creating shareable, interactive visualizations in the browser
3
+ """
4
+
5
+ __version__ = "0.2.9"
6
+
7
+ from .cli import view_figure
8
+ from .core import FigpackView, FigpackExtension, ExtensionRegistry, ExtensionView
9
+
10
+ __all__ = [
11
+ "view_figure",
12
+ "FigpackView",
13
+ "FigpackExtension",
14
+ "ExtensionRegistry",
15
+ "ExtensionView",
16
+ ]
@@ -0,0 +1,5 @@
1
+ from .figpack_view import FigpackView
2
+ from .figpack_extension import FigpackExtension, ExtensionRegistry
3
+ from .extension_view import ExtensionView
4
+
5
+ __all__ = ["FigpackView", "FigpackExtension", "ExtensionRegistry", "ExtensionView"]
@@ -0,0 +1,180 @@
1
+ import os
2
+ import pathlib
3
+ from typing import Set
4
+
5
+ import zarr
6
+
7
+ from .figpack_view import FigpackView
8
+ from .figpack_extension import ExtensionRegistry
9
+ from .extension_view import ExtensionView
10
+
11
+ thisdir = pathlib.Path(__file__).parent.resolve()
12
+
13
+
14
+ def prepare_figure_bundle(
15
+ view: FigpackView, tmpdir: str, *, title: str, description: str = None
16
+ ) -> None:
17
+ """
18
+ Prepare a figure bundle in the specified temporary directory.
19
+
20
+ This function:
21
+ 1. Copies all files from the figpack-figure-dist directory to tmpdir
22
+ 2. Writes the view data to a zarr group
23
+ 3. Discovers and writes extension JavaScript files
24
+ 4. Consolidates zarr metadata
25
+
26
+ Args:
27
+ view: The figpack view to prepare
28
+ tmpdir: The temporary directory to prepare the bundle in
29
+ title: Title for the figure (required)
30
+ description: Optional description for the figure (markdown supported)
31
+ """
32
+ html_dir = thisdir / ".." / "figpack-figure-dist"
33
+ if not os.path.exists(html_dir):
34
+ raise SystemExit(f"Error: directory not found: {html_dir}")
35
+
36
+ # Copy all files in html_dir recursively to tmpdir
37
+ for item in html_dir.iterdir():
38
+ if item.is_file():
39
+ target = pathlib.Path(tmpdir) / item.name
40
+ target.write_bytes(item.read_bytes())
41
+ elif item.is_dir():
42
+ target = pathlib.Path(tmpdir) / item.name
43
+ target.mkdir(exist_ok=True)
44
+ for subitem in item.iterdir():
45
+ target_sub = target / subitem.name
46
+ target_sub.write_bytes(subitem.read_bytes())
47
+
48
+ # Write the view data to the Zarr group
49
+ zarr_group = zarr.open_group(
50
+ pathlib.Path(tmpdir) / "data.zarr",
51
+ mode="w",
52
+ synchronizer=zarr.ThreadSynchronizer(),
53
+ )
54
+ view._write_to_zarr_group(zarr_group)
55
+
56
+ # Add title and description as attributes on the top-level zarr group
57
+ zarr_group.attrs["title"] = title
58
+ if description is not None:
59
+ zarr_group.attrs["description"] = description
60
+
61
+ # Discover and write extension JavaScript files
62
+ required_extensions = _discover_required_extensions(view)
63
+ _write_extension_files(required_extensions, tmpdir)
64
+
65
+ zarr.consolidate_metadata(zarr_group.store)
66
+
67
+
68
+ def _discover_required_extensions(view: FigpackView) -> Set[str]:
69
+ """
70
+ Recursively discover all extensions required by a view and its children
71
+
72
+ Args:
73
+ view: The root view to analyze
74
+
75
+ Returns:
76
+ Set of extension names required by this view hierarchy
77
+ """
78
+ extensions = set()
79
+ visited = set() # Prevent infinite recursion
80
+
81
+ def _collect_extensions(v: FigpackView):
82
+ # Prevent infinite recursion
83
+ if id(v) in visited:
84
+ return
85
+ visited.add(id(v))
86
+
87
+ # Check if this view is an extension view
88
+ if isinstance(v, ExtensionView):
89
+ extensions.add(v.extension_name)
90
+
91
+ # Recursively check all attributes that might contain child views
92
+ for attr_name in dir(v):
93
+ if attr_name.startswith("_"):
94
+ continue
95
+
96
+ try:
97
+ attr_value = getattr(v, attr_name)
98
+
99
+ # Handle single child view
100
+ if isinstance(attr_value, FigpackView):
101
+ _collect_extensions(attr_value)
102
+
103
+ # Handle lists/tuples of items that might contain views
104
+ elif isinstance(attr_value, (list, tuple)):
105
+ for item in attr_value:
106
+ # Check if item has a 'view' attribute (like LayoutItem)
107
+ if hasattr(item, "view") and isinstance(item.view, FigpackView):
108
+ _collect_extensions(item.view)
109
+ # Or if the item itself is a view
110
+ elif isinstance(item, FigpackView):
111
+ _collect_extensions(item)
112
+
113
+ # Handle objects that might have a 'view' attribute
114
+ elif hasattr(attr_value, "view") and isinstance(
115
+ attr_value.view, FigpackView
116
+ ):
117
+ _collect_extensions(attr_value.view)
118
+
119
+ except (AttributeError, TypeError):
120
+ # Skip attributes that can't be accessed or aren't relevant
121
+ continue
122
+
123
+ _collect_extensions(view)
124
+ return extensions
125
+
126
+
127
+ def _write_extension_files(extension_names: Set[str], tmpdir: str) -> None:
128
+ """
129
+ Write JavaScript files for the required extensions
130
+
131
+ Args:
132
+ extension_names: Set of extension names to write
133
+ tmpdir: Directory to write extension files to
134
+ """
135
+ if not extension_names:
136
+ return
137
+
138
+ registry = ExtensionRegistry.get_instance()
139
+ tmpdir_path = pathlib.Path(tmpdir)
140
+
141
+ for extension_name in extension_names:
142
+ extension = registry.get_extension(extension_name)
143
+ if extension is None:
144
+ raise RuntimeError(
145
+ f"Extension '{extension_name}' is required but not registered"
146
+ )
147
+
148
+ # Write the main JavaScript file
149
+ js_filename = extension.get_javascript_filename()
150
+ js_path = tmpdir_path / js_filename
151
+
152
+ # Add some metadata as comments at the top
153
+ js_content = f"""/*
154
+ * Figpack Extension: {extension.name}
155
+ * Version: {extension.version}
156
+ * Generated automatically - do not edit
157
+ */
158
+
159
+ {extension.javascript_code}
160
+ """
161
+
162
+ js_path.write_text(js_content, encoding="utf-8")
163
+
164
+ # Write additional JavaScript files
165
+ additional_filenames = extension.get_additional_filenames()
166
+ for original_name, safe_filename in additional_filenames.items():
167
+ additional_content = extension.additional_files[original_name]
168
+ additional_path = tmpdir_path / safe_filename
169
+
170
+ # Add metadata header to additional files too
171
+ additional_js_content = f"""/*
172
+ * Figpack Extension Additional File: {extension.name}/{original_name}
173
+ * Version: {extension.version}
174
+ * Generated automatically - do not edit
175
+ */
176
+
177
+ {additional_content}
178
+ """
179
+
180
+ additional_path.write_text(additional_js_content, encoding="utf-8")
@@ -0,0 +1,53 @@
1
+ """
2
+ Base class for views that use figpack extensions
3
+ """
4
+
5
+ import zarr
6
+ from .figpack_view import FigpackView
7
+ from .figpack_extension import ExtensionRegistry
8
+
9
+
10
+ class ExtensionView(FigpackView):
11
+ """
12
+ Base class for views that are rendered by figpack extensions
13
+ """
14
+
15
+ def __init__(self, *, extension_name: str):
16
+ """
17
+ Initialize an extension-based view
18
+
19
+ Args:
20
+ extension_name: Name of the extension that will render this view
21
+ """
22
+ super().__init__()
23
+ self.extension_name = extension_name
24
+
25
+ # Validate that the extension is registered
26
+ registry = ExtensionRegistry.get_instance()
27
+ extension = registry.get_extension(extension_name)
28
+ if extension is None:
29
+ raise ValueError(
30
+ f"Extension '{extension_name}' is not registered. "
31
+ f"Make sure to register the extension before creating views that use it."
32
+ )
33
+
34
+ def _write_to_zarr_group(self, group: zarr.Group) -> None:
35
+ """
36
+ Write the extension view metadata to a Zarr group.
37
+ Subclasses should call super()._write_to_zarr_group(group) first,
38
+ then add their own data.
39
+
40
+ Args:
41
+ group: Zarr group to write data into
42
+ """
43
+ # Set the view type to indicate this is an extension view
44
+ group.attrs["view_type"] = "ExtensionView"
45
+
46
+ # Store the extension name so the frontend knows which extension to use
47
+ group.attrs["extension_name"] = self.extension_name
48
+
49
+ # Store extension metadata for debugging/compatibility
50
+ registry = ExtensionRegistry.get_instance()
51
+ extension = registry.get_extension(self.extension_name)
52
+ if extension:
53
+ group.attrs["extension_version"] = extension.version
@@ -0,0 +1,137 @@
1
+ """
2
+ Extension system for figpack - allows runtime loading of custom view components
3
+ """
4
+
5
+ from typing import Dict, Optional
6
+
7
+
8
+ class FigpackExtension:
9
+ """
10
+ Base class for figpack extensions that provide custom view components
11
+ """
12
+
13
+ def __init__(
14
+ self,
15
+ *,
16
+ name: str,
17
+ javascript_code: str,
18
+ additional_files: Optional[Dict[str, str]] = None,
19
+ version: str = "1.0.0",
20
+ ):
21
+ """
22
+ Initialize a figpack extension
23
+
24
+ Args:
25
+ name: Unique name for the extension (used as identifier)
26
+ javascript_code: JavaScript code that implements the extension
27
+ additional_files: Optional dictionary of additional JavaScript files
28
+ {filename: content} that the extension can load
29
+ version: Version string for compatibility tracking
30
+ """
31
+ self.name = name
32
+ self.javascript_code = javascript_code
33
+ self.additional_files = additional_files or {}
34
+ self.version = version
35
+
36
+ # Validate extension name
37
+ if not name or not isinstance(name, str):
38
+ raise ValueError("Extension name must be a non-empty string")
39
+
40
+ # Basic validation of JavaScript code
41
+ if not javascript_code or not isinstance(javascript_code, str):
42
+ raise ValueError("Extension javascript_code must be a non-empty string")
43
+
44
+ def get_javascript_filename(self) -> str:
45
+ """
46
+ Get the filename that should be used for this extension's JavaScript file
47
+
48
+ Returns:
49
+ Filename for the extension JavaScript file
50
+ """
51
+ # Sanitize the name for use as a filename
52
+ safe_name = "".join(c for c in self.name if c.isalnum() or c in "-_")
53
+ return f"extension-{safe_name}.js"
54
+
55
+ def get_additional_filenames(self) -> Dict[str, str]:
56
+ """
57
+ Get the filenames for additional JavaScript files
58
+
59
+ Returns:
60
+ Dictionary mapping original filenames to safe filenames
61
+ """
62
+ safe_name = "".join(c for c in self.name if c.isalnum() or c in "-_")
63
+ return {
64
+ original_name: f"extension-{safe_name}-{original_name}"
65
+ for original_name in self.additional_files.keys()
66
+ }
67
+
68
+
69
+ class ExtensionRegistry:
70
+ """
71
+ Singleton registry for managing figpack extensions
72
+ """
73
+
74
+ _instance: Optional["ExtensionRegistry"] = None
75
+
76
+ def __init__(self):
77
+ self._extensions: Dict[str, FigpackExtension] = {}
78
+
79
+ @classmethod
80
+ def get_instance(cls) -> "ExtensionRegistry":
81
+ """Get the singleton instance of the extension registry"""
82
+ if cls._instance is None:
83
+ cls._instance = cls()
84
+ return cls._instance
85
+
86
+ @classmethod
87
+ def register(cls, extension: FigpackExtension) -> None:
88
+ """
89
+ Register an extension with the global registry
90
+
91
+ Args:
92
+ extension: The extension to register
93
+ """
94
+ registry = cls.get_instance()
95
+ registry._register_extension(extension)
96
+
97
+ def _register_extension(self, extension: FigpackExtension) -> None:
98
+ """
99
+ Internal method to register an extension
100
+
101
+ Args:
102
+ extension: The extension to register
103
+ """
104
+ if extension.name in self._extensions:
105
+ existing = self._extensions[extension.name]
106
+ if existing.version != extension.version:
107
+ print(
108
+ f"Warning: Replacing extension '{extension.name}' "
109
+ f"version {existing.version} with version {extension.version}"
110
+ )
111
+
112
+ self._extensions[extension.name] = extension
113
+
114
+ def get_extension(self, name: str) -> Optional[FigpackExtension]:
115
+ """
116
+ Get an extension by name
117
+
118
+ Args:
119
+ name: Name of the extension to retrieve
120
+
121
+ Returns:
122
+ The extension if found, None otherwise
123
+ """
124
+ return self._extensions.get(name)
125
+
126
+ def get_all_extensions(self) -> Dict[str, FigpackExtension]:
127
+ """
128
+ Get all registered extensions
129
+
130
+ Returns:
131
+ Dictionary mapping extension names to extension objects
132
+ """
133
+ return self._extensions.copy()
134
+
135
+ def clear(self) -> None:
136
+ """Clear all registered extensions (mainly for testing)"""
137
+ self._extensions.clear()
@@ -121,7 +121,7 @@ class FigpackView:
121
121
  )
122
122
  print("** Development mode **")
123
123
  print(
124
- f"For development, run figpack-figure in dev mode and use http://localhost:5173?data=http://localhost:{port}/{_local_figure_name}/data.zarr"
124
+ f"For development, run figpack-figure in dev mode and use http://localhost:5173?figure=http://localhost:{port}/{_local_figure_name}/"
125
125
  )
126
126
  print("")
127
127