Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit908f70b

Browse files
committed
Touchscreen support
- support for touch-to-drag and pinch-to-zoom in NavigationToolbar2 - pass touchscreen events from Qt4 and Qt5 backends - add option in matplotlibrc to pass touches as mouse events if desired
1 parentd5132fc commit908f70b

File tree

8 files changed

+484
-1
lines changed

8 files changed

+484
-1
lines changed

‎doc/users/navigation_toolbar.rst

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ The ``Pan/Zoom`` button
5050
mouse button. The radius scale can be zoomed in and out using the
5151
right mouse button.
5252

53+
If your system has a touchscreen, with certain backends the figure can
54+
be panned by touching and dragging, or zoomed by pinching with two fingers.
55+
The Pan/Zoom button does not need to be activated for touchscreen interaction.
56+
As above, the 'x' and 'y' keys will constrain movement to the x or y axes,
57+
and 'CONTROL' preserves aspect ratio.
58+
5359
..image::../../lib/matplotlib/mpl-data/images/zoom_to_rect_large.png
5460

5561
The ``Zoom-to-rectangle`` button
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
Touchscreen Support
2+
-------------------
3+
4+
Support for touch-to-drag and pinch-to-zoom have been added for the
5+
Qt4 and Qt5 backends. For other/custom backends, the interface in
6+
`NavigationToolbar2` is general, so that the backends only need to
7+
pass a list of the touch points, and `NavigationToolbar2` will do the rest.
8+
Support is added separately for touch rotating and zooming in `Axes3D`.

‎lib/matplotlib/backend_bases.py

Lines changed: 339 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,95 @@ def __str__(self):
15781578
self.dblclick,self.inaxes)
15791579

15801580

