Revision: 8103
          http://matplotlib.svn.sourceforge.net/matplotlib/?rev=8103&view=rev
Author:   leejjoon
Date:     2010-01-29 17:33:21 +0000 (Fri, 29 Jan 2010)

Log Message:
-----------
refactor draggable legend to support annotation

Modified Paths:
--------------
    trunk/matplotlib/lib/matplotlib/legend.py
    trunk/matplotlib/lib/matplotlib/offsetbox.py
    trunk/matplotlib/lib/matplotlib/text.py

Added Paths:
-----------
    trunk/matplotlib/examples/animation/draggable_legend.py

Added: trunk/matplotlib/examples/animation/draggable_legend.py
===================================================================
--- trunk/matplotlib/examples/animation/draggable_legend.py                     
        (rev 0)
+++ trunk/matplotlib/examples/animation/draggable_legend.py     2010-01-29 
17:33:21 UTC (rev 8103)
@@ -0,0 +1,43 @@
+import matplotlib.pyplot as plt
+
+
+ax = plt.subplot(111)
+ax.plot([1,2,3], label="test")
+
+l = ax.legend()
+d1 = l.draggable()
+
+xy = 1, 2        
+txt = ax.annotate("Test", xy, xytext=(-30, 30),
+                  textcoords="offset points",
+                  bbox=dict(boxstyle="round",fc=(0.2, 1, 1)),
+                  arrowprops=dict(arrowstyle="->"))
+d2 = txt.draggable()
+
+
+from matplotlib._png import read_png
+from matplotlib.cbook import get_sample_data
+
+from matplotlib.offsetbox import OffsetImage, AnnotationBbox
+
+fn = get_sample_data("lena.png", asfileobj=False)
+arr_lena = read_png(fn)
+
+imagebox = OffsetImage(arr_lena, zoom=0.2)
+
+ab = AnnotationBbox(imagebox, xy,
+                    xybox=(120., -80.),
+                    xycoords='data',
+                    boxcoords="offset points",
+                    pad=0.5,
+                    arrowprops=dict(arrowstyle="->",
+                                    
connectionstyle="angle,angleA=0,angleB=90,rad=3")
+                    )
+
+
+ax.add_artist(ab)
+
+d3 = ab.draggable()
+    
+    
+plt.show()

Modified: trunk/matplotlib/lib/matplotlib/legend.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/legend.py   2010-01-29 16:22:51 UTC (rev 
8102)
+++ trunk/matplotlib/lib/matplotlib/legend.py   2010-01-29 17:33:21 UTC (rev 
8103)
@@ -33,56 +33,28 @@
 from matplotlib.patches import Patch, Rectangle, Shadow, FancyBboxPatch
 from matplotlib.collections import LineCollection, RegularPolyCollection, \
      CircleCollection
-from matplotlib.transforms import Bbox, BboxBase, TransformedBbox, 
BboxTransformTo
+from matplotlib.transforms import Bbox, BboxBase, TransformedBbox, 
BboxTransformTo, BboxTransformFrom
 
-from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea
+from matplotlib.offsetbox import HPacker, VPacker, TextArea, DrawingArea, 
DraggableOffsetBox
 
 
-class DraggableLegend:
-    """helper code for a draggable legend -- see Legend.draggable"""
-
+class DraggableLegend(DraggableOffsetBox):
     def __init__(self, legend):
-        self.legend = legend
-        self.gotLegend = False
+        self.legend=legend
+        DraggableOffsetBox.__init__(self, legend, legend._legend_box)
 
-        c1 = legend.figure.canvas.mpl_connect('motion_notify_event', 
self.on_motion)
-        c2 = legend.figure.canvas.mpl_connect('pick_event', self.on_pick)
-        c3 = legend.figure.canvas.mpl_connect('button_release_event', 
self.on_release)
-        legend.set_picker(self.my_legend_picker)
-        self.cids = [c1, c2, c3]
-
-    def on_motion(self, evt):
-        if self.gotLegend:
-            dx = evt.x - self.mouse_x
-            dy = evt.y - self.mouse_y
-            loc_in_canvas = self.legend_x + dx, self.legend_y + dy
-            loc_in_norm_axes = 
self.legend.parent.transAxes.inverted().transform_point(loc_in_canvas)
-            self.legend._loc = tuple(loc_in_norm_axes)
-            self.legend.figure.canvas.draw()
-
-    def my_legend_picker(self, legend, evt):
+    def artist_picker(self, legend, evt):
         return self.legend.legendPatch.contains(evt)
 
