Skip to content

Commit 8736fb4

Browse files
separating template from python code
1 parent 3b5141f commit 8736fb4

1 file changed

Lines changed: 310 additions & 0 deletions

File tree

Lines changed: 310 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,310 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<title>{html_page_title}</title>
7+
<script type="text/javascript" src="{cdn_js_url}"></script>
8+
<link href="{cdn_css_url}" rel="stylesheet" type="text/css" />
9+
<style type="text/css">
10+
body, html {{
11+
margin: 0;
12+
padding: 0;
13+
width: 100%;
14+
height: 100%;
15+
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
16+
background-color: #f4f6f8; /* Light background for the page */
17+
display: flex;
18+
flex-direction: column;
19+
overflow: hidden; /* Prevent body scrollbars */
20+
}}
21+
22+
.config-panel-wrapper {{
23+
width: 100%;
24+
background-color: #ffffff;
25+
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
26+
z-index: 10; /* Ensure it's above the graph if any overlap issues */
27+
flex-shrink: 0; /* Prevent panel from shrinking */
28+
}}
29+
30+
.config-panel-header {{
31+
display: flex;
32+
justify-content: space-between;
33+
align-items: center;
34+
padding: 10px 15px;
35+
border-bottom: 1px solid #e0e0e0;
36+
cursor: pointer;
37+
background-color: #f9f9f9;
38+
}}
39+
40+
.config-panel-header h3 {{
41+
margin: 0;
42+
font-size: 16px;
43+
font-weight: 600;
44+
color: #333;
45+
}}
46+
47+
.config-toggle-btn {{
48+
background: none;
49+
border: none;
50+
font-size: 18px;
51+
cursor: pointer;
52+
padding: 5px;
53+
color: #555;
54+
}}
55+
.config-toggle-btn::after {{
56+
content: '\25BC'; /* Down arrow ▼ */
57+
display: inline-block;
58+
transition: transform 0.2s ease-in-out;
59+
}}
60+
.collapsed .config-toggle-btn::after {{
61+
transform: rotate(-90deg); /* Right arrow for collapsed state */
62+
}}
63+
64+
#config-container-content-{div_id_suffix} {{
65+
max-height: 40vh; /* Default expanded max height */
66+
overflow-y: auto;
67+
padding: 15px;
68+
box-sizing: border-box;
69+
background-color: #ffffff;
70+
transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out;
71+
}}
72+
73+
.collapsed #config-container-content-{div_id_suffix} {{
74+
max-height: 0;
75+
padding-top: 0;
76+
padding-bottom: 0;
77+
overflow: hidden;
78+
border-bottom: none; /* Hide border when collapsed */
79+
}}
80+
81+
#mynetwork-{div_id_suffix} {{
82+
width: 100%;
83+
flex-grow: 1; /* Graph takes remaining vertical space */
84+
min-height: 0; /* Important for flex children to shrink */
85+
background-color: #ffffff; /* Graph background */
86+
}}
87+
88+
.filter-panel {{
89+
display: flex;
90+
align-items: center;
91+
gap: 10px;
92+
padding: 8px 15px;
93+
background-color: #e9ecef;
94+
border-bottom: 1px solid #dee2e6;
95+
flex-shrink: 0;
96+
}}
97+
.filter-panel label {{
98+
font-size: 13px;
99+
font-weight: 500;
100+
color: #495057;
101+
}}
102+
.filter-panel input[type=text] {{
103+
flex-grow: 1;
104+
padding: 6px 8px;
105+
border: 1px solid #ced4da;
106+
border-radius: 4px;
107+
font-size: 13px;
108+
}}
109+
.filter-panel button {{
110+
padding: 6px 12px;
111+
font-size: 13px;
112+
border-radius: 4px;
113+
border: 1px solid #6c757d;
114+
background-color: #6c757d;
115+
color: white;
116+
cursor: pointer;
117+
}}
118+
.filter-panel button:hover {{
119+
background-color: #5a6268;
120+
}}
121+
122+
.info-panel {{
123+
padding: 8px 15px;
124+
background-color: #f8f9fa;
125+
font-size: 13px;
126+
color: #495057;
127+
text-align: center;
128+
border-bottom: 1px solid #dee2e6;
129+
flex-shrink: 0;
130+
}}
131+
132+
/* Basic styling for vis.js config elements to blend better */
133+
div.vis-configuration-wrapper {{ padding: 0; }}
134+
div.vis-configuration-wrapper table {{ width: 100%; }}
135+
div.vis-configuration-wrapper table tr td:first-child {{ width: 30%; font-size: 13px; }}
136+
div.vis-configuration-wrapper input[type=text],
137+
div.vis-configuration-wrapper select {{ width: 95%; padding: 6px; margin: 2px 0; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 13px; }}
138+
div.vis-configuration-wrapper input[type=range] {{ width: 60%; }}
139+
div.vis-configuration-wrapper .vis-label {{ font-size: 13px; color: #333; }}
140+
141+
</style>
142+
</head>
143+
<body>
144+
<div class="config-panel-wrapper" id="config-panel-wrapper-{div_id_suffix}">
145+
<div class="config-panel-header" id="config-panel-header-{div_id_suffix}" role="button" tabindex="0" aria-expanded="true" aria-controls="config-container-content-{div_id_suffix}">
146+
<h3>Configuration</h3>
147+
<button class="config-toggle-btn" id="config-toggle-btn-{div_id_suffix}" aria-label="Toggle configuration panel"></button>
148+
</div>
149+
<div id="config-container-content-{div_id_suffix}">
150+
<!-- Vis.js configuration UI will be injected here -->
151+
</div>
152+
</div>
153+
154+
<div class="filter-panel">
155+
<label for="search-input-{div_id_suffix}">Filter nodes (regex):</label>
156+
<input type="text" id="search-input-{div_id_suffix}" placeholder="e.g., ^IMP_ or ELEC$">
157+
<button id="clear-btn-{div_id_suffix}">Clear</button>
158+
</div>
159+
160+
<div class="info-panel">
161+
<strong>Tip:</strong> Click a node to isolate it and its neighbors. Click the background to reset the view.
162+
</div>
163+
164+
<div id="mynetwork-{div_id_suffix}"></div>
165+
166+
<script type="text/javascript">
167+
(function() {{
168+
// These arrays are the master dataset
169+
var allNodesArray = {nodes_json_str};
170+
var allEdgesArray = {edges_json_str};
171+
var optionsObject = {options_json_str};
172+
173+
// Get DOM elements
174+
var configWrapper = document.getElementById('config-panel-wrapper-{div_id_suffix}');
175+
var configHeader = document.getElementById('config-panel-header-{div_id_suffix}');
176+
var configContent = document.getElementById('config-container-content-{div_id_suffix}');
177+
var toggleButton = document.getElementById('config-toggle-btn-{div_id_suffix}');
178+
var searchInput = document.getElementById('search-input-{div_id_suffix}');
179+
var clearButton = document.getElementById('clear-btn-{div_id_suffix}');
180+
181+
// Handle config panel toggle
182+
if (optionsObject.configure && optionsObject.configure.enabled) {{
183+
if (!optionsObject.configure.container) {{
184+
optionsObject.configure.container = configContent;
185+
}}
186+
configHeader.addEventListener('click', function() {{
187+
configWrapper.classList.toggle('collapsed');
188+
var isExpanded = !configWrapper.classList.contains('collapsed');
189+
configHeader.setAttribute('aria-expanded', isExpanded);
190+
toggleButton.setAttribute('aria-expanded', isExpanded);
191+
}});
192+
configHeader.addEventListener('keydown', function(event) {{
193+
if (event.key === 'Enter' || event.key === ' ') {{
194+
configWrapper.classList.toggle('collapsed');
195+
var isExpanded = !configWrapper.classList.contains('collapsed');
196+
configHeader.setAttribute('aria-expanded', isExpanded);
197+
toggleButton.setAttribute('aria-expanded', isExpanded);
198+
event.preventDefault();
199+
}}
200+
}});
201+
}} else {{
202+
if (configWrapper) {{ configWrapper.style.display = 'none'; }}
203+
}}
204+
205+
// These DataSets are the "active" data being displayed
206+
var nodes = new vis.DataSet(allNodesArray);
207+
var edges = new vis.DataSet(allEdgesArray);
208+
var graphContainer = document.getElementById('mynetwork-{div_id_suffix}');
209+
var data = {{ nodes: nodes, edges: edges }};
210+
var network = new vis.Network(graphContainer, data, optionsObject);
211+
212+
// Function to restore the full graph view
213+
function resetView() {{
214+
searchInput.value = ""; // Clear search input
215+
nodes.clear();
216+
edges.clear();
217+
nodes.add(allNodesArray);
218+
edges.add(allEdgesArray);
219+
network.fit();
220+
}}
221+
222+
// Function to filter graph based on search query
223+
function filterBySearch(query) {{
224+
if (!query) {{
225+
resetView();
226+
return;
227+
}}
228+
var regex;
229+
try {{
230+
regex = new RegExp(query, 'i'); // Case-insensitive regex
231+
}} catch (e) {{
232+
console.error("Invalid Regex:", e);
233+
return; // Don't filter if regex is invalid
234+
}}
235+
236+
var matchingNodeIds = new Set();
237+
allNodesArray.forEach(function(node) {{
238+
var textToSearch = node.label || node.id;
239+
if (regex.test(textToSearch)) {{
240+
matchingNodeIds.add(node.id);
241+
}}
242+
}});
243+
244+
var filteredNodes = allNodesArray.filter(function(node) {{
245+
return matchingNodeIds.has(node.id);
246+
}});
247+
248+
var filteredEdges = allEdgesArray.filter(function(edge) {{
249+
return matchingNodeIds.has(edge.from) && matchingNodeIds.has(edge.to);
250+
}});
251+
252+
nodes.clear();
253+
edges.clear();
254+
nodes.add(filteredNodes);
255+
edges.add(filteredEdges);
256+
network.fit();
257+
}}
258+
259+
// Function to isolate a node and its neighbors
260+
function showNeighborhood(nodeId) {{
261+
searchInput.value = ""; // Clear search when isolating
262+
var nodesToShow = new Set([nodeId]);
263+
var edgesToShow = [];
264+
265+
allEdgesArray.forEach(function(edge) {{
266+
if (edge.from === nodeId) {{
267+
nodesToShow.add(edge.to);
268+
edgesToShow.push(edge);
269+
}} else if (edge.to === nodeId) {{
270+
nodesToShow.add(edge.from);
271+
edgesToShow.push(edge);
272+
}}
273+
}});
274+
275+
var filteredNodes = allNodesArray.filter(function(node) {{
276+
return nodesToShow.has(node.id);
277+
}});
278+
279+
nodes.clear();
280+
edges.clear();
281+
nodes.add(filteredNodes);
282+
edges.add(edgesToShow);
283+
network.fit();
284+
}}
285+
286+
// --- Event Listeners ---
287+
288+
// Filter as user types in the search box
289+
searchInput.addEventListener('input', function() {{
290+
filterBySearch(this.value);
291+
}});
292+
293+
// Clear button resets the view
294+
clearButton.addEventListener('click', resetView);
295+
296+
// Handle clicks on the network
297+
network.on("click", function (params) {{
298+
if (params.nodes.length > 0) {{
299+
// A node was clicked, show its neighborhood
300+
var clickedNodeId = params.nodes[0];
301+
showNeighborhood(clickedNodeId);
302+
}} else {{
303+
// The background was clicked, reset everything
304+
resetView();
305+
}}
306+
}});
307+
}})();
308+
</script>
309+
</body>
310+
</html>

0 commit comments

Comments
 (0)