One out of two of my tabs will not respond and update after initial run of data acquisition

Within my app designer I have two tabs that both collect data. The first tab displays the data on one graph (as shown below)
the second tab displays the data on two graphs(as shown below)
both work fine on the first run but, when I try to change the graph postio, number of channels, calibration... nothing changes
(this is after pressing start and stop)
This problem is only happening with my second tab that displays multiple graphs and its not happening with my first tab that uses one graph.
The plotting functions for each are as follows:
Tab 1(one graph)
function updateLivePlot(app)
if isempty(app.DataFIFOBufferch1) || isempty(app.SelectedChannels)
return
end
% Disable interactivity
disableDefaultInteractivity(app.LiveAxes);
% Keep the colors the same after each new data point
app.LiveAxes.ColorOrderIndex = 1;
% % set the x,y axis and legends
xlabel(app.LiveAxes,"Time (s)")
ylabel(app.LiveAxes, app.Units)
% First-Time Setup
if isempty(app.LivePlotLine)
% Preallocate for efficiency
app.LivePlotLine = gobjects(size(app.UITable.Data(:,1)));
% Plot for the first time
for ii = 1:numel(app.SelectedChannels)
app.LivePlotLine(ii) = plot(app.LiveAxes, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:, ii));
end
% setting the leegnds to match the channels that are sleected
legend(app.LiveAxes,app.legnam(app.SelectedChannels))
else
% Update existing plot
for i = 1:numel(app.SelectedChannels)
% Check if the handle is still valid and within the bounds
if i <= numel(app.LivePlotLine) && ishandle(app.LivePlotLine(i))
% used to update the plotting
set(app.LivePlotLine(i), 'XData', app.TimestampsFIFOBuffer, 'YData', app.DataFIFOBufferch1(:,i));
hold(app.LiveAxes, "on")
else
% Re-plot if handle is invalid or out of bounds
app.LivePlotLine(i) = plot(app.LiveAxes, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:, i));
hold(app.LiveAxes, "on")
end
end
% setting the legend to match the channels that are selected
legend(app.LiveAxes,app.legnam(app.SelectedChannels))
end
if numel(app.TimestampsFIFOBuffer) > 1
xlim(app.LiveAxes, [app.TimestampsFIFOBuffer(1), app.TimestampsFIFOBuffer(end)]);
end
end
Tab 2 (2 graphs)
function updateLivePlot2(app)
disp(app.graphnums)
if isempty(app.DataFIFOBufferch1) || isempty(app.SelectedChannels)
return
end
% Disable interactivity
disableDefaultInteractivity(app.LiveAxes_2);
disableDefaultInteractivity(app.LiveAxes_3);
% Keep the colors the same after each new data point
app.LiveAxes_2.ColorOrderIndex = 1;
app.LiveAxes_3.ColorOrderIndex = 1;
% set the x,y axis and
xlabel(app.LiveAxes_2,"Time (s)")
ylabel(app.LiveAxes_2, app.Units)
% set the x,y axis and
xlabel(app.LiveAxes_3,"Time (s)")
ylabel(app.LiveAxes_3, app.Units)
% First-Time Setup
if isempty(app.LivePlotLine)
% Preallocate for efficiency
app.LivePlotLine = gobjects(size(app.UITable.Data(:,1)));
% Plot for the first time
for ii = 1:numel(app.SelectedChannels)
if app.graphnums(ii) == 1
app.LivePlotLine(ii) = plot(app.LiveAxes_2, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:, ii));
elseif app.graphnums(ii) == 2
app.LivePlotLine(ii) = plot(app.LiveAxes_3, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:, ii));
end
end
% setting the legnds to match the channels that are sleected
for iii = app.SelectedChannels
if app.graphnums(iii) == 1
legend(app.LiveAxes_2,app.legnam(app.SelectedChannels(iii)))
elseif app.graphnums(iii) == 2
legend(app.LiveAxes_3,app.legnam(app.SelectedChannels(iii)))
end
end
else
% Update existing plot
for i = 1:numel(app.SelectedChannels)
if app.graphnums(i) == 1
% Check if the handle is still valid and within the bounds
if i <= numel(app.LivePlotLine) && ishandle(app.LivePlotLine(i))
% used to update the plotting
set(app.LivePlotLine(i), 'XData', app.TimestampsFIFOBuffer, 'YData', app.DataFIFOBufferch1(:,i));
hold(app.LiveAxes_2, "on")
else
% Re-plot if handle is invalid or out of bounds
app.LivePlotLine(i) = plot(app.LiveAxes_2, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:,i));
hold(app.LiveAxes_2, "on")
end
elseif app.graphnums(i) == 2
if i <= numel(app.LivePlotLine) && ishandle(app.LivePlotLine(i))
% used to update the plotting
set(app.LivePlotLine(i), 'XData', app.TimestampsFIFOBuffer, 'YData', app.DataFIFOBufferch1(:,i));
hold(app.LiveAxes_3, "on")
else
% Re-plot if handle is invalid or out of bounds
app.LivePlotLine(i) = plot(app.LiveAxes_3, app.TimestampsFIFOBuffer, app.DataFIFOBufferch1(:, i));
hold(app.LiveAxes_3, "on")
end
end
end
% setting the legend to match the channels that are selected
% also checking to see which graph the legend is to go on
for iii = 1:numel(app.SelectedChannels)
if app.graphnums(iii) == 1
legend(app.LiveAxes_2,app.legnam(app.SelectedChannels(iii)))
elseif app.graphnums(iii) == 2
legend(app.LiveAxes_3,app.legnam(app.SelectedChannels(iii)))
end
end
end
if numel(app.TimestampsFIFOBuffer) > 1
xlim(app.LiveAxes_3, [app.TimestampsFIFOBuffer(1), app.TimestampsFIFOBuffer(end)]);
end
if numel(app.TimestampsFIFOBuffer) > 1
xlim(app.LiveAxes_2, [app.TimestampsFIFOBuffer(1), app.TimestampsFIFOBuffer(end)]);
end
end
The following is what I use to update the graphs from the UItable
try
tic
if app.tabstate == 1
% Update the Legends on the Axes based on the channel names with their default names and/or renamed names
app.legnam = table2array(app.UITable.Data(:,1));
% Update SelectedChannels based on ofchan
app.SelectedChannels = find(table2array(app.UITable.Data(:,2)));
% Update SelectedCal based on IndvCal
app.SelectedCal = find(table2array(app.UITable.Data(:,3)));
% checks which values have been checked (for error checking)
app.cals = table2array(app.UITable.Data(:,3));
% Call the function that uses the selected channels (e.g., data calibration)
updateChannelMeasurementComponents(app)
elseif app.tabstate == 2
% Update the Legends on the Axes based on the channel names with their default names and/or renamed names
app.legnam = table2array(app.UITable.Data(:,1));
% Update SelectedChannels based on ofchan
app.SelectedChannels = find(table2array(app.UITable.Data(:,2)));
% Update SelectedCal based on IndvCal
app.SelectedCal = find(table2array(app.UITable.Data(:,3)));
% checks which values have been checked (for error checking)
app.cals = table2array(app.UITable.Data(:,3));
app.graphnums = (table2array(app.UITable.Data(:,4)));
disp(app.graphnums)
assignin("base","graphnums",app.graphnums)
% Call the function that uses the selected channels (e.g., data calibration)
updateChannelMeasurementComponents(app)
end
toc
catch exception
%In case of error show it and revert the change
uialert(app.LiveDataAcquisitionUIFigure, exception.message, 'Selected channels too fast, Please press restore default buttons to avoid any other error and select channels at a slower speed. Thanks');
setAppViewState(app, 'configuration');
app.legnam = table2array(app.UITable.Data(:,1));
app.SelectedChannels = find(table2array(app.UITable.Data(:,2)));
app.SelectedCal = find(table2array(app.UITable.Data(:,3)));
app.cals = table2array(app.UITable.Data(:,3));
app.graphnums = (table2array(app.UITable.Data(:,4)));
end
After using breakpoints and the debugging tool I can see that it is reading in the new values which is leaving me quite confued on what the problem might be. I also was having difficulty with setting up the multiple graphs, any and all help is appreciated!

