feat: add Obsidian-style graph visualization

Implement force-directed graph view for visualizing note connections:

- Add graph data model parsing [[wiki-style links]]
- Implement Fruchterman-Reingold layout algorithm
- Create character-based canvas renderer with highlights
- Add interactive filtering by tag/folder
- Support navigation (h/j/k/l), zoom (+/-), and node selection
- New commands: :IdeaGraph, :IdeaGraphFilter

New files:
- lua/ideaDrop/ui/graph/{init,types,data,layout,renderer}.lua

Updated documentation in README.md, CHANGELOG.md, and llms.txt
This commit is contained in:
2026-01-10 23:02:40 -05:00
parent de8ccfb9aa
commit 937f20b892
12 changed files with 2261 additions and 14 deletions

View File

@@ -115,12 +115,15 @@ M.ICONS = {
SEARCH = "🔍",
TAG = "🏷️",
TREE = "🌳",
GRAPH = "🕸️",
SUCCESS = "",
ERROR = "",
WARNING = "⚠️",
INFO = "",
SAVE = "💾",
REFRESH = "🔄",
NODE = "",
LINK = "",
}
-- Key mappings (default)
@@ -129,6 +132,78 @@ M.DEFAULT_KEYMAPS = {
REFRESH_FILE = "<C-r>",
CLOSE_WINDOW = "q",
SELECT_ITEM = "<CR>",
-- Graph keymaps
GRAPH_CLOSE = "q",
GRAPH_SELECT = "<CR>",
GRAPH_FILTER_TAG = "t",
GRAPH_FILTER_FOLDER = "f",
GRAPH_RESET_FILTER = "r",
GRAPH_TOGGLE_LABELS = "l",
GRAPH_CENTER = "c",
GRAPH_ZOOM_IN = "+",
GRAPH_ZOOM_OUT = "-",
}
-- Graph visualization settings
M.GRAPH_SETTINGS = {
-- Layout algorithm parameters
LAYOUT = {
-- Fruchterman-Reingold parameters
REPULSION_STRENGTH = 5000, -- How strongly nodes repel each other
ATTRACTION_STRENGTH = 0.01, -- Spring constant for connected nodes
IDEAL_EDGE_LENGTH = 50, -- Ideal distance between connected nodes
GRAVITY = 0.1, -- Pull toward center
DAMPING = 0.85, -- Velocity damping per iteration
MIN_VELOCITY = 0.01, -- Stop threshold
MAX_ITERATIONS = 300, -- Maximum layout iterations
COOLING_RATE = 0.95, -- Temperature cooling per iteration
INITIAL_TEMPERATURE = 100, -- Initial movement freedom
},
-- Visual settings
VISUAL = {
NODE_CHAR = "", -- Character for nodes
NODE_CHAR_SMALL = "", -- Character for small nodes
EDGE_CHAR_H = "", -- Horizontal edge
EDGE_CHAR_V = "", -- Vertical edge
EDGE_CHAR_DR = "", -- Down-right corner
EDGE_CHAR_DL = "", -- Down-left corner
EDGE_CHAR_UR = "", -- Up-right corner
EDGE_CHAR_UL = "", -- Up-left corner
EDGE_CHAR_CROSS = "", -- Crossing edges
EDGE_CHAR_SIMPLE = "·", -- Simple edge dot
MIN_NODE_SIZE = 1, -- Minimum node visual size
MAX_NODE_SIZE = 3, -- Maximum node visual size (based on degree)
LABEL_MAX_LENGTH = 20, -- Maximum label length
PADDING = 2, -- Canvas padding
},
-- Window settings
WINDOW = {
WIDTH_RATIO = 0.8, -- Window width as ratio of editor
HEIGHT_RATIO = 0.8, -- Window height as ratio of editor
BORDER = "rounded",
TITLE = " 🕸️ Graph View ",
},
-- Colors (highlight group names)
COLORS = {
NODE_DEFAULT = "IdeaDropGraphNode",
NODE_SELECTED = "IdeaDropGraphNodeSelected",
NODE_ORPHAN = "IdeaDropGraphNodeOrphan",
NODE_HIGH_DEGREE = "IdeaDropGraphNodeHighDegree",
EDGE = "IdeaDropGraphEdge",
LABEL = "IdeaDropGraphLabel",
BACKGROUND = "IdeaDropGraphBackground",
FILTER_ACTIVE = "IdeaDropGraphFilterActive",
},
-- Node size thresholds (degree-based)
NODE_DEGREE_THRESHOLDS = {
SMALL = 2, -- 0-2 connections = small
MEDIUM = 5, -- 3-5 connections = medium
-- > 5 = large
},
}
-- Notification messages
@@ -146,6 +221,12 @@ M.MESSAGES = {
NO_ACTIVE_FILE = "❌ No active idea file. Open an idea first.",
PROVIDE_TAG = "❌ Please provide a tag name",
PROVIDE_QUERY = "❌ Please provide a search query",
GRAPH_BUILDING = "🕸️ Building graph...",
GRAPH_LAYOUT = "🕸️ Computing layout for %d nodes...",
GRAPH_COMPLETE = "🕸️ Graph ready: %d nodes, %d edges",
GRAPH_REFRESHED = "🕸️ Graph refreshed",
GRAPH_NO_NODES = "🕸️ No notes found to visualize",
GRAPH_NO_SELECTION = "🕸️ No node selected",
}
return M