Cannot position third y-axis on the right

Hello, I wanted to plot a graph with 2 y-axis on the right side and further out so it doesn't overlap with the previous right y-axis. I used these codes, but the second y-axis (ax2) kept positioning on the left no matter how. Can anyone help me with it?
Months = string({'Jan','Feb','Mar','Apr','May','Jun',...
'Jul','Aug','Sep','Oct','Nov','Dec'});
Mtn_num = 1:12;
Elec_ABC = [1765.151 1939.867 2576.350 2253.700 2247.799 2495.007...
2583.614 2580.019 2609.666 2594.936 1768.655 1979.734];
Temp_ABC = [14.7 16.45 19.41 22.01 24.99 27.56 27.9 27.89 26.86 24.22 20.96 15.92];
GHI_ABC = [87.2 99.9 144.4 134.7 142.1 165.5 168.8 162.8 152.1 137.2 89.3 94.6];
% Plot on the left and right y axes
figure;
ax1 = axes;
yyaxis left;
bar(Elec_ABC,0.6,'FaceColor','#ffa07a');
ylabel('Electricity production / MWh');
ax1.XTickMode = 'manual';
ax1.XTickLabel = Months;
ax1.YLim = [min(ax1.YTick), max(ax1.YTick)];
ax1.YColor = 'k';
grid(ax1,'on');
yyaxis right;
plot(Mtn_num, GHI_ABC, '-x', 'Color', '#507d2a', 'LineWidth', 1.5, 'MarkerSize', 8, 'MarkerFaceColor', '#507d2a');
ylabel('Global Horizontal Irradiation / kWh/m²');
ax1.YColor = '#507d2a';
ax1.YLim = [0 200];
ytick = ax1.YTick;
% create 2nd, transparent axes
ax2 = axes('Position', ax1.Position, 'Color', 'none');
ax2.YAxisLocation = 'right';
plot(ax2, Mtn_num, Temp_ABC, '-o', 'Color', '#191970', 'LineWidth', 1.5, 'MarkerSize', 4, 'MarkerFaceColor', '#191970');
ylabel(ax2, 'Temperature / °C');
ax2.Color = 'none';
ax2.YColor = '#191970';
ax2.XColor = 'none';
hold(ax2, 'on');
ax2.XLim = ax1.XLim;
ax2.YLimMode = 'manual';
ax2.YLim = [0 30];
yl = ax2.YLim;
ax2.YTick = linspace(yl(1), yl(2), length(ytick));
ax2.YTickMode = 'manual';
ax2.YTick = 0:5:30;
% horzontally offset y tick labels
ax2.YTickLabel = strcat({' '}, ax2.YTickLabel);
title('Monthly enery production at ABC Reservoir against GHI and Temperature')

1 Comment

dpb
dpb on 31 Mar 2025
Edited: dpb on 31 Mar 2025
There are a number of File Exchange submittals that will make this job easier...
Each approaches is somewhat differently, but save you the trouble of the additional axis creation and resizing the previous.
ADDENDUM
Although I wasn't thinking about the bar, though; not aware of any that handle that...although could probably fake one to do so.

Sign in to comment.

 Accepted Answer

