jez
jez

Reputation: 15349

datacursormode: spurious MouseMotion event processing when UpdateFcn takes time to return

I'm using DATACURSORMODE in Matlab 8.1.0.604 (R2013a) on Mac OS 10.11.6 (El Capitan).

In the simple example below, the data cursor only moves when a new location is clicked, not when the mouse is merely moved. This is exactly the behaviour I want. However, in my code I get a different behaviour: after the first click, the cursor moves whenever the mouse just moves, until I subsequently double-click. I did not knowingly ask for this latter behaviour and do not want it.

The key difference seems to be that my UpdateFcn callback code takes time to complete (and let's assume it always will: my callback is intended to perform fairly complex actions, and is already as vectorized and optimized as it can get). This is emulated by the pause statement in the example below, which should hopefully replicate the problem if uncommented (might have to fiddle with the duration, depending on platform/setup).

In datacursormode.m the "UNDOCUMENTED FUNCTIONALITY" comments mention that both MouseMotion and ButtonDown events are thrown by a data cursor mode object. This is clearly not the full story, because by default there's no response to mouse motion alone, but something happens, presumably due to the delay, to make it do so.

So my question is: is this cursor-moving-on-mouse-motion-until-you-doubleclick a known feature/mode, or just unintended "undefined behaviour" as a side effect of the delay? In either case, how can I (programmatically) prevent it from happening, assuming I can't actually speed up the callback code?

function demo

fig = 1;
figure(fig), close, figure(fig)  % ensure virgin figure
image
h = datacursormode(fig);
datacursormode(fig, 'on')
set(h, 'UpdateFcn', @callback)

function txt = callback(cbo, event)
% pause(0.1) % uncomment this line (and/or lengthen the delay as necessary) to replicate the problem 
txt = evalc('cbo,event,disp(get(event))');

Upvotes: 0

Views: 293

Answers (2)

jez
jez

Reputation: 15349

Here's an edited version of Will's solution that works nicely. The StartDelay value is critical: <=6ms fails to solve the mouse motion problem. 7ms solves it most of the time, but occasionally lapses. 10ms seems to work fairly reliably (except the very first time in a given new Matlab session, when things are slow to wake up). Typical Matlab implementation flakiness...

function demo

fig = 100;
figure(fig), close, figure(fig)  % ensure virgin figure
img = image;
EnableDataCursor(img)



function EnableDataCursor(img)

ax = get(img, 'parent');
fig = get(ax, 'parent');
dcm = datacursormode(fig);
datacursormode(fig, 'on')

set(dcm, 'UpdateFcn', @CursorModeCallback)
setappdata(img, 'CursorModeObject', dcm);
setappdata(img, 'Timer', timer('StartDelay', 0.010, 'TimerFcn', @TimerCallback));
setappdata(img, 'LastText', 'Initial text');



function txt = CursorModeCallback(cbo, event)

img = get(event, 'Target');
t = getappdata(img, 'Timer');
if strcmp(get(t, 'Running'), 'off') % If we have already asked for an updated tooltip and haven't got one yet then don't ask for another one
    txt = 'updating...';
    set(t, 'UserData', {cbo, event}); % Store the data needed by the slow callback to generate the tooltip
    start(t); % Ask the timer to generate new text
else
    txt = getappdata(img, 'LastText'); % Display the most recently generated text in the tooltip
end



function TimerCallback(t, varargin)

ud = get(t, 'UserData');
[cbo, event] = deal(ud{:});
img = get(event, 'Target');

pause(1) % numbercrunch, numbercrunch, numbercrunch

txt = num2str(get(event, 'Position'));
if ~isequal(txt, getappdata(img, 'LastText'))
    setappdata(img, 'LastText', txt); % Store the latest text
    updateDataCursors(getappdata(img, 'CursorModeObject')); % Update the cursor so the text is displayed
end

Upvotes: 0

Will
Will

Reputation: 1880

I couldn't actually reproduce your problem on R2012a or R2016a on Windows, but it sounds like on your system MATLAB is failing to catch the ButtonUp event when you click. The reason MouseMotion events are processed is because you should be able to drag the cursor when you have the mouse button held down.

Assuming that it is indeed the slow response of the UpdateFcn that causes this, you may be able to resolve this by triggering the slow part in an asynchronous operation which then triggers another update when it is done. The most universal form of this, I think, is to use a timer with a very short StartDelay. In the example below I've used appdata to share handles between the datacursormode object and the timer, but you could approach this in many different ways in your specific implementation.

function demo

fig = 1;
figure(fig), close, figure(fig)  % ensure virgin figure
image
h = datacursormode(fig);
datacursormode(fig, 'on')
set(h, 'UpdateFcn', @cursormodecallback)
setappdata(fig, 'cursormode',h);
setappdata(fig, 'timer',timer('StartDelay',0.001, 'TimerFcn',@timercallback));
setappdata(fig, 'lasttxt','Initial text');

function txt = cursormodecallback(cbo, event)

txt = getappdata(1,'lasttxt'); % Display the most recently generated text in the tooltip
t = getappdata(1,'timer');
if strcmp(get(t,'Running'),'off') % If we have already asked for an updated tooltip and haven't got one yet then don't ask for another one
    set(t, 'UserData',{cbo,event}); % Store the data needed by the slow callback to generate the tooltip
    start(t); % Ask the timer to generate new text
end

function timercallback(cbo, event)

cursordata = get(cbo,'UserData');
[cbo,event] = cursordata{:};
pause(1)
txt = evalc('cbo,event,disp(get(event))');
if ~isequal(txt,getappdata(1,'lasttxt'))
    setappdata(1,'lasttxt',txt); % Store the latest text
    updateDataCursors(getappdata(1,'cursormode')); % Update the cursor so the text is displayed
end

Upvotes: 1

Related Questions