diff --git a/CHANGELOG.md b/CHANGELOG.md index e7fc7ce..b3bee53 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **[Viewer]**: The graph view now honours the active filters + - It drew every record and every edge regardless of the facets, contradicting the "N of M records" count above it + - A placeholder node stays while the record referencing it survives the filter + - An empty result now says "No ADRs match the current filters" instead of "No relationships to display" + ### Added - **[Docs]**: Add comprehensive dependencies reference documentation diff --git a/templates/app.js b/templates/app.js index c3a321f..0c360ee 100644 --- a/templates/app.js +++ b/templates/app.js @@ -736,6 +736,49 @@ }); } + // The graph is built once over every record, while the facets narrow the list. Without this + // the graph keeps drawing the whole estate after a filter, contradicting the record count. + function filteredGraph() { + var visible = {}; + state.filteredRecords.forEach(function(adr) { + visible[adr.id] = true; + }); + + var placeholder = {}; + state.graph.nodes.forEach(function(node) { + if (node.title === undefined) { + placeholder[node.id] = true; + } + }); + + // A placeholder stands for a reference to a record outside the collection, so it has no + // facets of its own. Keep it while the record pointing at it survives the filter. + var edges = state.graph.edges.filter(function(edge) { + var source = visible[edge.source] === true; + var target = visible[edge.target] === true; + + return (source && target) + || (source && placeholder[edge.target] === true) + || (target && placeholder[edge.source] === true); + }); + + var keep = {}; + Object.keys(visible).forEach(function(id) { + keep[id] = true; + }); + edges.forEach(function(edge) { + keep[edge.source] = true; + keep[edge.target] = true; + }); + + return { + nodes: state.graph.nodes.filter(function(node) { + return keep[node.id] === true; + }), + edges: edges + }; + } + function renderGraphView() { var canvas = elements.graphCanvas; var ctx = canvas.getContext('2d'); @@ -746,21 +789,29 @@ ctx.clearRect(0, 0, canvas.width, canvas.height); - if (state.graph.nodes.length === 0) { + var graph = filteredGraph(); + + if (graph.nodes.length === 0) { ctx.fillStyle = getComputedStyle(document.documentElement).getPropertyValue('--color-text-muted'); ctx.font = '14px sans-serif'; ctx.textAlign = 'center'; - ctx.fillText('No relationships to display', canvas.width / 2, canvas.height / 2); + ctx.fillText( + state.filteredRecords.length === 0 + ? 'No ADRs match the current filters' + : 'No relationships to display', + canvas.width / 2, + canvas.height / 2 + ); return; } // Simple force-directed layout simulation - var nodes = state.graph.nodes.map(function(n, i) { + var nodes = graph.nodes.map(function(n, i) { return { id: n.id, status: n.status, - x: canvas.width / 2 + Math.cos(i * 2 * Math.PI / state.graph.nodes.length) * 200, - y: canvas.height / 2 + Math.sin(i * 2 * Math.PI / state.graph.nodes.length) * 200, + x: canvas.width / 2 + Math.cos(i * 2 * Math.PI / graph.nodes.length) * 200, + y: canvas.height / 2 + Math.sin(i * 2 * Math.PI / graph.nodes.length) * 200, radius: 20 }; }); @@ -772,7 +823,7 @@ ctx.strokeStyle = getComputedStyle(document.documentElement).getPropertyValue('--color-border'); ctx.lineWidth = 1; - state.graph.edges.forEach(function(edge) { + graph.edges.forEach(function(edge) { var source = nodeMap[edge.source]; var target = nodeMap[edge.target]; if (source && target) {