staticdash 0.1.0__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.
- {staticdash-0.1.0 → staticdash-0.1.2}/PKG-INFO +40 -37
- staticdash-0.1.2/README.md +68 -0
- staticdash-0.1.2/pyproject.toml +29 -0
- staticdash-0.1.2/staticdash/__init__.py +1 -0
- staticdash-0.1.2/staticdash/assets/css/style.css +100 -0
- staticdash-0.1.2/staticdash/assets/js/script.js +43 -0
- staticdash-0.1.2/staticdash/dashboard.py +119 -0
- {staticdash-0.1.0 → staticdash-0.1.2}/staticdash.egg-info/PKG-INFO +40 -37
- {staticdash-0.1.0 → staticdash-0.1.2}/staticdash.egg-info/SOURCES.txt +4 -1
- staticdash-0.1.2/staticdash.egg-info/requires.txt +3 -0
- staticdash-0.1.0/README.md +0 -68
- staticdash-0.1.0/pyproject.toml +0 -22
- staticdash-0.1.0/staticdash/__init__.py +0 -2
- staticdash-0.1.0/staticdash/dashboard.py +0 -88
- {staticdash-0.1.0 → staticdash-0.1.2}/setup.cfg +0 -0
- {staticdash-0.1.0 → staticdash-0.1.2}/staticdash.egg-info/dependency_links.txt +0 -0
- {staticdash-0.1.0 → staticdash-0.1.2}/staticdash.egg-info/top_level.txt +0 -0
@@ -1,13 +1,16 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: staticdash
|
3
|
-
Version: 0.1.
|
4
|
-
Summary: A
|
3
|
+
Version: 0.1.2
|
4
|
+
Summary: A lightweight static HTML dashboard generator with Plotly and pandas support.
|
5
5
|
Author-email: Brian Day <brian.day1@gmail.com>
|
6
|
-
License
|
6
|
+
License: CC0-1.0
|
7
7
|
Project-URL: Homepage, https://github.com/briday1/staticdash
|
8
8
|
Project-URL: Repository, https://github.com/briday1/staticdash
|
9
9
|
Requires-Python: >=3.8
|
10
10
|
Description-Content-Type: text/markdown
|
11
|
+
Requires-Dist: plotly
|
12
|
+
Requires-Dist: pandas
|
13
|
+
Requires-Dist: dominate
|
11
14
|
|
12
15
|
# staticdash
|
13
16
|
|
@@ -29,50 +32,50 @@ Create a Python script like this:
|
|
29
32
|
|
30
33
|
```python
|
31
34
|
from staticdash.dashboard import Dashboard, Page
|
32
|
-
import plotly.
|
35
|
+
import plotly.express as px
|
33
36
|
import pandas as pd
|
37
|
+
import numpy as np
|
34
38
|
|
35
|
-
# Create
|
36
|
-
|
39
|
+
# Create sample data
|
40
|
+
df = pd.DataFrame({
|
41
|
+
"Category": ["A", "B", "C", "D"],
|
42
|
+
"Value": [10, 20, 30, 40]
|
43
|
+
})
|
37
44
|
|
38
|
-
|
39
|
-
|
45
|
+
df2 = pd.DataFrame({
|
46
|
+
"Time": pd.date_range("2024-01-01", periods=10, freq="D"),
|
47
|
+
"Signal": np.random.randn(10).cumsum()
|
48
|
+
})
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 1, 6], mode='lines+markers', name="Demo Line"))
|
44
|
-
fig.update_layout(title="Sample Plot")
|
45
|
-
page1.append(fig)
|
50
|
+
fig1 = px.bar(df, x="Category", y="Value", title="Bar Chart Example")
|
51
|
+
fig2 = px.line(df2, x="Time", y="Signal", title="Signal over Time")
|
46
52
|
|
47
|
-
#
|
48
|
-
|
49
|
-
"Category": ["A", "B", "C"],
|
50
|
-
"Value": [100, 200, 150]
|
51
|
-
})
|
52
|
-
page1.append(df1)
|
53
|
+
# Build dashboard
|
54
|
+
dashboard = Dashboard(title="StaticDash Demo")
|
53
55
|
|
54
|
-
#
|
55
|
-
page1
|
56
|
+
# Page 1: Overview
|
57
|
+
page1 = Page("overview", "Overview")
|
58
|
+
page1.add("Welcome to the StaticDash demo. Below is a bar chart and a table.")
|
59
|
+
page1.add(fig1)
|
60
|
+
page1.add(df)
|
61
|
+
|
62
|
+
# Page 2: Timeseries
|
63
|
+
page2 = Page("timeseries", "Timeseries")
|
64
|
+
page2.add("Here is a random time series with cumulative noise.")
|
65
|
+
page2.add(fig2)
|
66
|
+
page2.add(df2)
|
67
|
+
|
68
|
+
# Page 3: Summary
|
69
|
+
page3 = Page("summary", "Summary")
|
70
|
+
page3.add("Summary and notes can be added here.")
|
71
|
+
page3.add("StaticDash is a lightweight static dashboard generator.")
|
72
|
+
|
73
|
+
# Register pages
|
56
74
|
dashboard.add_page(page1)
|
57
|
-
|
58
|
-
# Page 2: Data Table
|
59
|
-
page2 = Page("data", "Data")
|
60
|
-
df2 = pd.DataFrame({
|
61
|
-
"Name": ["Alice", "Bob", "Charlie"],
|
62
|
-
"Score": [85, 92, 78],
|
63
|
-
"Passed": [True, True, False]
|
64
|
-
})
|
65
|
-
page2.append("This table shows individual scores and pass/fail status.")
|
66
|
-
page2.append(df2)
|
67
75
|
dashboard.add_page(page2)
|
68
|
-
|
69
|
-
# Page 3: Notes
|
70
|
-
page3 = Page("notes", "Notes")
|
71
|
-
page3.append("These are concluding notes about the dataset.")
|
72
|
-
page3.append("You can also add multiple text blocks like this.")
|
73
76
|
dashboard.add_page(page3)
|
74
77
|
|
75
|
-
#
|
78
|
+
# Export
|
76
79
|
dashboard.publish(output_dir="output")
|
77
80
|
```
|
78
81
|
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# staticdash
|
2
|
+
|
3
|
+
staticdash is a lightweight Python module for creating static, multi-page HTML dashboards. It supports Plotly plots, tables, and text content, with a fixed sidebar for navigation.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Clone the repository and install it in editable mode:
|
8
|
+
|
9
|
+
```bash
|
10
|
+
git clone https://github.com/briday1/staticdash.git
|
11
|
+
cd staticdash
|
12
|
+
pip install .
|
13
|
+
```
|
14
|
+
|
15
|
+
## Usage
|
16
|
+
|
17
|
+
Create a Python script like this:
|
18
|
+
|
19
|
+
```python
|
20
|
+
from staticdash.dashboard import Dashboard, Page
|
21
|
+
import plotly.express as px
|
22
|
+
import pandas as pd
|
23
|
+
import numpy as np
|
24
|
+
|
25
|
+
# Create sample data
|
26
|
+
df = pd.DataFrame({
|
27
|
+
"Category": ["A", "B", "C", "D"],
|
28
|
+
"Value": [10, 20, 30, 40]
|
29
|
+
})
|
30
|
+
|
31
|
+
df2 = pd.DataFrame({
|
32
|
+
"Time": pd.date_range("2024-01-01", periods=10, freq="D"),
|
33
|
+
"Signal": np.random.randn(10).cumsum()
|
34
|
+
})
|
35
|
+
|
36
|
+
fig1 = px.bar(df, x="Category", y="Value", title="Bar Chart Example")
|
37
|
+
fig2 = px.line(df2, x="Time", y="Signal", title="Signal over Time")
|
38
|
+
|
39
|
+
# Build dashboard
|
40
|
+
dashboard = Dashboard(title="StaticDash Demo")
|
41
|
+
|
42
|
+
# Page 1: Overview
|
43
|
+
page1 = Page("overview", "Overview")
|
44
|
+
page1.add("Welcome to the StaticDash demo. Below is a bar chart and a table.")
|
45
|
+
page1.add(fig1)
|
46
|
+
page1.add(df)
|
47
|
+
|
48
|
+
# Page 2: Timeseries
|
49
|
+
page2 = Page("timeseries", "Timeseries")
|
50
|
+
page2.add("Here is a random time series with cumulative noise.")
|
51
|
+
page2.add(fig2)
|
52
|
+
page2.add(df2)
|
53
|
+
|
54
|
+
# Page 3: Summary
|
55
|
+
page3 = Page("summary", "Summary")
|
56
|
+
page3.add("Summary and notes can be added here.")
|
57
|
+
page3.add("StaticDash is a lightweight static dashboard generator.")
|
58
|
+
|
59
|
+
# Register pages
|
60
|
+
dashboard.add_page(page1)
|
61
|
+
dashboard.add_page(page2)
|
62
|
+
dashboard.add_page(page3)
|
63
|
+
|
64
|
+
# Export
|
65
|
+
dashboard.publish(output_dir="output")
|
66
|
+
```
|
67
|
+
|
68
|
+
After running the script, open output/index.html in your browser.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
[build-system]
|
2
|
+
requires = ["setuptools>=61.0"]
|
3
|
+
build-backend = "setuptools.build_meta"
|
4
|
+
|
5
|
+
[project]
|
6
|
+
name = "staticdash"
|
7
|
+
version = "0.1.2"
|
8
|
+
description = "A lightweight static HTML dashboard generator with Plotly and pandas support."
|
9
|
+
authors = [
|
10
|
+
{ name = "Brian Day", email = "brian.day1@gmail.com" }
|
11
|
+
]
|
12
|
+
license = { text = "CC0-1.0" }
|
13
|
+
readme = "README.md"
|
14
|
+
requires-python = ">=3.8"
|
15
|
+
dependencies = [
|
16
|
+
"plotly",
|
17
|
+
"pandas",
|
18
|
+
"dominate"
|
19
|
+
]
|
20
|
+
|
21
|
+
[tool.setuptools]
|
22
|
+
packages = ["staticdash"]
|
23
|
+
|
24
|
+
[tool.setuptools.package-data]
|
25
|
+
staticdash = ["assets/css/*.css", "assets/js/*.js"]
|
26
|
+
|
27
|
+
[project.urls]
|
28
|
+
Homepage = "https://github.com/briday1/staticdash"
|
29
|
+
Repository = "https://github.com/briday1/staticdash"
|
@@ -0,0 +1 @@
|
|
1
|
+
from .dashboard import Page, Dashboard
|
@@ -0,0 +1,100 @@
|
|
1
|
+
body {
|
2
|
+
margin: 0;
|
3
|
+
font-family: sans-serif;
|
4
|
+
background-color: #f9f9f9;
|
5
|
+
color: #333;
|
6
|
+
}
|
7
|
+
|
8
|
+
#sidebar {
|
9
|
+
position: fixed;
|
10
|
+
top: 0;
|
11
|
+
left: 0;
|
12
|
+
width: 240px;
|
13
|
+
height: 100vh;
|
14
|
+
background-color: #2c3e50;
|
15
|
+
padding: 20px;
|
16
|
+
box-sizing: border-box;
|
17
|
+
overflow-y: auto;
|
18
|
+
}
|
19
|
+
|
20
|
+
#sidebar h1 {
|
21
|
+
color: #ecf0f1;
|
22
|
+
font-size: 20px;
|
23
|
+
margin-bottom: 20px;
|
24
|
+
}
|
25
|
+
|
26
|
+
.nav-link {
|
27
|
+
display: block;
|
28
|
+
color: #bdc3c7;
|
29
|
+
text-decoration: none;
|
30
|
+
margin: 10px 0;
|
31
|
+
font-weight: bold;
|
32
|
+
transition: color 0.3s;
|
33
|
+
}
|
34
|
+
|
35
|
+
.nav-link.active,
|
36
|
+
.nav-link:hover {
|
37
|
+
color: #ffffff;
|
38
|
+
}
|
39
|
+
|
40
|
+
#content {
|
41
|
+
margin-left: 260px;
|
42
|
+
padding: 20px;
|
43
|
+
box-sizing: border-box;
|
44
|
+
}
|
45
|
+
|
46
|
+
.page-section {
|
47
|
+
display: none;
|
48
|
+
}
|
49
|
+
|
50
|
+
.page-section.active {
|
51
|
+
display: block;
|
52
|
+
}
|
53
|
+
|
54
|
+
.plot-container {
|
55
|
+
margin: 20px 0;
|
56
|
+
}
|
57
|
+
|
58
|
+
table {
|
59
|
+
width: 100%;
|
60
|
+
border-collapse: collapse;
|
61
|
+
margin-top: 1em;
|
62
|
+
background-color: white;
|
63
|
+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
64
|
+
}
|
65
|
+
|
66
|
+
thead {
|
67
|
+
background-color: #2c3e50;
|
68
|
+
color: white;
|
69
|
+
}
|
70
|
+
|
71
|
+
th,
|
72
|
+
td {
|
73
|
+
padding: 10px;
|
74
|
+
border: 1px solid #ddd;
|
75
|
+
text-align: left;
|
76
|
+
}
|
77
|
+
|
78
|
+
tr:nth-child(even) {
|
79
|
+
background-color: #f2f2f2;
|
80
|
+
}
|
81
|
+
|
82
|
+
tr:hover {
|
83
|
+
background-color: #e8f0fe;
|
84
|
+
}
|
85
|
+
|
86
|
+
.dash-image {
|
87
|
+
max-width: 100%;
|
88
|
+
height: auto;
|
89
|
+
margin: 1em 0;
|
90
|
+
}
|
91
|
+
|
92
|
+
|
93
|
+
.plot-container {
|
94
|
+
width: 100%;
|
95
|
+
}
|
96
|
+
|
97
|
+
.plot-container .plotly-graph-div {
|
98
|
+
width: 100% !important;
|
99
|
+
height: auto !important;
|
100
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
document.addEventListener("DOMContentLoaded", () => {
|
2
|
+
const links = document.querySelectorAll(".nav-link");
|
3
|
+
const sections = document.querySelectorAll(".page-section");
|
4
|
+
|
5
|
+
function resizePlotsIn(section) {
|
6
|
+
const plots = section.querySelectorAll(".plotly-graph-div");
|
7
|
+
plots.forEach(plot => {
|
8
|
+
if (typeof Plotly !== "undefined" && plot.data) {
|
9
|
+
Plotly.Plots.resize(plot);
|
10
|
+
}
|
11
|
+
});
|
12
|
+
}
|
13
|
+
|
14
|
+
function showPage(id) {
|
15
|
+
sections.forEach(section => section.style.display = "none");
|
16
|
+
const page = document.getElementById(id);
|
17
|
+
if (page) {
|
18
|
+
page.style.display = "block";
|
19
|
+
|
20
|
+
// Resize Plotly charts within the newly visible section
|
21
|
+
requestAnimationFrame(() => {
|
22
|
+
resizePlotsIn(page);
|
23
|
+
});
|
24
|
+
}
|
25
|
+
|
26
|
+
links.forEach(link => link.classList.remove("active"));
|
27
|
+
const activeLink = Array.from(links).find(link => link.dataset.target === id);
|
28
|
+
if (activeLink) activeLink.classList.add("active");
|
29
|
+
}
|
30
|
+
|
31
|
+
links.forEach(link => {
|
32
|
+
link.addEventListener("click", e => {
|
33
|
+
e.preventDefault();
|
34
|
+
const targetId = link.dataset.target;
|
35
|
+
showPage(targetId);
|
36
|
+
});
|
37
|
+
});
|
38
|
+
|
39
|
+
// Initial page load
|
40
|
+
if (sections.length > 0) {
|
41
|
+
showPage(sections[0].id);
|
42
|
+
}
|
43
|
+
});
|
@@ -0,0 +1,119 @@
|
|
1
|
+
import os
|
2
|
+
import shutil
|
3
|
+
import uuid
|
4
|
+
import pandas as pd
|
5
|
+
import plotly.graph_objects as go
|
6
|
+
import plotly.express as px
|
7
|
+
from dominate import document
|
8
|
+
from dominate.tags import div, h1, h2, p, a, script, link, table, thead, tr, th, tbody, td, span
|
9
|
+
from dominate.util import raw as raw_util # To avoid ambiguity
|
10
|
+
|
11
|
+
class Page:
|
12
|
+
def __init__(self, slug, title):
|
13
|
+
self.slug = slug
|
14
|
+
self.title = title
|
15
|
+
self.elements = []
|
16
|
+
|
17
|
+
def add_text(self, text):
|
18
|
+
self.elements.append(("text", text))
|
19
|
+
|
20
|
+
def add_plot(self, plot):
|
21
|
+
html = raw_util(plot.to_html(full_html=False, include_plotlyjs='cdn', config={'responsive': True}))
|
22
|
+
self.elements.append(("plot", html))
|
23
|
+
|
24
|
+
def add_table(self, df, table_id=None):
|
25
|
+
if table_id is None:
|
26
|
+
table_id = f"table-{len(self.elements)}"
|
27
|
+
html = df.to_html(classes="table-hover table-striped", index=False, border=0, table_id=table_id)
|
28
|
+
self.elements.append(("table", (html, table_id)))
|
29
|
+
|
30
|
+
def add(self, element):
|
31
|
+
if isinstance(element, str):
|
32
|
+
self.add_text(element)
|
33
|
+
elif isinstance(element, go.Figure):
|
34
|
+
self.add_plot(element)
|
35
|
+
elif isinstance(element, pd.DataFrame):
|
36
|
+
self.add_table(element)
|
37
|
+
else:
|
38
|
+
raise ValueError(f"Unsupported element type: {type(element)}")
|
39
|
+
|
40
|
+
def render(self, index):
|
41
|
+
section = div(id=f"page-{index}", cls="page-section")
|
42
|
+
section += h1(self.title)
|
43
|
+
for kind, content in self.elements:
|
44
|
+
if kind == "text":
|
45
|
+
section += p(content)
|
46
|
+
elif kind == "plot":
|
47
|
+
section += div(content, cls="plot-container")
|
48
|
+
elif kind == "table":
|
49
|
+
table_html, _ = content
|
50
|
+
section += raw_util(table_html)
|
51
|
+
return section
|
52
|
+
|
53
|
+
class Dashboard:
|
54
|
+
def __init__(self, title="Dashboard"):
|
55
|
+
self.title = title
|
56
|
+
self.pages = []
|
57
|
+
|
58
|
+
def add_page(self, page: Page):
|
59
|
+
self.pages.append(page)
|
60
|
+
|
61
|
+
def publish(self, output_dir="output"):
|
62
|
+
os.makedirs(output_dir, exist_ok=True)
|
63
|
+
pages_dir = os.path.join(output_dir, "pages")
|
64
|
+
os.makedirs(pages_dir, exist_ok=True)
|
65
|
+
|
66
|
+
# Copy assets
|
67
|
+
assets_src = os.path.join(os.path.dirname(__file__), "assets")
|
68
|
+
assets_dst = os.path.join(output_dir, "assets")
|
69
|
+
shutil.copytree(assets_src, assets_dst, dirs_exist_ok=True)
|
70
|
+
|
71
|
+
# Generate each page
|
72
|
+
for page in self.pages:
|
73
|
+
doc = document(title=page.title)
|
74
|
+
with doc.head:
|
75
|
+
doc.head.add(link(rel="stylesheet", href="../assets/css/style.css"))
|
76
|
+
doc.head.add(script(type="text/javascript", src="../assets/js/script.js"))
|
77
|
+
|
78
|
+
with doc:
|
79
|
+
with div(cls="page-section", id=f"page-{page.slug}"):
|
80
|
+
h1(page.title)
|
81
|
+
for kind, content in page.elements:
|
82
|
+
if kind == "text":
|
83
|
+
p(content)
|
84
|
+
elif kind == "plot":
|
85
|
+
div(content, cls="plot-container")
|
86
|
+
elif kind == "table":
|
87
|
+
table_html, _ = content
|
88
|
+
doc.add(raw_util(table_html))
|
89
|
+
|
90
|
+
with open(os.path.join(pages_dir, f"{page.slug}.html"), "w") as f:
|
91
|
+
f.write(str(doc))
|
92
|
+
|
93
|
+
# Generate index.html with navigation
|
94
|
+
index_doc = document(title=self.title)
|
95
|
+
with index_doc.head:
|
96
|
+
index_doc.head.add(link(rel="stylesheet", href="assets/css/style.css"))
|
97
|
+
index_doc.head.add(script(type="text/javascript", src="assets/js/script.js"))
|
98
|
+
|
99
|
+
with index_doc:
|
100
|
+
with div(id="sidebar"):
|
101
|
+
h1(self.title)
|
102
|
+
for page in self.pages:
|
103
|
+
a(page.title, cls="nav-link", href="#", **{"data-target": f"page-{page.slug}"})
|
104
|
+
with div(id="content"):
|
105
|
+
for page in self.pages:
|
106
|
+
with div(id=f"page-{page.slug}", cls="page-section", style="display:none;"):
|
107
|
+
h2(page.title)
|
108
|
+
for kind, content in page.elements:
|
109
|
+
if kind == "text":
|
110
|
+
p(content)
|
111
|
+
elif kind == "plot":
|
112
|
+
div(content, cls="plot-container")
|
113
|
+
elif kind == "table":
|
114
|
+
table_html, _ = content
|
115
|
+
div(raw_util(table_html))
|
116
|
+
|
117
|
+
|
118
|
+
with open(os.path.join(output_dir, "index.html"), "w") as f:
|
119
|
+
f.write(str(index_doc))
|
@@ -1,13 +1,16 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: staticdash
|
3
|
-
Version: 0.1.
|
4
|
-
Summary: A
|
3
|
+
Version: 0.1.2
|
4
|
+
Summary: A lightweight static HTML dashboard generator with Plotly and pandas support.
|
5
5
|
Author-email: Brian Day <brian.day1@gmail.com>
|
6
|
-
License
|
6
|
+
License: CC0-1.0
|
7
7
|
Project-URL: Homepage, https://github.com/briday1/staticdash
|
8
8
|
Project-URL: Repository, https://github.com/briday1/staticdash
|
9
9
|
Requires-Python: >=3.8
|
10
10
|
Description-Content-Type: text/markdown
|
11
|
+
Requires-Dist: plotly
|
12
|
+
Requires-Dist: pandas
|
13
|
+
Requires-Dist: dominate
|
11
14
|
|
12
15
|
# staticdash
|
13
16
|
|
@@ -29,50 +32,50 @@ Create a Python script like this:
|
|
29
32
|
|
30
33
|
```python
|
31
34
|
from staticdash.dashboard import Dashboard, Page
|
32
|
-
import plotly.
|
35
|
+
import plotly.express as px
|
33
36
|
import pandas as pd
|
37
|
+
import numpy as np
|
34
38
|
|
35
|
-
# Create
|
36
|
-
|
39
|
+
# Create sample data
|
40
|
+
df = pd.DataFrame({
|
41
|
+
"Category": ["A", "B", "C", "D"],
|
42
|
+
"Value": [10, 20, 30, 40]
|
43
|
+
})
|
37
44
|
|
38
|
-
|
39
|
-
|
45
|
+
df2 = pd.DataFrame({
|
46
|
+
"Time": pd.date_range("2024-01-01", periods=10, freq="D"),
|
47
|
+
"Signal": np.random.randn(10).cumsum()
|
48
|
+
})
|
40
49
|
|
41
|
-
|
42
|
-
|
43
|
-
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 1, 6], mode='lines+markers', name="Demo Line"))
|
44
|
-
fig.update_layout(title="Sample Plot")
|
45
|
-
page1.append(fig)
|
50
|
+
fig1 = px.bar(df, x="Category", y="Value", title="Bar Chart Example")
|
51
|
+
fig2 = px.line(df2, x="Time", y="Signal", title="Signal over Time")
|
46
52
|
|
47
|
-
#
|
48
|
-
|
49
|
-
"Category": ["A", "B", "C"],
|
50
|
-
"Value": [100, 200, 150]
|
51
|
-
})
|
52
|
-
page1.append(df1)
|
53
|
+
# Build dashboard
|
54
|
+
dashboard = Dashboard(title="StaticDash Demo")
|
53
55
|
|
54
|
-
#
|
55
|
-
page1
|
56
|
+
# Page 1: Overview
|
57
|
+
page1 = Page("overview", "Overview")
|
58
|
+
page1.add("Welcome to the StaticDash demo. Below is a bar chart and a table.")
|
59
|
+
page1.add(fig1)
|
60
|
+
page1.add(df)
|
61
|
+
|
62
|
+
# Page 2: Timeseries
|
63
|
+
page2 = Page("timeseries", "Timeseries")
|
64
|
+
page2.add("Here is a random time series with cumulative noise.")
|
65
|
+
page2.add(fig2)
|
66
|
+
page2.add(df2)
|
67
|
+
|
68
|
+
# Page 3: Summary
|
69
|
+
page3 = Page("summary", "Summary")
|
70
|
+
page3.add("Summary and notes can be added here.")
|
71
|
+
page3.add("StaticDash is a lightweight static dashboard generator.")
|
72
|
+
|
73
|
+
# Register pages
|
56
74
|
dashboard.add_page(page1)
|
57
|
-
|
58
|
-
# Page 2: Data Table
|
59
|
-
page2 = Page("data", "Data")
|
60
|
-
df2 = pd.DataFrame({
|
61
|
-
"Name": ["Alice", "Bob", "Charlie"],
|
62
|
-
"Score": [85, 92, 78],
|
63
|
-
"Passed": [True, True, False]
|
64
|
-
})
|
65
|
-
page2.append("This table shows individual scores and pass/fail status.")
|
66
|
-
page2.append(df2)
|
67
75
|
dashboard.add_page(page2)
|
68
|
-
|
69
|
-
# Page 3: Notes
|
70
|
-
page3 = Page("notes", "Notes")
|
71
|
-
page3.append("These are concluding notes about the dataset.")
|
72
|
-
page3.append("You can also add multiple text blocks like this.")
|
73
76
|
dashboard.add_page(page3)
|
74
77
|
|
75
|
-
#
|
78
|
+
# Export
|
76
79
|
dashboard.publish(output_dir="output")
|
77
80
|
```
|
78
81
|
|
@@ -5,4 +5,7 @@ staticdash/dashboard.py
|
|
5
5
|
staticdash.egg-info/PKG-INFO
|
6
6
|
staticdash.egg-info/SOURCES.txt
|
7
7
|
staticdash.egg-info/dependency_links.txt
|
8
|
-
staticdash.egg-info/
|
8
|
+
staticdash.egg-info/requires.txt
|
9
|
+
staticdash.egg-info/top_level.txt
|
10
|
+
staticdash/assets/css/style.css
|
11
|
+
staticdash/assets/js/script.js
|
staticdash-0.1.0/README.md
DELETED
@@ -1,68 +0,0 @@
|
|
1
|
-
# staticdash
|
2
|
-
|
3
|
-
staticdash is a lightweight Python module for creating static, multi-page HTML dashboards. It supports Plotly plots, tables, and text content, with a fixed sidebar for navigation.
|
4
|
-
|
5
|
-
## Installation
|
6
|
-
|
7
|
-
Clone the repository and install it in editable mode:
|
8
|
-
|
9
|
-
```bash
|
10
|
-
git clone https://github.com/briday1/staticdash.git
|
11
|
-
cd staticdash
|
12
|
-
pip install .
|
13
|
-
```
|
14
|
-
|
15
|
-
## Usage
|
16
|
-
|
17
|
-
Create a Python script like this:
|
18
|
-
|
19
|
-
```python
|
20
|
-
from staticdash.dashboard import Dashboard, Page
|
21
|
-
import plotly.graph_objects as go
|
22
|
-
import pandas as pd
|
23
|
-
|
24
|
-
# Create the dashboard
|
25
|
-
dashboard = Dashboard(title="StaticDash Demo")
|
26
|
-
|
27
|
-
# Page 1: Overview
|
28
|
-
page1 = Page("overview", "Overview")
|
29
|
-
|
30
|
-
# Add plo
|
31
|
-
fig = go.Figure()
|
32
|
-
fig.add_trace(go.Scatter(x=[1, 2, 3], y=[4, 1, 6], mode='lines+markers', name="Demo Line"))
|
33
|
-
fig.update_layout(title="Sample Plot")
|
34
|
-
page1.append(fig)
|
35
|
-
|
36
|
-
# Add table
|
37
|
-
df1 = pd.DataFrame({
|
38
|
-
"Category": ["A", "B", "C"],
|
39
|
-
"Value": [100, 200, 150]
|
40
|
-
})
|
41
|
-
page1.append(df1)
|
42
|
-
|
43
|
-
# Add extra text
|
44
|
-
page1.append("This page includes a sample plot, table, and descriptive text.")
|
45
|
-
dashboard.add_page(page1)
|
46
|
-
|
47
|
-
# Page 2: Data Table
|
48
|
-
page2 = Page("data", "Data")
|
49
|
-
df2 = pd.DataFrame({
|
50
|
-
"Name": ["Alice", "Bob", "Charlie"],
|
51
|
-
"Score": [85, 92, 78],
|
52
|
-
"Passed": [True, True, False]
|
53
|
-
})
|
54
|
-
page2.append("This table shows individual scores and pass/fail status.")
|
55
|
-
page2.append(df2)
|
56
|
-
dashboard.add_page(page2)
|
57
|
-
|
58
|
-
# Page 3: Notes
|
59
|
-
page3 = Page("notes", "Notes")
|
60
|
-
page3.append("These are concluding notes about the dataset.")
|
61
|
-
page3.append("You can also add multiple text blocks like this.")
|
62
|
-
dashboard.add_page(page3)
|
63
|
-
|
64
|
-
# Publish the dashboard
|
65
|
-
dashboard.publish(output_dir="output")
|
66
|
-
```
|
67
|
-
|
68
|
-
After running the script, open output/index.html in your browser.
|
staticdash-0.1.0/pyproject.toml
DELETED
@@ -1,22 +0,0 @@
|
|
1
|
-
[project]
|
2
|
-
name = "staticdash"
|
3
|
-
version = "0.1.0"
|
4
|
-
description = "A minimal static dashboard generator with Plotly support"
|
5
|
-
authors = [
|
6
|
-
{ name = "Brian Day", email = "brian.day1@gmail.com" }
|
7
|
-
]
|
8
|
-
license = "MIT"
|
9
|
-
readme = "README.md"
|
10
|
-
requires-python = ">=3.8"
|
11
|
-
|
12
|
-
[project.urls]
|
13
|
-
Homepage = "https://github.com/briday1/staticdash"
|
14
|
-
Repository = "https://github.com/briday1/staticdash"
|
15
|
-
|
16
|
-
[build-system]
|
17
|
-
requires = ["setuptools>=61.0"]
|
18
|
-
build-backend = "setuptools.build_meta"
|
19
|
-
|
20
|
-
[tool.setuptools.packages.find]
|
21
|
-
include = ["staticdash*"]
|
22
|
-
exclude = ["output*"]
|
@@ -1,88 +0,0 @@
|
|
1
|
-
import os
|
2
|
-
import shutil
|
3
|
-
import dominate
|
4
|
-
from dominate.tags import div, h1, h2, p, a, script, link, table, thead, tr, th, tbody, td
|
5
|
-
from dominate.util import raw # ✅ correct usage
|
6
|
-
|
7
|
-
import plotly.graph_objs as go
|
8
|
-
import pandas as pd
|
9
|
-
|
10
|
-
|
11
|
-
class Page:
|
12
|
-
def __init__(self, slug, title):
|
13
|
-
self.slug = slug
|
14
|
-
self.title = title
|
15
|
-
self.elements = []
|
16
|
-
|
17
|
-
def append(self, element):
|
18
|
-
if isinstance(element, str):
|
19
|
-
self.elements.append(p(element))
|
20
|
-
elif isinstance(element, go.Figure):
|
21
|
-
html = element.to_html(include_plotlyjs=False, full_html=False)
|
22
|
-
self.elements.append(raw(html))
|
23
|
-
elif isinstance(element, pd.DataFrame):
|
24
|
-
tbl = table()
|
25
|
-
tbl.add(thead(tr(*[th(col) for col in element.columns])))
|
26
|
-
tb = tbody()
|
27
|
-
for _, row in element.iterrows():
|
28
|
-
tb.add(tr(*[td(str(val)) for val in row]))
|
29
|
-
tbl.add(tb)
|
30
|
-
self.elements.append(tbl)
|
31
|
-
else:
|
32
|
-
self.elements.append(element)
|
33
|
-
|
34
|
-
|
35
|
-
class Dashboard:
|
36
|
-
def __init__(self, title="Dashboard"):
|
37
|
-
self.title = title
|
38
|
-
self.pages = []
|
39
|
-
|
40
|
-
def add_page(self, page):
|
41
|
-
self.pages.append(page)
|
42
|
-
|
43
|
-
def publish(self, output_dir="output"):
|
44
|
-
os.makedirs(output_dir, exist_ok=True)
|
45
|
-
self._write_assets(output_dir)
|
46
|
-
self._write_index(output_dir)
|
47
|
-
|
48
|
-
def _write_assets(self, output_dir):
|
49
|
-
css_src = os.path.join(os.path.dirname(__file__), "assets", "css", "style.css")
|
50
|
-
css_dst_dir = os.path.join(output_dir, "assets", "css")
|
51
|
-
os.makedirs(css_dst_dir, exist_ok=True)
|
52
|
-
shutil.copyfile(css_src, os.path.join(css_dst_dir, "style.css"))
|
53
|
-
|
54
|
-
def _write_index(self, output_dir):
|
55
|
-
doc = dominate.document(title=self.title)
|
56
|
-
|
57
|
-
with doc.head:
|
58
|
-
link(rel="stylesheet", href="assets/css/style.css")
|
59
|
-
script(src="https://cdn.plot.ly/plotly-latest.min.js")
|
60
|
-
|
61
|
-
with doc:
|
62
|
-
with div(id="sidebar"):
|
63
|
-
h1(self.title)
|
64
|
-
for i, page in enumerate(self.pages):
|
65
|
-
a(page.title, href="#", cls="nav-link", onclick=f"showPage('page-{i}')")
|
66
|
-
|
67
|
-
with div(id="content"):
|
68
|
-
for i, page in enumerate(self.pages):
|
69
|
-
section = div(id=f"page-{i}", cls="page-section")
|
70
|
-
section.add(h2(page.title))
|
71
|
-
for elem in page.elements:
|
72
|
-
section.add(elem)
|
73
|
-
|
74
|
-
# JavaScript block
|
75
|
-
js_code = """
|
76
|
-
function showPage(id) {
|
77
|
-
document.querySelectorAll('.page-section').forEach(el => el.style.display = 'none');
|
78
|
-
document.getElementById(id).style.display = 'block';
|
79
|
-
document.querySelectorAll('.nav-link').forEach(el => el.classList.remove('active'));
|
80
|
-
event.target.classList.add('active');
|
81
|
-
}
|
82
|
-
window.onload = () => showPage('page-0');
|
83
|
-
"""
|
84
|
-
with doc:
|
85
|
-
script(raw(js_code))
|
86
|
-
|
87
|
-
with open(os.path.join(output_dir, "index.html"), "w") as f:
|
88
|
-
f.write(doc.render())
|
File without changes
|
File without changes
|
File without changes
|