David
David

Reputation: 1021

Sliding a point on a curve

I have tried the following code in Matlab 2017b:

function demo()

clc,close all

fig=figure();

ax=axes(fig,...
    'Units','Normalized',...
    'Position',[0.2,0.2,0.6,0.6],...
    'XGrid','on',...
    'YGrid','on')

slider=uicontrol(...
    'Parent',fig,...
    'Style','slider',...
    'Units','normalized',...
    'Position',[0.2,0.9,0.6,0.05],...
    'Tag','slider1',...
    'Min',0,...
    'Max',10,...
    'Value',1,...
    'Callback',@slider_callback);

x=linspace(0,10);
y=1/3*x.^2;
plot(x,y,'b-')
grid on
xlabel('x-axis')
ylabel('y-axis')
hold on
p=plot(1,1/3,'ro')
hold off

    function slider_callback(hObject,eventdata)
        x=hObject.Value;
        p.XData=x;
        p.YData=1/3*x.^2;
        drawnow
    end

end

Currently, I move the slider and it is only when I release the mouse button that the point is updated. What changes can I make so that I see the point move as I drag the slider?

Thanks.

UPDATE: Thanks to VIG's comment, I was able to do this:

function demo()

clc,close all

fig=figure();

% initialize some parameters
xstart=4;
ystart=1/3*4^2;

ax=axes(fig,...
    'Units','Normalized',...
    'Position',[0.2,0.2,0.6,0.6],...
    'XGrid','on',...
    'YGrid','on');

slider=uicontrol(...
    'Parent',fig,...
    'Style','slider',...
    'Units','normalized',...
    'Position',[0.2,0.9,0.6,0.05],...
    'Tag','slider1',...
    'Min',0,...
    'Max',10,...
    'Value',xstart);

addlistener(slider,'Value',...
    'PostSet',@(hObject, event) slider_callback(slider, event));

x=linspace(0,10);
y=1/3*x.^2;
plot(x,y,'b-')
grid on
xlabel('x-axis')
ylabel('y-axis')
hold on
plot([-5,10],[0,0],'k-');
plot([0,0],[-5,35],'k-');
p=plot(xstart,ystart,'ro');
a1=quiver(0,0,xstart,ystart,0,...
    'LineWidth',1,...
    'Color','red');
a2=quiver(0,0,0,xstart,0,...
    'LineWidth',1,...
    'Color','red');
a3=quiver(0,0,xstart,0,0,...
    'LineWidth',1,...
    'Color','red');
d=plot([0,xstart,xstart],[ystart,ystart,0],'k--');
hold off

    function slider_callback(hObject,eventdata)
        x=hObject.Value;
        p.XData=x;
        p.YData=1/3*x.^2;
        a1.UData=x;
        a1.VData=1/3*x.^2;
        a2.VData=1/3*x.^2;
        a3.UData=x;
        d.XData=[0,x,x];
        d.YData=[1/3*x.^2,1/3*x.^2,0];
        drawnow
    end

end

Which produces this image:

enter image description here

I'd love to hear any further suggestions.

Thanks.

Upvotes: 0

Views: 137

Answers (1)

ViG
ViG

Reputation: 1868

You need to add a listener, and then you might omit the callback when creating the slider:

slider=uicontrol(...
    'Parent',fig,...
    'Style','slider',...
    'Units','normalized',...
    'Position',[0.2,0.9,0.6,0.05],...
    'Tag','slider1',...
    'Min',0,...
    'Max',10,...
    'Value',1);

addlistener(slider,'Value','PostSet',@(hObject, event) slider_callback(slider, event));

EDIT:

You can use impoint. Then add:

....
addlistener(slider,'Value',...
    'PostSet',@(hObject, event) slider_callback(slider, event));

h = impoint(gca,xstart,ystart);
setColor(h,'r')
point = h;
addNewPositionCallback(h,@(h) make_constraint(h));

x=linspace(0,10);
...

and delete the plot p=plot(xstart,ystart,'ro');.

In slider_callback replace

p.XData=x;
p.YData=1/3*x.^2;

with

setPosition(point, [x 1/3*x^2])

and add

function make_constraint(h)
        fcn = makeConstrainToRectFcn('impoint',[0 10],[1/3*h(1).^2 1/3*h(1).^2]);
        % Enforce boundary constraint function using setPositionConstraintFcn
        setPositionConstraintFcn(point,fcn);
        a1.UData=h(1);
        a1.VData=1/3*h(1).^2;
        a2.VData=1/3*h(1).^2;
        a3.UData=h(1);
        d.XData=[0,h(1),h(1)];
        d.YData=[1/3*h(1).^2,1/3*h(1).^2,0];
        set(slider, 'Value', h(1))
        drawnow
    end

So total code:

function stackover()

clc,close all

fig=figure();

% initialize some parameters
xstart=4;
ystart=1/3*4^2;

ax=axes(fig,...
    'Units','Normalized',...
    'Position',[0.2,0.2,0.6,0.6],...
    'XGrid','on',...
    'YGrid','on');


slider=uicontrol(...
    'Parent',fig,...
    'Style','slider',...
    'Units','normalized',...
    'Position',[0.2,0.9,0.6,0.05],...
    'Tag','slider1',...
    'Min',0,...
    'Max',10,...
    'Value',xstart);

addlistener(slider,'Value',...
    'PostSet',@(hObject, event) slider_callback(slider, event));

h = impoint(gca,xstart,ystart);
setColor(h,'r')
point = h;
addNewPositionCallback(h,@(h) make_constraint(h));

x=linspace(0,10);
y=1/3*x.^2;
plot(x,y,'b-')
grid on
xlabel('x-axis')
ylabel('y-axis')
hold on
plot([-5,10],[0,0],'k-');
plot([0,0],[-5,35],'k-');

a1=quiver(0,0,xstart,ystart,0,...
    'LineWidth',1,...
    'Color','red');
a2=quiver(0,0,0,xstart,0,...
    'LineWidth',1,...
    'Color','red');
a3=quiver(0,0,xstart,0,0,...
    'LineWidth',1,...
    'Color','red');
d=plot([0,xstart,xstart],[ystart,ystart,0],'k--');
hold off

    function slider_callback(hObject,eventdata)
        x=hObject.Value;
        setPosition(point, [x 1/3*x^2])
        a1.UData=x;
        a1.VData=1/3*x.^2;
        a2.VData=1/3*x.^2;
        a3.UData=x;
        d.XData=[0,x,x];
        d.YData=[1/3*x.^2,1/3*x.^2,0];
        drawnow
    end

    function make_constraint(h)
        fcn = makeConstrainToRectFcn('impoint',[0 10],[1/3*h(1).^2 1/3*h(1).^2]);
        % Enforce boundary constraint function using setPositionConstraintFcn
        setPositionConstraintFcn(point,fcn);
        a1.UData=h(1);
        a1.VData=1/3*h(1).^2;
        a2.VData=1/3*h(1).^2;
        a3.UData=h(1);
        d.XData=[0,h(1),h(1)];
        d.YData=[1/3*h(1).^2,1/3*h(1).^2,0];
        set(slider, 'Value', h(1))
        drawnow
    end

end

ps demo() is an existing MATLAB function, so it's best that you choose another name.


EDIT 2:

In the case that you can't use impoint, you have to add 3 listeners:

  • WindowButtonUpFcn: detects when mouse is released.
  • WindowButtonMotionFcn: detects when mouse is moved.
  • ButtonDownFcn: detects when mouse is clicked.

The first 2 need to be attached to the figure:

fig=figure('WindowButtonUpFcn',@drop,'WindowButtonMotionFcn',@move);

The last one can be attached to the plot only:

p=plot(xstart,ystart,'ro','ButtonDownFcn',@click);

Since WindowButtonUpFcn and WindowButtonMotionFcn are for the whole figure, these will always be called on motion or release of the mouse. But we only want the functions to be executed when dragging the point. To do this a variable (dragging) is introduced. In the click function this variable is set to 1, to indicate that we're dragging.

Then while moving the mouse, the point, quivers and slider are updated in move. Here a control unit is added to make sure you don't drag the point out of the boundaries of the slider.

When the mouse is released, dragging is set to 0 again.

Of course it would be nice to know when you are hovering above the point, so that you know that if you click now, you can drag the point. To do this we can set the pointerbehaviour with

iptSetPointerBehavior(p, pointerBehavior);

Here pointerBehavior is a struct containing 3 functions:

  • enterFcn: executed when mouse enters object.
  • exitFcn: executed when mouse exits object.
  • traverseFcn: executed when mouse enters object and when it moves in object.

We don't need the last one. When entering the mousepointer is set to be a cross with arrows on the edges, when leaving the point is transformed to the (regular) arrow. (for more info see iptSetPointerBehavior and hggroup)

The total code is then:

function stackover()

clc,close all

fig=figure('WindowButtonUpFcn',@drop,'WindowButtonMotionFcn',@move);

% initialize some parameters
xstart=4;
ystart=1/3*4^2;

dragging = 0;

ax=axes('Units','Normalized',...
    'Position',[0.2,0.2,0.6,0.6],...
    'XGrid','on',...
    'YGrid','on');

slider=uicontrol(...
    'Parent',fig,...
    'Style','slider',...
    'Units','normalized',...
    'Position',[0.2,0.9,0.6,0.05],...
    'Tag','slider1',...
    'Min',0,...
    'Max',10,...
    'Value',xstart);

addlistener(slider,'Value',...
    'PostSet',@(hObject, event) slider_callback(slider, event));


pointerBehavior.enterFcn = @(figHandle, currentPoint) set(figHandle, 'Pointer', 'fleur');
pointerBehavior.exitFcn  = @(figHandle, currentPoint) set(figHandle, 'Pointer', 'arrow');
pointerBehavior.traverseFcn = [];



x=linspace(0,10);
y=1/3*x.^2;
plot(x,y,'b-')
grid on
xlabel('x-axis')
ylabel('y-axis')
hold on
plot([-5,10],[0,0],'k-');
plot([0,0],[-5,35],'k-');
p=plot(xstart,ystart,'ro','ButtonDownFcn',@click);

iptSetPointerBehavior(p, pointerBehavior);     % set behaviour of pointer when over p
iptPointerManager(gcf);                        % let figure know what you're doing

a1=quiver(0,0,xstart,ystart,0,...
    'LineWidth',1,...
    'Color','red');
a2=quiver(0,0,0,xstart,0,...
    'LineWidth',1,...
    'Color','red');
a3=quiver(0,0,xstart,0,0,...
    'LineWidth',1,...
    'Color','red');
d=plot([0,xstart,xstart],[ystart,ystart,0],'k--');
hold off

    function slider_callback(hObject,eventdata)
        x=hObject.Value;
        p.XData=x;
        p.YData=1/3*x.^2;
        a1.UData=x;
        a1.VData=1/3*x.^2;
        a2.VData=1/3*x.^2;
        a3.UData=x;
        d.XData=[0,x,x];
        d.YData=[1/3*x.^2,1/3*x.^2,0];
        drawnow
    end


    function click(hObject,eventdata)
        dragging = 1;
    end
    function drop(hObject,eventdata)
        dragging = 0;
    end
    function move(hObject,eventdata)
        if dragging
            mouse = ax.CurrentPoint;
            x = mouse(1,1);

            if x >= slider.Max
                x = slider.Max;
            elseif x <= slider.Min
                x = slider.Min;
            end

            p.XData=x;
            p.YData=1/3*x.^2;
            a1.UData=x;
            a1.VData=1/3*x.^2;
            a2.VData=1/3*x.^2;
            a3.UData=x;
            d.XData=[0,x,x];
            d.YData=[1/3*x.^2,1/3*x.^2,0];
            slider.Value = x;
            drawnow
        end
    end


end

Upvotes: 2

Related Questions