figpack 0.1.1__tar.gz → 0.1.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.

Potentially problematic release.


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

Files changed (36) hide show
  1. figpack-0.1.2/PKG-INFO +104 -0
  2. figpack-0.1.2/README.md +73 -0
  3. {figpack-0.1.1 → figpack-0.1.2}/figpack/__init__.py +1 -1
  4. {figpack-0.1.1 → figpack-0.1.2}/figpack/core/_bundle_utils.py +11 -1
  5. {figpack-0.1.1 → figpack-0.1.2}/figpack/core/_show_view.py +34 -8
  6. figpack-0.1.1/figpack/core/_upload_view.py → figpack-0.1.2/figpack/core/_upload_bundle.py +140 -49
  7. figpack-0.1.2/figpack/core/figpack_view.py +70 -0
  8. figpack-0.1.2/figpack/figpack-gui-dist/assets/index-BA_v5Jep.css +1 -0
  9. figpack-0.1.2/figpack/figpack-gui-dist/assets/index-CMzZutX1.js +78 -0
  10. figpack-0.1.2/figpack/figpack-gui-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
  11. figpack-0.1.2/figpack/figpack-gui-dist/index.html +14 -0
  12. figpack-0.1.2/figpack.egg-info/PKG-INFO +104 -0
  13. {figpack-0.1.1 → figpack-0.1.2}/figpack.egg-info/SOURCES.txt +4 -3
  14. {figpack-0.1.1 → figpack-0.1.2}/figpack.egg-info/top_level.txt +1 -0
  15. {figpack-0.1.1 → figpack-0.1.2}/pyproject.toml +2 -2
  16. figpack-0.1.1/PKG-INFO +0 -33
  17. figpack-0.1.1/README.md +0 -2
  18. figpack-0.1.1/figpack/core/figpack_view.py +0 -62
  19. figpack-0.1.1/figpack/figpack-gui-dist/assets/index-BW-ONVCL.js +0 -65
  20. figpack-0.1.1/figpack/figpack-gui-dist/assets/index-CeWL3OeJ.css +0 -1
  21. figpack-0.1.1/figpack/figpack-gui-dist/index.html +0 -14
  22. figpack-0.1.1/figpack.egg-info/PKG-INFO +0 -33
  23. {figpack-0.1.1 → figpack-0.1.2}/LICENSE +0 -0
  24. {figpack-0.1.1 → figpack-0.1.2}/MANIFEST.in +0 -0
  25. {figpack-0.1.1 → figpack-0.1.2}/figpack/core/__init__.py +0 -0
  26. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/Box.py +0 -0
  27. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/LayoutItem.py +0 -0
  28. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/Splitter.py +0 -0
  29. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/TabLayout.py +0 -0
  30. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/TabLayoutItem.py +0 -0
  31. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/TimeseriesGraph.py +0 -0
  32. {figpack-0.1.1 → figpack-0.1.2}/figpack/views/__init__.py +0 -0
  33. {figpack-0.1.1 → figpack-0.1.2}/figpack-gui/node_modules/flatted/python/flatted.py +0 -0
  34. {figpack-0.1.1 → figpack-0.1.2}/figpack.egg-info/dependency_links.txt +0 -0
  35. {figpack-0.1.1 → figpack-0.1.2}/figpack.egg-info/requires.txt +0 -0
  36. {figpack-0.1.1 → figpack-0.1.2}/setup.cfg +0 -0