1581+
classTouch(LocationEvent):
1582+
"""
1583+
A single touch.
1584+
1585+
In addition to the :class:`Event` and :class:`LocationEvent`
1586+
attributes, the following attributes are defined:
1587+
1588+
Attributes
1589+
----------
1590+
ID : int
1591+
A unique ID (generated by the backend) for this touch.
1592+
1593+
"""
1594+
x=None# x position - pixels from left of canvas
1595+
y=None# y position - pixels from right of canvas
1596+
inaxes=None# the Axes instance if mouse us over axes
1597+
xdata=None# x coord of mouse in data coords
1598+
ydata=None# y coord of mouse in data coords
1599+
ID=None# unique ID of touch event
1600+
1601+
def__init__(self,name,canvas,x,y,ID,guiEvent=None):
1602+
"""
1603+
x, y in figure coords, 0,0 = bottom, left
1604+
"""
1605+
LocationEvent.__init__(self,name,canvas,x,y,guiEvent=guiEvent)
1606+
self.ID=ID
1607+
1608+
def__str__(self):
1609+
return ("MPL Touch: xy=(%d,%d) xydata=(%s,%s) inaxes=%s ID=%d")% (self.x,
1610+
self.y,
1611+
self.xdata,
1612+
self.ydata,
1613+
self.inaxes,
1614+
self.ID)
1615+
1616+
1617+
classTouchEvent(Event):
1618+
"""
1619+
A touch event, with possibly several touches.
1620+
1621+
For
1622+
('touch_begin_event',
1623+
'touch_update_event',
1624+
'touch_end_event')
1625+
1626+
In addition to the :class:`Event` and :class:`LocationEvent`
1627+
attributes, the following attributes are defined:
1628+
1629+
Attributes
1630+
----------
1631+
1632+
touches : None, or list
1633+
A list of the touches (possibly several), which will be of class Touch.
1634+
They are passed to the class as a list of triples of the form (ID,x,y),
1635+
where ID is an integer unique to that touch (usually provided by the
1636+
backend) and x and y are the touch coordinates
1637+
1638+
key : None, or str
1639+
The key depressed when this event triggered.
1640+
1641+
Example
1642+
-------
1643+
Usage::
1644+
1645+
def on_touch(event):
1646+
print('touch at', event.touches[0].x, event.touches[0].y)
1647+
1648+
cid = fig.canvas.mpl_connect('touch_update_event', on_touch)
1649+
1650+
"""
1651+
touches=None
1652+
key=None
1653+
1654+
def__init__(self,name,canvas,touches,key,guiEvent=None):
1655+
"""
1656+
x, y in figure coords, 0,0 = bottom, left
1657+
"""
1658+
Event.__init__(self,name,canvas,guiEvent=guiEvent)
1659+
self.touches= [Touch(name+'_%d'%n,
1660+
canvas,
1661+
x,y,ID,
1662+
guiEvent=guiEvent)forn,(ID,x,y)inenumerate(touches)]
1663+
self.key=key
1664+
1665+
def__str__(self):
1666+
return"MPL TouchEvent: key="+str(self.key)+", touches="+'\n'.join(str(t)fortinself.touches)
1667+
1668+
1669+
15811670
classPickEvent(Event):
15821671
"""
15831672
a pick event, fired when the user picks a location on the canvas
@@ -1683,6 +1772,9 @@ class FigureCanvasBase(object):
16831772
'button_release_event',
16841773
'scroll_event',
16851774
'motion_notify_event',
1775+
'touch_begin_event',
1776+
'touch_update_event',
1777+
'touch_end_event',
16861778
'pick_event',
16871779
'idle_event',
16881780
'figure_enter_event',
@@ -1937,6 +2029,73 @@ def motion_notify_event(self, x, y, guiEvent=None):
19372029
guiEvent=guiEvent)
19382030
self.callbacks.process(s,event)
19392031

2032+
deftouch_begin_event(self,touches,guiEvent=None):
2033+
"""
2034+
Backend derived classes should call this function on first touch.
2035+
2036+
This method will call all functions connected to the
2037+
'touch_begin_event' with a :class:`TouchEvent` instance.
2038+
2039+
Parameters
2040+
----------
2041+
touches : list
2042+
a list of triples of the form (ID,x,y), where ID is a unique
2043+
integer ID for each touch, and x and y are the touch's
2044+
coordinates.
2045+
2046+
guiEvent
2047+
the native UI event that generated the mpl event
2048+
2049+
"""
2050+
s='touch_begin_event'
2051+
event=TouchEvent(s,self,touches,key=self._key,guiEvent=guiEvent)
2052+
self.callbacks.process(s,event)
2053+
2054+
deftouch_update_event(self,touches,guiEvent=None):
2055+
"""
2056+
Backend derived classes should call this function on all
2057+
touch updates.
2058+
2059+
This method will call all functions connected to the
2060+
'touch_update_event' with a :class:`TouchEvent` instance.
2061+
2062+
Parameters
2063+
----------
2064+
touches : list
2065+
a list of triples of the form (ID,x,y), where ID is a unique
2066+
integer ID for each touch, and x and y are the touch's
2067+
coordinates.
2068+
2069+
guiEvent
2070+
the native UI event that generated the mpl event
2071+
2072+
"""
2073+
s='touch_update_event'
2074+
event=TouchEvent(s,self,touches,key=self._key,guiEvent=guiEvent)
2075+
self.callbacks.process(s,event)
2076+
2077+
deftouch_end_event(self,touches,guiEvent=None):
2078+
"""
2079+
Backend derived classes should call this function on touch end.
2080+
2081+
This method will be call all functions connected to the
2082+
'touch_end_event' with a :class:`TouchEvent` instance.
2083+
2084+
Parameters
2085+
----------
2086+
touches : list
2087+
a list of triples of the form (ID,x,y), where ID is a unique
2088+
integer ID for each touch, and x and y are the touch's
2089+
coordinates.
2090+
2091+
guiEvent
2092+
the native UI event that generated the mpl event
2093+
2094+
"""
2095+
s='touch_end_event'
2096+
event=TouchEvent(s,self,touches,key=self._key,guiEvent=guiEvent)
2097+
self.callbacks.process(s,event)
2098+
19402099
defleave_notify_event(self,guiEvent=None):
19412100
"""
19422101
Backend derived classes should call this function when leaving
@@ -2788,6 +2947,11 @@ def __init__(self, canvas):
27882947
self._idDrag=self.canvas.mpl_connect(
27892948
'motion_notify_event',self.mouse_move)
27902949

