1-
21<!DOCTYPE html>
32< html lang ="en ">
43< head >
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 ;
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