-    def on_pick(self, evt):
-        legend = self.legend
-        if evt.artist == legend:
-            bbox = self.legend.get_window_extent()
-            self.mouse_x = evt.mouseevent.x
-            self.mouse_y = evt.mouseevent.y
-            self.legend_x = bbox.xmin
-            self.legend_y = bbox.ymin
-            self.gotLegend = 1
+    def finalize_offset(self):
+        loc_in_canvas = self.get_loc_in_canvas()
 
-    def on_release(self, event):
-        if self.gotLegend:
-            self.gotLegend = False
+        bbox = self.legend.get_bbox_to_anchor()
+        _bbox_transform = BboxTransformFrom(bbox)
+        self.legend._loc = 
tuple(_bbox_transform.transform_point(loc_in_canvas))
+        
 
-    def disconnect(self):
-        'disconnect the callbacks'
-        for cid in self.cids:
-            self.legend.figure.canvas.mpl_disconnect(cid)
 
-
 class Legend(Artist):
     """
     Place a legend on the axes at location loc.  Labels are a
@@ -323,7 +295,6 @@
                           'Falling back on "upper right".')
             loc = 1
 
-        self._loc = loc
         self._mode = mode
         self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
 
@@ -357,6 +328,8 @@
         # init with null renderer
         self._init_legend_box(handles, labels)
 
+        self._loc = loc
+
         self.set_title(title)
 
         self._last_fontsize_points = self._fontsize
@@ -373,6 +346,28 @@
         a.set_transform(self.get_transform())
 
 
+    def _set_loc(self, loc):
+        # find_offset function will be provided to _legend_box and
+        # _legend_box will draw itself at the location of the return
+        # value of the find_offset.
+        self._loc_real = loc
+        if loc == 0:
+            _findoffset = self._findoffset_best
+        else:
+            _findoffset = self._findoffset_loc
+
+        #def findoffset(width, height, xdescent, ydescent):
+        #    return _findoffset(width, height, xdescent, ydescent, renderer)
+
+        self._legend_box.set_offset(_findoffset)
+
+        self._loc_real = loc
+
+    def _get_loc(self):
+        return self._loc_real
+
+    _loc = property(_get_loc, _set_loc)
+
     def _findoffset_best(self, width, height, xdescent, ydescent, renderer):
         "Helper function to locate the legend at its best position"
         ox, oy = self._find_best_position(width, height, renderer)
@@ -401,19 +396,6 @@
         renderer.open_group('legend')
 
 
-        # find_offset function will be provided to _legend_box and
-        # _legend_box will draw itself at the location of the return
-        # value of the find_offset.
-        if self._loc == 0:
-            _findoffset = self._findoffset_best
-        else:
-            _findoffset = self._findoffset_loc
-
-        def findoffset(width, height, xdescent, ydescent):
-            return _findoffset(width, height, xdescent, ydescent, renderer)
-
-        self._legend_box.set_offset(findoffset)
-
         fontsize = renderer.points_to_pixels(self._fontsize)
 
         # if mode == fill, set the width of the legend_box to the

Modified: trunk/matplotlib/lib/matplotlib/offsetbox.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/offsetbox.py        2010-01-29 16:22:51 UTC 
(rev 8102)
+++ trunk/matplotlib/lib/matplotlib/offsetbox.py        2010-01-29 17:33:21 UTC 
(rev 8103)
@@ -20,7 +20,7 @@
 import matplotlib.text as mtext
 import numpy as np
 from matplotlib.transforms import Bbox, BboxBase, TransformedBbox, \
-     IdentityTransform
+     IdentityTransform, BboxTransformFrom
 
 from matplotlib.font_manager import FontProperties
 from matplotlib.patches import FancyBboxPatch, FancyArrowPatch
@@ -168,14 +168,14 @@
         """
         self._offset = xy
 
-    def get_offset(self, width, height, xdescent, ydescent):
+    def get_offset(self, width, height, xdescent, ydescent, renderer):
         """
         Get the offset
 
         accepts extent of the box
         """
         if callable(self._offset):
-            return self._offset(width, height, xdescent, ydescent)
+            return self._offset(width, height, xdescent, ydescent, renderer)
         else:
             return self._offset
 
@@ -222,7 +222,7 @@
         get the bounding box in display space.
         '''
         w, h, xd, yd, offsets = self.get_extent_offsets(renderer)
-        px, py = self.get_offset(w, h, xd, yd)
+        px, py = self.get_offset(w, h, xd, yd, renderer)
         return mtransforms.Bbox.from_bounds(px-xd, py-yd, w, h)
 
     def draw(self, renderer):
@@ -233,7 +233,7 @@
 
         width, height, xdescent, ydescent, offsets = 
self.get_extent_offsets(renderer)
 
-        px, py = self.get_offset(width, height, xdescent, ydescent)
+        px, py = self.get_offset(width, height, xdescent, ydescent, renderer)
 
         for c, (ox, oy) in zip(self.get_visible_children(), offsets):
             c.set_offset((px+ox, py+oy))
@@ -946,7 +946,7 @@
         '''
         self._update_offset_func(renderer)
         w, h, xd, yd = self.get_extent(renderer)
