Creating Interactive Graphics and Animation

(For more resources related to this topic, see here.)

Interactive graphics and animations

This article showcases MATLAB's capabilities for creating interactive graphics and animations. A static graphic is essentially two dimensional. The ability to rotate the axes and change the view, add annotations in real time, delete data, and zoom in or zoom out adds significantly to the user experience, as the brain is able to process and see more from that interaction. MATLAB supports interactivity with the standard zoom, pan features, a powerful set of camera tools to change the data view, data brushing, and axes linking. The set of functionalities accessible from the figure and camera toolbars are outlined briefly as follows:

The steps of interactive exploration can also be recorded and presented as an animation. This is very useful to demonstrate the evolution of the data in time or space or along any dimension where sequence has meaning. Note that some recipes in this article may require you to run the code from the source code files as a whole unit because they were developed as functions. As functions, they are not independently interpretable using the separate code blocks corresponding to each step.

Callback functions

A mouse drag movement from the top-left corner to bottom-right corner is commonly used for zooming in or selecting a group of objects. You can also program a custom behavior to such an interaction event, by using a callback function. When a specific event occurs (for example, you click on a push button or double-click with your mouse), the corresponding callback function executes. Many event properties of graphics handle objects can be used to define callback functions.

In this recipe, you will write callback functions which are essential to implement a slider element to get input from the user on where to create the slice or an isosurface for 3D exploration. You will also see options available to share data between the calling and callback functions.

Getting started

Load the dataset. Split the data into two main sets—userdataA is a structure with variables related to the demographics and userdataB is a structure with variables related to the Income Groups. Now create a nested structure with these two data structures as shown in the following code snippet:

load customCountyData userdataA.demgraphics = demgraphics; userdataA.lege = lege; userdataB.incomeGroups = incomeGroups; userdataB.crimeRateLI = crimeRateLI; userdataB.crimeRateHI = crimeRateHI; userdataB.crimeRateMI = crimeRateMI; userdataB.AverageSATScoresLI = AverageSATScoresLI; userdataB.AverageSATScoresMI = AverageSATScoresMI; userdataB.AverageSATScoresHI = AverageSATScoresHI; userdataB.icleg = icleg; userdataAB.years = years; userdataAB.userdataA = userdataA; userdataAB.userdataB = userdataB;

How to do it...

Perform the following steps:

  1. Run this as a function at the console:


  2. A figure is brought up with a non-standard menu item as highlighted in the following screenshot. Select the By Population item:

    Here is the resultant figure:

  3. Continue to explore the other options to fully exercise the interactivity built into this graphic.

How it works...

The function c3165_07_01_callback_functions works as follows:

A custom menu item Data Groups is created, with additional submenu items—By population, By Income Groups, or Show all.

% add main menu item f = uimenu('Label','Data Groups'); % add sub menu items with additional parameters uimenu(f,'Label','By Population','Callback','showData',... 'tag','demographics','userdata',userdataAB); uimenu(f,'Label','By IncomeGroups',... 'Callback','showData','tag','IncomeGroups',... 'userdata',userdataAB); uimenu(f,'Label','ShowAll','Callback','showData',... 'tag','together','userdata',userdataAB);

You defined the tag name and the callback function for each submenu item above. Having a tag name makes it easier to use the same callback function with multiple objects because you can query the tag name to find out which object initiated the call to the callback function (if you need that information). In this example, the callback function behavior is dependent upon which submenu item was selected. So the tag property allowed you to use the single function showData as callback for all three submenu items and still implement submenu item specific behavior. Alternately, you could also register three different callback functions and use no tag names.

You can specify the value of a callback property in three ways. Here, you gave it a function handle. Alternately, you can supply a string that is a MATLAB command that executes when the callback is invoked. Or, a cell array with the function handle and additional arguments as you will see in the next section.

For passing data between the calling and callback function, you also have three options. Here, you set the userdata property to the variable name that has the data needed by the callback function. Note that the userdata is just one variable and you passed a complicated data structure as userdata to effectively pass multiple values. The user data can be extracted from within the callback function of the object or menu item whose callback is executing as follows:

userdata = get(gcbo,'userdata');

The second alternative to pass data to callback functions is by means of the application data. This does not require you to build a complicated data structure. Depending on how much data you need to pass, this later option may be the faster mechanism. It also has the advantage that the userdata space cannot inadvertently get overwritten by some other function. Use the setappdata function to pass multiple variables. In this recipe, you maintained the main drawing area axis handles and the custom legend axis handles as application data.

setappdata(gcf,'mainAxes',[]); setappdata(gcf,'labelAxes',[]);

This was retrieved each time within the executing callback functions, to clear the graphic as new choices are selected by the user from the custom menu.

mainAxesHandle = getappdata(gcf,'mainAxes'); labelAxesHandles = getappdata(gcf,'labelAxes'); if ~isempty(mainAxesHandle), cla(mainAxesHandle); [mainAxesHandle, x, y, ci, cd] = ... redrawGrid(userdata.years, mainAxesHandle); else [mainAxesHandle, x, y, ci, cd] = ... redrawGrid(userdata.years); end if ~isempty(labelAxesHandles) for ij = 1:length(labelAxesHandles) cla(labelAxesHandles(ij)); end end

The third option to pass data to callback functions is at the time of defining the callback property, where you can supply a cell array with the function handle and additional arguments as you will see in the next section. These are local copies of data passed onto the function and will not affect the global values of the variables.

The callback function showData is given below. Functions that you want to use as function handle callbacks must define at least two input arguments in the function definition: the handle of the object generating the callback (the source of the event), the event data structure (can be empty for some callbacks).

function showData(src, evt) userdata = get(gcbo,'userdata'); if strcmp(get(gcbo,'tag'),'demographics') % Call grid f drawing code block % Call showDemographics with relevant inputs elseif strcmp(get(gcbo,'tag'),'IncomeGroups') % Call grid drawing code block % Call showIncomeGroups with relevant inputs else % Call grid drawing code block % Call showDemographics with relevant inputs % Call showIncomeGroups with relevant inputs end function labelAxesHandle = ... showDemographics(userdata, mainAxesHandle, x, y, cd) % Function specific code end function labelAxesHandle = ... showIncomeGroups(userdata, mainAxesHandle, x, y, ci) % Function specific code end function [mainAxesHandle x y ci cd] = ... redrawGrid(years, mainAxesHandle) % Grid drawing function specific code end end

There's more...

This section demonstrates the third option to pass data to callback functions by supplying a cell array with the function handle and additional arguments at the time of defining the callback property. Add a fourth submenu item as follows (uncomment line 45 of the source code):

uimenu(f,'Label',... 'Alternative way to pass data to callback',... 'Callback',{@showData1,userdataAB},'tag','blah');

Define the showData1 function as follows (uncomment lines 49 to 51 of the source code):

function showData1(src, evt, arg1) disp(arg1.years); end

Execute the function and see that the value of the years variable are displayed at the MATLAB console when you select the last submenu Alternative way to pass data to callback option.

Takeaways from this recipe:

  • Use callback functions to define custom responses for each user interaction with your graphic

  • Use one of the three options for sharing data between calling and callback functions—pass data as arguments with the callback definition, or via the user data space, or via the application data space, as appropriate

See also

Look up MATLAB help on the setappdata, getappdata, userdata property, callback property, and uimenu commands.

Obtaining user input from the graph

User input may be desired for annotating data in terms of adding a label to one or more data points, or allowing user settable boundary definitions on the graphic. This recipe illustrates how to use MATLAB to support these needs.

Getting started

The recipe shows a two-dimensional dataset of intensity values obtained from two different dye fluorescence readings. There are some clearly identifiable clusters of points in this 2D space. The user is allowed to draw boundaries to group points and identify these clusters. Load the data:

load clusterInteractivData

The imellipse function from the MATLAB image processing toolboxTM is used in this recipe. Trial downloads are available from their website.

How to do it...

The function constitutes the following steps:

  1. Set up the user data variables to share the data between the callback functions of the push button elements in this graph:

    userdata.symbChoice = {'+','x','o','s','^'}; userdata.boundDef = []; userdata.X = X; userdata.Y = Y; userdata.Calls = ones(size(X)); set(gcf,'userdata',userdata);

  2. Make the initial plot of the data:

    plot(userdata.X,userdata.Y,'k.','Markersize',18); hold on;

  3. Add the push button elements to the graphic:

    uicontrol('style','pushbutton',... 'string','Add cluster boundaries?', ... 'Callback',@addBound, ... 'Position', [10 21 250 20],'fontsize',12); uicontrol('style','pushbutton', ... 'string','Classify', ... 'Callback',@classifyPts, ... 'Position', [270 21 100 20],'fontsize',12); uicontrol('style','pushbutton', ... 'string','Clear Boundaries', ... 'Callback',@clearBounds, ... 'Position', [380 21 150 20],'fontsize',12);

  4. Define callback for each of the pushbutton elements. The addBound function is for defining the cluster boundaries. The steps are as follows:

    % Retrieve the userdata data userdata = get(gcf,'userdata'); % Allow a maximum of four cluster boundary definitions if length(userdata.boundDef)>4 msgbox('A maximum of four clusters allowed!'); return; end % Allow user to define a bounding curve h=imellipse(gca); % The boundary definition is added to a cell array with % each element of the array storing the boundary def. userdata.boundDef{length(userdata.boundDef)+1} = ... h.getPosition; set(gcf,'userdata',userdata);

  5. The classifyPts function draws points enclosed in a given boundary with a unique symbol per boundary definition. The logic used in this classification function is simple and will run into difficulties with complex boundary definitions. However, that is ignored as that is not the focus of this recipe. Here, first find points whose coordinates lie in the range defined by the coordinates of the boundary definition. Then, assign a unique symbol to all points within that boundary:

    for i = 1:length(userdata.boundDef) pts = ... find( (userdata.X>(userdata.boundDef{i}(:,1)))& ... (userdata.X<(userdata.boundDef{i}(:,1)+ ... userdata.boundDef{i}(:,3))) &... (userdata.Y>(userdata.boundDef{i}(:,2)))& ... (userdata.Y<(userdata.boundDef{i}(:,2)+ ... userdata.boundDef{i}(:,4)))); userdata.Calls(pts) = i; plot(userdata.X(pts),userdata.Y(pts), ... [userdata.colorChoice{i} '.'], ... 'Markersize',18); hold on; end

  6. The clearBounds function clears the drawn boundaries and removes the clustering based upon those boundary definitions.

    function clearBounds(src, evt) cla; userdata = get(gcf,'userdata'); userdata.boundDef = []; set(gcf,'userdata',userdata); plot(userdata.X,userdata.Y,'k.','Markersize',18); hold on; end

  7. Run the code and define cluster boundaries using the mouse. Note that until you click the on the Classify button, classification does not occur. Here is a snapshot of how it looks (the arrow and dashed boundary is used to depict the cursor movement from user interaction):

  8. Initiate a classification by clicking on Classify.

    The graph will respond by re-drawing all points inside the constructed boundary with a specific symbol:

How it works...

This recipe illustrates how user input is obtained from the graphical display in order to impact the results produced. The image processing toolbox has several such functions that allow user to provide input by mouse clicks on the graphical display—such as imellipse for drawing elliptical boundaries, and imrect for drawing rectangular boundaries. You can refer to the product pages for more information.

Takeaways from this recipe:

  • Obtain user input directly via the graph in terms of data point level annotations and/or user settable boundary definitions

See also

Look up MATLAB help on the imlineimpoly, imfreehandimrect, and imelli pseginput commands.

Linked axes and data brushing

MATLAB allows creation of programmatic links between the plot and the data sources and linking different plots together. This feature is augmented by support for data brushing, which is a way to select data and mark it up to distinguish from others. Linking plots to their data source allows you to manipulate the values in the variables and have the plot automatically get updated to reflect the changes. Linking between axes enables actions such as zoom or pan to simultaneously affect the view in all linked axes. Data brushing allows you to directly manipulate the data on the plot and have the linked views reflect the effect of that manipulation and/or selection. These features can provide a live and synchronized view of different aspects of your data.

Getting ready

You will use the same cluster data as the previous recipe. Each point is denoted by an x and y value pair. The angle of each point can be computed as the inverse tangent of the ratio of the y value to the x value. The amplitude of each point can be computed as the square root of the sum of squares of the x and y values. The main panel in row 1 show the data in a scatter plot. The two plots in the second row have the angle and amplitude values of each point respectively. The fourth and fifth panels in the third row are histograms of the x and y values respectively. Load the data and calculate the angle and amplitude data as described earlier:

load clusterInteractivData data(:,1) = X; data(:,2) = Y; data(:,3) = atan(Y./X); data(:,4) = sqrt(X.^2 + Y.^2); clear X Y

How to do it...

Perform the following steps:

  1. Plot the raw data:

    axes('position',[.3196 .6191 .3537 .3211], ... 'Fontsize',12); scatter(data(:,1), data(:,2),'ks', ... 'XDataSource','data(:,1)','YDataSource','data(:,2)'); box on; xlabel('Dye 1 Intensity'); ylabel('Dye 1 Intensity');title('Cluster Plot');

  2. Plot the angle data:

    axes('position',[.0682 .3009 .4051 .2240], ... 'Fontsize',12); scatter(1:length(data),data(:,3),'ks',... 'YDataSource','data(:,3)'); box on; xlabel('Serial Number of Points'); title('Angle made by each point to the x axis'); ylabel('tan^{-1}(Y/X)');

  3. Plot the amplitude data:

    axes('position',[.5588 .3009 .4051 .2240], ... 'Fontsize',12); scatter(1:length(data),data(:,4),'ks', ... 'YDataSource','data(:,4)'); box on; xlabel('Serial Number of Points'); title('Amplitude of each point'); ylabel('{\surd(X^2 + Y^2)}');

  4. Plot the two histograms:

    axes('position',[.0682 .0407 .4051 .1730], ... 'Fontsize',12); hist(data(:,1)); title('Histogram of Dye 1 Intensities'); axes('position',[.5588 .0407 .4051 .1730], ... 'Fontsize',12); hist(data(:,2)); title('Histogram of Dye 2 Intensities');

    The output is as follows:

  5. Programmatically, link the data to their source:


    Programmatically, turn brushing on and set the brush color to green:

    h = brush; set(h,'Color',[0 1 0],'Enable','on');

Use mouse movements to brush a set of points. You could do this on any one of the first three panels and observe the impact on corresponding points in the other graphs by its turning green. (The arrow and dashed boundary is used to depict the cursor movement from user interaction in the following figure):

How it works...

Because brushing is turned on, when you focus the mouse on any of the graph areas, a cross hair shows up at the cursor. You can drag to select an area of the graph. Points falling within the selected area are brushed to the color green, for the graphs on rows 1 and 2. Note that nothing is highlighted on the histograms at this point. This is because the x and y data source for the histograms is not correctly linked to the data source variables yet. For the other graphs, you programmatically set their x and y data source via the XDataSource and the YDataSource properties. You can also define the source data variables to link to a graphic and turn brushing on by using the icons from the figure toolbar as shown in the following screenshot. The first circle highlights the brush button; the second circle highlights the link data button. You can click on the Edit link pointed by the arrow to exactly define the x and y sources:

There's more...

To define the source data variables to link to a graphic and turn brushing on by using the icons from the Figure toolbar, do as follows:

  1. Clicking on Edit (pointed to in preceding figure) will bring up the following window:

  2. Enter data(:,1) in the YDataSource column for row 1 and data(:,2) in the YDataSource column for row 2.

  3. Now try brushing again. Observe that bins of the histogram get highlights in a bottom up order as corresponding points get selected (again, the arrow and dashed boundary is used to depict the cursor movement from user interaction):

  4. Link axes together to simultaneously investigate multiple aspects of the same data point. For example, in this step you plot the cluster data alongside a random quality value for each point of the data. Link the axes such that zoom and pan functions on either will impact the axes of the other linked axes:

    axes('position',[.13 .11 .34 .71]); scatter(data(:,1), data(:,2),'ks');box on; axes('position',[.57 .11 .34 .71]); scatter(data(:,1), data(:,2),[],rand(size(data,1),1), ... 'marker','o', 'LineWidth',2);box on; linkaxes;

The output is as follows. Experiment with zoom and pan functionalities on this graph.

Takeaways from this recipe:

  • Use data brushing and linked axes features to provide a live and synchronized view of different aspects of your data.

See also

Look up MATLAB help on the linkdata, linkaxes, and brush commands.

The magnifying glass demo

A virtual magnifying glass function is developed in this recipe. Using this, you can load any image and run the virtual magnifying glass over it for closer inspection at your cursor tip. This recipe was adapted from Mingjing Zhang's submission on MATLAB Central File Exchange.

Getting ready

Load the image of a microtiter plate with a distribution of filled and empty wells:

userdata.img_rgb = imread('sampleImage.png');

How to do it...

Perform the following steps:

  1. Define parameters for passing to the callback function to be invoked in response to cursor movement:

    % The image size userdata.size_img = size(userdata.img_rgb); % the start time (to be used along with the frame rate % info to determine how often image should be updated) userdata.start_time = tic; % The frame rate userdata.FPS = 20; % Magnifying Power userdata.MagPower = 2; % The Radius of the Magnifier userdata.MagRadius = 100; % The radius of the image to be magnified userdata.PreMagRadius = userdata.MagRadius./userdata.MagPower; userdata.alreadyDrawn = 0;

  2. Set up the figure and axes:

    MainFigureHdl = figure('Name', 'Magnifier Demo', ... 'NumberTitle' ,'off', ... 'Units', 'normalized', ... 'Position', [.1854 .0963 .4599 .8083], ... 'MenuBar', 'figure', ... 'Renderer', 'opengl'); MainAxesHdl = axes('Parent', MainFigureHdl, ... 'Units', 'normalized',... 'Position', [0 0 1 1], ... 'color', [0 0 0], ... 'YDir', 'reverse', ... 'NextPlot', 'add', ... 'Visible', 'on');

  3. Plot the initial image and initial magnified image at the initial cursor tip position:

    userdata.img_hdl = image(0,0,userdata.img_rgb); axis tight % The magnified image object userdata.mag_img_hdl = image(0,0,[]); userdata.mag_img = ... userdata.img_rgb(1:userdata.PreMagRadius*2+1,... 1:userdata.PreMagRadius*2+1,:);

  4. Create a circular mask for the magnified image:

    [x y] = ... meshgrid(-userdata.PreMagRadius:userdata.PreMagRadius); dist = double(sqrt(x.^2+y.^2)); % dist pixel to center in_circle_log = dist<userdata.PreMagRadius-1; out_circle_log = dist>userdata.PreMagRadius+1; dist(in_circle_log) = 0; dist(out_circle_log) = 1; dist(~(in_circle_log|out_circle_log)) = ... (dist(~(in_circle_log|out_circle_log)) - ... (userdata.PreMagRadius-1))./2; userdata.mask_img = 1 - dist;

  5. Initialize the image object:

    set(userdata.mag_img_hdl, 'CData',userdata.mag_img, ... 'AlphaData', userdata.mask_img); %designate the call back function set(MainFigureHdl,'userdata',userdata,... 'WindowButtonMotionFcn',... @stl_magnifier_WindowButtonMotionFcn);

  6. The next task is to define this callback function—this function should select the image pixels within the mask defined at the cursor tip, and display a magnified version of it at that location.

    function stl_magnifier_WindowButtonMotionFcn(obj,event) %extract parameters userdata = get(gcbo,'userdata'); % determine if image needs to be updated per frame rate % definitions cur_time = toc(userdata.start_time); curFrameNo = floor(cur_time.*userdata.FPS); % If this frame has not been drawn yet if userdata.alreadyDrawn < curFrameNo mag_pos = get(obj,'CurrentPoint'); mag_pos(1) = ... round(size(userdata.img_rgb,2)*mag_pos(1)); mag_pos(2) = size(userdata.img_rgb,2) - ... round(size(userdata.img_rgb,2)*mag_pos(2))+1; % The size of the part of the image to be magnified % The range has to be cropped in case it is outside the % image. mag_x = mag_pos(1)+[-userdata.PreMagRadius ... userdata.PreMagRadius]; mag_x_cropped = min(max(mag_x, 1),... userdata.size_img(2)); mag_y = mag_pos(2)+[-userdata.PreMagRadius ... userdata.PreMagRadius]; mag_y_cropped = min(max(mag_y, 1),... userdata.size_img(1)); % Take the magnified part of the image userdata.mag_img([mag_y_cropped(1):... mag_y_cropped(2)]-mag_pos(2)+... userdata.PreMagRadius+1,... [mag_x_cropped(1):mag_x_cropped(2)]-... mag_pos(1)+userdata.PreMagRadius+1,:) = ... userdata.img_rgb(mag_y_cropped(1):... mag_y_cropped(2),... mag_x_cropped(1):mag_x_cropped(2),:); % Show the image as twice its actual size set(userdata.mag_img_hdl, 'CData', ... userdata.mag_img,'XData', mag_pos(1)+... [-userdata.MagRadius userdata.MagRadius], ... 'YData', mag_pos(2)+[-userdata.MagRadius ... userdata.MagRadius]); % Update the object drawnow; userdata.alreadyDrawn = curFrameNo; end set(gcf,'userdata',userdata); end

    Here is a view of the final software in action (dashed line depicts cursor movement; arrow depicts the position of the cursor):

How it works...

The callback function accesses the coordinates of the cursor position and calculates the center coordinates of the magnifier as the current position ± the radius of the circular magnifying glass mask. The coordinates are then cropped to ensure that they do not access a location beyond the coordinates of the main image. This image is then plotted at twice the original size.

