staticdash 0.1.0__py3-none-any.whl → 0.1.1__py3-none-any.whl

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/__init__.py CHANGED
@@ -1,2 +1 @@
1
- from .dashboard import Page, Dashboard
2
- import simpledash.assets.css
1
+ from .dashboard import Page, Dashboard
@@ -0,0 +1,90 @@
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
+ }
@@ -0,0 +1,27 @@
1
+ document.addEventListener("DOMContentLoaded", () => {
2
+ const links = document.querySelectorAll(".nav-link");
3
+ const sections = document.querySelectorAll(".page-section");
4
+
5
+ function showPage(id) {
6
+ sections.forEach(section => section.style.display = "none");
7
+ const page = document.getElementById(id);
8
+ if (page) page.style.display = "block";
9
+
10
+ links.forEach(link => link.classList.remove("active"));
11
+ const activeLink = Array.from(links).find(link => link.dataset.target === id);
12
+ if (activeLink) activeLink.classList.add("active");
13
+ }
14
+
15
+ links.forEach(link => {
16
+ link.addEventListener("click", e => {
17
+ e.preventDefault();
18
+ const targetId = link.dataset.target;
19
+ showPage(targetId);
20
+ });
21
+ });
22
+
23
+ // Show the first page by default
24
+ if (sections.length > 0) {
25
+ showPage(sections[0].id);
26
+ }
27
+ });
staticdash/dashboard.py CHANGED
@@ -1,12 +1,12 @@
1
1
  import os
2
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
3
+ import uuid
8
4
  import pandas as pd
9
-
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
10
 
11
11
  class Page:
12
12
  def __init__(self, slug, title):
@@ -14,75 +14,106 @@ class Page:
14
14
  self.title = title
15
15
  self.elements = []
16
16
 
17
- def append(self, element):
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'))
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):
18
31
  if isinstance(element, str):
19
- self.elements.append(p(element))
32
+ self.add_text(element)
20
33
  elif isinstance(element, go.Figure):
21
- html = element.to_html(include_plotlyjs=False, full_html=False)
22
- self.elements.append(raw(html))
34
+ self.add_plot(element)
23
35
  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)
36
+ self.add_table(element)
31
37
  else:
32
- self.elements.append(element)
38
+ raise ValueError(f"Unsupported element type: {type(element)}")
33
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
34
52
 
35
53
  class Dashboard:
36
54
  def __init__(self, title="Dashboard"):
37
55
  self.title = title
38
56
  self.pages = []
39
57
 
40
- def add_page(self, page):
58
+ def add_page(self, page: Page):
41
59
  self.pages.append(page)
42
60
 
43
61
  def publish(self, output_dir="output"):
44
62
  os.makedirs(output_dir, exist_ok=True)
45
- self._write_assets(output_dir)
46
- self._write_index(output_dir)
63
+ pages_dir = os.path.join(output_dir, "pages")
64
+ os.makedirs(pages_dir, exist_ok=True)
47
65
 
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"))
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)
53
70
 
54
- def _write_index(self, output_dir):
55
- doc = dominate.document(title=self.title)
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"))
56
77
 
57
- with doc.head:
58
- link(rel="stylesheet", href="assets/css/style.css")
59
- script(src="https://cdn.plot.ly/plotly-latest.min.js")
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))
60
89
 
61
- with doc:
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:
62
100
  with div(id="sidebar"):
63
101
  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
-
102
+ for page in self.pages:
103
+ a(page.title, cls="nav-link", href="#", **{"data-target": f"page-{page.slug}"})
67
104
  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))
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
+
86
117
 
87
118
  with open(os.path.join(output_dir, "index.html"), "w") as f:
88
- f.write(doc.render())
119
+ f.write(str(index_doc))
@@ -1,13 +1,16 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: staticdash
3
- Version: 0.1.0
4
- Summary: A minimal static dashboard generator with Plotly support
3
+ Version: 0.1.1
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-Expression: MIT
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.graph_objects as go
35
+ import plotly.express as px
33
36
  import pandas as pd
37
+ import numpy as np
34
38
 
35
- # Create the dashboard
36
- dashboard = Dashboard(title="StaticDash Demo")
39
+ # Create sample data
40
+ df = pd.DataFrame({
41
+ "Category": ["A", "B", "C", "D"],
42
+ "Value": [10, 20, 30, 40]
43
+ })
37
44
 
38
- # Page 1: Overview
39
- page1 = Page("overview", "Overview")
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
- # Add plo
42
- fig = go.Figure()
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
- # Add table
48
- df1 = pd.DataFrame({
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
- # Add extra text
55
- page1.append("This page includes a sample plot, table, and descriptive text.")
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
- # Publish the dashboard
78
+ # Export
76
79
  dashboard.publish(output_dir="output")
77
80
  ```
78
81
 
@@ -0,0 +1,8 @@
1
+ staticdash/__init__.py,sha256=KqViaDkiQnhBI8-j3hr14umLDmPgddvdB_G1nJeC5Xs,38
2
+ staticdash/dashboard.py,sha256=7kLyd2UBKJNTi5HwepG9ivEcxhaSq9t3jqjSFujS5xg,4615
3
+ staticdash/assets/css/style.css,sha256=mc4j24VPIMB9pVWqH_ndyCrnLIJXUC0Tp3qn3lTOnJA,1186
4
+ staticdash/assets/js/script.js,sha256=L9HxPRlKMrvSwtLftxA_9WIEDLjMCi7axJNfeQQNRfM,842
5
+ staticdash-0.1.1.dist-info/METADATA,sha256=r55fdjdvi4aEJQ-6w8lhW9vD2vCXjjcLNQWTQO_6qRQ,2157
6
+ staticdash-0.1.1.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
7
+ staticdash-0.1.1.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
8
+ staticdash-0.1.1.dist-info/RECORD,,
@@ -1,6 +0,0 @@
1
- staticdash/__init__.py,sha256=NBEjkx3xBwyM5uAbBRGypiokkebsNwF7yM7AFP0quDg,67
2
- staticdash/dashboard.py,sha256=ShL9YkqiTL4Lwd9Wkvr6TEBFUFmMN5BOXoC3wKJ5rkQ,3081
3
- staticdash-0.1.0.dist-info/METADATA,sha256=cRIzyfwdxk1knwaC786xdqArgSbGu3i4YAllAV_qfwA,2075
4
- staticdash-0.1.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
5
- staticdash-0.1.0.dist-info/top_level.txt,sha256=3MzZU6SptkUkjcHV1cvPji0H4aRzPphLHnpStgGEcxM,11
6
- staticdash-0.1.0.dist-info/RECORD,,