9 Comments

after checking, I now know that the calibration works fine, Just the channels selected and the graph number is not properly updating between data collections.
I think it has something to do with the liveplotline axis handle but am unsure how to "fix it"
I suspect a lot of the problems would go away if you follow the advice I gave in the last paragraph of my answer to your previous question, about a 1:1 correspondence between channels and lines:
I was having issues with how you described it and found that If i just change the data that is coming in and out it would work easier, in essence recreating the 1:1 you had talked about in a different way. I am not great with matlab/coding in general and was honestly having some diffculty trying to execute some of the suggestions you had given. I do feel more confidenent with some things now and will try to look back on what you had talked about before!
Sounds good.
I'll offer some more advice while you are thinking about that, which is to try to forget about the code as it is now (at least the plot updating part) and think about how it would be if you had one line object per channel. For example, if you have 1:1 correspondence between channels and line objects, that plot updating function might be as simple as this:
function updateLivePlot(app)
% update the XData and YData of each line (whether its channel is selected or not)
for i = 1:size(app.UITable.Data,1)
set(app.LivePlotLine(i), 'XData', app.TimestampsFIFOBuffer, 'YData', app.DataFIFOBufferch1(:,i));
end
% update the axes x-limits
if numel(app.TimestampsFIFOBuffer) > 1
xlim(app.LiveAxes, app.TimestampsFIFOBuffer([1 end]));
end
end
Because the lines would already have all been created elsewhere (probably when the app starts), the legend would've been created elsewhere (probably when the app starts), and the lines' visibility would've been updated elsewhere (when the app starts and/or when a channel is selected or de-selected).
Note that that assumes that app.DataFIFOBufferch1 has the same number of columns as app.UITable.Data has rows - that is, one column for each channel (not only selected channels) - which I don't know is the case, and if not, then the 1:1 correspondence is not going to work. That is:
  • if app.DataFIFOBufferch1 contains data from the selected channels only (not all channels), then something like what you are already doing - creating lines as needed, etc. - might be what you have to do. But
  • if app.DataFIFOBufferch1 contains data from all channels (not only selected channels), then you can create all your lines in advance and the function that updates them can be simple.
