staticdash 2026.3__tar.gz → 2026.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.
- {staticdash-2026.3 → staticdash-2026.4}/PKG-INFO +1 -1
- {staticdash-2026.3 → staticdash-2026.4}/pyproject.toml +1 -1
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/dashboard.py +243 -18
- {staticdash-2026.3 → staticdash-2026.4}/staticdash.egg-info/PKG-INFO +1 -1
- {staticdash-2026.3 → staticdash-2026.4}/README.md +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/setup.cfg +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/setup.py +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/__init__.py +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/css/style.css +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/js/script.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/mathjax/tex-mml-chtml.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/plotly/plotly.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-bash.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-c.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-javascript.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-json.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-markup.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-python.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-sql.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/prism-tomorrow.min.css +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/prism.min.js +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash.egg-info/SOURCES.txt +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash.egg-info/dependency_links.txt +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash.egg-info/requires.txt +0 -0
- {staticdash-2026.3 → staticdash-2026.4}/staticdash.egg-info/top_level.txt +0 -0
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "staticdash"
|
|
7
|
-
version = "2026.
|
|
7
|
+
version = "2026.4"
|
|
8
8
|
description = "A lightweight static HTML dashboard generator with Plotly and pandas support."
|
|
9
9
|
authors = [
|
|
10
10
|
{ name = "Brian Day", email = "brian.day1@gmail.com" }
|
|
@@ -54,8 +54,13 @@ class AbstractPage:
|
|
|
54
54
|
def add_text(self, text, width=None):
|
|
55
55
|
self.elements.append(("text", text, width))
|
|
56
56
|
|
|
57
|
-
def add_plot(self, plot, width=None):
|
|
58
|
-
|
|
57
|
+
def add_plot(self, plot, width=None, height=None, width_px=None, align="center"):
|
|
58
|
+
# Keep backward-compatible: `width` is a fraction (0..1) of page width.
|
|
59
|
+
# `height` is pixels (existing behavior). New optional `width_px`
|
|
60
|
+
# (pixels) can be supplied to control plot width. `align` controls
|
|
61
|
+
# horizontal alignment: 'left', 'center' (default), or 'right'.
|
|
62
|
+
# We store a tuple (plot, height_px, width_px, align) for forward-compatibility.
|
|
63
|
+
self.elements.append(("plot", (plot, height, width_px, align), width))
|
|
59
64
|
|
|
60
65
|
def add_table(self, df, table_id=None, sortable=True, width=None):
|
|
61
66
|
self.elements.append(("table", df, width))
|
|
@@ -151,7 +156,23 @@ class Page(AbstractPage):
|
|
|
151
156
|
header_tag = {1: h1, 2: h2, 3: h3, 4: h4}[level]
|
|
152
157
|
elem = header_tag(text)
|
|
153
158
|
elif kind == "plot":
|
|
154
|
-
|
|
159
|
+
# content may be stored as (figure, height), (figure, height, width_px)
|
|
160
|
+
# or (figure, height, width_px, align)
|
|
161
|
+
specified_height = None
|
|
162
|
+
specified_width_px = None
|
|
163
|
+
specified_align = "center"
|
|
164
|
+
if isinstance(content, (list, tuple)):
|
|
165
|
+
if len(content) == 4:
|
|
166
|
+
fig, specified_height, specified_width_px, specified_align = content
|
|
167
|
+
elif len(content) == 3:
|
|
168
|
+
fig, specified_height, specified_width_px = content
|
|
169
|
+
elif len(content) == 2:
|
|
170
|
+
fig, specified_height = content
|
|
171
|
+
else:
|
|
172
|
+
fig = content
|
|
173
|
+
else:
|
|
174
|
+
fig = content
|
|
175
|
+
|
|
155
176
|
if hasattr(fig, "to_html"):
|
|
156
177
|
# Use local Plotly loaded in <head>
|
|
157
178
|
# Ensure the figure uses a robust font family so minus signs and other
|
|
@@ -165,26 +186,128 @@ class Page(AbstractPage):
|
|
|
165
186
|
except Exception:
|
|
166
187
|
# Be defensive: don't fail rendering if layout manipulation isn't available
|
|
167
188
|
pass
|
|
168
|
-
|
|
169
|
-
# to avoid rendering issues when a user's font lacks the U+2212 glyph.
|
|
189
|
+
|
|
170
190
|
try:
|
|
171
|
-
|
|
172
|
-
|
|
191
|
+
# Temporarily set layout height/width if specified (pixels)
|
|
192
|
+
orig_height = None
|
|
193
|
+
orig_width = None
|
|
194
|
+
try:
|
|
195
|
+
orig_height = getattr(fig.layout, 'height', None)
|
|
196
|
+
except Exception:
|
|
197
|
+
orig_height = None
|
|
198
|
+
try:
|
|
199
|
+
orig_width = getattr(fig.layout, 'width', None)
|
|
200
|
+
except Exception:
|
|
201
|
+
orig_width = None
|
|
202
|
+
|
|
203
|
+
try:
|
|
204
|
+
if specified_height is not None:
|
|
205
|
+
fig.update_layout(height=specified_height)
|
|
206
|
+
if specified_width_px is not None:
|
|
207
|
+
fig.update_layout(width=specified_width_px)
|
|
208
|
+
except Exception:
|
|
209
|
+
pass
|
|
210
|
+
|
|
211
|
+
# If a local vendored Plotly exists, rely on the head script.
|
|
212
|
+
# Otherwise include Plotly from CDN so the inline newPlot call works.
|
|
213
|
+
vendor_plotly = os.path.join(os.path.dirname(__file__), "assets", "vendor", "plotly", "plotly.min.js")
|
|
214
|
+
include_plotly = False
|
|
215
|
+
if not os.path.exists(vendor_plotly):
|
|
216
|
+
include_plotly = "cdn"
|
|
217
|
+
plotly_html = fig.to_html(full_html=False, include_plotlyjs=include_plotly, config={'responsive': True})
|
|
218
|
+
|
|
219
|
+
# Wrap the Plotly HTML in a container with explicit pixel sizing
|
|
220
|
+
container_style = "width:100%;"
|
|
221
|
+
if specified_width_px is not None:
|
|
222
|
+
container_style = f"width:{specified_width_px}px;"
|
|
223
|
+
if specified_height is not None:
|
|
224
|
+
container_style = container_style + f" height:{specified_height}px;"
|
|
225
|
+
|
|
226
|
+
plot_wrapped = f'<div style="{container_style}">{plotly_html}</div>'
|
|
227
|
+
# Apply alignment wrapper
|
|
228
|
+
if specified_align not in ("left", "right", "center"):
|
|
229
|
+
specified_align = "center"
|
|
230
|
+
if specified_align == "center":
|
|
231
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
232
|
+
elif specified_align == "left":
|
|
233
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
234
|
+
else:
|
|
235
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
236
|
+
|
|
237
|
+
outer = f'<div style="{align_style}">{plot_wrapped}</div>'
|
|
238
|
+
elem = div(raw_util(outer))
|
|
239
|
+
|
|
240
|
+
# restore original height/width if we changed them
|
|
241
|
+
try:
|
|
242
|
+
if specified_height is not None:
|
|
243
|
+
fig.update_layout(height=orig_height)
|
|
244
|
+
if specified_width_px is not None:
|
|
245
|
+
fig.update_layout(width=orig_width)
|
|
246
|
+
except Exception:
|
|
247
|
+
pass
|
|
173
248
|
except Exception as e:
|
|
174
249
|
elem = div(f"Plotly figure could not be rendered: {e}")
|
|
175
250
|
else:
|
|
251
|
+
# Robust Matplotlib -> PNG path. Ensure `buf` exists and is closed.
|
|
252
|
+
buf = io.BytesIO()
|
|
176
253
|
try:
|
|
254
|
+
# If pixel width/height specified, attempt to adjust figure size
|
|
255
|
+
orig_size = None
|
|
256
|
+
try:
|
|
257
|
+
dpi = fig.get_dpi()
|
|
258
|
+
except Exception:
|
|
259
|
+
dpi = None
|
|
260
|
+
try:
|
|
261
|
+
if dpi is not None and (specified_height is not None or specified_width_px is not None):
|
|
262
|
+
orig_size = fig.get_size_inches()
|
|
263
|
+
new_w = orig_size[0]
|
|
264
|
+
new_h = orig_size[1]
|
|
265
|
+
if specified_width_px is not None:
|
|
266
|
+
new_w = specified_width_px / dpi
|
|
267
|
+
if specified_height is not None:
|
|
268
|
+
new_h = specified_height / dpi
|
|
269
|
+
fig.set_size_inches(new_w, new_h)
|
|
270
|
+
except Exception:
|
|
271
|
+
orig_size = None
|
|
272
|
+
|
|
177
273
|
with rc_context({"axes.unicode_minus": False}):
|
|
178
274
|
fig.savefig(buf, format="png", bbox_inches="tight")
|
|
275
|
+
|
|
276
|
+
# restore original size if changed
|
|
277
|
+
try:
|
|
278
|
+
if orig_size is not None:
|
|
279
|
+
fig.set_size_inches(orig_size)
|
|
280
|
+
except Exception:
|
|
281
|
+
pass
|
|
282
|
+
|
|
179
283
|
buf.seek(0)
|
|
180
284
|
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
181
|
-
|
|
285
|
+
img_style = "max-width:100%;"
|
|
286
|
+
if specified_height is not None:
|
|
287
|
+
img_style = img_style + f" height:{specified_height}px;"
|
|
288
|
+
if specified_width_px is not None:
|
|
289
|
+
img_style = img_style + f" width:{specified_width_px}px;"
|
|
290
|
+
|
|
291
|
+
if specified_align not in ("left", "right", "center"):
|
|
292
|
+
specified_align = "center"
|
|
293
|
+
if specified_align == "center":
|
|
294
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
295
|
+
elif specified_align == "left":
|
|
296
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
297
|
+
else:
|
|
298
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
299
|
+
|
|
182
300
|
elem = div(
|
|
183
|
-
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="
|
|
184
|
-
style=
|
|
301
|
+
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="{img_style}">'),
|
|
302
|
+
style=align_style
|
|
185
303
|
)
|
|
186
304
|
except Exception as e:
|
|
187
305
|
elem = div(f"Matplotlib figure could not be rendered: {e}")
|
|
306
|
+
finally:
|
|
307
|
+
try:
|
|
308
|
+
buf.close()
|
|
309
|
+
except Exception:
|
|
310
|
+
pass
|
|
188
311
|
elif kind == "table":
|
|
189
312
|
df = content
|
|
190
313
|
try:
|
|
@@ -255,7 +378,19 @@ class MiniPage(AbstractPage):
|
|
|
255
378
|
header_tag = {1: h1, 2: h2, 3: h3, 4: h4}[level]
|
|
256
379
|
elem = header_tag(text)
|
|
257
380
|
elif kind == "plot":
|
|
258
|
-
|
|
381
|
+
# content may be stored as (figure, height) or (figure, height, width_px)
|
|
382
|
+
specified_height = None
|
|
383
|
+
specified_width_px = None
|
|
384
|
+
if isinstance(content, (list, tuple)):
|
|
385
|
+
if len(content) == 3:
|
|
386
|
+
fig, specified_height, specified_width_px = content
|
|
387
|
+
elif len(content) == 2:
|
|
388
|
+
fig, specified_height = content
|
|
389
|
+
else:
|
|
390
|
+
fig = content
|
|
391
|
+
else:
|
|
392
|
+
fig = content
|
|
393
|
+
|
|
259
394
|
if hasattr(fig, "to_html"):
|
|
260
395
|
# Use local Plotly loaded in <head>
|
|
261
396
|
# Ensure the figure uses a robust font family so minus signs and other
|
|
@@ -269,25 +404,115 @@ class MiniPage(AbstractPage):
|
|
|
269
404
|
except Exception:
|
|
270
405
|
pass
|
|
271
406
|
try:
|
|
272
|
-
|
|
273
|
-
|
|
407
|
+
orig_height = None
|
|
408
|
+
orig_width = None
|
|
409
|
+
try:
|
|
410
|
+
orig_height = getattr(fig.layout, 'height', None)
|
|
411
|
+
except Exception:
|
|
412
|
+
orig_height = None
|
|
413
|
+
try:
|
|
414
|
+
orig_width = getattr(fig.layout, 'width', None)
|
|
415
|
+
except Exception:
|
|
416
|
+
orig_width = None
|
|
417
|
+
|
|
418
|
+
try:
|
|
419
|
+
if specified_height is not None:
|
|
420
|
+
fig.update_layout(height=specified_height)
|
|
421
|
+
if specified_width_px is not None:
|
|
422
|
+
fig.update_layout(width=specified_width_px)
|
|
423
|
+
except Exception:
|
|
424
|
+
pass
|
|
425
|
+
|
|
426
|
+
vendor_plotly = os.path.join(os.path.dirname(__file__), "assets", "vendor", "plotly", "plotly.min.js")
|
|
427
|
+
include_plotly = False
|
|
428
|
+
if not os.path.exists(vendor_plotly):
|
|
429
|
+
include_plotly = "cdn"
|
|
430
|
+
plotly_html = fig.to_html(full_html=False, include_plotlyjs=include_plotly, config={'responsive': True})
|
|
431
|
+
container_style = "width:100%;"
|
|
432
|
+
if specified_width_px is not None:
|
|
433
|
+
container_style = f"width:{specified_width_px}px;"
|
|
434
|
+
if specified_height is not None:
|
|
435
|
+
container_style = container_style + f" height:{specified_height}px;"
|
|
436
|
+
plot_wrapped = f'<div style="{container_style}">{plotly_html}</div>'
|
|
437
|
+
if specified_align not in ("left", "right", "center"):
|
|
438
|
+
specified_align = "center"
|
|
439
|
+
if specified_align == "center":
|
|
440
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
441
|
+
elif specified_align == "left":
|
|
442
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
443
|
+
else:
|
|
444
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
445
|
+
outer = f'<div style="{align_style}">{plot_wrapped}</div>'
|
|
446
|
+
elem = div(raw_util(outer))
|
|
447
|
+
|
|
448
|
+
try:
|
|
449
|
+
if specified_height is not None:
|
|
450
|
+
fig.update_layout(height=orig_height)
|
|
451
|
+
if specified_width_px is not None:
|
|
452
|
+
fig.update_layout(width=orig_width)
|
|
453
|
+
except Exception:
|
|
454
|
+
pass
|
|
274
455
|
except Exception as e:
|
|
275
456
|
elem = div(f"Plotly figure could not be rendered: {e}")
|
|
276
457
|
else:
|
|
458
|
+
# Robust Matplotlib -> PNG path. Ensure `buf` exists and is closed.
|
|
459
|
+
buf = io.BytesIO()
|
|
277
460
|
try:
|
|
278
|
-
|
|
279
|
-
|
|
461
|
+
# If pixel width/height specified, attempt to adjust figure size
|
|
462
|
+
orig_size = None
|
|
463
|
+
try:
|
|
464
|
+
dpi = fig.get_dpi()
|
|
465
|
+
except Exception:
|
|
466
|
+
dpi = None
|
|
467
|
+
try:
|
|
468
|
+
if dpi is not None and (specified_height is not None or specified_width_px is not None):
|
|
469
|
+
orig_size = fig.get_size_inches()
|
|
470
|
+
new_w = orig_size[0]
|
|
471
|
+
new_h = orig_size[1]
|
|
472
|
+
if specified_width_px is not None:
|
|
473
|
+
new_w = specified_width_px / dpi
|
|
474
|
+
if specified_height is not None:
|
|
475
|
+
new_h = specified_height / dpi
|
|
476
|
+
fig.set_size_inches(new_w, new_h)
|
|
477
|
+
except Exception:
|
|
478
|
+
orig_size = None
|
|
479
|
+
|
|
280
480
|
with rc_context({"axes.unicode_minus": False}):
|
|
281
481
|
fig.savefig(buf, format="png", bbox_inches="tight")
|
|
482
|
+
|
|
483
|
+
# restore original size if changed
|
|
484
|
+
try:
|
|
485
|
+
if orig_size is not None:
|
|
486
|
+
fig.set_size_inches(orig_size)
|
|
487
|
+
except Exception:
|
|
488
|
+
pass
|
|
489
|
+
|
|
282
490
|
buf.seek(0)
|
|
283
491
|
img_base64 = base64.b64encode(buf.read()).decode("utf-8")
|
|
284
|
-
|
|
492
|
+
img_style = "max-width:100%;"
|
|
493
|
+
if specified_height is not None:
|
|
494
|
+
img_style = img_style + f" height:{specified_height}px;"
|
|
495
|
+
if specified_width_px is not None:
|
|
496
|
+
img_style = img_style + f" width:{specified_width_px}px;"
|
|
497
|
+
if specified_align not in ("left", "right", "center"):
|
|
498
|
+
specified_align = "center"
|
|
499
|
+
if specified_align == "center":
|
|
500
|
+
align_style = "display:flex; justify-content:center; align-items:center;"
|
|
501
|
+
elif specified_align == "left":
|
|
502
|
+
align_style = "display:flex; justify-content:flex-start; align-items:center;"
|
|
503
|
+
else:
|
|
504
|
+
align_style = "display:flex; justify-content:flex-end; align-items:center;"
|
|
285
505
|
elem = div(
|
|
286
|
-
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="
|
|
287
|
-
style=
|
|
506
|
+
raw_util(f'<img src="data:image/png;base64,{img_base64}" style="{img_style}">'),
|
|
507
|
+
style=align_style
|
|
288
508
|
)
|
|
289
509
|
except Exception as e:
|
|
290
510
|
elem = div(f"Matplotlib figure could not be rendered: {e}")
|
|
511
|
+
finally:
|
|
512
|
+
try:
|
|
513
|
+
buf.close()
|
|
514
|
+
except Exception:
|
|
515
|
+
pass
|
|
291
516
|
elif kind == "table":
|
|
292
517
|
df = content
|
|
293
518
|
html_table = df.to_html(classes="table-hover table-striped", index=False, border=0, table_id=f"table-{index}", escape=False)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-bash.min.js
RENAMED
|
File without changes
|
{staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-c.min.js
RENAMED
|
File without changes
|
|
File without changes
|
{staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-json.min.js
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/components/prism-sql.min.js
RENAMED
|
File without changes
|
{staticdash-2026.3 → staticdash-2026.4}/staticdash/assets/vendor/prism/prism-tomorrow.min.css
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|