How to Make Better Plots in MATLAB: Plot Wrapper

This is third and final of a series of posts on plotting in MATLAB. This series is focusing on suggestions and considerations to get your plot looking “just right” for your paper or presentation. In the first post, I talked about how to improve the look of curves. In the second post, I talked about fixing up the text, including axis labels and legends. Here, I will talk about using a plot wrapper to automate most of your work. I will also talk about exporting your figure to whatever format you need.

Recap

Where were we? At the end of the previous post, our last version of the plotting function was the following:


numPlotPoints = 50;
x = 1e-2:1e-2:100;
% Smooth curves
y1 = exp(-x);
y2 = 1-exp(-x);
% Noisy curves
n = length(x);
z1 = y1.*(1+0.005*randn(1,n));
z2 = y2.*(1+0.005*randn(1,n));
plotInd = unique(floor(logspace(0, log10(n), numPlotPoints)));
hFig = figure;
hAxes = axes('Parent',hFig,…
'FontSize',12,'XGrid','on', 'YGrid','on', 'Box', 'on',…
'TickLabelInterpreter', 'latex');
set(hFig, 'Color', 'w')
hold('on');
plot(hAxes, NaN,NaN,'LineStyle', '-', 'Color', [0 0 0],…
'LineWidth', 1);
plot(hAxes, NaN,NaN,'LineStyle', 'none', 'Color', [0 0 0],…
'LineWidth', 1, 'Marker', 'o');
plot(hAxes, x(plotInd),y1(plotInd),'LineStyle', '-', 'Color', [0 0 0],…
'LineWidth', 1);
plot(hAxes, x(plotInd),y2(plotInd),'LineStyle', '-', 'Color', [0 0 1],…
'LineWidth', 1);
plot(hAxes, x(plotInd),z1(plotInd),'LineStyle', 'none', 'Color', [0 0.6 0],…
'LineWidth', 1, 'Marker', 'o');
plot(hAxes, x(plotInd),z2(plotInd),'LineStyle', 'none', 'Color', [1 0 0],…
'LineWidth', 1, 'Marker', 'o');
xlabel('Time [s]', 'interpreter', 'Latex');
ylabel('Exponential Observation', 'interpreter', 'Latex');
legend(hAxes, 'Analytical', 'Simulation')
set(hAxes,'XScale','log');

Which produces the following figure:

Figure with legend based on ghost curves.We did a bit more tweaking with the figure to add some annotations, but they’re a bit harder to automate so I won’t discuss them here.

Wrapping it Up

Now, we want to make the plot wrapper. This will be a custom function that will apply our common figure changes and that we can call for any plot we want. I previously wrote about wrapper functions here.

We want as much flexibility as we can so that the wrapper will be useful. The input arguments will be:

  1. Handle to the existing axes
  2. Data to plot
  3. Changes to apply to the wrapper’s default settings

Below is a sample plot wrapper. It has 3 subfunctions to set the default properties for the figure, axes, and curve. 2 other subfunctions are used to change structure and object settings. These last 2 subfunctions use MATLAB’s ability to reference structure fields like they are an array.


function [hFig, hAxes, hCurve] = my_plot_function(hAxes, …
xData, yData, myFigProp, myAxesProp, myCurveProp, xStr, yStr)
% A plotting wrapper function. Designed to plot one curve per call.
%
% INPUTS
% hAxes – handle to existing axes (set to 0 to create new figure and axes)
% xData – x data to plot (set to [] to not plot)
% yData – y data to plot (set to [] to not plot)
% myFigProp – structure of figure properties to change from defaults. See
% subfunction buildFigureStruct for defaults. Ignored if hAxes == 0. Set
% to [] to not change any defaults
% myAxesProp – structure of axes properties to change from defaults. See
% subfunction buildAxesStruct for defaults. Ignored if hAxes == 0. Set
% to [] to not change any defaults
% myCurveProp – structure of curve properties to change from defaults. See
% subfunction buildCurveStruct for defaults. Ignored if xData == [] or
% yData == 0. Set to [] to not change any defaults
%
% OUTPUTS (All optional)
% hFig – handle to figure
% hAxes – handle to axes. Store to plot more curves later
% hCurve – handle to curve
%
% Created by Adam Noel, 2016-09-16
% Adapted from function accordPlotMaker.m, part of AcCoRD's MATLAB utilities
% https://github.com/adamjgnoel/AcCoRD
%
% Options to expand
% – control for type of plot (e.g., plot3, bar, surf)
% Create figure and apply properties if it does not exist
if hAxes == 0
figProp = buildFigureStruct(myFigProp);
axesProp = buildAxesStruct(myAxesProp);
% Create Figure
hFig = figure;
hFig = applyObjChanges(hFig, figProp);
% Create Axes
hAxes = axes('Parent',hFig);
hAxes = applyObjChanges(hAxes, axesProp);
hold(hAxes, 'on');
xlabel(xStr, 'interpreter', 'Latex');
ylabel(yStr, 'interpreter', 'Latex');
else
hFig = get(hAxes, 'Parent');
end
if ~isempty(xData) && ~isempty(yData)
% Load curve properties
curveProp = buildCurveStruct(myCurveProp);
hCurve = plot(hAxes, xData, yData);
hCurve = applyObjChanges(hCurve, curveProp);
end
end
% Set default figure properties and apply changes
function figProp = buildFigureStruct(myFigProp)
figProp.Color = 'w';
figProp = applyStructChanges(figProp, myFigProp);
end
% Set default axes properties and apply changes
function axesProp = buildAxesStruct(myAxesProp)
axesProp = struct(…
'Box', 'on', … % If on, draw complete box outline along axis limits
'FontSize', 12, …
'XGrid', 'on', …
'YGrid', 'on', …
'TickLabelInterpreter', 'latex');
axesProp = applyStructChanges(axesProp, myAxesProp);
end
% Set default curve properties and apply changes
function curveProp = buildCurveStruct(myCurveProp)
curveProp = struct(…
'LineStyle', '-', … % Line style. Options: '-', '–', ':', '-.', 'none'
'LineWidth', 1, … % Line width
'Color', 'black', … % Line color
'Marker', 'o', … % Marker shape. Options: 'o', '+', '*', '.', 'x', 's', 'd', '^', 'v', '>', '<', 'p', 'h', 'none'
'MarkerSize', 6, … % Marker size
'MarkerEdgeColor', 'auto', … % Marker edge color. Options: 'auto', 'none', 'RGB triplet', 'color string'
'MarkerFaceColor', 'none', … % Marker edge color. Options: 'auto', 'none', 'RGB triplet', 'color string'
'DisplayName', ''); % Display string (if legend turned on)
curveProp = applyStructChanges(curveProp, myCurveProp);
end
% Change specified elements of structure
function curStruct = applyStructChanges(curStruct, changeStruct)
if ~isempty(changeStruct)
propFields = fieldnames(changeStruct);
numProp = numel(propFields);
for j = 1:numProp
curStruct.(propFields{j}) = changeStruct.(propFields{j});
end
end
end
% Apply changes to object with handle
function h = applyObjChanges(h, changeStruct)
if ~isempty(changeStruct)
propFields = fieldnames(changeStruct);
numProp = numel(propFields);
for i = 1:numProp
set(h, propFields{i}, changeStruct.(propFields{i}));
end
end
end