Just a slightly amended version; I investigated the use of some of the FEX multi-axis submittals to see if might be any simpler. All of them I looked at were actually more trouble to fiddle with because of the bar() than this turned out to be, but I had converted to illustrate the use of categorical in that as well so just ended up looking at the nits of this version a little more.
This one retains spreading the title over the entire window instead of being centered over the smaller, reduced axes. It fiddles with the vertical position and reduces the font size to make fit in the default window size without clipping. Those adjustments can be tweaked for the specific target...
Months=datetime(year(now),[1:12],1,'Format','MMM');
Months=categorical(Months,Months,'ordinal',1);
Elec_ABC = [1765.151 1939.867 2576.350 2253.700 2247.799 2495.007...
2583.614 2580.019 2609.666 2594.936 1768.655 1979.734];
Temp_ABC = [14.7 16.45 19.41 22.01 24.99 27.56 27.9 27.89 26.86 24.22 20.96 15.92];
GHI_ABC = [87.2 99.9 144.4 134.7 142.1 165.5 168.8 162.8 152.1 137.2 89.3 94.6];
% Plot on the left and right y axes
figure;
ax1 = axes;
yyaxis left;
% make axis labels a little smaller for more room for extra axes
%disp([ax1.XAxis.FontSize ax1.YAxis.FontSize])
set([ax1.XAxis;ax1.YAxis],{'FontSize'},{[9]});
%disp([ax1.XAxis.FontSize ax1.YAxis.FontSize])
hB=bar(Months,Elec_ABC,0.6,'FaceColor','#ffa07a');
ylabel('Electricity production / MWh');
ax1.YLim = [min(ax1.YTick), max(ax1.YTick)];
ax1.YColor = 'k';
grid(ax1,'on');
yyaxis right;
plot(Months, GHI_ABC,'-x','Color','#507d2a','LineWidth',1.5,'MarkerSize',8,'MarkerFaceColor','#507d2a');
ylabel('Global Horizontal Irradiation / kWh/m²');
ax1.YColor = '#507d2a';
ax1.YLim = [0 200];
ytick = ax1.YTick;
% create 2nd, transparent axes
% modify to leave room for displaying the axis labels/ticks inside figure boundaries
WIDTH_FACTOR=0.90; % width reduction factor...salt to suit
posn0=ax1.Position; % retrieve current position
posn=posn0; % working copy
posn(3)=posn(3)*WIDTH_FACTOR; % reduce width onlyax1.Position=posn;
ax1.Position=posn; % shrink the present axes
ax2=axes('Position', posn, 'Color', 'none'); % create at reduced width
ax2.YAxisLocation = 'right';
% place hold on here per BK's observation
hold(ax2,'on')
set([ax2.XAxis;ax2.YAxis],{'FontSize'},{[9]});
plot(ax2,Months,Temp_ABC,'-o','Color','#191970','LineWidth',1.5,'MarkerSize', 4,'MarkerFaceColor','#191970');
ylabel(ax2, 'Temperature / °C');
ax2.Color = 'none';
ax2.YColor = '#191970';
ax2.XColor = 'none';
ax2.XLim = ax1.XLim;
ax2.YLimMode = 'manual';
ax2.YLim = [0 30];
yl = ax2.YLim;
ax2.YTick = 0:5:30;
% horzontally offset y tick labels
LABEL_POS_ADJ=1.13; % adjustment for label
LABEL_FIELD_WIDTH=17; % adjustment for label tick
ax2.YAxis.Label.Position=ax2.YAxis.Label.Position.*[LABEL_POS_ADJ 1 1];
ax2.YAxis.TickLabelFormat=sprintf('%%%dg',LABEL_FIELD_WIDTH);
% now the final piece de resistance -- center the big title over whole thing
ax3=axes('Position',posn0,'Color','none','visible','off'); % overall original size
hold(ax3,'on')
set([ax3.XAxis;ax3.YAxis],{'Visible'},{'off'}) % turn x,y axes off
ax3.Visible='on'; % and turn on so title shows
hT=title(ax3,'Monthly energy production at ABC Reservoir versus GHI and Temperature','FontSize',9.5);
hT.Position=hT.Position.*[1 1.01 1];
The default location of the title is on top of the axes upper box probably because the 'hold on' was set before the title was drawn so the inner size wasn't reduced to account for there being a title. I didn't try to go back to that point although it's possible that might solve the problem although when the axes are shrunk to provide the room for the RH labels, it will then be referred to those axes so it would only be a temporary fix just to see if the height is adjusted.

More Answers (3)

