2-D Plotting

This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production.

The reference material consulted while preparing these pages is listed in the section bibliography.

Written against MATLAB R2026b. Plotting needs a graphical environment, so the code on this page was not run in MATLAB. GNU Octave 11.3.0 was used for two things only: to compute the data behind the figure, and to check the structure of the objects that the plotting calls create (how many lines an axes holds, which scale is set). Those checks are labelled Octave 11.3.0 output; Octave’s graphics system is not MATLAB’s, so the output of a plot call itself is not claimed to match MATLAB. Everything else is not run here and was checked against the R2026b documentation.

MATLAB draws a chart by creating graphics objects: a figure holds an axes, and the axes holds the lines, bars or surfaces. The high-level functions on this page create all of them with one call and return handles to the new objects, which Graphics Objects and Customization explains. The 3-D counterparts are on 3-D Plotting and Surfaces, and saving a figure is on Exporting Figures and Images.

Terminology

  • Figure is the window (or tab, in the R2025a desktop) that holds charts. Axes is the single object that draws the x- and y-axis scales, ticks, labels and grid and holds the plotted data. A chart is an axes with its content, or a standalone object such as a pie chart that does not use axes.

  • Current figure and current axes are the ones that the next plotting call draws into. A plotting call that finds no figure creates one.

  • A series is one line, set of bars or set of markers, one entry of the legend.

  • A line specification (LineSpec) is the short string such as "r--o" that sets the colour, line style and marker of a line.

  • A tiled chart layout is a grid of axes managed by one object, created by tiledlayout; it replaces subplot.

  • A name-value argument sets a property when the object is created: plot(x,y,LineWidth=2) (the Name=Value form is R2021a; older code writes plot(x,y,'LineWidth',2)).

A first plot

plot(x,y) draws the vectors as a line. With one vector, plot(y) plots against the index 1:numel(y).

The first examples use synthetic heating curves, 20 + a*(1 - exp(-t/tau)) with a different a and tau per sensor, so that the shape of every chart is known in advance and the calls are easy to compare. The real thermocouple log, data/sensors.csv, is plotted in Plotting the sensor log.

t  = 0:30;                        % minutes
S1 = 20 + 6.0*(1 - exp(-t/8));    % thermocouple S1 (degrees Celsius)

plot(t, S1)
xlabel("Time (min)")
ylabel("Temperature (\circ C)")
title("Sensor S1")

Not run here. The code opens a figure with one blue line that rises from 20 to about 25.9 degrees Celsius and levels off. The \circ in the label is TeX markup, which is the default interpreter for text in MATLAB charts.

The syntaxes of plot that matter in practice:

Syntax Meaning

plot(Y)

Plots a vector against its index, or the columns of a matrix as separate lines.

plot(X,Y)

Plots Y against X. If Y is a matrix, each column is a line; the vector X must match the rows.

plot(X1,Y1,...,Xn,Yn)

Several x-y pairs in one call. Each pair is a series and gets the next colour from the colour order.

plot(X,Y,LineSpec)

A line specification such as "--sk" for the lines of this call.

plot(___,Name=Value)

Properties of the line objects, for example LineWidth=2, Color=[0 0.4 0.7], MarkerSize=6 or MarkerFaceColor="r".

plot(ax,___)

Draws into the given axes instead of the current one.

h = plot(___)

Returns the line object or an array with one line per series.

plot(tbl,xvar,yvar)

Plots variables of a table; see below.

Line specifications

A LineSpec combines up to three parts in any order: a colour, a line style and a marker. Every part is optional.

Colour Meaning Style Meaning Marker Meaning

r, g, b

red, green, blue

-

solid (default)

o

circle

c, m, y

cyan, magenta, yellow

--

dashed

*

asterisk

k, w

black, white

:

dotted

.

point

-.

dash-dot

, s, d, ^, v, <, >, p, h

square, diamond, triangles, pentagram, hexagram

A string such as "r--o" means a red dashed line with circle markers. A marker without a line style ("o") draws markers only. For colours other than the eight letters, set the Color property to an RGB triplet in the range 0 to 1, or to a hexadecimal colour code string such as "#1d4ed8". MATLAB takes the default colour of each series from the axes ColorOrder; colororder (R2019b) changes it.

Several series and hold

