visual enhancements

This commit is contained in:
overcuriousity 2025-09-15 00:25:27 +02:00
parent 62470673fe
commit 2410e689b8
2 changed files with 95 additions and 32 deletions

View File

@ -373,8 +373,8 @@ input[type="text"]:focus, select:focus {
padding: 0.75rem; padding: 0.75rem;
font-family: 'Roboto Mono', monospace; font-family: 'Roboto Mono', monospace;
font-size: 0.8rem; font-size: 0.8rem;
max-height: 40%; /*max-height: 40%;
overflow-y: auto; overflow-y: auto;*/
display: flex; display: flex;
gap: 1.5rem; gap: 1.5rem;
} }

View File

@ -15,6 +15,7 @@ class GraphManager {
this.contextMenu = null; this.contextMenu = null;
this.history = []; this.history = [];
this.filterPanel = null; this.filterPanel = null;
this.trueRootIds = new Set();
this.options = { this.options = {
nodes: { nodes: {
@ -363,6 +364,8 @@ class GraphManager {
this.nodes.update(processedNodes); this.nodes.update(processedNodes);
this.edges.update(processedEdges); this.edges.update(processedEdges);
// After data is loaded, compute roots and apply filters
this.computeTrueRoots();
this.updateFilterControls(); this.updateFilterControls();
this.applyAllFilters(); this.applyAllFilters();
@ -935,27 +938,55 @@ class GraphManager {
}; };
} }
computeTrueRoots() {
this.trueRootIds.clear();
const allNodes = this.nodes.get({ returnType: 'Object' });
const allEdges = this.edges.get();
const inDegrees = {};
for (const nodeId in allNodes) {
inDegrees[nodeId] = 0;
}
allEdges.forEach(edge => {
if (inDegrees[edge.to] !== undefined) {
inDegrees[edge.to]++;
}
});
for (const nodeId in allNodes) {
if (inDegrees[nodeId] === 0) {
this.trueRootIds.add(nodeId);
}
}
console.log("Computed true roots:", this.trueRootIds);
}
updateFilterControls() { updateFilterControls() {
if (!this.filterPanel) return; if (!this.filterPanel) return;
const nodeTypes = new Set(this.nodes.get().map(n => n.type)); const nodeTypes = new Set(this.nodes.get().map(n => n.type));
const edgeTypes = new Set(this.edges.get().map(e => e.metadata.relationship_type)); const edgeTypes = new Set(this.edges.get().map(e => e.metadata.relationship_type));
let nodeCheckboxes = '<div class="filter-column"><h4>Nodes</h4><div class="checkbox-group">'; // Wrap both columns in a single container with vertical layout
let filterHTML = '<div class="filter-container">';
// Nodes section
filterHTML += '<div class="filter-column"><h4>Nodes</h4><div class="checkbox-group">';
nodeTypes.forEach(type => { nodeTypes.forEach(type => {
const label = type === 'correlation_object' ? 'latent correlations' : type; const label = type === 'correlation_object' ? 'latent correlations' : type;
const isChecked = type !== 'correlation_object'; const isChecked = type !== 'correlation_object';
nodeCheckboxes += `<label><input type="checkbox" data-filter-type="node" value="${type}" ${isChecked ? 'checked' : ''}> ${label}</label>`; filterHTML += `<label><input type="checkbox" data-filter-type="node" value="${type}" ${isChecked ? 'checked' : ''}> ${label}</label>`;
}); });
nodeCheckboxes += '</div></div>'; filterHTML += '</div></div>';
let edgeCheckboxes = '<div class="filter-column"><h4>Edges</h4><div class="checkbox-group">'; // Edges section
filterHTML += '<div class="filter-column"><h4>Edges</h4><div class="checkbox-group">';
edgeTypes.forEach(type => { edgeTypes.forEach(type => {
edgeCheckboxes += `<label><input type="checkbox" data-filter-type="edge" value="${type}" checked> ${type}</label>`; filterHTML += `<label><input type="checkbox" data-filter-type="edge" value="${type}" checked> ${type}</label>`;
}); });
edgeCheckboxes += '</div></div>'; filterHTML += '</div></div>';
this.filterPanel.innerHTML = nodeCheckboxes + edgeCheckboxes; filterHTML += '</div>'; // Close filter-container
this.filterPanel.innerHTML = filterHTML;
this.filterPanel.querySelectorAll('input[type="checkbox"]').forEach(checkbox => { this.filterPanel.querySelectorAll('input[type="checkbox"]').forEach(checkbox => {
checkbox.addEventListener('change', () => this.applyAllFilters()); checkbox.addEventListener('change', () => this.applyAllFilters());
@ -963,6 +994,10 @@ class GraphManager {
} }
applyAllFilters() { applyAllFilters() {
console.log("Applying all filters (robust orphan detection)...");
if (this.nodes.length === 0) return;
// 1. Get filter criteria from the UI
const hiddenNodeTypes = new Set(); const hiddenNodeTypes = new Set();
this.filterPanel.querySelectorAll('input[data-filter-type="node"]:not(:checked)').forEach(cb => { this.filterPanel.querySelectorAll('input[data-filter-type="node"]:not(:checked)').forEach(cb => {
hiddenNodeTypes.add(cb.value); hiddenNodeTypes.add(cb.value);
@ -973,32 +1008,60 @@ class GraphManager {
hiddenEdgeTypes.add(cb.value); hiddenEdgeTypes.add(cb.value);
}); });
const nodeUpdates = []; // 2. Build adjacency list for the visible part of the graph
const edgeUpdates = []; const adj = {};
const visibleEdges = new Set(); this.nodes.getIds().forEach(id => adj[id] = []);
this.edges.forEach(edge => {
this.edges.get().forEach(edge => { if (!hiddenEdgeTypes.has(edge.metadata.relationship_type)) {
const isVisible = !hiddenEdgeTypes.has(edge.metadata.relationship_type); adj[edge.from].push(edge.to);
edgeUpdates.push({ id: edge.id, hidden: !isVisible });
if (isVisible) {
visibleEdges.add(edge.id);
} }
}); });
this.nodes.get().forEach(node => { // 3. Traverse from "true roots" to find all reachable nodes
let isVisible = !hiddenNodeTypes.has(node.type); const reachableNodes = new Set();
if (isVisible) { const queue = [];
const connectedEdges = this.network.getConnectedEdges(node.id);
const hasVisibleConnection = connectedEdges.some(edgeId => visibleEdges.has(edgeId)); // Start the traversal from true roots that aren't hidden by type
if (!hasVisibleConnection && connectedEdges.length > 0) { this.trueRootIds.forEach(rootId => {
isVisible = false; const node = this.nodes.get(rootId);
if (node && !hiddenNodeTypes.has(node.type)) {
if (!reachableNodes.has(rootId)) {
queue.push(rootId);
reachableNodes.add(rootId);
} }
} }
nodeUpdates.push({ id: node.id, hidden: !isVisible });
}); });
this.edges.update(edgeUpdates); let head = 0;
while (head < queue.length) {
const u = queue[head++];
for (const v of (adj[u] || [])) {
if (!reachableNodes.has(v)) {
const node = this.nodes.get(v);
if (node && !hiddenNodeTypes.has(node.type)) {
reachableNodes.add(v);
queue.push(v);
}
}
}
}
// 4. Create final node and edge visibility updates
const nodeUpdates = this.nodes.map(node => ({
id: node.id,
hidden: !reachableNodes.has(node.id)
}));
const edgeUpdates = this.edges.map(edge => ({
id: edge.id,
hidden: hiddenEdgeTypes.has(edge.metadata.relationship_type) || !reachableNodes.has(edge.from) || !reachableNodes.has(edge.to)
}));
this.nodes.update(nodeUpdates); this.nodes.update(nodeUpdates);
this.edges.update(edgeUpdates);
console.log(`Filters applied. Reachable nodes: ${reachableNodes.size}`);
} }