321 lines
9.7 KiB
Matlab
321 lines
9.7 KiB
Matlab
function exportLevelScatterTikz(sourceAx, pngFile, tikzFile, pngTikzPath, options)
|
|
%EXPORTLEVELSCATTERTIKZ Export showLevelScatter axes as PNG-backed TikZ.
|
|
% Rasterizes scatter objects into a PNG layer and writes average lines plus
|
|
% text annotations as PGFPlots objects.
|
|
|
|
arguments
|
|
sourceAx
|
|
pngFile (1,1) string
|
|
tikzFile (1,1) string
|
|
pngTikzPath (1,1) string
|
|
options.resolutionDpi (1,1) double {mustBePositive, mustBeInteger} = 150
|
|
options.xLabel (1,1) string = "Time in $\mu$s"
|
|
options.yLabel (1,1) string = "Normalized Amplitude"
|
|
options.generatedBy (1,1) string = "exportLevelScatterTikz.m"
|
|
end
|
|
|
|
outputDir = fileparts(pngFile);
|
|
if ~exist(outputDir, "dir")
|
|
mkdir(outputDir);
|
|
end
|
|
|
|
exportScatterLayerPng(sourceAx, pngFile, options.resolutionDpi);
|
|
|
|
linePlots = collectTikzLinePlots(sourceAx);
|
|
textAnnotations = collectTikzTextAnnotations(sourceAx);
|
|
writeTikzPngAxis(tikzFile, pngTikzPath, sourceAx, linePlots, textAnnotations, options);
|
|
end
|
|
|
|
function exportScatterLayerPng(sourceAx, pngFile, resolutionDpi)
|
|
sourceFig = ancestor(sourceAx, "figure");
|
|
xLimits = sourceAx.XLim;
|
|
yLimits = sourceAx.YLim;
|
|
scatterHandles = findGraphicsObjectsByType(sourceAx, "scatter");
|
|
|
|
exportFig = figure( ...
|
|
"Visible", "off", ...
|
|
"Color", "white", ...
|
|
"Units", sourceFig.Units, ...
|
|
"Position", sourceFig.Position);
|
|
cleanupFigure = onCleanup(@() close(exportFig));
|
|
|
|
exportAx = copyobj(sourceAx, exportFig);
|
|
exportAx.Units = "normalized";
|
|
exportAx.Position = [0 0 1 1];
|
|
exportAx.XLim = xLimits;
|
|
exportAx.YLim = yLimits;
|
|
exportAx.XLimMode = "manual";
|
|
exportAx.YLimMode = "manual";
|
|
stripAxesToScatterOnly(exportAx);
|
|
hideAxesInkForRasterExport(exportAx);
|
|
|
|
fprintf("Exporting scatter PNG with XLim=[%g %g], YLim=[%g %g], scatter objects=%d: %s\n", ...
|
|
xLimits(1), xLimits(2), yLimits(1), yLimits(2), numel(scatterHandles), pngFile);
|
|
drawnow;
|
|
exportFig.PaperPositionMode = "auto";
|
|
print(exportFig, char(pngFile), "-dpng", sprintf("-r%d", resolutionDpi));
|
|
end
|
|
|
|
function stripAxesToScatterOnly(ax)
|
|
plotObjects = findall(ax);
|
|
for objIdx = 1:numel(plotObjects)
|
|
obj = plotObjects(objIdx);
|
|
if obj == ax || ~isvalid(obj)
|
|
continue
|
|
end
|
|
|
|
objType = string(get(obj, "Type"));
|
|
if lower(objType) ~= "scatter"
|
|
delete(obj);
|
|
end
|
|
end
|
|
end
|
|
|
|
function hideAxesInkForRasterExport(ax)
|
|
ax.Visible = "on";
|
|
ax.Color = "white";
|
|
ax.Box = "off";
|
|
ax.XGrid = "off";
|
|
ax.YGrid = "off";
|
|
ax.XMinorGrid = "off";
|
|
ax.YMinorGrid = "off";
|
|
ax.XTick = [];
|
|
ax.YTick = [];
|
|
ax.XColor = "white";
|
|
ax.YColor = "white";
|
|
ax.Title.String = "";
|
|
ax.XLabel.String = "";
|
|
ax.YLabel.String = "";
|
|
end
|
|
|
|
function handles = findGraphicsObjectsByType(parentHandle, objectType)
|
|
allHandles = findall(parentHandle);
|
|
matches = false(size(allHandles));
|
|
for handleIdx = 1:numel(allHandles)
|
|
currentHandle = allHandles(handleIdx);
|
|
if ~isvalid(currentHandle)
|
|
continue
|
|
end
|
|
|
|
matches(handleIdx) = lower(string(get(currentHandle, "Type"))) == lower(string(objectType));
|
|
end
|
|
|
|
handles = allHandles(matches);
|
|
end
|
|
|
|
function linePlots = collectTikzLinePlots(sourceAx)
|
|
lineHandles = findGraphicsObjectsByType(sourceAx, "line");
|
|
lineHandles = flipud(lineHandles(:));
|
|
|
|
linePlots = repmat(struct( ...
|
|
"xData", [], ...
|
|
"yData", [], ...
|
|
"color", [], ...
|
|
"lineWidth", []), numel(lineHandles), 1);
|
|
validLineCount = 0;
|
|
|
|
for lineIdx = 1:numel(lineHandles)
|
|
lineHandle = lineHandles(lineIdx);
|
|
xData = lineHandle.XData(:);
|
|
yData = lineHandle.YData(:);
|
|
validSamples = isfinite(xData) & isfinite(yData);
|
|
|
|
if ~any(validSamples)
|
|
continue
|
|
end
|
|
|
|
validLineCount = validLineCount + 1;
|
|
linePlots(validLineCount).xData = xData(validSamples);
|
|
linePlots(validLineCount).yData = yData(validSamples);
|
|
linePlots(validLineCount).color = lineHandle.Color;
|
|
linePlots(validLineCount).lineWidth = lineHandle.LineWidth;
|
|
end
|
|
|
|
linePlots = linePlots(1:validLineCount);
|
|
end
|
|
|
|
function textAnnotations = collectTikzTextAnnotations(sourceAx)
|
|
textHandles = findGraphicsObjectsByType(sourceAx, "text");
|
|
textHandles = flipud(textHandles(:));
|
|
xLimits = sourceAx.XLim;
|
|
yLimits = sourceAx.YLim;
|
|
|
|
textAnnotations = repmat(struct( ...
|
|
"x", [], ...
|
|
"y", [], ...
|
|
"text", "", ...
|
|
"anchor", "center"), numel(textHandles), 1);
|
|
validTextCount = 0;
|
|
|
|
for textIdx = 1:numel(textHandles)
|
|
textHandle = textHandles(textIdx);
|
|
labelText = string(textHandle.String);
|
|
if strlength(strtrim(labelText)) == 0
|
|
continue
|
|
end
|
|
|
|
textPosition = textHandle.Position;
|
|
if textPosition(1) < xLimits(1) || textPosition(1) > xLimits(2) || ...
|
|
textPosition(2) < yLimits(1) || textPosition(2) > yLimits(2)
|
|
continue
|
|
end
|
|
|
|
validTextCount = validTextCount + 1;
|
|
textAnnotations(validTextCount).x = textPosition(1);
|
|
textAnnotations(validTextCount).y = textPosition(2);
|
|
textAnnotations(validTextCount).text = labelText;
|
|
textAnnotations(validTextCount).anchor = matlabTextAlignmentToTikzAnchor( ...
|
|
textHandle.HorizontalAlignment, textHandle.VerticalAlignment);
|
|
end
|
|
|
|
textAnnotations = textAnnotations(1:validTextCount);
|
|
end
|
|
|
|
function writeTikzPngAxis(tikzFile, pngTikzPath, sourceAx, linePlots, textAnnotations, options)
|
|
outputDir = fileparts(tikzFile);
|
|
if ~exist(outputDir, "dir")
|
|
mkdir(outputDir);
|
|
end
|
|
|
|
xLimits = sourceAx.XLim;
|
|
yLimits = sourceAx.YLim;
|
|
fid = fopen(tikzFile, "w");
|
|
if fid < 0
|
|
error("exportLevelScatterTikz:TikzOpenFailed", ...
|
|
"Could not open TikZ export file: %s", tikzFile);
|
|
end
|
|
cleanupFile = onCleanup(@() fclose(fid));
|
|
|
|
axisOptions = {
|
|
" every axis/.append style={font=\scriptsize},"
|
|
"width=\fwidth,"
|
|
"height=\fheight,"
|
|
"at={(0\fwidth,0\fheight)},"
|
|
"scale only axis,"
|
|
"axis on top,"
|
|
"xmin=" + formatPgfNumber(xLimits(1)) + ","
|
|
"xmax=" + formatPgfNumber(xLimits(2)) + ","
|
|
"xlabel style={font=\color{white!15!black}\small},"
|
|
"xlabel={" + options.xLabel + "},"
|
|
"ymin=" + formatPgfNumber(yLimits(1)) + ","
|
|
"ymax=" + formatPgfNumber(yLimits(2)) + ","
|
|
"ylabel style={font=\color{white!15!black}\small},"
|
|
"ylabel={" + options.yLabel + "},"
|
|
"axis background/.style={fill=white},"
|
|
"xmajorgrids,"
|
|
"ymajorgrids,"
|
|
"grid style={line width=0.4pt, dotted, color=black!20},"
|
|
"scaled ticks=false,"
|
|
"tick label style={/pgf/number format/fixed, /pgf/number format/1000 sep={}},"
|
|
"legend columns=1"
|
|
};
|
|
|
|
fprintf(fid, "%% This file was generated by %s.\n", options.generatedBy);
|
|
fprintf(fid, "%%\n");
|
|
for lineIdx = 1:numel(linePlots)
|
|
fprintf(fid, "%s\n", formatTikzColorDefinition(lineIdx, linePlots(lineIdx).color));
|
|
end
|
|
if ~isempty(linePlots)
|
|
fprintf(fid, "%%\n");
|
|
end
|
|
fprintf(fid, "%s\n\n", "\begin{tikzpicture}");
|
|
fprintf(fid, "%s\n", "\begin{axis}[%");
|
|
for optionIdx = 1:numel(axisOptions)
|
|
fprintf(fid, "%s\n", axisOptions{optionIdx});
|
|
end
|
|
fprintf(fid, "%s\n", "]");
|
|
|
|
graphicsLine = sprintf( ...
|
|
"\\addplot [forget plot] graphics [xmin=%s, xmax=%s, ymin=%s, ymax=%s] {%s};", ...
|
|
formatPgfNumber(xLimits(1)), ...
|
|
formatPgfNumber(xLimits(2)), ...
|
|
formatPgfNumber(yLimits(1)), ...
|
|
formatPgfNumber(yLimits(2)), ...
|
|
char(strrep(pngTikzPath, "\", "/")));
|
|
fprintf(fid, "%s\n\n", graphicsLine);
|
|
writeTikzLinePlots(fid, linePlots);
|
|
writeTikzTextAnnotations(fid, textAnnotations);
|
|
fprintf(fid, "%s\n\n", "\end{axis}");
|
|
fprintf(fid, "%s", "\end{tikzpicture}%");
|
|
end
|
|
|
|
function valueText = formatPgfNumber(value)
|
|
valueText = string(sprintf("%.15g", value));
|
|
end
|
|
|
|
function colorDefinition = formatTikzColorDefinition(colorIdx, rgbColor)
|
|
colorDefinition = sprintf( ...
|
|
"\\definecolor{mycolor%d}{rgb}{%.5f,%.5f,%.5f}%%", ...
|
|
colorIdx, rgbColor(1), rgbColor(2), rgbColor(3));
|
|
end
|
|
|
|
function writeTikzLinePlots(fid, linePlots)
|
|
for lineIdx = 1:numel(linePlots)
|
|
fprintf(fid, "\\addplot [color=mycolor%d, line width=%.1fpt, forget plot]\n", ...
|
|
lineIdx, linePlots(lineIdx).lineWidth);
|
|
fprintf(fid, " table[row sep=crcr]{%%\n");
|
|
|
|
for sampleIdx = 1:numel(linePlots(lineIdx).xData)
|
|
fprintf(fid, "%s\t%s\\\\\n", ...
|
|
formatPgfNumber(linePlots(lineIdx).xData(sampleIdx)), ...
|
|
formatPgfNumber(linePlots(lineIdx).yData(sampleIdx)));
|
|
end
|
|
|
|
fprintf(fid, "};\n\n");
|
|
end
|
|
end
|
|
|
|
function writeTikzTextAnnotations(fid, textAnnotations)
|
|
for textIdx = 1:numel(textAnnotations)
|
|
fprintf(fid, "\\node[font=\\scriptsize, anchor=%s, fill=white, draw=black!40, rounded corners=2pt, inner sep=2pt]\n", ...
|
|
textAnnotations(textIdx).anchor);
|
|
fprintf(fid, " at (axis cs:%s,%s) {%s};\n\n", ...
|
|
formatPgfNumber(textAnnotations(textIdx).x), ...
|
|
formatPgfNumber(textAnnotations(textIdx).y), ...
|
|
escapeTikzText(textAnnotations(textIdx).text));
|
|
end
|
|
end
|
|
|
|
function anchor = matlabTextAlignmentToTikzAnchor(horizontalAlignment, verticalAlignment)
|
|
horizontalAlignment = string(horizontalAlignment);
|
|
verticalAlignment = string(verticalAlignment);
|
|
|
|
if horizontalAlignment == "left"
|
|
horizontalAnchor = "west";
|
|
elseif horizontalAlignment == "right"
|
|
horizontalAnchor = "east";
|
|
else
|
|
horizontalAnchor = "";
|
|
end
|
|
|
|
if verticalAlignment == "top"
|
|
verticalAnchor = "north";
|
|
elseif verticalAlignment == "bottom"
|
|
verticalAnchor = "south";
|
|
else
|
|
verticalAnchor = "";
|
|
end
|
|
|
|
if strlength(horizontalAnchor) > 0 && strlength(verticalAnchor) > 0
|
|
anchor = verticalAnchor + " " + horizontalAnchor;
|
|
elseif strlength(horizontalAnchor) > 0
|
|
anchor = horizontalAnchor;
|
|
elseif strlength(verticalAnchor) > 0
|
|
anchor = verticalAnchor;
|
|
else
|
|
anchor = "center";
|
|
end
|
|
end
|
|
|
|
function textOut = escapeTikzText(textIn)
|
|
textOut = char(textIn);
|
|
% textOut = strrep(textOut, "\", "\textbackslash{}");
|
|
textOut = strrep(textOut, "{", "\{");
|
|
textOut = strrep(textOut, "}", "\}");
|
|
textOut = strrep(textOut, "%", "\%");
|
|
textOut = strrep(textOut, "&", "\&");
|
|
textOut = strrep(textOut, "#", "\#");
|
|
textOut = strrep(textOut, "_", "\_");
|
|
% textOut = strrep(textOut, "$", "\$");
|
|
end
|