One plot call can draw several series, or later calls can add to the same axes. By default a new plot call replaces what is in the axes. hold on keeps the existing content and adds the new series; hold off goes back to replacing. With hold on, the colour order continues where it stopped.

t  = (0:30)';
S1 = 20 + 6.0*(1 - exp(-t/8));
S2 = 20 + 4.0*(1 - exp(-t/11));
S3 = 20 + 2.5*(1 - exp(-t/15));

% One call, three series (a matrix has one column per series)
plot(t, [S1 S2 S3])

% The same chart built up with hold on, with line styles
figure
plot(t, S1, "-")
hold on
plot(t, S2, "--")
plot(t, S3, ":")
hold off
legend("S1", "S2", "S3", Location="southeast")

Not run here. Both charts show three curves that start at 20 degrees; S1 rises fastest and S3 slowest. The legend entries follow the order in which the series were drawn.

The data of this example is also what the Octave check below uses.

Octave 11.3.0 (code run as written below, with legend, scale and limit calls added to check the object structure; MATLAB’s output of these calls may differ in details):

t  = (0:30)';
S1 = 20 + 6.0*(1-exp(-t/8));
S2 = 20 + 4.0*(1-exp(-t/11));
S3 = 20 + 2.5*(1-exp(-t/15));
fig = figure("visible","off");
h1 = plot(t, S1, "-");
hold on
h2 = plot(t, S2, "--");
h3 = plot(t, S3, ":");
hold off
fprintf("lines on the axes: %d\n", numel(findobj(gca, "Type", "line")));
legend("S1", "S2", "S3");
xlim([5 25]);
fprintf("xlim after xlim([5 25]): %s\n", mat2str(xlim));
semilogy(t(2:end), S1(2:end)-20);
fprintf("YScale after semilogy: %s\n", get(gca, "YScale"));
loglog(t(2:end), S1(2:end)-20);
fprintf("XScale/YScale after loglog: %s/%s\n", get(gca,"XScale"), get(gca,"YScale"));
fprintf("plot with no hold replaced the lines: %d line(s)\n", numel(findobj(gca,"Type","line")));
fprintf("final temperatures: %s\n", mat2str([S1(end) S2(end) S3(end)], 4));
close(fig);

Octave 11.3.0 output (a warning about Octave’s gnuplot graphics toolkit that Octave prints first is omitted):

lines on the axes: 3
xlim after xlim([5 25]): [5 25]
YScale after semilogy: log
XScale/YScale after loglog: log/log
plot with no hold replaced the lines: 1 line(s)
final temperatures: [25.86 23.74 22.16]

The output shows the three rules of this section. After hold on the axes holds three lines; xlim changes the limits; semilogy and loglog set the axis scales; and a plain plot or semilogy call without hold on replaces the content. The last line gives the values the figure below shows at 30 minutes (S1 25.86, S2 23.74, S3 22.16 degrees Celsius).

The sensor time series

The figure shows the three heatflow thermocouples after the plate edge is switched on. The data is the three curves of the code above, computed in Octave 11.3.0 and drawn into the SVG by a script. It is an original drawing, not an export from MATLAB: the section on exporting shows the call that would save the same chart from MATLAB.

Line chart of temperature in degrees Celsius against time in minutes for sensors S1, S2 and S3. All start at 20 degrees and rise toward 26, 24 and 22.5 degrees, S1 fastest and S3 slowest.

Several axes in one figure

tiledlayout and nexttile

tiledlayout (R2019b) creates a grid of tiles and nexttile creates the axes of the next tile, returning it. The layout shares one title, one set of labels and one legend if you want, spaces the tiles automatically and lets a tile span several rows and columns.

t  = (0:30)';
S1 = 20 + 6.0*(1 - exp(-t/8));
S2 = 20 + 4.0*(1 - exp(-t/11));

tl = tiledlayout(2, 1, TileSpacing="compact");
title(tl, "Heatflow sensors")
xlabel(tl, "Time (min)")
ylabel(tl, "Temperature (\circ C)")

ax1 = nexttile;
plot(ax1, t, S1)
title(ax1, "S1")

ax2 = nexttile;
plot(ax2, t, S2)
title(ax2, "S2")

linkaxes([ax1 ax2], "xy")

Not run here. The figure has two stacked axes sharing one x-label and one y-label, and linkaxes makes zooming one axes zoom the other.

