# graf
A terminal-based force-directed graph visualizer for markdown wikilinks. Run `graf` in any directory and see your markdown files as an interactive, pannable, zoomable network graph.
# DISCLAIMER
`graf` is originally meant to be a feature for my main project `clin-rs`. When this project is fully developed it will be merged with the `clin-rs` project as a "graph view" feature. Currently **all the big features are implemented** and only the testing, bugfixing phase remains. So please create a issue for any problem you encounter using `graf`!
# Plans
- [X] Custom themes
- [X] Hot reloading the config
> Part of **[clin-rs](https://github.com/reekta92/clin-rs)**
# Showcase
https://github.com/user-attachments/assets/de06ffda-a1f6-4317-9cd2-f7a222c13f18
- - -
## Features
**Link Parsing**
- **Wikilinks** — extracts `[[Title]]` and `[[Title|Display Text]]` patterns from file content.
- **Frontmatter** — reads YAML `title:` and `tags: []` from `---`-delimited blocks.
- **Title resolution** — fallback chain: frontmatter `title:` → first `# heading` → filename stem. Links are resolved by case-insensitive matching against titles.
**Search**
- Press `f` to open a popup that fuzzy-matches against node titles, relative file paths, and tags (case-insensitive). Navigate results with arrows or Tab, press Enter to jump.
**Configuration**
- **3-layer override** — defaults → TOML config file (`~/.config/graf/config.toml`) → CLI arguments → `GRAF_*` environment variables.
- **11 built-in themes** — default, Tokyo Night, Catppuccin Mocha, One Dark, Gruvbox, Dracula, Nord, Rose Pine, Everforest, Kanagawa, Solarized.
- **Hex color overrides** — per-element (`node_color`, `edge_color`, `label_color`, etc.) on top of any theme.
- **Background modes** — `transparent` (passes through terminal transparency) or `solid` (theme background color).
**Overlays**
- **Minimap** - toggleable minimap that shows the entire canvas.
- **Status bar** — template-based with variables: `{files}`, `{links}`, `{selected}`, `{date}`, `{time}`, `{size}`, `{ratio}`.
- **Legend** — shows tag or folder color mapping; configurable position and max items.
- **Grid overlay** — configurable number of divisions per axis.
- **Help overlay** — press `?` for a quick reference of all controls.
- - -
## Installation
### Debian, Fedora, Arch Linux
Refer to the [releases](https://github.com/reekta92/graf/releases) page for the latest release.
### Cargo
```bash
cargo install graf-rs
```
### From source
```bash
git clone
cd graf
cargo install --path .
```
#### Dependencies
- Rust 1.85+ (Edition 2024)
- - -
## Usage
Navigate to any directory containing markdown files and run:
```bash
cd ~/docs/my-wiki
graf
```
`graf` recursively scans the current working directory for `*.md` and `*.mdx` files, parses `[[wikilinks]]` and YAML frontmatter tags, then renders a force-directed graph in your terminal.
Double-click a node or press `Enter` to open the linked file. The editor is read from the `EDITOR` environment variable (defaults to `vim`).
```bash
EDITOR=nvim graf
```
- - -
## Controls
### Keyboard
| Key | Action |
|-----|--------|
| `↑` `↓` `←` `→` / `k` `j` `h` `l` | Move between nodes |
| `+` `=` / `Ctrl+J` | Zoom in |
| `-` / `Ctrl+K` | Zoom out |
| `Enter` | Open selected file in editor |
| `a` | Auto-fit view to all nodes |
| `f` | Activate search |
| `r` | Refresh file scan |
| `Shift+M` | Toggle minimap |
| `Shift+L` | Toggle legend |
| `Shift+G` | Toggle grid |
| `Shift+S` | Toggle status bar |
| `?` | Toggle help overlay |
| `q` / `Esc` | Quit |
### Mouse
| Action | Effect |
|--------|--------|
| Scroll wheel | Zoom in/out |
| Click & drag background | Pan view (natural: drag up → view goes down) |
| Click & drag node | Reposition node |
| Single click | Select node |
| Double-click | Open file in editor |
| Double-click empty area | Deselect node |
### Search
Press `f` to activate the search popup. Search matches against node titles, file paths (relative), and tags (case-insensitive).
### Search controls
| Key | Action |
|-----|--------|
| `Enter` | Select result and jump to node |
| `↑` `↓` | Navigate results |
| `Tab` / `Shift+Tab` | Cycle forward/backward |
| `Esc` | Close search |
| `Ctrl+A` / `Ctrl+E` | Move to start/end of query |
| `Ctrl+U` | Clear entire query |
| `Ctrl+W` | Delete word backward |
- - -
## Configuration
Config file location: `~/.config/graf/config.toml`
If the file doesn't exist, `graf` uses all defaults. Every setting is optional — only include the ones you want to override.
### Full example
```toml
[visual]
theme = "catppuccin-mocha"
background = "transparent"
node_color_mode = "tag"
edge_color_mode = "source"
label_mode = "selected"
label_max_length = 20
node_size = 2.0
node_size_mode = "link_count"
edge_thickness = 1
show_legend = true
show_grid = false
show_minimap = true
minimap_position = "bottom_right"
minimap_width = 30
minimap_height = 12
canvas_marker = "braille"
node_shape = "circle"
label_offset = 4.0
grid_divisions = 10
[visual.colors]
node_color = "#ff6600"
edge_color = "#333333"
label_color = "#aaaaaa"
selection_ring_color = "#ffffff"
border_color = "#555555"
grid_color = "#222222"
[physics]
ideal_distance = 80.0
damping = 0.95
max_iterations = 800
gravity = 0.01
cooling = true
prevent_overlapping = true
timestep = 0.016
thread_sleep_ms = 16
[interaction]
double_click_ms = 300
zoom_factor = 1.15
drag_sensitivity = 0.5
auto_fit_padding = 1.4
drag_scale = 200.0
[display]
show_status_bar = true
status_format = "Files: {files} | Links: {links} | Selected: {selected}"
border_style = "rounded"
border_title = "graf"
[filter]
exclude_tags = ["draft", "private"]
exclude_patterns = ["*.bak", "archive/*"]
min_links = 0
max_nodes = 500
[legend]
position = "top_right"
max_items = 10
[search]
max_results = 20
max_visible = 10
popup_width = 50
popup_y = 3
cursor_glyph = "▎"
[editor]
command = ""
```
### `[visual]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `theme` | string | `"default"` | Color scheme preset (see **Themes** below) |
| `background` | string | `"transparent"` | `"transparent"` passes through terminal transparency, `"solid"` fills with theme background color |
| `node_color_mode` | string | `"tag"` | How nodes are colored: `"tag"`, `"folder"`, `"link_count"`, `"uniform"` |
| `edge_color_mode` | string | `"source"` | Edge color: `"source"`, `"target"`, `"uniform"` |
| `label_mode` | string | `"selected"` | Which labels to show: `"selected"`, `"neighbors"`, `"all"`, `"none"` |
| `label_max_length` | int | `20` | Max characters for title labels (1–60) |
| `node_size` | float | `2.0` | Base node radius (1–5) |
| `node_size_mode` | string | `"fixed"` | `"fixed"` constant size, `"link_count"` scales with connections |
| `edge_thickness` | int | `1` | Line thickness for edges (1–3) |
| `show_legend` | bool | `true` | Show tag legend |
| `show_grid` | bool | `false` | Show background grid overlay |
| `show_minimap` | bool | `true` | Show minimap in corner |
| `minimap_position` | string | `"top_right"` | Minimap position: `"top_right"`, `"top_left"`, `"bottom_right"`, `"bottom_left"` |
| `minimap_width` | int | `30` | Minimap width in columns |
| `minimap_height` | int | `12` | Minimap height in rows |
| `canvas_marker` | string | `"braille"` | Canvas rendering marker: `"braille"`, `"halfblock"`, `"dot"` |
| `minimap_marker` | string | `"braille"` | Minimap rendering marker: `"braille"`, `"halfblock"`, `"dot"` |
| `node_shape` | string | `"circle"` | Node shape: `"circle"`, `"square"`, `"diamond"` |
| `label_offset` | float | `4.0` | Distance between node edge and label |
| `grid_divisions` | int | `10` | Number of grid lines per axis (2–50) |
### `[visual.colors]`
Optional hex color overrides applied on top of the selected theme. All fields are optional.
| Key | Type | Description |
|-----|------|-------------|
| `node_color` | hex string | Override all node colors (e.g. `"#ff6600"`) |
| `edge_color` | hex string | Override edge color |
| `label_color` | hex string | Override label text color |
| `selection_ring_color` | hex string | Override selection ring color |
| `border_color` | hex string | Override border, legend border, and minimap border |
| `title_color` | hex string | Override title color |
| `grid_color` | hex string | Override grid line color |
| `legend_text_color` | hex string | Override legend text color |
| `status_bar_color` | hex string | Override status bar text color |
| `background_color` | hex string | Override canvas and minimap background color |
### `[physics]`
Controls the force-directed layout simulation.
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `ideal_distance` | float | `80.0` | Target distance between connected nodes |
| `damping` | float | `0.95` | Velocity damping per step (lower = faster settling) |
| `max_iterations` | int | `800` | Maximum simulation steps before stopping |
| `gravity` | float | `0.01` | Center pull to prevent drift |
| `cooling` | bool | `true` | Whether simulation cools down over time |
| `prevent_overlapping` | bool | `true` | Prevent nodes from overlapping |
| `timestep` | float | `0.016` | Simulation timestep (lower = smoother but slower) |
| `thread_sleep_ms` | int | `16` | Milliseconds between simulation steps (~60fps) |
### `[interaction]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `double_click_ms` | int | `300` | Time window in milliseconds to register a double-click |
| `zoom_factor` | float | `1.15` | Zoom multiplier per scroll or key press |
| `drag_sensitivity` | float | `0.5` | Mouse drag speed multiplier |
| `auto_fit_padding` | float | `1.4` | Padding multiplier when auto-fitting view (higher = more zoomed out) |
| `drag_scale` | float | `200.0` | Internal scaling factor for background drag panning |
### `[display]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `show_status_bar` | bool | `true` | Show the status bar at the bottom |
| `status_format` | string | `"Files: {files} \| Links: {links} \| Selected: {selected}"` | Status bar text template. Variables: `{files}`, `{links}`, `{selected}`, `{date}`, `{time}`, `{size}`, `{ratio}` |
| `border_style` | string | `"rounded"` | Border style: `"plain"`, `"rounded"`, `"double"`, `"none"` |
| `border_title` | string | `"graf"` | Window title. Supports `{cwd}` variable (current directory name) |
### `[filter]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `exclude_tags` | array | `[]` | Tags to exclude from the graph |
| `exclude_patterns` | array | `[]` | Glob patterns for files to exclude |
| `min_links` | int | `0` | Only show nodes with at least this many connections |
| `max_nodes` | int | `500` | Maximum nodes to display (`0` = unlimited) |
### `[legend]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `position` | string | `"top_right"` | Legend position: `"top_right"`, `"top_left"`, `"bottom_right"`, `"bottom_left"` |
| `max_items` | int | `10` | Maximum tag groups to show in the legend |
### `[search]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `max_results` | int | `20` | Maximum search results to return |
| `max_visible` | int | `10` | Maximum results visible without scrolling |
| `popup_width` | int | `50` | Search popup width in columns |
| `popup_y` | int | `3` | Vertical position of search popup from top |
| `cursor_glyph` | string | `"▎"` | Cursor character in search input |
### `[editor]`
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `command` | string | `""` | Editor command. Falls back to `$EDITOR` env var, then `vim` |
- - -
## CLI Arguments
```
graf [OPTIONS]
Options:
-d, --dir Directory to scan [default: current]
-c, --config Path to config file
--theme Theme preset
--max-nodes Maximum nodes to display
--exclude Exclude glob patterns (repeatable)
--exclude-tags Exclude tags (comma-separated)
--node-color-mode Node color mode
--edge-color-mode Edge color mode
--label-mode Label display mode
--labels Show all labels (shorthand for --label-mode all)
--no-status Hide status bar
--grid Show grid
--no-minimap Hide minimap
--no-legend Hide legend
--background Background style
--border-style