Here is another approach for adding a third y-axis that leverages tiledlayout.
tcl = tiledlayout(1,1);
ax1 = axes(tcl);
yyaxis(ax1,'left');
ylabel(ax1, 'Ruler 1');
yyaxis(ax1,'right');
ylabel(ax1, 'Ruler 2');
% Create a second axes just to draw the ruler.
ax2 = axes(tcl);
ax2.Layout.Tile = 'east';
ax2.PlotBoxAspectRatio = [eps 1 1]; % Make the axes really narrow.
ax2.YAxisLocation = 'right';
ylabel(ax2, 'Ruler 3')
Here is your original script, modified to use this approach:
Months = string({'Jan','Feb','Mar','Apr','May','Jun',...
'Jul','Aug','Sep','Oct','Nov',"Dec"});
Mtn_num = 1:12;
Elec_ABC = [1765.151 1939.867 2576.350 2253.700 2247.799 2495.007 ...
2583.614 2580.019 2609.666 2594.936 1768.655 1979.734];
Temp_ABC = [14.7 16.45 19.41 22.01 24.99 27.56 27.9 27.89 26.86 24.22 20.96 15.92];
GHI_ABC = [87.2 99.9 144.4 134.7 142.1 165.5 168.8 162.8 152.1 137.2 89.3 94.6];
% Plot on the left and right y axes
f = figure;
tcl = tiledlayout(1,1);
ax1 = axes(tcl);
yyaxis left
bar(Elec_ABC,0.6,'FaceColor','#ffa07a');
ylabel('Electricity production / MWh');
ax1.XTickMode = 'manual';
ax1.XTick = Mtn_num;
ax1.XTickLabel = Months;
ax1.YLim = [min(ax1.YTick), max(ax1.YTick)];
ax1.YColor = 'k';
grid(ax1,'on');
yyaxis right
plot(Mtn_num, GHI_ABC, '-x', 'Color', '#507d2a', 'LineWidth', 1.5, 'MarkerSize', 8, 'MarkerFaceColor', '#507d2a');
ylabel('Global Horizontal Irradiation / kWh/m²');
ax1.YColor = '#507d2a';
ax1.YLim = [0 200];
ytick = ax1.YTick;
title(tcl,'Monthly enery production at ABC Reservoir against GHI and Temperature','FontSize',9)
% create 2nd, transparent axes
ax2 = axes(tcl);
plot(ax2, Mtn_num, Temp_ABC, '-o', 'Color', '#191970', 'LineWidth', 1.5, 'MarkerSize', 4, 'MarkerFaceColor', '#191970');
ax2.Visible = 'off';
hold(ax2, 'on');
ax2.XLim = ax1.XLim;
ax2.YLimMode = 'manual';
ax2.YLim = [0 30];
yl = ax2.YLim;
% Create a 3rd axes that has zero width, just to draw the Y-Ticks.
ax3 = axes(tcl);
ax3.Color = 'none';
ax3.YColor = '#191970';
ax3.XColor = 'none';
ax3.YAxisLocation = 'right';
ax3.Layout.Tile = 'east';
ax3.PlotBoxAspectRatio = [eps 1 1];
ax3.YLim = yl;
ax3.YTick = linspace(yl(1), yl(2), length(ytick));
ax3.YTickMode = 'manual';
ylabel(ax3, 'Temperature / °C');

5 Comments

