wrote a selection bar widget (attached)

Dear matplotlib devels,

I wrote a selection widget someone else might find useful. The widget is based on the slider widget.

Things I implemented:
* blitting (bar still flashes once when clicked on, sorry about that!)
* Horizontal or vertical direction
* Multiple selection bars on a figure
* interaction between two bars to set a range

Things I struggled with:
* When a bar is dragged towards one of the figure edges too quickly, it gets stuck in its last recorded position on the graph (that should be the figure edge).
* Don't know how to change the mouse pointer when on top of the bar (the <-> pointer for a vertical bar would be nice)
* Don't know how to define a range in pixels around the bar (for grabbing), so used 0.01 of the full range instead
* Haven't implemented dragging when a bar reaches the other bar (set with set_barmin or set_barmax) but hasn't reached the limit of its range.

How to test:
have mplbar.py and test_bar.py in the same directory, launch test_bar.py

Hope this all makes sense (somehow I doubt it). And like I said, if you guys like it or want to adapt it further, please do, the code this is based on is yours after all!

Regards,
Egor

mplbar.zip (2.33 KB)

Dear matplotlib devels,

I wrote a selection widget someone else might find useful. The widget is
based on the slider widget.>
Things I implemented:
* blitting (bar still flashes once when clicked on, sorry about that!)
* Horizontal or vertical direction
* Multiple selection bars on a figure
* interaction between two bars to set a range

I think this is a useful tool, but I would like to see a few
enhancements, some of which you mention below.

Things I struggled with:
* When a bar is dragged towards one of the figure edges too quickly, it gets
stuck in its last recorded position on the graph (that should be the figure
edge).

I think the cause of this is when your mouse leaves the axes, the axes
stops processing events, so it no longer gets updates to move the bar.
I'm not sure what the right solution is though.

* Don't know how to change the mouse pointer when on top of the bar (the <->
pointer for a vertical bar would be nice)

In backend bases there is a cursors enum:::

    In [5]: import matplotlib.backend_bases as bb

    In [7]: bb.cursors.HAND
    Out[7]: 0

    In [8]: bb.cursors.MOVE
    Out[8]: 3

and you can use the canvas toolbar to change the cursor

    In [11]: fig.canvas.toolbar.set_cursor(bb.cursors.MOVE)

* Don't know how to define a range in pixels around the bar (for grabbing),
so used 0.01 of the full range instead

Read the event handling and picking tutorial at
http://matplotlib.sourceforge.net/users/event_handling.html if you
haven't already. I suggest doing the hit testing in figure rather
than data coordinates. So instead of

   val = event.xdata
   if val < self.val + self.radius ...

do something like the following::

    def _update(self, event)

      # store the line location in pixel space
      self.linex = event.x

    def _press(event):

        if abs(event.x-self.linex)<=5: # 5 pixels
            # hit!

* Haven't implemented dragging when a bar reaches the other bar (set with
set_barmin or set_barmax) but hasn't reached the limit of its range.

How to test:
have mplbar.py and test_bar.py in the same directory, launch test_bar.py

Hope this all makes sense (somehow I doubt it). And like I said, if you guys
like it or want to adapt it further, please do, the code this is based on is
yours after all!

With some additional cleanup along the lines of the above, I would be
happy to accept this. Please document every method (I realize some
exisint code does not meet this standard but we are trying to
improve). Also, please use the rest conventions when documenting new
code; the widgets code has not been ported over but a lot has -- see
http://matplotlib.sourceforge.net/devel/documenting_mpl.html

One feature you may want to consider. I think it would be nice to
have color coded ticks, or possibly a static hspan along the regions
where the bar can slide. Eg, see the following minor modification to
test_bar::

    bar1 = mplbar.Bar(ax1,valrange=[10,60],useblit=True, lw=2, color='g')
    ax1.axvspan(10, 60, ymin=0, ymax=0.05, facecolor='g', alpha=0.2)

    #range 10 to 60, init value set to 60, use blit, blue
    bar2 = mplbar.Bar(ax1,valrange=[10,60],valinit=60,useblit=True,
lw=2, color='b')
    ax1.axvspan(10, 60, ymin=0.95, ymax=1.0, facecolor='blue', alpha=0.2)
    #range 30 to +inf (None = no bound), default set to 30, use blit,
