I am reading data from a serialport using callback terminator:
configureCallback(s,"terminator",#readSerialData)
I want to plot this data in real time. So what I do is to use a global structure of arrays where the gathered data is stored in every new row. There are different arrays because I need to classify different sources of data
I use a function "createfigure" inside the #readSerialData, which plots the array of the corresponding source of incoming data.
However, this becomes extremely laggy, so this must not be the right way to do...
Also, in the graphs I want to see all the arrays plotted in once. But the result is that it only plots the last received array on top of the other plots. So it accumulates lines on top of each other, making the axes illegible
function createfigure2(source,X1,Y1)
% X1: vector of x data
% Y1: vector of y data
% Auto-generated by MATLAB on 21-Jan-2021 13:54:53
% Create figure
figure1 = figure(1);
hold on;
% Create axes
axes1 = axes('Parent',figure1,...
'Position',[0.109498680738786 0.69811320754717 0.800791556728232 0.273584905660377]);
hold(axes1,'on');
% Create plot
plot(X1,Y1,'DisplayName',source,'Parent',axes1,'Marker','*','LineWidth',0.01,...
'LineStyle',':');
end
You can create a plot object (parameter returned when calling the plot function) and send it to the callback itself by setting it as a parameter of "BytesAvailableFcn". Then update the plot directly inside the callback.
function mainFunction()
figure1 = figure();
axes1 = axes('Parent',figure1,...
'Position',[0.109498680738786 0.69811320754717 0.800791556728232 0.273584905660377]);
numParams = 4;
plotObjects = cell(1,numParams);
plotObjects{1} = plot(nan,nan,'r-','parent',axes1);
hold(axes1, 'on');
plotObjects{2} = plot(nan,nan,'g-','parent',axes1);
plotObjects{3} = plot(nan,nan,'b-','parent',axes1);
plotObjects{4} = plot(nan,nan,'k-','parent',axes1);
hold(axes1, 'off');
sp = openSerialPort('COM9', plotObjects);
...
end
function sp = openSerialPort(portName, plotObjects)
% intialize your com port here
sp = serial(portName);
sp.BaudRate = 9600;
...
sp.BytesAvailableFcn = {#plotMyData, plotObjects}
end
function plotMyData(sp, ~, plotObjects)
% read the data from the serial port...
[dataVectX, dataVectY] = get_data_from_serial...
% This considers that dataVectX and Y contains the readings of 4...
% different sensors where dataVectX(1) is the first sensor, dataVectX(2) is...
% the second sensor, and so one.
% update data
for paramID = 1:4
plotObjects{paramID}.XData = dataVectX(paramID);
plotObjects{paramID}.YData = dataVectY(paramID);
end
% if you wanted to append the data instead of replacing it you can either use
% plotObjects{paramID}.XData = [plotObjects{paramID}.XData, newXVector]; % use this if x is a vector
% plotObjects{paramID}.YData = [plotObjects{paramID}.YData, newYVector];
% or
% plotObjects{paramID}.XData(end+1) = dataVectX(paramID); % use this if x is a single value
% plotObjects{paramID}.YData(end+1) = dataVectY(paramID);
drawnow();
end
Related
I have a MATLAB figure with an axes containing a scatter plot.
Every point on this scatter plot has a signal data array associated with it.
I want to take user input as point selection from scatter plot and plot the corresponding signal data in another axes on the same figure.
Combine a global definition of your second axes with an UpdateFcn on the datacursor. See example below, which generates a sine wave based on a random variable selected.
function getSelectedDataPoint()
% create figure
fig = figure;
% make second axes a global to adress in myupdatefcn
global ax2
% define axes
ax1 = axes('parent',fig,'position',[0.05 0.05 0.9 0.4]);
ax2 = axes('parent',fig,'position',[0.05 0.55 0.9 0.4]);
% Random scatter
scatter(ax1,rand(25,1),rand(25,1),25,'filled')
% Set datacursormode to on
dcm_obj = datacursormode(fig);
datacursormode on
% Specify objective function for clock
set(dcm_obj,'UpdateFcn',#myupdatefcn)
% Define objective function
function text = myupdatefcn(~,obj)
text = sprintf('X: %f \n Y: %f',[obj.Position(1),obj.Position(2)]);
% Find corresponding signal
id = find(and(obj.Position(1) == obj.Target.XData,obj.Position(2) ==
obj.Target.YData));
% Do your thing with the signals
x = 0:0.1:100;
y = sin(obj.Target.XData(id)*x);
% plot on second axes
plot(ax2,x,y)
end
end
I am not sure I entirely understand what you want to do, but you might want to use the "Tag" option available for many objects in MATLAB.
Replace line under "Random scatter" by:
% Random scatter
hold(ax1,'on')
scatTag = cell(1,10);
for i = 1:10
scatTag{i} = scatter(ax1,rand(1,1),rand(1,1),25,'filled');
scatTag{i}.Tag = num2str(i);
end
In the data cursor update function, replace the line "id= find(..."
tagname = obj.Target.Tag;
And modify the signal function to point to your target tag, whatever function that is. In my example, you could do this to define the y-values:
y = sin(str2double(tagname)*x);
It will generate another sine wave, based on the tag.
Hope this helps :)
I currently want to have the legend of graph, however i'm plotting several lines that should be group in only 3 types.
My currently option is to use a dummy plot out of the boundaries, plotting the relevant data and calling the legend just at the end. It works but it is prone to errors. I wanted to update the legend and select just a few of the plots.
I tried to use the leg_handle.String, but then it comes two problems:
It still plot 5 handles instead of 3.
It does not have the proper line style & color.
Any ideas?
Bellow follow the code (with dummy plot commented) and the pictures of the current version giving the error and what i want to look.
clear
figure()
hold on
%using
%dummy plot
% leg_text={'a','b','c'};
% plot(100,100,'-r')
% plot(100,100,'-b')
% plot(100,100,'-k')
for ii=1:20,
plot(1:11,linspace(0,ii,11),'-r')
end
for ii=30:50,
plot(1:11,linspace(0,ii,11),'-b')
end
for ii=70:80,
plot(1:11,linspace(ii,25,11),'-k')
end
Yaxl=[-1 80];
Xaxl=[1 11];
set(gca, 'Visible','on', ...
'Box','on', ...
'Layer','top',...
'Xlim',Xaxl, ...
'Ylim',Yaxl);
%using
% legend(leg_text)
%want to use
leg_hand=legend(gca,'show');
leg_hand.String=leg_hand.String([1 21 42]);
%extra comand will give the things that i wanted above
% leg_hand.String=leg_hand.String([1 2 3]);
What it gives:
What I expect to have:
I have tried this method using [a,b,c,d]=legend, but this give only the a handle that i already using.
This little workaround should do the job:
clear();
figure();
hold on;
h = gobjects(3,1);
for ii = 1:20
h(1) = plot(1:11,linspace(0,ii,11),'-r');
end
for ii = 30:50
h(2) = plot(1:11,linspace(0,ii,11),'-b');
end
for ii = 70:80
h(3) = plot(1:11,linspace(ii,25,11),'-k');
end
set(gca,'Box','on','Layer','top','Visible','on','Xlim',[1 11],'Ylim',[-1 80]);
legend(h,'A','B','C');
hold off;
Actually, what I did is very simple. I created an array of graphical objects of size 3 (one for each iteration) using the gobjects function. Then, inside each iteration, I assigned the last plotted line to its respective array placeholder. Finally, I created the legend using the three graphical objects I previously stored.
Alternatively:
clear();
figure();
hold on;
h1 = gobjects(20,1);
for ii = 1:20
h1(ii) = plot(1:11,linspace(0,ii,11),'-r');
end
h2 = gobjects(21,1);
for ii = 30:50
h2(ii-29) = plot(1:11,linspace(0,ii,11),'-b');
end
h3 = gobjects(11,1);
for ii = 70:80
h3(ii-69) = plot(1:11,linspace(ii,25,11),'-k');
end
set(gca,'Box','on','Layer','top','Visible','on','Xlim',[1 11],'Ylim',[-1 80]);
legend([h1(1) h2(1) h3(1)],'A','B','C');
hold off;
You create an array of graphical objects for storing the plot handlers produced by every iteration. Then you create the legend using the first (basically, any) item of each array of graphical objects.
Given a .fig file of a Matlab boxplot (i.e. underlying data not available), is it possible to change the PlotStyle attribute (from 'traditional' to 'compact')?
This question is kind of tricky because not like other graphic objects in Matlab, boxplot is a group of lines. As so, all the properties that are set while you create it are inaccessible (and in fact does not exist) after plotting.
One option to deal with that is to create a 'dummy' boxplot, and then alter it to your data. Because boxplot has no simple properties of XData and YData, at least not as we use to them, it takes some work to do that.
Here is a short code to demonstrate that:
% this is just to make a figure for example:
X = normrnd(10,1,100,1);
boxplot(X) % this is the 'Traditional' figure that you load
% you start here, after you load your figure:
bx = findobj('tag','boxplot');
% get the properties of the axes:
axlimx = bx.Parent.XLim;
axlimy = bx.Parent.YLim;
% get all needed properties for plotting compact box plot
txt = bx.Parent.XAxis.TickLabels;
med = get(findobj(bx,'tag','Median'),'YData'); % Median
out = get(findobj(bx,'tag','Outliers'),'YData'); % Outliers
box = get(findobj(bx,'tag','Box'),'YData'); % the Box
whis = cell2mat(get([findobj(bx,'tag','Lower Whisker')...
findobj(bx,'tag','Upper Whisker')],'YData')); % Whisker
minmax = #(R) [min(R(:)) max(R(:))]; % helper function
close all
% Now we closed the original figure, and create a new one for manipulation:
boxplot(normrnd(10,1,100,1),'PlotStyle','Compact');
bxc = findobj('tag','boxplot');
% set the properties of the axes:
bxc.Parent.XLim = axlimx;
bxc.Parent.YLim = axlimy;
% set all properties of the compact box plot:
bxc.Children(1).String = txt;
set(bxc.Children(2),'YData',out) % Outliers
set(bxc.Children(3:4),'YData',med(1)) % MedianInner & MedianOuter
set(bxc.Children(5),'YData',minmax(box)) % the Box
set(bxc.Children(6),'YData',minmax(whis)) % Whisker
Another way to alter the boxplot to 'compact' style, is to change the graphics directly. In this case, we don't create a new dummy figure but work on the loaded figure.
Here is a code for that approach:
% this is just to make a figure for example:
X = normrnd(10,1,100,1);
boxplot(X) % this is the 'Traditional' figure that you load
% you start here, after you load your figure:
bx = findobj('tag','boxplot');
minmax = #(R) [min(R(:)) max(R(:))]; % helper function
% get the whisker limits:
whis = cell2mat(get([findobj(bx,'tag','Lower Whisker')...
findobj(bx,'tag','Upper Whisker')],'YData')); % Whisker
set(findobj(bx,'tag','Upper Whisker'),{'YData','Color','LineStyle'},...
{minmax(whis),'b','-'})
% set the median:
set(findobj(bx,'tag','Median'),{'XData','YData','LineStyle','Marker',...
'Color','MarkerSize','MarkerFaceColor'},...
{1,min(get(findobj(bx,'tag','Median'),'YData')),'none','o','b',6,'auto'});
% set the box:
set(findobj(bx,'tag','Box'),{'XData','YData','LineWidth'},...
{[1 1],minmax(get(findobj(bx,'tag','Box'),'YData')),4});
im_med = copyobj(findobj(bx,'tag','Median'),bx);
im_med.Marker = '.';
% set the outliers:
out = get(findobj(bx,'tag','Outliers'),'YData'); % Outliers
set(findobj(bx,'tag','Outliers'),{'XData','LineStyle','Marker','MarkerEdgeColor',...
'MarkerSize','MarkerFaceColor'},{0.9+0.2*rand(size(out)),'none','o','b',4,'none'});
% rotate x-axis labels:
bx.Parent.XAxis.TickLabelRotation = 90;
% delete all the rest:
delete(findobj(bx,'tag','Lower Whisker'))
delete(findobj(bx,'tag','Lower Adjacent Value'))
delete(findobj(bx,'tag','Upper Adjacent Value'))
I would like to refresh my figure (3D, obtained with the matlab slice command) using a slider (GUI) to update the slice position.
Here is my code to create the figure (it works):
% Create figure
Fig_3dview = figure;
Fig_3dview.Name= 'Microstructure viewer';
% hold on;
% The data to be displayed are within "Volume", a n*m*k matrix
Domain_size = size(Volume)
% Initial position of the slices
% Basically, the 6 faces of the volume
xslice = [1,Domain_size(1)];
yslice = [1,Domain_size(2)];
zslice = [1,Domain_size(3)];
% Display all the slices
imslice = slice(Volume,xslice,yslice,zslice,'parent',Fig_3dview);
% Remove voxel edge
set(imslice,'edgecolor','none')
% Current axis label
ax_3d_slice = gca;
% Set axis equal and tight
axis equal tight
hold off;
The code for the GUI, to control the slice position (it doesn't work):
% Create GUI figure
Fig_3dview_GUI = figure;
Fig_3dview_GUI.Name= 'Microstructure viewer 3D GUI';
% Create slider
h_slice1 = uicontrol('Style','slider','BackgroundColor','w',...
'Unit','pixels','Position',[10,200,250,50],'Parent',Fig_3dview_GUI,...
'Callback',{#slider_slice1_Callback});
% Define function associated with h_slice1
function slider_slice1_Callback(source,eventdata)
% Determine the selected data set.
pos_normalized = source.Value;
position_slice_1= round(pos_normalized*Domain_size(1)); % Get closest integer value
position_slice_1=max(1,position_slice_1); % 0 must be avoided
% Update figure
And here I don't know how to update the figure Fig_3dview with the new position for the slice normal to the first direction.
I would like the new figure looks like:
xslice = [position_slice_1,Domain_size(1)];
imslice = slice(Volume,xslice,yslice,zslice,'parent',Fig_3dview);
I do not want to close the figure and regenerate it, I want to refresh it, so that I get a smooth visualization.
% Update figure
end
Thanks for any advice.
François
I am collecting data and plotting that data in real time. The data are produced by a motion capture system. I have one class DynamicDataset that is just a wrapper around a 2-column matrix (although it's more nuanced than that) with an event notifier for new data added; another class DynamicPlotter that listens for the data-added event and updates the plot dynamically. Appropriate code snippets:
classdef DynamicDataset < handle
properties
newestData = [];
data = []
end
events
DataAdded
end
methods
function append(obj, val)
obj.data(end+1,:) = val;
obj.newestData = val;
notify(obj, 'DataAdded');
end
end
end
classdef DynamicPlotter < dynamicprops
properties
FH %# figure handle
AH %# axes handle
LH %# array of line handles - may have multiple lines on the plot
dynProps = {} %# cell array of dynamic property names -
%# use to access individual datasets
end
methods
function obj = DynamicPlotter(props) %# props is a cell array of dynamic
%# properties to store information
for i = 1:length(props)
addprop(obj, props{i});
obj.(props{i}) = DynamicDataset;
obj.dynProps = [obj.dynProps props{i}];
addlistener(obj.(props{i}), 'DataAdded', #obj.updatePlot(i));
end
obj.createBlankPlot();
end
function createBlankPlot(obj)
obj.FH = figure;
obj.AH = axes;
hold all;
for i = 1:length(obj.dynProps)
obj.LH(i) = plot(nan); %# only used to produce a line handle
set(obj.LH(i), 'XData', [], 'YData', []);
end
end
function updatePlot(obj, propNum)
X = get(obj.LH(propNum), 'XData');
Y = get(obj.LH(propNum), 'YData');
X(end+1) = obj.(dynProps{propNum}).newestData(1);
Y(end+1) = obj.(dynProps{propNum}).newestData(2);
set(obj.LH(propNum), 'XData', X, 'YData', Y);
end
end
end
Based on the MATLAB Code Profile, the set command in updatePlot() is rather expensive. I am wondering if there is a better way to plot individual points as they come? Ideally I would push the single point into XData and YData and draw that point only, but I don't know if this is possible.
Please note that there may be multiple lineseries objects (i.e., multiple graphs on the same plot); plot() takes an axes handle as an argument, so it wouldn't consider the properties of the previously drawn line handles (or is there a way to make it do so?); I thought of just doing plot(x,y);hold all; but that would give me separate line handles every time, each corresponding to a single point.
It might be that there's no way to make plotting incoming points any faster, but I figured I'd ask.
EDIT: Updated OP with actual code I'm working with, rather than using a generic example that's up for misinterpretation.
The amount of data you're handling in each update, is large (although only a single point is actually changing), making your code O(N^2).
By using a second lineseries to build up a large group of data, you can alternate between adding every point to a short "active" line, and infrequently adding large blocks to the main lineseries. While this doesn't exactly avoid O(N^2), it lets you reduce the constant significantly.
If you do this, remember to overlap the "old" lineseries and "active" lineseries by one point, so that they connect.
Essentially:
function updatePlot(obj, propNum)
X = get(obj.LHactive(propNum), 'XData');
Y = get(obj.LHactive(propNum), 'YData');
X(end+1) = obj.(dynProps{propNum}).newestData(1);
Y(end+1) = obj.(dynProps{propNum}).newestData(2);
if numel(X) > 100
Xold = [get(obj.LH(propNum), 'XData'); X(2:end)];
Yold = [get(obj.LH(propNum), 'YData'); Y(2:end)];
set(obj.LH(propNum), 'XData', Xold, 'YData', Yold);
X = X(end);
Y = Y(end);
end
set(obj.LHactive(propNum), 'XData', X, 'YData', Y);
end
Part of the reason why your code may be taking a long time to run is because you are using a for loop to assign your variables. Depending on what version of Matlab you are using, this will slow your process down significantly. I suggest using vectorization to assign values to your x and y like this:
x = 1:1000;
y = cosd(x);
You can then assign the first points in your data.
xi = x(1);
yi = y(1);
When you plot, assign the XDataSource and YDataSource.
h = plot(xi, yi, 'YDataSource', 'yi', 'XDataSource', 'xi');
Now when you loop through to change the values, use the refreshdata to update the Xdata and Ydata values. Use the drawnow function to update the figure window.
for k = 2:1000,
xi = x(1:k);
yi = y(1:k);
refreshdata(h, 'caller')
drawnow;
end
Your code is slow, because you are replotting all values everytime that you call updatePlot. I would therefore only plot the latest point in updatePlot (This is also the problem that you've stated: Ideally I would push the single point into XData and YData and draw that point only, but I don't know if this is possible.)
add property LH_point_counter
classdef DynamicPlotter < dynamicprops
properties
FH %# figure handle
AH %# axes handle
LH %# cell array of line handles - may have multiple lines on the plot
% counter that counts home many points we have for each dynProps
LH_point_counter = [];
dynProps = {} %# cell array of dynamic property names -
%# use to access individual datasets
end
modify updatePlot
function updatePlot(obj, propNum)
% plot new point
new_x = obj.(dynProps{propNum}).newestData(1);
new_y = obj.(dynProps{propNum}).newestData(2);
new_handle = plot(new_x, new_y);
% add new handle to list of handles of this property
counter_this_prop = obj.LH_point_counter(propNum);
counter_this_prop = counter_this_prop + 1;
obj.LH{propNum}(counter_this_prop) = new_handle;
% save new counter value
obj.LH_point_counter(propNum) = counter_this_prop;
end