From 484648b64808ff55fb6cb2a7ba2d586c2985864d Mon Sep 17 00:00:00 2001 From: ThorBarRa Date: Sun, 7 Dec 2025 16:05:59 +0100 Subject: [PATCH] Added a function to export figure data into ascii txt files (and to plot ascii text files to check if export worked) --- utils/exportFigureData.m | 281 +++++++++++++++++++++++++++++++++++++++ utils/plotExportedData.m | 151 +++++++++++++++++++++ 2 files changed, 432 insertions(+) create mode 100644 utils/exportFigureData.m create mode 100644 utils/plotExportedData.m diff --git a/utils/exportFigureData.m b/utils/exportFigureData.m new file mode 100644 index 0000000..e49e303 --- /dev/null +++ b/utils/exportFigureData.m @@ -0,0 +1,281 @@ +function exportFigureData(figHandle) +%exportFigureData Export line and scatter plot data to ASCII files +% +% This function extracts x and y data from all line and scatter plots in a +% figure (including tiled layouts) and saves each data series to a separate +% ASCII file. File names are constructed from the figure title, tile title +% (if in a tiled layout), axis labels, and legend entries. Data is limited +% to the current x-axis scale range. +% +% SYNTAX: +% exportFigureData() % Uses current active figure +% exportFigureData(figHandle) % Uses specified figure handle +% +% INPUTS: +% figHandle : figure handle (optional) +% If empty or omitted, the current active figure (gcf) is used. +% +% OUTPUT: +% ASCII files are saved in the current working directory, named as: +% ___vs__.txt +% For non-tiled figures: +% __vs__.txt +% Each file contains two columns: X-data and Y-data (tab-delimited) +% +% NOTES: +% - Works with line plots (line objects) and scatter plots (scatter objects) +% - Handles both regular axes and tiled layouts +% - Data outside the x-axis scale limits are excluded +% - Lines without legend entries are named as 'line_N' where N is a counter +% - Scatter plots without legend entries are named as 'scatter_N' +% - Axis labels default to 'x' and 'y' if not set +% - Tile titles are included only for tiled layouts +% - If no figure title is set, 'figure' is used as the base name +% +% EXAMPLE: +% % Create a tiled figure with multiple subplots +% figure +% tiledlayout(2, 1, 'TileSpacing', 'compact') +% +% nexttile +% x = 0:0.1:10; +% plot(x, sin(x), 'DisplayName', 'sin(x)') +% hold on +% plot(x, cos(x), 'DisplayName', 'cos(x)') +% title('Sine and Cosine') +% xlabel('Time (s)') +% ylabel('Amplitude') +% legend +% +% nexttile +% plot(x, sin(x).*cos(x), 'DisplayName', 'sin*cos') +% title('Product') +% xlabel('Time (s)') +% ylabel('Value') +% legend +% +% sgtitle('Trigonometric Functions') +% exportFigureData(gcf) +% +% % Creates files like: +% % Trigonometric_Functions_Sine_and_Cosine_Amplitude_vs_Time_(s)_sin(x).txt +% % Trigonometric_Functions_Sine_and_Cosine_Amplitude_vs_Time_(s)_cos(x).txt +% % Trigonometric_Functions_Product_Value_vs_Time_(s)_sin*cos.txt +% +% See also: tiledlayout, nexttile, legend, line, scatter + +% Handle input: use current figure if none provided +if nargin == 0 || isempty(figHandle) + figHandle = gcf; +end + +% Get figure title (use sgtitle if available, else Name) +figTitle = ''; +sgtitleObjs = findall(figHandle, 'type', 'text', 'Tag', 'sgtitle'); +if ~isempty(sgtitleObjs) + figTitle = get(sgtitleObjs(1), 'String'); + if iscell(figTitle) + figTitle = figTitle{1}; + end +end +if isempty(figTitle) + figTitle = get(figHandle, 'Name'); +end +if isempty(figTitle) + figTitle = 'figure'; +end +figTitle = sanitizeFilename(figTitle); % Sanitize figure title + +% Get all axes in the figure (works for both regular and tiled layouts) +allAxes = findall(figHandle, 'type', 'axes'); +if isempty(allAxes) + warning('exportFigureData:NoAxes', 'No axes found in figure.'); + return; +end + +% Counters for unnamed line and scatter objects +lineCounter = 0; +scatterCounter = 0; + +% Loop over all axes +for ii = 1:length(allAxes) + ax = allAxes(ii); + + % Skip legend axes and colorbar axes + if strcmp(ax.Tag, 'legend') || strcmp(ax.Tag, 'colorbar') + continue + end + + % Get axis titles + xLabel = ax.XLabel.String; + yLabel = ax.YLabel.String; + tileTitle = ax.Title.String; + + % Convert to cell if char, then handle empty cases + if ischar(xLabel), xLabel = cellstr(xLabel); end + if ischar(yLabel), yLabel = cellstr(yLabel); end + if ischar(tileTitle), tileTitle = cellstr(tileTitle); end + + if isempty(xLabel) || (iscell(xLabel) && isempty(xLabel{1})) + xLabel = 'x'; + else + xLabel = xLabel{1}; + end + + if isempty(yLabel) || (iscell(yLabel) && isempty(yLabel{1})) + yLabel = 'y'; + else + yLabel = yLabel{1}; + end + + if isempty(tileTitle) || (iscell(tileTitle) && isempty(tileTitle{1})) + tileTitle = ''; + else + tileTitle = tileTitle{1}; + end + + % Sanitize axis labels and tile title + xLabel = sanitizeFilename(xLabel); + yLabel = sanitizeFilename(yLabel); + tileTitle = sanitizeFilename(tileTitle); + + % Get x-axis limits for filtering + xLims = ax.XLim; + + % Get all children of the axes (lines, scatter, etc.) + children = get(ax, 'Children'); + + % Build a legend object if it exists (for extracting labels) + legendObj = legend(ax); + legendLabels = {}; + if ~isempty(legendObj) + legendLabels = legendObj.String; + if ischar(legendLabels) + legendLabels = {legendLabels}; + end + end + + % Initialize a counter for matching legend entries to graphics objects + graphicsIndex = 0; + + % Loop over all child graphics objects + for jj = 1:length(children) + obj = children(jj); + + % Check if this is a line object + if isa(obj, 'matlab.graphics.chart.primitive.Line') + graphicsIndex = graphicsIndex + 1; + + % Get x and y data + xData = get(obj, 'XData'); + yData = get(obj, 'YData'); + + % Skip if empty + if isempty(xData) || isempty(yData) + continue + end + + % Filter data to x-axis range + mask = (xData >= xLims(1)) & (xData <= xLims(2)); + xData = xData(mask); + yData = yData(mask); + + % Determine label: use legend entry or auto-generate name + if graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex}) + label = legendLabels{graphicsIndex}; + else + lineCounter = lineCounter + 1; + label = sprintf('line_%d', lineCounter); + end + + % Sanitize label for filename + label = sanitizeFilename(label); + + % Create filename with tile title if present + if ~isempty(tileTitle) + fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label); + else + fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label); + end + + % Write data to ASCII file + data = [xData(:), yData(:)]; + writematrix(data, fileName, 'Delimiter', '\t'); + fprintf('Saved: %s\n', fileName); + + % Check if this is a scatter object + elseif isa(obj, 'matlab.graphics.chart.primitive.Scatter') + graphicsIndex = graphicsIndex + 1; + + % Get x and y data + xData = get(obj, 'XData'); + yData = get(obj, 'YData'); + + % Skip if empty + if isempty(xData) || isempty(yData) + continue + end + + % Ensure row vectors for indexing + xData = xData(:)'; + yData = yData(:)'; + + % Filter data to x-axis range + mask = (xData >= xLims(1)) & (xData <= xLims(2)); + xData = xData(mask); + yData = yData(mask); + + % Determine label: use legend entry or auto-generate name + if graphicsIndex <= length(legendLabels) && ~isempty(legendLabels{graphicsIndex}) + label = legendLabels{graphicsIndex}; + else + scatterCounter = scatterCounter + 1; + label = sprintf('scatter_%d', scatterCounter); + end + + % Sanitize label for filename + label = sanitizeFilename(label); + + % Create filename with tile title if present + if ~isempty(tileTitle) + fileName = sprintf('%s_%s_%s_vs_%s_%s.txt', figTitle, tileTitle, yLabel, xLabel, label); + else + fileName = sprintf('%s_%s_vs_%s_%s.txt', figTitle, yLabel, xLabel, label); + end + + % Write data to ASCII file + data = [xData(:), yData(:)]; + writematrix(data, fileName, 'Delimiter', '\t'); + fprintf('Saved: %s\n', fileName); + end + end +end + +fprintf('Data export complete.\n'); +end + +% ========================================================================= +% Helper function: sanitize strings for use in filenames +% ========================================================================= +function cleanStr = sanitizeFilename(str) +%sanitizeFilename Remove or replace characters invalid in filenames +% +% Replaces spaces, slashes, backslashes, colons, asterisks, parentheses, +% and other problematic characters with underscores. + +cleanStr = str; +cleanStr = strrep(cleanStr, ' ', '_'); +cleanStr = strrep(cleanStr, '/', '_'); +cleanStr = strrep(cleanStr, '\', '_'); +cleanStr = strrep(cleanStr, ':', '_'); +cleanStr = strrep(cleanStr, '*', '_'); +cleanStr = strrep(cleanStr, '?', '_'); +cleanStr = strrep(cleanStr, '"', '_'); +cleanStr = strrep(cleanStr, '<', '_'); +cleanStr = strrep(cleanStr, '>', '_'); +cleanStr = strrep(cleanStr, '|', '_'); +cleanStr = strrep(cleanStr, '(', '_'); +cleanStr = strrep(cleanStr, ')', '_'); +cleanStr = strrep(cleanStr, '.', '_'); % Optional: replace dots + +end diff --git a/utils/plotExportedData.m b/utils/plotExportedData.m new file mode 100644 index 0000000..7826b3d --- /dev/null +++ b/utils/plotExportedData.m @@ -0,0 +1,151 @@ +function plotExportedData(dataDirectory) +%plotExportedData Plot all exported ASCII data files in one figure +% +% This function reads all .txt files from the exported data (created by +% exportFigureData) and plots them in a single figure for visual comparison. +% File names are parsed to extract axis labels and legend entries. +% +% SYNTAX: +% plotExportedData() % Uses current working directory +% plotExportedData(dataDirectory) % Uses specified directory path +% +% INPUTS: +% dataDirectory : char or string (optional) +% Path to directory containing exported .txt files. +% If omitted, uses current working directory (pwd). +% +% OUTPUT: +% A new figure with all data plotted as lines. Axis labels and legend +% entries are reconstructed from the file names. +% +% NOTES: +% - Reads all .txt files in the specified directory +% - File name format expected: _[_]_vs__.txt +% - Plots are created with underscores converted back to spaces for display +% - X and Y axis labels are set based on parsed filename +% - Legend shows line labels from filenames +% - All data is plotted in a single axes for direct visual comparison +% +% EXAMPLE: +% % After running exportFigureData(): +% plotExportedData() +% % or +% plotExportedData('../exported_data/') + +% Handle input: use current directory if none provided +if nargin == 0 || isempty(dataDirectory) + dataDirectory = pwd; +end + +% Get all .txt files in the directory +fileStruct = dir(fullfile(dataDirectory, '*.txt')); +if isempty(fileStruct) + warning('plotExportedData:NoFiles', 'No .txt files found in %s', dataDirectory); + return; +end + +% Create a new figure +fig = figure('Name', 'Exported Data Comparison', 'NumberTitle', 'off'); +ax = axes(fig); +hold(ax, 'on'); +grid(ax, 'on'); + +% Storage for axis labels (to ensure consistency) +xLabelStr = ''; +yLabelStr = ''; +legendEntries = {}; + +% Loop over all .txt files +for ii = 1:length(fileStruct) + fileName = fileStruct(ii).name; + filePath = fullfile(dataDirectory, fileName); + + % Read the data + try + data = readmatrix(filePath); + catch ME + warning('plotExportedData:ReadFailed', 'Could not read file: %s\n%s', fileName, ME.message); + continue; + end + + % Check if data has at least 2 columns + if size(data, 2) < 2 || size(data, 1) < 1 + warning('plotExportedData:InvalidData', 'File %s does not have valid X,Y columns', fileName); + continue; + end + + xData = data(:, 1); + yData = data(:, 2); + + % Parse filename to extract labels + % Expected format: _[_]_vs__.txt + + % Remove .txt extension + nameNoExt = fileName(1:end-4); + + % Split by '_vs_' to separate y-axis label from the rest + parts = strsplit(nameNoExt, '_vs_'); + + if length(parts) >= 2 + % Extract line label (after _vs__) + rightPart = parts{2}; % _ + + % Find the last underscore to separate XLabel from LineLabel + underscores = find(rightPart == '_'); + if ~isempty(underscores) + lastUnderscore = underscores(end); + xLabelExtracted = rightPart(1:lastUnderscore-1); + lineLabel = rightPart(lastUnderscore+1:end); + else + xLabelExtracted = rightPart; + lineLabel = 'data'; + end + + % Extract y-axis label (before _vs_) + yLabelExtracted = parts{1}; + + % Remove figure title (first part before first underscore) + % This is tricky, so we'll keep what we have and note it's approximate + + % Convert underscores back to spaces for display + xLabelDisplay = strrep(xLabelExtracted, '_', ' '); + yLabelDisplay = strrep(yLabelExtracted, '_', ' '); + lineLabel = strrep(lineLabel, '_', ' '); + + % Store axis labels (use the first valid ones found) + if isempty(xLabelStr) + xLabelStr = xLabelDisplay; + end + if isempty(yLabelStr) + yLabelStr = yLabelDisplay; + end + + else + % Fallback if filename parsing fails + lineLabel = nameNoExt; + end + + % Plot the data + plot(ax, xData, yData, 'DisplayName', lineLabel, 'LineWidth', 1.5, 'Marker', 'o', 'MarkerSize', 4); + + % Store legend entry + legendEntries{end+1} = lineLabel; +end + +% Set axis labels +xlabel(ax, xLabelStr, 'Interpreter', 'none'); +ylabel(ax, yLabelStr, 'Interpreter', 'none'); +title(ax, 'Exported Data Comparison', 'Interpreter', 'none'); + +% Add legend +if ~isempty(legendEntries) + legend(ax, legendEntries, 'Interpreter', 'none', 'Location', 'best'); +end + +% Optimize figure appearance +grid(ax, 'on'); +hold(ax, 'off'); + +fprintf('Plotted %d data files from: %s\n', length(fileStruct), dataDirectory); + +end