bio_img_graphics-and-apps

MATLAB Graphics and App Building

MATLAB graphics, data visualization, and app building

Polar plots with patches and surfaces – R2025a

Portrait of Grant Marshall Guest Writer: Grant Marshall
Grant is a software engineer on the MATLAB Graphics & Charting Team. He first joined MathWorks in 2023 as a member of the Engineering Development Group (EDG) and focused his contributions on the MATLAB Graphics and App Building space. In 2025, Grant joined the Charting Team, where he continues to work towards advancing MATLAB’s visualization capabilities. Aside from writing code, Grant enjoys outdoor activities such as skiing, running, and golf. He also enjoys watching movies and sports, especially his alma mater, the University of Michigan.

 


Open in MATLAB Online
Have you ever tried to visualize your polar data by using the surface and the patch functions in polar axes, only to be hit with this error message?

Error using patch/surface
Patch/Surface cannot be a child of PolarAxes.
We know you are not alone. The ability to plot patches and surfaces in polar axes has been a frequent request. Workarounds suggested converting your polar data to Cartesian coordinates, as explained in this 2013 blog article, but that still prevented the use of polar axes.
I'm pleased to proclaim that we've patched the polar patch problem by paving a pathway to plotting patches and surfaces in polar axes! Prepare to plunge into polar perspectives, peppered with plenty of precisely plotted powerful patches, promising the potential for profound possibilities and pretty presentations!

Exciting Changes in MATLAB R2025a

With the release of MATLAB R2025a, patch and surface objects can now be added directly to polar axes, preserving data in polar coordinates. This enhancement extends to the patch, fill, fill3, surface, surf, mesh, and pcolor functions, facilitating 2D visualizations in polaraxes.
Using these functions with polar axes is straightforward and follows the same syntaxes as with Cartesian axes. For fill, fill3, surf, mesh, and pcolor, ensure the polar axes handle is specified or set "hold on" before plotting. While this is optional for surface and patch, specifying axes handles is typically recommended.
For example:
pax = polaraxes;
surf(pax, theta, rho, ...)
patch(pax, theta, rho, ...)
See the documentation for a reminder of the different ways you can use these functions.

Thinking About Surfaces in Polar Coordinates

Thinking about surfaces in 2D polar coordinates can be mind-bending. Theta values are specified in radians while the radius argument describes the extent away from the origin. Height values (z) may affect color assignments depending on the syntax, but will not contribute to a third spatial dimension in polar axes.
To understand MATLAB's patch or surface objects in polar axes, consider this surface object with three horizontal stripes in Cartesian axes.
x = [0 10];
y = [1;2;3;4];
z = zeros(4,2);
c = [1;2;3];
surf(x,y,z,c);
colormap(jet)
view(2)
Now imagine the surface in polar coordinates. What would that look like?
The surface has four corners and four sides. Picture the horizontal stripes spanning between two angles at the left and right edges of the surface. What do the top and bottom edges look like? Are they curved or straight?
In MATLAB, the vertices are converted to polar coordinates, but the lines connecting the vertices behave in a Cartesian manner with straight lines (see also polarplot). Let's plot the rectangular surface above at various angular extents in polar axes. I applied 50% FaceAlpha to the surface so the background grid is visible.
figure(position=[1 1 750 400])
tcl = tiledlayout('flow');
thetaExtents = [45 90 135 225 270 315]*pi/180;
radius = [1;2;3;4];
z = zeros(4,2);
c = [1;2;3];
for i = 1:numel(thetaExtents)
    theta = [0, thetaExtents(i)];
    pax = polaraxes(tcl,ThetaTick=0:45:315);
    pax.Layout.Tile = i;
    surf(pax,theta,radius,z,c,FaceAlpha=0.5)
    title(sprintf('0:%.0f',thetaExtents(i)*180/pi))
