Daily-Papers-Atlas / js /paper-atlas.js
hesamation's picture
removed emoji from title
3e40086
// 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 += '<div><strong>' + attr + ':</strong> ' + node[attr] + '</div>';
}
}
$('.nodeattributes .data').html(dataHTML);
// Display connected nodes
let neighbors = sigmaInstance.graph.neighbors(node.id);
let linksHTML = '';
for (let id in neighbors) {
let neighbor = sigmaInstance.graph.nodes(id);
if (neighbor) {
linksHTML += '<li><a href="#" data-node-id="' + id + '">' + (neighbor.label || id) + '</a></li>';
}
}
$('.nodeattributes .link ul').html(linksHTML);
// Set up click event for connected nodes
$('.nodeattributes .link ul li a').click(function(e) {
e.preventDefault();
let nodeId = $(this).data('node-id');
let node = sigmaInstance.graph.nodes(nodeId);
if (node) {
selectedNode = node;
displayNodeDetails(node);
sigmaInstance.renderers[0].dispatchEvent('clickNode', {
node: node
});
}
});
}
// Close the attribute pane
function closeAttributePane() {
$('#attributepane').hide();
selectedNode = null;
// Reset colors
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();
}
// Color nodes by attribute
function colorNodesByAttribute(attribute) {
// Get all unique values for the attribute
let values = {};
let valueCount = 0;
sigmaInstance.graph.nodes().forEach(function(n) {
let value = n[attribute] || 'unknown';
if (!values[value]) {
values[value] = true;
valueCount++;
}
});
// Assign colors to values
let valueColors = {};
let i = 0;
let palette = config.colorPalette || colors;
for (let value in values) {
valueColors[value] = palette[i % palette.length];
i++;
}
// Update node colors
sigmaInstance.graph.nodes().forEach(function(n) {
let value = n[attribute] || 'unknown';
n.originalColor = valueColors[value];
if (!selectedNode) {
n.color = valueColors[value];
}
});
sigmaInstance.refresh();
// Update color legend
updateColorLegend(valueColors);
}
// Update color legend
function updateColorLegend(valueColors) {
let legendHTML = '';
for (let value in valueColors) {
let color = valueColors[value];
if (typeof color === 'object') {
color = color.color;
}
legendHTML += '<div class="legenditem"><span class="legendcolor" style="background-color: ' + color + '"></span>' + value + '</div>';
}
$('#colorLegend').html(legendHTML);
}
// Search nodes by term
function searchNodes(term) {
let results = [];
let lowerTerm = term.toLowerCase();
sigmaInstance.graph.nodes().forEach(function(n) {
if ((n.label && n.label.toLowerCase().indexOf(lowerTerm) >= 0) ||
(n.id && n.id.toLowerCase().indexOf(lowerTerm) >= 0)) {
results.push(n);
}
});
// Limit to top 10 results
results = results.slice(0, 10);
// Display results
let resultsHTML = '';
if (results.length > 0) {
results.forEach(function(n) {
resultsHTML += '<a href="#" data-node-id="' + n.id + '">' + (n.label || n.id) + '</a>';
});
} else {
resultsHTML = '<div>No results found</div>';
}
$('.results').html(resultsHTML);
// Set up click event for results
$('.results a').click(function(e) {
e.preventDefault();
let nodeId = $(this).data('node-id');
let node = sigmaInstance.graph.nodes(nodeId);
if (node) {
// Center the camera on the node
sigmaInstance.camera.goTo({
x: node[sigmaInstance.camera.readPrefix + 'x'],
y: node[sigmaInstance.camera.readPrefix + 'y'],
ratio: 0.5
});
// Select the node
selectedNode = node;
displayNodeDetails(node);
}
});
}