Question On Implementation

Now, the question: Which is the best approach with

    > Matplotlib for this kind of interaction? Should I plot
    > every point using plot(x1, y1), plot(x2, y2) etc...? Or
    > should I use Collections (i have no idea on what to do
    > with collections)? Scattered plots? Does anyone have any
    > suggestion/pointer for me? Some examples of
    > editing/interacting with plots?

Have you seen examples/poly_editor.py? It plots a polygon and at each
vertex plots a marker. You can click on the marker and drag it and
move the vertex around, as well as insert and delete vertices. It
would be easy to modify the callback to change the marker color, size
etc, to indicate that it is selected. The example doesn't do what you
are want precisely, but does provide a lot of code illustrating how to
do these things with mpl event handling.

Note particularly the "i" functionality for inserting a vertex. To
insert a vertex your cursor must be over a line segment connecting two
vertices. From your description of needing to interact with the line
between two points, it should like this lass might be helpful.

class PolygonInteractor:
    """
    An polygon editor.

    Key-bindings

      't' toggle vertex markers on and off. When vertex markers are on,
          you can move them, delete them

      'd' delete the vertex under point

      'i' insert a vertex at point. You must be within epsilon of the
          line connecting two existing vertices

You might want to define your own "interactor" classes similar to
PolygonInteractor (eg a LineSegmentInteractor). Then the line segment
will encompass all the intelligence you need and can easily be plugged
into your graph.

If you come up with useful widgets like these, please send them our
way. Also feel free to post partially working code if you get stuck,

On a related note, to make something like this work efficiently it
helps to be able to redraw just part of a figure. The current release
of mpl has support for this with GTKAgg only, but only a few new
backend methods that need to be defined to support this. This makes
things like moving objects around the canvas much more efficiently.
It would be nice to have them for the various *Agg backends

The required methods are:

      region = canvas.copy_from_bbox(bbox)
      canvas.restore_region(region)
      canvas.blit(bbox)

region can be any backend dependent object -- the front end doesn't
use it, only passes it around. bbox is a transforms.BBox instance.
If anyone is interested in animation and knows something about Tk, WX,
Qt, FLTK, you may want to take a crack at implementing these methods.

Below is another interaction/widget which provides a Rectangle widget
that can be resized or moved around (GTKAgg only). When you click on
the edge of the rectangle the edgecolor turns red and when you release
it the color is restored. There are some bugs, but it does illustrate
more interaction code as well as provide a test script for anyone who
wants to understand how to use the three methods shown above.

import matplotlib
matplotlib.use('GTKAgg')
from pylab import *
from matplotlib.transforms import lbwh_to_bbox
from matplotlib.mlab import dist_point_to_segment

class RectangleWidget(Rectangle):
    def __init__(self, figure, *args, **kwargs):
        Rectangle.__init__(self, *args, **kwargs)
        self.figure = figure
        self.idle = True
        self.oldbox = self.get_window_extent()
        self.newbox = self.get_window_extent()
        
        self.region = self.figure.canvas.copy_from_bbox(self.oldbox)
        self.figure.canvas.mpl_connect('motion_notify_event', self.move)
        self.figure.canvas.mpl_connect('button_press_event', self.press)
        self.figure.canvas.mpl_connect('button_release_event', self.release)
        
        self.figure.draw_artist(self)
        self._resize = False
        self._drag = False

    def redraw(self):
        canvas = self.figure.canvas
        canvas.restore_region(self.region)
        self.region = canvas.copy_from_bbox(self.newbox)
        self.figure.draw_artist(self)
        canvas.blit(self.oldbox)
        canvas.blit(self.newbox)
        
    def press(self, event):
        if event.button!=1: return
        x, y = self.xy
        w, h = self.width, self.height
        # the four line segments of the rectangle
        lb = x, y
        lt = x, y+h
        rb = x+w, y
        rt = x+w, y+h
        segments = {
            'left' : (lb, lt),
            'right' : (rb, rt),
            'top' : (lt, rt),
            'bottom' : (lb, rb),
            }
        p = event.x, event.y
        near =
        for name, segment in segments.items():
            s0, s1 = segment
            d = dist_point_to_segment(p, s0, s1)
            if d<5: near.append(name)

        info = event.x, event.y, self.xy[0], self.xy[1], self.width, self.height, near
        if len(near):
            self._resize = info
            self.set_edgecolor('red')
            self.set_linewidth(2)
        else:
            self._resize = False
            if self.get_window_extent().contains(event.x, event.y):
                self._drag = info
        self.redraw()

    def release(self, event):
        if event.button!=1: return
        self._resize = False
        self._drag = False
        self.idle = True
        self.set_edgecolor('black')
        self.set_linewidth(0.5)
        self.redraw()
        
    def move(self, event):
        if not (self.idle and (self._resize or self._drag)): return

        self.idle = False
        canvas = self.figure.canvas
        if self._resize:
            exo, eyo, xo, yo, wo, ho, near = self._resize
        else:
            exo, eyo, xo, yo, wo, ho, near = self._drag

        scalex=0; scaley=0; scalew=0; scaleh=0
        if 'left' in near:
            scalex = 1
            scalew = -1
        if 'right' in near:
            scalew = 1
        if 'bottom' in near:
            scaley = 1
            scaleh = -1
        if 'top' in near:
            scaleh = 1

        if self._drag:
            scalex = 1
            scaley = 1
        
        dx = event.x - exo
        dy = event.y - eyo
        if event.button ==1:
            self.oldbox = self.get_padded_box()

            newx = xo + scalex*dx
            if scalew==0 or newx<xo+wo:
                self.xy[0] = newx
            newy = yo + scaley*dy
            if scaleh==0 or newy<yo+ho:
                self.xy[1] = newy

            neww = wo + scalew*dx
            if neww>0: self.width = neww
            
            newh = ho + scaleh*dy
            if newh>0: self.height = newh
            
            #self.xy = event.x, event.y
            self.newbox = self.get_padded_box()
            #print event.x, event.y, self.get_padded_box().get_bounds()
            self.redraw()
        self.idle = True

    def get_padded_box(self, pad=5):
        xmin = self.xy[0]-pad
        ymin = self.xy[1]-pad
        return lbwh_to_bbox(xmin, ymin, self.width+2*pad, self.height+2*pad)
        
ion()
fig = figure()

def keypress(event):
    if event.key=='n':
        widget = RectangleWidget( fig, (100,100), 20, 30)
        fig.canvas.blit()

fig.canvas.mpl_connect('key_press_event', keypress)
fig.text(0.1, 0.9, "Press n for new rectangle and then resize it or move it")
draw()
show()

Have you seen examples/poly_editor.py? It plots a polygon and at each
vertex plots a marker. You can click on the marker and drag it and
move the vertex around, as well as insert and delete vertices. It
would be easy to modify the callback to change the marker color, size
etc, to indicate that it is selected. The example doesn't do what you
are want precisely, but does provide a lot of code illustrating how to
do these things with mpl event handling.

Thanks John for the pointer! You gave me a lot of new ideas...
Just another (probably stupid) question, but I'm becoming crazy to solve
it. In my plot, I would like to have the points plotted as dots and *without*
the line that connects them. I am using Line2D from matplotlib.lines.Line2D
as:

axdata = self.fig.add_subplot(111)
self.line = Line2D(pressure, depths, marker='o', markerfacecolor='r', linestyle=None)
axdata.add_line(self.line)

But whatever option I use, the connecting line is still there... is there
a way to have only the dots?

Thank you a lot.

Andrea.

Have you seen examples/poly_editor.py? It plots a polygon and at each
vertex plots a marker. You can click on the marker and drag it and
move the vertex around, as well as insert and delete vertices. It
would be easy to modify the callback to change the marker color, size
etc, to indicate that it is selected. The example doesn't do what you
are want precisely, but does provide a lot of code illustrating how to
do these things with mpl event handling.

Thanks John for the pointer! You gave me a lot of new ideas...
Just another (probably stupid) question, but I'm becoming crazy to solve
it. In my plot, I would like to have the points plotted as dots and *without*
the line that connects them. I am using Line2D from matplotlib.lines.Line2D
as:

axdata = self.fig.add_subplot(111)
self.line = Line2D(pressure, depths, marker='o', markerfacecolor='r', linestyle=None)
axdata.add_line(self.line)

But whatever option I use, the connecting line is still there... is there
a way to have only the dots?

Thank you a lot.

Andrea.

can't you use plot() instead of Line2D? something like:

b = fig.subplot
b.plot(your_data, marker='0', linestyle=None)

donour

···

On Jul 13, 2005, at 9:57 AM, andrea_gavana@...517... wrote:

Donour Sizemore donour@...677...|
Technical Programmer & Numerical Analyst |
Economics Research Center Ph: 773-834-4399 |
University of Chicago Office: Walker 303-a |

can't you use plot() instead of Line2D? something like:

b = fig.subplot
b.plot(your_data, marker='0', linestyle=None)

I don't think that "plot" has the "set_data" attribute that I need in order
to interact with every single point of the plot, but I may be wrong.

Andrea.

I don't think that "plot" has the "set_data" attribute that I need in order
to interact with every single point of the plot, but I may be wrong.

Andrea.

Sorry, I have said something stupid. Of course I can use plot, now it's
working. However, I have another (probably basic) question. In order to
reverse the y-axis, for example, the only way I found is to get the minimum
and the maximum of my data and then do something like:

myaxes.set_ylim(mymax, mymin)

Is there a more intelligent way? I mean, in this way I have to calculate
the minimum and maximum values for every series of point I have, then lower
the minimum and increase the maximum by a small quantity in order to make
the plot looking better (and not tighten to the data) and then use set_ylim.
In the standard case (no axis reverse), is matplotlib that does all the
job and it presents me the plot, nice scaled. What I usually do in Matlab,
is something like set(myaxes, 'ydir', 'reverse'), and it does the job...
does anything similar exists for matplotlib?
Sorry if it is a very basic question.

Andrea.