// Global variables let sigmaInstance; let graph; let filter; let config = {}; let greyColor = '#ccc'; let activeState = { activeNodes: [], activeEdges: [] }; let selectedNode = null; let colorAttributes = []; let colors = [ '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', '#e377c2', '#7f7f7f', '#bcbd22', '#17becf' ]; let nodeTypes = { 'paper': { color: '#2ca02c', size: 3 }, 'author': { color: '#9467bd', size: 5 }, 'organization': { color: '#1f77b4', size: 4 }, 'unknown': { color: '#ff7f0e', size: 3 } }; // Initialize when document is ready $(document).ready(function() { console.log("Document ready, initializing Daily Paper Atlas"); // Initialize attribute pane $('#attributepane').css('display', 'none'); // Load configuration $.getJSON('config.json', function(data) { console.log("Configuration loaded:", data); config = data; document.title = config.text.title || 'Daily Paper Atlas'; $('#title').text(config.text.title || 'Daily Paper Atlas'); $('#titletext').text(config.text.intro || ''); loadGraph(); }).fail(function(jqXHR, textStatus, errorThrown) { console.error("Failed to load config:", textStatus, errorThrown); }); // Set up search functionality $('#search-input').keyup(function(e) { let searchTerm = $(this).val(); if (searchTerm.length > 2) { searchNodes(searchTerm); } else { $('.results').empty(); } }); $('#search-button').click(function() { let searchTerm = $('#search-input').val(); if (searchTerm.length > 2) { searchNodes(searchTerm); } }); // Set up zoom buttons $('#zoom .z[rel="in"]').click(function() { if (sigmaInstance) { let a = sigmaInstance._core; sigmaInstance.zoomTo(a.domElements.nodes.width / 2, a.domElements.nodes.height / 2, a.mousecaptor.ratio * 1.5); } }); $('#zoom .z[rel="out"]').click(function() { if (sigmaInstance) { let a = sigmaInstance._core; sigmaInstance.zoomTo(a.domElements.nodes.width / 2, a.domElements.nodes.height / 2, a.mousecaptor.ratio * 0.5); } }); $('#zoom .z[rel="center"]').click(function() { if (sigmaInstance) { sigmaInstance.position(0, 0, 1).draw(); } }); // Set up attribute pane functionality $('.returntext').click(function() { nodeNormal(); }); // Set up color selector $('#color-attribute').change(function() { let attr = $(this).val(); colorNodesByAttribute(attr); }); // Set up filter selector $('#filter-select').change(function() { let filterValue = $(this).val(); filterByNodeType(filterValue); }); }); // Load graph data function loadGraph() { console.log("Loading graph data from:", config.data); // Check if data is a .gz file and needs decompression if (config.data && config.data.endsWith('.gz')) { console.log("Compressed data detected, loading via fetch and pako"); fetch(config.data) .then(response => response.arrayBuffer()) .then(arrayBuffer => { try { // Decompress the gzipped data const uint8Array = new Uint8Array(arrayBuffer); const decompressed = pako.inflate(uint8Array, { to: 'string' }); // Parse the JSON data const data = JSON.parse(decompressed); console.log("Graph data decompressed and parsed successfully"); initializeGraph(data); } catch (error) { console.error("Error decompressing data:", error); } }) .catch(error => { console.error("Error fetching compressed data:", error); }); } else { // Load uncompressed JSON directly $.getJSON(config.data, function(data) { console.log("Graph data loaded successfully"); initializeGraph(data); }).fail(function(jqXHR, textStatus, errorThrown) { console.error("Failed to load graph data:", textStatus, errorThrown); alert('Failed to load graph data. Please check the console for more details.'); }); } } // Initialize the graph with the loaded data function initializeGraph(data) { graph = data; console.log("Initializing graph with nodes:", graph.nodes.length, "edges:", graph.edges.length); try { // Initialize Sigma instance using the older sigma.init pattern sigmaInstance = sigma.init(document.getElementById('sigma-canvas')); // Configure mouse properties to ensure events work sigmaInstance.mouseProperties({ maxRatio: 32, minRatio: 0.5, mouseEnabled: true, mouseInertia: 0.8 }); console.log("Sigma mouse properties configured"); // Add nodes and edges to sigma for (let i = 0; i < graph.nodes.length; i++) { let node = graph.nodes[i]; sigmaInstance.addNode(node.id, { label: node.label || node.id, x: node.x || Math.random() * 100, y: node.y || Math.random() * 100, size: node.size || 1, color: node.color || (node.type && config.nodeTypes && config.nodeTypes[node.type] ? config.nodeTypes[node.type].color : nodeTypes[node.type]?.color || '#666'), type: node.type }); } for (let i = 0; i < graph.edges.length; i++) { let edge = graph.edges[i]; sigmaInstance.addEdge(edge.id, edge.source, edge.target, { size: edge.size || 1, color: edge.color || '#aaa' }); } // Configure drawing properties sigmaInstance.drawingProperties({ labelThreshold: config.sigma?.drawingProperties?.labelThreshold || 8, defaultLabelColor: config.sigma?.drawingProperties?.defaultLabelColor || '#000', defaultLabelSize: config.sigma?.drawingProperties?.defaultLabelSize || 14, defaultEdgeType: config.sigma?.drawingProperties?.defaultEdgeType || 'curve', defaultHoverLabelBGColor: config.sigma?.drawingProperties?.defaultHoverLabelBGColor || '#002147', defaultLabelHoverColor: config.sigma?.drawingProperties?.defaultLabelHoverColor || '#fff', borderSize: 2, nodeBorderColor: '#fff', defaultNodeBorderColor: '#fff', defaultNodeHoverColor: '#fff' }); // Configure graph properties sigmaInstance.graphProperties({ minNodeSize: config.sigma?.graphProperties?.minNodeSize || 1, maxNodeSize: config.sigma?.graphProperties?.maxNodeSize || 8, minEdgeSize: config.sigma?.graphProperties?.minEdgeSize || 0.5, maxEdgeSize: config.sigma?.graphProperties?.maxEdgeSize || 2, sideMargin: 50 }); // Force redraw and refresh sigmaInstance.draw(2, 2, 2, 2); sigmaInstance.refresh(); console.log("Sigma instance created and configured:", sigmaInstance); // Initialize ForceAtlas2 layout if configured if (config.features && config.features.forceAtlas2) { console.log("Starting ForceAtlas2 layout..."); sigmaInstance.startForceAtlas2(); setTimeout(function() { sigmaInstance.stopForceAtlas2(); console.log("ForceAtlas2 layout completed"); sigmaInstance.refresh(); // Initialize node colors and sizes by type applyNodeStyles(); // Initialize filtering initFilters(); // If a default color attribute is set, apply it if (config.features && config.features.defaultColorAttribute) { $('#color-attribute').val(config.features.defaultColorAttribute); colorNodesByAttribute(config.features.defaultColorAttribute); } else { updateColorLegend(nodeTypes); } // Bind events bindEvents(); }, config.features?.forceAtlas2Time || 5000); } else { // Initialize node colors and sizes by type applyNodeStyles(); // Initialize filtering initFilters(); // If a default color attribute is set, apply it if (config.features && config.features.defaultColorAttribute) { $('#color-attribute').val(config.features.defaultColorAttribute); colorNodesByAttribute(config.features.defaultColorAttribute); } else { updateColorLegend(nodeTypes); } // Bind events bindEvents(); } } catch (e) { console.error("Error initializing sigma instance:", e); } } // Apply node styles based on node type function applyNodeStyles() { if (!sigmaInstance) return; try { sigmaInstance.iterNodes(function(node) { if (node.type && config.nodeTypes && config.nodeTypes[node.type]) { node.color = config.nodeTypes[node.type].color; node.size = config.nodeTypes[node.type].size; } else if (node.type && nodeTypes[node.type]) { node.color = nodeTypes[node.type].color; node.size = nodeTypes[node.type].size; } }); sigmaInstance.refresh(); } catch (e) { console.error("Error applying node styles:", e); } } // Initialize filters function initFilters() { try { if (sigma.plugins && sigma.plugins.filter) { filter = new sigma.plugins.filter(sigmaInstance); console.log("Filter plugin initialized"); } else { console.warn("Sigma filter plugin not available"); } } catch (e) { console.error("Error initializing filter plugin:", e); } } // Filter nodes by type function filterByNodeType(filterValue) { if (!filter) return; try { filter.undo('node-type'); if (filterValue === 'papers') { filter.nodesBy(function(n) { return n.type === 'paper'; }, 'node-type'); } else if (filterValue === 'authors') { filter.nodesBy(function(n) { return n.type === 'author'; }, 'node-type'); } filter.apply(); sigmaInstance.refresh(); } catch (e) { console.error("Error filtering nodes:", e); } } // Bind events function bindEvents() { if (!sigmaInstance) { console.error("Sigma instance not found when binding events"); return; } console.log("Binding sigma events to instance:", sigmaInstance); // Add a direct click handler to the sigma canvas document.getElementById('sigma-canvas').addEventListener('click', function(evt) { console.log("Canvas clicked, checking if it's on a node"); // The event happened on canvas, now check if it was on a node var x = evt.offsetX || evt.layerX; var y = evt.offsetY || evt.layerY; var nodeFound = false; sigmaInstance.iterNodes(function(n) { if (!nodeFound && n.displayX && n.displayY && n.displaySize) { var dx = n.displayX - x; var dy = n.displayY - y; var distance = Math.sqrt(dx * dx + dy * dy); if (distance < n.displaySize) { console.log("Node found under click:", n.id); nodeFound = true; nodeActive(n.id); } } }); if (!nodeFound) { console.log("No node found under click, closing node panel"); nodeNormal(); } }); // Still try to use sigma's events try { // When a node is clicked, display its details sigmaInstance.bind('clickNode', function(e) { var node = e.target || e.data.node || e.data; console.log("Official clickNode event received:", e); var nodeId = node.id || node; console.log("Node clicked via official event:", nodeId); nodeActive(nodeId); }); // When stage is clicked, close the attribute pane sigmaInstance.bind('clickStage', function() { console.log("Official clickStage event received"); nodeNormal(); }); // Highlight connected nodes on hover sigmaInstance.bind('overNode', function(e) { var node = e.target || e.data.node || e.data; var nodeId = node.id || node; console.log("Node hover enter:", nodeId); // First identify neighbors var neighbors = {}; sigmaInstance.iterEdges(function(edge) { if (edge.source == nodeId || edge.target == nodeId) { neighbors[edge.source == nodeId ? edge.target : edge.source] = true; } }); // Then update node and edge colors sigmaInstance.iterNodes(function(node) { if (node.id == nodeId || neighbors[node.id]) { node.originalColor = node.color; } else { node.originalColor = node.originalColor || node.color; node.color = greyColor; } }); sigmaInstance.iterEdges(function(edge) { if (edge.source == nodeId || edge.target == nodeId) { edge.originalColor = edge.color; } else { edge.originalColor = edge.originalColor || edge.color; edge.color = greyColor; } }); sigmaInstance.refresh(); }); sigmaInstance.bind('outNode', function(e) { var node = e.target || e.data.node || e.data; var nodeId = node.id || node; console.log("Node hover leave:", nodeId); if (!sigmaInstance.detail) { sigmaInstance.iterNodes(function(n) { n.color = n.originalColor || n.color; }); sigmaInstance.iterEdges(function(e) { e.color = e.originalColor || e.color; }); sigmaInstance.refresh(); } }); console.log("Sigma events bound successfully"); } catch (e) { console.error("Error binding sigma events:", e); } } // Display node details (used when a node is clicked) function nodeActive(nodeId) { console.log("nodeActive called with id:", nodeId); // Find the node var node = null; sigmaInstance.iterNodes(function(n) { if (n.id == nodeId) { node = n; } }); if (!node) { console.error("Node not found:", nodeId); return; } console.log("Node found:", node); sigmaInstance.detail = true; selectedNode = node; // Find neighbors var neighbors = {}; sigmaInstance.iterEdges(function(e) { if (e.source == nodeId || e.target == nodeId) { neighbors[e.source == nodeId ? e.target : e.source] = { name: e.label || "", color: e.color }; } }); console.log("Neighbors found:", Object.keys(neighbors).length); // Update node appearance sigmaInstance.iterNodes(function(n) { if (n.id == nodeId) { n.color = n.originalColor || n.color; n.size = n.size * 1.5; // Emphasize selected node } else if (neighbors[n.id]) { n.color = n.originalColor || n.color; } else { n.originalColor = n.originalColor || n.color; n.color = greyColor; } }); // Refresh display sigmaInstance.refresh(); // Populate connection list var connectionList = []; for (var id in neighbors) { var neighbor = null; sigmaInstance.iterNodes(function(n) { if (n.id == id) { neighbor = n; } }); if (neighbor) { connectionList.push('