offset in dragging a legend

Using mpl 0.98.5.2 in OO mode with wxAgg backend.

I'm trying to make my legends draggable. It works, but
there is a some inaccuracy with positioning. As I drag it,
the cursor "outruns" the position of the legend, and that
error grows the further away from the initial starting point
the cursor has been moved. It makes the feel of dragging
completely wrong.

The work of repositioning the legend is done in an
on_motion event. self.figure here is a mpl Figure.
The motion event handler is:

#drag the legend
    def on_motion(self, event):
        height = float(self.figure.bbox.height)
        width = float(self.figure.bbox.width)

        dx = event.x/width
        dy = event.y/height

        if self.gotLegend == 1: #it's picked up
            self.legend._loc=(dx,dy) #reposition it
            self.canvas.draw()
            self.parent.Refresh()

Any idea why this is wrong? I cobbled this from
a few sources online and boiled it down to this.
I am confused as to why it should use dx and dy
instead of just the exact position of the mouse.

Any ideas are appreciated, and full runnable
sample is below.

Thanks,
Che

#Boa:Frame:Frame1

import wx

import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure

def create(parent):
    return Frame1(parent)

[wxID_FRAME1, wxID_FRAME1NOTEBOOK1, wxID_FRAME1PANEL1,
] = [wx.NewId() for _init_ctrls in range(3)]

class PlotPanel(wx.Panel):
    def __init__(self, parent,id = -1, color = None,\
        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):

        self.parent = parent
        self.line_collections_list = []

        wx.Panel.__init__(self, parent, **kwargs)

        self.figure = Figure(None, dpi)
        self.canvas = FigureCanvasWxAgg( self, -1, self.figure )

        #Connect all the mpl events
        self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        self.canvas.mpl_connect('pick_event', self.on_pick)
        self.canvas.mpl_connect('button_release_event', self.on_release)

        self.gotLegend = 0 #to begin, legend is not picked.

        self._SetInitialSize()

        self.Bind(wx.EVT_SIZE, self._onSize)
        self.state = 'Initial'
        self.draw()

    def _onSize(self, event):
        self._SetSize()
        event.Skip()

    def _SetSize( self ):
        pixels = tuple( self.GetClientSize() )
        self.SetSize( pixels )
        self.canvas.SetSize( pixels )
        self.figure.set_size_inches( float( pixels[0] )/self.figure.get_dpi(),
                                     float( pixels[1] )/self.figure.get_dpi() )

    def _SetInitialSize(self,):
        pixels = self.parent.GetClientSize()
        self.canvas.SetSize(pixels)
        self.figure.set_size_inches( (pixels[0])/self.figure.get_dpi(),
         (pixels[1])/self.figure.get_dpi(), forward=True )

    def draw(self):
        self.subplot = self.figure.add_subplot(111)
        line, = self.subplot.plot([1,2,3],[4,5,6],'o',picker=5)
        self.line_collections_list.append(line)

        #Legend
        self.legend = self.subplot.legend(self.line_collections_list,
['datum'], 'right',
           numpoints=1)
        self.legend.set_picker(self.my_legend_picker)

    #pick up the legend patch
    def my_legend_picker(self, legend, event):
        return self.legend.legendPatch.contains(event)

    #pick the legend
    def on_pick(self, event):
        legend = self.legend
        if event.artist == legend:
            self.gotLegend = 1

    #drag the legend
    #(This doesn't work well enough)
    def on_motion(self, event):
        height = float(self.figure.bbox.height)
        width = float(self.figure.bbox.width)

        dx = event.x/width
        dy = event.y/height

        if self.gotLegend == 1:
            self.legend._loc=(dx,dy)
            self.canvas.draw()
            self.parent.Refresh()

    #release the legend
    def on_release(self, event):
        if self.gotLegend == 1:
            self.gotLegend = 0