Of course, I don't know what app.DataFIFOBufferch1 is, so I can't know the answer to that question.
Thanks, I had not thought of that as a possiblity but it makes sense. Currently as is the calibration function is in essence "calibratedData" with a buffer
function calibratedData = calibrateData(app, data, slopes, intercepts)
% We want to make sure all channels selected are included to prevent
% any errors from occuring
calibratedData(:,app.SelectedChannels) = data(:,app.SelectedChannels);
for jj = app.SelectedChannels
% We want to make sure all channels selected are included to prevent
% any errors from occuring because sometimes we still need to plot data that
% isn't calibrated with calibrated data simultaneously
calibratedData(:,jj) = (data(:,jj));
end
for j = app.SelectedCal
% Apply calibration (ie slopes and intercepts from the base
% workspace (not linked to the app designer workspace)
calibratedData(:, j) = (data(:, j) .* slopes(:,j)) + intercepts(:,j);
end
end
I have a varibale that is called numchan that is the number of channels that are read into the app depending on the NI device plugged into it. I think that might work with when your talking about "
if app.DataFIFOBufferch1 contains data from all channels (not only selected channels), then you can create all your lines in advance and the function that updates them can be simple." I might need to rework somestuff with how I save the data to the workspace but I dont think that it will be a problem. I am not quite sure what you mean when you say the lines would be created elsewhere. I have also had to put the legend within the update plotting function before becuase it would not show during the plotting of the data being read in and would only show at the beginning or the end. Do you know a way I can work around this?
I am also a litle lost on how to get my set function to plot to the axes associated with the UItables assignment. Do you know what the best way to go about that would be? Thanks
"I am not quite sure what you mean when you say the lines would be created elsewhere."
Basically your code would create the lines as soon as its known how many channels you have. That may be in the startup function of your app. Or if the number of channels can change while the app is running, then it might be in some function that updates what needs to be updated when the number of channels changes, adding or deleting lines as necessary.
"I have also had to put the legend within the update plotting function"
I'm pretty sure legends can update themselves automatically when lines are created/deleted/made visible/made invisible. The lines' DisplayName is what will show up in the legend, so as long as that is set correctly for each line, the legend would update properly automatically.
If I had your app, I could maybe offer more specific advice or fix up the code a bit, but I'm not sure I'd be able to run/adapt it since I don't have the Data Acquisition Toolbox. What I'll do instead is put together an example app that illustrates how I think your app should run and post an answer with that app. It might be a few hours before I can have it done. In the meantime, try to do what you can.
Thanks, I appreciate it alot! I have attached my app file, not sure if you will be able to run it but I hope it can help in someway.

Sign in to comment.

 Accepted Answer

I'm attaching an app that does kind of a simplified version of what I think your app is meant to do.
You can select from multiple devices, select and deselect channels, change the names of the channels, switch between the single plot tab and dual plot tab, and change whether each channel is plotted in the top axes or the bottom axes in the dual plot tab (double-click in the third column of the table to see the embedded dropdown), all of which is similar to how your app functions. However, I don't have the Data Acquisition Toolbox, so in this app you have to click the "Get (More) Data" button to "acquire" and plot new data.
The idea behind the design of the code is that as soon as the user does anything, whatever needs to be updated is updated immediately. This may be different than how you designed your app (I don't know because I didn't look into it beyond to get the gist of what it does), but in my opinion it makes for a more robust app because you're basically guaranteeing (ideally) that the app is never in a self-inconsistent state. I think your app gets into some self-inconsistent state(s) somehow or other because of how things are updated, and that's ultimately what's causing the problems you've been seeing.
Anyway, please run the attached app, and mess around with it - make sure it behaves like yours is supposed to - and then investigate the code, and try to apply some of the same principles to your app. Let me know if you have any questions.

More Answers (0)

Products

Release

R2023b

Asked:

on 12 Apr 2024

Edited:

on 12 Apr 2024

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!