The syntax:

  • tiledlayout(m,n) makes a fixed grid; tiledlayout("flow") (also the default when called without sizes since R2024b) adds tiles as needed; tiledlayout("vertical") and tiledlayout("horizontal") stack tiles in one direction (R2023a).

  • nexttile(span) makes a tile that spans [rows cols] tiles, nexttile(tilenum) selects a given tile, and tilenum (R2022b) reports a tile’s number.

  • TileSpacing ("loose", "compact", "tight", "none") and Padding set the gaps. R2021a gave the spacing and padding options new names, so code written for older releases can use names that the documentation now lists only for compatibility.

  • A layout can hold a layout in a tile, legend can be placed on the layout (lgd.Layout.Tile = "east"), and exportgraphics accepts the layout object to save all tiles.

subplot, the older form

subplot(m,n,p) creates an axes at position p of an m-by-n grid and makes it current. It still works and is not flagged for removal, but it leaves spacing and shared labels to you, and each call can create overlapping axes if the grid arguments change. For new code use tiledlayout. Old books use subplot throughout, and the R2025a release notes also point to tiledlayout as the replacement for the removed Figure Palette tools.

Titles, labels and legends

Function What it does

title(txt), subtitle(txt)

Title above the axes; subtitle (R2020b) adds a smaller second line. sgtitle(txt) (R2018b) is a title over a grid of subplot axes; for tiledlayout use title(tl,txt).

xlabel(txt), ylabel(txt), zlabel(txt)

Axis labels. The text can be a string, a character vector, a string array or cell array for several lines, or TeX or LaTeX markup via the Interpreter property.

legend(labels), legend(Location=...)

Legend for the series, in the order they were drawn. lgd = legend(___) returns a Legend object whose properties (Location, NumColumns, Title) you can set. legend("off") removes it. A line with DisplayName="S1" is labelled without passing the strings.

xlim(lims), ylim(lims)

Axis limits; xlim("auto") restores automatic limits. xlim padded, xlim tight, ylim padded choose the padding.

axis equal, axis tight, axis([xmin xmax ymin ymax])

Aspect ratio and limits in one command.

xline(x), yline(y)

A constant vertical or horizontal reference line (R2018b), for instance a limit temperature.

grid on, box on

Grid lines and the box around the axes.

fontsize(scale, "increase")

Changes the font size of the objects of a figure (R2022a).

The Location values of legend are compass words such as "northeast" (the default), "southeast", "best" and the same with outside. Since R2024b the multi-output syntax [lgd,icons,plots,txt] = legend(___) warns and will be removed: return the Legend object only.

t  = (0:30)';
S1 = 20 + 6.0*(1 - exp(-t/8));
S2 = 20 + 4.0*(1 - exp(-t/11));

p = plot(t, S1, t, S2);
p(1).DisplayName = "S1";
p(2).DisplayName = "S2";
title("Plate temperatures")
subtitle("Edge heated at t = 0")
xlabel("Time (min)")
ylabel("Temperature (\circ C)")
lgd = legend(Location="southeast");
lgd.Title.String = "Sensor";
xlim([0 30])
yline(25, "--", "Alarm limit")

Not run here. The chart shows two curves with a title and a subtitle, a legend in the lower right whose title is "Sensor", and a horizontal dashed line at 25 degrees labelled "Alarm limit". Only S1 crosses the line, after about 14 minutes (where 6(1 - exp(-t/8)) reaches 5).

Axis scales

  • semilogx, semilogy and loglog plot with a logarithmic x-axis, y-axis or both. Equivalent: set(gca,"YScale","log") or ax.YScale = "log" on an existing chart. Values that are zero or negative cannot be drawn on a log axis and are omitted (MATLAB can warn that it ignored non-positive data).

  • yyaxis left and yyaxis right (R2016a) give one axes two y-axes with independent scales and colours. Plot after each yyaxis call to put the series on that side. This replaces the legacy plotyy, which the current documentation lists as to be removed.

t  = (0:30)';
S1 = 20 + 6.0*(1 - exp(-t/8));
q  = 40*exp(-t/10);                 % heat flux through the plate edge (W/m^2)

yyaxis left
plot(t, S1)
ylabel("Temperature (\circ C)")
yyaxis right
plot(t, q)
ylabel("Heat flux (W/m^2)")
xlabel("Time (min)")

