Skip to content

05 · Advanced Plotting & Figures

Verification note

MATLAB was not available in the environment used to write this page. Plotting function signatures, handle-graphics property names, and subplot/tiledlayout behavior are documented in MATLAB's Graphics documentation and were hand-traced against it, not executed in MATLAB itself.

Level 1 covered plot, xlabel, title, and saving a figure. This module goes into the handle-graphics object model underneath every plot, multi-panel layouts, and the plot types you reach for once a simple line plot doesn't say enough.

Everything is an object with a handle

Every graphics element MATLAB draws — figure, axes, line, text — is an object, and every plotting function returns a handle to the object it created:

x = linspace(0, 10, 100);
fig = figure;                  % handle to the figure window
ax = axes;                     % handle to axes within it
h = plot(ax, x, sin(x));       % handle to the line object itself

Once you hold a handle, you can query or change any property of that object after the fact, instead of only through plotting-call arguments:

h.Color = [0.85 0.33 0.10];    % RGB triplet, orange
h.LineWidth = 2;
h.LineStyle = '--';
set(ax, 'FontSize', 12, 'XGrid', 'on');

get(h) lists every settable property on an object — invaluable when you know roughly what you want ("thicker line", "dashed") but not the exact property name.

gcf, gca, gco

When you don't hold a handle explicitly (e.g. inside a script that called bare plot(x,y)), three functions retrieve the "current" object:

gcf   % get current figure
gca   % get current axes
gco   % get current object (e.g. last clicked)

gca is the workhorse — xlabel(...), title(...), etc. without an axes argument implicitly target gca; passing a handle explicitly (as above with ax) is preferred in any function or script that might create multiple figures, since gca silently targeting the wrong axes is a common plotting bug.

Multi-panel figures: tiledlayout (preferred) vs subplot

subplot(rows, cols, index) is the classic way to place multiple axes in one figure:

figure;
subplot(2, 2, 1); plot(x, sin(x));   title('sin');
subplot(2, 2, 2); plot(x, cos(x));   title('cos');
subplot(2, 2, 3); plot(x, tan(x));   title('tan'); ylim([-10 10]);
subplot(2, 2, 4); plot(x, x.^2);     title('x^2');

tiledlayout (R2019b+) is the modern replacement, with better control over spacing and a cleaner API for shared labels/titles:

figure;
t = tiledlayout(2, 2, 'TileSpacing', 'compact', 'Padding', 'compact');
nexttile; plot(x, sin(x));  title('sin');
nexttile; plot(x, cos(x));  title('cos');
nexttile; plot(x, tan(x));  title('tan'); ylim([-10 10]);
nexttile; plot(x, x.^2);    title('x^2');

title(t, 'Four Basic Functions');   % one shared title for the whole layout
xlabel(t, 'x');                     % one shared x-label

tiledlayout's big advantage: title(t, ...) and xlabel(t, ...) set a label once for the whole grid instead of repeating it in every subplot panel, and tiles can span multiple grid cells with nexttile([2 1]) (span 2 rows, 1 column) — something subplot can't do cleanly.

Plot types beyond plot

Function Use case
scatter(x, y) Discrete points, optionally sized/colored per point
bar(categories, values) Categorical comparisons
histogram(data) Distribution of a single variable
stairs(x, y) Step functions / discrete-time signals
stem(x, y) Discrete sequences (common in signal processing)
surf(X, Y, Z) / mesh(X, Y, Z) 3D surfaces over a grid
contour(X, Y, Z) 2D contour lines of a scalar field
errorbar(x, y, err) Data with uncertainty bars
data = randn(1, 1000);       % NOTE: randn output not verifiable without
                              % running MATLAB; shown for the API pattern.
figure;
histogram(data, 30, 'Normalization', 'pdf');
hold on;
xs = linspace(-4, 4, 200);
plot(xs, exp(-xs.^2/2)/sqrt(2*pi), 'r-', 'LineWidth', 2);
legend('sample histogram', 'standard normal pdf');

scatter with per-point size and color encodes two extra data dimensions on a 2D plot:

n = 50;
x = 1:n; y = (1:n) + 5*sin((1:n)/3);
sizes = 20 + 5*(1:n);
colors = (1:n);
scatter(x, y, sizes, colors, 'filled');
colorbar;   % shows the color -> value mapping

3D surfaces: meshgrid + surf

[X, Y] = meshgrid(-3:0.2:3, -3:0.2:3);
Z = X .* exp(-X.^2 - Y.^2);

figure;
surf(X, Y, Z);
shading interp;        % smooth face coloring instead of flat facets
colormap(parula);
colorbar;
xlabel('x'); ylabel('y'); zlabel('z');
view(-30, 45);          % azimuth, elevation for the 3D camera

