Jan 14, 2011

MatLab–Using callbacks in a classdef

Overview

I needed to use MatLab with a C#/C++/CLI program to plot some instrumentation collected data in real time.  In this case, a new data buffer was going to be available once every second. Furthermore, I wanted to allow the user to use the zoom/pan controls and not lose their setting when the next plot was displayed. Being a newcomer to MatLab and coming from an OOP background the classdef construct provided by MatLab seemed a logical place to start. In this blog entry I’m only going to talk about the MatLab specific aspects of the solution. In a future blog I’ll discuss the issues involved in using this MatLab class from C++/CLI and C#.

SNAGHTML6a05d00SNAGHTML69fac29

The importance of Inheritance

I’ll start with the last piece I figured out. When you create a classdef in MatLab you can specify a base class to derive from. There is a very important base class built in called “handle”. This gives your class pass by reference behavior. Without this behavior I could not figure out how to get the callbacks to update the member properties of the class. The class definition looks like this:

classdef RadarPlotter < handle 

There is a private properties section with four values for controlling the axes limits. If I thought a little more MatLab natively, these would probably reduce to one array property but that’s a future upgrade.

%======================================================================
% READ ONLY PROPERTIES
%======================================================================
properties (GetAccess='private', SetAccess = 'private')
    %This section can contain "read only" properties if I need any
    %Four properties for controlling zooming and panning -
    
    MinX;
    MaxX;
    MinY;
    MaxY;
end

Constructing the Callback

The class has a constructor (that I’ll elide most of ) that sets up the callback. Thee are two important things to notice here.  First note that the third parameter isn’t the normal function handle to the callback. Instead it’s a cell array that contains both the function handle and the instance of the class itself. If the class weren’t derived from handle this wouldn’t do us much good since it would be passed by value, but since we derived from handle it will get passed to the callback by reference. Second, notice that the method is a static method. Passing a handle to an instance method didn’t work (although I’m not sure I tried it after I derived from handle).  Also you can create these callbacks and omit the figure handle  parameter that I’m passing to the zoom() and pan() functions but since I was creating the figure first, I decided to use it. FigureHandle is just another property on my class, that I use to position the figure to a desirable position on the screen.

%======================================================================
% Constructor
%======================================================================
function rp = RadarPlotter

rp.FigureHandle = figure('Position',[left bottom width height]);
ph = pan(rp.FigureHandle);
set(ph,'ActionPostCallback',{@RadarPlotter.ZoomPanCallback,rp});
zh = zoom(rp.FigureHandle);
set(zh,'ActionPostCallback',{@RadarPlotter.ZoomPanCallback,rp});

end

The Callback Itself

There was nothing too tricky about the callback itself once I found the command for getting the current axes limits. I was a little confused about how to receive my cell array but I just tried adding the extra parameter to the end of the function parameter list and it worked. Then it was just a matter of assigning the limit values to my class properties. Since my GUI controls also allow these limits to be set there is a member function that both sets the values and makes sure the target values are in range.  I could have just as easily done something like rp.XMin = newXLim(1) for each property. Since both zooming and panning set the limits, this same static method is used for both.

    %======================================================================
    % STATIC METHODS
    %======================================================================
    methods(Static)
        function ZoomPanCallback(hThis,evd,rp) %#ok<INUSD>
            newXLim = get(evd.Axes,'XLim');
            newYLim = get (evd.Axes, 'YLim');
            rp.SetMinMaxAxes(newXLim(1), newXLim(2), newYLim(1), newYLim(2));
        end
       

The Final Step

I should mention for completeness how to use those member properties. In my PlotIt method I have this code :

function PlotIt(hThis)

axis([hThis.MinX,hThis.MaxX,hThis.MinY,hThis.MaxY]);

%remainder of code elided

If you want to see this technique in action, it is demonstrated in the video below.

Demonstration of using zoom/pan tools and maintaining values on replot.

About Me

My photo
Tod Gentille (@todgentille) is now a Curriculum Director for Pluralsight. He's been programming professionally since well before you were born and was a software consultant for most of his career. He's also a father, husband, drummer, and windsurfer. He wants to be a guitar player but he just hasn't got the chops for it.