Phoenix
Phoenix copied to clipboard
Mouse scroll events are not correctly passed to matplotlib in 4.1.0
Win10 64bit: wxPython 4.1.0 pypi: 3.7.6 Anaconda: matplotlib 3.2.2
I am trying to use mouse scroll events in a matplotlib canvas but the mouse scroll event data is wrong. The attached script will print the mouse scroll events. I have observerd:
- that the "button" property is always "down" when it should switch between "up" and "down" depending on the user input.
- xy coordinates are not correct.
- xydata is None
Additional information: This seems to be related to https://github.com/matplotlib/matplotlib/issues/17513 The script and events work correctly when using wxPython 4.0.7 In 4.1.0 the events are sometimes correct when holding the right mouse-button down.
Example output from 4.1.0 scroll_event: xy=(32766, 484) xydata=(None, None) button=down dblclick=False inaxes=None scroll_event: xy=(32766, 484) xydata=(None, None) button=down dblclick=False inaxes=None scroll_event: xy=(32766, 484) xydata=(None, None) button=down dblclick=False inaxes=None
Example output from 4.0.7 scroll_event: xy=(321, 241) xydata=(2.3669753832761042, 2.161450996881461) button=down dblclick=False inaxes=AxesSubplot(0.125,0.125;0.775x0.755) scroll_event: xy=(321, 241) xydata=(2.3669753832761042, 2.161450996881461) button=up dblclick=False inaxes=AxesSubplot(0.125,0.125;0.775x0.755) scroll_event: xy=(321, 241) xydata=(2.3669753832761042, 2.161450996881461) button=up dblclick=False inaxes=AxesSubplot(0.125,0.125;0.775x0.755) scroll_event: xy=(321, 241) xydata=(2.3669753832761042, 2.161450996881461) button=up dblclick=False inaxes=AxesSubplot(0.125,0.125;0.775x0.755)
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigureCanvas
from matplotlib.figure import Figure
import wx
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = FigureCanvas(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.canvas.mpl_connect("scroll_event", self.OnMouseScroll)
def draw(self):
x = [1,2,3,4]
y = [1,2,3,4]
self.axes.plot(x, y)
def OnMouseScroll(self, e):
print(e)
if __name__ == "__main__":
app = wx.PySimpleApp()
fr = wx.Frame(None, title='test')
panel = CanvasPanel(fr)
panel.draw()
fr.Show()
app.MainLoop()
Seems to possibly be platform specific also. I can't reproduce with wxPy 4.1.0 and matplotlib 3.2.2 on Linux (Ubuntu 18.04).
I am having the same exact issue: Scroll wheel is always reported as "down" no matter the wheel direction.
WxPython version: 4.1.1 Matplotlib version: 3.3.4
Using wxPython version 4.0.7, everything is fine again. However, version 4.1.1 is needed
I have the same issue with the wheel direction always reported as down, and the number of lines as 382. Seems to be an issue with the interaction at the matplotlib canvas Windows 10 Python 3.8 WxPython 4.0.7post2 or 4.1.1 Matplotlib 3.4.3 I tried binding the wx.EVT_MOUSEWHEEL at the Panel and recursively Connect it for all children. The event.WheelRotation works correctly while the mouse is over the toolbar and rest of the panel, but fails when moving the scroll wheel over the matplotlib FigureCanvasWxAgg
I found the same issue in:
Win 10, Python 3.9.10 (miniconda), matplotlib 3.5.1, wxPython 4.1.1
One add-on, the event.step value is also incorrect. Typically always negative regardless of direction, and around a value of -12000 or so, as opposed to 1 or 2 or 3.
I used StephanSchwebel's script, with an additional print out of e.step for my purpose.
Did not find a workaround yet, except to go back to wxPython 4.0.4 and Python 3.7 (on my other environment).
I have just tried the script in a debugger on Windows.
Incidentally, I picked two of my installations that are almost the same, just 32 vs. 64 bit.
It seems to be a 64 bit problem.
matplotlib backend_wx._FigureCanvasWxBase._onMouseWheel
uses GetWheelDelta
etc. to decide for 'up'
or 'down'
. The values are completely off.
Correct behaviour:
3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:04:45) [MSC v.1900 32 bit (Intel)]
(4, 1, 0, '')
backend_wx._FigureCanvasWxBase._onMouseWheel:
evt.GetWheelDelta() 120
evt.GetWheelRotation() 120
evt.GetLinesPerAction() 3
Wrong behaviour:
3.6.4 (v3.6.4:d48eceb, Dec 19 2017, 06:54:40) [MSC v.1900 64 bit (AMD64)]
(4, 1, 0, '')
backend_wx._FigureCanvasWxBase._onMouseWheel:
evt.GetWheelDelta() 10045
evt.GetWheelRotation() 417
evt.GetLinesPerAction() -31992
Honestly, I'm a bit surprised that none of the reporters has a debugger available. This could have speeded up things a lot...
I'm surprised that a pure wxPython script does not have issues with the EVT_MOUSEWHEEL
.
OK, I have further tracked down the issue.
matplotlib is storing references to wx events.
If I modify backend_wx._FigureCanvasWxBase._onMotion:
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=evt)
to
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=None)
then the problem goes away.
A workaround would be to derive your own class and override the _onMotion
method accordingly
@swt2c : Please see attached script with handlers for EVT_MOTION and EVT_MOUSEWHEEL.
It's showing the same problem for me due to the line self.event = event
in onMotion
.
Is it 'legal' for a wxPython application to store a reference to an event instance? I would think that the reference should be removed at the end of the matplotlib event handler.
I don't know of any technical reason why you shouldn't be able to retain a reference to a mouse event. The behavior you are seeing is really strange. GetWheelDelta()
is populated using a constant so it should always be 120. Because you're seeing something other than 120 suggests to me that there might be some sort of memory corruption going on here.
Hi @DietmarSchwertberger and thanks for the really useful information. Apologies for any loose/incorrect terminology below, I'm still learning how to post.
I've reworked the @StephanSchwebel test code to contain a local version of the FigureCanvasWxAgg backend to test if that can give a proper 'step' value in my matplotlib event (my original problem). I found that I had to override both _OnMotion() and _onMouseWheel() methods to get proper behaviour. Both contained ongoing references to the event in their code.
Thought it might be useful to the thread.
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.backend_bases import FigureCanvasBase
from matplotlib.figure import Figure
import wx
class LocalFigureCanvasWxAgg(FigureCanvasWxAgg):
def _onMouseWheel(self, event):
"""Translate mouse wheel events into matplotlib events"""
# Determine mouse location
x = event.GetX()
y = self.figure.bbox.height - event.GetY()
# Convert delta/rotation/rate into a floating point step size
step = event.LinesPerAction * event.WheelRotation / event.WheelDelta
# Done handling event
event.Skip()
# Mac gives two events for every wheel event; skip every second one.
if wx.Platform == '__WXMAC__':
if not hasattr(self, '_skipwheelevent'):
self._skipwheelevent = True
elif self._skipwheelevent:
self._skipwheelevent = False
return # Return without processing event
else:
self._skipwheelevent = True
FigureCanvasBase.scroll_event(self, x, y, step, guiEvent=None) #event)
def _onMotion(self, event):
"""Start measuring on an axis."""
x = event.GetX()
y = self.figure.bbox.height - event.GetY()
event.Skip()
FigureCanvasBase.motion_notify_event(self, x, y, guiEvent=None) #event)
class CanvasPanel(wx.Panel):
def __init__(self, parent):
wx.Panel.__init__(self, parent)
self.figure = Figure()
self.axes = self.figure.add_subplot(111)
self.canvas = LocalFigureCanvasWxAgg(self, -1, self.figure)
self.sizer = wx.BoxSizer(wx.VERTICAL)
self.sizer.Add(self.canvas, 1, wx.LEFT | wx.TOP | wx.GROW)
self.SetSizer(self.sizer)
self.Fit()
self.canvas.mpl_connect("scroll_event", self.OnMouseScroll)
def draw(self):
x = [1,2,3,4]
y = [1,2,3,4]
self.axes.plot(x, y)
def OnMouseScroll(self, e):
print(str(e) + ' step = ' + str(e.step))
if __name__ == "__main__":
app = wx.PySimpleApp()
fr = wx.Frame(None, title='test')
panel = CanvasPanel(fr)
panel.draw()
fr.Show()
app.MainLoop()
I think this is related to https://github.com/wxWidgets/Phoenix/issues/2034
I can indeed reproduce this issue on Windows with wxPython 4.1.1. However, the problem seems to be fixed in the latest snapshots. Can anyone else confirm please?
Hi @swt2c I installed (pip) wheel wxPython-4.1.2a1.dev5259+d3bdb143 from https://wxpython.org/Phoenix/snapshot-builds/ into my python 3.9 env in miniconda. I ran my test code (see above) and it behaved properly: x,y xdata,ydata button dblclick and step values all seemed to track with mouse position and wheel roll direction properly. Thanks for pointing that out.
This issue has been mentioned on Discuss wxPython. There might be relevant details there:
https://discuss.wxpython.org/t/a-problem-with-event-objects/35766/3
What's strange is that I have no idea what might have fixed this. My best guess was that it had something to do with sip 4 and moving to sip 5 fixed it.
I have also built wxPython now and also don't see the issue.
Confirmed that the problem is fixed in wx 4.1.2a1 with matplotlib 3.5.1/WXAgg.
@swt2c, I don't think the sip
version is the only reason the problem has been fixed because there was no problem with wx 4.0.7. Actually, I don't think the problem has been completely resolved, but I think it's just hidden and a similar problem will appear someday...
As your example code from the first post on Discuss still fails, this might be a good starting point for debugging. https://discuss.wxpython.org/t/a-problem-with-event-objects/35766/2
Read Exception and backtrace look like this:
> _core_d.cp310-win_amd64.pyd!meth_wxObject_GetClassInfo(_object * sipSelf, _object * sipArgs) Zeile 47 C++
python310_d.dll!cfunction_call(_object * func, _object * args, _object * kwargs) Zeile 552 C
python310_d.dll!_PyObject_MakeTpCall(_ts * tstate, _object * callable, _object * const * args, __int64 nargs, _object * keywords) Zeile 215 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 112 C
python310_d.dll!PyObject_CallOneArg(_object * func, _object * arg) Zeile 185 C
python310_d.dll!property_descr_get(_object * self, _object * obj, _object * type) Zeile 1607 C
python310_d.dll!_PyObject_GenericGetAttrWithDict(_object * obj, _object * name, _object * dict, int suppress) Zeile 1243 C
python310_d.dll!PyObject_GenericGetAttr(_object * obj, _object * name) Zeile 1323 C
python310_d.dll!PyObject_GetAttr(_object * v, _object * name) Zeile 922 C
python310_d.dll!builtin_getattr(_object * self, _object * const * args, __int64 nargs) Zeile 1106 C
python310_d.dll!cfunction_vectorcall_FASTCALL(_object * func, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 430 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 115 C
[Inlineframe] python310_d.dll!PyObject_Vectorcall(_object *) Zeile 123 C
python310_d.dll!call_function(_ts * tstate, PyTraceInfo * trace_info, _object * * * pp_stack, __int64 oparg, _object * kwnames) Zeile 5870 C
python310_d.dll!_PyEval_EvalFrameDefault(_ts * tstate, _frame * f, int throwflag) Zeile 4214 C
[Inlineframe] python310_d.dll!_PyEval_EvalFrame(_ts *) Zeile 46 C
python310_d.dll!_PyEval_Vector(_ts * tstate, PyFrameConstructor * con, _object * locals, _object * const * args, unsigned __int64 argcount, _object * kwnames) Zeile 5072 C
python310_d.dll!_PyFunction_Vectorcall(_object * func, _object * const * stack, unsigned __int64 nargsf, _object * kwnames) Zeile 342 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 115 C
[Inlineframe] python310_d.dll!PyObject_Vectorcall(_object *) Zeile 123 C
python310_d.dll!call_function(_ts * tstate, PyTraceInfo * trace_info, _object * * * pp_stack, __int64 oparg, _object * kwnames) Zeile 5870 C
python310_d.dll!_PyEval_EvalFrameDefault(_ts * tstate, _frame * f, int throwflag) Zeile 4214 C
[Inlineframe] python310_d.dll!_PyEval_EvalFrame(_ts *) Zeile 46 C
python310_d.dll!_PyEval_Vector(_ts * tstate, PyFrameConstructor * con, _object * locals, _object * const * args, unsigned __int64 argcount, _object * kwnames) Zeile 5072 C
python310_d.dll!_PyFunction_Vectorcall(_object * func, _object * const * stack, unsigned __int64 nargsf, _object * kwnames) Zeile 342 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 114 C
python310_d.dll!method_vectorcall(_object * method, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 83 C
python310_d.dll!PyVectorcall_Call(_object * callable, _object * tuple, _object * kwargs) Zeile 255 C
python310_d.dll!_PyObject_Call(_ts * tstate, _object * callable, _object * args, _object * kwargs) Zeile 290 C
python310_d.dll!PyObject_CallObject(_object * callable, _object * args) Zeile 400 C
_core_d.cp310-win_amd64.pyd!wxPyCallback::EventThunker(wxEvent & event) Zeile 72 C++
wxbase315ud_vc140_x64.dll!wxAppConsoleBase::HandleEvent(wxEvtHandler * handler, void(wxEvtHandler::*)(wxEvent &) func, wxEvent & event) Zeile 655 C++
wxbase315ud_vc140_x64.dll!wxAppConsoleBase::CallEventHandler(wxEvtHandler * handler, wxEventFunctor & functor, wxEvent & event) Zeile 667 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::ProcessEventIfMatchesId(const wxEventTableEntryBase & entry, wxEvtHandler * handler, wxEvent & event) Zeile 1420 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::SearchDynamicEventTable(wxEvent & event) Zeile 1890 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::TryHereOnly(wxEvent & event) Zeile 1611 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::TryBeforeAndHere(wxEvent & event) Zeile 3975 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::ProcessEventLocally(wxEvent & event) Zeile 1548 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::ProcessEvent(wxEvent & event) Zeile 1521 C++
_core_d.cp310-win_amd64.pyd!sipwxFrame::ProcessEvent(wxEvent & event) Zeile 297 C++
wxbase315ud_vc140_x64.dll!wxEvtHandler::SafelyProcessEvent(wxEvent & event) Zeile 1639 C++
wxmsw315ud_core_vc140_x64.dll!wxWindowBase::HandleWindowEvent(wxEvent & event) Zeile 1556 C++
wxmsw315ud_core_vc140_x64.dll!wxWindow::HandleMouseWheel(wxMouseWheelAxis axis, unsigned __int64 wParam, __int64 lParam) Zeile 6089 C++
wxmsw315ud_core_vc140_x64.dll!wxWindow::MSWHandleMessage(__int64 * result, unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 3139 C++
wxmsw315ud_core_vc140_x64.dll!wxWindow::MSWWindowProc(unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 3883 C++
wxmsw315ud_core_vc140_x64.dll!wxNonOwnedWindow::MSWWindowProc(unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 259 C++
wxmsw315ud_core_vc140_x64.dll!wxTopLevelWindowMSW::MSWWindowProc(unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 324 C++
wxmsw315ud_core_vc140_x64.dll!wxFrame::MSWWindowProc(unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 912 C++
wxmsw315ud_core_vc140_x64.dll!wxWndProc(HWND__ * hWnd, unsigned int message, unsigned __int64 wParam, __int64 lParam) Zeile 2924 C++
[Externer Code]
wxmsw315ud_core_vc140_x64.dll!wxGUIEventLoop::ProcessMessage(tagMSG * msg) Zeile 166 C++
wxmsw315ud_core_vc140_x64.dll!wxGUIEventLoop::Dispatch() Zeile 226 C++
wxbase315ud_vc140_x64.dll!wxEventLoopManual::ProcessEvents() Zeile 234 C++
wxbase315ud_vc140_x64.dll!wxEventLoopManual::DoRun() Zeile 288 C++
wxbase315ud_vc140_x64.dll!wxEventLoopBase::Run() Zeile 87 C++
wxbase315ud_vc140_x64.dll!wxAppConsoleBase::MainLoop() Zeile 377 C++
_core_d.cp310-win_amd64.pyd!wxPyApp::MainLoop() Zeile 314 C++
_core_d.cp310-win_amd64.pyd!meth_wxPyApp_MainLoop(_object * sipSelf, _object * sipArgs) Zeile 1968 C++
python310_d.dll!cfunction_call(_object * func, _object * args, _object * kwargs) Zeile 552 C
python310_d.dll!_PyObject_MakeTpCall(_ts * tstate, _object * callable, _object * const * args, __int64 nargs, _object * keywords) Zeile 215 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 112 C
[Inlineframe] python310_d.dll!PyObject_Vectorcall(_object *) Zeile 123 C
python310_d.dll!call_function(_ts * tstate, PyTraceInfo * trace_info, _object * * * pp_stack, __int64 oparg, _object * kwnames) Zeile 5870 C
python310_d.dll!_PyEval_EvalFrameDefault(_ts * tstate, _frame * f, int throwflag) Zeile 4183 C
[Inlineframe] python310_d.dll!_PyEval_EvalFrame(_ts *) Zeile 46 C
python310_d.dll!_PyEval_Vector(_ts * tstate, PyFrameConstructor * con, _object * locals, _object * const * args, unsigned __int64 argcount, _object * kwnames) Zeile 5072 C
python310_d.dll!_PyFunction_Vectorcall(_object * func, _object * const * stack, unsigned __int64 nargsf, _object * kwnames) Zeile 342 C
python310_d.dll!_PyObject_VectorcallTstate(_ts * tstate, _object * callable, _object * const * args, unsigned __int64 nargsf, _object * kwnames) Zeile 115 C
[Inlineframe] python310_d.dll!PyObject_Vectorcall(_object *) Zeile 123 C
python310_d.dll!call_function(_ts * tstate, PyTraceInfo * trace_info, _object * * * pp_stack, __int64 oparg, _object * kwnames) Zeile 5870 C
python310_d.dll!_PyEval_EvalFrameDefault(_ts * tstate, _frame * f, int throwflag) Zeile 4199 C
[Inlineframe] python310_d.dll!_PyEval_EvalFrame(_ts *) Zeile 46 C
python310_d.dll!_PyEval_Vector(_ts * tstate, PyFrameConstructor * con, _object * locals, _object * const * args, unsigned __int64 argcount, _object * kwnames) Zeile 5072 C
python310_d.dll!PyEval_EvalCode(_object * co, _object * globals, _object * locals) Zeile 1135 C
python310_d.dll!run_eval_code_obj(_ts * tstate, PyCodeObject * co, _object * globals, _object * locals) Zeile 1291 C
python310_d.dll!run_mod(_mod * mod, _object * filename, _object * globals, _object * locals, PyCompilerFlags * flags, _arena * arena) Zeile 1312 C
python310_d.dll!pyrun_file(_iobuf * fp, _object * filename, int start, _object * globals, _object * locals, int closeit, PyCompilerFlags * flags) Zeile 1208 C
python310_d.dll!_PyRun_SimpleFileObject(_iobuf * fp, _object * filename, int closeit, PyCompilerFlags * flags) Zeile 456 C
python310_d.dll!_PyRun_AnyFileObject(_iobuf * fp, _object * filename, int closeit, PyCompilerFlags * flags) Zeile 90 C
python310_d.dll!pymain_run_file_obj(_object * program_name, _object * filename, int skip_source_first_line) Zeile 353 C
python310_d.dll!pymain_run_file(const PyConfig * config) Zeile 372 C
python310_d.dll!pymain_run_python(int * exitcode) Zeile 587 C
python310_d.dll!Py_RunMain() Zeile 668 C
python310_d.dll!pymain_main(_PyArgv * args) Zeile 697 C
python310_d.dll!Py_Main(int argc, wchar_t * * argv) Zeile 709 C
python_d.exe!wmain(int argc, wchar_t * * argv) Zeile 10 C
This might be related to a wxWidgets/wxPython <-> sip interation. Anyway, not keeping a reference should fix the issue. See https://github.com/matplotlib/matplotlib/issues/22211#issuecomment-1065424567