EchaRz commited on
Commit
453b7d3
·
verified ·
1 Parent(s): 921e201

Add mobile responsiveness to explorepage.html

Browse files
Files changed (1) hide show
  1. explorepage.html +126 -108
explorepage.html CHANGED
@@ -13,7 +13,6 @@
13
  padding: 0;
14
  box-sizing: border-box;
15
  }
16
-
17
  body {
18
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
19
  background: linear-gradient(135deg, #4a5568 0%, #2d3748 50%, #1a202c 100%);
@@ -21,12 +20,10 @@
21
  height: 100vh;
22
  overflow: hidden;
23
  }
24
-
25
  .container {
26
  display: flex;
27
  height: 100vh;
28
  }
29
-
30
  /* Sidebar Styles */
31
  .sidebar {
32
  width: 400px;
@@ -37,30 +34,26 @@
37
  position: relative;
38
  z-index: 100;
39
  }
40
-
41
  .sidebar.collapsed {
42
  margin-left: -400px;
43
  }
44
-
45
  /* Header */
46
  .sidebar-header {
47
  padding: 1.5rem;
48
  }
49
-
50
  .header-controls {
51
  display: flex;
52
  justify-content: space-between;
53
  align-items: center;
54
  margin-bottom: 1rem;
55
  }
56
-
57
  /* Menu Toggle - Always Visible */
58
  .menu-toggle {
59
  position: fixed;
60
  top: 1.5rem;
61
  left: 0.7rem;
62
- z-index: 300;
63
- background: none;
64
  border: none;
65
  color: white;
66
  font-size: 1.5rem;
@@ -69,11 +62,9 @@
69
  border-radius: 8px;
70
  backdrop-filter: blur(10px);
71
  }
72
-
73
  .menu-toggle:hover {
74
  background: rgba(0, 0, 0, 0.8);
75
  }
76
-
77
  .home-btn {
78
  position: fixed;
79
  background: none;
@@ -85,11 +76,9 @@
85
  border-radius: 4px;
86
  transition: background-color 0.3s ease;
87
  }
88
-
89
  .home-btn:hover {
90
  background-color: rgba(0, 0, 0, 0.8);
91
  }
92
-
93
  .sidebar-title {
94
  font-size: 2.5rem;
95
  font-weight: 700;
@@ -97,12 +86,10 @@
97
  line-height: 1.2;
98
  margin-top : 50px;
99
  }
100
-
101
  /* Search Section */
102
  .search-section {
103
  padding: 0 1.5rem 1.5rem;
104
  }
105
-
106
  .search-input {
107
  width: 100%;
108
  padding: 0.75rem 1rem;
@@ -115,19 +102,16 @@
115
  color: #797979;
116
  margin-bottom: 1rem;
117
  }
118
-
119
  .search-input::placeholder {
120
  color: #a0aec0;
121
  }
122
-
123
  .search-input:focus {
124
  outline: none;
125
  background: rgba(255, 255, 255, 1);
126
  }
127
-
128
  .reset-btn {
129
- display: block; /* make it a block so margin works */
130
- margin: 0 auto; /* this centers it horizontally */
131
  background: rgb(110 131 131);
132
  border: none;
133
  color: white;
@@ -141,11 +125,9 @@
141
  box-shadow: 0 4px 4px rgba(0, 0, 0, 0.2);
142
  transition: all 0.3s ease;
143
  }
144
-
145
  .reset-btn:hover {
146
  background: rgba(74, 85, 104, 1);
147
  }
148
-
149
  /* Instructions Panel */
150
  .instructions-panel {
151
  margin: 1rem;
@@ -157,7 +139,6 @@
157
  margin-bottom: 7rem;
158
  box-shadow: inset 0 4px 4px rgba(0,0,0,0.25);
159
  }
160
-
161
  .instructions-title {
162
  font-size: 1.5rem;
163
  font-weight: 800;
@@ -166,7 +147,6 @@
166
  margin-bottom: 1rem;
167
  text-align: center;
168
  }
169
-
170
  .instruction-item {
171
  margin-bottom: 1rem;
172
  font-family: 'Varta', sans-serif;
@@ -175,29 +155,25 @@
175
  line-height: 1.5;
176
  font-weight: 300;
177
  }
178
-
179
  .instruction-item:last-child {
180
  margin-bottom: 0;
181
  }
182
-
183
  .instruction-action {
184
  font-weight: 700;
185
  color: #485656;
186
  }
187
-
188
  /* Main Graph Area */
189
  .main-content {
190
  flex: 1;
191
  position: relative;
192
  background: rgb(77 83 109);
193
  }
194
-
195
  /* Home Button in Top-Right Corner */
196
  .main-home-btn {
197
  position: absolute;
198
  top: 1.5rem;
199
  right: 1.5rem;
200
- z-index: 200;
201
  background: rgba(0, 0, 0, 0.6);
202
  border: none;
203
  color: white;
@@ -208,73 +184,60 @@
208
  transition: background-color 0.3s ease;
209
  backdrop-filter: blur(10px);
210
  }
211
-
212
  .main-home-btn:hover {
213
  background: rgba(0, 0, 0, 0.8);
214
  }
215
-
216
  /* Remove floating home - not needed anymore */
217
  .floating-home {
218
  display: none;
219
  }
220
-
221
  /* Graph Styles */
222
  #graph {
223
  width: 100%;
224
  height: 100%;
225
  }