2950+
self._idTouchBegin=self.canvas.mpl_connect(
2951+
'touch_begin_event',self.handle_touch)
2952+
self._idTouchUpdate=None
2953+
self._idTouchEnd=None
2954+
27912955
self._ids_zoom= []
27922956
self._zoom_mode=None
27932957

@@ -2871,6 +3035,181 @@ def _set_cursor(self, event):
28713035

28723036
self._lastCursor=cursors.MOVE
28733037

3038+
defhandle_touch(self,event,prev=None):
3039+
iflen(event.touches)==1:
3040+
ifself._idTouchUpdateisnotNone:
3041+
self.touch_end_disconnect(event)
3042+
self.touch_pan_begin(event)
3043+
3044+
eliflen(event.touches)==2:
3045+
ifself._idTouchUpdateisnotNone:
3046+
self.touch_end_disconnect(event)
3047+
self.pinch_zoom_begin(event)
3048+
3049+
else:
3050+
ifprev=='pan':
3051+
self.touch_pan_end(event)
3052+
elifprev=='zoom':
3053+
self.pinch_zoom_end(event)
3054+
ifself._idTouchUpdateisNone:
3055+
self._idTouchUpdate=self.canvas.mpl_connect(
3056+
'touch_update_event',self.handle_touch)
3057+
self._idTouchEnd=self.canvas.mpl_connect(
3058+
'touch_end_event',self.touch_end_disconnect)
3059+
3060+
deftouch_end_disconnect(self,event):
3061+
self._idTouchUpdate=self.canvas.mpl_disconnect(self._idTouchUpdate)
3062+
self._idTouchEnd=self.canvas.mpl_disconnect(self._idTouchEnd)
3063+
3064+
deftouch_pan_begin(self,event):
3065+
self._idTouchUpdate=self.canvas.mpl_connect(
3066+
'touch_update_event',self.touch_pan)
3067+
self._idTouchEnd=self.canvas.mpl_connect(
3068+
'touch_end_event',self.touch_pan_end)
3069+
3070+
touch=event.touches[0]
3071+
x,y=touch.x,touch.y
3072+
3073+
# push the current view to define home if stack is empty
3074+
ifself._views.empty():
3075+
self.push_current()
3076+
3077+
self._xypress= []
3078+
fori,ainenumerate(self.canvas.figure.get_axes()):
3079+
if (xisnotNoneandyisnotNoneanda.in_axes(touch)and
3080+
a.get_navigate()anda.can_pan()):
3081+
a.start_pan(x,y,1)
3082+
self._xypress.append((a,i))
3083+
3084+
deftouch_pan(self,event):
3085+
iflen(event.touches)!=1:# number of touches changed
3086+
self.touch_pan_end(event,push_view=False)
3087+
self.handle_touch(event,prev='pan')
3088+
return
3089+
3090+
touch=event.touches[0]
3091+
3092+
fora,_inself._xypress:
3093+
a.drag_pan(1,event.key,touch.x,touch.y)
3094+
self.dynamic_update()
3095+
3096+
deftouch_pan_end(self,event,push_view=True):
3097+
self.touch_end_disconnect(event)
3098+
3099+
fora,_inself._xypress:
3100+
a.end_pan()
3101+
3102+
self._xypress= []
3103+
self._button_pressed=None
3104+
ifpush_view:# don't push when going from pan to pinch-to-zoom
3105+
self.push_current()
3106+
self.draw()
3107+
3108+
defpinch_zoom_begin(self,event):
3109+
3110+
# push the current view to define home if stack is empty
3111+
# this almost never happens because you have a single touch
3112+
# first. but good to be safe
3113+
ifself._views.empty():
3114+
self.push_current()
3115+
3116+
self._xypress= []
3117+
forainself.canvas.figure.get_axes():
3118+
if (all(a.in_axes(t)fortinevent.touches)and
3119+
a.get_navigate()anda.can_zoom()):
3120+
3121+
trans=a.transData
3122+
3123+
view=a._get_view()
3124+
transview=trans.transform(list(zip(view[:2],view[2:])))
3125+
3126+
self._xypress.append((a,event.touches,trans.inverted(),transview ))
3127+
3128+
self._idTouchUpdate=self.canvas.mpl_connect(
3129+
'touch_update_event',self.pinch_zoom)
3130+
self._idTouchEnd=self.canvas.mpl_connect(
3131+
'touch_end_event',self.pinch_zoom_end)
3132+
3133+
defpinch_zoom(self,event):
3134+
iflen(event.touches)!=2:# number of touches changed
3135+
self.pinch_zoom_end(event,push_view=False)
3136+
self.handle_touch(event,prev='zoom')
3137+
return
3138+
3139+
ifnotself._xypress:
3140+
return
3141+
3142+
# check that these are the same two touches!
3143+
e_IDs= {t.IDfortinevent.touches}
3144+
orig_IDs= {t.IDfortinself._xypress[0][1]}
3145+
ife_IDs!=orig_IDs:
3146+
self.pinch_zoom_end(event)
3147+
self.pinch_zoom_begin(event)
3148+
3149+
last_a= []
3150+
3151+
forcur_xypressinself._xypress:
3152+
a,orig_touches,orig_trans,orig_lims=cur_xypress
3153+
3154+
center= (sum(t.xfortinevent.touches)/2,
3155+
sum(t.yfortinevent.touches)/2)
3156+
3157+
orig_center= (sum(t.xfortinorig_touches)/2,
3158+
sum(t.yfortinorig_touches)/2)
3159+
3160+
ot1,ot2=orig_touches
3161+
t1,t2=event.touches
3162+
ifevent.key=='control'ora.get_aspect()notin ['auto','normal']:
3163+
zoom_scales= (np.sqrt(((t1.x-t2.x)**2+(t1.y-t2.y)**2)/((ot1.x-ot2.x)**2+ (ot1.y-ot2.y)**2)),)*2
3164+
else:
3165+
zoom_scales= (abs((t1.x-t2.x)/(ot1.x-ot2.x)),
3166+
abs((t1.y-t2.y)/(ot1.y-ot2.y)))
3167+
3168+
# if the zoom is really extreme, make it not crazy
3169+
zoom_scales= [zifz>0.01else0.01forzinzoom_scales]
3170+
3171+
ifevent.key=='y':
3172+
xlims=orig_lims[:,0]
3173+
else:
3174+
xlims= [orig_center[0]+ (x-center[0])/zoom_scales[0]forxinorig_lims[:,0]]
3175+
3176+
ifevent.key=='x':
3177+
ylims=orig_lims[:,1]
3178+
else:
3179+
ylims= [orig_center[1]+ (y-center[1])/zoom_scales[1]foryinorig_lims[:,1]]
3180+
3181+
lims=orig_trans.transform(list(zip(xlims,ylims)))
3182+
xlims=lims[:,0]
3183+
ylims=lims[:,1]
3184+
3185+
# detect twinx,y axes and avoid double zooming
3186+
twinx,twiny=False,False
3187+
iflast_a:
3188+
forlainlast_a:
3189+
ifa.get_shared_x_axes().joined(a,la):
3190+
twinx=True
3191+
ifa.get_shared_y_axes().joined(a,la):
3192+
twiny=True
3193+
last_a.append(a)
3194+
3195+
xmin,xmax,ymin,ymax=a._get_view()
3196+
3197+
iftwinxandnottwiny:# and maybe a key
3198+
a._set_view((xmin,xmax,ylims[0],ylims[1]))
3199+
eliftwinyandnottwinx:
3200+
a._set_view((xlims[0],xlims[1],ymin,ymax))
3201+
elifnottwinxandnottwiny:
3202+
a._set_view(list(xlims)+list(ylims))
3203+
3204+
self.dynamic_update()
3205+
3206+
defpinch_zoom_end(self,event,push_view=True):
3207+
self.touch_end_disconnect(event)
3208+
3209+
ifpush_view:# don't push when going from zoom back to pan
3210+
self.push_current()
3211+
self.draw()
3212+
28743213
defmouse_move(self,event):
28753214
self._set_cursor(event)
28763215

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp