MATLAB Answers

0

How can I periodically update an axes plot in Matlab GUI?

Hello,
I'm trying to create a simple GUI, using GUIDE, which should, upon being started through a proper "Start" button, periodically check if a CSV file has been modified (by an external program, which already takes care of avoiding that Matlab reads it while it is still being written) with new data and, if it has been modified, parse that data and automatically display it in the GUI.
Following this post I created a timer which elapses every 100 ms (which will be more fine-tuned later on). The "Start" and "Stop" buttons are working properly, but I can't seem to be able to update the "axes" element with the plot.
This is my code:
function varargout = automatic_plot(varargin)
% ....
% Edit the above text to modify the response to help LaTe_plotter
% Last Modified by GUIDE v2.5 19-May-2019 16:26:40
% Begin initialization code - DO NOT EDIT
gui_Singleton = 1;
gui_State = struct('gui_Name', mfilename, ...
'gui_Singleton', gui_Singleton, ...
'gui_OpeningFcn', @LaTe_plotter_OpeningFcn, ...
'gui_OutputFcn', @LaTe_plotter_OutputFcn, ...
'gui_LayoutFcn', [] , ...
'gui_Callback', []);
if nargin && ischar(varargin{1})
gui_State.gui_Callback = str2func(varargin{1});
end
if nargout
[varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT
% --- Executes just before LaTe_plotter is made visible.
function automatic_plot_OpeningFcn(hObject, eventdata, handles, varargin)
% This function has no output args, see OutputFcn.
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% varargin command line arguments to LaTe_plotter (see VARARGIN)
% Choose default command line output for LaTe_plotter
handles.output = hObject;
% Update handles structure
guidata(hObject, handles);
% UIWAIT makes LaTe_plotter wait for user response (see UIRESUME)
% uiwait(handles.figure1);
% --- Outputs from this function are returned to the command line.
function varargout = automatic_plot_OutputFcn(hObject, eventdata, handles)
% varargout cell array for returning output args (see VARARGOUT);
% hObject handle to figure
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
% Get default command line output from handles structure
varargout{1} = handles.output;
% --- Executes on button press in start.
function start_Callback(hObject, eventdata, handles)
% hObject handle to start (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
filepath='C:\FileExchange\prova.csv';
handles.filename=filepath; % Store file path (for now, hardcoded, later on it will be user-selectable)
if(isfile(filepath)==1) % Delete an already existing CSV file
delete(filepath);
end
handles.filedata_prev.date='0';
handles.idx=1;
% Define and start a periodic timer
handles.timer = timer('Name','MyTimer', ...
'Period',0.1, ...
'StartDelay',0, ...
'TasksToExecute',inf, ...
'ExecutionMode','fixedSpacing', ...
'TimerFcn',{@timerCallback,handles});
% Prepare plots
hold on;
handles.PlotA=plot(nan,nan,'o-');
handles.PlotB=plot(nan,nan,'x-');
handles.PlotC=plot(nan,nan,'^-');
handles.PlotD=plot(nan,nan,'d-');
xlabel('Value X');
ylabel('Value Y');
xlim([0 inf]);
ylim([0 inf]);
grid on;
legend('Set 1','Set 2','Set 3','Set 4');
hold off;
guidata(hObject,handles);
start(handles.timer);
% --- Executes on button press in stop.
function stop_Callback(hObject, eventdata, handles)
% hObject handle to stop (see GCBO)
% eventdata reserved - to be defined in a future version of MATLAB
% handles structure with handles and user data (see GUIDATA)
if isfield(handles,'timer')
stop(handles.timer);
plot(nan,nan);
disp('Live plot stopped.');
end
function timerCallback(~,~,handles)
if(~isempty(handles))
if ~isempty(handles)
disp('.'); % This 'disp' was insterted just for debug purposes
if(isfile(handles.filename)==1)
filedata=dir(handles.filename); % Get filename (it is retrieved correctly)
if(strcmp(filedata.date,handles.filedata_prev.date)==0) % Compare the current date with the previous iteration one, if it is different, the file has been modified -> parse new data
disp('New CSV data detected!');
disp(handles.idx);
% The file has been modified, i.e. a new line has been added
csvData=csvreadadvanced(handles.filename,[1 2 3 4 5 10],[1 2 3 4 5 10]); % Custom function (already tested) to read from CSV file
% Depending on some CSV fields, update only the proper plot.
% updateArrays is a function which simply adds the newly read data to the arrays passed as first and second argument,
% and returns the updated X and Y arrays
if(strcmp(csvData.Field1{handles.idx},'A')==1 && strcmp(csvData.Field2{handles.idx},'U')==1)
[xArray,yArray]=updateArrays(get(handles.PlotA,'XData'),get(handles.PlotA,'YData'),csvData,handles.idx);
set(handles.PlotA,'XData',xArray,'YData',yArray);
elseif(strcmp(csvData.Field1{handles.idx},'B')==1 && strcmp(csvData.Field2{handles.idx},'U')==1)
[xArray,yArray]=updateArrays(get(handles.PlotB,'XData'),get(handles.PlotB,'YData'),csvData,handles.idx);
set(handles.PlotB,'XData',xArray,'YData',yArray);
elseif(strcmp(csvData.Field1{handles.idx},'A')==1 && strcmp(csvData.Field2{handles.idx},'R')==1)
[xArray,yArray]=updateArrays(get(handles.PlotC,'XData'),get(handles.PlotC,'YData'),csvData,handles.idx);
set(handles.PlotC,'XData',xArray,'YData',yArray);
else
[xArray,yArray]=updateArrays(get(handles.PlotD,'XData'),get(handles.PlotD,'YData'),csvData,handles.idx);
set(handles.PlotD,'XData',xArray,'YData',yArray);
end
handles.idx=handles.idx+1; % Index of the current line in the CSV: it is stored to avoid reading the same CSV line twice
end
handles.filedata_prev=filedata;
end
%guidata(???,handles); % <- how to save the data (handles.filedata_prev, handles.idx) for the next iteration?
end
end
This code is not working for two main reasons, if I'm not wrong:
1) As the timer callback is executed, I get this error:
Error while evaluating TimerFcn for timer 'MyTimer'
Reference to non-existent field 'PlotA'.
Do you know why I am getting this error? If I put a
disp(handles)
inside the timer callback function I can see that "PlotA" is indeed there.
2) How can I update "handles" inside the timer callback function? During various attempts, I tried putting
function timerCallback(hObject,~,handles)
and
guidata(hObject,handles);
but I costantly got an error:
H must be the handle to a figure or figure descendent.
Thank you very much in advance!

  0 Comments