Takeaways from this recipe:

  • Use the virtual magnifying glass to load any image and subject it to closer inspection at your cursor tip.

See also

Look up MATLAB help on the image and WindowButtonMotionFcn commands.

Animation with playback of frame captures

Animation is a sequence of images telling a story. For a sequence that is changing a lot from frame to frame, the best approach to animation is to play it back at some meaningful rate as a movie, the key point being that the rendering is not in real time; rather, it is the playback of a pre-generated series of images. In this recipe, you will explore the concept of playing back a series of pre-rendered images.

Getting ready

MATLAB supplies a dataset of brain MRI slices. First, load the data:

load MRI

How to do it...

Perform the following steps:

  1. The first approach will be to use the getframe command to collect each frame of the display and then playback using the movie command. Note that as you call the image command to make the frames, the live rendering of the different frames will be visible. The command movie does the actual off line playback of the images.

    figure; for image_num = 1:27; image(D(:,:,image_num));colormap(map); f(image_num) = getframe; end close; movie(f);

  2. Another alternative is to record the frames as part of an AVI (Audio Video Interleave) object and then save that as an avi file that you can play with any media player:

    aviobj = avifile('example.avi','fps',3,... 'quality',100,'compression','none'); for image_num = 1:27; image(D(:,:,image_num)); colormap(map); aviobj = addframe(aviobj,getframe); end aviobj = close(aviobj); close;

  3. Yet another alternative is to use the command imwrite and save in the gif format. You can set a frame rate and other parameters for the animation as follows:

    imwrite(D,map,'letsTry.gif','gif', 'DelayTime',2,... 'Location', [503 289], 'LoopCount',7);

How it works...

In this recipe, you have essentially taken snapshots of the image as it is displayed and stored it, ready to be played back at a later tim e. getframe returns a snapshot (pixmap) of the current axes or figure. Note that if the screensaver is started up while waiting for an image to render or you open up a browser that plots over the figure, then the pixel values from the screensaver or whatever else is displayed in the same location as the figure gets captured. When running remotely on virtual desktops, the virtual desktop window needs to be active on your current desktop for successful capture.

The avifile command creates an avifile object. As you saw, a number of parameters such as the frame rate, quality, and compression codec in use can be specified as a parameter and value pairing with this command.

The imwrite command writes the image D to the file specified by filename in a specified format. There are format specific parameters that can be supplied as property name and value pairs. In this recipe, you have set the delay time, the location of the plot and the loop count for the .gif format. The delay time refers to how long each frame is displayed. The location indicates where on the browser window the image will be created. The loop count specifies how many times the animation is run before it is stopped.

There's more...

One possibility to generate a smooth playback would be to generate the intermediate image between two snapshots. Here, this is generated using the anymate function, a submission by Jerker Wågberg on MATLAB File Exchange. The anymate function analyzes the changes in the properties of Handle Graphics objects and can interpolate between these changes to generate a smooth transition between each given state, here called a break. To generate an animation, anymate collects the values of all Handle Graphics objects for each break and then estimates the true changes between these breaks for presentation. Perform the following to generate an animation that smoothly interpolates between the given frames:

D_less = D(:,:,1,1:2:end); for image_num = 1:size(D_less,4)-1; figure; image(D_less(:,:,image_num)); colormap(map); end anymate;

The output is as follows:

Takeaways from this recipe:

  • Use play back of pre-rendered frame captures to show evolution of data in time or space

  • Use image interpolation techniques to generate a smooth playback from few available time lapse snapshots

Stream particle animation

A stream particle animation is useful for visualizing the flow direction and speed of a vector field. The "particles", represented by a line marker, trace the flow along a particular stream line. The speed of each particle in the animation is proportional to the magnitude of the vector field at any given point along the stream line.

Getting ready

This recipe builds a stream particle animation to trace the streamline path of a certain section of the wind flow data that comes as part of the MATLAB installation. Start with loading the dataset:

load wind

How to do it...

Perform the following steps:

  1. Investigate range of data and experiment at different values of x, y, and z to decide the cross sections you want to use in this animation:

    disp([min(x(:)) max(x(:)) min(y(:)) max(y(:)) ... min(z(:)) max(z(:))]);

  2. Define the mesh over which stream lines will be constructed:

    [sx sy sz] = meshgrid(85:20:100, 20:2:50, 6);

  3. Define the stream lines:

    verts = stream3(x,y,z,u,v,w,sx,sy,sz); sl = streamline(verts);

  4. Define the view:

    axis tight; box on; grid on; daspect([19.9445 15.1002 1.0000]); campos([ -165.7946 -223.0056 11.0223]);

  5. Pick the vertices at which to place the particles:

    iverts = interpstreamspeed(x,y,z,u,v,w,verts,0.08);

  6. Start the animation:

    set(gca,'drawmode','fast'); streamparticles(iverts,15,... 'FrameRate',5,... 'Animate',10,... 'ParticleAlignment','on',... 'MarkerEdgeColor','green',... 'MarkerFaceColor','green',... 'Marker','o');

    The output is as follows:

How it works...

The stream3 and streamline commands help to create the paths for the particles to travel enabling a visual context for the animation. All the animations start at the plane z = 6. The campos function sets the position of the camera from where this animation will be observed. Setting the data aspect provides greater resolution along the x and y axis.

The interpstreamspeed function returns the vertices at which the particles should be drawn. As the path is provided, it is not required to draw every vertex. Making it appear at a certain step will be adequate to create the desired effect. The velocity with which the particles move may be scaled to increase or decrease the number of interpolated vertices. In this example, the velocities are scaled by 0.08 to increase the number of interpolated vertices.

Setting the axis property DrawMode to fast makes the animation run faster.

The streamlineparticles function allows a number of its properties to be set to impact the visualization. In addition to the 3D vertices, the command takes an integer argument n, set to 15 here, that determines how many particles to draw. Since subsequently, the ParticleAlignment property is set to On, n is interpreted as the number of particles on the streamline having the most vertices and sets the spacing on the other streamlines to this value.

Additionally, you set properties such as the FrameRate to 5 frames to be displayed per second, impacting how fast the animation is changing; the property Animate to 10 times indicating that the entire sequence is to be run 10 times before it stops; and the properties MarkerEdgeColor, MarkerFaceColor, and Marker impacting the way the actual particles look. Animations run faster when marker edges are not drawn, so the none value is recommended for the MarkerEdgeColor property .

Note that the particles on the z plane travel much faster than the particles along the spiral.

Takeaways from this recipe:

  • Use stream particle animation along any path of your choice (including stream lines generated by MATLAB command) to observe the flow of particles in time.

See also

Look up MATLAB help on the interpstreamspeed and streamlineparticles commands.

Animation by incremental changes to chart elements

An alternative approach to animation is to continually erase and redraw the objects on the screen, making incremental changes with each redraw. One of the ways to do this is to redefine the XData, YData, ZData, and/or CData plot object properties for every change, then make calls to refreshdata followed by drawnow; or equivalently linking the plot to the data sources, which will cause the plot to be automatically updated each time the source data is changed by implicitly calling refreshdata and drawnow. This alternative approach allows for faster rendering at a finite cost to the rendering accuracy. In this recipe, you will create an animation using this erase and redraw strategy.

Getting ready

This recipe illustrates the process of convolution between two functions. It was adapted from a File Exchange submission on convolution by Laine Berhane Kahsay. Convolution is a mathematical operation on two functions f and g, producing a third function that is a modified version of one of the original function, giving the area overlap between the two functions as a function of the amount by which the second function is translated. This recipe uses a parabolic and a square function. Generate the data. This involves defining a range for the x values of each function and defining the y values for each function.

%% data generation s_int = 0.1; % sampling interval constant t = -10:s_int:10; % interval for function 'f(t)' f = 0.1*(t.^2); % definition of function 'f(t)' t1 = -7:s_int:7; % interval for function 'g(t1)' go = [-1*ones(1,40) ones(1, 61) -1*ones(1, 40)]; % definition of function 'g(t1)' c = s_int * conv(f, go); % convolve: note the % multiplication by the % sampling interval % flip 'go(t1)' for the graphical convolutions g = go(-t1) g = fliplr(go); tf = fliplr(-t1); % slide range of 'g' to discard non-overlapping areas with % 'f' in the convolution tf = tf + ( min(t)-max(tf) ); % get the range of function 'c' which is the convolution of % 'f(t)' and 'go(t1)' tc = [ tf t(2:end)]; tc = tc+max(t1);

Perform the following steps:

  1. Plot the static part of the data into a set of three panel graphics:

    figure('units','normalized','Position', [.16 .14 .31 .69]); % Plot f(t) and g0(t1) in panel 1 subplot(3,1,1); op = plot(t,f, 'b'); hold on; plot(t1, go, 'r');grid on; xlim( [ ( min(t)-abs(max(tf)-min(tf)) - 1 ) ... ( max(t)+abs(max(tf)-min(tf)) + 1 ) ] ); title('Graph of f(t) and go(t)'); legend({'f(t)' 'go(t)'}); % Plot f and g in panel 2. And two vertical lines showing % the overlapped region between the two functions. % Add yellow on the overlapped region. subplot(3,1,2); plot(t, f);hold on; grid on; title('Graphical Convolution: f(t) and g = go(-t1)'); q = plot(tf, g, 'r'); xlim( [ ( min(t)-abs(max(tf)-min(tf))-1 ) ... ( max(t)+abs(max(tf)-min(tf))+1 ) ] ); u_ym = get(gca, 'ylim'); % bound and shade the overlapped region s_l = line( [min(t) min(t)], ... [u_ym(1) u_ym(2)], 'color', 'g' ); e_l = line( [min(t) min(t)], ... [u_ym(1) u_ym(2)], 'color', 'g' ); sg = rectangle('Position', ... [min(t) u_ym(1) 0.0001 u_ym(2)-u_ym(1)], ... 'EdgeColor', 'w', 'FaceColor', 'y', ... 'EraseMode', 'xor'); % convolution result in panel 3 subplot(3,1,3); r = plot(tc, c);grid on; hold on; xlim( [ ( min(t)-abs(max(tf)-min(tf)) - 1 ) ... ( max(t)+abs(max(tf)-min(tf)) + 1 ) ] ); title('Convolutional Product c(t)');

    The output is as follows:

  2. Animation block—perform these steps for the range of time over which the convolution product is going to show effect. Pause a certain time at each step to allow user to observe the effects:

    for i=1:length(tc) pause(0.1); % Update the position of sliding function 'g', % its handle is 'q' tf=tf+s_int; set(q,'EraseMode','xor','XData',tf,'YData',g); % Show a vertical line for a left boundary of % overlapping region sx = min( max( tf(1), min(t) ), max(t) ); sx_a = [sx sx]; set(s_l,'EraseMode','xor', 'XData', sx_a); % Show a second vertical line for the right % boundary of overlapping region ex = min( tf(end), max(t) ); ex_a = [ex ex]; set(e_l,'EraseMode','xor', 'XData', ex_a); % Update shading on overlapped region rpos = [sx u_ym(1) max(0.0001, ex-sx) u_ym(2)-u_ym(1)]; set(sg, 'Position', rpos); % Update the plot of convolution product 'c', % its handle is r set(r,'EraseMode','xor','XData',tc(1:i),'YData',c(1:i) ); end

    The output is as follows:

How it works...

The main idea is to extract handles for those items that need to be changed at every step of the animation. This includes the two boundary lines (handles s_l and e_l respectively) and the shaded yellow rectangle (with handle sg) demarking the overlapped region between the two functions; the sliding function g (with handle q) and the convolution product c (with handle r). For each of these objects, the new data to be displayed is calculated at each step and the XData and YData properties of these objects are set to the new values to be displayed (except for the rectangle, whose position coordinates are updated).

At each step, data is updated and a redraw effort is necessary. If you updated the data by means of using the xdatasource or ydatasource object property, you would need to explicitly make a call to the refreshdata function to actually update the data being graphed. Here, the data properties are directly manipulated and therefore you are not making explicit calls to refreshdata. Also, you are not making explicit calls to the function drawnow to reflect the changes. The reason drawnow need not be called is because pause is called at each iteration. It implicitly causes all changed handles to be re-drawn.

For all handles, the EraseMode property is set to xor. xor enables the draw and erase of these elements by performing an exclusive OR (XOR) with the color of the screen beneath it. This mode does not damage the color of the objects beneath the line.

Takeaways from this recipe:

  • Use the erase and redraw strategy making incremental changes at each step for creating animations.

See also

Look up MATLAB help on the EraseMode, XData, YData, refreshdata, and drawnow properties.


This article showcases MATLAB's capabilities of creating interactive graphics and animations. Recipes cover the essentials of programming callback functionality to add custom behavior to user interactions. Further recipes cover ways to obtain user input directly from the graph, including exploratory techniques such as data brushing and linking. Other recipes cover how to animate a sequence of frames, or use erase and redraw strategies to create animation effects.

Resources for Article :

Further resources on this subject:

You've been reading an excerpt of:

MATLAB Graphics and Data Visualization Cookbook

Explore Title