Add 2nd horizontal axis to plot

Hey everyone,
I'm trying to plot 3 lines onto a single graph, but I'd like to have a second xaxis on the top since I'm dealing with ratios here.
This is what I got up to now and it's the base graph.
I've then tried to add a second xaxis using the tiledlayout (t = tiledlayout(1,1)) approach suggested on Matalab documentation (cause xxaxis doesn't exist ugh!!!)
but when I do that I can't seem to use any sort of hold command to plot more than one graph at the same time, so I end up with this below. (Also, for some reason the text seems to be of very low quality and the legend also seems to die when tilelayout is involved.)
%% Data Import
T = readtable('Sample Compositions - Copy.xlsx');
markers = {'-ok','-sk','-^k'};
%% Ni Ratio
figure
k=1; %Marker counter
NiCoRatios = [T.Ni(1),T.Ni(4),T.Ni(7),T.Ni(2),T.Ni(5),T.Ni(8),T.Ni(3),T.Ni(6),T.Ni(9)]';
NiCoP = [T.P(1),T.P(4),T.P(7),T.P(2),T.P(5),T.P(8),T.P(3),T.P(6),T.P(9)]';
T1 = table(NiCoRatios,NiCoP);
t = tiledlayout(1,1);
for i = 1:3:9
j = i+2;
ax1 = axes(t);
hold all % if I keep this & tilelayout, the output will be completely broken.
plot(ax1,T1.NiCoRatios(i:j),T1.NiCoP(i:j),sprintf('%s',markers{k}))
ax2 = axes(t);
ax2.XAxisLocation = 'top';
ax2.YAxisLocation = 'right';
ax2.Color = 'none';
ax2.XDir = 'reverse';
xlim (ax1,[15,60])
ylim (ax1,[0,7])
xlim (ax2,[15,60])
ylim (ax2,[0,7])
grid on
xlabel(ax1,'Ni at.%')
xlabel(ax2,'Co at.%')
ylabel(ax1,'Oxidation Rate Cte. \itk')
ylabel(ax2,'Oxidation Rate Cte. \itk')
legend('Al:Ti 1:3','Al:Ti 1:1','Al:Ti 3:1','Location','Best Outside')
k =k+1;
end

2 Comments

We can't run your code because we do not have access to the xlsx file. You could attach it to your question so we can reproduce the problems.
I have added it now, but keep in mind that the Excel sheet wasn't meant to be read by others...

Sign in to comment.

 Accepted Answer

Adam Danz
Adam Danz on 28 May 2021
To create axes using tiledlayout you should use nexttile to create the primary axes (see this answer).
To create multiple x-tick-labels you've got a few options.
If you want the x-ticks on top an easier solution would be use text() to add labels above the top of the axes at the locations of the xticks. The down-side of this approach is that the text object are anchored to data units so if there are any changes to the axis limits the text objects may move with the data rather than staying achored to the axis ticks. There are ways around that such as setting callback functions or listeners to adjust the text label positions in the event of an axis limit change but that starts to get complicated, but achievable.
Another relatively easy approach that avoide that problem is use use multiple rows of x-tick-labels as shown in these answers
Lastly, you could add a second pair of axes on top of the primary axes which is your current approach but requires some additional work to ensure that an adjustment in axis limits will not decouple the two axes. This answer demonstrates that process using subplot but the same approach can be used with tiledlayout with a few minor changes. Consider using subplot to make things easier. If you use tiledlayout, the position properties cannot be set with TiledLayout and you'll receive warnings when that happens. You can modify the demo (2 lines will need modified - you'll see warnings otherwise).
If you have any trouble, share your updated code, any files or variables needed to run the code, and an updated screenshot would be helpful.

4 Comments

