MATLAB - best way to dynamically update a line handles' XData and YData?

北城余情 提交于 2019-12-18 04:55:20

问题


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.


回答1:


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



回答2:


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



回答3:


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.)

  1. 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
    
  2. 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
    


来源:https://stackoverflow.com/questions/7413320/matlab-best-way-to-dynamically-update-a-line-handles-xdata-and-ydata

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!