horizontal line, red
    bar3 = mplbar.Bar(ax1,valrange=[30,None],useblit=True,
direction='horizontal', lw=2, color='r')
    ax1.axhspan(30, 2*npts, xmin=0.95, xmax=1.0, facecolor='red', alpha=0.2)

Of course the use can always add this, so I am not sure if it is worth
building in... but support for something like this via a kwar might be
nice, eg

   Bar(blah, ..., showrange=True, rangespan=[0.95, 1.0])

JDH

···

On Wed, Nov 26, 2008 at 5:34 AM, Egor Zindy <ezindy@...149...> wrote:

> Things I struggled with:
> * When a bar is dragged towards one of the figure edges too quickly,
it gets
> stuck in its last recorded position on the graph (that should be the
figure
> edge).

I think the cause of this is when your mouse leaves the axes, the axes
stops processing events, so it no longer gets updates to move the bar.
I'm not sure what the right solution is though.

I'm pretty sure this is exactly what's happening. This is also annoying when you're trying to use a zoom rectangle and want to start at some point in the plot and drag it to the boundary. We've been planning on putting some time in to fix this this but just haven't been able to make the time yet.

I think one possible solution is that anytime the mouse moves outside the axes, it should get one last event that is at the boundary which would require tracking which axes the last event was sent in. We haven't tried to see how hard that is to implement thought...

Ted

probably fairly easy using a class level attr in the MouseEvent.
Another possibility would be to add support for figure_enter_event,
figure_leave_event, axes_enter_event, axes_leave_event. The user code
would have to separately process the axes_leave_event but this might
be the cleanest solution. I am happy to add support for these ...

JDH

···

On Wed, Nov 26, 2008 at 10:41 AM, Drain, Theodore R <theodore.r.drain@...179...> wrote:

I'm pretty sure this is exactly what's happening. This is also annoying when you're trying to use a zoom rectangle and want to start at some point in the plot and drag it to the boundary. We've been planning on putting some time in to fix this this but just haven't been able to make the time yet.

I think one possible solution is that anytime the mouse moves outside the axes, it should get one last event that is at the boundary which would require tracking which axes the last event was sent in. We haven't tried to see how hard that is to implement thought...

John Hunter wrote:

I'm pretty sure this is exactly what's happening. This is also annoying when you're trying to use a zoom rectangle and want to start at some point in the plot and drag it to the boundary. We've been planning on putting some time in to fix this this but just haven't been able to make the time yet.

I think one possible solution is that anytime the mouse moves outside the axes, it should get one last event that is at the boundary which would require tracking which axes the last event was sent in. We haven't tried to see how hard that is to implement thought...
    
probably fairly easy using a class level attr in the MouseEvent.
Another possibility would be to add support for figure_enter_event,
figure_leave_event, axes_enter_event, axes_leave_event. The user code
would have to separately process the axes_leave_event but this might
be the cleanest solution. I am happy to add support for these ...
  
John,

in my code, I implemented tests like:

       if self.valmin is not None:
           if val < self.valmin:
               val = self.valmin

which makes limit handling better than that of the Slider class this was based on:

       if self.slidermin is not None:
           if val<=self.slidermin.val: return

Theodore nailed what is happening. At the moment, tests like the one I used cannot be implemented because event.xdata returns None when outside the boundaries. This cannot be compared with ax.get_xlim().

Maybe a value should be returned even when outside. the event.inaxes should be enough to know if the mouse pointer is inside or outside.

Although that would probably break a few things that at the moment depend on event.xdata being None when outside!

···

On Wed, Nov 26, 2008 at 10:41 AM, Drain, Theodore R > <theodore.r.drain@...179...> wrote:

I just committed axes/figure enter/leave event notifications, with
example in examples/event_handling/figure_axes_enter_leave.py. If you
register for a leave event, you will get passed the last event that
was in your axes/figure before it left

