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