function editingtool(action)
%Graphic Editing Tool
%
%Usage:
% editingtool('On') - enables the function
% editingtool('Off') - disenables the function
% editingtool - Enables the function if it is Off
% or disenables the function if it is On
% If there is no current figure, a demo is run
% editingtool(handles) - enables the function only
% to the handles specified
%
%Description:
% Allows you to edit plots and text by just
% selecting them with the mouse.
%
% You can easily move, rotate, change properties etc. of lines
% and text by simply clicking on them or typing specific keys.
%
% A double click will select a specific point within a line.
%
%The following Key-Press Functions may used with this program
% return - Opens the Java property window of the current object(s)
% delete - Deletes the current object(s)
% insert - Inserts in additional point to the selected line object
% c - copies the current object(s) and places them using the mouse
% d - Draw a line by left-clicking on points and right-click when finished
% The Tag property is set to 'line'
% p - Draw a polygon by left-clicking on points and right-click when
% finished, the polygon is close with the first point
% The Tag property is set to 'polygon'
% t - Insert text by click on a position and typing
%
% Works by setting the ButtonDownFcn of the
% current axes and children as editingtool('BtnDown').
%
%NOTES:
% The Tag property is useful for extracting the data from the lines
% of polygons. For example
% x = get(findobj('Tag','line'),'Xdata')
% y = get(findobj('Tag','polygon'),'Ydata')
%
% The userdata of the figure is set to a structure.
% If the userdata already contains a structure,
% new fields are added for internal use of the program.
% The fields are described bellow
% hAx: Current axes
% hSlc: Handle of line used for point seleccion
% hObj: List of handles of selected objects
% NewObj: Used for pasting
% index: Index of point within a line
% hMod: Handle of object that contains a selected point
% Button: Mouse button selected
% CurrentPoint: Last point selected, [2x3 double]
% hLine: Line drawn during rotation
% Orig: Used for rotation and movement of objects, [2x3 double]
%
%Key Words: Edit, Rotate, Move, Delete, Add Lines, Draw
%
%Copy-Left, Alejandro Sanchez-Barba
if nargin==0
hFig = get(0,'CurrentFigure');
if isempty(hFig)
rundemo
drawnow
end
ud = get(hFig,'UserData');
if isfield(ud,'manual')
if strcmp(ud.manual,'On')
action = 'Off';
else %off
action = 'On';
end
else
action = 'On';
end
elseif strcmpi(action,'On')
action = 'On';
elseif strcmpi(action,'Off')
action = 'Off';
elseif ishandle(action)
editobj(action)
return
end
eval(action)
return
%-----------------------------------------------------------
function BtnDown
hFig = get(0,'CurrentFigure');
ud = get(hFig,'UserData');
hAx = get(hFig,'CurrentAxes');
ud.hAx = hAx;
Button = get(hFig,'SelectionType');
ud.Button = Button;
ud.CurrentPoint = get(hAx,'CurrentPoint');
hObj = get(hFig,'CurrentObject');
ObjType = {get(hObj,'Type')}; %Force to cell
switch ud.Button
case 'normal' %Move
%check if it is alredy highlighted
if strcmpi(get(hObj,'Selected'),'On')
ud.Orig = ud.CurrentPoint;
% setptr(hFig,'closedhand');
set(hFig,'pointer','custom')
set(hFig,'PointerShapeCData',NaN*ones(16))
set(hFig,'WindowButtonMotionFcn', ...
'editingtool(''BtnMotion'')',...
'WindowButtonUpFcn', ...
'editingtool(''BtnUp'')')
else
set(hObj,'Selected','On')
if all(ud.hObj~=hObj)
ud.hObj(end+1) = hObj;
end
set(hFig,'WindowButtonMotionFcn','',...
'WindowButtonUpFcn', '');
end %if
case 'alt' %Rotate
cond1 = strcmpi(get(hObj,'Selected'),'On');
cond2 = (hObj==hAx & strcmpi(get(ud.hObj,'Selected'),'On'));
if cond1 || cond2
setptr(hFig,'rotate'); %only valid with newer versions
ud.hLine = line('XData',[],'YData',[], ...
'Parent',hAx,'Color','k');
ud.Orig = ud.CurrentPoint;
set(hFig,'WindowButtonMotionFcn', ...
'editingtool(''BtnMotion'')',...
'WindowButtonUpFcn', ...
'editingtool(''BtnUp'')')
else
set(hObj,'Selected','Off')
set(hFig,'WindowButtonMotionFcn','',...
'WindowButtonUpFcn', '');
end %if
case 'open'
if strcmpi(ObjType,'line')
set(hObj,'Selected','Off')
x = get(hObj,'Xdata');
y = get(hObj,'Ydata');
CurrentPoint = ud.CurrentPoint;
x0 = CurrentPoint(1,1);
y0 = CurrentPoint(1,2);
dist = sqrt((x0 - x).^2 + (y0 - y).^2);
[dummy,index] = min(dist);
ud.index = index;
ud.hMod = hObj;
marker = get(hObj,'Marker');
if strcmpi(marker,'.')
marker = 'o';
end
markersize = get(hObj,'MarkerSize');
markeredgecolor = get(hObj,'MarkerEdgeColor');
markerfacecolor = get(hObj,'MarkerFaceColor');
set(ud.hSlc,'Parent',hAx, ...
'Xdata',x(index),'Ydata',y(index), ...
'Color','m','Marker',marker, ...
'MarkerSize',markersize, ...
'MarkerEdgeColor',markeredgecolor, ...
'MarkerFaceColor',markerfacecolor, ...
'Visible','On','Selected','On');
%Now bring hSlc to the front
chldrn = get(hAx,'Children');
chldrn(chldrn == ud.hSlc) = [];
chldrn = [ud.hSlc; chldrn(:)];
set(hAx,'Children',chldrn)
elseif strcmpi(ObjType,'text')
set(hObj,'Editing','On')
end %if
end %switch
set(hFig,'UserData',ud)
return
%-----------------------------------------------------------
function BtnMotion
hFig = get(0,'CurrentFigure');
ud = get(hFig,'UserData');
hAx = get(hFig,'CurrentAxes');
ud.hAx = hAx;
hObj = ud.hObj;
ObjType = get(hObj,'Type');
n = length(hObj);
if n==1
ObjType = {ObjType};
end
LastPoint = ud.CurrentPoint;
CurrentPoint = get(hAx,'CurrentPoint');
switch ud.Button
case 'normal' %Move
if ~isempty(ud.hMod) %select object
xb = get(ud.hMod,'Xdata');
yb = get(ud.hMod,'Ydata');
xb(ud.index) = CurrentPoint(1,1);
yb(ud.index) = CurrentPoint(1,2);
set(ud.hMod,'Xdata',xb,'Ydata',yb)
set(ud.hSlc,'Xdata',CurrentPoint(1,1), ...
'Ydata',CurrentPoint(1,2))
else %move object
%Calculate Difference
dx = CurrentPoint(1,1) - LastPoint(1,1);
dy = CurrentPoint(1,2) - LastPoint(1,2);
%Update position on Plot
for k=1:n
switch ObjType{k}
case 'line'
x = get(hObj(k),'XData') + dx;
y = get(hObj(k),'YData') + dy;
set(hObj(k),'XData',x,'YData',y)
case 'text'
pos = get(hObj(k),'Position');
x = pos(1) + dx;
y = pos(2) + dy;
set(hObj(k),'Position',[x,y])
end %switch
end %for
dxx = LastPoint(1,1) - ud.Orig(1,1);
dyy = LastPoint(1,2) - ud.Orig(1,2);
dist = sqrt(dxx^2 + dyy^2);
disp(['Distances Moved: dx=',num2str(dxx),...
', dy=',num2str(dyy),', d=',num2str(dist)])
end %if
case 'alt' %Rotate
xline = [ud.Orig(1,1),CurrentPoint(1,1)];
yline = [ud.Orig(1,2),CurrentPoint(1,2)];
set(ud.hLine,'XData',xline,'YData',yline)
u1 = CurrentPoint(1,1) - ud.Orig(1,1);
v1 = CurrentPoint(1,2) - ud.Orig(1,2);
th1 = atan2(v1,u1);
u2 = LastPoint(1,1) - ud.Orig(1,1);
v2 = LastPoint(1,2) - ud.Orig(1,2);
th2 = atan2(v2,u2);
theta = th1 - th2;
for k=1:n
switch ObjType{k}
case 'line'
x = get(hObj(k),'XData');
y = get(hObj(k),'YData');
rot = [cos(theta), - sin(theta); ...
sin(theta), cos(theta)];
vec = [x(:) - ud.Orig(1,1), ...
y(:) - ud.Orig(1,2)];
newvec = (rot*vec')';
x = newvec(:,1) + ud.Orig(1,1);
y = newvec(:,2) + ud.Orig(1,2);
set(hObj(k),'XData',x,'YData',y)
case 'text'
thetaold = get(hObj(k),'Rotation');
thetanew = thetaold + theta*180/pi;
set(hObj(k),'Rotation',thetanew)
end %switch
end %for
rotangle = th2*180/pi;
disp(['Angle Rotated: ',num2str(rotangle),''])
end %switch
ud.CurrentPoint = CurrentPoint;
set(hFig,'UserData',ud)
return
%-----------------------------------------------------------
function BtnUp
hFig = get(0,'CurrentFigure');
setptr(hFig,'arrow');
set(hFig,'WindowButtonMotionFcn','',...
'WindowButtonUpFcn','');
ud = get(hFig,'UserData');
%set hObj to the ones that are selected
% set(ud.hObj,'Selected','Off')
set(ud.NewObj,'Selected','Off')
set(ud.hSlc,'Visible','Off')
% ud.hObj = [];
ud.NewObj = [];
ud.hMod = [];
if strcmp(ud.Button,'alt')
set(ud.hLine,'XData',[],'YData',[])
end
set(hFig,'UserData',ud)
return
%----------------------------------------------------------
function clean
hFig = get(0,'CurrentFigure');
hAx = findobj(get(hFig,'Children'),'Type','Axes');
setptr(hFig,'arrow')
ud = get(hFig,'UserData');
Button = get(hFig,'SelectionType');
if strcmpi(Button,'alt') && ~isempty(ud.hObj) %rotate from any point
BtnDown
return
end
set(hFig,'WindowButtonMotionFcn','', ...
'WindowButtonUpFcn','', ...
'WindowButtonDownFcn','')
set(hAx,'ButtonDownFcn', ...
'editingtool(''clean'')')
h = get(hAx,'Children');
if iscell(h)
h = cell2mat(h);
end
set(h,'Selected','Off')
% set(h,'ButtonDownFcn','editingtool(''BtnDown'')')
ud.hObj = [];
ud.NewObj = [];
ud.hMod = [];
ud.index = [];
set(ud.hSlc,'Visible','Off')
set(hFig,'UserData',ud)
return
%----------------------------------------------------------
function KeyPress(src,evnt,op)
hFig = get(0,'CurrentFigure');
hAx = get(hFig,'CurrentAxes');
ud = get(hFig,'UserData');
% evnt.Key = get(hFig,'CurrentCharacter');
if isempty(ud.hObj) && ~any(strcmpi(evnt.Key,{'d','p','t'}))
return
end
ud.key = evnt.Key;
switch evnt.Key
case 'return'
inspect(ud.hObj)
case 'delete'
if ~isempty(ud.hMod)
xb = get(ud.hMod,'Xdata');
yb = get(ud.hMod,'Ydata');
xb(ud.index) = [];
yb(ud.index) = [];
set(ud.hMod,'Xdata',xb,'Ydata',yb)
set(ud.hSlc,'Xdata',[],'Ydata',[])
else
delete(ud.hObj)
clean
return
end
case 'insert'
%You can only insert a point in the last selected object
if ~isempty(ud.hMod)
ud.hObj = ud.hMod;
ud.hMod = [];
set(ud.hObj,'Selected','On')
else
ud.hObj = ud.hObj(end);
end
set(ud.hObj,'ButtonDownFcn', ...
'editingtool(''DrawBtnDwnFcn'')')
setptr(hFig,'add');
case 'c'
ud.NewObj = copyobj(ud.hObj,gca);
set(ud.hObj,'Selected','Off')
setptr(hFig,'hand')
set(hFig,'WindowButtonMotionFcn', ...
'editingtool(''WinBtnMtnFcn'')', ...
'WindowButtonDownFcn', ...
'editingtool(''clean'')')
case {'d','p'} %line or polygon
clean
ud.hAx = get(hFig,'CurrentAxes');
setptr(hFig,'add');
ud.NewObj = line('Xdata',[],'Ydata',[], ...
'Color','k','LineStyle','-', ...
'Marker','o','MarkerFaceColor','k', ...
'MarkerSize',4);
if strcmpi(evnt.Key,'d')
tag = 'line';
else
tag = 'polygon';
end
set(ud.NewObj,'Tag',tag)
set(hFig,'WindowButtonDownFcn', ...
'editingtool(''DrawBtnDwnFcn'')', ...
'WindowButtonMotionFcn', ...
'editingtool(''DrawMtnFcn'')')
hAx = findobj(get(hFig,'Children'),'Type','Axes');
set(hAx,'ButtonDownFcn','')
h = get(hAx,'Children');
if iscell(h)
h = cell2mat(h);
end
set(h,'ButtonDownFcn','')
case 't'
clean
xyt = ginput(1);
text(xyt(1),xyt(2),'','Editing','On')
end
set(hFig,'UserData',ud)
return
%-----------------------------------------------------------
function DrawBtnDwnFcn
hFig = get(0,'CurrentFigure');
setptr(hFig,'add');
ud = get(hFig,'UserData');
% state = uisuspend(hFig);
button = get(hFig,'SelectionType');
pt = get(gca,'CurrentPoint');
%inserting new point in an exiting line
if isempty(ud.NewObj)
x = get(ud.hObj,'Xdata');
y = get(ud.hObj,'Ydata');
if any(strcmpi(button,{'open','normal'}))
%Insert point after nearest point
x0 = pt(1,1);
y0 = pt(1,2);
dist = sqrt((x0 - x).^2 + (y0 - y).^2);
[dummy,index] = min(dist);
x(index+1:end+1) = x(index:end);
y(index+1:end+1) = y(index:end);
ud.index = index + 1;
set(hFig,'WindowButtonDownFcn', ...
'editingtool(''DrawBtnDwnFcn'')', ...
'WindowButtonMotionFcn', ...
'editingtool(''DrawMtnFcn'')')
set(ud.hObj,'Xdata',x,'Ydata',y, ...
'ButtonDownFcn','')
else
x(ud.index) = [];
y(ud.index) = [];
set(ud.hObj,'Xdata',x,'Ydata',y, ...
'ButtonDownFcn','editingtool(''BtnDown'')')
set(hFig,'WindowButtonDownFcn','', ...
'WindowButtonMotionFcn','')
ud.index = [];
ud.hObj = [];
end
else %drawing a new line object
x = get(ud.NewObj,'Xdata');
y = get(ud.NewObj,'Ydata');
switch lower(button)
case {'open','normal'}
if isempty(x)
x(end+1) = pt(1,1);
y(end+1) = pt(1,2);
else
x(end) = pt(1,1);
y(end) = pt(1,2);
end
x(end+1) = NaN;
y(end+1) = NaN;
set(ud.NewObj,'Xdata',x,'Ydata',y)
case {'extend','alt'} %finish drawing
if isequal(ud.key,'p') %close polygon
x = get(ud.NewObj,'Xdata');
y = get(ud.NewObj,'Ydata');
x(end) = x(1);
y(end) = y(1);
set(ud.NewObj,'Xdata',x,'Ydata',y)
else %line
set(ud.NewObj,'Xdata',x(1:end-1),'Ydata',y(1:end-1))
end
clean
hAx = findobj(get(hFig,'Children'),'Type','Axes');
h = findobj(hAx,'Type','Line','-or','Type','Text');
hh = findobj(hAx,'Type','Surface','-or',...
'Type','Image','-or','Type','hggroup',...
'-or','Type','Patch');
if iscell(h)
h = cell2mat(h);
end
if iscell(hh)
hh = cell2mat(hh);
end
set(h,'ButtonDownFcn','editingtool(''BtnDown'')')
set(hh,'ButtonDownFcn','editingtool(''clean'')')
end %switch
end %if
% uirestore(state);
set(hFig,'UserData',ud)
return
%-----------------------------------------------------------
function DrawMtnFcn
hFig = get(0,'CurrentFigure');
ud = get(hFig,'UserData');
pt = get(ud.hAx,'CurrentPoint');
if isempty(ud.NewObj) %inserting new point in an exiting line
x = get(ud.hObj,'Xdata');
y = get(ud.hObj,'Ydata');
x(ud.index) = pt(1,1);
y(ud.index) = pt(1,2);
set(ud.hObj,'Xdata',x,'Ydata',y)
else %drawing a new line object
x = get(ud.NewObj,'Xdata');
y = get(ud.NewObj,'Ydata');
if isempty(x)
return
end %if
x(end) = pt(1,1);
y(end) = pt(1,2);
set(ud.NewObj,'Xdata',x,'Ydata',y)
end %if
return
%-----------------------------------------------------------
function WinBtnMtnFcn
hFig = get(0,'CurrentFigure');
ud = get(hFig,'UserData');
CurrentPoint = get(ud.hAx,'CurrentPoint');
LastPoint = ud.CurrentPoint;
NewObjType = get(ud.NewObj,'Type');
n = length(ud.NewObj);
if n==1
NewObjType = {NewObjType};
end
dx = CurrentPoint(1,1) - LastPoint(1,1);
dy = CurrentPoint(1,2) - LastPoint(1,2);
for k=1:n
switch NewObjType{k}
case 'line'
x = get(ud.NewObj(k),'XData') + dx;
y = get(ud.NewObj(k),'YData') + dy;
set(ud.NewObj(k),'XData',x,'YData',y)
case 'text'
pos = get(ud.NewObj(k),'Position');
x = pos(1) + dx;
y = pos(2) + dy;
set(ud.NewObj(k),'Position',[x,y])
end %switch
end %for
ud.CurrentPoint = CurrentPoint;
set(hFig,'UserData',ud)
return
%---------------------------------------------------------
function editobj(h)
editingtool('Off')
h = h(:);
hFig = get(0,'CurrentFigure');
set(hFig,'DoubleBuffer','On', ...
'KeyPressFcn',@KeyPress)
ud = get(hFig,'UserData');
ud.hAx = get(h,'Parent');
if iscell(ud.hAx)
ud.hAx = cell2mat(ud.hAx);
end
%Bring to front
chldrn = get(ud.hAx,'Children');
if iscell(chldrn)
chldrn = cell2mat(chldrn);
end
for k=1:length(h)
chldrn(chldrn == h(k)) = [];
end
chldrn = [h; chldrn(:)];
set(ud.hAx,'Children',chldrn)
ud.manual = 'On';
ud.hSlc = line('Xdata',[],'Ydata',[]);
ud.index = [];
ud.hObj = [];
ud.hMod = [];
ud.NewObj = [];
set(hFig,'UserData',ud)
set(ud.hAx,'XLimMode','manual','YLimMode','manual', ...
'ButtonDownFcn','editingtool(''clean'')')
set([h; ud.hSlc],'ButtonDownFcn','editingtool(''BtnDown'')')
hh = findobj(ud.hAx,'Type','Surface','-or',...
'Type','Image','-or','Type','hggroup',...
'-or','Type','Patch');
if iscell(hh)
hh = cell2mat(hh);
end
set(hh,'ButtonDownFcn','editingtool(''clean'')')
return
%-----------------------------------------------------------
function On
hFig = get(0,'CurrentFigure');
set(0,'DefaultLineCreateFcn','editingtool(''CreateFcn'')')
set(0,'DefaultTextCreateFcn','editingtool(''CreateFcn'')')
if isempty(hFig)
return
end
ud = get(hFig,'UserData');
ud.hAx = get(hFig,'CurrentAxes');
set(hFig,'DoubleBuffer','On', ...
'KeyPressFcn',@KeyPress)
% 'KeyPressFcn','editingtool(''KeyPress'')') %older releases
hAx = findobj(get(hFig,'Children'),'Type','Axes');
set(hAx,'XLimMode','manual','YLimMode','manual', ...
'ButtonDownFcn','editingtool(''clean'')')
ud.manual = 'On';
ud.hSlc = line('Xdata',[],'Ydata',[]);
ud.index = [];
ud.hObj = [];
ud.hMod = [];
ud.NewObj = [];
set(hFig,'UserData',ud)
%editingtool only works with lines and text
h = findobj(hAx,'Type','Line','-or','Type','Text');
hh = findobj(hAx,'Type','Surface','-or',...
'Type','Image','-or','Type','hggroup',...
'-or','Type','Patch');
if iscell(h)
h = cell2mat(h);
end
if iscell(hh)
hh = cell2mat(hh);
end
set(h,'ButtonDownFcn','editingtool(''BtnDown'')')
set(hh,'ButtonDownFcn','editingtool(''clean'')')
return
%---------------------------------------------------------
function CreateFcn
h = get(0,'CallbackObject');
set(h,'ButtonDownFcn','editingtool(''On'')')
return
%-----------------------------------------------------------
function Off
hFig = get(0,'CurrentFigure');
ud = get(hFig,'UserData');
hAx = findobj(get(hFig,'Children'),'Type','Axes');
% set(hAx,'XLimMode','auto','YLimMode','auto')
set(hAx,'ButtonDownFcn',[])
ud.manual = 'Off';
set(hFig,'UserData',ud)
h = get(hAx,'Children');
if iscell(h)
h = cell2mat(h);
end
set(h,'ButtonDownFcn',[])
set(hFig,'WindowButtonMotionFcn','',...
'WindowButtonUpFcn','')
set(0,'DefaultLineCreateFcn',[])
set(0,'DefaultTextCreateFcn',[])
return
%-----------------------------------------------------------
function rundemo
subplot(2,1,1)
x = -pi:pi/10:pi;
y = tan(sin(x)) - sin(tan(x));
plot(x,y,'--rs','LineWidth',2,...
'MarkerEdgeColor','k',...
'MarkerFaceColor','g',...
'MarkerSize',3)
hold on
plot(y,-x,'-bo','LineWidth',1,...
'MarkerEdgeColor','k',...
'MarkerFaceColor','r',...
'MarkerSize',5)
text(pi/2,sin(-pi/4),'\leftarrow sin(-\pi\div4)',...
'HorizontalAlignment','left')
subplot(2,1,2)
y = sin(x);
plot(x,y,'b<-')
xlabel('x-axis')
ylabel('y-axis')
title('Title')
return