Everything is working well with one caveat -- a figure leave event is
only triggered when you enter a new figure, not simply when you leave
the figure. This is because I just used the existing mpl
LocationEvents to support the new events, and none of these are
created when you are not over a canvas. To properly handle the figure
leave events, we will need to tap into the underlying GUI leave
events. The axes leave events work fine, with the one caveat that if
the axes region is [0,0,1,1] you may not see the leave event for the
same reason you do not see the figure leave event until you are over
another canvas.

JDH

···

On Wed, Nov 26, 2008 at 10:59 AM, John Hunter <jdh2358@...149...> wrote:

probably fairly easy using a class level attr in the MouseEvent.
Another possibility would be to add support for figure_enter_event,
figure_leave_event, axes_enter_event, axes_leave_event. The user code
would have to separately process the axes_leave_event but this might
be the cleanest solution. I am happy to add support for these ...

John Hunter wrote:

probably fairly easy using a class level attr in the MouseEvent.
Another possibility would be to add support for figure_enter_event,
figure_leave_event, axes_enter_event, axes_leave_event. The user code
would have to separately process the axes_leave_event but this might
be the cleanest solution. I am happy to add support for these ...
    
I just committed axes/figure enter/leave event notifications, with
example in examples/event_handling/figure_axes_enter_leave.py. If you
register for a leave event, you will get passed the last event that
was in your axes/figure before it left

Everything is working well with one caveat -- a figure leave event is
only triggered when you enter a new figure, not simply when you leave
the figure. This is because I just used the existing mpl
LocationEvents to support the new events, and none of these are
created when you are not over a canvas. To properly handle the figure
leave events, we will need to tap into the underlying GUI leave
events. The axes leave events work fine, with the one caveat that if
the axes region is [0,0,1,1] you may not see the leave event for the
same reason you do not see the figure leave event until you are over
another canvas.

JDH
  

John,

thank you, I'll have to check out the repository to see how I can use the changes.

Just a thought: a figure_leave / figure_enter event could be triggered by comparing the currently held axis with event.inaxes (that's what Slider and my bar widget do in _update() ).

Cheers,
Egor

···

On Wed, Nov 26, 2008 at 10:59 AM, John Hunter <jdh2358@...149...> wrote:

That's exactly how its done. The problem I referred to above (which
will not affect your use case since you are leaving the axes) occurs
when you want to connect to a figure leave event and move your mouse
to a non-mpl window. Then we get no events to compare to the prior.

JDH

···

On Wed, Nov 26, 2008 at 11:57 AM, Egor Zindy <ezindy@...149...> wrote:

thank you, I'll have to check out the repository to see how I can use the
changes.

Just a thought: a figure_leave / figure_enter event could be triggered by
comparing the currently held axis with event.inaxes (that's what Slider and
my bar widget do in _update() ).

Hi John,

John Hunter wrote:

thank you, I'll have to check out the repository to see how I can use the
changes.

Just a thought: a figure_leave / figure_enter event could be triggered by
comparing the currently held axis with event.inaxes (that's what Slider and
my bar widget do in _update() ).
    
That's exactly how its done. The problem I referred to above (which
will not affect your use case since you are leaving the axes) occurs
when you want to connect to a figure leave event and move your mouse
to a non-mpl window. Then we get no events to compare to the prior.

JDH
  
The new version attached that fixes a few of the things I was unhappy about:
* radius for clicking the bar now in pixels
* case when mouse pointer is outside the graph now handled correctly.
* commented the methods a bit more (still working on that)

Both these rely on the following two functions:

    #convert from x to xdata
    print self.ax.transData.inverted().transform_point((event.x, event.y))

    #convert from xdata to x
    print self.ax.transData.transform_point((event.xdata, event.ydata))

event.x always returns a value, so I used that to "fix" xdata when the cursor is outside the graph

