Subplot label in matlab figures - matlab

I would like to give the subplots I make a simple label. Unfortunately I'm getting an ugly behavior. Consider the following function:
function h = set_label1(label)
tlh = get(gca, 'Title');
if strcmp(get(tlh, 'String'), '')
title(' ');
end
ylh = get(gca, 'YLabel');
if strcmp(get(ylh, 'String'), '')
ylabel(' ');
end
ylp = get(ylh, 'Position');
x = ylp(1);
tlp = get(tlh, 'Position');
y = tlp(2);
h = text('String', label, ...
'HorizontalAlignment', 'right',...
'VerticalAlignment', 'Baseline', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Position', [x y 0]);
end
Here is a simple test run:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
set_label1('A');
axes(h2);
plot([0 1], [4 5]);
set_label1('B');
The picture I obtain is:
If you resize the figure the labels will not be in the right position anymore. That is fine, I expected it (If you know how to put them back where they belong and you tell us that would make me very happy).
THe problem I'm facing is that I do not want to specify the position of the label in 'data' units.
Instead, I want to use normalized units. So I used modified form of function. Now let us use this:
function h = set_label2(label)
tlh = get(gca, 'Title');
if strcmp(get(tlh, 'String'), '')
title(' ');
end
ylh = get(gca, 'YLabel');
if strcmp(get(ylh, 'String'), '')
ylabel(' ');
end
oldUnits = replace_prop(ylh, 'Units', 'normalized');
ylp = get(ylh, 'Position');
x = ylp(1);
set(ylh, 'Units', oldUnits);
oldUnits = replace_prop(tlh, 'Units', 'normalized');
tlp = get(tlh, 'Position');
y = tlp(2);
set(ylh, 'Units', oldUnits);
h = text('String', label, ...
'HorizontalAlignment', 'right',...
'VerticalAlignment', 'Baseline', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Units', 'normalized',...
'Position', [x y 0]);
end
function oldvalue = replace_prop(handle, propName, newvalue)
oldvalue = get(handle, propName);
set(handle, propName, newvalue);
end
Running the same test:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
set_label2('A');
axes(h2);
plot([0 1], [4 5]);
set_label2('B');
We obtain the exact same picture as before. The only problem is that when we resize it now something bad happens:
The labels are actually in the correct position. But it seems that the 'LooseInset' and 'TightInset' property I used make the axes act as if there is no labels.
Is there any fix for this? Really all I am doing is getting the position of the title and ylabel in normalized units as opposed in data units and this seems to mess it up.
The reason I need to get it in normalized units is so that when we get a 3D plot I can position the label with respect to the title and the zlabel.

For posterity's sake here is the version I decided to go with. It does what I expect it to do, but now I have a problem which I have no idea how to solve. OK, first the good news, here is the function called axes_label.
function c = axes_label(varargin)
if isa(varargin{1}, 'char')
axesHandle = gca;
else
axesHandle = get(varargin{1}{1}, 'Parent');
end
if strcmp(get(get(axesHandle, 'Title'), 'String'), '')
title(axesHandle, ' ');
end
if strcmp(get(get(axesHandle, 'YLabel'), 'String'), '')
ylabel(axesHandle, ' ');
end
if strcmp(get(get(axesHandle, 'ZLabel'), 'String'), '')
zlabel(axesHandle, ' ');
end
if isa(varargin{1}, 'char')
label = varargin{1};
if nargin >=2
dx = varargin{2};
if nargin >= 3
dy = varargin{3};
else
dy = 0;
end
else
dx = 3;
dy = 3;
end
h = text('String', label, ...
'HorizontalAlignment', 'left',...
'VerticalAlignment', 'top', ...
'FontUnits', 'pixels', ...
'FontSize', 16, ...
'FontWeight', 'bold', ...
'FontName', 'Arial', ...
'Units', 'normalized');
el = addlistener(axesHandle, 'Position', 'PostSet', #(o, e) posChanged(o, e, h, dx, dy));
c = {h, el};
else
h = varargin{1}{1};
delete(varargin{1}{2});
if nargin >= 2
if isa(varargin{2}, 'char')
set(h, 'String', varargin{2});
if nargin >=3
dx = varargin{3};
dy = varargin{4};
else
dx = 3;
dy = 3;
end
else
dx = varargin{2};
dy = varargin{3};
end
else
error('Needs more arguments. Type help axes_label');
end
el = addlistener(axesHandle, 'Position', 'PostSet', #(o, e) posChanged(o, e, h, dx, dy));
c = {h, el};
end
posChanged(0, 0, h, dx, dy);
end
function posChanged(~, ~, h, dx, dy)
axh = get(h, 'Parent');
p = get(axh, 'Position');
o = get(axh, 'OuterPosition');
xp = (o(1)-p(1))/p(3);
yp = (o(2)-p(2)+o(4))/p(4);
set(h, 'Units', get(axh, 'Units'),'Position', [xp yp]);
set(h, 'Units', 'pixels');
p = get(h, 'Position');
set(h, 'Position', [p(1)+dx, p(2)+5-dy]);
set(h, 'Units', 'normalized');
end
Ok, so how do we use this crappy function? I made it so that we can have these uses:
% c = axes_label('label')
% Places the text object with the string 'label' on the upper-left
% corner of the current axes and returns a cell containing the handle
% of the text and an event listener.
%
% c = axes_label('label', dx, dy)
% Places the text object dx pixels from the left side of the axes
% and dy pixels from the top. These values are set to 3 by default.
%
% c = axes_label(c, ...)
% Peforms the operations mentioned above on cell c containing the
% handle of the text and the event listener.
%
% c = axes_label(c, dx, dy)
% Adjusts the current label to the specifed distance from the
% upper-left corner of the current axes.
If we perform the same test as before:
figure;
h1 = axes('OuterPosition', [0,0,.5 1]);
set(h1,'LooseInset',get(h1,'TightInset'));
h2 = axes('OuterPosition', [.5,0,.5 1]);
set(h2,'LooseInset',get(h2,'TightInset'));
axes(h1);
plot([0 1], [4 5]);
axes_label('A');
axes(h2);
plot([0 1], [4 5]);
axes_label('B', 250, 250);
Now we obtain what I wanted. Label 'A' is set at the upper-left corner of the axes's Outerbox. And label B I explicitly set it to be 250 pixels from its upper-left corner. Here is a plot:
What I like about this function is that if I were to store the cell returned from it and then I put back I can change the position. For instance if label = axes_label('A'); Then on the command prompt I can do label = axes_label(label, 10, 20); and I will see my label move.
The problem I'm facing now is ralated to the function export_fig
If I try to use this:
export_fig('testing.png', '-nocrop', '-painters');
Then this is the figure I obtain.
This is the reason why I exaggerated with label B. Before I added the event listeners export_fig would do an OK job at printing the labels where I had positioned them. But somehow now export_fig doesn't do what it claims it does. Mainly exporting an image with
Figure/axes reproduced as it appears on screen
If instead we remove the option -painters then we get this:
There is probably a bug in code since I'm not experienced with listeners, so if anyone can fix this behavior and/or can improve on this code please feel free to do so and share it as an answer.

First of all, I like your idea of using the title/y-label to position the text on the upper left corner, clever :)
Now, instead of using normalized units, keep using data units and create an event listener for whenever the title or the y-label change their positions, and use their new values to re-adjust the created text.
So add the following to the end of your set_label1 function:
addlistener(ylh, 'Position', 'PostSet', #(o,e) posChanged(o,e,h,1))
addlistener(tlh, 'Position', 'PostSet', #(o,e) posChanged(o,e,h,2))
and here is the callback function used for both cases (we use the last argument idx to control whether to set x or y coordinate):
function posChanged(src,evt,hTxt,idx)
posLabel = evt.NewValue; %# new position of either title/y-label
posText = get(hTxt, 'Position'); %# current text position
posText(idx) = posLabel(idx); %# update x or y position (based on idx)
set(hTxt, 'Position',posText) %# adjust the text position
end

Related

Brushing multiple plots in one go

I have a series of lines which I would like to mark interactively (using brush). For this purpose, I tried adapting a similar code found here. However that code is not suitable for my use case because it is written with a single line in mind, whereas I have multiple lines (plotted on a single axes).
Hence, I extended the code to achieve my goal as follows:
function testbrushcode()
% Create data
t = 0:0.2:25;
x = sin(t);
y = tan(2.*t);
z = sin(2*t);
% Create figure with points
for i = 1:3
myfig = figure();
m{1} = plot(t, x);
hold on
m{2} = plot(t, y);
m{3} = plot(t, z);
hold off
brush on;
index_vec1{i} = brushed_Data_ids(myfig, m{i});
end
plot(t(index_vec1{1}), x(index_vec1{1}))
hold on
plot(t(index_vec1{2}), x(index_vec1{2}))
plot(t(index_vec1{3}), x(index_vec1{3}))
hold off
end
function [index_vec1] = brushed_Data_ids(myfig, m)
uicontrol('Parent', myfig, ...
'Style', 'pushbutton',...
'String', 'Get selected points index',...
'Position', [5, 5, 200, 30],...
'Units', 'pixels',...
'Callback', {#mycallback, m} ...
);
% ---> Now the user should select the points and click the button 'Get
% selected points index'
waitfor(myfig)
% Display index of selected points once the figure is closed
% disp(selectedPoints);
index_vec1 = [];
for i = 1:length(selectedPoints)
if selectedPoints(i) == 1
index_vec1 = [index_vec1 i];
end
end
end
function mycallback(~, ~, mylineseries)
% Ignore the first 2 function inputs: handle of invoking object & event
% data
assignin('caller', 'selectedPoints', get(mylineseries,'BrushData'))
end
There is a problem in the code stemming from using a for loop - because of it, I have to brush the same data thrice. I want to brush the data once and get the brushed indices (from the unbrushed data) for all three lines.
Try this somewhat simplified code:
function q60017140()
% Create data
t = 0:0.2:25;
x = sin(t);
y = tan(2*t);
z = sin(2*t);
% Create figure with points
hFig = figure('Position', [295,303,1014,626]);
hAx = subplot(1,2,1,'Parent', hFig);
hLines = plot(hAx, t,x, t,y, t,z);
hAx(2) = subplot(1,2,2);
uicontrol('Parent', hFig, ...
'Style', 'pushbutton',...
'String', 'Get selected points index',...
'Position', [5, 5, 200, 30],...
'Units', 'pixels',...
'Callback', {#brushCallback, hLines, hAx} ...
);
brush(hFig, 'on');
end
function brushCallback(~, ~, hLines, hAx)
index_vec = cellfun(#logical, get(hLines,'BrushData'), 'UniformOutput', false);
plot(hAx(2), ...
hLines(1).XData(index_vec{1}), hLines(1).YData(index_vec{1}), ...
hLines(2).XData(index_vec{2}), hLines(2).YData(index_vec{2}), ...
hLines(3).XData(index_vec{3}), hLines(3).YData(index_vec{3}));
end

How to use togglebutton to turn labels in scatterplot on/off

I tried to create a scatter plot with labels for each point:
Now I would like to give the user of the code the possibility to turn the labels on and off.
So far my code looks like this:
x = rand(1,100); y = rand(1,100); pointsize = 30;
idx = repmat([1 : 10], 1, 10) % I used class memberships here
figure(1)
MDSnorm = scatter(x, y, pointsize, idx, 'filled');
dx = 0.015; dy = 0.015; % displacement so the text does not overlay the data points
T = text(x + dx, y +dy, labels);
colormap( jet ); % in my code I use a specific colormap
Button = uicontrol('Parent',figure(2),'Style','toggle','String',...
'labels','Units','normalized','Position',[0.8025 0.82 0.1 0.1],'Visible','on',...
'callback',{#pb_call, MDSnorm, ???});
At the end of my script I then tried to define the pb_call function. I tried out several different versions, they all failed.
I have a rough idea what I need to do. Something like:
function [] = pb_call( ??? )
if get(Button, 'Value')
T --> invisible % ???
else
T --> visible % ???
end
end
How can I modify the above to turn labels on or off as desired?
Here is a working example:
x = rand(1,9); y = rand(1,9); pointsize = 30;
idx = repmat(1 : 3, 1, 3); % I used class memberships here
labels = {'a', 'b', 'c', 'd', 'e', 'f', 'h', 'i', 'j'};
h_fig = figure(1); %Create a figure, an keep the handle to the figure.
MDSnorm = scatter(x, y, pointsize, idx, 'filled');
dx = 0.015; dy = 0.015; % displacement so the text does not overlay the data points
T = text(x + dx, y +dy, labels);
colormap( jet ); % in my code I use a specific colormap
%Add a button to the same figure (the figure with the labels).
Button = uicontrol('Parent',h_fig,'Style','toggle','String',...
'labels','Units','normalized','Position',[0.8025 0.82 0.1 0.1],'Visible','on',...
'callback',#pb_call);
function pb_call(src, event)
%Callback function (executed when button is pressed).
h_fig = src.Parent; %Get handle to the figure (the figure is the parent of the button).
h_axes = findobj(h_fig, 'Type', 'Axes'); %Handle to the axes (the axes is a children of the figure).
h_text = findobj(h_axes, 'Type', 'Text'); %Handle to all Text labels (the axes is the parent of the text labels).
%Decide to turn on or off, according to the visibility of the first text label.
if isequal(h_text(1).Visible, 'on')
set(h_text, 'Visible', 'off'); %Set all labels visibility to off
else
set(h_text, 'Visible', 'on'); %Set all labels visibility to on
end
end
Explanations are in the comments.

How to decrease the size of the legend in a figure?

The following code plots a figure. The code should work on Matlab >= R2014b. I want to remove the space within the legend. How to do this?
x = 0:0.5:10;
figure; hold on;
plot(x,sin(x), 'Marker', 'o');
plot(x,cos(x), 'Marker', 's');
[leg, objs] = legend({'sin', 'cos'}, 'Location', 'SouthWest');
line_start_end = [0.01, 0.4];
line_text_step = 0.01;
% for each line, text object, adjust their position in legend
for i = 1:numel(objs)
if strcmp(get(objs(i), 'Type'), 'line')
% line object
if 2 == numel(get(objs(i), 'XData')) % line
set(objs(i), 'XData', line_start_end);
else % marker on line
set(objs(i), 'XData', sum(line_start_end)/2);
end
else
%text object
text_pos = get(objs(i), 'Position');
text_pos(1) = line_start_end(2) + line_text_step;
set(objs(i), 'Position', text_pos);
end
end
See the following result:
What I want is :
There is a submission named resizeLegend by David J. Mack on File Exchange that does exactly that.
You can replace your loop with just this line:
resizeLegend();
which gives:
As #Sardar Usama suggested, I put my solution here. I hope it is useful for you.
The first answer is nice. However, I finally use the following code to do this task.
close all
x = 0:0.5:10;
figure; hold on;
ph(1) = plot(x,sin(x), 'Marker', 'o');
ph(2) = plot(x,cos(x), 'Marker', 's');
ax = gca;
ax.Box = 'on';
bx = axes('position', [ax.Position(1:2), 0.3, 0.4], 'Box', 'on', 'XTick', [], 'YTick', []);%, 'Color', [0.8549,0.7020,1.0000]);
cx = axes('position', [ax.Position(1:2), 0.3, 0.4], 'Box', 'on', 'XTick', [], 'YTick', []);%, 'Color', [0.8549,0.5020,1.0000]);
[legb, objsb] = legend(bx, ph, {'sin', 'cos'}, 'Location', 'SouthWest');
[legc, objsc] = legend(cx, ph, {'sin', 'cos'}, 'Location', 'SouthWest');
line_start_end = [0.01, 0.3];
line_text_step = 0.05;
legendshrink(ax.Position(1:2), legb, objsb, bx, line_start_end, line_text_step);
legendshrink(ax.Position(1:2), legc, objsc, cx, line_start_end, line_text_step);
% you need only to adjust cx' position.
cx.Position(1:2) = [ax.Position(1)+ax.Position(3)-cx.Position(3), ax.Position(2)];
legc.Position(1:2) = cx.Position(1:2);
where legendshrink is defined as:
function legendshrink(leg_pos_xy, leg, objs, bx, line_start_end, line_text_step)
% leg_post_xy = ax.Position(1:2);
% [leg, objs] = legend(bx, line_handles, text_cell);
% line_start_end = [0.01, 0.4];
% line_text_step = 0.01;
% for each line, text object, adjust their position in legend
for i = 1:numel(objs)
if strcmp(get(objs(i), 'Type'), 'line')
% line object
if 2 == numel(get(objs(i), 'XData')) % line
set(objs(i), 'XData', line_start_end);
else % marker on line
set(objs(i), 'XData', sum(line_start_end)/2);
end
else
%text object
text_pos = get(objs(i), 'Position');
text_pos(1) = line_start_end(2) + line_text_step;
set(objs(i), 'Position', text_pos);
end
end
% get minimum possible width and height
legend_width_no_right = 0;
for i = 1:numel(objs)
% legend margin left
if strcmp(get(objs(i), 'Type'), 'line')
if numel(get(objs(i), 'XData')) == 2
leg_margin_x = get(objs(i), 'XData');
leg_margin_x = leg_margin_x(1)*leg.Position(3);
end
else
cur_right = get(objs(i), 'Extent');
cur_right = (cur_right(1)+cur_right(3))*leg.Position(3);
if cur_right > legend_width_no_right
legend_width_no_right = cur_right;
end
end
end
legend_width = legend_width_no_right + leg_margin_x;
legend_height = leg.Position(4);
bx.Position = [leg_pos_xy, legend_width, legend_height];
leg.Position(1:2) = bx.Position(1:2);
leg.Box = 'off';
end
resulting

GUI Axes Handle Hold On/Off Not Working Within Callback Function - Matlab

Firstly, programatically, I created and axes object, then an empty scatter which is subsequently populated with data from within another callback function named guiList which works fine. However, as my title states, I cannot seem to be able to get the hold function to work when I pass my axes_h handle into my callback function 'guiHold`.
Here is my scripted code for axes and scatter:
%% create empty scatter plot with panel
e_panel_position = [0.1 0.3 0.5 0.6];
e_panel_h = uipanel('Parent', fig_h, 'Title','Emotion','FontSize',12,'FontWeight', 'bold','BackgroundColor','white','Position',e_panel_position);
axes_position = [0.15 0.12 0.7 0.8];
axes_h = axes('Parent', e_panel_h, 'Position', axes_position);
scatter_h = scatter(axes_h, [],[], 'MarkerEdgeColor',[0 .5 .5], 'MarkerFaceColor',[0 .7 .7],'LineWidth',1.5);
axis(axes_h, [-4 4 -4 4]);
xlabel(axes_h, 'Valence', 'FontSize', 12); % 'FontWeight', 'bold'
ylabel(axes_h, 'Arousal', 'FontSize', 12);
grid on
box on
Here is my guiHold function:
function guiHold(hold_toggle_h, evt, axes_h)
button_state = get(hold_toggle_h,'Value');
if button_state == 1
hold(axes_h, 'on')
%hold on
elseif button_state == 0
hold(axes_h, 'off')
%holf off
end
end
As you may see, I have tried alternative versions of hold to try and make this
happen.
guiHold is being invoked with a GUI toggle button.
EDIT:
Here is my guiList function that is invoked when you select an item from my list_h handle:
function guiList(list_h, evt, scatter_h)
global predict_valence
global predict_arousal
val = get(list_h, 'value');
a = 100;
x = predict_valence;
y = predict_arousal;
N = length(predict_valence);
for i=1:N
if i == val
set(scatter_h, 'XData', x(i), 'YData', y(i), 'SizeData', a);
end
end
end

Matlab vertical line keeps appearing while using hold on and area()

It is beginning to annoy me that I can't get rid of this vertical line that keeps appearing when I plot the area;
[x y] = ginputExtra(4)
x = 0.1947 0.6118 0.8329 0.4136
y = 0.5746 0.8173 0.4225 0.3553
area([x x(1)],[y y(1)])
[x y] = ginputExtra(4,true)
x = 0.5087 0.6881 0.4954 0.3204
y = 0.4961 0.2382 0.1566 0.3566
hold on;
area([x x(1)],[y y(1)],'FaceColor',[1 0 0])
Is there any way to avoid this line?
BTW: the ginputExtra method call I use..
function [x y] = ginputExtra(n,booText)
% INPUT
% n: Number of points to plot
% booText: Boolean (default false) command to display point number in
% the plot.
% Author: Lasse Nørfeldt (Norfeldt)
% Date: 2012-04-09
if nargin ==2
bText = booText;
else
bText = false;
end
H = gca;
set(H, 'YLimMode', 'manual'); set(H, 'XLimMode', 'manual');
set(H, 'YLim', get(H,'YLim')); set(H, 'XLim', get(H,'XLim'));
numPoints = n; xg = []; yg = [];
for i=1:numPoints
[xi yi] = ginput(1);
xg = [xg xi]; yg = [yg yi];
if i == 1
hold on;
plot(H, xg(i),yg(i),'ro');
if bText text(xg(i),yg(i),num2str(i),'FontSize',14); end
else
plot(xg([i-1:i]),yg([i-1:i]),'r');
if bText text(xg(i),yg(i),num2str(i),'FontSize',14); end
end
end
hold off;
x = xg; y = yg;
Your issue might be in plotting by area(), as it seems to be primarily for stacking several vecotrs. If you zoom out a bit and see a similar vertical line from the first point in the blue area, the area function is most likely the issue.
The function:
fill([x x(1)],[y y(1)],COLOR)
Might do the trick for you, as it plots a filled polygon.
/Thomas