figpack-0.1.2/PKG-INFO ADDED
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: figpack
3
+ Version: 0.1.2
4
+ Summary: A Python package for creating shareable, interactive visualizations in the browser
5
+ Author-email: Jeremy Magland <jmagland@flatironinstitute.org>
6
+ License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/magland/figpack
8
+ Project-URL: Repository, https://github.com/magland/figpack
9
+ Project-URL: Documentation, https://github.com/magland/figpack#readme
10
+ Project-URL: Bug Tracker, https://github.com/magland/figpack/issues
11
+ Keywords: visualization,plotting,timeseries,interactive
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Science/Research
15
+ Classifier: License :: OSI Approved :: Apache Software License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.8
19
+ Classifier: Programming Language :: Python :: 3.9
20
+ Classifier: Programming Language :: Python :: 3.10
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Topic :: Scientific/Engineering :: Visualization
24
+ Requires-Python: >=3.8
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: numpy
28
+ Requires-Dist: zarr
29
+ Requires-Dist: requests
30
+ Dynamic: license-file
31
+
32
+ # figpack
33
+
34
+ A Python package for creating shareable, interactive visualizations in the browser.
35
+
36
+ ## Overview
37
+
38
+ figpack enables you to create interactive data visualizations that can be displayed in a web browser and optionally shared online. The package focuses on timeseries data visualization with support for complex, nested layouts.
39
+
40
+ ### Key Features
41
+
42
+ - **Interactive timeseries graphs** with line series, markers, and interval plots
43
+ - **Flexible layout system** with boxes, splitters, and tab layouts
44
+ - **Web-based rendering** that works in any modern browser
45
+ - **Shareable visualizations** that can be uploaded and shared via URLs
46
+ - **Zarr-based data storage** for efficient handling of large datasets
47
+
48
+ ## Installation
49
+
50
+ Install figpack using pip:
51
+
52
+ ```bash
53
+ pip install figpack
54
+ ```
55
+
56
+ ## Quick Start
57
+
58
+ ```python
59
+ import numpy as np
60
+ import figpack.views as vv
61
+
62
+ # Create a timeseries graph
63
+ graph = vv.TimeseriesGraph(y_label="Signal")
64
+
65
+ # Add some data
66
+ t = np.linspace(0, 10, 1000)
67
+ y = np.sin(2 * np.pi * t)
68
+ graph.add_line_series(name="sine wave", t=t, y=y, color="blue")
69
+
70
+ # Display the visualization
71
+ graph.show(open_in_browser=True)
72
+ ```
73
+
74
+ ## Examples
75
+
76
+ See the `examples/` directory.
77
+
78
+ ## Usage Modes
79
+
80
+ ### Local Development
81
+
82
+ ```python
83
+ view.show(open_in_browser=True)
84
+ ```
85
+
86
+ ### Sharing Online
87
+
88
+ Set the `FIGPACK_UPLOAD_PASSCODE` environment variable and use:
89
+
90
+ ```python
91
+ view.show(upload=True, open_in_browser=True)
92
+ ```
93
+
94
+ ### Development Mode
95
+
96
+ Set `_dev=True` in the call to show() to enable development mode, which allows for live updates and debugging with figpack-gui.
97
+
98
+ ## License
99
+
100
+ Apache-2.0
101
+
102
+ ## Contributing
103
+
104
+ Visit the [GitHub repository](https://github.com/magland/figpack) for issues, contributions, and the latest updates.
@@ -0,0 +1,73 @@
1
+ # figpack
2
+
3
+ A Python package for creating shareable, interactive visualizations in the browser.
4
+
5
+ ## Overview
6
+
7
+ figpack enables you to create interactive data visualizations that can be displayed in a web browser and optionally shared online. The package focuses on timeseries data visualization with support for complex, nested layouts.
8
+
9
+ ### Key Features
10
+
11
+ - **Interactive timeseries graphs** with line series, markers, and interval plots
12
+ - **Flexible layout system** with boxes, splitters, and tab layouts
13
+ - **Web-based rendering** that works in any modern browser
14
+ - **Shareable visualizations** that can be uploaded and shared via URLs
15
+ - **Zarr-based data storage** for efficient handling of large datasets
16
+
17
+ ## Installation
18
+
19
+ Install figpack using pip:
20
+
21
+ ```bash
22
+ pip install figpack
23
+ ```
24
+
25
+ ## Quick Start
26
+
27
+ ```python
28
+ import numpy as np
29
+ import figpack.views as vv
30
+
31
+ # Create a timeseries graph
32
+ graph = vv.TimeseriesGraph(y_label="Signal")
33
+
34
+ # Add some data
35
+ t = np.linspace(0, 10, 1000)
36
+ y = np.sin(2 * np.pi * t)
37
+ graph.add_line_series(name="sine wave", t=t, y=y, color="blue")
38
+
39
+ # Display the visualization
40
+ graph.show(open_in_browser=True)
41
+ ```
42
+
43
+ ## Examples
44
+
45
+ See the `examples/` directory.
46
+
47
+ ## Usage Modes
48
+
49
+ ### Local Development
50
+
51
+ ```python
52
+ view.show(open_in_browser=True)
53
+ ```
54
+
55
+ ### Sharing Online
56
+
57
+ Set the `FIGPACK_UPLOAD_PASSCODE` environment variable and use:
58
+
59
+ ```python
60
+ view.show(upload=True, open_in_browser=True)
61
+ ```
62
+
63
+ ### Development Mode
64
+
65
+ Set `_dev=True` in the call to show() to enable development mode, which allows for live updates and debugging with figpack-gui.
66
+
67
+ ## License
68
+
69
+ Apache-2.0
70
+
71
+ ## Contributing
72
+
73
+ Visit the [GitHub repository](https://github.com/magland/figpack) for issues, contributions, and the latest updates.
@@ -1,5 +1,5 @@
1
1
  """
2
- figpack - A Python package for creating interactive visualizations
2
+ figpack - A Python package for creating shareable, interactive visualizations in the browser
3
3
  """
4
4
 
5
5
  __version__ = "0.1.0"
@@ -6,7 +6,9 @@ from .figpack_view import FigpackView
6
6
  thisdir = pathlib.Path(__file__).parent.resolve()
7
7
 
8
8
 
9
- def prepare_figure_bundle(view: FigpackView, tmpdir: str) -> None:
9
+ def prepare_figure_bundle(
10
+ view: FigpackView, tmpdir: str, *, title: str = None, description: str = None
11
+ ) -> None:
10
12
  """
11
13
  Prepare a figure bundle in the specified temporary directory.
12
14
 
@@ -18,6 +20,8 @@ def prepare_figure_bundle(view: FigpackView, tmpdir: str) -> None:
18
20
  Args:
19
21
  view: The figpack view to prepare
20
22
  tmpdir: The temporary directory to prepare the bundle in
23
+ title: Optional title for the figure
24
+ description: Optional description for the figure (markdown supported)
21
25
  """
22
26
  html_dir = thisdir / ".." / "figpack-gui-dist"
23
27
  if not os.path.exists(html_dir):
@@ -43,4 +47,10 @@ def prepare_figure_bundle(view: FigpackView, tmpdir: str) -> None:
43
47
  )
44
48
  view._write_to_zarr_group(zarr_group)
45
49
 
50
+ # Add title and description as attributes on the top-level zarr group
51
+ if title is not None:
52
+ zarr_group.attrs["title"] = title
53
+ if description is not None:
54
+ zarr_group.attrs["description"] = description
55
+
46
56
  zarr.consolidate_metadata(zarr_group.store)
@@ -12,6 +12,7 @@ from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
12
12
 
13
13
  from .figpack_view import FigpackView
14
14
  from ._bundle_utils import prepare_figure_bundle
15
+ from ._upload_bundle import _upload_bundle
15
16
 
16
17
  thisdir = pathlib.Path(__file__).parent.resolve()
17
18
 
@@ -22,16 +23,41 @@ def _show_view(
22
23
  open_in_browser: bool = False,
23
24
  port: Union[int, None] = None,
24
25
  allow_origin: Union[str, None] = None,
26
+ upload: bool = False,
27
+ title: Union[str, None] = None,
28
+ description: Union[str, None] = None,
25
29
  ):
26
30
  with tempfile.TemporaryDirectory(prefix="figpack_") as tmpdir:
27
- prepare_figure_bundle(view, tmpdir)
28
-
29
- serve_files(
30
- tmpdir,
31
- port=port,
32
- open_in_browser=open_in_browser,
33
- allow_origin=allow_origin,
34
- )
31
+ prepare_figure_bundle(view, tmpdir, title=title, description=description)
32
+
33
+ if upload:
34
+ # Check for required environment variable
35
+ passcode = os.environ.get("FIGPACK_UPLOAD_PASSCODE")
36
+ if not passcode:
37
+ raise EnvironmentError(
38
+ "FIGPACK_UPLOAD_PASSCODE environment variable must be set to upload views."
39
+ )
40
+
41
+ # Upload the bundle
42
+ print("Starting upload...")
43
+ figure_url = _upload_bundle(tmpdir, passcode)
44
+
45
+ if open_in_browser:
46
+ webbrowser.open(figure_url)
47
+ print(f"Opening {figure_url} in browser.")
48
+ # wait until user presses Enter
49
+ input("Press Enter to continue...")
50
+ else:
51
+ print(f"View the figure at: {figure_url}")
52
+
53
+ return figure_url
54
+ else:
55
+ serve_files(
56
+ tmpdir,
57
+ port=port,
58
+ open_in_browser=open_in_browser,
59
+ allow_origin=allow_origin,
60
+ )
35
61
 
36
62
 
37
63
  class CORSRequestHandler(SimpleHTTPRequestHandler):
@@ -1,62 +1,18 @@
1
- import os
2
1
  import time
3
2
  import json
4
3
  import uuid
5
- import tempfile
6
4
  import pathlib
7
5
  import requests
8
6
  import threading
7
+ import hashlib
8
+ from .. import __version__
9
9
  from concurrent.futures import ThreadPoolExecutor, as_completed
10
10
  from datetime import datetime, timedelta, timezone
11
11
 
12
- from .figpack_view import FigpackView
13
- from ._bundle_utils import prepare_figure_bundle
14
-
15
12
  thisdir = pathlib.Path(__file__).parent.resolve()
16
13
 
17
14
  FIGPACK_API_BASE_URL = "https://figpack-api.vercel.app"
18
- TEMPORY_BASE_URL = "https://tempory.net/figpack/figures"
19
-
20
-
21
- def _upload_view(view: FigpackView) -> str:
22
- """
23
- Upload a figpack view to the cloud
24
-
25
- Args:
26
- view: The figpack view to upload
27
-
28
- Returns:
29
- str: URL where the uploaded figure can be viewed
30
-
31
- Raises:
32
- EnvironmentError: If FIGPACK_UPLOAD_PASSCODE is not set
33
- Exception: If upload fails
34
- """
35
- # Check for required environment variable
36
- passcode = os.environ.get("FIGPACK_UPLOAD_PASSCODE")
37
- if not passcode:
38
- raise EnvironmentError(
39
- "FIGPACK_UPLOAD_PASSCODE environment variable must be set"
40
- )
41
-
42
- # Generate random figure ID
43
- figure_id = str(uuid.uuid4())
44
- print(f"Generated figure ID: {figure_id}")
45
-
46
- with tempfile.TemporaryDirectory(prefix="figpack_upload_") as tmpdir:
47
- # Prepare the figure bundle (reuse logic from _show_view)
48
- print("Preparing figure bundle...")
49
- prepare_figure_bundle(view, tmpdir)
50
-
51
- # Upload the bundle
52
- print("Starting upload...")
53
- _upload_bundle(tmpdir, figure_id, passcode)
54
-
55
- # Return the final URL
56
- figure_url = f"{TEMPORY_BASE_URL}/{figure_id}/index.html"
57
- print(f"Upload completed successfully!")
58
- print(f"Figure available at: {figure_url}")
59
- return figure_url
15
+ TEMPORY_BASE_URL = "https://tempory.net/figpack/default/figures"
60
16
 
61
17
 
62
18
  def _upload_single_file(
@@ -83,12 +39,121 @@ def _upload_single_file(
83
39
  MAX_WORKERS_FOR_UPLOAD = 16
84
40
 
85
41
 
86
- def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
42
+ def _compute_deterministic_figure_id(tmpdir_path: pathlib.Path) -> str:
43
+ """
44
+ Compute a deterministic figure ID based on SHA1 hashes of all files
45
+
46
+ Returns:
47
+ str: 40-character SHA1 hash representing the content of all files
48
+ """
49
+ file_hashes = []
50
+
51
+ # Collect all files and their hashes
52
+ for file_path in sorted(tmpdir_path.rglob("*")):
53
+ if file_path.is_file():
54
+ relative_path = file_path.relative_to(tmpdir_path)
55
+
56
+ # Compute SHA1 hash of file content
57
+ sha1_hash = hashlib.sha1()
58
+ with open(file_path, "rb") as f:
59
+ for chunk in iter(lambda: f.read(4096), b""):
60
+ sha1_hash.update(chunk)
61
+
62
+ # Include both the relative path and content hash to ensure uniqueness
63
+ file_info = f"{relative_path}:{sha1_hash.hexdigest()}"
64
+ file_hashes.append(file_info)
65
+
66
+ # Create final hash from all file hashes
67
+ combined_hash = hashlib.sha1()
68
+ for file_hash in file_hashes:
69
+ combined_hash.update(file_hash.encode("utf-8"))
70
+
71
+ return combined_hash.hexdigest()
72
+
73
+
74
+ def _check_existing_figure(figure_id: str) -> dict:
75
+ """
76
+ Check if a figure already exists and return its status
77
+
78
+ Returns:
79
+ dict: Contains 'exists' (bool) and 'status' (str) if exists
80
+ """
81
+ figpack_url = f"{TEMPORY_BASE_URL}/{figure_id}/figpack.json"
82
+
83
+ try:
84
+ response = requests.get(figpack_url, timeout=10)
85
+ if response.ok:
86
+ figpack_data = response.json()
87
+ return {"exists": True, "status": figpack_data.get("status", "unknown")}
88
+ else:
89
+ return {"exists": False}
90
+ except Exception:
91
+ return {"exists": False}
92
+
93
+
94
+ def _find_available_figure_id(base_figure_id: str) -> tuple:
95
+ """
96
+ Find an available figure ID by checking base_figure_id, then base_figure_id-1, base_figure_id-2, etc.
97
+
98
+ Returns:
99
+ tuple: (figure_id_to_use, completed_figure_id) where:
100
+ - figure_id_to_use is None if upload should be skipped
101
+ - completed_figure_id is the ID of the completed figure if one exists
102
+ """
103
+ # First check the base figure ID
104
+ result = _check_existing_figure(base_figure_id)
105
+ if not result["exists"]:
106
+ return (base_figure_id, None)
107
+ elif result["status"] == "completed":
108
+ print(
109
+ f"Figure {base_figure_id} already exists and is completed. Skipping upload."
110
+ )
111
+ return (None, base_figure_id) # Signal to skip upload, return completed ID
112
+
113
+ # If exists but not completed, try with suffixes
114
+ suffix = 1
115
+ while True:
116
+ candidate_id = f"{base_figure_id}-{suffix}"
117
+ result = _check_existing_figure(candidate_id)
118
+
119
+ if not result["exists"]:
120
+ print(f"Using figure ID: {candidate_id}")
121
+ return (candidate_id, None)
122
+ elif result["status"] == "completed":
123
+ print(
124
+ f"Figure {candidate_id} already exists and is completed. Skipping upload."
125
+ )
126
+ return (None, candidate_id) # Signal to skip upload, return completed ID
127
+
128
+ suffix += 1
129
+ if suffix > 100: # Safety limit
130
+ raise Exception(
131
+ "Too many existing figure variants, unable to find available ID"
132
+ )
133
+
134
+
135
+ def _upload_bundle(tmpdir: str, passcode: str) -> None:
87
136
  """
88
137
  Upload the prepared bundle to the cloud using parallel uploads
89
138
  """
90
139
  tmpdir_path = pathlib.Path(tmpdir)
91
140
 
141
+ # Compute deterministic figure ID based on file contents
142
+ print("Computing deterministic figure ID...")
143
+ base_figure_id = _compute_deterministic_figure_id(tmpdir_path)
144
+ print(f"Base figure ID: {base_figure_id}")
145
+
146
+ # Find available figure ID (check for existing uploads)
147
+ figure_id, completed_figure_id = _find_available_figure_id(base_figure_id)
148
+
149
+ # If figure_id is None, it means we found a completed upload and should skip
150
+ if figure_id is None:
151
+ figure_url = f"{TEMPORY_BASE_URL}/{completed_figure_id}/index.html"
152
+ print(f"Figure already exists at: {figure_url}")
153
+ return figure_url
154
+
155
+ print(f"Using figure ID: {figure_id}")
156
+
92
157
  # First, upload initial figpack.json with "uploading" status
93
158
  print("Uploading initial status...")
94
159
  figpack_json = {
@@ -96,6 +161,7 @@ def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
96
161
  "upload_started": datetime.now(timezone.utc).isoformat(),
97
162
  "upload_updated": datetime.now(timezone.utc).isoformat(),
98
163
  "figure_id": figure_id,
164
+ "figpack_version": __version__,
99
165
  }
100
166
  _upload_small_file(
101
167
  figure_id, "figpack.json", json.dumps(figpack_json, indent=2), passcode
@@ -179,8 +245,27 @@ def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
179
245
  print(f"Failed to upload {relative_path}: {e}")
180
246
  raise # Re-raise the exception to stop the upload process
181
247
 
248
+ # Create and upload manifest.json
249
+ print("Creating manifest.json...")
250
+ manifest = {
251
+ "timestamp": datetime.now(timezone.utc).isoformat(),
252
+ "files": [],
253
+ "total_size": 0,
254
+ "total_files": len(files_to_upload),
255
+ }
256
+
257
+ for rel_path, file_path in files_to_upload:
258
+ file_size = file_path.stat().st_size
259
+ manifest["files"].append({"path": rel_path, "size": file_size})
260
+ manifest["total_size"] += file_size
261
+
262
+ _upload_small_file(
263
+ figure_id, "manifest.json", json.dumps(manifest, indent=2), passcode
264
+ )
265
+ print("Uploaded manifest.json")
266
+ print(f"Total size: {manifest['total_size'] / (1024 * 1024):.2f} MB")
267
+
182
268
  # Finally, upload completion status
183
- print("Uploading completion status...")
184
269
  figpack_json = {
185
270
  **figpack_json,
186
271
  "status": "completed",
@@ -188,10 +273,16 @@ def _upload_bundle(tmpdir: str, figure_id: str, passcode: str) -> None:
188
273
  "expiration": (datetime.now(timezone.utc) + timedelta(days=1)).isoformat(),
189
274
  "figure_id": figure_id,
190
275
  "total_files": len(all_files),
276
+ "total_size": manifest["total_size"],
277
+ "figpack_version": __version__,
191
278
  }
192
279
  _upload_small_file(
193
280
  figure_id, "figpack.json", json.dumps(figpack_json, indent=2), passcode
194
281
  )
282
+ print("Upload completed successfully")
283
+
284
+ figure_url = f"{TEMPORY_BASE_URL}/{figure_id}/index.html"
285
+ return figure_url
195
286
 
196
287
 
197
288
  def _determine_file_type(file_path: str) -> str:
@@ -0,0 +1,70 @@
1
+ """
2
+ Base view class for figpack visualization components
3
+ """
4
+
5
+ from typing import Union
6
+
7
+ import zarr
8
+
9
+
10
+ class FigpackView:
11
+ """
12
+ Base class for all figpack visualization components
13
+ """
14
+
15
+ def show(
16
+ self,
17
+ *,
18
+ port: Union[int, None] = None,
19
+ open_in_browser: bool = False,
20
+ allow_origin: Union[str, None] = None,
21
+ upload: bool = False,
22
+ _dev: bool = False,
23
+ title: Union[str, None] = None,
24
+ description: Union[str, None] = None,
25
+ ):
26
+ """
27
+ Display the visualization component
28
+
29
+ Args:
30
+ port: Port number for local server
31
+ open_in_browser: Whether to open in browser automatically
32
+ allow_origin: CORS allow origin header
33
+ upload: Whether to upload the figure
34
+ _dev: Development mode flag
35
+ title: Title for the browser tab and figure
36
+ description: Description text (markdown supported) for the figure
37
+ """
38
+ from ._show_view import _show_view
39
+
40
+ if _dev:
41
+ if port is None:
42
+ port = 3004
43
+ if allow_origin is not None:
44
+ raise ValueError("Cannot set allow_origin when _dev is True.")
45
+ allow_origin = "http://localhost:5173"
46
+ if upload:
47
+ raise ValueError("Cannot upload when _dev is True.")
48
+
49
+ print(
50
+ f"For development, run figpack-gui in dev mode and use http://localhost:5173?data=http://localhost:{port}/data.zarr"
51
+ )
52
+
53
+ _show_view(
54
+ self,
55
+ port=port,
56
+ open_in_browser=open_in_browser,
57
+ allow_origin=allow_origin,
58
+ upload=upload,
59
+ title=title,
60
+ description=description,
61
+ )
62
+
63
+ def _write_to_zarr_group(self, group: zarr.Group) -> None:
64
+ """
65
+ Write the view data to a Zarr group. Must be implemented by subclasses.
66
+
67
+ Args:
68
+ group: Zarr group to write data into
69
+ """
70
+ raise NotImplementedError("Subclasses must implement _write_to_zarr_group")
@@ -0,0 +1 @@
1
+ :root{font-family:system-ui,Avenir,Helvetica,Arial,sans-serif;line-height:1.5;font-weight:400;font-synthesis:none;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}a{font-weight:500;color:#646cff;text-decoration:inherit}a:hover{color:#535bf2}body{margin:0;overflow:hidden}h1{font-size:3.2em;line-height:1.1}button{border-radius:8px;border:1px solid transparent;padding:.6em 1.2em;font-size:1em;font-weight:500;font-family:inherit;background-color:#1a1a1a;cursor:pointer;transition:border-color .25s}button:hover{border-color:#646cff}button:focus,button:focus-visible{outline:4px auto -webkit-focus-ring-color}.status-bar{position:fixed;bottom:0;left:0;right:0;height:30px;background-color:#2a2a2a;color:#fff;display:flex;align-items:center;padding:0 12px;font-size:12px;border-top:1px solid #444;z-index:1000}.status-bar.warning{background-color:#ff9800;color:#000}.status-bar.error{background-color:#f44336;color:#fff}.status-bar.expired{background-color:#d32f2f;color:#fff}.manage-button{background-color:#444;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;margin-left:12px;transition:background-color .2s}.manage-button:hover{background-color:#666}.about-button{background-color:#444;color:#fff;border:none;padding:4px 8px;border-radius:4px;cursor:pointer;font-size:11px;height:22px;margin-left:12px;transition:background-color .2s}.about-button:hover{background-color:#666}.about-dialog-overlay{position:fixed;inset:0;background-color:#00000080;display:flex;justify-content:center;align-items:center;z-index:2000}.about-dialog{background-color:#2a2a2a;color:#fff;border-radius:8px;max-width:600px;max-height:80vh;width:90%;box-shadow:0 4px 20px #0000004d;overflow:hidden}.about-dialog-header{display:flex;justify-content:space-between;align-items:center;padding:16px 20px;border-bottom:1px solid #444}.about-dialog-header h2{margin:0;font-size:18px;font-weight:600}.about-dialog-close{background:none;border:none;color:#fff;font-size:24px;cursor:pointer;padding:0;width:30px;height:30px;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:background-color .2s}.about-dialog-close:hover{background-color:#444}.about-dialog-content{padding:20px;overflow-y:auto;max-height:calc(80vh - 80px)}.about-dialog-description{line-height:1.6}.about-dialog-description h1,.about-dialog-description h2,.about-dialog-description h3{margin-top:0;margin-bottom:12px}.about-dialog-description p{margin-bottom:12px}.about-dialog-description code{background-color:#444;padding:2px 4px;border-radius:3px;font-family:Courier New,monospace;font-size:.9em}.about-dialog-description pre{background-color:#444;padding:12px;border-radius:4px;overflow-x:auto;margin-bottom:12px}.about-dialog-description ul,.about-dialog-description ol{margin-bottom:12px;padding-left:20px}.about-dialog-description blockquote{border-left:4px solid #666;padding-left:16px;margin:12px 0;font-style:italic}@media (prefers-color-scheme: light){:root{color:#213547;background-color:#fff}a:hover{color:#747bff}button{background-color:#f9f9f9}.status-bar{background-color:#f5f5f5;color:#333;border-top:1px solid #ddd}.status-bar.warning{background-color:#fff3cd;color:#856404;border-top:1px solid #ffeaa7}.status-bar.error,.status-bar.expired{background-color:#f8d7da;color:#721c24;border-top:1px solid #f5c6cb}.manage-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc}.manage-button:hover{background-color:#d0d0d0}.about-button{background-color:#e0e0e0;color:#333;border:1px solid #ccc}.about-button:hover{background-color:#d0d0d0}.about-dialog{background-color:#fff;color:#333;box-shadow:0 4px 20px #00000026}.about-dialog-header{border-bottom:1px solid #ddd}.about-dialog-close{color:#333}.about-dialog-close:hover{background-color:#f0f0f0}.about-dialog-description code,.about-dialog-description pre{background-color:#f5f5f5;color:#333}.about-dialog-description blockquote{border-left:4px solid #ccc}}