6

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.

Dang Khoa
  • 5,693
  • 8
  • 51
  • 80
  • I don't know if you've seen this but take a look at http://stackoverflow.com/questions/1693429/matlab-oop-is-it-slow-or-am-i-doing-something-wrong . Basically using classes in matlab generally results in poor performance – Marm0t Sep 14 '11 at 21:52
  • thanks, I have seen that before. My project requires the use of classes for reasons I won't get into, so there isn't any way around that.. but would the `set` call be slow simply because it's called inside a method? – Dang Khoa Sep 14 '11 at 21:58
  • @strictlyrude27: you should correct the addlistener line as: `addlistener(obj.(props{i}), 'DataAdded', @(src,ev) obj.updatePlot(i));`. You also might wanna add `drawnow` at the end of the `updatePlot` function – Amro Sep 17 '11 at 21:15
  • @Amro - whoops, should have been `props`, error in transcription. For adding `@(src,ev)` - if I don't use those arguments are they really needed in there? Finally, I did have `drawnow` in there before, but that didn't affect how setting `XData` and `YData` was plotted.. – Dang Khoa Sep 17 '11 at 22:35
  • @strictlyrude27: Can I ask how bad/slow is it for you currently? also at what intervals does the data come in, and for how long? `drawnow` is just to flush any queued operations. `@(src,ev)` was needed otherwise MATLAB was throwing an error. – Amro Sep 17 '11 at 22:38
  • 1
    @strictlyrude27: also note the data collected gets larger over time. Thus if you do not have to draw the entire thing from the start after every append, there is a [previous solution](http://stackoverflow.com/questions/2947497/how-to-make-sliding-window-model-for-data-stream-mining/2954394#2954394) I posted that can be adapted to your case (OOP style), where I display the data in a sliding-window manner using a circular buffer... – Amro Sep 17 '11 at 22:51
  • @Amro - I have 4 `props` per `DynamicPlotter` and two `DynamicPlotter` objects for a total of 8 lines to be drawn on two separate graphs; I wanted to get data at 100Hz (i.e. request every 10 ms). I do this with a `for` loop and just append `[uptime sin(uptime)]` where `uptime` is a `toc` corresponding to a `tic` to start the loop; in the loop I `pause(1/100)`. If I plot, this runs in 24 seconds; if I don't, it runs in 11. According to the code profiler, 67% of the time is spent in `set`; 33% is spent in `pause()`. I wanted to maximize `pause()` time, basically.. – Dang Khoa Sep 18 '11 at 00:07
  • @Amro - finally, you're definitely right in that I don't need draw from the start. I'm showing 30 second snapshots right now - so your suggestion will certainly speed up execution if I collect data over a long period of time, but won't help inside that threshold. I can probably just discard the old `XData` and `YData` since it's stored in the `DynamicDataset` object anyhow. – Dang Khoa Sep 18 '11 at 00:09

3 Answers3

4

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
Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • this drastically reduced the plot time! if you have any more ideas to reduce time further, let me know.. – Dang Khoa Nov 30 '11 at 07:26
1

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
Thea
  • 11
  • 1
  • This unfortunately won't work for my needs - my data is not actually generated in a for-loop, but is produced by a motion capture system in real time. The code snippet above just demonstrates that I am plotting my data dynamically. I'll update the OP to reflect my specific case; I was trying to be more general but my example was clearly misleading. – Dang Khoa Sep 14 '11 at 16:22
1

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
    
memyself
  • 11,907
  • 14
  • 61
  • 102
  • This may work - I'm sure having to recopy over properties wouldn't be the most costly thing in the world. However, I do wonder about how this will interfere with the main axes handle I have? I manually adjust the plot window and other axes handle properties, so constantly writing this might not be the best option either. – Dang Khoa Sep 23 '11 at 19:10
  • 1
    the code is not recopying any properties. All it does is add the newest handle to the list of handles - this is a very cheap operation. Your manual axis adjustment is a separate issue and you haven't posted the code for that. – memyself Sep 24 '11 at 07:41