226
-
227
  .node {
228
  cursor: pointer;
229
  transition: all 0.3s ease;
230
  filter: drop-shadow(0 0 6px rgba(76, 175, 80, 0.3));
231
  }
232
-
233
  .node:hover {
234
  stroke-width: 3px;
235
  filter: drop-shadow(0 0 12px rgba(76, 175, 80, 0.6));
236
  }
237
-
238
  .node.highlighted {
239
  stroke: #4CAF50 !important;
240
  stroke-width: 3px !important;
241
  filter: drop-shadow(0 0 15px rgba(76, 175, 80, 0.8));
242
  }
243
-
244
  .node.selected {
245
  stroke: #FFD700 !important;
246
  stroke-width: 4px !important;
247
  filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.8));
248
  }
249
-
250
  .node.dimmed {
251
  opacity: 0.2;
252
  filter: none;
253
  }
254
-
255
  .link {
256
  stroke: rgba(255, 255, 255, 0.4);
257
  stroke-width: 2px;
258
  cursor: pointer;
259
  transition: all 0.3s ease;
260
  }
261
-
262
  .link:hover {
263
  stroke: #4CAF50;
264
  stroke-width: 3px;
265
  filter: drop-shadow(0 0 6px rgba(76, 175, 80, 0.5));
266
  }
267
-
268
  .link.highlighted {
269
  stroke: #4CAF50 !important;
270
  stroke-width: 3px !important;
271
  filter: drop-shadow(0 0 8px rgba(76, 175, 80, 0.6));
272
  }
273
-
274
  .link.dimmed {
275
  opacity: 0.1;
276
  }
277
-
278
  .node-label {
279
  font-size: 11px;
280
  font-weight: 600;
@@ -284,17 +247,14 @@
284
  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
285
  transition: all 0.3s ease;
286
  }
287
-
288
  .node-label.dimmed {
289
  opacity: 0.2;
290
  }
291
-
292
  .node-label.highlighted {
293
  fill: #4CAF50;
294
  font-size: 13px;
295
  text-shadow: 0 0 8px rgba(76, 175, 80, 0.8);
296
  }
297
-
298
  .tooltip {
299
  position: absolute;
300
  text-align: left;
@@ -311,13 +271,11 @@
311
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
312
  z-index: 1000;
313
  }
314
-
315
  .tooltip h4 {
316
  margin: 0 0 0.5rem 0;
317
  color: #4CAF50;
318
  font-weight: 700;
319
  }
320
-
321
  .loading {
322
  position: absolute;
323
  top: 50%;
@@ -327,7 +285,6 @@
327
  color: white;
328
  text-align: center;
329
  }
330
-
331
  .loading-spinner {
332
  border: 3px solid rgba(255, 255, 255, 0.3);
333
  border-radius: 50%;
@@ -337,12 +294,10 @@
337
  animation: spin 1s linear infinite;
338
  margin: 0 auto 1rem;
339
  }
340
-
341
  @keyframes spin {
342
  0% { transform: rotate(0deg); }
343
  100% { transform: rotate(360deg); }
344
  }
345
-
346
  .error {
347
  color: #ff6b6b;
348
  background: rgba(255, 107, 107, 0.1);
@@ -351,7 +306,6 @@
351
  margin: 1rem;
352
  border: 1px solid rgba(255, 107, 107, 0.3);
353
  }
354
-
355
  /* Responsive Design */
356
  @media (max-width: 768px) {
357
  .sidebar {
@@ -364,15 +318,95 @@
364
  .sidebar.collapsed {
365
  margin-left: -100%;
366
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
367
  }
368
  </style>
369
  </head>
370
  <body>
371
  <div class="container">
372
- <!-- Fixed Menu Toggle Button -->
373
  <button class="menu-toggle" id="menuToggle">☰</button>
374
 
375
- <!-- Sidebar -->
376
  <div class="sidebar" id="sidebar">
377
  <div class="sidebar-header">
378
  <h1 class="sidebar-title">KNOWLEDGE<br>GRAPH</h1>
@@ -408,9 +442,7 @@
408
  </div>
409
  </div>
410
 
411
- <!-- Main Graph Area -->
412
  <div class="main-content">
413
- <!-- Home Button in Top-Right -->
414
  <button class="main-home-btn" id="mainHomeBtn">
415
  <img src="/static/Home.png" alt="Home" style="width: 20px; height: 20px;">
416
  </button>
@@ -425,10 +457,8 @@
425
  <div class="tooltip" id="tooltip"></div>
426
 
427
  <script>
428
- // Configuration
429
  const API_BASE = '/api';
430
 
431
- // Global variables
432
  let graphData = { nodes: [], edges: [] };
433
  let simulation;
434
  let svg, g;
@@ -437,20 +467,23 @@
437
  let highlightedElements = { nodes: new Set(), edges: new Set() };
438
  let sidebarCollapsed = false;
439
 
440
- // Initialize the application
441
  async function init() {
442
  setupEventListeners();
443
  setupVisualizationSVG();
 
 
 
 
 
 
 
444
  await loadGraphData();
445
  hideLoading();
446
  }
447
 
448
  function setupEventListeners() {
449
- // Sidebar toggle
450
  document.getElementById('menuToggle').addEventListener('click', toggleSidebar);
451
  document.getElementById('mainHomeBtn').addEventListener('click', goHome);
452
-
453
- // Search and reset
454
  document.getElementById('searchInput').addEventListener('input', debounce(handleSearch, 300));
455
  document.getElementById('resetBtn').addEventListener('click', resetHighlighting);
456
  }
@@ -467,7 +500,6 @@
467
  }
468
 
469
  function goHome() {
470
- // Navigate back to main page
471
  window.location.href = '/';
472
  }
473
 
@@ -478,19 +510,15 @@
478
  svg = d3.select('#graph')
479
  .attr('width', containerRect.width)
480
  .attr('height', containerRect.height);
481
-
482
  g = svg.append('g');
483
 
484
- // Add zoom behavior
485
  const zoom = d3.zoom()
486
  .scaleExtent([0.1, 4])
487
  .on('zoom', (event) => {
488
  g.attr('transform', event.transform);
489
  });
490
-
491
  svg.call(zoom);
492
 
493
- // Click on empty space to reset highlighting
494
  svg.on('click', (event) => {
495
  if (event.target === event.currentTarget) {
496
  resetHighlighting();
@@ -499,23 +527,23 @@
499
  }
500
 
501
  async function loadGraphData(search = '') {
502
- try {
503
- showLoading();
504
- const url = search
505
- ? `${API_BASE}/graph?search=${encodeURIComponent(search)}`
506
- : `${API_BASE}/graph`;
507
-
508
- const response = await fetch(url);
509
- if (!response.ok) throw new Error('Failed to fetch graph data');
510
-
511
- graphData = await response.json();
512
- renderGraph();
513
- } catch (error) {
514
- showError('Failed to load graph data: ' + error.message);
515
- } finally {
516
- hideLoading();
 
517
  }
518
- }
519
 
520
  function renderGraph() {
521
  if (!graphData.nodes || graphData.nodes.length === 0) {
@@ -523,22 +551,23 @@
523
  return;
524
  }
525
 
526
- // Clear existing elements
527
  g.selectAll('*').remove();
528
  resetHighlighting();
529
 
530
- // Get container dimensions
531
  const width = +svg.attr('width');
532
  const height = +svg.attr('height');
533
 
534
- // Create simulation
 
 
 
 
535
  simulation = d3.forceSimulation(graphData.nodes)
536
- .force('link', d3.forceLink(graphData.edges).id(d => d.id).distance(100))
537
- .force('charge', d3.forceManyBody().strength(-300))
538
  .force('center', d3.forceCenter(width / 2, height / 2))
539
- .force('collision', d3.forceCollide().radius(30));
540
 
541
- // Create links
542
  const link = g.append('g')
543
  .selectAll('line')
544
  .data(graphData.edges)
@@ -547,13 +576,14 @@
547
  .on('mouseover', showEdgeTooltip)
548
  .on('mouseout', hideTooltip);
549
 
550
- // Create nodes
 
551
  const node = g.append('g')
552
  .selectAll('circle')
553
  .data(graphData.nodes)
554
  .join('circle')
555
  .attr('class', 'node')
556
- .attr('r', 12)
557
  .attr('fill', d => getNodeColor(d))
558
  .attr('stroke', '#fff')
559
  .attr('stroke-width', 2)
@@ -565,26 +595,23 @@
565
  .on('drag', dragged)
566
  .on('end', dragEnded));
567
 
568
- // Create labels
569
  const labels = g.append('g')
570
  .selectAll('text')
571
  .data(graphData.nodes)
572
  .join('text')
573
  .attr('class', 'node-label')
574
- .text(d => d.label.length > 12 ? d.label.substring(0, 12) + '...' : d.label);
575
 
576
- // Update positions on simulation tick
577
  simulation.on('tick', () => {
578
  link
579
  .attr('x1', d => d.source.x)
580
  .attr('y1', d => d.source.y)
581
  .attr('x2', d => d.target.x)
582
  .attr('y2', d => d.target.y);
583
-
584
  node
585
  .attr('cx', d => d.x)
586
  .attr('cy', d => d.y);
587
-
588
  labels
589
  .attr('x', d => d.x)
590
  .attr('y', d => d.y + 20);
@@ -605,6 +632,8 @@
605
 
606
  function showNodeTooltip(event, d) {
607
  const tooltip = d3.select('#tooltip');
 
 
608
  tooltip.transition().duration(200).style('opacity', 1);
609
 
610
  const connectionCount = graphData.edges.filter(edge =>
@@ -615,7 +644,7 @@
615
  <h4>${d.label}</h4>
616
  <p><strong>Type:</strong> ${d.type || 'Node'}</p>
617
  <p><strong>Connections:</strong> ${connectionCount}</p>
618
- <p>Click to highlight connections</p>
619
  `)
620
  .style('left', (event.pageX + 10) + 'px')
621
  .style('top', (event.pageY - 28) + 'px');
@@ -644,7 +673,6 @@
644
  resetHighlighting();
645
  return;
646
  }
647
-
648
  selectedNode = d;
649
  highlightConnections(d);
650
  }
@@ -652,7 +680,6 @@
652
  function highlightConnections(selectedNode) {
653
  highlightedElements.nodes.clear();
654
  highlightedElements.edges.clear();
655
-
656
  graphData.edges.forEach(edge => {
657
  if (edge.source.id === selectedNode.id || edge.target.id === selectedNode.id) {
658
  highlightedElements.edges.add(edge);
@@ -660,7 +687,6 @@
660
  highlightedElements.nodes.add(edge.target.id);
661
  }
662
  });
663
-
664
  applyHighlighting();
665
  }
666
 
@@ -669,11 +695,9 @@
669
  .classed('highlighted', d => highlightedElements.nodes.has(d.id) && (!selectedNode || d.id !== selectedNode.id))
670
  .classed('selected', d => selectedNode && d.id === selectedNode.id)
671
  .classed('dimmed', d => selectedNode && !highlightedElements.nodes.has(d.id));
672
-
673
  g.selectAll('.link')
674
  .classed('highlighted', d => highlightedElements.edges.has(d))
675
  .classed('dimmed', d => selectedNode && !highlightedElements.edges.has(d));
676
-
677
  g.selectAll('.node-label')
678
  .classed('highlighted', d => highlightedElements.nodes.has(d.id))
679
  .classed('dimmed', d => selectedNode && !highlightedElements.nodes.has(d.id));
@@ -683,16 +707,13 @@
683
  selectedNode = null;
684
  highlightedElements.nodes.clear();
685
  highlightedElements.edges.clear();
686
-
687
  g.selectAll('.node')
688
  .classed('highlighted', false)
689
  .classed('selected', false)
690
  .classed('dimmed', false);
691
-
692
  g.selectAll('.link')
693
  .classed('highlighted', false)
694
  .classed('dimmed', false);
695
-
696
  g.selectAll('.node-label')
697
  .classed('highlighted', false)
698
  .classed('dimmed', false);
@@ -735,7 +756,6 @@
735
  };
736
  }
737
 
738
- // Drag functions
739
  function dragStarted(event, d) {
740
  if (!event.active) simulation.alphaTarget(0.3).restart();
741
  d.fx = d.x;
@@ -753,7 +773,6 @@
753
  d.fy = null;
754
  }
755
 
756
- // Window resize handler
757
  window.addEventListener('resize', () => {
758
  const container = document.querySelector('.main-content');
759
  const containerRect = container.getBoundingClientRect();
@@ -767,7 +786,6 @@
767
  }
768
  });
769
 
770
- // Initialize when DOM is loaded
771
  document.addEventListener('DOMContentLoaded', init);
772
  </script>
773
  </body>
 
13
  padding: 0;
14
  box-sizing: border-box;
15
  }
 
16
  body {
17
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
18
  background: linear-gradient(135deg, #4a5568 0%, #2d3748 50%, #1a202c 100%);
 
20
  height: 100vh;
21
  overflow: hidden;
22
  }
 
23
  .container {
24
  display: flex;
25
  height: 100vh;
26
  }
 
27
  /* Sidebar Styles */
28
  .sidebar {
29
  width: 400px;
 
34
  position: relative;
35
  z-index: 100;
36
  }
 
37
  .sidebar.collapsed {
38
  margin-left: -400px;
39
  }
 
40
  /* Header */
41
  .sidebar-header {
42
  padding: 1.5rem;
43
  }
 
44
  .header-controls {
45
  display: flex;
46
  justify-content: space-between;
47
  align-items: center;
48
  margin-bottom: 1rem;
49
  }
 
50
  /* Menu Toggle - Always Visible */
51
  .menu-toggle {
52
  position: fixed;
53
  top: 1.5rem;
54
  left: 0.7rem;
55
+ z-index: 1100;
56
+ background: rgba(0, 0, 0, 0.6);
57
  border: none;
58
  color: white;
59
  font-size: 1.5rem;
 
62
  border-radius: 8px;
63
  backdrop-filter: blur(10px);
64
  }
 
65
  .menu-toggle:hover {
66
  background: rgba(0, 0, 0, 0.8);
67
  }
 
68
  .home-btn {
69
  position: fixed;
70
  background: none;
 
76
  border-radius: 4px;
77
  transition: background-color 0.3s ease;
78
  }
 
79
  .home-btn:hover {
80
  background-color: rgba(0, 0, 0, 0.8);
81
  }
 
82
  .sidebar-title {
83
  font-size: 2.5rem;
84
  font-weight: 700;
 
86
  line-height: 1.2;
87
  margin-top : 50px;
88
  }
 
89
  /* Search Section */
90
  .search-section {
91
  padding: 0 1.5rem 1.5rem;
92
  }
 
93
  .search-input {
94
  width: 100%;
95
  padding: 0.75rem 1rem;
 
102
  color: #797979;
103
  margin-bottom: 1rem;
104
  }
 
105
  .search-input::placeholder {
106
  color: #a0aec0;
107
  }
 
108
  .search-input:focus {
109
  outline: none;
110
  background: rgba(255, 255, 255, 1);
111
  }
 
112
  .reset-btn {
113
+ display: block;
114
+ margin: 0 auto;
115
  background: rgb(110 131 131);
116
  border: none;
117
  color: white;
 
125
  box-shadow: 0 4px 4px rgba(0, 0, 0, 0.2);
126
  transition: all 0.3s ease;
127
  }
 
128
  .reset-btn:hover {
129
  background: rgba(74, 85, 104, 1);
130
  }
 
131
  /* Instructions Panel */
132
  .instructions-panel {
133
  margin: 1rem;
 
139
  margin-bottom: 7rem;
140
  box-shadow: inset 0 4px 4px rgba(0,0,0,0.25);
141
  }
 
142
  .instructions-title {
143
  font-size: 1.5rem;
144
  font-weight: 800;
 
147
  margin-bottom: 1rem;
148
  text-align: center;
149
  }
 
150
  .instruction-item {
151
  margin-bottom: 1rem;
152
  font-family: 'Varta', sans-serif;
 
155
  line-height: 1.5;
156
  font-weight: 300;
157
  }
 
158
  .instruction-item:last-child {
159
  margin-bottom: 0;
160
  }
 
161
  .instruction-action {
162
  font-weight: 700;
163
  color: #485656;
164
  }
 
165
  /* Main Graph Area */
166
  .main-content {
167
  flex: 1;
168
  position: relative;
169
  background: rgb(77 83 109);
170
  }
 
171
  /* Home Button in Top-Right Corner */
172
  .main-home-btn {
173
  position: absolute;
174
  top: 1.5rem;
175
  right: 1.5rem;
176
+ z-index: 1050;
177
  background: rgba(0, 0, 0, 0.6);
178
  border: none;
179
  color: white;
 
184
  transition: background-color 0.3s ease;
185
  backdrop-filter: blur(10px);
186
  }
 
187
  .main-home-btn:hover {
188
  background: rgba(0, 0, 0, 0.8);
189
  }
 
190
  /* Remove floating home - not needed anymore */
191
  .floating-home {
192
  display: none;
193
  }
 
194
  /* Graph Styles */
195
  #graph {
196
  width: 100%;
197
  height: 100%;
198
  }
 
199
  .node {
200
  cursor: pointer;
201
  transition: all 0.3s ease;
202
  filter: drop-shadow(0 0 6px rgba(76, 175, 80, 0.3));
203
  }
 
204
  .node:hover {
205
  stroke-width: 3px;
206
  filter: drop-shadow(0 0 12px rgba(76, 175, 80, 0.6));
207
  }
 
208
  .node.highlighted {
209
  stroke: #4CAF50 !important;
210
  stroke-width: 3px !important;
211
  filter: drop-shadow(0 0 15px rgba(76, 175, 80, 0.8));
212
  }
 
213
  .node.selected {
214
  stroke: #FFD700 !important;
215
  stroke-width: 4px !important;
216
  filter: drop-shadow(0 0 20px rgba(255, 215, 0, 0.8));
217
  }
 
218
  .node.dimmed {
219
  opacity: 0.2;
220
  filter: none;
221
  }
 
222
  .link {
223
  stroke: rgba(255, 255, 255, 0.4);
224
  stroke-width: 2px;
225
  cursor: pointer;
226
  transition: all 0.3s ease;
227
  }
 
228
  .link:hover {
229
  stroke: #4CAF50;
230
  stroke-width: 3px;
231
  filter: drop-shadow(0 0 6px rgba(76, 175, 80, 0.5));
232
  }
 
233
  .link.highlighted {
234
  stroke: #4CAF50 !important;
235
  stroke-width: 3px !important;
236
  filter: drop-shadow(0 0 8px rgba(76, 175, 80, 0.6));
237
  }
 
238
  .link.dimmed {
239
  opacity: 0.1;
240
  }
 
241
  .node-label {
242
  font-size: 11px;
243
  font-weight: 600;
 
247
  text-shadow: 1px 1px 3px rgba(0, 0, 0, 0.8);
248
  transition: all 0.3s ease;
249
  }
 
250
  .node-label.dimmed {
251
  opacity: 0.2;
252
  }
 
253
  .node-label.highlighted {
254
  fill: #4CAF50;
255
  font-size: 13px;
256
  text-shadow: 0 0 8px rgba(76, 175, 80, 0.8);
257
  }
 
258
  .tooltip {
259
  position: absolute;
260
  text-align: left;
 
271
  box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3);
272
  z-index: 1000;
273
  }
 
274
  .tooltip h4 {
275
  margin: 0 0 0.5rem 0;
276
  color: #4CAF50;
277
  font-weight: 700;
278
  }
 
279
  .loading {
280
  position: absolute;
281
  top: 50%;
 
285
  color: white;
286
  text-align: center;
287
  }
 
288
  .loading-spinner {
289
  border: 3px solid rgba(255, 255, 255, 0.3);
290
  border-radius: 50%;
 
294
  animation: spin 1s linear infinite;
295
  margin: 0 auto 1rem;
296
  }
 
297
  @keyframes spin {
298
  0% { transform: rotate(0deg); }
299
  100% { transform: rotate(360deg); }
300
  }
 
301
  .error {
302
  color: #ff6b6b;
303
  background: rgba(255, 107, 107, 0.1);
 
306
  margin: 1rem;
307
  border: 1px solid rgba(255, 107, 107, 0.3);
308
  }
 
309
  /* Responsive Design */
310
  @media (max-width: 768px) {
311
  .sidebar {
 
318
  .sidebar.collapsed {
319
  margin-left: -100%;
320
  }
321
+
322
+ .sidebar-title {
323
+ font-size: 2rem;
324
+ margin-top: 60px;
325
+ }
326
+
327
+ .search-section {
328
+ padding: 0 1rem 1rem;
329
+ }
330
+
331
+ .instructions-panel {
332
+ margin: 0.5rem;
333
+ padding: 1rem;
334
+ margin-bottom: 2rem;
335
+ }
336
+
337
+ .instructions-title {
338
+ font-size: 1.2rem;
339
+ }
340
+
341
+ .instruction-item {
342
+ font-size: 0.9rem;
343
+ margin-bottom: 0.75rem;
344
+ }
345
+
346
+ .main-home-btn {
347
+ top: 1rem;
348
+ right: 1rem;
349
+ padding: 0.5rem;
350
+ font-size: 1.2rem;
351
+ }
352
+
353
+ .menu-toggle {
354
+ top: 1rem;
355
+ left: 1rem;
356
+ padding: 0.5rem;
357
+ font-size: 1.2rem;
358
+ }
359
+
360
+ .tooltip {
361
+ max-width: 200px;
362
+ font-size: 0.8rem;
363
+ padding: 0.75rem;
364
+ }
365
+
366
+ .node-label {
367
+ font-size: 9px;
368
+ }
369
+
370
+ .node-label.highlighted {
371
+ font-size: 11px;
372
+ }
373
+ }
374
+
375
+ /* Extra small devices */
376
+ @media (max-width: 480px) {
377
+ .sidebar-title {
378
+ font-size: 1.75rem;
379
+ }
380
+
381
+ .search-input {
382
+ font-size: 0.85rem;
383
+ padding: 0.6rem 0.8rem;
384
+ }
385
+
386
+ .reset-btn {
387
+ padding: 0.6rem 1.2rem;
388
+ font-size: 0.85rem;
389
+ }
390
+
391
+ .instructions-title {
392
+ font-size: 1.1rem;
393
+ }
394
+
395
+ .instruction-item {
396
+ font-size: 0.85rem;
397
+ }
398
+
399
+ .main-home-btn img {
400
+ width: 16px !important;
401
+ height: 16px !important;
402
+ }
403
  }
404
  </style>
405
  </head>
406
  <body>
407
  <div class="container">
 
408
  <button class="menu-toggle" id="menuToggle">☰</button>
409
 
 
410
  <div class="sidebar" id="sidebar">
411
  <div class="sidebar-header">
412
  <h1 class="sidebar-title">KNOWLEDGE<br>GRAPH</h1>
 
442
  </div>
443
  </div>
444
 
 
445
  <div class="main-content">
 
446
  <button class="main-home-btn" id="mainHomeBtn">
447
  <img src="/static/Home.png" alt="Home" style="width: 20px; height: 20px;">
448
  </button>
 
457
  <div class="tooltip" id="tooltip"></div>
458
 
459
  <script>
 
460
  const API_BASE = '/api';
461
 
 
462
  let graphData = { nodes: [], edges: [] };
463
  let simulation;
464
  let svg, g;
 
467
  let highlightedElements = { nodes: new Set(), edges: new Set() };
468
  let sidebarCollapsed = false;
469
 
 
470
  async function init() {
471
  setupEventListeners();
472
  setupVisualizationSVG();
473
+
474
+ // Auto-collapse sidebar on mobile
475
+ if (window.innerWidth <= 768) {
476
+ sidebarCollapsed = true;
477
+ document.getElementById('sidebar').classList.add('collapsed');
478
+ }
479
+
480
  await loadGraphData();
481
  hideLoading();
482
  }
483
 
484
  function setupEventListeners() {
 
485
  document.getElementById('menuToggle').addEventListener('click', toggleSidebar);
486
  document.getElementById('mainHomeBtn').addEventListener('click', goHome);
 
 
487
  document.getElementById('searchInput').addEventListener('input', debounce(handleSearch, 300));
488
  document.getElementById('resetBtn').addEventListener('click', resetHighlighting);
489
  }
 
500
  }
501
 
502
  function goHome() {
 
503
  window.location.href = '/';
504
  }
505
 
 
510
  svg = d3.select('#graph')
511
  .attr('width', containerRect.width)
512
  .attr('height', containerRect.height);
 
513
  g = svg.append('g');
514
 
 
515
  const zoom = d3.zoom()
516
  .scaleExtent([0.1, 4])
517
  .on('zoom', (event) => {
518
  g.attr('transform', event.transform);
519
  });
 
520
  svg.call(zoom);
521
 
 
522
  svg.on('click', (event) => {
523
  if (event.target === event.currentTarget) {
524
  resetHighlighting();
 
527
  }
528
 
529
  async function loadGraphData(search = '') {
530
+ try {
531
+ showLoading();
532
+ const url = search
533
+ ? `${API_BASE}/graph?search=${encodeURIComponent(search)}`
534
+ : `${API_BASE}/graph`;
535
+
536
+ const response = await fetch(url);
537
+ if (!response.ok) throw new Error('Failed to fetch graph data');
538
+
539
+ graphData = await response.json();
540
+ renderGraph();
541
+ } catch (error) {
542
+ showError('Failed to load graph data: ' + error.message);
543
+ } finally {
544
+ hideLoading();
545
+ }
546
  }
 
547
 
548
  function renderGraph() {
549
  if (!graphData.nodes || graphData.nodes.length === 0) {
 
551
  return;
552
  }
553
 
 
554
  g.selectAll('*').remove();
555
  resetHighlighting();
556
 
 
557
  const width = +svg.attr('width');
558
  const height = +svg.attr('height');
559
 
560
+ const isMobile = window.innerWidth <= 768;
561
+ const linkDistance = isMobile ? 60 : 100;
562
+ const chargeStrength = isMobile ? -150 : -300;
563
+ const collisionRadius = isMobile ? 20 : 30;
564
+
565
  simulation = d3.forceSimulation(graphData.nodes)
566
+ .force('link', d3.forceLink(graphData.edges).id(d => d.id).distance(linkDistance))
567
+ .force('charge', d3.forceManyBody().strength(chargeStrength))
568
  .force('center', d3.forceCenter(width / 2, height / 2))
569
+ .force('collision', d3.forceCollide().radius(collisionRadius));
570
 
 
571
  const link = g.append('g')
572
  .selectAll('line')
573
  .data(graphData.edges)
 
576
  .on('mouseover', showEdgeTooltip)
577
  .on('mouseout', hideTooltip);
578
 
579
+ const nodeRadius = isMobile ? 8 : 12;
580
+
581
  const node = g.append('g')
582
  .selectAll('circle')
583
  .data(graphData.nodes)
584
  .join('circle')
585
  .attr('class', 'node')
586
+ .attr('r', nodeRadius)
587
  .attr('fill', d => getNodeColor(d))
588
  .attr('stroke', '#fff')
589
  .attr('stroke-width', 2)
 
595
  .on('drag', dragged)
596
  .on('end', dragEnded));
597
 
598
+ const maxLabelLength = isMobile ? 8 : 12;
599
  const labels = g.append('g')
600
  .selectAll('text')
601
  .data(graphData.nodes)
602
  .join('text')
603
  .attr('class', 'node-label')
604
+ .text(d => d.label.length > maxLabelLength ? d.label.substring(0, maxLabelLength) + '...' : d.label);
605
 
 
606
  simulation.on('tick', () => {
607
  link
608
  .attr('x1', d => d.source.x)
609
  .attr('y1', d => d.source.y)
610
  .attr('x2', d => d.target.x)
611
  .attr('y2', d => d.target.y);
 
612
  node
613
  .attr('cx', d => d.x)
614
  .attr('cy', d => d.y);
 
615
  labels
616
  .attr('x', d => d.x)
617
  .attr('y', d => d.y + 20);
 
632
 
633
  function showNodeTooltip(event, d) {
634
  const tooltip = d3.select('#tooltip');
635
+ const isMobile = window.innerWidth <= 768;
636
+
637
  tooltip.transition().duration(200).style('opacity', 1);
638
 
639
  const connectionCount = graphData.edges.filter(edge =>
 
644
  <h4>${d.label}</h4>
645
  <p><strong>Type:</strong> ${d.type || 'Node'}</p>
646
  <p><strong>Connections:</strong> ${connectionCount}</p>
647
+ ${isMobile ? '<p>Tap to highlight</p>' : '<p>Click to highlight connections</p>'}
648
  `)
649
  .style('left', (event.pageX + 10) + 'px')
650
  .style('top', (event.pageY - 28) + 'px');
 
673
  resetHighlighting();
674
  return;
675
  }
 
676
  selectedNode = d;
677
  highlightConnections(d);
678
  }
 
680
  function highlightConnections(selectedNode) {
681
  highlightedElements.nodes.clear();
682
  highlightedElements.edges.clear();
 
683
  graphData.edges.forEach(edge => {
684
  if (edge.source.id === selectedNode.id || edge.target.id === selectedNode.id) {
685
  highlightedElements.edges.add(edge);
 
687
  highlightedElements.nodes.add(edge.target.id);
688
  }
689
  });
 
690
  applyHighlighting();
691
  }
692
 
 
695
  .classed('highlighted', d => highlightedElements.nodes.has(d.id) && (!selectedNode || d.id !== selectedNode.id))
696
  .classed('selected', d => selectedNode && d.id === selectedNode.id)
697
  .classed('dimmed', d => selectedNode && !highlightedElements.nodes.has(d.id));
 
698
  g.selectAll('.link')
699
  .classed('highlighted', d => highlightedElements.edges.has(d))
700
  .classed('dimmed', d => selectedNode && !highlightedElements.edges.has(d));
 
701
  g.selectAll('.node-label')
702
  .classed('highlighted', d => highlightedElements.nodes.has(d.id))
703
  .classed('dimmed', d => selectedNode && !highlightedElements.nodes.has(d.id));
 
707
  selectedNode = null;
708
  highlightedElements.nodes.clear();
709
  highlightedElements.edges.clear();
 
710
  g.selectAll('.node')
711
  .classed('highlighted', false)
712
  .classed('selected', false)
713
  .classed('dimmed', false);
 
714
  g.selectAll('.link')
715
  .classed('highlighted', false)
716
  .classed('dimmed', false);
 
717
  g.selectAll('.node-label')
718
  .classed('highlighted', false)
719
  .classed('dimmed', false);
 
756
  };
757
  }
758
 
 
759
  function dragStarted(event, d) {
760
  if (!event.active) simulation.alphaTarget(0.3).restart();
761
  d.fx = d.x;
 
773
  d.fy = null;
774
  }
775
 
 
776
  window.addEventListener('resize', () => {
777
  const container = document.querySelector('.main-content');
778
  const containerRect = container.getBoundingClientRect();
 
786
  }
787
  });
788
 
 
789
  document.addEventListener('DOMContentLoaded', init);
790
  </script>
791
  </body>