davidardz07 commited on
Commit
76f3c6e
·
1 Parent(s): 7888a2c

Editar Links y Nodos

Browse files
Files changed (3) hide show
  1. index.html +92 -47
  2. script.js +192 -23
  3. style.css +67 -0
index.html CHANGED
@@ -11,31 +11,78 @@
11
  <script src="https://unpkg.com/feather-icons"></script>
12
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
13
  </head>
14
- <body class="bg-gray-100 min-h-screen flex">
15
- <div class="flex flex-1">
16
- <!-- Sidebar -->
17
- <div class="w-80 bg-white shadow-lg flex flex-col">
18
- <div class="p-4 border-b flex items-center justify-between">
19
- <h1 class="text-xl font-bold text-gray-800">Sankey Flow Wizard</h1>
20
- <button id="settings-btn" class="bg-gray-100 hover:bg-gray-200 text-gray-800 px-3 py-2 rounded-md text-sm flex items-center space-x-2">
21
- <i data-feather="settings" class="mr-1"></i>
22
  </button>
23
  </div>
24
-
25
- <div class="flex-1 overflow-y-auto p-4 space-y-6">
26
- <!-- Settings panel (hidden by default) -->
27
- <div id="settings-panel" class="bg-gray-50 p-4 rounded-lg hidden">
28
- <h3 class="font-medium text-gray-700 mb-3 flex items-center">
29
- <i data-feather="sliders" class="mr-2"></i> Configuración
30
- </h3>
31
- <div class="space-y-4">
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
32
  <div>
33
  <label class="block text-sm text-gray-600 mb-1">Text Color</label>
34
- <input type="color" id="text-color" value="#000000" class="w-full h-10">
35
  </div>
36
  <div>
37
  <label class="block text-sm text-gray-600 mb-1">Text Position</label>
38
- <select id="text-position" class="w-full p-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
39
  <option value="left">Left</option>
40
  <option value="middle" selected>Middle</option>
41
  <option value="right">Right</option>
@@ -55,38 +102,36 @@
55
  </div>
56
  </div>
57
  </div>
58
- <!-- Import/Export -->
59
- <div class="bg-gray-50 p-4 rounded-lg">
60
- <h3 class="font-medium text-gray-700 mb-3 flex items-center">
61
- <i data-feather="upload" class="mr-2"></i> Import/Export
62
- </h3>
63
- <div class="grid grid-cols-2 gap-2">
64
- <button id="import-csv" class="bg-blue-500 hover:bg-blue-600 text-white px-3 py-2 rounded-md text-sm transition">
65
- <i data-feather="upload" class="mr-1"></i> Import CSV
66
- </button>
67
- <button id="export-csv" class="bg-green-500 hover:bg-green-600 text-white px-3 py-2 rounded-md text-sm transition">
68
- <i data-feather="download" class="mr-1"></i> Export CSV
69
- </button>
70
- <button id="center-diagram" class="bg-purple-500 hover:bg-purple-600 text-white px-3 py-2 rounded-md text-sm transition col-span-2">
71
- <i data-feather="crosshair" class="mr-1"></i> Center Diagram
72
- </button>
73
- </div>
74
- <input type="file" id="csv-file" accept=".csv" class="hidden">
75
- </div>
76
-
77
- <!-- Appearance moved into the Settings panel -->
78
 
79
  <!-- Nodes Management -->
80
- <div class="bg-gray-50 p-4 rounded-lg">
81
- <h3 class="font-medium text-gray-700 mb-3 flex items-center">
82
- <i data-feather="circle" class="mr-2"></i> Nodes
83
- </h3>
84
- <div class="space-y-3">
85
- <div class="flex space-x-2">
86
- <input type="text" id="node-name" placeholder="Node name" class="flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
87
- <button id="add-node" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md transition">Add</button>
 
 
 
 
 
88
  </div>
89
- <div id="nodes-list" class="max-h-60 overflow-y-auto border rounded-md p-2 space-y-2"></div>
90
  </div>
91
  </div>
92
 
 
11
  <script src="https://unpkg.com/feather-icons"></script>
12
  <script src="https://cdn.jsdelivr.net/npm/feather-icons/dist/feather.min.js"></script>
13
  </head>
14
+ <body class="bg-gray-100 min-h-screen flex flex-col">
15
+ <!-- Edit Modal -->
16
+ <div id="edit-modal" class="fixed inset-0 bg-gray-600 bg-opacity-50 hidden flex items-center justify-center z-50">
17
+ <div class="bg-white rounded-lg p-6 w-96 shadow-xl">
18
+ <div class="flex justify-between items-center mb-4">
19
+ <h3 id="modal-title" class="text-lg font-semibold text-gray-800"></h3>
20
+ <button id="close-modal" class="text-gray-500 hover:text-gray-700">
21
+ <i data-feather="x"></i>
22
  </button>
23
  </div>
24
+ <div id="modal-content">
25
+ <!-- Node editing form -->
26
+ <div id="node-edit-form" class="hidden">
27
+ <div class="mb-4">
28
+ <label class="block text-sm text-gray-600 mb-1">Node Name</label>
29
+ <input type="text" id="edit-node-name" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
30
+ </div>
31
+ </div>
32
+ <!-- Link editing form -->
33
+ <div id="link-edit-form" class="hidden">
34
+ <div class="mb-4">
35
+ <label class="block text-sm text-gray-600 mb-1">Source Node</label>
36
+ <select id="edit-link-source" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
37
+ </div>
38
+ <div class="mb-4">
39
+ <label class="block text-sm text-gray-600 mb-1">Target Node</label>
40
+ <select id="edit-link-target" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"></select>
41
+ </div>
42
+ <div class="mb-4">
43
+ <label class="block text-sm text-gray-600 mb-1">Value</label>
44
+ <input type="number" id="edit-link-value" class="w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500" min="0">
45
+ </div>
46
+ </div>
47
+ </div>
48
+ <div class="flex justify-end space-x-2 mt-6">
49
+ <button id="cancel-edit" class="px-4 py-2 text-gray-600 hover:text-gray-800 rounded-md">Cancel</button>
50
+ <button id="save-edit" class="px-4 py-2 bg-blue-500 hover:bg-blue-600 text-white rounded-md">Save</button>
51
+ </div>
52
+ </div>
53
+ </div>
54
+ <!-- Main Header -->
55
+ <div class="bg-white shadow-lg p-4 flex items-center justify-between relative">
56
+ <div class="flex items-center">
57
+ <button id="toggle-sidebar" class="text-gray-600 hover:text-gray-800 p-2 rounded-md mr-4 tooltip-trigger" title="Toggle Sidebar">
58
+ <i data-feather="menu"></i>
59
+ </button>
60
+ <h1 class="text-xl font-bold text-gray-800">Sankey Flow Wizard</h1>
61
+ </div>
62
+ <div class="flex items-center space-x-2">
63
+ <button id="import-csv" class="p-2 text-gray-600 hover:text-gray-800 rounded-md tooltip-trigger" title="Import CSV">
64
+ <i data-feather="upload"></i>
65
+ </button>
66
+ <button id="export-csv" class="p-2 text-gray-600 hover:text-gray-800 rounded-md tooltip-trigger" title="Export CSV">
67
+ <i data-feather="download"></i>
68
+ </button>
69
+ <button id="center-diagram" class="p-2 text-gray-600 hover:text-gray-800 rounded-md tooltip-trigger" title="Center Diagram">
70
+ <i data-feather="crosshair"></i>
71
+ </button>
72
+ <div class="relative">
73
+ <button id="settings-btn" class="p-2 text-gray-600 hover:text-gray-800 rounded-md tooltip-trigger" title="Settings">
74
+ <i data-feather="settings"></i>
75
+ </button>
76
+ <!-- Settings Dropdown Menu -->
77
+ <div id="settings-dropdown" class="hidden absolute right-0 mt-2 w-64 bg-white rounded-lg shadow-lg z-50">
78
+ <div class="p-4 space-y-4">
79
  <div>
80
  <label class="block text-sm text-gray-600 mb-1">Text Color</label>
81
+ <input type="color" id="text-color" value="#000000" class="w-full h-8">
82
  </div>
83
  <div>
84
  <label class="block text-sm text-gray-600 mb-1">Text Position</label>
85
+ <select id="text-position" class="w-full p-2 border rounded-md">
86
  <option value="left">Left</option>
87
  <option value="middle" selected>Middle</option>
88
  <option value="right">Right</option>
 
102
  </div>
103
  </div>
104
  </div>
105
+ </div>
106
+ </div>
107
+ </div>
108
+ <div class="flex flex-1">
109
+ <!-- Sidebar -->
110
+ <div id="sidebar" class="w-80 bg-white shadow-lg flex flex-col transition-all duration-300 ease-in-out">
111
+ <div class="p-2 border-b">
112
+ <h2 class="text-lg font-semibold text-gray-800">Diagram Editor</h2>
113
+ </div>
114
+
115
+ <div class="flex-1 overflow-y-auto p-2 space-y-1">
116
+
117
+ <!-- File input for CSV import -->
118
+ <input type="file" id="csv-file" accept=".csv" class="hidden">
 
 
 
 
 
 
119
 
120
  <!-- Nodes Management -->
121
+ <div class="bg-gray-50 rounded-lg">
122
+ <div class="p-2 border-b">
123
+ <h3 class="font-medium text-gray-700 flex items-center">
124
+ <i data-feather="circle" class="mr-2"></i> Nodes
125
+ </h3>
126
+ </div>
127
+ <div class="p-3">
128
+ <div class="space-y-3">
129
+ <div id="nodes-list" class="max-h-[200px] overflow-y-auto border rounded-md p-2 space-y-2 bg-white"></div>
130
+ <div class="flex space-x-2">
131
+ <input type="text" id="node-name" placeholder="Node name" class="flex-1 px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500">
132
+ <button id="add-node" class="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded-md transition">Add</button>
133
+ </div>
134
  </div>
 
135
  </div>
136
  </div>
137
 
script.js CHANGED
@@ -1,4 +1,109 @@
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
  // Define the d3.sankey layout function