Thank for the thorough answer.
I have attempted to merge the method shown in the last example you gave here. Everything sems to work fine but the plots seem to have been set behind a white background that also covers part of the top axis.
Furthemore, I can't really wrap my head around the last part of the code in the demo where it calculates the xticks for the top axis. I got it to display properly from 2-8%, but through trial and error. Had to set it to [1,2] for some reason.
Thanks for baring with me here, I was never taught much past basic matlab.
clear; clc; close all
set(0,'DefaultFigureVisible','on')
% Data Import
T = readtable('Sample Compositions - Copy.xlsx');
% Al Ratios
fig = figure();
ax0 = axes();
ax0.Position(3:4) = ax0.Position(3:4) * .95; % [1]
ax = copyobj(ax0, fig);
j=1;
markers = {'-ok','-sk','-^k'};
% Plotting
k=1; %Marker counter
for i = 1:3:9
j = i+2;
hold all
x=T.Al(1:3);
y=T.P(1:3);
plot(x,y,sprintf('%s',markers(k)))
grid on
legend('Ni:CO 1:3','Ni:CO 1:1','Ni:CO 3:1','Location','Best Outside')
k =k+1;
end
% Set x axis ticks, limit, xlabel, and any
% other properties of the main axis here. [3]
ax.XTick = 2:1:8;
ax.XLim = ([2,8]); % Important to set xlim
xlabel(ax, 'Al at.%')
ax.YTick = 0:1:7;
ax.YLim = ([0,7]); % Important to set ylim
ylabel(ax, 'Oxidation Rate Cte. \itk')
% Link the 2 axes [4]
linkaxes([ax0,ax],'y')
linkprop([ax,ax0], {'position','xScale','units',...
'box','boxstyle','DataAspectRatio','FontName',...
'FontSize','xlim','xtick'});
% Set the bottom axis x-axis to the top and remove its y axis
ax0.XAxisLocation = 'top';
ax0.YTick = [];
ax0.XDir = 'reverse';
% Compute the x ticks for the 2nd x-axis on top by
% scaling the 2nd x-values to the first x-values.
x2Limits = [1, 2]; % The [min, max] values of the second x-axis (Energy) [5]
x2LimScaled = (x2Limits-x2Limits(1))*range(ax.XLim) + ax.XLim(1);
X1TickNorm = (ax.XTick - ax.XLim(1)) / range(ax.XLim);
X2Tick = (X1TickNorm * range(x2LimScaled)) + x2LimScaled(1);
% Sanity check: number of x2 ticks equals number of x1 ticks
assert(isequal(numel(X2Tick),numel(ax.XTick)), 'Tick lenghts between the two x axes are not equal.')
set(ax0, 'XTickLabel', compose('%d',X2Tick)) %[6]
xlabel(ax0, 'Ti at.%')
Code review
Once you run this 2nd line, those settings are saved for the rest of the session. Also you're only creating one figure here so there no need for that line of code. You can set the visibility of the figure using fig=figure('Visible','on').
clear; clc; close all
set(0,'DefaultFigureVisible','on') % <---------------
% Data Import
T = readtable('Sample Compositions - Copy.xlsx');
% Al Ratios
fig = figure();
ax0 = axes();
ax0.Position(3:4) = ax0.Position(3:4) * .95; % [1]
ax = copyobj(ax0, fig);
Yuk, this loop is a mess. Also, you only need to run hold once so move that out of the loop. Also "hold all" will be removed in the future. You also don't need to keep turning the grid on over and over again. The legend only needs to be created once, not within the loop. Most importantly, you're not using axis handles so Matlab is guessing which axes to plot on. Always use parent handles. The "ax" axes are the ones you're plotting on. The "ax0" are the one underneath hosting the top x-tick-labels.
j=1;
markers = {'-ok','-sk','-^k'};
% Plotting
k=1; %Marker counter
for i = 1:3:9
j = i+2;
hold all
x=T.Al(1:3);
y=T.P(1:3);
plot(x,y,sprintf('%s',markers(k)))
grid on
legend('Ni:CO 1:3','Ni:CO 1:1','Ni:CO 3:1','Location','Best Outside')
k =k+1;
end
Instead, use something like this. I don't know what you're using 1:3:9 for but you can index those numbers using idx(i).
markers = {____};
idx = 1:3:9;
hold(ax,'on')
grid(ax,'on')
for i = 1:numel(idx)
x = ___;
y = ___;
plot(ax,x,y,markers{i})
end
legend(ax,____)
This section is fine.
% Set x axis ticks, limit, xlabel, and any
% other properties of the main axis here. [3]
ax.XTick = 2:1:8;
ax.XLim = ([2,8]); % Important to set xlim
xlabel(ax, 'Al at.%')
ax.YTick = 0:1:7;
ax.YLim = ([0,7]); % Important to set ylim
ylabel(ax, 'Oxidation Rate Cte. \itk')
% Link the 2 axes [4]
linkaxes([ax0,ax],'y')
linkprop([ax,ax0], {'position','xScale','units',...
'box','boxstyle','DataAspectRatio','FontName',...
'FontSize','xlim','xtick'});
From the image you shared in your question, it looks like you want the upper tick labels to merely be a reversal of the lower tick labels. In that case, you did the right thing by reversing the ax0.XDir. Since the xtick property is already linked in the section above, there's no need to set the xtick labels so I've commented that section out.
% Set the bottom axis x-axis to the top and remove its y axis
ax0.XAxisLocation = 'top';
ax0.YTick = [];
ax0.XDir = 'reverse';
% Compute the x ticks for the 2nd x-axis on top by
% scaling the 2nd x-values to the first x-values.
% x2Limits = [1, 2]; % The [min, max] values of the second x-axis (Energy) [5]
% x2LimScaled = (x2Limits-x2Limits(1))*range(ax.XLim) + ax.XLim(1);
% X1TickNorm = (ax.XTick - ax.XLim(1)) / range(ax.XLim);
% X2Tick = (X1TickNorm * range(x2LimScaled)) + x2LimScaled(1);
% Sanity check: number of x2 ticks equals number of x1 ticks
% assert(isequal(numel(X2Tick),numel(ax.XTick)), 'Tick lenghts between the two x axes are not equal.')
% set(ax0, 'XTickLabel', compose('%d',X2Tick)) %[6]
xlabel(ax0, 'Ti at.%')
Dennis Premoli
Dennis Premoli on 28 May 2021
Edited: Dennis Premoli on 28 May 2021
Thank you very much for the great advice.
"Yuk, this loop is a mess." That was my bad. I had simplified my code to make a clearer example to post here but forgot that the loop wasn't being use anymore. That was to plot 3 graphs (hence the hold on).
I had read about hold all being removed in the future, but mainly just forgot to change it.
Regarding the handles and indexing, that is great advice. It's a very important part of Matlab that I wasn't taught (I mainly use Matalab for data plotting as a materials engineer) so I've been trying to make sense of how/when to properly use them in. I still have a long way to go regarding best practice.
Again, thanks for all the great feedback and the great result below! (now just waiting for Matalab to finally make a xxaxis function.)
Looks good!

Sign in to comment.

More Answers (0)

Categories

Find more on 2-D and 3-D Plots in Help Center and File Exchange

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!