-
Notifications
You must be signed in to change notification settings - Fork 0
Graph Construction
After parsing, the extracted dependencies and entities are assembled into a dependency graph using graphology.
Parsed Files ──→ buildGraph() ──→ enrichNodes() ──→ exportGraphToJson()
│ │ │
▼ ▼ ▼
Directed MGraph Communities + graph.json
(multi-graph) Colors + Sizes
Each parsed file produces up to three types of nodes:
Every source file becomes a primary graph node:
graph.addNode(filePath, {
label: basename(filePath),
dependencies: parsedData.dependencies,
entities: parsedData.entities,
language: detectedLanguage
})Classes, functions, and methods become secondary nodes connected to their parent file:
File: src/handler.js
├── ::Handler (class) ── contains ──▶ src/handler.js
├── ::validateToken (function)
├── ::handleRequest (method)
└── ::processData (method)
Entity nodes use :: notation in their key (e.g., src/handler.js::Handler). They're connected to the parent file via contains edges (rendered as dashed lines in the visualizer).
When a dependency can't be resolved to a local file, an external node is created:
graph.addNode('express', {
external: true,
npm: true, // detected via package.json
label: 'express',
community: 'dependencies'
})Dependency resolution:
-
Relative paths (
./,../) — resolve relative to the importing file's directory -
Bare imports (
express,lodash) — check if any local file matches; if not, create an external node -
npm detection — reads
package.jsonto identify which bare imports are npm packages
Each dependency becomes a directed edge between nodes:
graph.addEdge(sourceFile, resolvedTarget, {
relationship: 'imports'
})After building the raw graph, enrichNodes() applies community detection:
File-only nodes (excluding entities and external packages) are extracted into an undirected subgraph. Multi-edges become single edges.
The Louvain algorithm maximizes modularity — it groups files that are densely connected by dependency edges into communities.
import louvain from 'graphology-communities-louvain'
const communities = louvain(subgraph)Each community is named by its most frequent directory:
function nameCommunities(communityFileMap, commonRoot) {
// For each community, count files per directory
// Pick the directory with the most files
// Disambiguate duplicates with #1, #2 suffixes
}If two communities share the same dominant directory (e.g., both are src/utils), they get disambiguated as src/utils #1 and src/utils #2.
| Community Type | Color |
|---|---|
| File communities | 12-color palette (#4E79A7, #F28E2B, #E15759, ...) |
| External packages |
#2d6a4f (green), community: dependencies
|
| Entity nodes |
#6a2d6a (purple), inherit parent's community name |
node.size = clamp(degree, 5, 15) // Proportional to connection count
node.x = Math.random() * 100 // Initial positions for ForceAtlas2
node.y = Math.random() * 100
node.language = detectedLanguage // From file extension
node.label = basename(filePath) // Default display namedetectCycles() runs DFS on file-only nodes:
- Track visited nodes and current DFS path
- When a back-edge is found, record the cycle
- Rotate to canonical form for deduplication
- Cap at 200 cycles
See the detect command page for details.
| Type | Meaning | Visual |
|---|---|---|
imports |
File A depends on file B | Solid arrow |
contains |
A file contains a class/function | Dashed arrow |
| Type | Key Pattern | Attributes | Size |
|---|---|---|---|
| File | src/file.js |
label, community, color, language, dependencies | 5–15 |
| Entity | src/file.js::ClassName |
label, kind (class/function/method), community, color | 3 |
| External | express |
label, external: true, npm: boolean | 5–15 |