|
From: <lee...@us...> - 2010-01-29 17:33:31
|
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.
|