Much more better approach, @Benjamin Kraus! I had just come back with the thought of whether could make tiled layout work similarly...but you beat me to it! :J)
With the conversion to categorical dates to save some extra manual labelling and an attempt to center the title...
Months=datetime(year(now),[1:12],1,'Format','MMM');
Months=categorical(Months,Months,'ordinal',1);
Elec_ABC = [1765.151 1939.867 2576.350 2253.700 2247.799 2495.007 ...
2583.614 2580.019 2609.666 2594.936 1768.655 1979.734];
Temp_ABC = [14.7 16.45 19.41 22.01 24.99 27.56 27.9 27.89 26.86 24.22 20.96 15.92];
GHI_ABC = [87.2 99.9 144.4 134.7 142.1 165.5 168.8 162.8 152.1 137.2 89.3 94.6];
% Plot on the left and right y axes
f = figure;
tcl = tiledlayout(1,1);
ax1 = axes(tcl);
yyaxis left
bar(Months,Elec_ABC,0.6,'FaceColor','#ffa07a');
ylabel('Electricity production / MWh');
ax1.YLim = [min(ax1.YTick), max(ax1.YTick)];
ax1.YColor = 'k';
grid(ax1,'on');
yyaxis right
plot(Months, GHI_ABC, '-x', 'Color', '#507d2a', 'LineWidth', 1.5, 'MarkerSize', 8, 'MarkerFaceColor', '#507d2a');
ylabel('Global Horizontal Irradiation / kWh/m²');
ax1.YColor = '#507d2a';
ax1.YLim = [0 200];
ytick = ax1.YTick;
% create 2nd, transparent axes
ax2 = axes(tcl);
plot(ax2, Months, Temp_ABC, '-o', 'Color', '#191970', 'LineWidth', 1.5, 'MarkerSize', 4, 'MarkerFaceColor', '#191970');
ax2.Visible = 'off';
hold(ax2, 'on');
ax2.XLim = ax1.XLim;
ax2.YLim = [0 30];
yl = ax2.YLim;
% Create a 3rd axes that has zero width, just to draw the Y-Ticks.
ax3 = axes(tcl);
ax3.Color = 'none';
ax3.YColor = '#191970';
ax3.XColor = 'none';
ax3.YAxisLocation = 'right';
ax3.Layout.Tile = 'east';
ax3.PlotBoxAspectRatio = [eps 1 1];
ax3.YLim = yl;
ax3.YTick = linspace(yl(1), yl(2), length(ytick));
ax3.YTickMode = 'manual';
ylabel(ax3, 'Temperature / °C');
title(tcl,'Monthly energy production at ABC Reservoir against GHI and Temperature','FontSize',9)
I haven't explored all the ins and outs of tiledlayout; is there a way to have the title be shared across the whole width, the purpose of my extra axes in the last iteration? From the documentation, I had expected placing title((tcl,...) after the added panel would have done so, but doesn't seem to know anything about the additional one...is that owing to the zero-width aspect ratio,maybe?
@dpb: The title alignment is because we are using the "east" tile for the third axes. The title is aligned with the main body of the tiled layout, and the "east" tile is meant for "decorations" like the legend or colorbar.
I tried a few different options to get the title alignment to include the side axle, and couldn't get any to work the way I would have liked. The issue is that tiledlayout tries really hard to make hte "main body" of each tile equal size, so if you try to move the ruler into a main tile, it causes weird spacing issues, where the narrow axes is center in a tile.
The only solution I could find is a bit more of a hack, which is to leverage tile spanning to make the main axes span multiple tiles.
tcl = tiledlayout(1, 8);
title(tcl,'Monthly energy production at ABC Reservoir against GHI and Temperature','FontSize',9)
ax1 = nexttile(tcl,[1 7]);
yyaxis(ax1,'left');
ylabel(ax1, 'Ruler 1');
yyaxis(ax1,'right');
ylabel(ax1, 'Ruler 2');
% Create a second axes just to draw the ruler.
ax2 = axes(tcl);
ax2.Layout.Tile = 8;
ax2.PlotBoxAspectRatio = [eps 1 1]; % Make the axes really narrow.
ax2.YAxisLocation = 'right';
ylabel(ax2, 'Ruler 3')
dpb
dpb on 1 Apr 2025
Edited: dpb on 1 Apr 2025
Thanks for the exploration, @Benjamin Kraus; that's basically what I inferred from the behavior and your "hack" is a little cleaner than the one I ended up with, although I poked around with subplot locally and did the same thing as yours above with spanning and the narrow second/third axes. I had not thought of nor seen the 'DataAspectRatio' trick for the minimal width to show only the axes; when had done similar in the past had actually set the width, instead. This is also much cleaner/simpler...
Would not a "suptitle" feature be a good enhancement for tiledlayouts if it isn't already in the "todo" list for consideration?
We have sgtitle, which was meant to be the "suptitle" replacement that is built-in to core MATLAB, but using sgtitle with tiledlayout is equivalent to calling title on tiledlayout. I think what is missing here is a setting for the tiledlayout to include the decorations when automatically computing the title alignment. I'll capture this request in our system.
"We have sgtitle, which was meant to be the "suptitle" replacement ..."
I knew there was one, but I could not for the life of me find/remember what is was/is named...and at least in R2021b that have here, it's not in the 'See Also' list...

Sign in to comment.

The issue you are running into is that calling plot resets your YAxisLocation.
ax = axes;
ax.YAxisLocation = 'right';
plot(ax,1:10)
ax.YAxisLocation % left
You either need to:
  • Turn on hold before you call plot or,
  • Set YAxisLocation after calling plot.
ax = axes;
plot(ax,1:10)
ax.YAxisLocation = 'right';
ax.YAxisLocation % right
or
ax = axes;
ax.YAxisLocation = 'right';
hold(ax,'on')
plot(ax,1:10)
ax.YAxisLocation % right

1 Comment

Thank you! such a simple fix I didn't realise it.

Sign in to comment.

dpb
dpb on 31 Mar 2025
Edited: dpb on 31 Mar 2025
Some minor readjustmenst to provide room inside the default figure...
Can adjust just factors I picked for the reduction in width and then paired with that the adjust of the position of the second axes label and ticklabels. This use properties of the label text position and the format string for the tick labels instead of adjusting the string itself...same effect, alternate way to produce the same effect since, unfortumately for this exercise, can't set the postion ot the ticklabels.
I'm not sure why the title appears to be on top of the axes...I didn't pursue that detail. One further refininement that would help the title this way would be to create yet another axis of the default original size (that is, without the width reduction) and write the title to it...that would make the entire figure/axis width available and center it "more better".
Months = string({'Jan','Feb','Mar','Apr','May','Jun',...
'Jul','Aug','Sep','Oct','Nov','Dec'});
Mtn_num = 1:12;
Elec_ABC = [1765.151 1939.867 2576.350 2253.700 2247.799 2495.007...
2583.614 2580.019 2609.666 2594.936 1768.655 1979.734];
Temp_ABC = [14.7 16.45 19.41 22.01 24.99 27.56 27.9 27.89 26.86 24.22 20.96 15.92];
GHI_ABC = [87.2 99.9 144.4 134.7 142.1 165.5 168.8 162.8 152.1 137.2 89.3 94.6];
% Plot on the left and right y axes
figure;
ax1 = axes;
yyaxis left;
bar(Elec_ABC,0.6,'FaceColor','#ffa07a');
ylabel('Electricity production / MWh');
ax1.XTickMode = 'manual';
ax1.XTickLabel = Months;
ax1.YLim = [min(ax1.YTick), max(ax1.YTick)];
ax1.YColor = 'k';
grid(ax1,'on');
yyaxis right;
plot(Mtn_num, GHI_ABC, '-x', 'Color', '#507d2a', 'LineWidth', 1.5, 'MarkerSize', 8, 'MarkerFaceColor', '#507d2a');
ylabel('Global Horizontal Irradiation / kWh/m²');
ax1.YColor = '#507d2a';
ax1.YLim = [0 200];
ytick = ax1.YTick;
% create 2nd, transparent axes
% modify to leave room for displaying the axis labels/ticks inside figure boundaries
WIDTH_FACTOR=0.85; % width reduction factor...salt to suit
posn=ax1.Position; % retrieve current position
posn(3)=posn(3)*WIDTH_FACTOR; % reduce width only
ax1.Position=posn; % shrink the presnet axes
ax2 = axes('Position', posn, 'Color', 'none'); % at reduced with
ax2.YAxisLocation = 'right';
% place hold on here per BK's observation
hold(ax2,'on')
plot(ax2, Mtn_num, Temp_ABC, '-o', 'Color', '#191970', 'LineWidth', 1.5, 'MarkerSize', 4, 'MarkerFaceColor', '#191970');
ylabel(ax2, 'Temperature / °C');
ax2.Color = 'none';
ax2.YColor = '#191970';
ax2.XColor = 'none';
ax2.XLim = ax1.XLim;
ax2.YLimMode = 'manual';
ax2.YLim = [0 30];
yl = ax2.YLim;
ax2.YTick = linspace(yl(1), yl(2), length(ytick));
ax2.YTickMode = 'manual';
ax2.YTick = 0:5:30;
% horzontally offset y tick labels
%ax2.YTickLabel = strcat({' '}, ax2.YTickLabel);
LABEL_POS_ADJ=1.15; % adjustment for label tickax2.YAxis.Label.Position=ax2.YAxis.Label.Position.*[1.15 1 1];
LABEL_FIELD_WIDTH=15; % adjustment for label tick
ax2.YAxis.Label.Position=ax2.YAxis.Label.Position.*[LABEL_POS_ADJ 1 1];
ax2.YAxis.TickLabelFormat=sprintf('%%%dg',LABEL_FIELD_WIDTH);
hT=title(ax1,'Monthly energy production at ABC Reservoir vs GHI and Temperature','fontsize',9);

Categories

Find more on Printing and Saving in Help Center and File Exchange

Asked:

on 31 Mar 2025

Edited:

dpb
on 1 Apr 2025

Community Treasure Hunt

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

Start Hunting!