figpack 0.1.2__tar.gz → 0.1.4__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.
- {figpack-0.1.2/figpack.egg-info → figpack-0.1.4}/PKG-INFO +23 -1
- {figpack-0.1.2 → figpack-0.1.4}/README.md +22 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/__init__.py +3 -1
- figpack-0.1.4/figpack/cli.py +312 -0
- figpack-0.1.2/figpack/figpack-gui-dist/assets/index-BA_v5Jep.css → figpack-0.1.4/figpack/figpack-gui-dist/assets/index-BDa2iJW9.css +1 -1
- figpack-0.1.4/figpack/figpack-gui-dist/assets/index-Dw14QqeQ.js +846 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/figpack-gui-dist/index.html +2 -2
- figpack-0.1.4/figpack/spike_sorting/__init__.py +5 -0
- figpack-0.1.4/figpack/spike_sorting/views/AutocorrelogramItem.py +41 -0
- figpack-0.1.4/figpack/spike_sorting/views/Autocorrelograms.py +76 -0
- figpack-0.1.4/figpack/spike_sorting/views/CrossCorrelogramItem.py +45 -0
- figpack-0.1.4/figpack/spike_sorting/views/CrossCorrelograms.py +82 -0
- figpack-0.1.4/figpack/spike_sorting/views/UnitSimilarityScore.py +40 -0
- figpack-0.1.4/figpack/spike_sorting/views/UnitsTable.py +68 -0
- figpack-0.1.4/figpack/spike_sorting/views/UnitsTableColumn.py +40 -0
- figpack-0.1.4/figpack/spike_sorting/views/UnitsTableRow.py +36 -0
- figpack-0.1.4/figpack/spike_sorting/views/__init__.py +23 -0
- figpack-0.1.4/figpack/views/Image.py +82 -0
- figpack-0.1.4/figpack/views/Markdown.py +34 -0
- figpack-0.1.4/figpack/views/MatplotlibFigure.py +65 -0
- figpack-0.1.4/figpack/views/PlotlyFigure.py +58 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/__init__.py +4 -0
- {figpack-0.1.2 → figpack-0.1.4/figpack.egg-info}/PKG-INFO +23 -1
- figpack-0.1.4/figpack.egg-info/SOURCES.txt +42 -0
- figpack-0.1.4/figpack.egg-info/entry_points.txt +2 -0
- figpack-0.1.4/figpack.egg-info/top_level.txt +1 -0
- {figpack-0.1.2 → figpack-0.1.4}/pyproject.toml +5 -1
- figpack-0.1.2/figpack/figpack-gui-dist/assets/index-CMzZutX1.js +0 -78
- figpack-0.1.2/figpack-gui/node_modules/flatted/python/flatted.py +0 -149
- figpack-0.1.2/figpack.egg-info/SOURCES.txt +0 -27
- figpack-0.1.2/figpack.egg-info/top_level.txt +0 -4
- {figpack-0.1.2 → figpack-0.1.4}/LICENSE +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/MANIFEST.in +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/core/__init__.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/core/_bundle_utils.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/core/_show_view.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/core/_upload_bundle.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/core/figpack_view.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/figpack-gui-dist/assets/neurosift-logo-CLsuwLMO.png +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/Box.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/LayoutItem.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/Splitter.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/TabLayout.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/TabLayoutItem.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack/views/TimeseriesGraph.py +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack.egg-info/dependency_links.txt +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/figpack.egg-info/requires.txt +0 -0
- {figpack-0.1.2 → figpack-0.1.4}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: figpack
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.4
|
|
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
|
|
@@ -95,6 +95,28 @@ view.show(upload=True, open_in_browser=True)
|
|
|
95
95
|
|
|
96
96
|
Set `_dev=True` in the call to show() to enable development mode, which allows for live updates and debugging with figpack-gui.
|
|
97
97
|
|
|
98
|
+
## Command Line Interface
|
|
99
|
+
|
|
100
|
+
figpack includes a command-line interface for working with figures:
|
|
101
|
+
|
|
102
|
+
### Download a Figure
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
figpack download <figure-url> <dest.tar.gz>
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Download a figure from any figpack URL and save it as a local archive.
|
|
109
|
+
|
|
110
|
+
### View a Figure Archive
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
figpack view <figure.tar.gz>
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Extract and view a figure archive in your browser. The server will run locally until you press Enter.
|
|
117
|
+
|
|
118
|
+
Use `--port <number>` to specify a custom port.
|
|
119
|
+
|
|
98
120
|
## License
|
|
99
121
|
|
|
100
122
|
Apache-2.0
|
|
@@ -64,6 +64,28 @@ view.show(upload=True, open_in_browser=True)
|
|
|
64
64
|
|
|
65
65
|
Set `_dev=True` in the call to show() to enable development mode, which allows for live updates and debugging with figpack-gui.
|
|
66
66
|
|
|
67
|
+
## Command Line Interface
|
|
68
|
+
|
|
69
|
+
figpack includes a command-line interface for working with figures:
|
|
70
|
+
|
|
71
|
+
### Download a Figure
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
figpack download <figure-url> <dest.tar.gz>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Download a figure from any figpack URL and save it as a local archive.
|
|
78
|
+
|
|
79
|
+
### View a Figure Archive
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
figpack view <figure.tar.gz>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
Extract and view a figure archive in your browser. The server will run locally until you press Enter.
|
|
86
|
+
|
|
87
|
+
Use `--port <number>` to specify a custom port.
|
|
88
|
+
|
|
67
89
|
## License
|
|
68
90
|
|
|
69
91
|
Apache-2.0
|
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Command-line interface for figpack
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
import argparse
|
|
6
|
+
import sys
|
|
7
|
+
import pathlib
|
|
8
|
+
import tempfile
|
|
9
|
+
import tarfile
|
|
10
|
+
import json
|
|
11
|
+
import requests
|
|
12
|
+
import threading
|
|
13
|
+
import webbrowser
|
|
14
|
+
import socket
|
|
15
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
16
|
+
from urllib.parse import urlparse, urljoin
|
|
17
|
+
from typing import Dict, List, Tuple, Optional, Union
|
|
18
|
+
from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
|
|
19
|
+
|
|
20
|
+
from . import __version__
|
|
21
|
+
from .core._show_view import serve_files
|
|
22
|
+
|
|
23
|
+
MAX_WORKERS_FOR_DOWNLOAD = 16
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_figure_base_url(figure_url: str) -> str:
|
|
27
|
+
"""
|
|
28
|
+
Get the base URL from any figpack URL
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
figure_url: Any figpack URL (may or may not end with /index.html)
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
str: The base URL for the figure directory
|
|
35
|
+
"""
|
|
36
|
+
# Handle URLs that end with /index.html
|
|
37
|
+
if figure_url.endswith("/index.html"):
|
|
38
|
+
base_url = figure_url[:-11] # Remove "/index.html"
|
|
39
|
+
elif figure_url.endswith("/"):
|
|
40
|
+
base_url = figure_url[:-1] # Remove trailing slash
|
|
41
|
+
else:
|
|
42
|
+
# Assume it's already a directory URL
|
|
43
|
+
base_url = figure_url
|
|
44
|
+
|
|
45
|
+
# Ensure it ends with a slash for urljoin to work properly
|
|
46
|
+
if not base_url.endswith("/"):
|
|
47
|
+
base_url += "/"
|
|
48
|
+
|
|
49
|
+
return base_url
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def download_file(
|
|
53
|
+
base_url: str, file_info: Dict, temp_dir: pathlib.Path
|
|
54
|
+
) -> Tuple[str, bool]:
|
|
55
|
+
"""
|
|
56
|
+
Download a single file from the figure
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
base_url: The base URL for the figure
|
|
60
|
+
file_info: Dictionary with 'path' and 'size' keys
|
|
61
|
+
temp_dir: Temporary directory to download to
|
|
62
|
+
|
|
63
|
+
Returns:
|
|
64
|
+
Tuple of (file_path, success)
|
|
65
|
+
"""
|
|
66
|
+
file_path = file_info["path"]
|
|
67
|
+
file_url = urljoin(base_url, file_path)
|
|
68
|
+
|
|
69
|
+
try:
|
|
70
|
+
response = requests.get(file_url, timeout=30)
|
|
71
|
+
response.raise_for_status()
|
|
72
|
+
|
|
73
|
+
# Create directory structure if needed
|
|
74
|
+
local_file_path = temp_dir / file_path
|
|
75
|
+
local_file_path.parent.mkdir(parents=True, exist_ok=True)
|
|
76
|
+
|
|
77
|
+
# Write file content
|
|
78
|
+
if file_path.endswith(
|
|
79
|
+
(
|
|
80
|
+
".json",
|
|
81
|
+
".html",
|
|
82
|
+
".css",
|
|
83
|
+
".js",
|
|
84
|
+
".zattrs",
|
|
85
|
+
".zgroup",
|
|
86
|
+
".zarray",
|
|
87
|
+
".zmetadata",
|
|
88
|
+
)
|
|
89
|
+
):
|
|
90
|
+
# Text files
|
|
91
|
+
local_file_path.write_text(response.text, encoding="utf-8")
|
|
92
|
+
else:
|
|
93
|
+
# Binary files
|
|
94
|
+
local_file_path.write_bytes(response.content)
|
|
95
|
+
|
|
96
|
+
return file_path, True
|
|
97
|
+
|
|
98
|
+
except Exception as e:
|
|
99
|
+
print(f"Failed to download {file_path}: {e}")
|
|
100
|
+
return file_path, False
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def download_figure(figure_url: str, dest_path: str) -> None:
|
|
104
|
+
"""
|
|
105
|
+
Download a figure from a figpack URL and save as tar.gz
|
|
106
|
+
|
|
107
|
+
Args:
|
|
108
|
+
figure_url: The figpack URL
|
|
109
|
+
dest_path: Destination path for the tar.gz file
|
|
110
|
+
"""
|
|
111
|
+
print(f"Downloading figure from: {figure_url}")
|
|
112
|
+
|
|
113
|
+
# Get base URL
|
|
114
|
+
base_url = get_figure_base_url(figure_url)
|
|
115
|
+
print(f"Base URL: {base_url}")
|
|
116
|
+
|
|
117
|
+
# Check if manifest.json exists
|
|
118
|
+
manifest_url = urljoin(base_url, "manifest.json")
|
|
119
|
+
print("Checking for manifest.json...")
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
response = requests.get(manifest_url, timeout=10)
|
|
123
|
+
response.raise_for_status()
|
|
124
|
+
manifest = response.json()
|
|
125
|
+
print(f"Found manifest with {len(manifest['files'])} files")
|
|
126
|
+
except requests.exceptions.RequestException as e:
|
|
127
|
+
print(f"Error: Could not retrieve manifest.json from {manifest_url}: {e}")
|
|
128
|
+
print("Make sure the URL points to a valid figpack figure with a manifest.")
|
|
129
|
+
sys.exit(1)
|
|
130
|
+
except json.JSONDecodeError as e:
|
|
131
|
+
print(f"Error: Invalid manifest.json format: {e}")
|
|
132
|
+
sys.exit(1)
|
|
133
|
+
|
|
134
|
+
# Create temporary directory for downloads
|
|
135
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
136
|
+
temp_path = pathlib.Path(temp_dir)
|
|
137
|
+
|
|
138
|
+
# Download all files in parallel
|
|
139
|
+
print(
|
|
140
|
+
f"Downloading {len(manifest['files'])} files with up to {MAX_WORKERS_FOR_DOWNLOAD} concurrent downloads..."
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
downloaded_count = 0
|
|
144
|
+
failed_files = []
|
|
145
|
+
count_lock = threading.Lock()
|
|
146
|
+
|
|
147
|
+
with ThreadPoolExecutor(max_workers=MAX_WORKERS_FOR_DOWNLOAD) as executor:
|
|
148
|
+
# Submit all download tasks
|
|
149
|
+
future_to_file = {
|
|
150
|
+
executor.submit(
|
|
151
|
+
download_file, base_url, file_info, temp_path
|
|
152
|
+
): file_info["path"]
|
|
153
|
+
for file_info in manifest["files"]
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
# Process completed downloads
|
|
157
|
+
for future in as_completed(future_to_file):
|
|
158
|
+
file_path = future_to_file[future]
|
|
159
|
+
try:
|
|
160
|
+
downloaded_path, success = future.result()
|
|
161
|
+
|
|
162
|
+
with count_lock:
|
|
163
|
+
if success:
|
|
164
|
+
downloaded_count += 1
|
|
165
|
+
print(
|
|
166
|
+
f"Downloaded {downloaded_count}/{len(manifest['files'])}: {downloaded_path}"
|
|
167
|
+
)
|
|
168
|
+
else:
|
|
169
|
+
failed_files.append(downloaded_path)
|
|
170
|
+
|
|
171
|
+
except Exception as e:
|
|
172
|
+
with count_lock:
|
|
173
|
+
failed_files.append(file_path)
|
|
174
|
+
print(f"Failed to download {file_path}: {e}")
|
|
175
|
+
|
|
176
|
+
if failed_files:
|
|
177
|
+
print(f"Warning: Failed to download {len(failed_files)} files:")
|
|
178
|
+
for failed_file in failed_files:
|
|
179
|
+
print(f" - {failed_file}")
|
|
180
|
+
|
|
181
|
+
if len(failed_files) == len(manifest["files"]):
|
|
182
|
+
print("Error: Failed to download any files. Aborting.")
|
|
183
|
+
sys.exit(1)
|
|
184
|
+
|
|
185
|
+
# Save manifest.json to temp directory
|
|
186
|
+
manifest_path = temp_path / "manifest.json"
|
|
187
|
+
manifest_path.write_text(json.dumps(manifest, indent=2))
|
|
188
|
+
print("Added manifest.json to bundle")
|
|
189
|
+
|
|
190
|
+
# Create tar.gz file
|
|
191
|
+
print(f"Creating tar.gz archive: {dest_path}")
|
|
192
|
+
dest_pathlib = pathlib.Path(dest_path)
|
|
193
|
+
dest_pathlib.parent.mkdir(parents=True, exist_ok=True)
|
|
194
|
+
|
|
195
|
+
with tarfile.open(dest_path, "w:gz") as tar:
|
|
196
|
+
# Add all downloaded files (excluding figpack.json if it exists)
|
|
197
|
+
for file_path in temp_path.rglob("*"):
|
|
198
|
+
if file_path.is_file():
|
|
199
|
+
arcname = file_path.relative_to(temp_path)
|
|
200
|
+
# Skip figpack.json as requested
|
|
201
|
+
if str(arcname) != "figpack.json":
|
|
202
|
+
tar.add(file_path, arcname=arcname)
|
|
203
|
+
|
|
204
|
+
# Count files in archive (excluding directories)
|
|
205
|
+
archive_files = [
|
|
206
|
+
f for f in temp_path.rglob("*") if f.is_file() and f.name != "figpack.json"
|
|
207
|
+
]
|
|
208
|
+
total_size = sum(f.stat().st_size for f in archive_files)
|
|
209
|
+
|
|
210
|
+
print(f"Archive created successfully!")
|
|
211
|
+
print(
|
|
212
|
+
f"Total files: {len(archive_files)} (including manifest.json, excluding figpack.json)"
|
|
213
|
+
)
|
|
214
|
+
print(f"Total size: {total_size / (1024 * 1024):.2f} MB")
|
|
215
|
+
print(f"Archive saved to: {dest_path}")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def view_figure(archive_path: str, port: Union[int, None] = None) -> None:
|
|
219
|
+
"""
|
|
220
|
+
Extract and serve a figure archive locally
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
archive_path: Path to the tar.gz archive
|
|
224
|
+
port: Optional port number to serve on
|
|
225
|
+
"""
|
|
226
|
+
archive_pathlib = pathlib.Path(archive_path)
|
|
227
|
+
|
|
228
|
+
if not archive_pathlib.exists():
|
|
229
|
+
print(f"Error: Archive file not found: {archive_path}")
|
|
230
|
+
sys.exit(1)
|
|
231
|
+
|
|
232
|
+
if not archive_pathlib.suffix.lower() in [".gz", ".tgz"] or not str(
|
|
233
|
+
archive_pathlib
|
|
234
|
+
).endswith(".tar.gz"):
|
|
235
|
+
print(f"Error: Expected a .tar.gz file, got: {archive_path}")
|
|
236
|
+
sys.exit(1)
|
|
237
|
+
|
|
238
|
+
print(f"Extracting figure archive: {archive_path}")
|
|
239
|
+
|
|
240
|
+
# Create temporary directory and extract files
|
|
241
|
+
with tempfile.TemporaryDirectory(prefix="figpack_view_") as temp_dir:
|
|
242
|
+
temp_path = pathlib.Path(temp_dir)
|
|
243
|
+
|
|
244
|
+
try:
|
|
245
|
+
with tarfile.open(archive_path, "r:gz") as tar:
|
|
246
|
+
tar.extractall(temp_path)
|
|
247
|
+
|
|
248
|
+
# Count extracted files
|
|
249
|
+
extracted_files = list(temp_path.rglob("*"))
|
|
250
|
+
file_count = len([f for f in extracted_files if f.is_file()])
|
|
251
|
+
print(f"Extracted {file_count} files")
|
|
252
|
+
|
|
253
|
+
# Check if index.html exists
|
|
254
|
+
index_html = temp_path / "index.html"
|
|
255
|
+
if not index_html.exists():
|
|
256
|
+
print("Warning: No index.html found in archive")
|
|
257
|
+
print("Available files:")
|
|
258
|
+
for f in sorted(extracted_files):
|
|
259
|
+
if f.is_file():
|
|
260
|
+
print(f" {f.relative_to(temp_path)}")
|
|
261
|
+
|
|
262
|
+
# Serve the files
|
|
263
|
+
serve_files(
|
|
264
|
+
str(temp_path),
|
|
265
|
+
port=port,
|
|
266
|
+
open_in_browser=True,
|
|
267
|
+
allow_origin=None,
|
|
268
|
+
)
|
|
269
|
+
|
|
270
|
+
except tarfile.TarError as e:
|
|
271
|
+
print(f"Error: Failed to extract archive: {e}")
|
|
272
|
+
sys.exit(1)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def main():
|
|
276
|
+
"""Main CLI entry point"""
|
|
277
|
+
parser = argparse.ArgumentParser(
|
|
278
|
+
description="figpack - A Python package for creating shareable, interactive visualizations",
|
|
279
|
+
prog="figpack",
|
|
280
|
+
)
|
|
281
|
+
parser.add_argument("--version", action="version", version=f"figpack {__version__}")
|
|
282
|
+
|
|
283
|
+
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
284
|
+
|
|
285
|
+
# Download command
|
|
286
|
+
download_parser = subparsers.add_parser(
|
|
287
|
+
"download", help="Download a figure from a figpack URL"
|
|
288
|
+
)
|
|
289
|
+
download_parser.add_argument("figure_url", help="The figpack URL to download")
|
|
290
|
+
download_parser.add_argument("dest", help="Destination path for the tar.gz file")
|
|
291
|
+
|
|
292
|
+
# View command
|
|
293
|
+
view_parser = subparsers.add_parser(
|
|
294
|
+
"view", help="Extract and serve a figure archive locally"
|
|
295
|
+
)
|
|
296
|
+
view_parser.add_argument("archive", help="Path to the tar.gz archive file")
|
|
297
|
+
view_parser.add_argument(
|
|
298
|
+
"--port", type=int, help="Port number to serve on (default: auto-select)"
|
|
299
|
+
)
|
|
300
|
+
|
|
301
|
+
args = parser.parse_args()
|
|
302
|
+
|
|
303
|
+
if args.command == "download":
|
|
304
|
+
download_figure(args.figure_url, args.dest)
|
|
305
|
+
elif args.command == "view":
|
|
306
|
+
view_figure(args.archive, port=args.port)
|
|
307
|
+
else:
|
|
308
|
+
parser.print_help()
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
if __name__ == "__main__":
|
|
312
|
+
main()
|
|
@@ -1 +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}}
|
|
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}}.VerticalToolbar{overflow:hidden;position:absolute}.HorizontalToolbar{display:flex;justify-content:center;align-items:center;overflow:hidden;margin:auto}.SortableTableWidget .MuiTableCell-root{padding:1px 0;font-size:12px;border:solid 1px #f0f0f0}.SortableTableWidget .MuiTableRow-root.selectedRow{background-color:#b5d1ff}.SortableTableWidget .MuiTableRow-root.currentRow{background-color:#95b1df}.SortCaretSpan{font-size:16px;color:gray;padding-left:3px;padding-right:5px;padding-top:2px}.SortableTableWidget .unitLabel{width:10px;height:10px;position:relative;display:inline-block}.SortableTableWidget .SortableTableCheckbox{padding:1px}.unitEntryBase{padding-right:13px;padding-top:10px}.unselectedUnitEntry{border:1px transparent solid;font-weight:400;color:#000;background-color:#fff}.selectedUnitEntry{border:1px solid blue;font-weight:700;color:#000;background-color:#b5d1ff}.unitIdsColumnLabelStyle{min-width:200px;font-weight:700;padding:7px 5px}.unitLabelsStyle{padding-right:3px;color:#333}.plotUnitLabel{text-align:left;margin-left:20px;margin-top:5px;font-weight:700}.plotWrapperButtonParent{display:inline-block}.plotWrapperStyleButton{padding-top:50px}.plotUnselectedStyle{border:3px solid rgb(202,224,231)}.plotSelectedStyle{border:3px solid #9999ff;background-color:#fff}.plotUnselectableStyle{border:3px solid transparent}.plotCurrentStyle{border:3px solid blue;background-color:#fff}.plotLabelStyle{font-weight:700;text-align:center;overflow:hidden;white-space:nowrap}
|