-        ox, oy = self.get_offset(w, h, xd, yd)
+        ox, oy = self.get_offset(w, h, xd, yd, renderer)
         return Bbox.from_bounds(ox-xd, oy-yd, w, h)
 
 
@@ -996,7 +996,7 @@
 
         width, height, xdescent, ydescent = self.get_extent(renderer)
 
-        px, py = self.get_offset(width, height, xdescent, ydescent)
+        px, py = self.get_offset(width, height, xdescent, ydescent, renderer)
 
         self.get_child().set_offset((px, py))
         self.get_child().draw(renderer)
@@ -1121,12 +1121,15 @@
 #         self.offset_transform.translate(xy[0], xy[1])
 
 
+
     def get_offset(self):
         """
         return offset of the container.
         """
         return self._offset
 
+    def get_children(self):
+        return [self.image]
 
     def get_window_extent(self, renderer):
         '''
@@ -1243,9 +1246,9 @@
 
     def contains(self,event):
         t,tinfo = self.offsetbox.contains(event)
-        if self.arrow is not None:
-            a,ainfo=self.arrow.contains(event)
-            t = t or a
+        #if self.arrow_patch is not None:
+        #    a,ainfo=self.arrow_patch.contains(event)
+        #    t = t or a
 
         # self.arrow_patch is currently not checked as this can be a line - JJ
 
@@ -1380,7 +1383,151 @@
 
 
 
+class DraggableBase(object):
+    """
+    helper code for a draggable artist (legend, offsetbox)
+    The derived class must override following two method.
 
+      def saveoffset(self):
+          pass
+
+      def update_offset(self, dx, dy):
+          pass
+
+    *saveoffset* is called when the object is picked for dragging and it is
+    meant to save reference position of the artist.
+
+    *update_offset* is called during the dragging. dx and dy is the pixel
+     offset from the point where the mouse drag started.
+
+    Optionally you may override following two methods.
+    
+      def artist_picker(self, artist, evt):
+          return self.ref_artist.contains(evt)
+
+      def finalize_offset(self):
+          pass
+
+    *artist_picker* is a picker method that will be
+     used. *finalize_offset* is called when the mouse is released. In
+     current implementaion of DraggableLegend and DraggableAnnotation,
+     *update_offset* places the artists simply in display
+     coordinates. And *finalize_offset* recalculate their position in
+     the normalized axes coordinate and set a relavant attribute.
+
+    """
+    def __init__(self, ref_artist):
+        self.ref_artist = ref_artist
+        self.got_artist = False
+
+        self.canvas = self.ref_artist.figure.canvas
+        c2 = self.canvas.mpl_connect('pick_event', self.on_pick)
+        c3 = self.canvas.mpl_connect('button_release_event', self.on_release)
+
+        ref_artist.set_picker(self.artist_picker)
+        self.cids = [c2, c3]
+
+    def on_motion(self, evt):
+        if self.got_artist:
+            dx = evt.x - self.mouse_x
+            dy = evt.y - self.mouse_y
+            self.update_offset(dx, dy)
+
+    def on_pick(self, evt):
+        if evt.artist == self.ref_artist:
+
+            self.save_offset()
+            self.mouse_x = evt.mouseevent.x
+            self.mouse_y = evt.mouseevent.y
+            self.got_artist = True
+
+            self._c1 = self.canvas.mpl_connect('motion_notify_event', 
self.on_motion)
+
+    def on_release(self, event):
+        if self.got_artist:
+            self.finalize_offset()
+            self.got_artist = False
+            self.canvas.mpl_disconnect(self._c1)
+
+    def disconnect(self):
+        'disconnect the callbacks'
+        for cid in self.cids:
+            self.canvas.mpl_disconnect(cid)
+
+    def artist_picker(self, artist, evt):
+        return self.ref_artist.contains(evt)
+
+    def save_offset(self):
+        pass
+
+    def update_offset(self, dx, dy):
+        pass
+        
+    def finalize_offset(self):
+        pass
+
+
+class DraggableOffsetBox(DraggableBase):
+    def __init__(self, ref_artist, offsetbox):
+        DraggableBase.__init__(self, ref_artist)
+        self.offsetbox = offsetbox
+
+    def save_offset(self):
+        offsetbox = self.offsetbox
+        renderer = offsetbox.figure._cachedRenderer
+        w, h, xd, yd = offsetbox.get_extent(renderer)
+        offset = offsetbox.get_offset(w, h, xd, yd, renderer)
+        self.offsetbox_x, self.offsetbox_y = offset
+
+    def update_offset(self, dx, dy):
+        loc_in_canvas = self.offsetbox_x + dx, self.offsetbox_y + dy
+        self.offsetbox.set_offset(loc_in_canvas)
+        self.offsetbox.figure.canvas.draw()
+        
+    def get_loc_in_canvas(self):
+
+        offsetbox=self.offsetbox
+        renderer = offsetbox.figure._cachedRenderer
+        w, h, xd, yd = offsetbox.get_extent(renderer)
+        ox, oy = offsetbox._offset
+        loc_in_canvas = (ox-xd, oy-yd)
+
+        return loc_in_canvas
+        
+
+class DraggableAnnotation(DraggableBase):
+    def __init__(self, annotation):
+        DraggableBase.__init__(self, annotation)
+        self.annotation = annotation
+
+    def save_offset(self):
+        ann = self.annotation
+        x, y = ann.xytext
+        if isinstance(ann.textcoords, tuple):
+            xcoord, ycoord = ann.textcoords
+            x1, y1 = ann._get_xy(x, y, xcoord)
+            x2, y2 = ann._get_xy(x, y, ycoord)
+            ox0, oy0 = x1, y2
+        else:
+            ox0, oy0 = ann._get_xy(x, y, ann.textcoords)
+
+        self.ox, self.oy = ox0, oy0
+        self.annotation.textcoords = "figure pixels"
+
+    def update_offset(self, dx, dy):
+        ann = self.annotation
+        ann.xytext = self.ox + dx, self.oy + dy
+        x, y = ann.xytext
+        xy = ann._get_xy(x, y, ann.textcoords)
+        self.canvas.draw()
+
+    def finalize_offset(self):
+        loc_in_canvas = self.annotation.xytext
+        self.annotation.textcoords = "axes fraction"
+        pos_axes_fraction = 
self.annotation.axes.transAxes.inverted().transform_point(loc_in_canvas)
+        self.annotation.xytext = tuple(pos_axes_fraction)
+
+
 if __name__ == "__main__":
 
     fig = plt.figure(1)

Modified: trunk/matplotlib/lib/matplotlib/text.py
===================================================================
--- trunk/matplotlib/lib/matplotlib/text.py     2010-01-29 16:22:51 UTC (rev 
8102)
+++ trunk/matplotlib/lib/matplotlib/text.py     2010-01-29 17:33:21 UTC (rev 
8103)
@@ -1402,6 +1402,9 @@
         self.textcoords = textcoords
         self.set_annotation_clip(annotation_clip)
 
+        self._draggable = None
+
+
     def _get_xy(self, x, y, s):
         if s=='data':
             trans = self.axes.transData
@@ -1534,8 +1537,38 @@
         return True
 
 
+    def draggable(self, state=None):
+        """
+        Set the draggable state -- if state is
 
+          * None : toggle the current state
 
+          * True : turn draggable on
+
+          * False : turn draggable off
+          
+        If draggable is on, you can drag the annotation on the canvas with
+        the mouse.  The DraggableAnnotation helper instance is returned if
+        draggable is on.
+        """
+        from matplotlib.offsetbox import DraggableAnnotation
+        is_draggable = self._draggable is not None
+
+        # if state is None we'll toggle
+        if state is None:
+            state = not is_draggable
+            
+        if state:
+            if self._draggable is None:
+                self._draggable = DraggableAnnotation(self)
+        else:
+            if self._draggable is not None:
+                self._draggable.disconnect()
+            self._draggable = None
+
+        return self._draggable
+
+
 class Annotation(Text, _AnnotationBase):
     """
     A :class:`~matplotlib.text.Text` class to make annotating things
@@ -1661,6 +1694,7 @@
         else:
             self.arrow_patch = None
 
+        
     def contains(self,event):
         t,tinfo = Text.contains(self,event)
         if self.arrow is not None:
@@ -1803,6 +1837,9 @@
         Text.draw(self, renderer)
 
 
+
+
+
 docstring.interpd.update(Annotation=Annotation.__init__.__doc__)
 
 


This was sent by the SourceForge.net collaborative development platform, the 
world's largest Open Source development site.

------------------------------------------------------------------------------
The Planet: dedicated and managed hosting, cloud storage, colocation
Stay online with enterprise data centers and the best network in the business
Choose flexible plans and management services without long-term contracts
Personal 24x7 support from experience hosting pros just a phone call away.
http://p.sf.net/sfu/theplanet-com
_______________________________________________
Matplotlib-checkins mailing list
Matplotlib-checkins@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/matplotlib-checkins

Reply via email to