class Frame1(wx.Frame):
    def _init_coll_boxSizer1_Items(self, parent):
        # generated method, don't edit

        parent.AddWindow(self.notebook1, 1, border=0, flag=wx.EXPAND)

    def _init_sizers(self):
        # generated method, don't edit
        self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)

        self._init_coll_boxSizer1_Items(self.boxSizer1)

        self.panel1.SetSizer(self.boxSizer1)

    def _init_ctrls(self, prnt):
        # generated method, don't edit
        wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
              pos=wx.Point(333, 202), size=wx.Size(592, 474),
              style=wx.DEFAULT_FRAME_STYLE,
              title='moving the legend accurately')
        self.SetClientSize(wx.Size(584, 440))

        self.panel1 = wx.Panel(id=wxID_FRAME1PANEL1, name='panel1', parent=self,
              pos=wx.Point(0, 0), size=wx.Size(584, 440),
              style=wx.TAB_TRAVERSAL)

        self.notebook1 = wx.Notebook(id=wxID_FRAME1NOTEBOOK1, name='notebook1',
              parent=self.panel1, pos=wx.Point(0, 0), size=wx.Size(584, 440),
              style=0)

        self._init_sizers()

    def __init__(self, parent):
        self._init_ctrls(parent)
        graph = PlotPanel(self.notebook1)
        self.notebook1.AddPage(graph,'graph')

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = create(None)
    frame.Show()

    app.MainLoop()

I don't have wx installed, so i'm not able to test your code.
However, here are some of my thoughts.

The location of the legend is the location of the lower-left corner in
the normalized coordinate of its parent. In your case, it would be
normalized "axes" coordinates. However, it seems that you're setting
the legend location in the normalized "figure" coordinate.

http://matplotlib.sourceforge.net/api/artist_api.html#module-matplotlib.legend

The event.x and event.y is the position of the mouse, and often this
would not be the position of the legend (lower left corner) you want.
I guess a common practice is to calculate how much your mouse moved
since you started dragging and adjust the position of the legend from
its original position by the same amount. What I would do is, in the
on_pick call, save the current location of the mouse and the current
location of the legend. And, when on_motion is called, calculate the
dx, dy of your current mouse position from the saved (original) mouse
position, and set the location of the legend by adding the same amount
to the original legend position. Of course, the coordinate need to be
converted in a proper system.

IHTH,

-JJ

···

On Wed, Mar 25, 2009 at 2:37 AM, C M <cmpython@...287...> wrote:

Using mpl 0.98.5.2 in OO mode with wxAgg backend.

I'm trying to make my legends draggable. It works, but
there is a some inaccuracy with positioning. As I drag it,
the cursor "outruns" the position of the legend, and that
error grows the further away from the initial starting point
the cursor has been moved. It makes the feel of dragging
completely wrong.

The work of repositioning the legend is done in an
on_motion event. self.figure here is a mpl Figure.
The motion event handler is:

#drag the legend
def on_motion(self, event):
height = float(self.figure.bbox.height)
width = float(self.figure.bbox.width)

   dx = event\.x/width
   dy = event\.y/height

   if self\.gotLegend == 1:  \#it&#39;s picked up
       self\.legend\.\_loc=\(dx,dy\)  \#reposition it
       self\.canvas\.draw\(\)
       self\.parent\.Refresh\(\)

Any idea why this is wrong? I cobbled this from
a few sources online and boiled it down to this.
I am confused as to why it should use dx and dy
instead of just the exact position of the mouse.

Any ideas are appreciated, and full runnable
sample is below.

Thanks,
Che

#Boa:Frame:Frame1

import wx

import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure

def create(parent):
return Frame1(parent)

[wxID_FRAME1, wxID_FRAME1NOTEBOOK1, wxID_FRAME1PANEL1,
] = [wx.NewId() for _init_ctrls in range(3)]

