function G = buildDependencyGraph(scanPath, outputFile) % BUILDDEPENDENCYGRAPH Scans MATLAB files for custom dependency tags and builds a directed graph. % % USAGE: % G = buildDependencyGraph(); % G = buildDependencyGraph(scanPath); % G = buildDependencyGraph(scanPath, outputFile); % % DEPENDENCY TAGS: % % @DEP_IN: filename.mat % % @DEP_OUT: filename.mat % % @DEP_RUN: scriptname.m % % % ========================================================================= % SCRIPT OUTPUTS & VISUALIZATION OPTIONS % ========================================================================= % 1. MATLAB Figure: A standard MATLAB figure window will automatically % open displaying the dependency graph. % % 2. Static Image (PNG): A high-resolution (300 DPI) PNG file is saved to % your current directory (e.g., 'ProjectDependencies.png'). This is % ideal for quick viewing, sharing, or inserting into reports. % % 3. Interactive Graph (yEd Graph Editor): For complex networks where you % can drag, auto-layout, and isolate nodes: % a) Find the generated text file (e.g., 'Dependency_Graph.tgf'). % b) Open the free yEd Graph Editor app (macOS/Windows/Linux). % c) Go to File -> Open and select the .tgf file. % d) To arrange the messy graph, go to the top menu: % Layout -> Hierarchical (or press Cmd+Option+H). % % ========================================================================= % COMPANION FUNCTIONS % ========================================================================= % Once the main graph (G) is built, you can pass it to these additional % functions to inspect specific files in detail: % % plotSingleFileDependencies(G, 'FileName.mat'); % -> Extracts and plots only the immediate "neighborhood" (the scripts % that directly create it, and the scripts that directly read it). % % plotNodeHistory(G, 'FileName.fig'); % -> Traces the data provenance. It plots the entire upstream chain % of all scripts and data files that ultimately led to this file. % ========================================================================= % ========================================================================= % HOW TO UNTANGLE THE GRAPH IN yEd (First-time Open) % ========================================================================= % When you first open the .tgf file, the nodes will be in a messy pile. % Follow these 3 steps to instantly format the dependency tree: % % 1. UNTANGLE: Go to Layout -> Hierarchical (or press Cmd+Option+H). % This arranges the scripts and data files in a logical flow. % % 2. FIX BOX SIZES: Press Cmd+A to select all, then go to: % Tools -> Fit Node to Label... -> Click OK. % This resizes the boxes to fit your file names perfectly. % % 3. VIEW ALL: Click the 'Fit Content' icon (magnifying glass with square) % in the top toolbar to center the graph on your screen. % % You can now click and drag any node; the arrows will follow automatically. % ========================================================================= % Handle optional input arguments if nargin < 1 || isempty(scanPath) scanPath = pwd; end if nargin < 2 || isempty(outputFile) outputFile = 'Dependency_MasterGraph.png'; end % 1. Find scripts in the main directory scriptFiles = dir(fullfile(scanPath, '*.m')); for i = 1:length(scriptFiles) scriptFiles(i).relPath = scriptFiles(i).name; end % 2. Find subdirectories (excluding '.' and '..') allDirs = dir(scanPath); subDirs = allDirs([allDirs.isdir] & ~startsWith({allDirs.name}, '.')); % 3. Scan 1 level deep into subdirectories for i = 1:length(subDirs) subDirName = subDirs(i).name; subFolderPath = fullfile(scanPath, subDirName); subFiles = dir(fullfile(subFolderPath, '*.m')); for j = 1:length(subFiles) subFiles(j).relPath = [subDirName '/' subFiles(j).name]; end if ~isempty(subFiles) if isempty(scriptFiles) scriptFiles = subFiles; else scriptFiles = [scriptFiles; subFiles]; end end end % 4. Initialize Dependency Lists sources = {}; targets = {}; % 5. Extract Tags for i = 1:length(scriptFiles) scriptName = scriptFiles(i).relPath; if contains(scriptName, 'buildDependencyGraph.m'), continue; end actualPath = fullfile(scriptFiles(i).folder, scriptFiles(i).name); code = fileread(actualPath); inputs = regexp(code, '@DEP_IN:\s*([^\n\r]+)', 'tokens'); outputs = regexp(code, '@DEP_OUT:\s*([^\n\r]+)', 'tokens'); runs = regexp(code, '@DEP_RUN:\s*([^\n\r]+)', 'tokens'); for k = 1:length(inputs) depName = strrep(strtrim(inputs{k}{1}), '\', '/'); sources{end+1} = depName; targets{end+1} = scriptName; end for k = 1:length(outputs) depName = strrep(strtrim(outputs{k}{1}), '\', '/'); sources{end+1} = scriptName; targets{end+1} = depName; end for k = 1:length(runs) depName = strrep(strtrim(runs{k}{1}), '\', '/'); sources{end+1} = scriptName; targets{end+1} = depName; end end if isempty(sources) disp('No @DEP_ tags found in the scanned files.'); G = digraph(); return; end G = digraph(sources, targets); % --------------------------------------------------------- % 6. Construct Node Colors Based on File Extension % --------------------------------------------------------- numNodes = numnodes(G); nodeColors = zeros(numNodes, 3); % Define standard colorblind-friendly colors colorScript = [0 0.4470 0.7410]; % Blue for .m scripts colorData = [0.8500 0.3250 0.0980]; % Orange for .mat data colorOther = [0.4660 0.6740 0.1880]; % Green for anything else (e.g. .csv) for n = 1:numNodes nodeName = G.Nodes.Name{n}; if endsWith(nodeName, '.m', 'IgnoreCase', true) nodeColors(n, :) = colorScript; elseif endsWith(nodeName, '.mat', 'IgnoreCase', true) nodeColors(n, :) = colorData; else nodeColors(n, :) = colorOther; end end % Plot the Graph fig = figure('Name', 'Project Dependencies', 'Color', 'w'); p = plot(G, 'Layout', 'layered', ... 'NodeLabel', G.Nodes.Name, ... 'Interpreter', 'none', ... 'NodeColor', nodeColors, ... % <-- Using the new color array here 'MarkerSize', 8, ... 'EdgeColor', [0.7 0.7 0.7], ... % Lightened edge color for visual clarity 'LineWidth', 1.5, ... 'ArrowSize', 12); p.NodeFontSize = 10; p.NodeFontWeight = 'bold'; title('Data and Script Dependency Flow','Interpreter', 'none'); % --------------------------------------------------------- % 7. Export % --------------------------------------------------------- % Change the output file extension outputFile = strrep(outputFile, '.svg', '.png'); % --- Export text for yEd Graph Editor (TGF Format) --- % 1. Create a unique list of all nodes allNodes = unique([sources, targets]); tgfFile = 'Dependency_MasterGraph.tgf'; fid = fopen(tgfFile, 'w'); % 2. Write the Node List (Format: ID Label) for i = 1:length(allNodes) fprintf(fid, '%d %s\n', i, allNodes{i}); end % 3. Write the Separator fprintf(fid, '#\n'); % 4. Write the Edge List (Format: SourceID TargetID) for i = 1:length(sources) srcIdx = find(strcmp(allNodes, sources{i})); tgtIdx = find(strcmp(allNodes, targets{i})); fprintf(fid, '%d %d\n', srcIdx, tgtIdx); end fclose(fid); disp('Generated Dependency_Graph.tgf for yEd Graph Editor.'); % Export as a 300 DPI high-resolution PNG exportgraphics(fig, outputFile, 'Resolution', 300, 'BackgroundColor', 'w'); fprintf('Dependency graph saved as %s\n', outputFile); end