Sign in to comment.

Products


Release

R2018a

1 Answer

Answer by Geoff Hayes
on 19 May 2019
 Accepted Answer

Francesco - while the following code will pass in the handles structure
handles.timer = timer('Name','MyTimer', ...
'Period',0.1, ...
'StartDelay',0, ...
'TasksToExecute',inf, ...
'ExecutionMode','fixedSpacing', ...
'TimerFcn',{@timerCallback,handles});
it is a copy of the handles structure at the time that you create the timer. So if any other UI control callback updates the structure, the timerCallback will not have that updated structure...it will continue to use the older (out of date) stucture. (Which is why the PlotA fields/members cannot be found since they are added to handles after the timer is created.)
So instead of passing the handles structure, pass the handle to the figure/GUI (usually named figure1)
handles.timer = timer('Name','MyTimer', ...
'Period',0.1, ...
'StartDelay',0, ...
'TasksToExecute',inf, ...
'ExecutionMode','fixedSpacing', ...
'TimerFcn',{@timerCallback,handles.figure1});
Then, in your callback, you would do
function timerCallback(~,~,hFigure)
handles = guidata(hFigure);
if ~isempty(handles)
% etc.
end
And if you need to update the handles structure from within your timer callback, you would just do
guidata(hFigure, handles);
where you use the handle to the figure/GUI (and not the handle to the timer).

  1 Comment

Thank you very much! Following your suggestions, I solved my problem!

Sign in to comment.