shading interp removes the visible grid lines between facets by interpolating color across each patch — the difference between a "blocky" and a "smooth" surface plot with identical underlying data. view(az, el) sets the camera angle; view(2) snaps to a top-down 2D view (equivalent to contour's viewpoint) which is a quick way to sanity-check a surf plot against a contour of the same data.

Annotations and legends

figure;
plot(x, sin(x), 'b-', x, cos(x), 'r--');
legend('sin(x)', 'cos(x)', 'Location', 'northeast');
text(pi, 0, '\pi', 'FontSize', 14, 'HorizontalAlignment', 'center');
annotation('arrow', [0.3 0.4], [0.6 0.5]);

text(x, y, str) places a label at data coordinates; annotation instead uses normalized figure coordinates (0 to 1 across the whole figure, independent of axes limits) — useful for callouts that shouldn't move if the axes limits change. MATLAB's text/title/xlabel strings support a subset of TeX/LaTeX markup by default (\pi, ^{}, _{}, Greek letters) without any special flag.

Saving figures for publication

exportgraphics(gcf, 'figure1.png', 'Resolution', 300);
exportgraphics(gcf, 'figure1.pdf', 'ContentType', 'vector');

exportgraphics (R2020a+) is the modern, recommended replacement for the older saveas/print combination — it crops whitespace sensibly by default and cleanly distinguishes raster ('Resolution', for .png/ .jpg) from vector ('ContentType','vector', for .pdf/.eps) output. For a multi-page or figure-per-tile export, loop over figures/tiles and call exportgraphics once per file, since a single call exports one target.

Colormaps and colorbars

colormap(turbo);     % or: parula (default), jet, viridis-like 'turbo', gray
c = colorbar;
c.Label.String = 'Temperature (°C)';
clim([0 100]);        % explicit color-axis limits (renamed from caxis in R2022a+)

Choosing a colormap matters for correctness, not just aesthetics: parula and turbo are perceptually more uniform than the legacy jet colormap, meaning equal numeric differences look like roughly equal color differences — jet's sharp perceptual jumps can visually exaggerate or hide features that aren't actually there in the underlying data.

Interactive/live updates: redrawing efficiently

For an animation or a live-updating plot, avoid calling plot repeatedly in a loop (which recreates the whole line object every frame) — update the existing line's data instead:

h = plot(NaN, NaN);   % create an empty line object once
xlim([0 10]); ylim([-1 1]);
for k = 1:100
    t = 0:0.01:k/10;
    set(h, 'XData', t, 'YData', sin(t));
    drawnow;                 % force the figure to render this frame now
end

set(h, 'XData', ..., 'YData', ...) mutates the existing line object in place; drawnow flushes the graphics event queue so the change is visible immediately rather than batched until the script finishes. This pattern is dramatically faster than re-plotting from scratch every iteration, and is the basis for any real-time or animated MATLAB visualization.

How It Actually Works

Every graphics element you touch — Figure, Axes, Line, Text, Legend — is a handle object: a reference to a live entity in MATLAB's graphics engine, not a value copied around like a double. This is why set(h, 'Color', 'r') changes what's on screen even though h was "just" passed around like a variable — handles have reference semantics specifically so multiple variables (or a function you passed the handle into) can all observe and mutate the same underlying graphics object. Every settable graphical attribute is a property on that object, stored in a property table the renderer reads from on every redraw — there is no separate "plot data" versus "plot appearance" system; both are just properties on the same object graph, which is why get(h) can enumerate dozens of properties for something as simple as a single line.

Subplots and tiled layouts (subplot, tiledlayout) don't draw multiple independent plots — they create multiple Axes objects as children of one Figure, each occupying a computed rectangular region of the figure's normalized coordinate space (Position in units from 0 to 1 relative to the figure). linkaxes works by registering a listener between two Axes objects' XLim/YLim properties, so that a change event fired on one axes' limit property triggers an update callback on the other — an event-driven mechanism, not a one-time copy of limit values.

Exporting to a vector format (PDF, EPS, SVG) routes through the painters renderer specifically because it can emit true vector primitives (paths, not pixels); exporting the same figure to PNG can instead use the OpenGL-accelerated opengl renderer, which rasterizes at whatever DPI you request — this is why the same figure can look crisp at any zoom level as a PDF but pixelate in a low-DPI PNG export.

Note: based on MATLAB's documented handle-graphics object model; there is no MATLAB installation here to render and inspect a live figure.

Summary

  • Every plot element has a handle; hold onto it (h = plot(...)) rather than relying on gca/gcf once a script creates more than one figure or axes.
  • Prefer tiledlayout/nexttile over subplot for new code — shared titles/labels and tile spanning make multi-panel figures much less fiddly.
  • Match plot type to data shape: scatter for point clouds with extra encoded dimensions, histogram for distributions, surf/contour for functions of two variables.
  • exportgraphics is the modern way to save publication-quality figures, with a clear raster/vector distinction.
  • For anything animated, mutate XData/YData on an existing handle and call drawnow, rather than re-plotting every frame.