end
colormap(jet)
This arrangement makes it possible to create geometric shapes in polar axes, like those found in a spider plot. The example below shows average snowfall for Anchorage Alaska (source), Portland Maine (source), and Danmarkshavn Greenland (source).
anchorage = [12.6 13.9 8.7 5.4 0.1 0.0 0.0 0.0 0.3 1.7 10.8 16.6]; % Anchorage 2010-2019 avg
anchorage(end+1) = anchorage(1); % Wrap the first and last value
maine = [19.1 24.1 10.9 2.6 0.0 0.0 0.0 0.0 0.0 0.5 3.2 15.0]; % Maine 2010-2019 avg
maine(end+1) = maine(1);
Danmarkshavn = [14.1 12.8 11.7 8.9 3.7 1.3 0.1 0.8 7.8 9.0 10.7 14.0];
Danmarkshavn(end+1) = Danmarkshavn(1);
th = linspace(0, 2*pi, numel(anchorage));
figure()
pax = polaraxes(ThetaTick = th(1:end-1)*180/pi, ...
    ThetaTickLabel = ["Jan" "Feb" "Mar" "Apr" "May" "Jun" "Jul" "Aug" "Sep" "Oct" "Nov" "Dec"]);
patch(pax, th, anchorage, 'blue', ...
    FaceAlpha=0.2, ...
    Marker = 'o', ...
    MarkerFaceColor = 'blue', ...
    LineWidth = 1, ...
    EdgeColor = 'blue', ...
    DisplayName = 'Anchorage AK')
hold(pax,'on')
patch(pax, th, maine, 'magenta', ...
    FaceAlpha = 0.2, ...
    Marker = 'o', ...
    MarkerFaceColor = 'magenta', ...
    LineWidth = 1, ...
    EdgeColor = 'magenta', ...
    DisplayName = 'Portland ME')
patch(pax, th, Danmarkshavn, 'green', ...
    FaceAlpha = 0.2, ...
    Marker = 'o', ...
    MarkerFaceColor = 'green', ...
    LineWidth = 1, ...
    EdgeColor = 'green', ...
    DisplayName = 'Danmarkshavn GL')
title(pax, 'Average snowfall (inches)')
legend(pax)
To create surfaces or patches with curved edges defined by polar coordinates, specify densely spaced theta values between the vertices. Instead of a rectangle defined by four corners, there are 150 theta values that define these curved surfaces below. The MeshStyle property of the surface object is set to "row" in this example to avoid the densely packed column edges of the surface from obscuring the faces (I'll let you explore the MeshStyle property).
figure(position=[1 1 750 400])
tcl = tiledlayout('flow');
thetaExtents = [45 90 135 225 270 315]*pi/180;
n = 150;
radius = [1;2;3;4];
z = zeros(4,n);
c = repmat([1;2;3],1,n);
for i = 1:numel(thetaExtents)
    theta = linspace(0,thetaExtents(i),n); % Densely spaced theta values
    pax = polaraxes(tcl,ThetaTick=0:45:315);
    pax.Layout.Tile = i;
    h = surf(pax,theta,radius,z,c,FaceAlpha=0.5,MeshStyle='row');
    title(sprintf('0:%.0f',thetaExtents(i)*180/pi))
end
colormap(jet)

Use Color as a Third Dimension in Polar Axes

This example creates a semicircular heatmap in polar axes using data from NOAA National Centers for Environmental Information (csv file available in GitHub). The theta axis defines 50 states in the USA, the radial axis defines average, low, and high temperatures for 2024, and color is used to define temperature (degrees Fahrenheit).
The states are labeled using text objects that are rotated by the same angle as their theta position or with an additional 180° to flip the orientation after the vertex (between Missouri and Montana).
% Read in data.
T = readtable("average-temperatures-by-state-2024.csv");
% Isolate the average, high, and low data
zg = T{:,[5 2 6]};
% Theta will span from 0 to 180 degrees with a face for every row in T.
% Rho values are arbitrary but there needs to be 4 rho values to define the 3 categories in the data.
theta = linspace(0,pi,height(T)+1);
rho = linspace(2,1,width(zg)+1);
[tg, rg] = ndgrid(theta,rho);
zg(end+1,:) = 0;
zg(:,end+1) = 0;
% Create the initial "heat map" using the "surf" function
fig = figure(position=[1 1 820 550]);
tcl = tiledlayout(fig);
pax = polaraxes(tcl,Color='none');
s = surf(pax,tg,rg,zg);
% Add the corresponding state labels. We must add logic to correctly rotate
% the labels.
pax.ThetaTick = [];
thLabels = theta(1:end-1) + diff(theta([1,2]))/2;
rhoLabels = max(rho).*1.02;
for i = 1:height(T)
    if thLabels(i)<pi/2
        horizAlign = 'left';
        rotate = thLabels(i)*180/pi;
    else
        horizAlign = 'right';
        rotate = thLabels(i)*180/pi + 180;
    end
    text(thLabels(i), rhoLabels, T.state(i), ...
        'HorizontalAlignment', horizAlign, ...
        'Rotation',rotate);
end
% Add the labels for average, low, and high temperature.
pax.ThetaLim = [0 180];
pax.RAxisLocation = 180;
pax.RTick = rho(end:-1:2)+(rho(1)-rho(2))/2;
pax.RTickLabel = ["AvgLow","Avg(ºF)","AvgHigh"];
% Create a title.
 title(pax,{'Average Temperature','2024'},'Position',[0,0], 'FontSize', 20)
% Change the colormap and add a colorbar.
colormap(pax,turbo);
colorbar(pax,'southoutside');
pax.CLim = [0,100];

Interpolating FaceColor in Polar Coordinates

Anyone who works frequently with circular data has run into the [0,360] border problem. In polar axes with a [0,360] theta limit, coordinates that lie along theta=0 and theta=360 share the same theta position. Likewise, when the lower radial limit is 0, data across all theta values share the same position when their radius is 0. This complicates tasks such as spatial averaging, 2D binning, and interpolating across those borders.
Patch and surface objects support FaceColor interpolation, but they do not interpolate across the [0,360] border in polar axes. The appearance of circular interpolation can be achieved if the color data are wrapped at the border. For example, the hsv colormap is circularly defined such that the first and last color of the colormap can be joined in a circle. The interpolated FaceColor of the disc in this next example uses the hsv colormap.
Compare that to the turbo colormap which is not circularly designed and shows a clear interruption of interpolation at [0, 360].
% Define Theta and Rho points.
n = 360;
th = linspace(0,2*pi,n);
r = [0;1];
z = zeros(2,n);
% Create a colormap using the hsv function
cmap = hsv(n); %  Replace the colormap to see the [0,360] border
truecolor = ones(2,n,3);
truecolor(2,:,:) = cmap;
% Create a customized polar axes.
fig = figure();
pax = polaraxes(fig, Layer='top',ThetaZeroLocation='top',ThetaDir='Clockwise',RAxisLocation=90);
pax.RAxis.Label.String = 'saturation';
pax.ThetaAxis.Label.String = 'hue';
% Create the surface object with interpolated colors
surf(pax,th,r,z,truecolor,FaceColor="interp",LineStyle="none");
title('hsv color wheel');

Working with Radial Limits

When creating a surface in polaraxes, RData cannot extend across the lower radial limit (Rlim). If the range of RData passes through the lower RLim, a warning will appear and the surface will not be rendered. This limitation does not apply to patches where all RData values less than the minimum RLim will be reflected across the origin. See this MATLAB Answers post to dive deeper into this topic.

Conclusion

To summarize, the patch, fill, fill3, surface, surf, mesh, and pcolor functions can now be used with theta and r data values on polaraxes. The examples above demonstrate the versatility of this feature and its potential to enhance your data visualizations. Lastly, I would like to thank the amazing MATLAB community for their collaborative spirit, creating valuable workarounds such as pcolor in polar coordinates which has been downloaded by over seven thousand MATLAB users! Please leave a comment sharing how you plan to utilize this new feature and any suggestions for further improvements.

 

|
  • print

Comments

To leave a comment, please click here to sign in to your MathWorks Account or create a new one.