// Global variables let sigmaInstance; let graph; let filter; let config = {}; let greyColor = '#666'; 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() { // Load configuration $.getJSON('config.json', function(data) { config = data; document.title = config.title || 'Daily Papers Atlas'; $('#title').text(config.title || 'Daily Papers Atlas'); // Don't modify the intro text at all - using hardcoded HTML loadGraph(); }); // 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) sigmaInstance.camera.goTo({ratio: sigmaInstance.camera.ratio / 1.5}); }); $('#zoom .z[rel="out"]').click(function() { if (sigmaInstance) sigmaInstance.camera.goTo({ratio: sigmaInstance.camera.ratio * 1.5}); }); $('#zoom .z[rel="center"]').click(function() { if (sigmaInstance) sigmaInstance.camera.goTo({x: 0, y: 0, ratio: 1}); }); // Set up attribute pane functionality $('.returntext').click(function() { closeAttributePane(); }); // 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() { $.getJSON(config.data, function(data) { graph = data; // Initialize Sigma instance sigmaInstance = new sigma({ graph: graph, container: 'sigma-canvas', settings: { labelThreshold: config.labelThreshold || 7, minEdgeSize: config.minEdgeSize || 0.5, maxEdgeSize: config.maxEdgeSize || 2, minNodeSize: config.minNodeSize || 1, maxNodeSize: config.maxNodeSize || 8, drawLabels: config.drawLabels !== false, defaultLabelColor: config.defaultLabelColor || '#000', defaultEdgeColor: config.defaultEdgeColor || '#999', edgeColor: 'default', defaultNodeColor: config.defaultNodeColor || '#666', defaultNodeBorderColor: config.defaultNodeBorderColor || '#fff', borderSize: config.borderSize || 1, nodeHoverColor: 'default', defaultNodeHoverColor: config.defaultNodeHoverColor || '#000', defaultHoverLabelBGColor: config.defaultHoverLabelBGColor || '#fff', defaultLabelHoverColor: config.defaultLabelHoverColor || '#000', enableEdgeHovering: config.enableEdgeHovering !== false, edgeHoverColor: 'edge', edgeHoverSizeRatio: config.edgeHoverSizeRatio || 1.5, defaultEdgeHoverColor: config.defaultEdgeHoverColor || '#000', edgeHoverExtremities: config.edgeHoverExtremities !== false, batchEdgesDrawing: config.batchEdgesDrawing !== false, hideEdgesOnMove: config.hideEdgesOnMove !== false, canvasEdgesBatchSize: config.canvasEdgesBatchSize || 500, animationsTime: config.animationsTime || 1500 } }); // Initialize ForceAtlas2 layout if (config.forceAtlas2) { console.log("Starting ForceAtlas2 layout..."); sigmaInstance.startForceAtlas2({ worker: true, barnesHutOptimize: true, gravity: 1, scalingRatio: 2, slowDown: 10 }); 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.defaultColorAttribute) { $('#color-attribute').val(config.defaultColorAttribute); colorNodesByAttribute(config.defaultColorAttribute); } else { updateColorLegend(nodeTypes); } // Bind events bindEvents(); }, config.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.defaultColorAttribute) { $('#color-attribute').val(config.defaultColorAttribute); colorNodesByAttribute(config.defaultColorAttribute); } else { updateColorLegend(nodeTypes); } // Bind events bindEvents(); } }).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.'); }); } // Apply node styles based on node type function applyNodeStyles() { sigmaInstance.graph.nodes().forEach(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(); } // Initialize filters function initFilters() { try { filter = new sigma.plugins.filter(sigmaInstance); console.log("Filter plugin initialized"); } catch (e) { console.error("Error initializing filter plugin:", e); } } // Filter nodes by type function filterByNodeType(filterValue) { if (!filter) return; filter.undo('node-type'); if (filterValue !== 'all') { filter.nodesBy(function(n) { return n.type === filterValue; }, 'node-type'); } filter.apply(); sigmaInstance.refresh(); } // Bind events function bindEvents() { // When a node is clicked, display its details sigmaInstance.bind('clickNode', function(e) { let node = e.data.node; selectedNode = node; displayNodeDetails(node); }); // When stage is clicked, close the attribute pane sigmaInstance.bind('clickStage', function() { closeAttributePane(); }); // Highlight connected nodes on hover sigmaInstance.bind('hovers', function(e) { if (!e.data.enter.nodes.length) return; let nodeId = e.data.enter.nodes[0]; let toKeep = sigmaInstance.graph.neighbors(nodeId); toKeep[nodeId] = e.data.enter.nodes[0]; activeState.activeNodes = Object.keys(toKeep); sigmaInstance.graph.nodes().forEach(function(n) { if (toKeep[n.id]) { n.originalColor = n.color; n.color = n.color; } else { n.originalColor = n.originalColor || n.color; n.color = greyColor; } }); sigmaInstance.graph.edges().forEach(function(e) { if (toKeep[e.source] && toKeep[e.target]) { e.originalColor = e.color; e.color = e.color; } else { e.originalColor = e.originalColor || e.color; e.color = greyColor; } }); sigmaInstance.refresh(); }); sigmaInstance.bind('hovers', function(e) { if (e.data.leave.nodes.length && !selectedNode) { sigmaInstance.graph.nodes().forEach(function(n) { n.color = n.originalColor || n.color; }); sigmaInstance.graph.edges().forEach(function(e) { e.color = e.originalColor || e.color; }); sigmaInstance.refresh(); } }); } // Display node details in the attribute pane function displayNodeDetails(node) { $('#attributepane').show(); $('.nodeattributes .name').text(node.label || node.id); let dataHTML = ''; for (let attr in node) { if (attr !== 'id' && attr !== 'x' && attr !== 'y' && attr !== 'size' && attr !== 'color' && attr !== 'label' && attr !== 'originalColor' && attr !== 'hidden') { dataHTML += '