|
|
|
let sigmaInstance; |
|
let graph; |
|
let filter; |
|
let config = {}; |
|
let greyColor = '#ccc'; |
|
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 } |
|
}; |
|
|
|
|
|
$(document).ready(function() { |
|
console.log("Document ready, initializing 🤗 Daily Papers Atlas"); |
|
|
|
|
|
$('#attributepane').css('display', 'none'); |
|
|
|
|
|
$.getJSON('config.json', function(data) { |
|
console.log("Configuration loaded:", data); |
|
config = data; |
|
document.title = config.text.title || 'Daily Papers Atlas'; |
|
$('#title').text(config.text.title || 'Daily Papers Atlas'); |
|
|
|
|
|
|
|
|
|
if (config.data && !config.data.startsWith('data/')) { |
|
config.data = 'data/' + config.data; |
|
} |
|
|
|
|
|
$('#grey-edges-toggle').prop('checked', config.features?.useGreyEdges || false); |
|
|
|
loadGraph(); |
|
}).fail(function(jqXHR, textStatus, errorThrown) { |
|
console.error("Failed to load config:", textStatus, errorThrown); |
|
}); |
|
|
|
|
|
$('#search-input').on('input', function(e) { |
|
let searchTerm = $(this).val(); |
|
if (searchTerm.length > 2) { |
|
searchNodes(searchTerm); |
|
} else { |
|
$('.results').empty(); |
|
} |
|
}); |
|
|
|
|
|
$('#search-input').keypress(function(e) { |
|
if (e.which === 13) { |
|
let searchTerm = $(this).val(); |
|
if (searchTerm.length > 0) { |
|
searchNodes(searchTerm); |
|
} |
|
} |
|
}); |
|
|
|
|
|
$('#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(); |
|
} |
|
}); |
|
|
|
|
|
$('.returntext').click(function() { |
|
nodeNormal(); |
|
}); |
|
|
|
|
|
$('#filter-select').change(function() { |
|
let filterValue = $(this).val(); |
|
filterByNodeType(filterValue); |
|
}); |
|
|
|
|
|
setTimeout(function() { |
|
updateLegend(); |
|
}, 500); |
|
}); |
|
|
|
|
|
function loadGraph() { |
|
console.log("Loading graph data from:", config.data); |
|
|
|
|
|
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 { |
|
|
|
const uint8Array = new Uint8Array(arrayBuffer); |
|
const decompressed = pako.inflate(uint8Array, { to: 'string' }); |
|
|
|
|
|
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 { |
|
|
|
$.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.'); |
|
}); |
|
} |
|
} |
|
|
|
|
|
function initializeGraph(data) { |
|
graph = data; |
|
console.log("Initializing graph with nodes:", graph.nodes.length, "edges:", graph.edges.length); |
|
|
|
try { |
|
|
|
sigmaInstance = sigma.init(document.getElementById('sigma-canvas')); |
|
|
|
console.log("Sigma instance created:", sigmaInstance); |
|
|
|
if (!sigmaInstance) { |
|
console.error("Failed to create sigma instance"); |
|
return; |
|
} |
|
|
|
|
|
sigmaInstance.mouseProperties({ |
|
maxRatio: 32, |
|
minRatio: 0.5, |
|
mouseEnabled: true, |
|
mouseInertia: 0.8 |
|
}); |
|
|
|
console.log("Sigma mouse properties configured"); |
|
|
|
|
|
console.log("Adding nodes to sigma instance..."); |
|
for (let i = 0; i < graph.nodes.length; i++) { |
|
let node = graph.nodes[i]; |
|
|
|
|
|
if (!node.type && node.id && node.id.includes('_')) { |
|
const idParts = node.id.split('_'); |
|
if (idParts.length >= 2 && ['paper', 'author', 'organization'].includes(idParts[0])) { |
|
node.type = idParts[0]; |
|
console.log(`Detected type from ID: ${node.id} → ${node.type}`); |
|
} |
|
} |
|
|
|
let nodeColor = node.color || (node.type && config.nodeTypes && config.nodeTypes[node.type] ? |
|
config.nodeTypes[node.type].color : nodeTypes[node.type]?.color || '#666'); |
|
|
|
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: nodeColor, |
|
type: node.type |
|
}); |
|
|
|
|
|
if (i < 3) { |
|
console.log("Added node:", node.id, "with type:", node.type); |
|
} |
|
} |
|
|
|
|
|
console.log("Adding edges to sigma instance..."); |
|
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 |
|
|
|
}); |
|
} |
|
|
|
|
|
sigmaInstance.drawingProperties({ |
|
labelThreshold: 3000, |
|
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', |
|
edgeColor: 'default', |
|
defaultEdgeColor: '#ccc' |
|
}); |
|
|
|
console.log("Edge color mode: solid grey"); |
|
|
|
|
|
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 |
|
}); |
|
|
|
|
|
sigmaInstance.draw(); |
|
|
|
console.log("Graph data loaded into sigma instance"); |
|
|
|
|
|
initFilters(); |
|
|
|
|
|
updateLegend(); |
|
|
|
|
|
bindEvents(); |
|
|
|
console.log("Graph initialization complete"); |
|
|
|
} catch (e) { |
|
console.error("Error in initializeGraph:", e, e.stack); |
|
} |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
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); |
|
} |
|
} |
|
|
|
|
|
function bindEvents() { |
|
if (!sigmaInstance) { |
|
console.error("Sigma instance not found when binding events"); |
|
return; |
|
} |
|
|
|
console.log("Binding events to sigma instance"); |
|
|
|
|
|
sigmaInstance.bind('upnodes', function(event) { |
|
console.log("Node clicked event fired:", event); |
|
if (event.content && event.content.length > 0) { |
|
var nodeId = event.content[0]; |
|
console.log("Processing node click for node:", nodeId); |
|
|
|
sigmaInstance.isMouseDown = true; |
|
|
|
setTimeout(function() { |
|
nodeActive(nodeId); |
|
|
|
setTimeout(function() { |
|
sigmaInstance.isMouseDown = false; |
|
}, 10); |
|
}, 10); |
|
} |
|
}); |
|
|
|
|
|
sigmaInstance.bind('overnodes', function(event) { |
|
if (event.content && event.content.length > 0) { |
|
var nodeId = event.content[0]; |
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
if (n.id === nodeId) { |
|
|
|
n.forceLabel = true; |
|
|
|
|
|
if (sigmaInstance.detail && selectedNode && n.id !== selectedNode.id) { |
|
|
|
n.attr = n.attr || {}; |
|
n.attr.isHovered = true; |
|
} |
|
} |
|
}); |
|
|
|
sigmaInstance.draw(2, 2, 2, 2); |
|
} |
|
}); |
|
|
|
|
|
sigmaInstance.bind('outnodes', function(event) { |
|
|
|
if (event.content && event.content.length > 0) { |
|
var nodeId = event.content[0]; |
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
if (n.id === nodeId) { |
|
|
|
if (n.attr && n.attr.isHovered) { |
|
delete n.attr.isHovered; |
|
} |
|
|
|
|
|
if (sigmaInstance.detail) { |
|
if (selectedNode && n.id !== selectedNode.id) { |
|
n.forceLabel = false; |
|
} |
|
} else { |
|
|
|
n.forceLabel = false; |
|
} |
|
} |
|
}); |
|
} |
|
|
|
sigmaInstance.draw(2, 2, 2, 2); |
|
}); |
|
|
|
|
|
document.getElementById('sigma-canvas').addEventListener('click', function(evt) { |
|
|
|
if (sigmaInstance.detail && !sigmaInstance.isMouseDown) { |
|
|
|
setTimeout(function() { |
|
|
|
if (!sigmaInstance.isMouseDown) { |
|
console.log("Canvas clicked while in detail view - returning to full view"); |
|
nodeNormal(); |
|
} |
|
}, 100); |
|
} |
|
}); |
|
} |
|
|
|
|
|
function nodeActive(nodeId) { |
|
console.log("nodeActive called with id:", nodeId); |
|
|
|
if (!sigmaInstance) { |
|
console.error("Sigma instance not ready for nodeActive"); |
|
return; |
|
} |
|
|
|
if (sigmaInstance.detail && selectedNode && selectedNode.id === nodeId) { |
|
|
|
return; |
|
} |
|
|
|
|
|
nodeNormal(); |
|
|
|
|
|
var selected = null; |
|
sigmaInstance.iterNodes(function(n) { |
|
if (n.id == nodeId) { |
|
selected = n; |
|
} |
|
}); |
|
|
|
if (!selected) { |
|
console.error("Node not found:", nodeId); |
|
return; |
|
} |
|
|
|
|
|
console.log("Selected node structure:", selected); |
|
if (selected.id && selected.id.includes('_')) { |
|
console.log("ID parts:", selected.id.split('_')); |
|
} |
|
console.log("Node type from property:", selected.type); |
|
console.log("Node color:", selected.color); |
|
|
|
|
|
sigmaInstance.detail = true; |
|
|
|
|
|
selectedNode = selected; |
|
|
|
|
|
var neighbors = {}; |
|
sigmaInstance.iterEdges(function(e) { |
|
if (e.source == nodeId || e.target == nodeId) { |
|
|
|
const neighborId = e.source == nodeId ? e.target : e.source; |
|
|
|
neighbors[neighborId] = neighbors[neighborId] || {}; |
|
|
|
neighbors[neighborId].edgeLabel = e.label || ""; |
|
neighbors[neighborId].edgeColor = e.color; |
|
} |
|
}); |
|
|
|
|
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
n.attr = n.attr || {}; |
|
n.attr.originalColor = n.color; |
|
|
|
|
|
n.attr.originalForceLabel = n.forceLabel; |
|
|
|
if (n.id === nodeId) { |
|
|
|
n.attr.originalSize = n.size; |
|
const sizeFactor = config.highlighting?.selectedNodeSizeFactor ?? 1.5; |
|
n.size = n.size * sizeFactor; |
|
|
|
n.forceLabel = true; |
|
} else if (neighbors[n.id]) { |
|
|
|
n.forceLabel = false; |
|
} else if (!neighbors[n.id]) { |
|
|
|
|
|
n.attr.dimmed = true; |
|
|
|
var rgb = getRGBColor(n.color); |
|
const opacity = config.highlighting?.nodeOpacity ?? 0.2; |
|
n.color = 'rgba(' + rgb.r + ',' + rgb.g + ',' + rgb.b + ',' + opacity + ')'; |
|
|
|
n.forceLabel = false; |
|
} |
|
}); |
|
|
|
|
|
let debugCounts = { connected: 0, notConnected: 0 }; |
|
let edgeCount = 0; |
|
|
|
console.log("Starting edge processing for node:", nodeId); |
|
|
|
sigmaInstance.iterEdges(function(e) { |
|
edgeCount++; |
|
e.attr = e.attr || {}; |
|
|
|
|
|
if (typeof e.attr.originalColor === 'undefined') { |
|
e.attr.originalColor = e.color; |
|
|
|
} |
|
|
|
|
|
if (typeof e.attr.originalSize === 'undefined') { |
|
e.attr.originalSize = e.size || 1; |
|
} |
|
|
|
|
|
let sourceId, targetId; |
|
|
|
|
|
if (typeof e.source === 'object' && e.source !== null) { |
|
sourceId = e.source.id; |
|
} else { |
|
sourceId = String(e.source); |
|
} |
|
|
|
|
|
if (typeof e.target === 'object' && e.target !== null) { |
|
targetId = e.target.id; |
|
} else { |
|
targetId = String(e.target); |
|
} |
|
|
|
|
|
const selectedNodeId = String(nodeId); |
|
|
|
|
|
const isConnected = (sourceId === selectedNodeId || targetId === selectedNodeId); |
|
|
|
|
|
if (isConnected) { |
|
debugCounts.connected++; |
|
} else { |
|
debugCounts.notConnected++; |
|
} |
|
|
|
|
|
if (isConnected) { |
|
|
|
const sizeFactor = config.highlighting?.highlightedEdgeSizeFactor ?? 2; |
|
e.size = (e.attr.originalSize) * sizeFactor; |
|
|
|
|
|
} else { |
|
|
|
|
|
e.color = '#ededed'; |
|
e.size = e.attr.originalSize * 0.5; |
|
} |
|
}); |
|
|
|
console.log("Edge processing complete. Total edges:", edgeCount, "Connected:", debugCounts.connected, "Not connected:", debugCounts.notConnected); |
|
|
|
|
|
sigmaInstance.draw(2, 2, 2, 2); |
|
|
|
|
|
setTimeout(function() { |
|
console.log("Verifying edge colors after redraw:"); |
|
let colorCount = { original: 0, greyed: 0, other: 0 }; |
|
|
|
sigmaInstance.iterEdges(function(e) { |
|
if (e.color === '#ededed') { |
|
colorCount.greyed++; |
|
} else if (e.attr && e.attr.originalColor && e.color === e.attr.originalColor) { |
|
colorCount.original++; |
|
} else { |
|
colorCount.other++; |
|
} |
|
}); |
|
|
|
console.log("Edge color counts:", colorCount); |
|
}, 100); |
|
|
|
|
|
try { |
|
$('#attributepane') |
|
.show() |
|
.css({ |
|
'display': 'block', |
|
'visibility': 'visible', |
|
'opacity': '1' |
|
}); |
|
|
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
if (neighbors[n.id]) { |
|
neighbors[n.id].label = n.label || n.id; |
|
|
|
|
|
let nodeType = "unknown"; |
|
|
|
|
|
if (n.type) { |
|
nodeType = n.type; |
|
} |
|
|
|
else if (n.id && n.id.includes('_')) { |
|
const idParts = n.id.split('_'); |
|
if (idParts.length >= 2 && ['paper', 'author', 'organization'].includes(idParts[0])) { |
|
nodeType = idParts[0]; |
|
} |
|
} |
|
|
|
else if (n.color) { |
|
|
|
for (const type in config.nodeTypes) { |
|
if (config.nodeTypes[type].color === n.color) { |
|
nodeType = type; |
|
break; |
|
} |
|
} |
|
|
|
if (nodeType === "unknown") { |
|
for (const type in nodeTypes) { |
|
if (nodeTypes[type].color === n.color) { |
|
nodeType = type; |
|
break; |
|
} |
|
} |
|
} |
|
} |
|
|
|
neighbors[n.id].type = nodeType; |
|
neighbors[n.id].color = n.color; |
|
} |
|
}); |
|
|
|
|
|
var connectionList = []; |
|
|
|
var neighborsByType = {}; |
|
|
|
for (var id in neighbors) { |
|
var neighbor = neighbors[id]; |
|
if (neighbor) { |
|
|
|
neighborsByType[neighbor.type] = neighborsByType[neighbor.type] || []; |
|
|
|
neighborsByType[neighbor.type].push({ |
|
id: id, |
|
label: neighbor.label || id, |
|
color: neighbor.color |
|
}); |
|
} |
|
} |
|
|
|
|
|
|
|
let sortedTypes = Object.keys(neighborsByType).sort((a, b) => { |
|
if (a === 'unknown') return 1; |
|
if (b === 'unknown') return -1; |
|
return a.localeCompare(b); |
|
}); |
|
|
|
sortedTypes.forEach(function(type) { |
|
|
|
var typeColor = config.nodeTypes && config.nodeTypes[type] ? |
|
config.nodeTypes[type].color : |
|
nodeTypes[type]?.color || '#666'; |
|
|
|
|
|
console.log(`Found ${neighborsByType[type].length} neighbors of type: ${type}`); |
|
|
|
|
|
let typeName = type; |
|
if (type === 'unknown') { |
|
typeName = 'Other Connections'; |
|
} |
|
|
|
|
|
connectionList.push('<li class="connection-type-header" style="margin-top: 8px; margin-bottom: 5px; font-weight: bold; color: ' + typeColor + ';">' + |
|
(type === 'unknown' ? typeName : typeName.charAt(0).toUpperCase() + typeName.slice(1) + 's') + |
|
' (' + neighborsByType[type].length + '):</li>'); |
|
|
|
|
|
neighborsByType[type].forEach(function(neighbor) { |
|
|
|
let labelHint = ''; |
|
if (type === 'unknown' && neighbor.id && neighbor.id.includes('_')) { |
|
const idParts = neighbor.id.split('_'); |
|
if (idParts.length >= 2) { |
|
labelHint = ` (${idParts[0]})`; |
|
} |
|
} |
|
|
|
connectionList.push('<li><a href="#" data-node-id="' + neighbor.id + '" style="color: ' + typeColor + ';">' + |
|
neighbor.label + labelHint + '</a></li>'); |
|
}); |
|
}); |
|
|
|
|
|
$('.nodeattributes .name').text(selected.label || selected.id); |
|
|
|
|
|
console.log("Selected node:", selected); |
|
console.log("Node properties:"); |
|
for (let prop in selected) { |
|
console.log(`- ${prop}: ${selected[prop]}`); |
|
} |
|
|
|
|
|
let nodeType = null; |
|
|
|
|
|
if (selected.id && selected.id.includes('_')) { |
|
const idParts = selected.id.split('_'); |
|
if (idParts.length >= 2) { |
|
nodeType = idParts[0]; |
|
console.log("Extracted type from ID:", nodeType); |
|
} |
|
} |
|
|
|
else if (selected.type) { |
|
nodeType = selected.type; |
|
console.log("Node has type directly:", selected.type); |
|
} else if (selected.attr && selected.attr.type) { |
|
nodeType = selected.attr.type; |
|
console.log("Node has type in attr:", selected.attr.type); |
|
} |
|
|
|
|
|
if (nodeType) { |
|
nodeType = nodeType.charAt(0).toUpperCase() + nodeType.slice(1); |
|
$('.nodeattributes .nodetype').text('Type: ' + nodeType).show(); |
|
} else { |
|
$('.nodeattributes .nodetype').hide(); |
|
} |
|
|
|
|
|
let dataHTML = ''; |
|
if (typeof selected.degree !== 'undefined') { |
|
dataHTML = '<div><strong>Degree:</strong> ' + selected.degree + '</div>'; |
|
} |
|
|
|
if (dataHTML === '') dataHTML = '<div>No additional attributes</div>'; |
|
$('.nodeattributes .data').html(dataHTML); |
|
|
|
|
|
$('.nodeattributes .link ul') |
|
.html(connectionList.length ? connectionList.join('') : '<li>No connections</li>') |
|
.css('display', 'block'); |
|
|
|
|
|
$('.nodeattributes .link ul li a').click(function(e) { |
|
e.preventDefault(); |
|
var nextNodeId = $(this).data('node-id'); |
|
nodeActive(nextNodeId); |
|
}); |
|
|
|
} catch (e) { |
|
console.error("Error updating attribute pane:", e); |
|
} |
|
} |
|
|
|
|
|
function nodeNormal() { |
|
console.log("nodeNormal called"); |
|
|
|
if (!sigmaInstance || !sigmaInstance.detail) { |
|
|
|
return; |
|
} |
|
|
|
sigmaInstance.detail = false; |
|
|
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
n.attr = n.attr || {}; |
|
|
|
|
|
if (typeof n.attr.originalColor !== 'undefined') { |
|
n.color = n.attr.originalColor; |
|
delete n.attr.originalColor; |
|
} |
|
|
|
|
|
if (typeof n.attr.originalSize !== 'undefined') { |
|
n.size = n.attr.originalSize; |
|
delete n.attr.originalSize; |
|
} |
|
|
|
|
|
|
|
n.forceLabel = false; |
|
delete n.attr.originalForceLabel; |
|
|
|
|
|
delete n.attr.dimmed; |
|
}); |
|
|
|
|
|
sigmaInstance.iterEdges(function(e) { |
|
e.attr = e.attr || {}; |
|
|
|
if (typeof e.attr.originalColor !== 'undefined') { |
|
e.color = e.attr.originalColor; |
|
delete e.attr.originalColor; |
|
} |
|
|
|
if (typeof e.attr.originalSize !== 'undefined') { |
|
e.size = e.attr.originalSize; |
|
delete e.attr.originalSize; |
|
} |
|
}); |
|
|
|
|
|
selectedNode = null; |
|
|
|
|
|
$('#attributepane').css({ |
|
'display': 'none', |
|
'visibility': 'hidden' |
|
}); |
|
|
|
|
|
sigmaInstance.draw(2, 2, 2, 2); |
|
|
|
|
|
try { |
|
if (typeof forceEdgeColors === 'function') { |
|
forceEdgeColors(); |
|
} |
|
} catch (e) { |
|
console.error("Error refreshing edge colors:", e); |
|
} |
|
} |
|
|
|
|
|
function getRGBColor(color) { |
|
|
|
if (color.charAt(0) === '#') { |
|
var r = parseInt(color.substr(1, 2), 16); |
|
var g = parseInt(color.substr(3, 2), 16); |
|
var b = parseInt(color.substr(5, 2), 16); |
|
return { r: r, g: g, b: b }; |
|
} |
|
|
|
else if (color.startsWith('rgb')) { |
|
var parts = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); |
|
if (parts) { |
|
return { |
|
r: parseInt(parts[1], 10), |
|
g: parseInt(parts[2], 10), |
|
b: parseInt(parts[3], 10) |
|
}; |
|
} |
|
} |
|
|
|
|
|
return { r: 100, g: 100, b: 100 }; |
|
} |
|
|
|
|
|
function searchNodes(term) { |
|
if (!sigmaInstance) return; |
|
|
|
let results = []; |
|
let lowerTerm = term.toLowerCase(); |
|
|
|
sigmaInstance.iterNodes(function(n) { |
|
if ((n.label && n.label.toLowerCase().indexOf(lowerTerm) >= 0) || |
|
(n.id && n.id.toLowerCase().indexOf(lowerTerm) >= 0)) { |
|
results.push(n); |
|
} |
|
}); |
|
|
|
|
|
results = results.slice(0, 10); |
|
|
|
|
|
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); |
|
|
|
|
|
$('.results a').click(function(e) { |
|
e.preventDefault(); |
|
let nodeId = $(this).data('node-id'); |
|
nodeActive(nodeId); |
|
}); |
|
} |
|
|
|
|
|
function updateLegend() { |
|
console.log("Updating legend with node types"); |
|
|
|
|
|
let typesToShow = config.nodeTypes || nodeTypes; |
|
|
|
|
|
let legendHTML = ''; |
|
|
|
|
|
for (let type in typesToShow) { |
|
if (typesToShow.hasOwnProperty(type)) { |
|
let typeConfig = typesToShow[type]; |
|
let color = typeConfig.color || '#ccc'; |
|
|
|
legendHTML += `<div class="legend-item"> |
|
<div class="legend-color" style="background-color: ${color};"></div> |
|
<div class="legend-label">${type}</div> |
|
</div>`; |
|
} |
|
} |
|
|
|
|
|
legendHTML += `<div class="legend-item"> |
|
<div class="legend-line" style="background-color: #ccc;"></div> |
|
<div class="legend-label">Edge (Solid Grey)</div> |
|
</div>`; |
|
|
|
|
|
$('#colorLegend').html(legendHTML); |
|
} |