function uisignalbuilder()
% UISIGNALBUILDER Signal Builder GUI
%
% UISIGNALBUILDER allows the user to create, delete and drag control points in
% order to build a signal using different interpolation methods.
%
% Useful commands:
% - double-click on a blank spot to create a control point
% - double-click on an existing control point to delete it
% - right-click on a control point to specify its coordinates manually
% - drag control points to adjust interpolation
%
% Example:
% >> uisignalbuilder()
%
% See also :
%
%
% Author: Laurent VAYLET
% Release: 1.0
% Release date: 04/22/2009
%
% Copyright 2009 - Laurent VAYLET
%
% Create controls
% Do not specify positions as GridBagLayout will take care of the layout later
hFig = figure('Name', 'Signal Builder', ...
'NumberTitle', 'off', ...
'MenuBar', 'none', ...
'Color', 'w');
hAxes = axes('Parent', hFig, ...
'ButtonDownFcn', @(src,evt)axesbuttondown());
hHelp = uicontrol(hFig, ...
'Style', 'pushbutton', ...
'BackgroundColor', 'w', ...
'String', 'Help', ...
'Callback', @(srv,evt)showhelp());
hOptions = uipanel(hFig, 'Title', ' Options ', 'FontWeight', 'bold', 'BackgroundColor', 'w', 'ForegroundColor', [0 0 0.5]);
hInterpMethodsLbl = uicontrol(hOptions, ...
'Style', 'text', ...
'BackgroundColor', 'w', ...
'String', 'Interpolation method:', ...
'HorizontalAlignment', 'left');
hInterpMethods = uicontrol(hOptions, ...
'Style', 'popup', ...
'BackgroundColor', 'w', ...
'String', {'linear', 'spline', 'pchip', 'nearest'}, ...
'Callback', @(src,evt)changeinterpmethod(), ...
'ToolTipString', 'Method used when interpolating');
hSamplingPeriodLbl = uicontrol(hOptions, ...
'Style', 'text', ...
'BackgroundColor', 'w', ...
'String', 'Sampling period (s):', ...
'HorizontalAlignment', 'left');
hSamplingPeriod = uicontrol(hOptions, ...
'Style', 'edit', ...
'BackgroundColor', 'w', ...
'String', '0.01', ...
'Callback', @(src,evt)changesamplingperiod(src), ...
'ToolTipString', 'Sampling period (s)');
hSignalLengthLbl = uicontrol(hOptions, ...
'Style', 'text', ...
'BackgroundColor', 'w', ...
'String', 'Signal length (s):', ...
'HorizontalAlignment', 'left');
hSignalLength = uicontrol(hOptions, ...
'Style', 'edit', ...
'BackgroundColor', 'w', ...
'String', '6', ...
'Callback', @(src,evt)changesignallength(src), ...
'ToolTipString', 'Signal length (s)');
hSnapGrid = uicontrol(hOptions, ...
'Style', 'checkbox', ...
'BackgroundColor', 'w', ...
'String', 'Snap To Grid', ...
'Value', 1, ...
'Callback', @(src,evt)togglesnapgrid());
hSave = uicontrol(hOptions, ...
'Style', 'pushbutton', ...
'BackgroundColor', 'w', ...
'String', 'Save...', ...
'Value', 1, ...
'Callback', @(src,evt)save(), ...
'ToolTipString', 'Save interpolated signal to workspace');
% Layout controls using GridBagLayout
figureLayout = layout.GridBagLayout(hFig, 'HorizontalGap', 15, 'VerticalGap', 15);
figureLayout.add(hAxes, [1 2], 1, 'Fill', 'Both');
figureLayout.add(hHelp, 1, 2, 'Fill', 'Horizontal', 'MinimumHeight', 25);
figureLayout.add(hOptions, 2, 2, 'Fill', 'Vertical', 'MinimumWidth', 130);
figureLayout.VerticalWeights = [0 1];
figureLayout.HorizontalWeights = [1 0];
figureLayout.setConstraints([1 2], 1, ... % hAxes
'LeftInset', 25, ...
'BottomInset', 25, ...
'RightInset', 15, ...
'TopInset', 15);
optionsLayout = layout.GridBagLayout(hOptions, 'HorizontalGap', 15, 'VerticalGap', 0);
optionsLayout.add(hInterpMethodsLbl, 1, 1, 'Fill', 'Horizontal');
optionsLayout.add(hInterpMethods, 2, 1, 'Fill', 'Horizontal');
optionsLayout.add(hSamplingPeriodLbl, 3, 1, 'Fill', 'Horizontal');
optionsLayout.add(hSamplingPeriod, 4, 1, 'Fill', 'Horizontal');
optionsLayout.add(hSignalLengthLbl, 5, 1, 'Fill', 'Horizontal');
optionsLayout.add(hSignalLength, 6, 1, 'Fill', 'Horizontal');
optionsLayout.add(hSnapGrid, 7, 1, 'Fill', 'Horizontal', 'Anchor', 'North');
optionsLayout.add(hSave, 8, 1, 'Fill', 'Horizontal', 'MinimumHeight', 30, 'Anchor', 'South');
optionsLayout.VerticalWeights = [0 0 0 0 0 0 1 0];
optionsLayout.HorizontalWeights = 1;
optionsLayout.setConstraints(1, 1, ... % hInterpMethodsLbL
'TopInset', 30);
optionsLayout.setConstraints(3, 1, ... % hSamplingPeriodLbL
'TopInset', 15);
optionsLayout.setConstraints(5, 1, ... % hSignalLengthLbL
'TopInset', 15);
optionsLayout.setConstraints(7, 1, ... % hSnapGrid
'TopInset', 15);
optionsLayout.setConstraints(8, 1, ... % hSave
'BottomInset', 15);
% Perform some initializations
hMarkers = []; % handles to markers
hSignal = []; % handle to interpolated signal
% Add some default markers
hold(hAxes, 'all')
signalLength = str2double(get(hSignalLength, 'String'));
addmarker(0:signalLength, zeros(1,7));
% Freeze axis
axis tight
ylim([-3 3])
axis equal
axis manual % freeze axis
% Add a grid
xLim = xlim(hAxes);
yLim = ylim(hAxes);
xGridInc = 0.2;
yGridInc = 0.2;
xGrid = xLim(1):xGridInc:xLim(2);
yGrid = yLim(1):yGridInc:yLim(2);
[XGrid,YGrid] = meshgrid(xGrid, yGrid);
hGrid = plot(XGrid, YGrid, ...
'Color', [.6 .6 .6], ...
'LineStyle', 'none', ...
'Marker', '.', ...
'MarkerSize', 1, ...
'HitTest', 'off');
snapGrid = true;
% Update plot
updateplot()
% -------------------------------------------------------------------------
function selectmarker(hMarker)
switch get(hFig, 'SelectionType')
case 'normal' % left click: drag
set(hFig, 'WindowButtonMotionFcn', @(src,evt)drag(hMarker), ...
'WindowButtonUpFcn',@(src,evt)releasemarker());
case 'open' % double click: delete
if length(hMarkers) > 2
delete(hMarker);
hMarkers(hMarkers == hMarker) = [];
updateplot()
else
warning('UISIGNALBUILDER:TwoMarkersNeeded', 'Cannot delete this marker. At least two markers are needed.');
end
case 'alt' % right click: change coordinates
prompt = {'X:', 'Y:'};
dlgTitle = 'Coordinates?';
numLines = 1;
defaults = {num2str(get(hMarker, 'XData')), ...
num2str(get(hMarker, 'YData'))};
answer = inputdlg(prompt, dlgTitle, numLines, defaults);
if ~isempty(answer)
set(hMarker, 'XData', str2double(answer{1}), ...
'YData', str2double(answer{2}));
updateplot()
end
end
end
% -------------------------------------------------------------------------
function [x,y] = getclosestgridpoint(x,y)
x = xGrid(abs(xGrid - x) <= xGridInc/2);
y = yGrid(abs(yGrid - y) <= yGridInc/2);
end
% -------------------------------------------------------------------------
function drag(hMarker)
cp = get(hAxes, 'CurrentPoint');
x = cp(1,1);
y = cp(1,2);
if snapGrid
[x, y] = getclosestgridpoint(x, y);
end
set(hMarker, 'XData', x, 'YData', y, 'ZData', -1);
updateplot()
end
% -------------------------------------------------------------------------
function releasemarker()
set(hFig,'WindowButtonMotionFcn',[]);
end
% -------------------------------------------------------------------------
function updateplot()
idxMethod = get(hInterpMethods, 'Value');
availableMethods = get(hInterpMethods, 'String');
chosenMethod = availableMethods{idxMethod};
x = cell2mat(get(hMarkers, 'XData'));
[x, sortOrder] = sort(x);
y = cell2mat(get(hMarkers, 'YData'));
y = y(sortOrder);
% Use specified sampling period for interpolating values
xi = xLim(1):str2double(get(hSamplingPeriod, 'String')):xLim(2);
try
yi = interp1(x, y, xi, chosenMethod);
catch me
if strcmp(me.identifier, 'MATLAB:interp1:RepeatedValuesX')
yi = NaN(size(xi)); % plot nothing
end
end
% Plot if signal does not exist yet, update it otherwise
if ~isempty(hSignal)
set(hSignal, 'XData', xi, 'YData', yi, 'Visible', 'on');
else
hSignal = plot(xi, yi, 'b', 'HitTest', 'off');
end
end
% -------------------------------------------------------------------------
function axesbuttondown()
if strcmp(get(hFig, 'SelectionType'), 'open') % double click
cp = get(hAxes, 'CurrentPoint');
addmarker(cp(1,1), cp(1,2));
updateplot();
end
end
% -------------------------------------------------------------------------
function addmarker(x,y)
error(nargchk(0, 2, nargin))
for k = 1:length(x)
hMarkers(end+1) = plot3(x(k), y(k), -1, 'o', ...
'MarkerEdgeColor', 'b', ...
'MarkerFaceColor', 'y', ...
'MarkerSize', 8, ...
'ButtonDownFcn', @(src,evt)selectmarker(src)); %#ok<AGROW>
end
end
% -------------------------------------------------------------------------
function changeinterpmethod()
updateplot()
end
% -------------------------------------------------------------------------
function togglesnapgrid()
snapGrid = ~snapGrid;
end
% -------------------------------------------------------------------------
function save()
checkLabels = {'Save X coordinates to variable named:' ...
'Save Y coordinates to variable named:'};
varNames = {'x', 'y'};
items = {get(hSignal, 'XData'), get(hSignal, 'YData')};
export2wsdlg(checkLabels, varNames, items, 'Save to Workspace');
end
% -------------------------------------------------------------------------
function changesamplingperiod(src)
% Get new sampling period
newValue = str2double(get(src, 'String'));
% Validate
if isempty(newValue)
warning('UISIGNALBUILDER:InvalidSamplingPeriod', 'Invalid value for sampling period. Resetting to 0.01.');
newValue = 0.01;
set(src, 'String', num2str(newValue));
end
updateplot()
end
% -------------------------------------------------------------------------
function changesignallength(src)
% Get new sampling period
newValue = str2double(get(src, 'String'));
% Validate
if isempty(newValue)
warning('UISIGNALBUILDER:InvalidSignalLength', 'Invalid value for signal length. Resetting to 6.');
newValue = 6;
set(src, 'String', num2str(newValue));
end
xlim(hAxes, [0 newValue])
updategrid()
updateplot()
end
% -------------------------------------------------------------------------
function updategrid()
delete(hGrid)
xLim = xlim(hAxes);
xGrid = xLim(1):xGridInc:xLim(2);
yGrid = yLim(1):yGridInc:yLim(2);
[XGrid,YGrid] = meshgrid(xGrid, yGrid);
hGrid = plot(XGrid, YGrid, ...
'Color', [.6 .6 .6], ...
'LineStyle', 'none', ...
'Marker', '.', ...
'MarkerSize', 1, ...
'HitTest', 'off');
end
% -------------------------------------------------------------------------
function showhelp()
mes = {' - Drag a marker to change its position', ...
' - Double-click anywhere to create a marker', ...
' - Double-click on a marker to delete it', ...
' - Right-click on a marker to fine tune its position'};
uiwait(msgbox(mes,'Help','help','modal'))
end
% -------------------------------------------------------------------------
end