3
  d3.sankey = function() {
4
  var sankey = {},
@@ -320,29 +425,81 @@ function saveData() {
320
  localStorage.setItem('sankeyData', JSON.stringify(sankeyData));
321
  }
322
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
323
  // Initialize the diagram
324
  function initDiagram() {
325
- const svg = d3.select("#sankey-diagram");
326
- svg.selectAll("*").remove();
327
-
328
- // Early return if SVG container doesn't exist
329
- if (!svg.node()) return;
330
-
331
- // Set up the sankey diagram properties
332
- const width = document.getElementById('diagram-container').clientWidth;
333
- const baseHeight = parseInt(document.getElementById('diagram-height').value);
334
- const textPosition = document.getElementById('text-position').value;
335
- const textPadding = textPosition !== 'middle' ? 50 : 0; // Extra space for text above/below
336
- const height = baseHeight + textPadding;
337
- const linkAlpha = parseFloat(document.getElementById('link-alpha').value);
338
-
339
- // Create Sankey generator with increased padding for better distribution
340
- const sankey = d3.sankey()
341
- .nodeWidth(parseInt(document.getElementById('node-width').value))
342
- .nodePadding(120) // Increased padding for better vertical distribution
343
- .size([width - textPadding, height - textPadding]);
344
-
345
- // Create a copy of the data
 
 
 
 
 
 
 
 
 
 
346
  const graph = {
347
  nodes: sankeyData.nodes.map(d => Object.assign({}, d)),
348
  links: sankeyData.links.map(d => Object.assign({}, d))
@@ -505,7 +662,7 @@ function updateLists() {
505
 
506
  sankeyData.nodes.forEach((node, index) => {
507
  const nodeElement = document.createElement('div');
508
- nodeElement.className = 'flex items-center justify-between bg-white p-2 rounded border border-gray-200';
509
  nodeElement.innerHTML = `
510
  <span>${node.name}</span>
511
  <div class="flex items-center space-x-2">
@@ -515,6 +672,12 @@ function updateLists() {
515
  </button>
516
  </div>
517
  `;
 
 
 
 
 
 
518
  nodesList.appendChild(nodeElement);
519
  });
520
 
@@ -527,13 +690,19 @@ function updateLists() {
527
  const targetNode = sankeyData.nodes[link.target]?.name || 'Unknown';
528
 
529
  const linkElement = document.createElement('div');
530
- linkElement.className = 'flex items-center justify-between bg-white p-2 rounded border border-gray-200';
531
  linkElement.innerHTML = `
532
  <span>${sourceNode} → ${targetNode} (${link.value})</span>
533
  <button class="text-red-500 hover:text-red-700 delete-link" data-index="${index}">
534
  <i data-feather="trash-2" width="16"></i>
535
  </button>
536
  `;
 
 
 
 
 
 
537
  linksList.appendChild(linkElement);
538
  });
539
 
 
1
 
2
+ // DOM Elements
3
+ const sidebar = document.getElementById('sidebar');
4
+ const toggleSidebarBtn = document.getElementById('toggle-sidebar');
5
+ const settingsBtn = document.getElementById('settings-btn');
6
+ const settingsDropdown = document.getElementById('settings-dropdown');
7
+
8
+ // Modal Elements
9
+ const editModal = document.getElementById('edit-modal');
10
+ const modalTitle = document.getElementById('modal-title');
11
+ const nodeEditForm = document.getElementById('node-edit-form');
12
+ const linkEditForm = document.getElementById('link-edit-form');
13
+ const editNodeName = document.getElementById('edit-node-name');
14
+ const editLinkSource = document.getElementById('edit-link-source');
15
+ const editLinkTarget = document.getElementById('edit-link-target');
16
+ const editLinkValue = document.getElementById('edit-link-value');
17
+ const saveEditBtn = document.getElementById('save-edit');
18
+ const cancelEditBtn = document.getElementById('cancel-edit');
19
+ const closeModalBtn = document.getElementById('close-modal');
20
+
21
+ let currentEditItem = null; // Store the item being edited
22
+
23
+ // Modal Functions
24
+ function openModal() {
25
+ editModal.classList.remove('hidden');
26
+ feather.replace();
27
+ }
28
+
29
+ function closeModal() {
30
+ editModal.classList.add('hidden');
31
+ currentEditItem = null;
32
+ nodeEditForm.classList.add('hidden');
33
+ linkEditForm.classList.add('hidden');
34
+ }
35
+
36
+ function populateNodeForm(node) {
37
+ modalTitle.textContent = 'Edit Node';
38
+ nodeEditForm.classList.remove('hidden');
39
+ linkEditForm.classList.add('hidden');
40
+ editNodeName.value = node.name;
41
+ }
42
+
43
+ function populateLinkForm(link) {
44
+ modalTitle.textContent = 'Edit Link';
45
+ linkEditForm.classList.remove('hidden');
46
+ nodeEditForm.classList.add('hidden');
47
+
48
+ // Clear and populate source/target dropdowns
49
+ editLinkSource.innerHTML = '';
50
+ editLinkTarget.innerHTML = '';
51
+ sankeyData.nodes.forEach(node => {
52
+ const sourceOption = document.createElement('option');
53
+ sourceOption.value = node.name;
54
+ sourceOption.textContent = node.name;
55
+ editLinkSource.appendChild(sourceOption.cloneNode(true));
56
+ editLinkTarget.appendChild(sourceOption);
57
+ });
58
+
59
+ // Set current values
60
+ editLinkSource.value = sankeyData.nodes[link.source].name;
61
+ editLinkTarget.value = sankeyData.nodes[link.target].name;
62
+ editLinkValue.value = link.value;
63
+ }
64
+
65
+ // Event Handlers for Modal
66
+ closeModalBtn.addEventListener('click', closeModal);
67
+ cancelEditBtn.addEventListener('click', closeModal);
68
+
69
+ // Click outside modal to close
70
+ editModal.addEventListener('click', (e) => {
71
+ if (e.target === editModal) {
72
+ closeModal();
73
+ }
74
+ });
75
+
76
+ // Toggle sidebar
77
+ toggleSidebarBtn.addEventListener('click', () => {
78
+ sidebar.classList.toggle('collapsed');
79
+ });
80
+
81
+ // Toggle settings dropdown
82
+ let isSettingsOpen = false;
83
+ settingsBtn.addEventListener('click', (e) => {
84
+ e.stopPropagation();
85
+ isSettingsOpen = !isSettingsOpen;
86
+ settingsDropdown.classList.toggle('hidden', !isSettingsOpen);
87
+ });
88
+
89
+ // Close settings dropdown when clicking outside
90
+ document.addEventListener('click', (e) => {
91
+ if (!settingsDropdown.contains(e.target) && !settingsBtn.contains(e.target)) {
92
+ settingsDropdown.classList.add('hidden');
93
+ isSettingsOpen = false;
94
+ }
95
+ });
96
+
97
+ // Prevent settings dropdown from closing when clicking inside it
98
+ settingsDropdown.addEventListener('click', (e) => {
99
+ e.stopPropagation();
100
+ });
101
+
102
+ // Initialize Feather icons
103
+ document.addEventListener('DOMContentLoaded', () => {
104
+ feather.replace();
105
+ });
106
+
107
  // Define the d3.sankey layout function
108
  d3.sankey = function() {
109
  var sankey = {},
 
425
  localStorage.setItem('sankeyData', JSON.stringify(sankeyData));
426
  }
427
 
428
+ // Edit node or link
429
+ function handleEditNode(node, index) {
430
+ currentEditItem = { type: 'node', index };
431
+ populateNodeForm(node);
432
+ openModal();
433
+ }
434
+
435
+ function handleEditLink(link, index) {
436
+ currentEditItem = { type: 'link', index };
437
+ populateLinkForm(link);
438
+ openModal();
439
+ }
440
+
441
+ // Save changes from modal
442
+ saveEditBtn.addEventListener('click', () => {
443
+ if (currentEditItem) {
444
+ if (currentEditItem.type === 'node') {
445
+ const newName = editNodeName.value.trim();
446
+ if (newName) {
447
+ sankeyData.nodes[currentEditItem.index].name = newName;
448
+ }
449
+ } else if (currentEditItem.type === 'link') {
450
+ const sourceNode = editLinkSource.value;
451
+ const targetNode = editLinkTarget.value;
452
+ const value = parseInt(editLinkValue.value);
453
+
454
+ if (sourceNode && targetNode && value > 0) {
455
+ sankeyData.links[currentEditItem.index] = {
456
+ source: sankeyData.nodes.findIndex(n => n.name === sourceNode),
457
+ target: sankeyData.nodes.findIndex(n => n.name === targetNode),
458
+ value: value
459
+ };
460
+ }
461
+ }
462
+
463
+ updateLists();
464
+ initDiagram();
465
+ saveData();
466
+ closeModal();
467
+ }
468
+ });
469
+
470
  // Initialize the diagram
471
  function initDiagram() {
472
+ const svg = d3.select("#sankey-diagram");
473
+ svg.selectAll("*").remove();
474
+
475
+ // Early return if SVG container doesn't exist
476
+ if (!svg.node()) return;
477
+
478
+ // Get the container dimensions
479
+ const container = document.getElementById('diagram-container');
480
+ const width = container.clientWidth;
481
+ const containerHeight = container.clientHeight;
482
+
483
+ // Set up the sankey diagram properties
484
+ const baseHeight = parseInt(document.getElementById('diagram-height').value);
485
+ const textPosition = document.getElementById('text-position').value;
486
+ const textPadding = textPosition !== 'middle' ? 50 : 0; // Extra space for text above/below
487
+
488
+ // Use the larger of container height or base height to prevent shrinking
489
+ const height = Math.max(containerHeight, baseHeight + textPadding);
490
+ const linkAlpha = parseFloat(document.getElementById('link-alpha').value);
491
+
492
+ // Update SVG dimensions
493
+ svg.attr("width", width)
494
+ .attr("height", height)
495
+ .attr("viewBox", [0, 0, width, height])
496
+ .attr("preserveAspectRatio", "xMinYMin");
497
+
498
+ // Create Sankey generator with increased padding for better distribution
499
+ const sankey = d3.sankey()
500
+ .nodeWidth(parseInt(document.getElementById('node-width').value))
501
+ .nodePadding(120) // Increased padding for better vertical distribution
502
+ .size([width - textPadding, height - textPadding]); // Create a copy of the data
503
  const graph = {
504
  nodes: sankeyData.nodes.map(d => Object.assign({}, d)),
505
  links: sankeyData.links.map(d => Object.assign({}, d))
 
662
 
663
  sankeyData.nodes.forEach((node, index) => {
664
  const nodeElement = document.createElement('div');
665
+ nodeElement.className = 'flex items-center justify-between bg-white p-2 rounded border border-gray-200 cursor-pointer';
666
  nodeElement.innerHTML = `
667
  <span>${node.name}</span>
668
  <div class="flex items-center space-x-2">
 
672
  </button>
673
  </div>
674
  `;
675
+ // Add double-click handler for editing
676
+ nodeElement.addEventListener('dblclick', (e) => {
677
+ if (!e.target.classList.contains('node-color-picker') && !e.target.closest('.delete-node')) {
678
+ handleEditNode(node, index);
679
+ }
680
+ });
681
  nodesList.appendChild(nodeElement);
682
  });
683
 
 
690
  const targetNode = sankeyData.nodes[link.target]?.name || 'Unknown';
691
 
692
  const linkElement = document.createElement('div');
693
+ linkElement.className = 'flex items-center justify-between bg-white p-2 rounded border border-gray-200 cursor-pointer';
694
  linkElement.innerHTML = `
695
  <span>${sourceNode} → ${targetNode} (${link.value})</span>
696
  <button class="text-red-500 hover:text-red-700 delete-link" data-index="${index}">
697
  <i data-feather="trash-2" width="16"></i>
698
  </button>
699
  `;
700
+ // Add double-click handler for editing
701
+ linkElement.addEventListener('dblclick', (e) => {
702
+ if (!e.target.closest('.delete-link')) {
703
+ handleEditLink(link, index);
704
+ }
705
+ });
706
  linksList.appendChild(linkElement);
707
  });
708
 
style.css CHANGED
@@ -1,4 +1,68 @@
1
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2
 
3
  /* Custom styles for the sankey diagram */
4
  #sankey-diagram {
@@ -12,6 +76,9 @@
12
  border-radius: 0.5rem;
13
  position: relative;
14
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
 
 
 
15
  }
16
  .node rect {
17
  cursor: move;
 
1
 
2
+ /* Main layout */
3
+ body {
4
+ display: flex;
5
+ flex-direction: column;
6
+ min-height: 100vh;
7
+ }
8
+
9
+ /* Header styles */
10
+ .header {
11
+ background-color: white;
12
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
13
+ z-index: 10;
14
+ }
15
+
16
+ /* Tooltip styles */
17
+ .tooltip-trigger {
18
+ position: relative;
19
+ }
20
+
21
+ .tooltip-trigger:hover::after {
22
+ content: attr(title);
23
+ position: absolute;
24
+ bottom: -30px;
25
+ left: 50%;
26
+ transform: translateX(-50%);
27
+ padding: 4px 8px;
28
+ background-color: rgba(0, 0, 0, 0.8);
29
+ color: white;
30
+ border-radius: 4px;
31
+ font-size: 12px;
32
+ white-space: nowrap;
33
+ z-index: 1000;
34
+ }
35
+
36
+ /* Settings dropdown */
37
+ #settings-dropdown {
38
+ border: 1px solid #e5e7eb;
39
+ animation: fadeIn 0.2s ease-out;
40
+ }
41
+
42
+ @keyframes fadeIn {
43
+ from { opacity: 0; transform: translateY(-10px); }
44
+ to { opacity: 1; transform: translateY(0); }
45
+ }
46
+
47
+ /* Sidebar transitions */
48
+ #sidebar {
49
+ transition: width 0.3s ease-in-out;
50
+ }
51
+
52
+ #sidebar.collapsed {
53
+ width: 0;
54
+ overflow: hidden;
55
+ }
56
+
57
+ /* Button hover effects */
58
+ button {
59
+ transition: all 0.2s ease-in-out;
60
+ }
61
+
62
+ button:hover {
63
+ transform: scale(1.05);
64
+ background-color: rgba(0, 0, 0, 0.05);
65
+ }
66
 
67
  /* Custom styles for the sankey diagram */
68
  #sankey-diagram {
 
76
  border-radius: 0.5rem;
77
  position: relative;
78
  box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
79
+ min-height: 400px; /* Set a minimum height */
80
+ height: 100%; /* Take full height of parent */
81
+ display: flex; /* Ensure SVG fills container */
82
  }
83
  .node rect {
84
  cursor: move;