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;