class PlotPanel(wx.Panel):
def __init__(self, parent,id = -1, color = None,\
dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):

   self\.parent = parent
   self\.line\_collections\_list = \[\]

   wx\.Panel\.\_\_init\_\_\(self, parent, \*\*kwargs\)

   self\.figure = Figure\(None, dpi\)
   self\.canvas = FigureCanvasWxAgg\( self, \-1, self\.figure \)

   \#Connect all the mpl events
   self\.canvas\.mpl\_connect\(&#39;motion\_notify\_event&#39;, self\.on\_motion\)
   self\.canvas\.mpl\_connect\(&#39;pick\_event&#39;, self\.on\_pick\)
   self\.canvas\.mpl\_connect\(&#39;button\_release\_event&#39;, self\.on\_release\)

   self\.gotLegend = 0  \#to begin, legend is not picked\.

   self\.\_SetInitialSize\(\)

   self\.Bind\(wx\.EVT\_SIZE, self\.\_onSize\)
   self\.state = &#39;Initial&#39;
   self\.draw\(\)

def _onSize(self, event):
self._SetSize()
event.Skip()

def _SetSize( self ):
pixels = tuple( self.GetClientSize() )
self.SetSize( pixels )
self.canvas.SetSize( pixels )
self.figure.set_size_inches( float( pixels[0] )/self.figure.get_dpi(),
float( pixels[1] )/self.figure.get_dpi() )

def _SetInitialSize(self,):
pixels = self.parent.GetClientSize()
self.canvas.SetSize(pixels)
self.figure.set_size_inches( (pixels[0])/self.figure.get_dpi(),
(pixels[1])/self.figure.get_dpi(), forward=True )

def draw(self):
self.subplot = self.figure.add_subplot(111)
line, = self.subplot.plot([1,2,3],[4,5,6],'o',picker=5)
self.line_collections_list.append(line)

   \#Legend
   self\.legend = self\.subplot\.legend\(self\.line\_collections\_list,

['datum'], 'right',
numpoints=1)
self.legend.set_picker(self.my_legend_picker)

#pick up the legend patch
def my_legend_picker(self, legend, event):
return self.legend.legendPatch.contains(event)

#pick the legend
def on_pick(self, event):
legend = self.legend
if event.artist == legend:
self.gotLegend = 1

#drag the legend
#(This doesn't work well enough)
def on_motion(self, event):
height = float(self.figure.bbox.height)
width = float(self.figure.bbox.width)

   dx = event\.x/width
   dy = event\.y/height

   if self\.gotLegend == 1:
       self\.legend\.\_loc=\(dx,dy\)
       self\.canvas\.draw\(\)
       self\.parent\.Refresh\(\)

#release the legend
def on_release(self, event):
if self.gotLegend == 1:
self.gotLegend = 0

class Frame1(wx.Frame):
def _init_coll_boxSizer1_Items(self, parent):
# generated method, don't edit

   parent\.AddWindow\(self\.notebook1, 1, border=0, flag=wx\.EXPAND\)

def _init_sizers(self):
# generated method, don't edit
self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)

   self\.\_init\_coll\_boxSizer1\_Items\(self\.boxSizer1\)

   self\.panel1\.SetSizer\(self\.boxSizer1\)

def _init_ctrls(self, prnt):
# generated method, don't edit
wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
pos=wx.Point(333, 202), size=wx.Size(592, 474),
style=wx.DEFAULT_FRAME_STYLE,
title='moving the legend accurately')
self.SetClientSize(wx.Size(584, 440))

   self\.panel1 = wx\.Panel\(id=wxID\_FRAME1PANEL1, name=&#39;panel1&#39;, parent=self,
         pos=wx\.Point\(0, 0\), size=wx\.Size\(584, 440\),
         style=wx\.TAB\_TRAVERSAL\)

   self\.notebook1 = wx\.Notebook\(id=wxID\_FRAME1NOTEBOOK1, name=&#39;notebook1&#39;,
         parent=self\.panel1, pos=wx\.Point\(0, 0\), size=wx\.Size\(584, 440\),
         style=0\)

   self\.\_init\_sizers\(\)

def __init__(self, parent):
self._init_ctrls(parent)
graph = PlotPanel(self.notebook1)
self.notebook1.AddPage(graph,'graph')

if __name__ == '__main__':
app = wx.PySimpleApp()
frame = create(None)
frame.Show()

app.MainLoop()

------------------------------------------------------------------------------
Apps built with the Adobe(R) Flex(R) framework and Flex Builder(TM) are
powering Web 2.0 with engaging, cross-platform capabilities. Quickly and
easily build your RIAs with Flex Builder, the Eclipse(TM)based development
software that enables intelligent coding and step-through debugging.
Download the free 60 day trial. http://p.sf.net/sfu/www-adobe-com
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options

Using mpl 0.98.5.2 in OO mode with wxAgg backend.

I'm trying to make my legends draggable. It works, but
there is a some inaccuracy with positioning. As I drag it,
the cursor "outruns" the position of the legend, and that
error grows the further away from the initial starting point
the cursor has been moved. It makes the feel of dragging
completely wrong.

The work of repositioning the legend is done in an
on_motion event. self.figure here is a mpl Figure.
The motion event handler is:

#drag the legend
def on_motion(self, event):
height = float(self.figure.bbox.height)
width = float(self.figure.bbox.width)

   dx = event\.x/width
   dy = event\.y/height

   if self\.gotLegend == 1:  \#it&#39;s picked up
       self\.legend\.\_loc=\(dx,dy\)  \#reposition it
       self\.canvas\.draw\(\)
       self\.parent\.Refresh\(\)

Weird. This is essentially what we do, but in our case there's no
problem. I ran your example and experienced the same "outrun"
behavior so I checked to see what was different between yours and
ours.

In ours, we catch the mpl button down event and after establishing a
hit on the legend do:

            bbox = self._legend.get_window_extent()
            self._dragOffset = (bbox.xmin() - mplEvent.x, bbox.ymin()
- mplEvent.y)

Then in the mousemove event handler we do:

            self._moveLegend(mplEvent.x + self._dragOffset[0],
mplEvent.y + self._dragOffset[1])

where self._moveLegend is:

    def _moveLegend(self, x, y, autoDraw=True):

        height = float(self.figure.bbox.height())
        width = float(self.figure.bbox.width())

        dx = x/width
        dy = y/height

        # The following two lines are specific to our implementation
and our internal data classes
        self._legendLocation = (dx, dy)
        self.plot.getLegend().setLocation(self._legendLocation)

        # This is the line that "moves" the legend
        self._legend._loc=(dx,dy)

        if autoDraw:
            self.draw()

Now we're using an ancient version of MPL (0.90.1) and many things
have changed in the meantime. YMMV.

···

On Tue, Mar 24, 2009 at 11:37 PM, C M <cmpython@...287...> wrote:

Hi Che,

There is still a problem with offset, but the legend seems to move the same distance as the mouse if you get height and width from the axes instead of the figure:

     def on_motion(self, event):
         height = float(self.figure.axes[0].bbox.height)
         width = float(self.figure.axes[0].bbox.width)

···

--
Christopher Brown, Ph.D.
Department of Speech and Hearing Science
Arizona State University

The event.x and event.y is the position of the mouse, and often this
would not be the position of the legend (lower left corner) you want.
I guess a common practice is to calculate how much your mouse moved
since you started dragging and adjust the position of the legend from
its original position by the same amount. What I would do is, in the
on_pick call, save the current location of the mouse and the current
location of the legend. And, when on_motion is called, calculate the
dx, dy of your current mouse position from the saved (original) mouse
position, and set the location of the legend by adding the same amount
to the original legend position. Of course, the coordinate need to be
converted in a proper system.

This is what I have attempted to do below. Here is the relevant part:

    #pick the legend
    def on_pick(self, event):
        legend = self.legend
        if event.artist == legend:
            bbox = self.legend.get_window_extent() #gets the box of the legend.
            self.mouse_x = event.mouseevent.x #get mouse coordinates
at time of pick.
            self.mouse_y = event.mouseevent.y
            self.legend_x = bbox.xmin #get legend coordinates
at time of pick.
            self.legend_y = bbox.ymin

            self.gotLegend = 1 #indicates we picked up the legend.

    #drag the legend
    def on_motion(self, event):
        if self.gotLegend == 1:
            mouse_diff_x = self.mouse_x - event.x #how much the mouse moved.
            mouse_diff_y = self.mouse_y - event.y

            #move the legend from its previous location by that same amount
            self.legend._loc=(self.legend_x + mouse_diff_x,
self.legend_y + mouse_diff_y)
            self.canvas.draw()
            self.parent.Refresh()

Now when I run this, the legend just disappears (well, is moved to somewhere
off the screen).

For those with wx installed, runnable sample follows...

Thanks,
Che

#Boa:Frame:Frame1

import wx

import matplotlib
matplotlib.interactive(True)
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg
from matplotlib.figure import Figure

def create(parent):
    return Frame1(parent)

[wxID_FRAME1, wxID_FRAME1NOTEBOOK1, wxID_FRAME1PANEL1,
] = [wx.NewId() for _init_ctrls in range(3)]

class PlotPanel(wx.Panel):
    def __init__(self, parent,id = -1, color = None,\
        dpi = None, style = wx.NO_FULL_REPAINT_ON_RESIZE, **kwargs):

        self.parent = parent
        self.line_collections_list =

        wx.Panel.__init__(self, parent, **kwargs)

        self.figure = Figure(None, dpi)
        self.canvas = FigureCanvasWxAgg( self, -1, self.figure )

        #Connect all the mpl events
        self.canvas.mpl_connect('motion_notify_event', self.on_motion)
        self.canvas.mpl_connect('pick_event', self.on_pick)
        self.canvas.mpl_connect('button_release_event', self.on_release)

        self.gotLegend = 0 #to begin, legend is not picked.

        self._SetInitialSize()

        self.Bind(wx.EVT_SIZE, self._onSize)
        self.state = 'Initial'
        self.draw()

    def _onSize(self, event):
        self._SetSize()
        event.Skip()

    def _SetSize( self ):
        pixels = tuple( self.GetClientSize() )
        self.SetSize( pixels )
        self.canvas.SetSize( pixels )
        self.figure.set_size_inches( float( pixels[0] )/self.figure.get_dpi(),
                                     float( pixels[1] )/self.figure.get_dpi() )

    def _SetInitialSize(self,):
        pixels = self.parent.GetClientSize()
        self.canvas.SetSize(pixels)
        self.figure.set_size_inches( (pixels[0])/self.figure.get_dpi(),
         (pixels[1])/self.figure.get_dpi(), forward=True )

    def draw(self):
        self.subplot = self.figure.add_subplot(111)
        line, = self.subplot.plot([1,2,3],[4,5,6],'o',picker=5)
        self.line_collections_list.append(line)

        #Legend
        self.legend = self.subplot.legend(self.line_collections_list,
['1'], numpoints=1)
        self.legend.set_picker(self.my_legend_picker)

    #pick up the legend patch
    def my_legend_picker(self, legend, event):
        return self.legend.legendPatch.contains(event)

    #pick the legend
    def on_pick(self, event):
        legend = self.legend
        if event.artist == legend:
            bbox = self.legend.get_window_extent() #gets the box of the legend.
            self.mouse_x = event.mouseevent.x #get mouse coordinates
at time of pick.
            self.mouse_y = event.mouseevent.y
            self.legend_x = bbox.xmin #get legend coordinates
at time of pick.
            self.legend_y = bbox.ymin

            self.gotLegend = 1 #indicates we picked up the legend.

    #drag the legend
    def on_motion(self, event):
        if self.gotLegend == 1:
            mouse_diff_x = self.mouse_x - event.x #how much the mouse moved.
            mouse_diff_y = self.mouse_y - event.y

            #move the legend from its previous location by that same amount
            self.legend._loc=(self.legend_x + mouse_diff_x,
self.legend_y + mouse_diff_y)
            self.canvas.draw()
            self.parent.Refresh()

    #release the legend
    def on_release(self, event):
        if self.gotLegend == 1:
            self.gotLegend = 0
            print 'release'

class Frame1(wx.Frame):
    def _init_coll_boxSizer1_Items(self, parent):
        # generated method, don't edit

        parent.AddWindow(self.notebook1, 1, border=0, flag=wx.EXPAND)

    def _init_sizers(self):
        # generated method, don't edit
        self.boxSizer1 = wx.BoxSizer(orient=wx.VERTICAL)

        self._init_coll_boxSizer1_Items(self.boxSizer1)

        self.panel1.SetSizer(self.boxSizer1)

    def _init_ctrls(self, prnt):
        # generated method, don't edit
        wx.Frame.__init__(self, id=wxID_FRAME1, name='', parent=prnt,
              pos=wx.Point(333, 202), size=wx.Size(592, 474),
              style=wx.DEFAULT_FRAME_STYLE,
              title='moving the legend accurately')
        self.SetClientSize(wx.Size(584, 440))

        self.panel1 = wx.Panel(id=wxID_FRAME1PANEL1, name='panel1', parent=self,
              pos=wx.Point(0, 0), size=wx.Size(584, 440),
              style=wx.TAB_TRAVERSAL)

        self.notebook1 = wx.Notebook(id=wxID_FRAME1NOTEBOOK1, name='notebook1',
              parent=self.panel1, pos=wx.Point(0, 0), size=wx.Size(584, 440),
              style=0)

        self._init_sizers()

    def __init__(self, parent):
        self._init_ctrls(parent)
        graph = PlotPanel(self.notebook1)
        self.notebook1.AddPage(graph,'graph')

if __name__ == '__main__':
    app = wx.PySimpleApp()
    frame = create(None)
    frame.Show()

    app.MainLoop()

Ok, getting there. When I print the various coordinates to stdout, it
SHOULD be working, but my legend is simply disappearing. This
is the stdout on one pixel move with the mouse in the x:

mouse x position at pick time 489
mouse y position at pick time 349.0
Legend x position at pick time = 445.878125
Legend y position at pick time= 339.8
motion_event.x = 488
motion_event.y = 349.0
mouse moved x = 1
mouse moved y = 0.0
new legend location on move = (446.87812499999995, 339.80000000000001)
release

But what instead happens is the legend poofs out of existence.

At the end of the motion event, I am calling this:

self.canvas.draw()
self.parent.Refresh()

And wonder if I should be using some other way to redraw the legend?
Is it just not being redrawn this way? I thought I should use self.draw(),
which is a method in my class which draws the line and the legend,
but using that does nothing at all.

Very stuck here. Any help appreciated.
C

As I said in my previous email, the _loc attribute of the legend need
to be in the normalized axes coordinate, i.e., the lower left corner
of the axes being (0,0) and the upper-right corner being (1,1). Thus,
it needs to be something like below.

  loc_in_canvas = self.legend_x + mouse_diff_x, self.legend_y + mouse_diff_y
  loc_in_norm_axes =
self.legend.transAxes.inverted().transform_point(loc_in_canvas)
  self.legend._loc = loc_in_norm_axes

Note that it assumes that the parent of the legend is an Axes
instance, which I think is your case.
IHTH,

-JJ

···

On Wed, Mar 25, 2009 at 8:26 PM, C M <cmpython@...287...> wrote:

Ok, getting there. When I print the various coordinates to stdout, it
SHOULD be working, but my legend is simply disappearing. This
is the stdout on one pixel move with the mouse in the x:

mouse x position at pick time 489
mouse y position at pick time 349.0
Legend x position at pick time = 445.878125
Legend y position at pick time= 339.8
motion_event.x = 488
motion_event.y = 349.0
mouse moved x = 1
mouse moved y = 0.0
new legend location on move = (446.87812499999995, 339.80000000000001)
release

But what instead happens is the legend poofs out of existence.

At the end of the motion event, I am calling this:

self.canvas.draw()
self.parent.Refresh()

And wonder if I should be using some other way to redraw the legend?
Is it just not being redrawn this way? I thought I should use self.draw(),
which is a method in my class which draws the line and the legend,
but using that does nothing at all.

Very stuck here. Any help appreciated.
C

------------------------------------------------------------------------------
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options

Tony wrote a nice summary of the various coordinate system used in
MPL, but it seems that it didn't make into the official documentation
yet. Here is the link. This will give you some idea about the MPL's
coordinate system.

http://article.gmane.org/gmane.comp.python.matplotlib.general/14008

-JJ

···

On Wed, Mar 25, 2009 at 9:06 PM, Jae-Joon Lee <lee.j.joon@...287...> wrote:

As I said in my previous email, the _loc attribute of the legend need
to be in the normalized axes coordinate, i.e., the lower left corner
of the axes being (0,0) and the upper-right corner being (1,1). Thus,
it needs to be something like below.

loc_in_canvas = self.legend_x + mouse_diff_x, self.legend_y + mouse_diff_y
loc_in_norm_axes =
self.legend.transAxes.inverted().transform_point(loc_in_canvas)
self.legend._loc = loc_in_norm_axes

Note that it assumes that the parent of the legend is an Axes
instance, which I think is your case.
IHTH,

-JJ

On Wed, Mar 25, 2009 at 8:26 PM, C M <cmpython@...287...> wrote:

Ok, getting there. When I print the various coordinates to stdout, it
SHOULD be working, but my legend is simply disappearing. This
is the stdout on one pixel move with the mouse in the x:

mouse x position at pick time 489
mouse y position at pick time 349.0
Legend x position at pick time = 445.878125
Legend y position at pick time= 339.8
motion_event.x = 488
motion_event.y = 349.0
mouse moved x = 1
mouse moved y = 0.0
new legend location on move = (446.87812499999995, 339.80000000000001)
release

But what instead happens is the legend poofs out of existence.

At the end of the motion event, I am calling this:

self.canvas.draw()
self.parent.Refresh()

And wonder if I should be using some other way to redraw the legend?
Is it just not being redrawn this way? I thought I should use self.draw(),
which is a method in my class which draws the line and the legend,
but using that does nothing at all.

Very stuck here. Any help appreciated.
C

------------------------------------------------------------------------------
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options

Thanks, but I am getting this error now:

AttributeError: 'Legend' object has no attribute 'transAxes'

My legend is created this way:

self.legend = self.subplot.legend(self.line_collections_list, ['1'],
numpoints=1)

And self.subplot is made this way:

self.figure = Figure(None, dpi)
self.subplot = self.figure.add_subplot(111)

I thought self.subplot is an Axes instance. Is it not?

Thank you,
C

···

On Wed, Mar 25, 2009 at 9:06 PM, Jae-Joon Lee <lee.j.joon@...287...> wrote:

As I said in my previous email, the _loc attribute of the legend need
to be in the normalized axes coordinate, i.e., the lower left corner
of the axes being (0,0) and the upper-right corner being (1,1). Thus,
it needs to be something like below.

loc_in_canvas = self.legend_x + mouse_diff_x, self.legend_y + mouse_diff_y
loc_in_norm_axes =
self.legend.transAxes.inverted().transform_point(loc_in_canvas)
self.legend._loc = loc_in_norm_axes

Note that it assumes that the parent of the legend is an Axes
instance, which I think is your case.
IHTH,

-JJ

Ah, my bad.

Try

   self.legend.parent.transAxes.inverted().transform_point(loc_in_canvas)

legend.parent points to the parent axes.

-JJ

···

On Wed, Mar 25, 2009 at 9:36 PM, C M <cmpython@...287...> wrote:

On Wed, Mar 25, 2009 at 9:06 PM, Jae-Joon Lee <lee.j.joon@...287...> wrote:

As I said in my previous email, the _loc attribute of the legend need
to be in the normalized axes coordinate, i.e., the lower left corner
of the axes being (0,0) and the upper-right corner being (1,1). Thus,
it needs to be something like below.

loc_in_canvas = self.legend_x + mouse_diff_x, self.legend_y + mouse_diff_y
loc_in_norm_axes =
self.legend.transAxes.inverted().transform_point(loc_in_canvas)
self.legend._loc = loc_in_norm_axes

Note that it assumes that the parent of the legend is an Axes
instance, which I think is your case.
IHTH,

-JJ

Thanks, but I am getting this error now:

AttributeError: 'Legend' object has no attribute 'transAxes'

My legend is created this way:

self.legend = self.subplot.legend(self.line_collections_list, ['1'],
numpoints=1)

And self.subplot is made this way:

self.figure = Figure(None, dpi)
self.subplot = self.figure.add_subplot(111)

I thought self.subplot is an Axes instance. Is it not?

Thank you,
C

------------------------------------------------------------------------------
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options

That cleared up the error, thanks. But it is still not actually moving the
legend. The coordinates are changing as you described it:

loc position (JJ method) = [ 0.89354419 0.91002415]
motion_event.x = 436
motion_event.y = 349.0
mouse moved x = 31
mouse moved y = 3.0
loc position (JJ method) = [ 0.90698505 0.91002415]

but the legend doesn't move.

I have tried to different ways of doing the draw method.

1. self.canvas.draw()
That gives this error and does not move the legend:
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()

2. self.draw() (this is a method which draws the lines and legend
originally in my class).
That just does nothing, though the output is as shown above.

What do I have to do to actually show the repositioned legend?

Thanks for your patience,
C

···

On Wed, Mar 25, 2009 at 9:58 PM, Jae-Joon Lee <lee.j.joon@...287...> wrote:

Ah, my bad.

Try

self.legend.parent.transAxes.inverted().transform_point(loc_in_canvas)

legend.parent points to the parent axes.

-JJ

Sorry, It's hard to track down what's wrong without actually running the code.

Change

self.legend._loc = loc_in_norm_axes

to

self.legend._loc = tuple(loc_in_norm_axes)

and see if it works.

You need to call canvas.draw. However, it will draw whole figure
again. If you're concerned about speed, you may consider to use blit
method.

http://matplotlib.sourceforge.net/examples/animation/animation_blit_wx.html

-JJ

···

On Wed, Mar 25, 2009 at 10:55 PM, C M <cmpython@...287...> wrote:

On Wed, Mar 25, 2009 at 9:58 PM, Jae-Joon Lee <lee.j.joon@...287...> wrote:

Ah, my bad.

Try

self.legend.parent.transAxes.inverted().transform_point(loc_in_canvas)

legend.parent points to the parent axes.

-JJ

That cleared up the error, thanks. But it is still not actually moving the
legend. The coordinates are changing as you described it:

loc position (JJ method) = [ 0.89354419 0.91002415]
motion_event.x = 436
motion_event.y = 349.0
mouse moved x = 31
mouse moved y = 3.0
loc position (JJ method) = [ 0.90698505 0.91002415]

but the legend doesn't move.

I have tried to different ways of doing the draw method.

1. self.canvas.draw()
That gives this error and does not move the legend:
ValueError: The truth value of an array with more than one element is
ambiguous. Use a.any() or a.all()

2. self.draw() (this is a method which draws the lines and legend
originally in my class).
That just does nothing, though the output is as shown above.

What do I have to do to actually show the repositioned legend?

Thanks for your patience,
C

------------------------------------------------------------------------------
_______________________________________________
Matplotlib-users mailing list
Matplotlib-users@lists.sourceforge.net
matplotlib-users List Signup and Options

Sorry, It's hard to track down what's wrong without actually running the code.

I really appreciate your patience.

Change

self.legend._loc = loc_in_norm_axes

to

self.legend._loc = tuple(loc_in_norm_axes)

and see if it works.

That did it! Thank you.

You need to call canvas.draw. However, it will draw whole figure
again. If you're concerned about speed, you may consider to use blit
method.

http://matplotlib.sourceforge.net/examples/animation/animation_blit_wx.html

I'll look into it. For the time being, this is already a very good improvement.

Thanks so much for the help. For the archives, the working runnable
sample (if you have wxPython) is attached.

Che

draggable_legend2.py (5.81 KB)

···

On Thu, Mar 26, 2009 at 12:19 AM, Jae-Joon Lee <lee.j.joon@...287...> wrote: