Skip to content

Commit b704172

Browse files
Merge pull request #2 from ParticularlyPythonicBS/feat/filtering
2 parents 3b5141f + 0555e67 commit b704172

14 files changed

Lines changed: 1320 additions & 593 deletions

File tree

docs/assets/generated_graphs/advanced_showcase_example.html

Lines changed: 174 additions & 55 deletions
Large diffs are not rendered by default.

docs/assets/generated_graphs/course_prerequisites_example.html

Lines changed: 174 additions & 55 deletions
Large diffs are not rendered by default.

docs/assets/generated_graphs/cycle_graph_example.html

Lines changed: 174 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
21
<!DOCTYPE html>
32
<html lang="en">
43
<head>
@@ -62,7 +61,7 @@
6261
transform: rotate(-90deg); /* Right arrow for collapsed state */
6362
}
6463

65-
#config-container-content-574513c8 {
64+
#config-container-content-ee568209 {
6665
max-height: 40vh; /* Default expanded max height */
6766
overflow-y: auto;
6867
padding: 15px;
@@ -71,88 +70,124 @@
7170
transition: max-height 0.3s ease-in-out, padding 0.3s ease-in-out;
7271
}
7372

74-
.collapsed #config-container-content-574513c8 {
73+
.collapsed #config-container-content-ee568209 {
7574
max-height: 0;
7675
padding-top: 0;
7776
padding-bottom: 0;
7877
overflow: hidden;
7978
border-bottom: none; /* Hide border when collapsed */
8079
}
8180

82-
#mynetwork-574513c8 {
81+
#mynetwork-ee568209 {
8382
width: 100%;
8483
flex-grow: 1; /* Graph takes remaining vertical space */
8584
min-height: 0; /* Important for flex children to shrink */
86-
/* border-top: 1px solid #e0e0e0; */ /* Optional: if config panel is directly above */
8785
background-color: #ffffff; /* Graph background */
8886
}
8987

