- Implement @DEP_IN/OUT/RUN tag standard for MATLAB script metadata - Add buildDependencyGraph.m for full project mapping and yEd (.tgf) export - Add plotSingleFileDependencies.m and plotNodeHistory.m for targeted analysis - Standardize output naming convention to 'Dependency_*' for Finder organization - Configure high-resolution PNG export as default static fallback
217 lines
8.0 KiB
Matlab
217 lines
8.0 KiB
Matlab
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 |