Not run here. The chart has the temperature curve against the left axis and the falling heat flux against the right axis, each axis coloured like its line.

fplot: plotting a function handle

fplot(f) plots a function handle over a default interval of -5 to 5, and fplot(f,[a b]) over [a b]. It samples adaptively, so it resolves sharp features that a fixed grid would miss and needs no linspace and no vectorization of the plot call. It is the modern replacement for ezplot, which the documentation marks as not recommended. See Anonymous Functions and Function Handles for the handles.

% temperature along the centre line of a plate, one Fourier mode (illustrative)
f = @(x) 100*sin(pi*x);
fplot(f, [0 1])
hold on
fplot(@(x) 100*sin(pi*x).^3, [0 1], "--")
hold off
legend("first mode", "third power")

Not run here. The chart shows a half sine wave with peak 100 at x = 0.5 and a narrower dashed curve below it with the same peak. fplot accepts a handle of one variable that returns a vector for a vector input; for parametric curves use fplot(xfun,yfun).

The chart catalogue

All these functions draw into the current axes (or ax as first argument), return handle objects, and accept the same title, label, legend and limit commands.

Function Draws Notes

bar(y), barh(y)

Vertical or horizontal bars; a matrix gives grouped bars, "stacked" stacks them.

Bar labels as string vectors since R2023b. A table can be passed directly since R2026a.

scatter(x,y,sz,c)

Markers with their own size and colour.

scatter(tbl,xvar,yvar) takes a table. For a plain marker plot without size or colour per point use plot(x,y,"o"), which is faster for large data.

area(x,y)

Filled area under a curve, stacked for a matrix.

Tables since R2026a.

stairs(x,y)

Step curve that holds each value until the next sample.

Use it for piecewise-constant data such as a sensor’s last known reading. Tables since R2022b.

stem(x,y)

Vertical lines with markers, for discrete sequences.

The x-axis limits are padded by default since R2024b. Tables since R2022b.

errorbar(x,y,err)

A line with symmetric (or asymmetric) error bars.

Matrices plot several series at once since R2022b.

histogram(x)

Bar chart of counts per bin; histogram(x,edges), Normalization="pdf".

R2014b. Preferred over hist, which is not recommended. The histogram object holds BinEdges and Values. Counting without drawing is histcounts.

polarplot(theta,rho)

A line in polar coordinates.

R2016a. It creates a PolarAxes, with ThetaLim, RLim and ThetaZeroLocation. It replaces polar, which the documentation marks as not recommended.

polarhistogram(theta)

Angle histogram.

R2016b. Replaces rose, which is not recommended.

piechart(data), donutchart(data)

Pie and donut charts with names and percentages.

R2023b. They replace the legacy pie. From R2024b undefined categorical values appear in a slice labelled "Others" by default.

boxchart(ydata)

Box chart with median, quartiles and whiskers.

R2020a. Tables and timetables are accepted since R2025a. The default box width changed in R2025a; specify BoxWidth to keep older appearance.

violinplot(ydata)

A violin plot for each column or group; GroupByColor separates groups by colour.

R2024b.

raincloudplot(ydata)

A rain cloud plot for each column of the data, or for each group of xgroupdata.

R2026a.

rng(1)                                    % reproducible random numbers (not run here)
T1 = 21.5 + 0.3*randn(200, 1);            % noisy readings of sensor S1

tiledlayout(1, 3)
nexttile
histogram(T1, 12)
title("histogram")
nexttile
boxchart(T1)
title("boxchart")
nexttile
errorbar(1:3, [21.5 22.1 22.4], [0.3 0.2 0.4], "o")
xlim([0.5 3.5])
xticks(1:3)
xticklabels(["S1" "S2" "S3"])
title("errorbar")

Not run here. The first tile is a histogram of the 200 readings in 12 bins centred near 21.5, the second a box chart of the same values, and the third three points with error bars labelled S1 to S3. Random numbers differ between MATLAB and Octave even with the same seed, so no values are shown.

Plotting table variables

Many plotting functions take a table and variable names, which labels the axes and the legend with the variable names automatically. See Tables and Timetables.

tbl = table((0:5)', [20 21.8 23.0 23.9 24.6 25.1]', [20 20.9 21.6 22.1 22.5 22.8]', ...
            VariableNames=["Time" "S1" "S2"]);