90-
/* Basic styling for vis.js config elements to blend better */
91-
div.vis-configuration-wrapper {
92-
padding: 0; /* Remove default padding if vis.js adds it */
93-
}
94-
div.vis-configuration-wrapper table {
95-
width: 100%;
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;
9696
}
97-
div.vis-configuration-wrapper table tr td:first-child {
98-
width: 30%; /* Adjust label width */
97+
.filter-panel label {
9998
font-size: 13px;
99+
font-weight: 500;
100+
color: #495057;
100101
}
101-
div.vis-configuration-wrapper input[type=text],
102-
div.vis-configuration-wrapper select {
103-
width: 95%;
104-
padding: 6px;
105-
margin: 2px 0;
106-
border: 1px solid #ccc;
102+
.filter-panel input[type=text] {
103+
flex-grow: 1;
104+
padding: 6px 8px;
105+
border: 1px solid #ced4da;
107106
border-radius: 4px;
108-
box-sizing: border-box;
109107
font-size: 13px;
110108
}
111-
div.vis-configuration-wrapper input[type=range] {
112-
width: 60%; /* Adjust slider width */
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;
113120
}
114-
div.vis-configuration-wrapper .vis-label {
115-
font-size: 13px;
116-
color: #333;
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;
117130
}
118131

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+
119141
</style>
120142
</head>
121143
<body>
122-
<div class="config-panel-wrapper" id="config-panel-wrapper-574513c8">
123-
<div class="config-panel-header" id="config-panel-header-574513c8" role="button" tabindex="0" aria-expanded="true" aria-controls="config-container-content-574513c8">
144+
<div class="config-panel-wrapper" id="config-panel-wrapper-ee568209">
145+
<div class="config-panel-header" id="config-panel-header-ee568209" role="button" tabindex="0" aria-expanded="true" aria-controls="config-container-content-ee568209">
124146
<h3>Configuration</h3>
125-
<button class="config-toggle-btn" id="config-toggle-btn-574513c8" aria-label="Toggle configuration panel"></button>
147+
<button class="config-toggle-btn" id="config-toggle-btn-ee568209" aria-label="Toggle configuration panel"></button>
126148
</div>
127-
<div id="config-container-content-574513c8">
149+
<div id="config-container-content-ee568209">
128150
<!-- Vis.js configuration UI will be injected here -->
129151
</div>
130152
</div>
131153

132-
<div id="mynetwork-574513c8"></div>
154+
<div class="filter-panel">
155+
<label for="search-input-ee568209">Filter nodes (regex):</label>
156+
<input type="text" id="search-input-ee568209" placeholder="e.g., ^IMP_ or ELEC$">
157+
<button id="clear-btn-ee568209">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-ee568209"></div>
133165

134166
<script type="text/javascript">
135167
(function() {
136-
var nodesArray = [{"id": "0", "label": "N1", "title": "Tooltip for Node N1", "group": 0, "color": "skyblue", "size": 25}, {"id": "1", "label": "N2", "title": "Tooltip for Node N2", "group": 1}, {"id": "2", "label": "N3", "title": "Tooltip for Node N3", "group": 0}, {"id": "3", "label": "N4", "title": "Tooltip for Node N4", "group": 1}, {"id": "4", "label": "N5", "title": "Tooltip for Node N5", "group": 0}];
137-
var edgesArray = [{"from": "0", "to": "1", "label": "Link 0-1", "color": "green", "width": 3}, {"from": "0", "to": "4"}, {"from": "1", "to": "2"}, {"from": "2", "to": "3"}, {"from": "3", "to": "4"}];
138-
var optionsObject = {"autoResize": true, "nodes": {"shape": "dot", "size": 16, "font": {"size": 14, "color": "#333"}, "borderWidth": 2}, "edges": {"width": 2, "smooth": {"type": "dynamic", "roundness": 0.5, "enabled": true}, "arrows": {"to": {"enabled": false, "scaleFactor": 1}}}, "physics": {"enabled": true, "barnesHut": {"gravitationalConstant": -8000, "springConstant": 0.04, "springLength": 150, "damping": 0.09, "avoidOverlap": 0.1}, "solver": "barnesHut", "stabilization": {"iterations": 1000, "fit": true}}, "interaction": {"hover": true, "dragNodes": true, "dragView": true, "zoomView": true, "tooltipDelay": 200, "navigationButtons": true, "keyboard": true}, "layout": {"randomSeed": 12345, "improvedLayout": true}, "groups": {"0": {"shape": "dot", "color": {"background": "lightcoral", "border": "red"}}, "1": {"shape": "square", "color": {"background": "lightgreen", "border": "green"}}}};
168+
// These arrays are the master dataset
169+
var allNodesArray = [{"id": "0", "label": "N1", "title": "Tooltip for Node N1", "group": 0, "color": "skyblue", "size": 25}, {"id": "1", "label": "N2", "title": "Tooltip for Node N2", "group": 1}, {"id": "2", "label": "N3", "title": "Tooltip for Node N3", "group": 0}, {"id": "3", "label": "N4", "title": "Tooltip for Node N4", "group": 1}, {"id": "4", "label": "N5", "title": "Tooltip for Node N5", "group": 0}];
170+
var allEdgesArray = [{"from": "0", "to": "1", "label": "Link 0-1", "color": "green", "width": 3}, {"from": "0", "to": "4"}, {"from": "1", "to": "2"}, {"from": "2", "to": "3"}, {"from": "3", "to": "4"}];
171+
var optionsObject = {"autoResize": true, "nodes": {"shape": "dot", "size": 16, "font": {"size": 14, "color": "#333"}, "borderWidth": 2}, "edges": {"width": 2, "smooth": {"type": "dynamic", "roundness": 0.5, "enabled": true}, "arrows": {"to": {"enabled": false, "scaleFactor": 1}}}, "physics": {"enabled": true, "barnesHut": {"gravitationalConstant": -8000, "springConstant": 0.04, "springLength": 150, "damping": 0.09, "avoidOverlap": 0.1}, "solver": "barnesHut", "stabilization": {"iterations": 1000, "fit": true}}, "interaction": {"hover": true, "dragNodes": true, "dragView": true, "zoomView": true, "tooltipDelay": 200, "navigationButtons": true, "keyboard": {"enabled": true, "bindToWindow": false}}, "layout": {"randomSeed": 12345, "improvedLayout": true}, "groups": {"0": {"shape": "dot", "color": {"background": "lightcoral", "border": "red"}}, "1": {"shape": "square", "color": {"background": "lightgreen", "border": "green"}}}};
139172

140-
var configWrapper = document.getElementById('config-panel-wrapper-574513c8');
141-
var configHeader = document.getElementById('config-panel-header-574513c8');
142-
var configContent = document.getElementById('config-container-content-574513c8');
143-
var toggleButton = document.getElementById('config-toggle-btn-574513c8'); // Also target button for ARIA
173+
// Get DOM elements
174+
var configWrapper = document.getElementById('config-panel-wrapper-ee568209');
175+
var configHeader = document.getElementById('config-panel-header-ee568209');
176+
var configContent = document.getElementById('config-container-content-ee568209');
177+
var toggleButton = document.getElementById('config-toggle-btn-ee568209');
178+
var searchInput = document.getElementById('search-input-ee568209');
179+
var clearButton = document.getElementById('clear-btn-ee568209');
144180

181+
// Handle config panel toggle
145182
if (optionsObject.configure && optionsObject.configure.enabled) {
146-
if (!optionsObject.configure.container) { // Only set if user hasn't provided one
183+
if (!optionsObject.configure.container) {
147184
optionsObject.configure.container = configContent;
148185
}
149-
// optionsObject.configure.showButton = false; // User should set this in Python options
150-
151186
configHeader.addEventListener('click', function() {
152187
configWrapper.classList.toggle('collapsed');
153188
var isExpanded = !configWrapper.classList.contains('collapsed');
154189
configHeader.setAttribute('aria-expanded', isExpanded);
155-
toggleButton.setAttribute('aria-expanded', isExpanded); // Keep button ARIA in sync
190+
toggleButton.setAttribute('aria-expanded', isExpanded);
156191
});
157192
configHeader.addEventListener('keydown', function(event) {
158193
if (event.key === 'Enter' || event.key === ' ') {
@@ -163,27 +198,111 @@ <h3>Configuration</h3>
163198
event.preventDefault();
164199
}
165200
});
166-
167-
// Optional: Start collapsed by default
168-
// configWrapper.classList.add('collapsed');
169-
// configHeader.setAttribute('aria-expanded', 'false');
170-
// toggleButton.setAttribute('aria-expanded', 'false');
171-
172201
} else {
173-
// If configure is not enabled, hide the whole panel wrapper
174-
if (configWrapper) {
175-
configWrapper.style.display = 'none';
176-
}
202+
if (configWrapper) { configWrapper.style.display = 'none'; }
177203
}
178204

179-
var nodes = new vis.DataSet(nodesArray);
180-
var edges = new vis.DataSet(edgesArray);
181-
var graphContainer = document.getElementById('mynetwork-574513c8');
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-ee568209');
182209
var data = { nodes: nodes, edges: edges };
183210
var network = new vis.Network(graphContainer, data, optionsObject);
184211

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
185297
network.on("click", function (params) {
186-
console.log('Click event:', 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+
}
187306
});
188307
})();
189308
</script>

0 commit comments

Comments
 (0)