A few comments on this plot wrapper:

  1. It can be called with empty data to just build the figure and axes without plotting anything
  2. You don’t need to include any of the figure, axes, or label arguments if you already have the axes handle from a previous call (or elsewhere)
  3. You can pass your own custom structures for the figure, axes, and curve properties. As long as the structures have fields with the proper naming convention, then you can change (almost) any property this way. Disclaimer: I haven’t seen this work well when properties are also structures.

Now let’s try a simple script to build the figure we have above:


clear;
numPlotPoints = 50;
x = 1e-2:1e-2:100;
% Smooth curves
y1 = exp(-x);
y2 = 1-exp(-x);
% Noisy curves
n = length(x);
z1 = y1.*(1+0.005*randn(1,n));
z2 = y2.*(1+0.005*randn(1,n));
plotInd = unique(floor(logspace(0, log10(n), numPlotPoints)));
% My axes properties
axesProp.XScale = 'log';
hAxes = 0;
% Create 'Ghost curves' for legend
curveProp.Marker = 'none';
[~, hAxes, ~] = my_plot_function(hAxes, NaN, NaN,[],axesProp,curveProp,…
'Time [s]', 'Exponential Observation');
curveProp.Marker = 'o';
curveProp.LineStyle = 'none';
[~, hAxes, ~] = my_plot_function(hAxes, NaN, NaN,[],axesProp,curveProp,…
'Time [s]', 'Exponential Observation');
hLegend = legend(hAxes, 'Analytical', 'Simulation');
set(hLegend, 'Interpreter', 'latex');
% Add smooth curves
curveProp.Marker = 'none';
curveProp.LineStyle = '-';
[~, hAxes, ~] = my_plot_function(hAxes, x(plotInd),y1(plotInd),[],[],curveProp,…
[], []);
curveProp.Color = [0 0 1];
[~, hAxes, ~] = my_plot_function(hAxes, x(plotInd),y2(plotInd),[],[],curveProp, [], []);
% Add noisy curves
curveProp.LineStyle = 'none';
curveProp.Marker = 'o';
curveProp.Color = [0 0.6 0];
[~, hAxes, ~] = my_plot_function(hAxes, x(plotInd),z1(plotInd),[],[],curveProp, [], []);
curveProp.Color = [1 0 0];
[~, hAxes, ~] = my_plot_function(hAxes, x(plotInd),z2(plotInd),[],[],curveProp, [], []);

This script produces the following:

Figure generated by script using plot wrapper
Figure generated by script using plot wrapper

The script using the wrapper is no shorter than the full plotting script above. But, it’s easier to copy and modify. If you make a large number of figures, then having a good wrapper should reduce your workload.

Exporting

Now to finish off by describing how to export your figures. I’m not going to re-invent the wheel here. I highly recommend using export_fig (found here on the MathWorks file exchange). It does a great job at reproducing what you see on your screen. It also works well with various file formats. I’ve used it to make pdf, eps, and jpeg images (including the ones in these posts).

Is it the End?

This short series shows an end-to-end workflow of plotting in MATLAB. There will be more posts on specific “tricks”.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.