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
Related
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.
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
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
I have a question concerning my GUI that has bothered me for some hours now and I cannot get it fixed. The function: I load an image, subset it and plot it as as surf plot.
Afterwards I start a GUI, which lets me choose upper left and lower right coordinates of rectangles. The rectangles get drawn onto the surf-plot.
I have two issues: I want to indicate the mouse-clicks by little red patches, the rectangles by red lines.
1)
The little red patches work fine, except the first on is really big and fills almost the whole plot-window. As soon as I choose the second point for the first coordinate, everything goes back to normal and the patches plot in the small manner I want them to.
I debugged the code to see if somehting is wrong with the coordinates, but they seem to be ok!
2)
The drawing of the lines is inaccurate! Especially the first few lines are offset of the mousclick by up to 100 pixels, usually to the left. Sometimes they even move around when adding the next lines. After placing a few rectangles in that way they usually get better and are in place. Any idea why this is?
Here is the code:
function resampdem
clc;clear;clear all
%% read DEMs and header
img1 = imread('srtm_55_06.tif');
% subset
img1 = img1(1:500,1:500);
[x y] = meshgrid(1:500,1:500);
%%
f = figure;
imageHandle = surfl(x,y,img1);
colormap jet
shading interp
view(0,90);
set(imageHandle,'ButtonDownFcn',#ImageClickCallback)
hold on
a = axes;
set(a, 'Visible', 'off');
%# Create controls.
uicontrol('Parent', f, 'Style', 'edit', 'String', 'Input...');
m = 1;
bin = [];
%%% Funktion zur Auswertung des Mouseklicks
helpdlg('Corner: Upper Left');
function ImageClickCallback ( objectHandle , eventData )
% if mod(m,2) == 0
% string = 'Upper Left';
% else
% string = 'Lower Right';
%
% end
% message = sprintf('Corner: %s',string);
% helpdlg(message);
axesHandle = get(objectHandle,'Parent');
coordinates = get(axesHandle,'CurrentPoint');
coordinates = coordinates(1,1:2);
bin(m,1) = coordinates(1)
bin(m,2) = coordinates(2)
patch([bin(m,1)-3 bin(m,1)+3 bin(m,1)+3 bin(m,1)-3], [bin(m,2)+3 bin(m,2)+3 bin(m,2)-3 bin(m,2)-3],'r','Parent',a);
if mod(size(bin,1),2) == 0
resamp_area(bin,m);
end
m = m+1
end
%%% Funktion zum Zeichnen der Rechtecke
function resamp_area(coords,m)
x1 = coords(m-1,1);
x2 = coords(m,1);
y1 = coords(m-1,2);
y2 = coords(m,2);
patch([x1 x1+20 x1+20 x1], [y1 y1 y1-20 y1-20],'w','Parent',a);
%horizontal lines
line([x1, x2], [y1, y1], 'Parent', a, 'Color',[1 0 0], 'LineWidth',2.0);
line([x1, x2], [y2, y2], 'Parent', a, 'Color',[1 0 0], 'LineWidth',2.0);
%vertical lines
line([x1, x1], [y1, y2], 'Parent', a, 'Color',[1 0 0], 'LineWidth',2.0);
line([x2, x2], [y1, y2], 'Parent', a, 'Color',[1 0 0], 'LineWidth',2.0);
%get(t)
end
end
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