plot(tbl, "Time", ["S1" "S2"])         % x variable, then one or more y variables
bar(tbl, "Time", "S1")                  % tables in bar since R2026a

Not run here. The first call draws two lines against Time, with the axis labelled Time and a legend of S1 and S2 generated from the variable names. A timetable works the same way with plot(tt) or plot(tt, "Temperature"), using the row times on the x-axis, and plot(tt.Time, tt.Temperature) works in every release.

The functions accepting tables include plot, scatter, stairs and stem (R2022b), boxchart (R2025a), violinplot (R2024b with grouping options extended in R2025a), and bar and area (R2026a). Since R2022b the labels keep special characters such as underscores in variable names. Before R2022b they were interpreted as TeX.

Plotting the sensor log

The heatflow log data/sensors.csv is in long format: one row per reading with the columns Time, SensorID and Temperature, the sensors S1 and S2 interleaved, and some temperatures missing (see The heatflow scenario). A long table cannot go straight to plot(tt,"Temperature"), which would draw all the sensors as one zigzag line. Read it as a timetable and draw one series per sensor:

% Not run here (MATLAB R2026b).
opts = detectImportOptions("data/sensors.csv");
opts = setvartype(opts, "SensorID", "categorical");
tt = readtimetable("data/sensors.csv", opts);    % row times from Time

figure
hold on
for id = categories(tt.SensorID)'
    rows = tt.SensorID == id{1};
    plot(tt.Time(rows), tt.Temperature(rows), ".-", DisplayName=id{1})
end
hold off
xlabel("Time")
ylabel("Temperature (\circ C)")
legend(Location="southeast")

Not run here. The x-axis is a datetime axis with date and time tick labels, there is one line per sensor with the legend entries S1 and S2 taken from the DisplayName of each line, and each missing temperature (NaN) leaves a gap in its line instead of being joined over. categories returns the sensor names as a cell array of character vectors, so the loop variable id is a 1-by-1 cell and id{1} the name. To get one column per sensor instead, unstack(tt,"Temperature","SensorID") turns the long timetable into a wide one with the variables S1 and S2; because the two sensors are read at different times, each row of the wide timetable then has a missing value for the sensor that was not read at that time, so plot it with markers or use retime first (see Timetables).

Common errors and pitfalls

The messages were not run here; the causes are documented behaviour.

  • The next plot erases the chart. Without hold on, a new plot call replaces the axes content (the Octave output above shows one line left). Use hold on, one call with several series, or tiledlayout.

  • Vectors of different length. plot(x,y) errors when x and y have different numbers of elements (message: "Vectors must be the same length", wording varies by release). Check size, and numel.

  • Matrix orientation. For a matrix Y, each column is a series. A 3-by-30 matrix draws 30 lines of 3 points; transpose it.

  • Legend order and missing entries. legend("a","b") labels the series in the order they were drawn.

  • Log axes and zeros. A zero or negative value cannot be placed on a log axis, so the point is dropped. Use semilogy on strictly positive data or shift the data.

  • ezplot, hist, polar, rose and plotyy in old code. They still exist in some form (plotyy is marked to be removed). Replace them with fplot, histogram, polarplot, polarhistogram and yyaxis.

  • Plot edit tools. plottools, plotbrowser, propertyeditor and figurepalette were removed in R2025a. To edit a chart interactively use plot edit mode and the Property Inspector, and to lay out axes use tiledlayout.

  • Time on the x-axis. Plot datetime and duration values directly: the axis shows dates and times and its limits take datetime values. Do not convert to datenum.

Where older books differ

  • Interface. Books written before R2014b describe graphics handles as plain numbers and use set(h,"Color","r"). Since R2014b a handle is an object and h.Color = "r" works too (see Graphics Objects and Customization).

  • Colours. Old books show a blue-green default colour order and the jet colormap. R2014b introduced a new default order and the parula colormap, and R2025a changed some default light-theme colours again and introduced the "gem" palette and a dark theme.

  • Older functions. subplot, ezplot, hist, polar, rose, pie, plotyy, hold all and the one-argument legend strings are all in older books. The current equivalents are tiledlayout, fplot, histogram, polarplot, polarhistogram, piechart, yyaxis, hold on with the colour order, and DisplayName.

  • Newer charts. piechart and donutchart (R2023b), violinplot (R2024b) and raincloudplot (R2026a) are in no book of this age.