image thumbnail
from LissajousPlot by Robert Szalapski
Generate and plot Lissajous curves along with their derivative curves - Graphical User Interface.

LissajousPlot
function hPlot = LissajousPlot
%
%  hPlot = LissajousPlot;
%
%  Input   None.
%  Output: Handle to user interface which may be simply ignored.
%
%  Synopsis:
%
%  This is a graphical user interface for exploring Lissajous figures.
% 
%  (1) Notation is defined in the graph title of the Current Plot.
%  (2) Set parameters in the edit fields of the Controls panel.
%  (3) Plot will refresh automatically or with Update Plot button.
%  (4) When plotted, derivative curve is half weight.
%  (5) Adjust angle with slider to mark point on curve.
%  (6) Right-click on highlighted point to display value.
%  (7) Point on derivative curve indicates slope of point on main curve.
%
%   Copyright 2012
%   Dr. Robert F. Szalapski
%   www.CallMeDrRob.com
%   
%   Use and distribute freely for non-commercial purposes,
%   but but give appropriate credit to author Dr. Robert F. Szalapski
%   with reference to www.CallMeDrRob.com as publisher.
%

%
%  Set some default values ...
%
Data.Ax = 1;
Data.Ay = 1;
Data.nx = 1;
Data.ny = 1;
Data.piMult = 0.5;

hPlot = figure(...
    'Name', 'Lissajous Plot', ...
    'Resize', 'Off', ...
    'Toolbar', 'None', ...
    'Menubar', 'None', ...
    'Position', [50,50,500,600], ...
    'Units', 'Normalized', ...
    'NumberTitle', 'Off' ...
    );
BGC = [0.95, 0.95, 0.95];
Data.hPLot = hPlot;
Data.hPlotPanel = uipanel(hPlot,  ...
    'Title', 'Current Plot', ...
    'BackgroundColor', BGC, ...
    'Position', [0.02, 0.20, .96, .78] ...
    );
Data.hAxes = axes(...
    'Parent', Data.hPlotPanel, ...
    'XGrid', 'On', ...
    'YGrid', 'On', ...
    'Position', [0.15, 0.10, 0.81, 0.81] ...
    );
Data.hXAxisLabel = text(0.35, -0.07, ' ' ...
    );
set(Data.hXAxisLabel, ...
    'FontSize', 14, ...
    'String', 'x = 1 cos(1 \theta)' ...
    );
Data.hYAxisLabel = text(-0.12, 0.3, ' ' ...
    );
set(Data.hYAxisLabel, ...
    'Rotation', 90, ...
    'FontSize', 14, ...
    'String', 'y = 1 cos(1 \theta + 0.5 \pi)' ...
    );
Data.Title = text(0.30, 1.05, '');
set(Data.Title, ....
    'FontSize', 18, ...
    'String', 'Lissajous Figure' ...
    );
Data.hLine = line();
set(Data.hLine, ...
    'Parent', Data.hAxes, ...
    'XData', [], ...
    'YData', [], ...
    'Color', 'Blue', ...
    'LineWidth', 2 ...
    );
Data.hDerivLine = line();
set(Data.hDerivLine, ...
    'Parent', Data.hAxes, ...
    'XData', [], ...
    'YData', [], ...
    'Color', 'Red', ...
    'LineWidth', 1 ...
    );

Data.hPoint = line();
set(Data.hPoint, ...
    'Parent', Data.hAxes, ...
    'XData', [], ...
    'YData', [], ...
    'LineWidth', 2, ...
    'Color', 'Blue', ...
    'Marker', 'o', ...
    'MarkerFaceColor', 'Blue', ...
    'MarkerEdgeColor', 'Blue', ...
    'MarkerSize', 6 ...
    );
Data.hDerivPoint = line();
set(Data.hDerivPoint, ...
    'Parent', Data.hAxes, ...
    'XData', [], ...
    'YData', [], ...
    'LineStyle', 'None', ...
    'Marker', 'o', ...
    'MarkerFaceColor', 'Red', ...
    'MarkerEdgeColor', 'Red', ...
    'MarkerSize', 6 ...
    );

Data.hPointMenu = uicontextmenu;
Data.hPointMenuLabel = uimenu(Data.hPointMenu, ...
    'Label', '(0.00, 0.00)' ...
    );
set(Data.hPoint, ...
    'uicontextmenu', Data.hPointMenu ...
    );
Data.hDerivMenu = uicontextmenu;
Data.DerivMenuLabel = uimenu(Data.hDerivMenu, ...
    'Label', '(0.00, 0.00)' ...
    );
set(Data.hDerivPoint, ...
    'uicontextmenu', Data.hDerivMenu ...
    );

Data.hControlPanel = uipanel(hPlot,  ...
    'Title', 'Controls', ...
    'BackgroundColor', BGC, ...
    'Position', [0.02, 0.02, .96, .16], ...
    'Units', 'Normalized' ...
    );
Data.hUpdateButton = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'PushButton', ...
    'Position', [10 35 100 40], ...
    'FontSize', 12, ...
    'String', 'Update Plot', ...
    'TooltipString', 'Press to synchronize values with plot.', ...
    'Callback', @UpdateCallback ...
    );
Data.hGridOn = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'CheckBox', ...
    'Position', [10 10 100 15], ...
    'FontSize', 10, ...
    'String', 'Grid On', ...
    'TooltipString', 'Vertical/horizontal rules on/off', ...
    'Value', 1, ...
    'Callback', @GridOnCallback ...
    );
Data.hAmpXLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [125, 60, 100, 15], ...
    'TooltipString', 'Ax must be greater than zero.', ...
    'String', 'Ax' ...
    );
Data.hAmpX = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Edit', ...
    'Position', [125, 45, 50, 15], ...
    'String', '1', ...
    'UserData', '1', ...
    'TooltipString', 'Ax must be greater than zero.', ...
    'Callback', @EditAmpXCallback ...
    );
Data.hNXLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [125, 25, 100, 15], ...
    'TooltipString', 'Must be a positive integer', ...
    'String', 'nx' ...
    );
Data.hNX = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Edit', ...
    'Position', [125, 10, 50, 15], ...
    'String', '1', ...
    'UserData', '1', ...
    'TooltipString', 'Must be a positive integer', ...
    'Callback', @EditNXCallback ...
    );

Data.hAmpYLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [205, 60, 100, 15], ...
    'TooltipString', 'Ay must be greater than zero.', ...
    'String', 'Ay' ...
    );
Data.hAmpY = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Edit', ...
    'Position', [205, 45, 50, 15], ...
    'String', '1', ...
    'UserData', '1', ...
    'TooltipString', 'Ay must be greater than zero.', ...
    'Callback', @EditAmpYCallback ...
    );
Data.hNYLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [205, 25, 100, 15], ...
    'TooltipString', 'Must be a positive integer', ...
    'String', 'ny' ...
    );
Data.hNY = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Edit', ...
    'Position', [205, 10, 50, 15], ...
    'String', '1', ...
    'UserData', '1', ...
    'TooltipString', 'Must be a positive integer', ...
    'Callback', @EditNYCallback ...
    );

Data.hPLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [285, 60, 100, 15], ...
    'TooltipString', 'p varies from -2 pi to 2 pi', ...
    'String', 'p' ...
    );
Data.hP = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Edit', ...
    'Position', [285, 45, 50, 15], ...
    'String', '0.5', ...
    'UserData', '0.5', ...
    'TooltipString', 'p varies from -2 to 2', ...
    'Callback', @EditPCallback ...
    );

Data.hDerivOn = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'CheckBox', ...
    'Position', [360 10 100 15], ...
    'FontSize', 10, ...
    'String', 'Plot Derivative', ...
    'TooltipString', 'Plot Derivative', ...
    'Value', 0, ...
    'Callback', @plotDerivCallback ...
    );
Data.hPlotPoint = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'Slider', ...
    'Position', [360 40 100 15], ...
    'TooltipString', 'Angle between 0 and 2 pi', ...
    'Min', 0, ...
    'Max', 2*pi, ...
    'Value', 0, ...
    'Callback', @thetaSliderCallback ...
    );
Data.hSliderLabel = uicontrol(...
    'Parent', Data.hControlPanel, ...
    'Style', 'text', ...
    'HorizontalAlignment', 'Left', ...
    'Position', [360, 55, 100, 15], ...
    'TooltipString', 'Angle between 0 and 2 pi', ...
    'String', 'Plot at Angle = 0' ...
    );

Data.hGraph = uimenu(Data.hPLot, ...
    'Label', 'Graph' ...
    ); 
Data.hClearLines = uimenu(Data.hGraph, ...
    'Label', 'Clear', ...
    'Callback', @clearLinesCallback ...
    ); 

Data.hPrint = uimenu(Data.hPLot, ...
    'Label', 'Print' ...
    ); 
Data.hPrintPrint = uimenu(Data.hPrint, ...
    'Label', 'Print', ...
    'Callback', @printCallback ...
    ); 
Data.hHelp = uimenu(Data.hPLot, ...
    'Label', 'Help' ...
    ); 
Data.hHelpHelp = uimenu(Data.hHelp, ...
    'Label', 'Help', ...
    'Callback', @helpCallback ...
    ); 
Data.hAbout = uimenu(Data.hHelp, ...
    'Label', 'About', ...
    'Callback', @aboutCallback ...
    ); 
updatePlot(Data);
guidata(hPlot, Data);
return;

function updatePlot(Data)


AxString = get(Data.hAmpX, 'String');
Ax = str2double(AxString);

AyString = get(Data.hAmpY, 'String');
Ay = str2double(AyString);

nxString = get(Data.hNX, 'String');
nx = str2double(nxString);

nyString = get(Data.hNY, 'String');
ny = str2double(nyString);

pString = get(Data.hP, 'String');
piMult = str2double(pString);

thetaVal = get(Data.hPlotPoint, 'Value');
deriveOn = get(Data.hDerivOn, 'Value');

numPoints = 100*nx*ny;
thetaStep = 2 * pi /numPoints;
thetaVec = 0:thetaStep:(2*pi);

switch deriveOn 
    
    case 0
        
        xMax = Ax;
        yMax = Ay;
        
    case 1
        
        xMax = Ax*nx;
        yMax = Ay*ny;
end
set(Data.hAxes, ...
    'XLim', [-xMax, xMax], ...
    'YLim', [-yMax, yMax] ...
    );

xVal = Ax * cos(nx*thetaVec);
yVal = Ay * cos(ny*thetaVec + piMult*pi);
set(Data.hLine, ...
    'XData', xVal, ...
    'YData', yVal ...
    );

xPt = Ax * cos(nx*thetaVal);
yPt = Ay * cos(ny*thetaVal + piMult*pi);
set(Data.hPoint, ...
    'XData', xPt, ...
    'YData', yPt ...
    );

coordStr = sprintf('(%6.2f, %6.2f)', xPt, yPt);
set(Data.hPointMenuLabel, ...
    'Label', coordStr ...
    );

xString = sprintf('x = %6.2f cos (%d \\theta)', Ax, nx);
set(Data.hXAxisLabel, ...
    'HorizontalAlignment', 'Center', ...
    'Position', [0, -1.15*yMax], ...
    'FontSize', 14, ...
    'String', xString ...
    );
yString = sprintf('y = %6.2f cos (%d \\theta + %6.2f \\pi)', Ay, ny, piMult);
set(Data.hYAxisLabel, ...
    'HorizontalAlignment', 'Center', ...
    'Position', [-1.25*xMax, 0], ...
    'Rotation', 90, ...
    'FontSize', 14, ...
    'String', yString ...
    );
titleString = 'A_x cos(n_x \theta) vs. A_y cos(n_y \theta + p \pi)';
set(Data.Title, ....
    'HorizontalAlignment', 'Center', ...
    'Position', [0, 1.10*yMax], ...
    'FontSize', 14, ...
    'String', titleString ...
    );

switch deriveOn 
    
    case 0
        
        set(Data.hDerivLine, ...
            'XData', [], ...
            'YData', [] ...
            );
        
        set(Data.hDerivPoint, ...
            'XData', [], ...
            'YData', [] ...
            );
        derivStr = ' ';
        
    case 1
        
        xValDeriv = -nx*Ax * sin(nx*thetaVec);
        yValDeriv = -ny*Ay * sin(ny*thetaVec + piMult*pi);
        set(Data.hDerivLine, ...
            'XData', xValDeriv, ...
            'YData', yValDeriv ...
            );
        
        dxPt = -nx*Ax * sin(nx*thetaVal);
        dyPt = -ny*Ay * sin(ny*thetaVal + piMult*pi);
        set(Data.hDerivPoint, ...
            'XData', dxPt, ...
            'YData', dyPt ...
            );
        derivStr = sprintf('(%6.2f, %6.2f)', dxPt, dyPt);

end

set(Data.DerivMenuLabel, ...
    'Label', derivStr ...
    );

return;


function thetaSliderCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
thetaVal = get(Data.hPlotPoint, 'Value');
thetaStr = sprintf('Plot at Angle = %4.2f', thetaVal);
set(Data.hSliderLabel, ...
    'String', thetaStr ...
    );
guidata(hObj, Data);
updatePlot(Data);
return;

function plotDerivCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
guidata(hObj, Data);
updatePlot(Data);
return;

function GridOnCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
Value = get(hObj, 'Value');
switch Value
    
    case 0
        
        set(Data.hAxes, ...
            'XGrid', 'Off', ...
            'YGrid', 'Off' ...
            );
        
    case 1
        
        set(Data.hAxes, ...
            'XGrid', 'On', ...
            'YGrid', 'On' ...
            );
end    
guidata(hObj, Data);
return;


function UpdateCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
updatePlot(Data);
guidata(hObj, Data);
return;

function EditAmpXCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

strValue = get(hObj, 'String');
numValue = str2num(strValue); %#ok<*ST2NM>
if isnumeric(numValue) ...
        && (numel(numValue) == 1) ...
        && (numValue > 0)
    set(hObj, ...
        'UserData', strValue ...
        );
else
    oldValue = get(hObj, 'UserData');
    set(hObj, ...
        'String', oldValue ...
    );
    msg = sprintf('%s%s%s%s', ...
        strValue, ...
        ' is not a valid value for Ax.', ...
        ' A positive numeric scalar value is required.  Restoring old value = ', ...
        oldValue ...
        );
    msgbox(msg, 'Data Entry Error');
end

guidata(hObj, Data);
updatePlot(Data);
return;

function EditAmpYCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

strValue = get(hObj, 'String');
numValue = str2num(strValue); %#ok<*ST2NM>
if isnumeric(numValue) ...
        && (numel(numValue) == 1) ...
        && (numValue > 0)
    set(hObj, ...
        'UserData', strValue ...
        );
else
    oldValue = get(hObj, 'UserData');
    set(hObj, ...
        'String', oldValue ...
    );
    msg = sprintf('%s%s%s%s', ...
        strValue, ...
        ' is not a valid value for Ay.', ...
        ' A positive numeric scalar value is required.  Restoring old value = ', ...
        oldValue ...
        );
    msgbox(msg, 'Data Entry Error');
end

guidata(hObj, Data);
updatePlot(Data);
return;


function EditNXCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

strValue = get(hObj, 'String');
numValue = str2num(strValue); %#ok<*ST2NM>
if isnumeric(numValue) ...
        && (numel(numValue) == 1) ...
        && (numValue > 0) ...
        && (round(numValue) == numValue)
    set(hObj, ...
        'UserData', strValue ...
        );
else
    oldValue = get(hObj, 'UserData');
    set(hObj, ...
        'String', oldValue ...
    );
    msg = sprintf('%s%s%s%s', ...
        strValue, ...
        ' is not a valid value for nx.', ...
        ' A positive numeric INTEGER scalar value is required.  Restoring old value = ', ...
        oldValue ...
        );
    msgbox(msg, 'Data Entry Error');
end

guidata(hObj, Data);
updatePlot(Data);
return;

function EditNYCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

strValue = get(hObj, 'String');
numValue = str2num(strValue); %#ok<*ST2NM>
if isnumeric(numValue) ...
        && (numel(numValue) == 1) ...
        && (numValue > 0) ...
        && (round(numValue) == numValue)
    set(hObj, ...
        'UserData', strValue ...
        );
else
    oldValue = get(hObj, 'UserData');
    set(hObj, ...
        'String', oldValue ...
    );
    msg = sprintf('%s%s%s%s', ...
        strValue, ...
        ' is not a valid value for ny.', ...
        ' A positive numeric INTEGER scalar value is required.  Restoring old value = ', ...
        oldValue ...
        );
    msgbox(msg, 'Data Entry Error');
end

guidata(hObj, Data);
updatePlot(Data);
return;

function EditPCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

strValue = get(hObj, 'String');
numValue = str2num(strValue); %#ok<*ST2NM>
if isnumeric(numValue) ...
        && (numel(numValue) == 1) ...
        && (numValue > -2) ...
        && (numValue < 2)
    set(hObj, ...
        'UserData', strValue ...
        );
else
    oldValue = get(hObj, 'UserData');
    set(hObj, ...
        'String', oldValue ...
    );
    msg = sprintf('%s%s%s%s', ...
        strValue, ...
        ' is not a valid value for p.', ...
        ' A scalar value -2 < p < 2 is required.  Restoring old value = ', ...
        oldValue ...
        );
    msgbox(msg, 'Data Entry Error');
end

guidata(hObj, Data);
updatePlot(Data);
return;


function printCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
printdlg(Data.hPLot);
guidata(hObj, Data);
return;

function clearLinesCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);

set(Data.hLine, ...
    'XData', [], ...
    'YData', [] ...
    );
    
set(Data.hDerivLine, ...
    'XData', [], ...
    'YData', [] ...
    );

set(Data.hPoint, ...
    'XData', [], ...
    'YData', [] ...
    );

set(Data.hDerivPoint, ...
    'XData', [], ...
    'YData', [] ...
    );

set(Data.hPointMenuLabel, ...
    'Label', '(0.00,0.00)' ...
    );
set(Data.DerivMenuLabel, ...
    'Label', '(0.00,0.00)' ...
    );

guidata(hObj, Data);
return;
    
function aboutCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
msg = {'Lissajous Plot';
    'Copyright 2012';
    'Dr. Robert F. Szalapski';
    'www.CallMeDrRob.com'
    ' ' ;
    'Use and distribute freely for non-commercial purposes, ';
    'but but give appropriate credit to author Dr. Robert F. Szalapski ';
    'with reference to www.CallMeDrRob.com as publisher.'};
msgbox(msg, 'About Lissajous Plot');
guidata(hObj, Data);
return;

function helpCallback(hObj,event)  %#ok<INUSD>

Data = guidata(hObj);
msg = {'(1) Notation is defined in the graph title of the Current Plot.'; 
    ' ';
    '(2)  Set parameters in the edit fields of the Controls panel.'; 
    ' ';
    '(3)  Plot will refresh automatically or with Update Plot button.'; 
    ' ';
    '(4)  When plotted, derivative curve is half weight.';
    ' ';
    '(5)  Adjust angle with slider to mark point on curve.';
    ' ';
    '(6)  Right-click on highlighted point to display value.';
    ' ';
    '(7)  Point on derivative curve indicates slope of point on main curve.';};
msgbox(msg, 'Lissajous Plot Help');
guidata(hObj, Data);
return;

Contact us