When creating a GUI using OOP in MATLAB, I am having troubles understanding how to control which Axis of which Tab I want to plot my data in. I attach sample code to aid with my questions
First of all, when the GUI is loaded, only Axis 2 under Tab 2 is titled (Axis 2), the code title('Axis 1'); does not seem to get through. Why is this?
When the code is run, by default there are no plots in Tab1 or Tab2. If you click "Plot Data" while Tab1 is open, then switch to Tab2, you can see the sine curve is plot in Tab2. But according to my code, or at leaste what I am trying to do, is that the sine curve should appear in Axis1 of Tab1.
But when you click on Tab2, and click 'Plot Data', the curve changes to the exponential, which is what I am expecting.
I am still quite new at OOP GUI with MATLA so there might be some simple stuff I am missing. Thanks for any help and comments.
classdef example < handle
properties
Figure;
TabGroupAxis;
TabsAxis;
Axis1;
Axis2;
ButtonPlotData;
DataToPlot;
end
methods
function obj = example()
create(obj)
makeUpData(obj);
end
function create(obj)
obj.Figure = figure('Position',[300 300 640 640]);
obj.TabGroupAxis = uitabgroup(obj.Figure,'Units','pixels','Position',[100 20 600 600]);
obj.TabsAxis(1) = uitab(obj.TabGroupAxis,'Title','Tab1');
obj.TabsAxis(2) = uitab(obj.TabGroupAxis,'Title','Tab2');
obj.Axis1 = axes('Parent',obj.TabsAxis(1),'Units','pixels','Position',[30 20 500 500]);
obj.Axis2 = axes('Parent',obj.TabsAxis(2),'Units','pixels','Position',[30 20 500 500]);
obj.ButtonPlotData = uicontrol(obj.Figure,'Style','pushbutton','String','Plot Data',...
'Callback',#obj.buttonPlotDataCallback);
axis(obj.Axis1);
title('Axis 1');
axis(obj.Axis2);
title('Axis 2');
end
function makeUpData(obj)
obj.DataToPlot(1).x = linspace(0,2*pi);
obj.DataToPlot(1).y = sin(obj.DataToPlot(1).x);
obj.DataToPlot(2).x = linspace(0,2*pi);
obj.DataToPlot(2).y = exp(obj.DataToPlot(1).x);
end
function buttonPlotDataCallback(obj,hObject,eventdata)
activeTab = obj.TabGroupAxis.SelectedTab.Title;
switch activeTab
case 'Tab1'
axis(obj.Axis1);
plot(obj.DataToPlot(1).x,obj.DataToPlot(1).y);
case 'Tab2'
axis(obj.Axis2);
plot(obj.DataToPlot(2).x,obj.DataToPlot(2).y);
end
end
end
end
Long Answer
Excellent question, your example made it really easy for me to understand the problem. I put a break point in the buttonPlotDataCallback and saw that the switch case is operation correctly, the problem was that axis(obj.Axis1) didn't change the focus. Looking at the documentation, that's because that isn't the correct use of axis.
An alternative way to plot the function is to use an overloaded method of plot
case 'Tab1'
plot(obj.Axis1,obj.DataToPlot(1).x,obj.DataToPlot(1).y);
Now we realize that the reason that axis1 is not getting titled is that the call to axis on line 34 is not working either. This can also be fixed by overloading, this time title.
title(obj.Axis1,'Axis 1');
Shorter Answer
Turns out axes does exactly what you are using axis for. You have a typo that didn't get caught because axis is a valid function. change axis(< handle >) to axes(< handle >) and you'll be good to go.
Related
I want a figure with six plots inside; I split it with subplots. For example
for i = 1:12
subplot(3,4,i)
plot(peaks)
title(['Title plot ',num2str(i)])
end
I would like to add two global titles, let's say a global title for the six plots on the left hand side and another title for the six other plots on the right hand side.
I don't have 2018b version, so I cannot use sgtitle('Subplot Title');. Is it possible use suptitle('my title'); somehow?
I can use text() but resizing the window, the two labels move.
You can use annotation for that, with the location of subplots 1 and 3:
for k = 1:12
sp(k) = subplot(3,4,k);
plot(peaks)
title(['Title plot ',num2str(k)])
end
spPos = cat(1,sp([1 3]).Position);
titleSettings = {'HorizontalAlignment','center','EdgeColor','none','FontSize',18};
annotation('textbox','Position',[spPos(1,1:2) 0.3 0.3],'String','Left title',titleSettings{:})
annotation('textbox','Position',[spPos(2,1:2) 0.3 0.3],'String','Right title',titleSettings{:})
I did not test this, but you can get the handle to a subplot object and then perform the title method on this handle. I would also suggest to then apply the title after the loop.
CODE
for k = 1:12
h(k) = subplot(3, 4, i)
plot(peak)
end
title(h(1), 'Left side')
title(h(8), 'Right side') % find out the right index yourself
Remark:
Do not use i or j as iteration variable for they are already defined in the namespace of MATLAB as imaginary unit.
I am writing a code for a real time experiment using psychtoolbox to present the stimulus. In my experiment, I need to show the subject a graph that indicates his performance. I have plotted the graph using this simple code:
% Draw the graph
figure('visible','off','color',[0 0 0]);
pcolor([0 Num_timepoint+2],[-10 0],ones(2,2));
hold on;
pcolor([0 Num_timepoint+2],[0 10],2*ones(2,2));
colormap([79 167 255;255 187 221]/256);
plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
axis([0,Num_timepoint+2,-10,10]);
saveas(gcf,'line_chart.png'); %save it
close(figure);
line_chart=imread('line_chart.png'); %read it
resized_plot=imresize(line_chart,0.5);
imageSize = size(resized_plot);
[imageHeight,imageWidth,colorChannels]=size(resized_plot);
bottomRect = [xCenter-imageWidth/1.5, yCenter+gapdown, xCenter+imageWidth/1.5, yCenter+gapdown+imageHeight];
imageDisplay=Screen('MakeTexture', win0, resized_plot);
Screen('DrawTexture', win0, imageDisplay, [], bottomRect);
Unfortunately, this simple code is very slow. In addition, I couldn't make the graph moving along x axis, as soon as the new value comes.
Any help would be Awesome. Thanks in advance for your efforts.
Why are you saving the figure and redisplaying as an image? Maybe I'm missing something but you should be able to accomplish what you need by updating the existing plot with the fresh data using the handles properties of the plot:
.... First time through we need the initial plot ....
% Set up figure with colormaps and such but leave as 'visible','on'
hPlot = plot(plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
hAxes = gca;
.... Loop over your real time updates ....
% Assuming value0 has subject's results from t=0 to t=now
hPlot.XData = value0; hPlot.YData = [1:subloop];
hAxes.XLim = [0 numTimePoints+2];
hAxes.YLim = [-10 10]
.... Continue test and update value0 ....
I think that should keep your plots current without having to save the figure as image to file then reopen the image to display to subject.
If you want to move your data one sample, you can use the circshift function. For example, if you want your new values to appear on the left hand side, you can shift all values 1 sample rightward, then add your new value in the first position.
For converting a MATLAB figure to a Psychtoolbox texture, you don't need to save, then load the temporary images. You can instead use the getframe function to capture the MATLAB figure data, which can then be given to MakeTexture to turn it into a Psychtoolbox texture.
I'm not sure what values you're actually using for subloop, value0, etc. but there is an example that I think might be close to what you want. In this example, 30 frames of figures are plotted, with each figure being on screen for 1 second. New data points are generated randomly and appear from the left hand side of the figure.
Depending on the details of your experiment, you may find that this approach is still too slow. You could also create the figure directly via Psychtoolbox drawing methods like DrawLines, etc. though that would require more effort.
try
win0 = Screen('OpenWindow', 0, 0);
Num_timepoint = 100;
subloop = 100;
value0 = zeros(1,100);
num_demo_frames = 30;
% Draw the graph
fig_h = figure('visible','off','color',[0 0 0]);
pcolor([0 Num_timepoint+2],[-10 0],ones(2,2));
hold on;
pcolor([0 Num_timepoint+2],[0 10],2*ones(2,2));
colormap([79 167 255;255 187 221]/256);
plot_h = plot(1:subloop,value0,'*-',...
'color',[0,0,0],...
'LineWidth',1,...
'MarkerSize',5,...
'MarkerEdgeColor','k',...
'MarkerFaceColor',[0.5,0.5,0.5]);
axis([0,Num_timepoint+2,-10,10]);
for f = 1:num_demo_frames
new_value = randn(1,1);
data_values = plot_h.YData;
data_values = circshift(data_values, 1);
data_values(1) = new_value;
plot_h.YData = data_values;
plot_values = getframe(fig_h);
imageDisplay=Screen('MakeTexture', win0, plot_values.cdata);
Screen('DrawTexture', win0, imageDisplay);
Screen('Flip', win0);
WaitSecs(1);
end
sca;
catch e
sca;
rethrow(e);
end
I have added uicontextmenu to the line object. uicontextmenu includes 3 check boxes. whenever I check any of them uicontextmenu disappears. I want uicontextmenu visible for sometime so that i can check multiple boxes and see the change (same as a button group but in uicontextmenu). Is there any solution to this or some other approach?
cmenu=uicontextmenu;
set(he,'uicontextmenu',cmenu);
item1=uimenu(cmenu,'label','Data A','checked','off','callback',#func_a);
item2=uimenu(cmenu,'label','Data B','checked','off','callback',#func_b);
item3=uimenu(cmenu,'label','Data C','checked','off','callback',#func_c);
basically, he is the line object created by plot(x,y) and func_a, func_b, func_c are function to convert property 'checked' to on|off.
This example is greatly inspired by Benoit_11 solution, but a bit refined. I was also under the impression that the 3 different functions in your callback were doing different things so I made the 3 different menus change different properties of the line (instead of changing the same property with different values).
I made the uimenu callback in one single nested function. It decides what to do based on the parameter what2do supplied at the uimenu definition (but feel free to keep 3 separate functions). However, note that the function that toggle the check mark is the same for all uimenu (you don't need a separate function for each of them).
function hf = TestUiContext2
%// Extension of Benoit_11 solution
clear ; clc ; close all
hf = figure ; %// return the handle of the figure
hax = axes; %// Create axes and save handle
plot(rand(20,3)); %// Plot three lines
hcmenu = uicontextmenu; %// Define a context menu; it is not attached to anything
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu, 'Label','Bold line' , 'Callback' , {#uiCallback,'bold'} );
item2 = uimenu(hcmenu, 'Label','Dotted line' , 'Callback' , {#uiCallback,'dots'} );
item3 = uimenu(hcmenu, 'Label','Markers on' , 'Callback' , {#uiCallback,'mark'} );
hlines = findall(hax,'Type','line'); %// Locate line objects
for line = 1:length(hlines) %// Attach the context menu to each line
set(hlines(line),'uicontextmenu',hcmenu)
end
function uiCallback(obj,~,what2do)
hline = gco ;
switch what2do
case 'bold'
toggle_bold_line(hline)
case 'dots'
toggle_dotted_line(hline)
case 'mark'
toggle_markers(hline)
end
%// reposition the context menu and make it visible
set(hcmenu,'Position',get(gcf,'CurrentPoint'),'Visible','on')
toggle_checkmark(obj) %// toggle the checkmark
end
function toggle_checkmark(obj)
if strcmp(get(obj,'Checked'),'on')
set(obj,'Checked','off')
else
set(obj,'Checked','on')
end
end
function toggle_bold_line(hline)
if get(hline,'LineWidth')==0.5
set(hline,'LineWidth',2)
else
set(hline,'LineWidth',0.5)
end
end
function toggle_dotted_line(hline)
if strcmpi(get(hline,'LineStyle'),':')
set(hline,'LineStyle','-')
else
set(hline,'LineStyle',':')
end
end
function toggle_markers(hline)
if strcmpi(get(hline,'Marker'),'none')
set(hline,'Marker','o')
else
set(hline,'Marker','none')
end
end
end
Now you can enjoy ticking all your menu in one go ;)
Here is a workaround which might do the trick for you. That's not too elegant but it seems to work.
The trick is to set the menu 'Visible' property to 'on' in every callback you have (i.e. #func_a, #funct_b and #funct_c). When I run the following example (based on the demo on the Mathworks website) the menu does not disappear when the selection is changed. Notice that I created separate functions for each callback.
Here is the code:
function TestUiContext( ~)
%// Based on example from The Mathworks
%// http://www.mathworks.com/help/matlab/ref/uicontextmenu.html
clear
clc
close all
%// Create axes and save handle
hax = axes;
%// Plot three lines
plot(rand(20,3));
%// Define a context menu.
hcmenu = uicontextmenu;
%// Define the context menu items and install their callbacks
item1 = uimenu(hcmenu,'Label','dashed','Callback',#(s,e) hcb1);
item2 = uimenu(hcmenu,'Label','dotted','Callback',#(s,e) hcb2);
item3 = uimenu(hcmenu,'Label','solid','Callback',#(s,e) hcb3);
%// Locate line objects
hlines = findall(hax,'Type','line');
%// Attach the context menu to each line
for line = 1:length(hlines)
set(hlines(line),'uicontextmenu',hcmenu)
end
%// In the callback of every item/option, set the menu property 'Visible' to 'on'.
function hcb1
set(gco,'LineStyle','--');
set(hcmenu,'Visible','on')
end
function hcb2
set(gco,'LineStyle',':');
set(hcmenu,'Visible','on')
end
function hcb3
set(gco,'LineStyle','-');
set(hcmenu,'Visible','on')
end
end
And 2 screenshots to show what it looks like:
And moving the cursor down:
So as I said, not perfect but hopefully it will do the job for you!
I have some sliders on figure 1, and I have some images on figure 2. I want to do the callbacks for the sliders in a way that, when I change the sliders in figure 1 , the threshold changes and images update automatically in figure 2.
I'm using addlistener to send values for callback function. The problem is when you move slider the active figure is figure 1, and you want to do changes on figure 2.
adding some code for clarification:
M.rgbImage = imread('euhedral-mag-on-po-edge-pseudo-sub-ophitic-rl-fov-4-8mm.jpg');
[rows, columns, numberOfColorBands] = size(M.rgbImage);
F.f = figure; % This is the figure which has the axes to be controlled.
% Now create the other GUI
S.fh = figure('units','pixels',...
'position',[400 400 500 100],...
'menubar','none',...
'name','Image control',...
'numbertitle','off',...
'resize','off');
S.sl = uicontrol('style','slide',...
'unit','pix',...
'position',[60 10 270 20],...
'min',0,'max',255,'val',100,...
'callback',{#sl_call2,S},'deletefcn',{#delete,F.f});
....
lis = addlistener(S.sl,'Value','PostSet',#(e,h) sl_call3(S,F,M));
function sl_call3(S,F,M)
v = get(S.sl,'value');
figure(F.f), subplot(4, 4, 13);
M.redMask = (M.redPlane > v);
imshow(M.redObjectsMask, []);
set(S.ed(2),'string',v);
Create reference to both your figures:
f1=figure(1);
f2=figure(2);
And then when doing the callback pass f2 as a parameter.
In the callback, you'll have get the handle to the second figure.
There's various ways to do that.
You can specify the handle to the second figure at the time of callback-definition:
figure2 = ...;
addlistener(hSlider, ..., #(a,b) changeStuffOn(figure2));
Or during the callback:
function callbackFunction(hObject, evt)
% get the handle to the second figure, e.g. by a tag, or its name
fig2 = findobj(0, 'type', 'figure', 'tag', 'figure2'); %
% do whatever you want with fig2
end
The latter might be somewhat worse in performance, but e.g. has the benefit of working reliably even if figure2 was deleted and recreated and some point.
To avoid the change of focus you'll have to get rid of this line your callback:
figure(F.f)
This explicitly moves the focus to the second figure.
You'll have to use e.g. the imshow(axes_handle, ...) syntax, in order to show the image not in the "current axes".
I have data from simulations in one single .dat file. Depending on certain criteria ('bu') that is contained in one column of the file (#13 here), I want to plot the data with different markers, while also defining the markersize and markerface properties.
What I have is a switch environment for the different cases - defining which markers and properties I want, and all this in a for-loop, to go through all simulation data.
I've tried the following:
for i=1:s1(1)
bu = data1(i,13);
switch bu
case 1
set(h,'kd','MarkerSize',14,'MarkerFaceColor','k');
case 2
set(h,'kd','MarkerSize',14);
case 3
set(h,'k>','MarkerSize',14,'MarkerFaceColor','k');
case 4
set(h,'ks','MarkerSize',14,'MarkerFaceColor','k');
case 5
set(h,'ks','MarkerSize',14);
case 6
set(h,'ko','markersize',14);
case 7
set(findobj(gca,'k^','MarkerSize',14,'MarkerFaceColor','k'));
end
figure(1);
h=plot(Re1(i),A1(i)); hold on
end
First I tried to use a handle 'h', but it said it was undefined, I guess since the h=plot comes later. Then I tried findobj in the last case (which is the case for the first simulation, so this gives the error in the first round), didn't work either ("Incomplete property-value pair" - not sure what it means here).
I also tried putting all these properties in a string like
str=['kd','MarkerSize',14,'MarkerFaceColor','k']
then plot with
h=plot(Re1(i),A1(i),str); hold on
but it doesn't work with/without brackets either.
Now I don't have any further ideas, thankful for any suggestions!
I think the easiest change for you is to put the plot options in a cell array in the switch block. For example:
options = {'kd', 'MarkerSize', 14, 'MarkerFaceColor', 'k'};
Later, when you plot:
plot(x, y, options{:})
Another way I've done it is to set variables and use them in the plot command:
style = 'kd';
markerSize = 14;
markerFaceColor = 'k';
plot(x, y, style, 'MarkerSize', markerSize, 'MarkerFaceColor', markerFaceColor);
There are few different ways to do that, one of them - create all plot objects before hand and then fill them with both data and formatting:
figureHandle = figure;
for i=1:s1(1)
plotHandle(i) = plot(0,0); %just creating valid handle for future here
end;
code above before your for loop with bu switch, and then in your switch
set(ph(i),'kd','MarkerSize',14,'MarkerFaceColor','k', 'Xdata', Re(1), 'Ydata', A1(i));
Approach with str would work too, except you would need two cell arrays - option nad value like that:
firstoption = 'kd';
option = {'MarkerSize','MarkerFaceColor'};
value = {14,'k'};
h=plot(Re1(i),A1(i),firstoption);
for i=1:length(option)
set(h,option{i},value{i});
end;