How to Trace Upstream and Downstream Reach in Archify
Archify traces upstream and downstream reach by performing a breadth-first search (BFS) through SVG edge relationships, using viewer/focus.js to compute traversal paths and viewer/route-probe.js for low-level graph utilities, then applies visual indicators via data attributes on DOM elements.
Archify is an open-source diagram visualization tool that enables developers to analyze node dependencies through bidirectional reachability tracing. Understanding how to trace upstream and downstream reach in Archify allows you to identify all ancestors or descendants of a selected node within directed graphs. The implementation relies on two core JavaScript modules that parse SVG edge data and execute deterministic graph traversal algorithms.
Core Architecture for Reachability Tracing
viewer/focus.js - Primary Reachability Controller
Located in viewer/focus.js, this module contains the main entry points for reachability analysis. The computeReachability(originId, direction, relationships) function initiates a breadth-first traversal from a specified origin node, while reachabilityFor(id, direction) provides a standalone computation interface without UI side effects. The applyReachability(direction, options) method handles the visual layer, painting traversal results onto the SVG canvas and updating interface counters.
viewer/route-probe.js - Graph Algorithms
The viewer/route-probe.js module supplies the underlying graph theory implementations reused by the focus controller. Key exports include reachableFrom(source), which returns a Boolean map of all downstream nodes; hopDistancesFrom(source), which calculates hop counts from the origin; and shortestDirectedPath(source, target), which computes optimal routes between specific nodes.
The Reachability Traversal Process
Relationship Discovery
The tracing begins when reachabilityRelationships() scans the DOM for all SVG edges marked with [data-edge-from] and [data-edge-to] attributes. This generates a flat array of relationship objects containing from, to, and key properties that define the directed graph structure.
BFS Execution Logic
The computeReachability function implements a queue-based breadth-first search. It initializes a depths Map with the origin node set to depth 0, then processes nodes iteratively:
- For downstream traversal, the algorithm follows edges where
relationship.from === current - For upstream traversal, the algorithm follows edges where
relationship.to === current
Each discovered node receives a depth value of depths[current] + 1, and the process continues until the queue empties.
Result Data Structure
Upon completion, the function returns a structured object:
{
direction, // 'upstream' or 'downstream'
originId, // Starting node identifier
nodeIds: order, // BFS-ordered array of visited IDs
edgeKeys, // Array of traversed edge identifiers
depths, // Map of node ID to hop count
maxDepth // Integer representing longest path
}
Visual Application and State Management
The applyReachability method consumes this result object to update the DOM. Nodes within the reachability set receive data-reach-match and data-reach-depth attributes, while the origin node gets data-reach-origin. Corresponding edges are tagged with data-reach-match attributes. The UI counters update via Math.max(0, result.nodeIds.length - 1) to exclude the origin from dependency counts.
The reachability state persists in the URL hash (e.g., #focus=nodeId&reach=upstream) and integrates with viewer/export.js to include traversal context in exported artifacts.
Practical Implementation Examples
Trace Downstream Reach from Current Selection
To visualize all descendants of the currently selected node:
// Retrieve active selection from Archify focus state
const nodeId = Archify.focus.activeIds[0];
// Apply downstream reachability visualization
Archify.focus.applyReachability('downstream');
// Capture shareable snapshot for URL persistence or export
const snapshot = Archify.focus.reachabilitySnapshot();
console.log(snapshot);
// Outputs: { direction: 'downstream', origin: {...}, nodeIds: [...], depths: {...}, maxDepth: n, edges: [...] }
Compute Upstream Reach Programmatically
For headless computation without DOM updates:
const reach = Archify.focus.reachabilityFor('myNodeId', 'upstream');
console.log(reach.nodeIds); // Array of ancestor IDs
console.log(reach.depths); // Map of hop distances from origin
Export Diagrams with Reach Overlay
To generate an SVG export that includes the reach visualization:
Archify.focus.applyReachability('upstream');
Archify.exportMenu.exportSvg({
reachSnapshot: Archify.focus.reachabilitySnapshot()
})
.then(blob => {
// Process exported SVG file containing visual reach indicators
});
Access Low-Level Graph Utilities
Import functions directly from viewer/route-probe.js for custom analysis:
import { reachableFrom, hopDistancesFrom } from './viewer/route-probe.js';
const downstreamMap = reachableFrom('myNodeId'); // { nodeId: true, ... }
const hopCounts = hopDistancesFrom('myNodeId'); // { nodeId: 0, ... }
Summary
- Bidirectional BFS: Archify uses breadth-first search in
viewer/focus.jsto trace both upstream (incoming edges) and downstream (outgoing edges) relationships from any origin node - Deterministic results: The
computeReachabilityfunction returns structured data including node IDs, edge keys, and hop depths for every traversal - Visual integration: The
applyReachabilitymethod synchronizes graph state with DOM attributes (data-reach-match,data-reach-depth) and updates UI counters excluding the origin node - Export support: Reachability snapshots serialize via
reachabilitySnapshot()for URL persistence (#focus=...&reach=upstream) and integration withviewer/export.js - Low-level access:
viewer/route-probe.jsprovides reusable graph algorithms includingreachableFromandhopDistancesFromfor custom tooling and testing scenarios
Frequently Asked Questions
How does Archify determine which nodes are reachable upstream versus downstream?
Archify distinguishes direction by inspecting edge relationships during BFS queue processing. For downstream reach, it follows edges where the current node matches the from property; for upstream reach, it follows edges where the current node matches the to property. This logic resides in the computeReachability function within viewer/focus.js.
Can I export the reachability analysis along with the diagram?
Yes. After calling Archify.focus.applyReachability(), invoke Archify.focus.reachabilitySnapshot() to capture the current state, then pass this object to Archify.exportMenu.exportSvg({ reachSnapshot: ... }). The exported SVG includes visual reach indicators and traversal metadata according to the implementation in viewer/export.js.
What is the maximum depth limit for reachability tracing?
The analysis uses standard BFS without artificial depth limits, constrained only by graph size and JavaScript execution context. The maxDepth property in the result object indicates the longest hop count found during traversal, representing the furthest dependency layer from the origin node.
How can I compute reachability without updating the UI?
Use the reachabilityFor(id, direction) method in viewer/focus.js instead of applyReachability(). This returns the raw traversal data—including node IDs, depths, and edge keys—without modifying DOM attributes or triggering visual updates, suitable for background analysis or automated testing scenarios.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →