Reputation: 15349
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
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
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