Things I have struggled with (this time):
* there is a toobar.set_cursor() but no toolbar.get_cursor() which would be interesting to have to set the cursor back to what it should be (rather than arbitrary POINTER type).
* more cursor types would be nice (could do with size_ver and size_hor in http://wiki.openusability.org/guidelines/index.php/Design_and_Layout:Visual_Design:Cursors).
* took a long time to check out matplotlib from SVN, have yet to check the new events out.
* a nice name? (been struggling with that a lot lately!) Is "Bar" OK? or maybe "SelectionBar"?

Regards,
Egor

mplbar.zip (2.96 KB)

···

On Wed, Nov 26, 2008 at 11:57 AM, Egor Zindy <ezindy@...149...> wrote:

Jeff,
Is it possible to install the basemap data into a different directory?

I'm trying to set up a tool delivery layout for our users that allows me to rapidly update packages that tend to change. We have a core set of software that our code is built on and it's a lot of work for us to update that and test it on every platform. What I'm doing is separating out the packages of that core set that we generally need to update at a higher rate (currently that's matplotlib and sphinx) because they're under more active development.

The largest piece (by an order of magnitude) of these active tools is the basemap data. What I'd really like to do is install the basemap data with the core set of tools and then tell basemap where it's located instead of having to redeliver this large chunk of unchanging data every time we do a bug fix update to MPL. Perhaps by setting an environment variable that takes precedence over the default location?

Any thoughts? I could put a patch together for it if you think it's worthwhile.

Thanks,
Ted

Drain, Theodore R wrote:

Jeff,
Is it possible to install the basemap data into a different directory?

I'm trying to set up a tool delivery layout for our users that allows me to rapidly update packages that tend to change. We have a core set of software that our code is built on and it's a lot of work for us to update that and test it on every platform. What I'm doing is separating out the packages of that core set that we generally need to update at a higher rate (currently that's matplotlib and sphinx) because they're under more active development.

The largest piece (by an order of magnitude) of these active tools is the basemap data. What I'd really like to do is install the basemap data with the core set of tools and then tell basemap where it's located instead of having to redeliver this large chunk of unchanging data every time we do a bug fix update to MPL. Perhaps by setting an environment variable that takes precedence over the default location?

Any thoughts? I could put a patch together for it if you think it's worthwhile.

Thanks,
Ted

-

Ted: I've changed Basemap to look for it's data in BASEMAPDATA, and if that env var is not set to use the default location (svn revision 6646). Will that work for you? Do you also want an option in setup.py to not install the data?

I'm a bit worried that this won't work in Windows - do you have Windows users?

-Jeff

···

--
Jeffrey S. Whitaker Phone : (303)497-6313
NOAA/OAR/CDC R/PSD1 FAX : (303)497-6449
325 Broadway Boulder, CO, USA 80305-3328

Nope - no windows users (yet - but we'll test it first if we ever have to deliver there).

That will work perfectly - Thanks!!!

Ted

···

-----Original Message-----
From: Jeff Whitaker [mailto:jswhit@…196…]
Sent: Wednesday, December 17, 2008 5:03 AM
To: Drain, Theodore R
Cc: matplotlib-devel@lists.sourceforge.net
Subject: Re: [matplotlib-devel] Basemap question for Jeff: different
data directory?

Drain, Theodore R wrote:
> Jeff,
> Is it possible to install the basemap data into a different
directory?
>
> I'm trying to set up a tool delivery layout for our users that allows
me to rapidly update packages that tend to change. We have a core set
of software that our code is built on and it's a lot of work for us to
update that and test it on every platform. What I'm doing is
separating out the packages of that core set that we generally need to
update at a higher rate (currently that's matplotlib and sphinx)
because they're under more active development.
>
> The largest piece (by an order of magnitude) of these active tools is
the basemap data. What I'd really like to do is install the basemap
data with the core set of tools and then tell basemap where it's
located instead of having to redeliver this large chunk of unchanging
data every time we do a bug fix update to MPL. Perhaps by setting an
environment variable that takes precedence over the default location?
>
> Any thoughts? I could put a patch together for it if you think it's
worthwhile.
>
> Thanks,
> Ted
>
> -

Ted: I've changed Basemap to look for it's data in BASEMAPDATA, and if
that env var is not set to use the default location (svn revision
6646). Will that work for you? Do you also want an option in
setup.py
to not install the data?

I'm a bit worried that this won't work in Windows - do you have Windows
users?

-Jeff

--
Jeffrey S. Whitaker Phone : (303)497-6313
NOAA/OAR/CDC R/PSD1 FAX : (303)497-6449
325 Broadway Boulder, CO, USA 80305-3328