2012-07-18 19 views
22

के साथ मैटलप्लिब्ब प्लॉट ज़ूमिंग क्या कर्सर एक matplotlib प्लॉट पर होवर करते समय ज़ूम इन/आउट करने के लिए स्क्रॉल व्हील को बांधना संभव है?स्क्रॉल व्हील

+0

आप एक फोन वापस समारोह लिखते हैं कि http://matplotlib.sourceforge.net/api/backend_bases_api.html?highlight=mpl_connect#matplotlib.backend_bases.FigureCanvasBase.mpl_connect – tacaswell

+0

इस के किसी भी उदाहरण ऐसा करने के लिए कर सकते हैं? – dimka

उत्तर

17

यह काम करना चाहिए। जब आप स्क्रॉल करते हैं तो यह पॉइंटर के स्थान पर ग्राफ़ को फिर से केंद्रित करता है।

import matplotlib.pyplot as plt 


def zoom_factory(ax,base_scale = 2.): 
    def zoom_fun(event): 
     # get the current x and y limits 
     cur_xlim = ax.get_xlim() 
     cur_ylim = ax.get_ylim() 
     cur_xrange = (cur_xlim[1] - cur_xlim[0])*.5 
     cur_yrange = (cur_ylim[1] - cur_ylim[0])*.5 
     xdata = event.xdata # get event x location 
     ydata = event.ydata # get event y location 
     if event.button == 'up': 
      # deal with zoom in 
      scale_factor = 1/base_scale 
     elif event.button == 'down': 
      # deal with zoom out 
      scale_factor = base_scale 
     else: 
      # deal with something that should never happen 
      scale_factor = 1 
      print event.button 
     # set new limits 
     ax.set_xlim([xdata - cur_xrange*scale_factor, 
        xdata + cur_xrange*scale_factor]) 
     ax.set_ylim([ydata - cur_yrange*scale_factor, 
        ydata + cur_yrange*scale_factor]) 
     plt.draw() # force re-draw 

    fig = ax.get_figure() # get the figure of interest 
    # attach the call back 
    fig.canvas.mpl_connect('scroll_event',zoom_fun) 

    #return the function 
    return zoom_fun 

मान लें कि आप एक धुरी वस्तु ax

ax.plot(range(10)) 
scale = 1.5 
f = zoom_factory(ax,base_scale = scale) 

वैकल्पिक तर्क base_scale आप क्या कभी आप चाहते होने के लिए पैमाने कारक स्थापित करने के लिए अनुमति देता है।

सुनिश्चित करें कि आप f की प्रतिलिपि बना लें। कॉल बैक कमजोर-रेफरी का उपयोग करता है, इसलिए यदि आप f की प्रति नहीं रखते हैं तो यह कचरा एकत्र हो सकता है।

इस जवाब लिखने के बाद मैं इस वास्तव में काफी उपयोगी फैसला किया और में रख एक gist

+0

मैंने स्वतंत्र रूप से यह भी किया! मेरी इच्छा है कि मैंने पहले एसओ की जांच की हो। मैं भी योगदान देना चाहता हूं। – RodericDay

+1

@RodericDay आप इसे पकड़ सकते हैं और इसे बेहतर बना सकते हैं – tacaswell

+0

मैं उस चरण में नहीं हूं जहां मैं अन्य लोगों के उपयोग के लिए वास्तविक कोड सबमिट करता हूं, लेकिन अगर उपयोगकर्ता संबंधित सापेक्ष निर्देशांक में रुचि रखते हैं तो मैं नीचे एक फिक्स की सिफारिश करूंगा – RodericDay

4
def zoom(self, event, factor): 
    curr_xlim = self.ax.get_xlim() 
    curr_ylim = self.ax.get_ylim() 

    new_width = (curr_xlim[1]-curr_ylim[0])*factor 
    new_height= (curr_xlim[1]-curr_ylim[0])*factor 

    relx = (curr_xlim[1]-event.xdata)/(curr_xlim[1]-curr_xlim[0]) 
    rely = (curr_ylim[1]-event.ydata)/(curr_ylim[1]-curr_ylim[0]) 

    self.ax.set_xlim([event.xdata-new_width*(1-relx), 
       event.xdata+new_width*(relx)]) 
    self.ax.set_ylim([event.ydata-new_width*(1-rely), 
         event.ydata+new_width*(rely)]) 
    self.draw() 

इस थोड़ा बदल कोड के प्रयोजन के नए ज़ूम केंद्र के लिए कर्सर रिश्तेदार की स्थिति पर नज़र रखने के लिए है । इस तरह, यदि आप केंद्र के अलावा अन्य बिंदुओं पर चित्र को ज़ूम इन और आउट करते हैं तो आप एक ही बिंदु पर रहते हैं।

10

धन्यवाद दोस्तों, उदाहरण बहुत उपयोगी थे। मुझे स्कैटर प्लॉट के साथ काम करने में कुछ बदलाव करना पड़ा और मैंने बाएं बटन ड्रैग के साथ पैनिंग जोड़ा। उम्मीद है कि किसी को यह उपयोगी लगेगा।

from matplotlib.pyplot import figure, show 
import numpy 

class ZoomPan: 
    def __init__(self): 
     self.press = None 
     self.cur_xlim = None 
     self.cur_ylim = None 
     self.x0 = None 
     self.y0 = None 
     self.x1 = None 
     self.y1 = None 
     self.xpress = None 
     self.ypress = None 


    def zoom_factory(self, ax, base_scale = 2.): 
     def zoom(event): 
      cur_xlim = ax.get_xlim() 
      cur_ylim = ax.get_ylim() 

      xdata = event.xdata # get event x location 
      ydata = event.ydata # get event y location 

      if event.button == 'down': 
       # deal with zoom in 
       scale_factor = 1/base_scale 
      elif event.button == 'up': 
       # deal with zoom out 
       scale_factor = base_scale 
      else: 
       # deal with something that should never happen 
       scale_factor = 1 
       print event.button 

      new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor 
      new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor 

      relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0]) 
      rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0]) 

      ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)]) 
      ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)]) 
      ax.figure.canvas.draw() 

     fig = ax.get_figure() # get the figure of interest 
     fig.canvas.mpl_connect('scroll_event', zoom) 

     return zoom 

    def pan_factory(self, ax): 
     def onPress(event): 
      if event.inaxes != ax: return 
      self.cur_xlim = ax.get_xlim() 
      self.cur_ylim = ax.get_ylim() 
      self.press = self.x0, self.y0, event.xdata, event.ydata 
      self.x0, self.y0, self.xpress, self.ypress = self.press 

     def onRelease(event): 
      self.press = None 
      ax.figure.canvas.draw() 

     def onMotion(event): 
      if self.press is None: return 
      if event.inaxes != ax: return 
      dx = event.xdata - self.xpress 
      dy = event.ydata - self.ypress 
      self.cur_xlim -= dx 
      self.cur_ylim -= dy 
      ax.set_xlim(self.cur_xlim) 
      ax.set_ylim(self.cur_ylim) 

      ax.figure.canvas.draw() 

     fig = ax.get_figure() # get the figure of interest 

     # attach the call back 
     fig.canvas.mpl_connect('button_press_event',onPress) 
     fig.canvas.mpl_connect('button_release_event',onRelease) 
     fig.canvas.mpl_connect('motion_notify_event',onMotion) 

     #return the function 
     return onMotion 


fig = figure() 

ax = fig.add_subplot(111, xlim=(0,1), ylim=(0,1), autoscale_on=False) 

ax.set_title('Click to zoom') 
x,y,s,c = numpy.random.rand(4,200) 
s *= 200 

ax.scatter(x,y,s,c) 
scale = 1.1 
zp = ZoomPan() 
figZoom = zp.zoom_factory(ax, base_scale = scale) 
figPan = zp.pan_factory(ax) 
show() 
2

बहुत बहुत धन्यवाद। यह बहुत अच्छा काम किया। हालांकि, भूखंडों के लिए जहां स्केल अब रैखिक नहीं है (उदाहरण के लिए लॉग प्लॉट), यह टूट जाता है। मैंने इसके लिए एक नया संस्करण लिखा है। मुझे उम्मीद है कि यह किसी की मदद करेगा।

असल में, मैं धुरी निर्देशांक में ज़ूम करता हूं जो [0,1] होने के लिए सामान्यीकृत होते हैं। इसलिए, यदि मैं एक्स में दो से ज़ूम करता हूं, तो अब मैं [.25, .75] रेंज में रहना चाहता हूं। मैंने एक्स अक्ष में सीधे या नीचे होने पर केवल ज़ूम इन करने के लिए एक सुविधा भी जोड़ दी है, और केवल वाई में यदि आप सीधे वाई या अक्ष के दाएं हैं। अगर आपको इसकी आवश्यकता नहीं है, तो बस zoomx = True सेट करें, और ज़ूमी = सही करें और अगर कथन को अनदेखा करें। http://matplotlib.org/users/transforms_tutorial.html

यह समारोह एक वस्तु है कि कुल्हाड़ियों (self.ax) के लिए सूचक होता है के भीतर है:

इस संदर्भ जो समझते हैं कि कैसे matplotlib अलग समन्वय प्रणालियों के बीच बदल देती चाहते लोगों के लिए बहुत उपयोगी है।

def zoom(self,event): 
    '''This function zooms the image upon scrolling the mouse wheel. 
    Scrolling it in the plot zooms the plot. Scrolling above or below the 
    plot scrolls the x axis. Scrolling to the left or the right of the plot 
    scrolls the y axis. Where it is ambiguous nothing happens. 
    NOTE: If expanding figure to subplots, you will need to add an extra 
    check to make sure you are not in any other plot. It is not clear how to 
    go about this. 
    Since we also want this to work in loglog plot, we work in axes 
    coordinates and use the proper scaling transform to convert to data 
    limits.''' 

    x = event.x 
    y = event.y 

    #convert pixels to axes 
    tranP2A = self.ax.transAxes.inverted().transform 
    #convert axes to data limits 
    tranA2D= self.ax.transLimits.inverted().transform 
    #convert the scale (for log plots) 
    tranSclA2D = self.ax.transScale.inverted().transform 

    if event.button == 'down': 
     # deal with zoom in 
     scale_factor = self.zoom_scale 
    elif event.button == 'up': 
     # deal with zoom out 
     scale_factor = 1/self.zoom_scale 
    else: 
     # deal with something that should never happen 
     scale_factor = 1 

    #get my axes position to know where I am with respect to them 
    xa,ya = tranP2A((x,y)) 
    zoomx = False 
    zoomy = False 
    if(ya < 0): 
     if(xa >= 0 and xa <= 1): 
      zoomx = True 
      zoomy = False 
    elif(ya <= 1): 
     if(xa <0): 
      zoomx = False 
      zoomy = True 
     elif(xa <= 1): 
      zoomx = True 
      zoomy = True 
     else: 
      zoomx = False 
      zoomy = True 
    else: 
     if(xa >=0 and xa <= 1): 
      zoomx = True 
      zoomy = False 

    new_alimx = (0,1) 
    new_alimy = (0,1) 
    if(zoomx): 
     new_alimx = (np.array([1,1]) + np.array([-1,1])*scale_factor)*.5 
    if(zoomy): 
     new_alimy = (np.array([1,1]) + np.array([-1,1])*scale_factor)*.5 

    #now convert axes to data 
    new_xlim0,new_ylim0 = tranSclA2D(tranA2D((new_alimx[0],new_alimy[0]))) 
    new_xlim1,new_ylim1 = tranSclA2D(tranA2D((new_alimx[1],new_alimy[1]))) 

    #and set limits 
    self.ax.set_xlim([new_xlim0,new_xlim1]) 
    self.ax.set_ylim([new_ylim0,new_ylim1]) 
    self.redraw() 
+0

ज़ूम कर रहे हैं की बेहतर भावना/समझ दे रहे हैं क्या आप इसे अपस्ट्रीम सबमिट कर सकते हैं? लगभग https://github.com/matplotlib/matplotlib/blob/master/lib/matplotlib/backend_tools.py#L625 – tacaswell

+0

में पैच होना चाहिए। https://github.com/matplotlib/matplotlib/pull/4970 पहली बार ऐसा करने के बाद, तो मुझे बताएं कि मुझे कुछ करना चाहिए था, या बेहतर हो सकता था। धन्यवाद! – julienl

2

मुझे वास्तव में आकृति भूखंडों में "एक्स केवल" या "वाई केवल" मोड पसंद हैं। आप एक्स और वाई कुंजी को बांध सकते हैं ताकि ज़ूमिंग केवल एक दिशा में हो। ध्यान रखें कि आप अगर आप एक एंट्री बॉक्स या कुछ और पर क्लिक करें कैनवास पर वापस ध्यान केंद्रित रखने के लिए हो सकता है -

canvas.mpl_connect('button_press_event', lambda event:canvas._tkcanvas.focus_set())

संशोधित कोड के बाकी नीचे है:

from matplotlib.pyplot import figure, show 
import numpy 

