Starting in MATLAB R2021a axis tick labels will auto-rotate to avoid overlap when the user manually specifies ticks or tick labels ( release notes ). In custom visualization functions, the tick label density or tick label lengths may be variable and unknown. The new auto-rotation feature removes the burden of detecting the need to rotate manually-set labels and eliminates the need to manually rotate them.
Many properties and combinations of properties can cause tick labels to overlap if they are not rotated.
- Length of tick labels
- Number of tick labels
- Interval between tick labels
- Font size
- Font name
- Figure size
- Axes size
- Viewing angle of the axes
Demo: varying tick density and length of tick labels
These 9 axes vary by the number of x-ticks and length of x-tick-labels. MATLAB auto-rotates the labels when needed.
Demo: Changes to axis view angle and rotation
The auto-rotation feature updates the label angles as the axes change programmatically or during user interaction.
What if I don't want auto-rotation?
Auto-rotation mode is on by default for each X|Y|Z axis. When the tick label rotation angle is manually set from the X|Y|ZTickLabelRotation property of axes or by using xtickangle | ytickangle | ztickangle , auto-rotation is turned off. Auto-rotation can also be turned off by setting the X|Y|ZTickLabelRotationMode axis property to manual but it's important to also hold the axis properties so that the rotation mode does not revert to the default value, auto. If you're looking for a broader method of reverting to older behavior you can set the default label rotation mode to manual at the start of a function that produces multiple plots and then revert to the factory default rotation mode at the end of the file (consider using onCleanup).
set(groot,'defaultAxesXTickLabelRotationMode','manual') set(groot,'defaultAxesYTickLabelRotationMode','manual') set(groot,'defaultAxesZTickLabelRotationMode','manual')
% Revert to factory-default set(groot,'defaultAxesXTickLabelRotationMode','remove') set(groot,'defaultAxesYTickLabelRotationMode','remove') set(groot,'defaultAxesZTickLabelRotationMode','remove')
A copy of this Community Highlight is attached as a live script.
New in R2021a, LimitsChangedFcn
LimitsChangedFcn is a callback function that responds to changes to axis limits ( release notes ). The function responds to axis interaction such as panning and zooming, programmatically setting the axis limits, or when axis limits are automatically adjusted by other processes.
LimitsChangedFcn is a property of ruler objects which are properties of axes and can be independently set for each axis. For example,
ax = gca(); ax.XAxis.LimitsChangedFcn = ... % Responds to changes to XLim ax.YAxis.LimitsChangedFcn = ... % Responds to changes to YLim ax.ZAxis.LimitsChangedFcn = ... % Responds to changes to ZLim
Previously, a listener could be assigned to respond to changes to axis limits. Here are some examples.
However, LimitsChangedFcn responds more reliably than a listener that responds to setting/getting axis limits. For example, after zooming or panning the axes in the demo below, the listener does not respond to the Restore View button in the axis toolbar but LimitsChangedFcn does! After restoring the view, try zooming out which does not result in changes to axis limits yet the listener will respond but the LimitsChangedFcn will not. Adding objects to axes after an axis-limit listener is set will not trigger the listener even if the added object expands the axis limits ( why not? ) but LimitsChangedFcn will!
ax = gca(); ax.UserData.Listener = addlistener(ax,'XLim','PostSet',@(~,~)disp('Listener')); ax.XAxis.LimitsChangedFcn = @(~,~)disp('LimitsChangedFcn')
How to use LimitsChangedFcn
The LimitsChangedFcn works like any other callback. For review,
The first input to the LimitsChangedFcn callback function is the handle to the axis ruler object that was changed.
The second input is a structure that contains the old and new limits. For example,
LimitsChanged with properties:
OldLimits: [0 1] NewLimits: [0.25 0.75] Source: [1×1 NumericRuler] EventName: 'LimitsChanged'
Importantly, since LimitsChangedFcn is a property of the axis rulers rather than the axis object, changes to the axes may clear the LimitsChangedFcn property if the axes aren't held using hold on. For example,
% Axes not held ax = gca(); ax.XAxis.LimitsChangedFcn = @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!'); plot(ax, 1:5, rand(1,5), 'o') ax.XAxis.LimitsChangedFcn
ans = 0×0 empty char array
% Axes held ax = gca(); hold(ax,'on') ax.XAxis.LimitsChangedFcn = @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!'); plot(ax, 1:5, rand(1,5), 'o') ax.XAxis.LimitsChangedFcn
ans = function_handle with value: @(ruler,~)title(ancestor(ruler,'axes'),'LimitsChangedFcn fired!')
Demo
In this simple app a LimitsChangedFcn callback function is assigned to the x and y axes. The function does two things:
- Text boxes showing the current axis limits are updated
- The prying eyes that are centered on the axes will move to the new axis center
This demo also uses Name=Value syntax and emoji text objects !
Create app
h.fig = uifigure(Name="LimitsChangedFcn Demo", ... Resize="off"); h.fig.Position(3:4) = [500,260]; movegui(h.fig) h.ax = uiaxes(h.fig,... Units="pixels", ... Position=[200 26 250 208], ... Box="on"); grid(h.ax,"on") title(h.ax,"I'm following you!") h.eyeballs = text(h.ax, .5, .5, ... char([55357 56385 55357 56385]), ... HorizontalAlignment="center", ... FontSize=40); h.label = uilabel(h.fig, ... Text="Axis limits", ... Position=[25 212 160 15], ... FontWeight="bold",... HorizontalAlignment="center"); h.xtxt = uitextarea(h.fig, ... position=[25 191 160 20], ... HorizontalAlignment="center", ... WordWrap="off", ... Editable="off",... FontName=get(groot, 'FixedWidthFontName')); h.ytxt = uitextarea(h.fig, ... position=[25 165 160 20], ... HorizontalAlignment="center", ... WordWrap="off", ... Editable="off", ... FontName=get(groot, 'FixedWidthFontName')); h.label = uilabel(h.fig, ... Text=['X',newline,newline,'Y'], ... Position=[10 170 15 38], ... FontWeight="bold");
Set LimitsChangedFcn of x and y axes
h.ax.XAxis.LimitsChangedFcn = @(hObj,data)limitsChangedCallbackFcn(hObj,data,h,'x'); h.ax.YAxis.LimitsChangedFcn = @(hObj,data)limitsChangedCallbackFcn(hObj,data,h,'y');
Update text fields
xlim(h.ax, [-100,100]) ylim(h.ax, [-100,100])
Define LimitsChangedFcn
function limitsChangedCallbackFcn(rulerHand, limChgData, handles, xy) % limitsChangedCallbackFcn() responds to changes to x or y axis limits. % - rulerHand: Ruler handle for x or y axis that was changed (not used in this demo) % - limChgData: LimitsChanged data structure % - handles: structure of App handles % - xy: either 'x' or 'y' identifying rulerHand switch lower(xy) case 'x' textHandle = handles.xtxt; positionIndex = 1; case 'y' textHandle = handles.ytxt; positionIndex = 2; otherwise error('xy is a character ''x'' or ''y''.') end % Update text boxes showing rounded axis limits textHandle.Value = sprintf('[%.3f, %.3f]',limChgData.NewLimits); % Move the eyes to the new center position handles.eyeballs.Position(positionIndex) = limChgData.NewLimits(1)+range(limChgData.NewLimits)/2; % for linear scales only! drawnow end
See attached mlx file for a copy of this thread.
Highlight Icon image
Starting in MATLAB R2021a, name-value arguments have a new optional syntax!
A property name can be paired with its value by an equal sign and the property name is not enclosed in quotes.
Compare the comma-separated name,value syntax to the new equal-sign syntax, either of which can be used in >=r2021a:
- plot(x, y, "b-", "LineWidth", 2)
- plot(x, y, "b-", LineWidth=2)
It comes with some limitations:
- It's recommended to use only one syntax in a function call but if you're feeling rebellious and want to mix the syntaxes, all of the name=value arguments must appear after the comma-separated name,value arguments.
- Like the comma-separated name,value arguments, the name=value arguments must appear after positional arguments.
- Name=value pairs must be used directly in function calls and cannot be wrapped in cell arrays or additional parentheses.
Some other notes:
- The property names are not case-sensitive so color='r' and Color='r' are both supported.
- Partial name matches are also supported. plot(1:5, LineW=4)
The new syntax is helpful in distinguishing property names from property values in long lists of name-value arguments within the same line.
For example, compare the following 2 lines:
h = uicontrol(hfig, "Style", "checkbox", "String", "Long", "Units", "Normalize", "Tag", "chkBox1")
h = uicontrol(hfig, Style="checkbox", String="Long", Units="Normalize", Tag="chkBox1")
Here's another side-by-side comparison of the two syntaxes. See the attached mlx file for the full code and all content of this Community Highlight.
tiledlayout, introduced in MATLAB R2019b, offers a flexible way to add subplots, or tiles, to a figure.
Reviewing two changes to tiledlayout in MATLAB R2021a
- The new TileIndexing property
- Changes to TileSpacing and Padding properties
1) TileIndexing
By default, axes within a tiled layout are created from left to right, top to bottom, but sometimes it's better to organize plots column-wise from top to bottom and then left to right. Starting in r2021a, the TileIndexing property of tiledlayout specifies the direction of flow when adding new tiles.
tiledlayout(__,'TileIndexing','rowmajor') creates tiles by row (default).
tiledlayout(__,'TileIndexing','columnmajor') creates tiles by column.
.
2) TileSpacing & Padding changes
Some changes have been made to the spacing properties of tiles created by tiledlayout.
TileSpacing: sets the spacing between tiles.
- "loose" is the new default and replaces "normal" which is no longer recommended but is still accepted.
- "tight" replaces "none" and brings the tiles closer together still leaving space for axis ticks and labels.
- "none" results in tile borders touching, following the true meaning of none.
- "compact" is unchanged and has slightly more space between tiles than "tight".
Padding: sets the spacing of the figure margins.
- "loose" is the new default and replaces "normal" which is no longer recommended but is still accepted.
- "tight" replaces "none" and reduces the figure margins. "none" is no longer recommended but is still accepted.
- "compact" is unchanged and adds slightly more marginal space than "tight".
- Reducing the figure margins to a true none is still not an option.
The release notes show a comparison of these properties between r2020b and r2021a.
Here's what the new TileSpacing options (left column of figures below) and Padding options (right column) look like in R2021a. Spacing properties are written in the figure names.
.
And here's a grid of all 12 combinations of the 4 TileSpacing options and 3 Padding options in R2021a.
.
Code used to generate these figures
%% Animate the RowMajor and ColumnMajor indexing with colored tiles fig1 = figure('position',[200 200 560 420]); tlo1 = tiledlayout(fig1, 3, 3, 'TileIndexing','rowmajor'); title(tlo1, 'RowMajor indexing')
fig2 = figure('position',[760 200 560 420]); tlo2 = tiledlayout(fig2, 3, 3, 'TileIndexing','columnmajor'); title(tlo2, 'ColumnMajor indexing')
colors = jet(9); drawnow()
for i = 1:9 ax = nexttile(tlo1); ax.Color = colors(i,:); text(ax, .5, .5, num2str(i), 'Horiz','Cent','Vert','Mid','Fontsize',24)
ax = nexttile(tlo2); ax.Color = colors(i,:); text(ax, .5, .5, num2str(i), 'Horiz','Cent','Vert','Mid','Fontsize',24)
drawnow pause(.3) end
%% Show TileSpacing options tileSpacing = ["loose","compact","tight","none"]; figHeight = 140; % unit: pixels figPosY = fliplr(50 : figHeight+32 : (figHeight+30)*numel(tileSpacing));
for i = 1:numel(tileSpacing) uif = uifigure('Units','Pixels','Position', [150 figPosY(i) 580 figHeight], ... 'Name', ['TileSpacing: ', tileSpacing{i}]); tlo = tiledlayout(uif,1,3,'TileSpacing',tileSpacing(i)); h = arrayfun(@(i)nexttile(tlo), 1:tlo.GridSize(2)); box(h,'on') drawnow() end
%% Show Padding options padding = ["loose","compact","tight"]; for i = 1:numel(padding) uif = uifigure('Units','Pixels','Position', [732 figPosY(i) 580 figHeight], ... 'Name', ['Padding: ', padding{i}]); tlo = tiledlayout(uif,1,3,'Padding',padding(i)); h = arrayfun(@(i)nexttile(tlo), 1:tlo.GridSize(2)); box(h,'on') drawnow() end
%% Show all combinations of TileSpacing and Padding options tileSpacing = ["loose","compact","tight","none"]; padding = ["loose","compact","tight"]; [tsIdx, padIdx] = meshgrid(1:numel(tileSpacing), 1:numel(padding)); figSize = [320 220]; % width, height (pixels) figPosX = 150 + (figSize(1)+2)*(0:numel(tileSpacing)-1); figPosY = 50 + (figSize(2)+32)*(0:numel(padding)-1); [figX, figY] = meshgrid(figPosX, fliplr(figPosY)); for i = 1:numel(padIdx) uif = uifigure('Units','pixels','Position',[figX(i), figY(i), figSize], ... 'name', ['TS: ', tileSpacing{tsIdx(i)}, ', Pad: ', padding{padIdx(i)}]); tlo = tiledlayout(uif,2,2,'TileSpacing',tileSpacing(tsIdx(i)),'Padding',padding(padIdx(i))); h = arrayfun(@(i)nexttile(tlo), 1:prod(tlo.GridSize)); box(h,'on') drawnow() end
We've all been there. You've got some kind of output that displays perfectly in the command window and you just want to capture that display as a string so you can use it again somewhere else. Maybe it's a multidimensional array, a table, a structure, or a fit object that perfectly displays the information you need in a neat and tidy format but when you try to recreate the display in a string variable it's like reconstructing the Taj Mahal out of legos.
Enter Matlab r2021a > formattedDisplayText()
Use str=formattedDisplayText(var) the same way you use disp(var) except instead of displaying the output, it's stored as a string as it would appear in the command window.
Additional name-value pairs allow you to
- Specify a numeric format
- Specify loose|compact line spacing
- Display true|false instead of 1|0 for logical values
- Include or suppress markup formatting that may appear in the display such as the bold headers in tables.
Demo: Record the input table and results of a polynomial curve fit
load census [fitobj, gof] = fit(cdate, pop, 'poly3', 'normalize', 'on')
Results printed to the command window:
fitobj = Linear model Poly3: fitobj(x) = p1*x^3 + p2*x^2 + p3*x + p4 where x is normalized by mean 1890 and std 62.05 Coefficients (with 95% confidence bounds): p1 = 0.921 (-0.9743, 2.816) p2 = 25.18 (23.57, 26.79) p3 = 73.86 (70.33, 77.39) p4 = 61.74 (59.69, 63.8)
gof = struct with fields:
sse: 149.77 rsquare: 0.99879 dfe: 17 adjrsquare: 0.99857 rmse: 2.9682
Capture the input table, the printed fit object, and goodness-of-fit structure as strings:
rawDataStr = formattedDisplayText(table(cdate,pop),'SuppressMarkup',true) fitStr = formattedDisplayText(fitobj) gofStr = formattedDisplayText(gof)
Display the strings:
rawDataStr = " cdate pop _____ _____ 1790 3.9 1800 5.3 1810 7.2 1820 9.6 1830 12.9 1840 17.1 1850 23.1 1860 31.4 1870 38.6 1880 50.2 1890 62.9 1900 76 1910 92 1920 105.7 1930 122.8 1940 131.7 1950 150.7 1960 179 1970 205 1980 226.5 1990 248.7 "
fitStr = " Linear model Poly3: ary(x) = p1*x^3 + p2*x^2 + p3*x + p4 where x is normalized by mean 1890 and std 62.05 Coefficients (with 95% confidence bounds): p1 = 0.921 (-0.9743, 2.816) p2 = 25.18 (23.57, 26.79) p3 = 73.86 (70.33, 77.39) p4 = 61.74 (59.69, 63.8) "
gofStr = " sse: 149.77 rsquare: 0.99879 dfe: 17 adjrsquare: 0.99857 rmse: 2.9682 "
Combine the strings into a single string and write it to a text file in your temp directory:
txt = strjoin([rawDataStr; fitStr; gofStr],[newline newline]); file = fullfile(tempdir,'results.txt'); fid = fopen(file,'w+'); cleanup = onCleanup(@()fclose(fid)); fprintf(fid, '%s', txt); clear cleanup
Open results.txt.
winopen(file) % for Windows platforms