Resizeable Legend in Matlab GUI or Legend Scroll Bar - matlab

In Matlab I have a GUI that analyses and plots data on to a plot in my main figure of the GUI. I often have to plot a lot of different data sets though with it and have two main problems:
I cannot set a fixed size area for the legend to be constructed in
I cannot work out how to make the legend text and box scale when the GUI is full screened
One solution I was thinking about is a scroll bar in the legend, is this possible? Hopefully the image below highlights the problem:

Here is a solution that will scale the legend with whatever scaling factor you desire:
close all;
% Generate data
N = 10;
T = 10;
x = rand(T, N);
% How much to scale by
xLegScale = 0.5;
yLegScale = 0.5;
% Plot some data
labels = arrayfun(#(n){sprintf('Legend Entry for Line %i', n)}, 1:N);
plot(x, 'LineWidth', 2);
hLeg = legend(labels);
% Figure out new legend width / height, including a little fudge
legPos = get(hLeg, 'Position');
widthFudgeFactor = 0.1;
legPosNew = legPos;
legPosNew(3:4) = legPosNew(3:4) .* [xLegScale yLegScale];
legPosNew(3) = legPosNew(3) * (1 + widthFudgeFactor);
% Create a new axes that matches the legend axes and copy all legend
% children to it, then delete the legend
axNew = axes('Parent', gcf);
xlim(axNew, get(hLeg, 'XLim'));
ylim(axNew, get(hLeg, 'YLim'));
box(axNew, 'on');
set(axNew, 'Position', legPosNew);
set(axNew, 'XTick', [], 'YTick', []);
copyobj(get(hLeg, 'Children'), axNew)
delete(hLeg);
hLeg = axNew;
% Find text objects inside legend
hLegTexts = findobj('Parent', hLeg, 'Type', 'text');
% Scale font size
legTextFontSize = get(hLegTexts, 'FontSize');
fszScale = mean([xLegScale yLegScale]);
legTextFontSizeNew = cellfun(#(x){fszScale * x}, legTextFontSize);
arrayfun(#(h, fontSize)set(h, 'FontSize', fontSize{:}), hLegTexts, legTextFontSizeNew);
This code creates a new axes that is a facsimile of the original legend axes and does all the position setting work on that. The reason is that the legend object doesn't like being resized smaller than it thinks it should be (presumably there is some code doing this when it resizes, but there is no ResizeFcn property for axes objects, so I can't see a way to disable this functionality aside from making a copy of the axes).
The only thing inside the axes you actually need to scale is the font size: the rest will be scaled automatically due to the use of normalized units.
If this kind of scaling solution doesn't tickle your fancy, then you could do something similar (copy the legend axes children) but add a scrollbar to the new axes (and set its units to something other than normalized so that it doesn't scale its contents when you resize it). You might draw some inspiration for how to do the scrolling from this question.

Related

Resize legend box in octave [duplicate]

I am trying to create a plot in Octave (using v4.4.1 on Windows) using plotyy and putting the legend outside the plot (because the data covers all the usable space inside the graph). The following MVCE should reproduce the issue fairly well:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
timedate = linspace(737310,737313,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
grid on
This the output of the code using the gnuplot graphics toolkit:
As you can see, the legend does not go outside the plot, and the second y axis is not visible (it looks like part of the plot is actually truncated).
I have tried using the qt and fltk graphics toolkits, which give issues of their own:
With qt graphics toolkit
With fltk graphics toolkit
Can anoybody suggest a fix or at least workaround? Does the same issue also happen in MATLAB or is it Octave-specific?
EDIT
Using the suggestion in Tasos' answer, I managed to almost make it work with gnuplot:
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
datetick(ax(1),'x','HH:MM:SS')
datetick(ax(2),'x','HH:MM:SS')
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ax1Pos = get(ax(1), 'position');
ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.73;
ax2Pos(3) = ax2Pos(3) * 0.73;
set(ax(1), 'position', ax2Pos);
set(ax(2), 'position', ax2Pos);
xlabel('Date & time')
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
pos = get(hl,'Position');
pos(1) = 0.9;
set(hl,'Position',pos)
grid on
Which produces:
Apart from the fact that the legend overlays with the second y axis label (which it doesn't on my screen, only when printing to jpg), the problem is that Octave appears to plot two legends on top of each other for some reason: one with the first set of data attached to the first set of axes, and one with the complete set of data, for both axes right on top of the first legend. This is obviously wrong, and trying to set the Visible property of hl to off deletes both legends, not just the one.
UPDATED: deals with both legend placement and OpenGL precision affecting graph.
Regarding the problem of the legend not appearing exactly in the position you want it to, you can manually adjust the position of all axes involved in a figure, to place them exactly where you want.
Regarding the problem of OpenGL being unable to deal with the precision involved when adding small numbers to a large number, plot the graph with only the small numbers involved, and then simply adjust the xticklabels to correspond to the numbers you desire.
Full code below:
% Generate some random data to reproduce the issue
data = rand(1000,10);
data(:,1:8) = data(:,1:8)-0.5;
data(:,9:10) = data(:,9:10)+30;
t_offset = 737310;
timedate = linspace(0,3,size(data,1));
data_labels={'1';'2';'3';'4';'5';'6';'7';'8';'9';'10'};
% Plot the data
figure('Name','MVCE','Position',[300 200 1000 600])
[ax,h1,h2] = plotyy(timedate,data(:,1:8),timedate,data(:,9:10));
set(h2,'Visible','on');
ylim(ax(1),[-1 1])
ylim(ax(2),[20 50])
ylabel(ax(1),'Something')
ylabel(ax(2),'Something else')
title('plotyy graph with legend problem')
[hl,hlo] = legend([h1;h2],data_labels,'location','eastoutside');
set(hl, 'position', get(hl, 'position') .* [0.975, 1, 0.975, 1] )
grid on
ax1Pos = get(ax(1), 'position'); ax2Pos = get(ax(2), 'position');
ax1Pos(3) = ax1Pos(3) * 0.95; ax2Pos(3) = ax2Pos(3) * 0.95;
set(ax(1), 'position', ax2Pos); set(ax(2), 'position', ax2Pos);
XTicks = get(ax(1), 'xtick');
set(ax(1), 'xticklabel', datestr(XTicks + t_offset, 'HH:MM:SS'))
xlabel('Date & time')
set(ax(2), 'xtick', []);
Output:

Labeling plots such that label is aligned with the ylabel outside the axes

Please see the following code which creates a 2 by 2 subplot with some plots:
x = linspace(0,2*pi);
y = sin(x);
hfig = figure('Position',[1317 474 760 729]);
subplot(2,2,1)
plot(x,y)
ylabel('plot1');
subplot(2,2,2)
plot(x,y.^2)
ylabel('plot2');
subplot(2,2,3)
plot(x,y.^3)
ylabel('plot3');
subplot(2,2,4)
plot(x,abs(y))
ylabel('plot4');
in each one, I have added labels by hand in Tools: Edit plot (a) (b) (c) (d) producing this figure:
The problem is, if I resize the plot they are no longer aligned with the ylabel text:
Is there a way to add these labels programmatically and have them automatically align to the ylabel text? I am surprised MATLAB does not have something like this built in already.
Thanks
This is not something that is easy to do without attaching a listener to the figure resize event (see example), and doing some computations related to aspect ratios.
It's not entirely clear what sort of objects your labels are (text or annotation), so I'll just show how to do this programmatically using the text command, which creates labels in axes coordinates (as opposed to figure coordinates). This doesn't solve the problem entirely, but it looks better, possibly to an acceptable degree:
function q56624258
x = linspace(0,2*pi);
y = sin(x);
hF = figure('Position',[-1500 174 760 729]);
%% Create plots
[hAx,hYL] = deal(gobjects(4,1));
for ind1 = 1:3
hAx(ind1) = subplot(2,2,ind1, 'Parent' , hF);
plot(hAx(ind1), x,y.^ind1);
hYL(ind1) = ylabel("plot" + ind1);
end
hAx(4) = subplot(2,2,4);
plot(hAx(4), x,abs(y));
hYL(4) = ylabel('plot4');
%% Add texts (in data coordinates; x-position is copied from the y-label)
for ind1 = 1:4
text(hAx(ind1), hYL(ind1).Position(1), 1.1, ['(' char('a'+ind1-1) ')'], ...
'HorizontalAlignment', 'center');
end
Note several modifications to your code:
The handles returned by some functions that create graphical elements are now stored (mainly: hAx, hYL).
All functions that create graphical elements (subplot, plot, ylabel) now have the target (i.e. parent or container) specified.
I changed the 'Position' of the figure so that it works in my setup (you might want to change it back).

Ezpolar plots function string over polar axes

I'm using Ezpolar function in order to plot some graphics. Three of them have as maximum value 4, but the last one only has a bit more than 2.
When I plot them, the last one is plotted with it's function over it (seems like if ezpolar paints it some pixels after it maxium value used as radius).
Code and Generated Plot
% This subplot is used since I've 4 graphics to draw.
subplot(2,2,4)
ezpolar('0.25*(5 - 4*cosd(-180 * sin(t) ))');
title('D')
If I don't use this subplot, using a complete figure to draw the graphics seems fine. However, since I need to have all four of them together, it results in (will draw only the problematic one, subplot 2,2,4):
As you can see, r = 0.25 (5 - 4...) is plotted just over the polar axes.
Why is it happening? How can I fix it?
The issue appears to be with the position of that annotation and the fact that when you use a subplot, the limits on the radius of the polar plot actually change but the position of the annotation does not.
To combat this, you could actually compute the limits of the axes and change the position of the text to be explicitly outside of the plot.
hax = subplot(2,2,4);
p = ezpolar('0.25*(5 - 4*cosd(-180 * sin(t) ))');
% Get the handle to the label text object
label = findobj(hax, 'type', 'text');
% Figure out the current axes limits
ylims = get(hax, 'ylim');
% Add some padding (as a percent) to the position
padding = 0.2;
set(label, 'Position', [0 ylims(1)*(1 + padding), 0]);
A better way to do this would be to change the Units of the label to use Normalized units (relative to the axes) rather than Data units. This way, it will not change if the axes limits change.
hax = subplot(2,2,4);
p = ezpolar('0.25*(5 - 4*cosd(-180 * sin(t) ))');
% Get the handle to the label text object
label = findobj(hax, 'type', 'text');
set(label, 'Units', 'Normalized', 'Position', [0.5, -0.2]);

How can I show only the legend in MATLAB

I want to show only the legend for a group of data in MATLAB.
The reason I want to do this is that I want to export the legend to .eps, but I only want the legend, not the plots.
Is there a way to turn off the plots and remove them from the figure, but still show just the legend centered?
This seems to do the trick:
plot(0,0,'k',0,0,'.r') %make dummy plot with the right linestyle
axis([10,11,10,11]) %move dummy points out of view
legend('black line','red dot')
axis off %hide axis
There is probably a lot of whitespace around the legend. You could try to resize the legend by hand, or save the plot and use some other program to set the bounding box of the eps.
The chosen solution by Marcin doesn't work anymore for R2016b because MATLAB's legend will automatically gray out invisible plots like this:
Neither turning off the automatic update of the legend nor changing the TextColor property afterwards fixes this. To see that, try Marcin's modified example:
clear all; close all;
figHandle = figure;
p1 = plot([1:10], [1:10], '+-');
hold on;
p2 = plot([1:10], [1:10]+2, 'o--');
legHandle = legend('text1', 'text2');
%turn off auto update
set(figHandle,'defaultLegendAutoUpdate','off');
set(p1, 'visible', 'off');
set(p2, 'visible', 'off');
set(gca, 'visible', 'off');
%set legend text color to black
legHandle.TextColor = [0 0 0];
The result remains the same. (To avoid throwing my laptop through the window) and fix this without zooming, which might leave fragments of the plot in the way, I wrote a function that fixes the legend and saves it to a file (with frame):
function saveLegendToImage(figHandle, legHandle, ...
fileName, fileType)
%make all contents in figure invisible
allLineHandles = findall(figHandle, 'type', 'line');
for i = 1:length(allLineHandles)
allLineHandles(i).XData = NaN; %ignore warnings
end
%make axes invisible
axis off
%move legend to lower left corner of figure window
legHandle.Units = 'pixels';
boxLineWidth = legHandle.LineWidth;
%save isn't accurate and would swallow part of the box without factors
legHandle.Position = [6 * boxLineWidth, 6 * boxLineWidth, ...
legHandle.Position(3), legHandle.Position(4)];
legLocPixels = legHandle.Position;
%make figure window fit legend
figHandle.Units = 'pixels';
figHandle.InnerPosition = [1, 1, legLocPixels(3) + 12 * boxLineWidth, ...
legLocPixels(4) + 12 * boxLineWidth];
%save legend
saveas(figHandle, [fileName, '.', fileType], fileType);
end
Tips for use:
fileType is a string that specifies a valid argument for saveas(), such as 'tif'.
Use it after you have plotted everything you want to appear in your legend, but without extra stuff. I'm not sure if all potential elements of a plot contain an XData member that isn't empty.
Add other types of displayed things that you want deleted, but are not of type line if there are any.
The resulting image will contain the legend, its box, and a little bit of space around it the legend is smaller than the minimum width (see Minimum Width of Figures in MATLAB under Windows). However, this is usually not the case.
Here is a complete example using the function from above:
clear all; close all;
fig = figure;
p1 = plot([1:10], [1:10], '+-');
hold on;
p2 = plot([1:10], [1:10]+2, 'o--');
legendHandle = legend('myPrettyGraph', 'evenMoreGraphs');
saveLegendToImage(fig, legendHandle, 'testImage', 'tif');
I think that you need to "hide" the elements you don't want in your plot, leaving out only the legend. For example,
clear all; close all;
figure;
p1 = plot([1:10], [1:10], '+-');
hold on;
p2 = plot([1:10], [1:10]+2, 'o--');
legend('text1', 'text2');
set(p1, 'visible', 'off');
set(p2, 'visible', 'off');
set(gca, 'visible', 'off');

Matlab: Overlapping subplot titles

I'm using subplot which contains three different plots. Each plot has its own labels and title.
The problem is that I have to maximize the plot when I save it. Otherwise, the texts will overlap each other.
When I maximize it, the subplot's label text will appear little blurry in the image, even if I use ESP format or any vector format.
How can I resolve this issue?
For the title overlap issues, you can produce multiple lines of title text use a cell array of strings as the input parameter of title():
title_text = {'first line', 'second line', 'third line'};
title(title_text);
And it works for label text too.
In addition to Da Kuang's answer, if you would like to keep your titles and labels on the same line, you could change the font size
a = axes;
t = title('My Really Long Title');
l = xlabel('My Really Long x label')
set(t, 'FontSize', 8)
set(l, 'FontSize', 8)
I'm not sure why your labels are blurry, but I can help with the overlap.
I never use subplot when I want to save images (eg. for a paper). What I do instead is create each axes individually, which allows a lot more control over each of them.
Below is a rather general example, which illustrates how to generate an arbitrary grid of axes with much finer control over their placement than subplot allows. Of course, with only 3 axes, you don't really need the loop, but I'm sure you can adapt this to fit your needs.
% first create the figure
figPos = [200 200 800 500];
figure('Color', 'w', 'Position', figPos)
% next, determine how much padding you want on each side of the axes, and in
% between axes. I usually play around with these, and the figure size until
% the layout looks correct.
leftPadding = 50/figPos(3); % the space at the left of the figure
rightPadding = 25/figPos(3); % the space at the right of the figure
horizPadding = 80/figPos(3); % the space between axes (horizontally)
topPadding = 30/figPos(4); % the space at the top of the figure
bottomPadding = 50/figPos(4); % the space at the bottom of the figure
vertPadding = 120/figPos(4); % the space between axes (vertically)
% set up the grid size
nHorizAxes = 2;
nVertAxes = 3;
% figure out how big each axes should be
horizPlotSpace = 1-leftPadding-rightPadding-(nHorizAxes-1)*horizPadding;
vertPlotSpace = 1-topPadding-bottomPadding-(nVertAxes-1)*vertPadding;
width = horizPlotSpace/nHorizAxes;
height = vertPlotSpace/nVertAxes;
myAxes = zeros(nVertAxes, nHorizAxes);
% create some sample data to plot for illustrative purposes
x = linspace(0, 2*pi);
y = sin(x);
for iRow = 1:nVertAxes
for iCol = 1:nHorizAxes
% calculate the position
left = leftPadding+(iCol-1)*(width+horizPadding);
bottom = bottomPadding+(iRow-1)*(height+vertPadding);
position = [left bottom width height];
myAxes(iRow, iCol) = axes('Position', position);
plot(x, y)
xlabel('Test Label')
ylabel('Test Label')
title(sprintf('axes(%d, %d)', iRow, iCol))
end
end
Those answers should help but here are some other things to try, depending on the cause of the overlapping text:
Change the figure's size so there's room for the text. For example:
set(gcf, 'PaperSize', [5 7])
Change the size of the subplots.
s = get(gca, 'Position');
set(gca, 'Position', [s(1), s(2), s(3), s(4) * 0.5])
MATLAB (R2021b) appears to stop updating the size of subplots after the axes function is used to set the current axes. The following code causes the title to be cut off.
sp1 = subplot(2, 1, 1);
sp2 = subplot(2, 1, 2);
axes(sp1) % Set the current axes to the first subplot.
title(sprintf('Hello\nCruel\nWorld'))
On the other hand, if title is called immediately after the first subplot is opened, without using axes, then the title has sufficient space to be completely visible.
sp1 = subplot(2, 1, 1);
title(sprintf('Hello\nCruel\nWorld'))
sp2 = subplot(2, 1, 2);
As a workaround, if you need to set the values for a prior subplot, you can simply pass sp1 as the first argument of the desired function.
sp1 = subplot(2, 1, 1);
sp2 = subplot(2, 1, 2);
title(sp1, sprintf('Hello\nCruel\nWorld'))
Simple you can use below function :
plt.tight_layout()
description: The tight_layout() function in pyplot module of matplotlib library is used to automatically adjust subplot parameters to give specified padding.
note : always use this function before plt.show() function.