class ZoomPan: 
    def __init__(self): 
     self.press = None 
     self.cur_xlim = None 
     self.cur_ylim = None 
     self.x0 = None 
     self.y0 = None 
     self.x1 = None 
     self.y1 = None 
     self.xpress = None 
     self.ypress = None 
     self.xzoom = True 
     self.yzoom = True 
     self.cidBP = None 
     self.cidBR = None 
     self.cidBM = None 
     self.cidKeyP = None 
     self.cidKeyR = None 
     self.cidScroll = None 

    def zoom_factory(self, ax, base_scale = 2.): 
     def zoom(event): 
      cur_xlim = ax.get_xlim() 
      cur_ylim = ax.get_ylim() 

      xdata = event.xdata # get event x location 
      ydata = event.ydata # get event y location 
      if(xdata is None): 
       return() 
      if(ydata is None): 
       return() 

      if event.button == 'down': 
       # deal with zoom in 
       scale_factor = 1/base_scale 
      elif event.button == 'up': 
       # deal with zoom out 
       scale_factor = base_scale 
      else: 
       # deal with something that should never happen 
       scale_factor = 1 
       print(event.button) 

      new_width = (cur_xlim[1] - cur_xlim[0]) * scale_factor 
      new_height = (cur_ylim[1] - cur_ylim[0]) * scale_factor 

      relx = (cur_xlim[1] - xdata)/(cur_xlim[1] - cur_xlim[0]) 
      rely = (cur_ylim[1] - ydata)/(cur_ylim[1] - cur_ylim[0]) 

      if(self.xzoom): 
       ax.set_xlim([xdata - new_width * (1-relx), xdata + new_width * (relx)]) 
      if(self.yzoom): 
       ax.set_ylim([ydata - new_height * (1-rely), ydata + new_height * (rely)]) 
      ax.figure.canvas.draw() 
      ax.figure.canvas.flush_events() 

     def onKeyPress(event): 
      if event.key == 'x': 
       self.xzoom = True 
       self.yzoom = False 
      if event.key == 'y': 
       self.xzoom = False 
       self.yzoom = True 

     def onKeyRelease(event): 
      self.xzoom = True 
      self.yzoom = True 

     fig = ax.get_figure() # get the figure of interest 

     self.cidScroll = fig.canvas.mpl_connect('scroll_event', zoom) 
     self.cidKeyP = fig.canvas.mpl_connect('key_press_event',onKeyPress) 
     self.cidKeyR = fig.canvas.mpl_connect('key_release_event',onKeyRelease) 

     return zoom 

    def pan_factory(self, ax): 
     def onPress(event): 
      if event.inaxes != ax: return 
      self.cur_xlim = ax.get_xlim() 
      self.cur_ylim = ax.get_ylim() 
      self.press = self.x0, self.y0, event.xdata, event.ydata 
      self.x0, self.y0, self.xpress, self.ypress = self.press 


     def onRelease(event): 
      self.press = None 
      ax.figure.canvas.draw() 

     def onMotion(event): 
      if self.press is None: return 
      if event.inaxes != ax: return 
      dx = event.xdata - self.xpress 
      dy = event.ydata - self.ypress 
      self.cur_xlim -= dx 
      self.cur_ylim -= dy 
      ax.set_xlim(self.cur_xlim) 
      ax.set_ylim(self.cur_ylim) 

      ax.figure.canvas.draw() 
      ax.figure.canvas.flush_events() 

     fig = ax.get_figure() # get the figure of interest 

     self.cidBP = fig.canvas.mpl_connect('button_press_event',onPress) 
     self.cidBR = fig.canvas.mpl_connect('button_release_event',onRelease) 
     self.cidBM = fig.canvas.mpl_connect('motion_notify_event',onMotion) 
     # attach the call back 

     #return the function 
     return onMotion 
1

यह ऊपर दिए गए कोड में मामूली संशोधन के लिए एक सुझाव है - यह ज़ूम केंद्रित अधिक प्रबंधनीय बनाए रखता है।

cur_xrange = (cur_xlim[1] - cur_xlim[0])*.5 
    cur_yrange = (cur_ylim[1] - cur_ylim[0])*.5 
    xmouse = event.xdata # get event x location                                                        
    ymouse = event.ydata # get event y location                                                        
    cur_xcentre = (cur_xlim[1] + cur_xlim[0])*.5 
    cur_ycentre = (cur_ylim[1] + cur_ylim[0])*.5 
    xdata = cur_xcentre+ 0.25*(xmouse-cur_xcentre) 
    ydata = cur_ycentre+ 0.25*(ymouse-cur_ycentre) 
संबंधित मुद्दे