MEP9: Global interaction manager#

Add a global manager for all user interactivity with artists; make anyartist resizeable, moveable, highlightable, and selectable as desiredby the user.

Status#

Discussion

Branches and Pull requests#

dhyams/matplotlib

Abstract#

The goal is to be able to interact with matplotlib artists in a verysimilar way as drawing programs do. When appropriate, the user shouldbe able to move, resize, or select an artist that is already on thecanvas. Of course, the script writer is ultimately in control ofwhether an artist is able to be interacted with, or whether it isstatic.

This code to do this has already been privately implemented andtested, and would need to be migrated from its current "mixin"implementation, to a bona-fide part of matplotlib.

The end result would be to have four new keywords available tomatplotlib.artist.Artist: _moveable_, _resizeable_, _selectable_, and_highlightable_. Setting any one of these keywords to True wouldactivate interactivity for that artist.

In effect, this MEP is a logical extension of event handling inmatplotlib; matplotlib already supports "low level" interactions likeleft mouse presses, a key press, or similar. The MEP extends thesupport to the logical level, where callbacks are performed on theartists when certain interactive gestures from the user are detected.

Detailed description#

This new functionality would be used to allow the end-user to betterinteract with the graph. Many times, a graph is almost what the userwants, but a small repositioning and/or resizing of components isnecessary. Rather than force the user to go back to the script totrial-and-error the location, and simple drag and drop would beappropriate.

Also, this would better support applications that use matplotlib;here, the end-user has no reasonable access or desire to edit theunderlying source in order to fine-tune a plot. Here, if matplotliboffered the capability, one could move or resize artists on the canvasto suit their needs. Also, the user should be able to highlight (witha mouse over) an artist, and select it with a double-click, if theapplication supports that sort of thing. In this MEP, we also want tosupport the highlighting and selection natively; it is up toapplication to handle what happens when the artist is selected. Atypical handling would be to display a dialog to edit the propertiesof the artist.

In the future, as well (this is not part of this MEP), matplotlibcould offer backend-specific property dialogs for each artist, whichare raised on artist selection. This MEP would be a necessarystepping stone for that sort of capability.

There are currently a few interactive capabilities in matplotlib(e.g. legend.draggable()), but they tend to be scattered and are notavailable for all artists. This MEP seeks to unify the interactiveinterface and make it work for all artists.

The current MEP also includes grab handles for resizing artists, andappropriate boxes drawn when artists are moved or resized.

Implementation#

  • Add appropriate methods to the "tree" of artists so that theinteractivity manager has a consistent interface for theinteractivity manager to deal with. The proposed methods to add tothe artists, if they are to support interactivity, are:

    • get_pixel_position_ll(self): get the pixel position of the lowerleft corner of the artist's bounding box

    • get_pixel_size(self): get the size of the artist's bounding box,in pixels

    • set_pixel_position_and_size(self,x,y,dx,dy): set the new size ofthe artist, such that it fits within the specified bounding box.

  • add capability to the backends to 1) provide cursors, since theseare needed for visual indication of moving/resizing, and 2) providea function that gets the current mouse position

  • Implement the manager. This has already been done privately (bydhyams) as a mixin, and has been tested quite a bit. The goal wouldbe to move the functionality of the manager into the artists so thatit is in matplotlib properly, and not as a "monkey patch" as Icurrently have it coded.

Current summary of the mixin#

(Note that this mixin is for now just private code, but can be addedto a branch obviously)

InteractiveArtistMixin:

Mixin class to make any generic object that is drawn on a matplotlibcanvas moveable and possibly resizeable. The Powerpoint model isfollowed as closely as possible; not because I'm enamoured withPowerpoint, but because that's what most people understand. An artistcan also be selectable, which means that the artist will receive theon_activated() callback when double clicked. Finally, an artist canbe highlightable, which means that a highlight is drawn on the artistwhenever the mouse passes over. Typically, highlightable artists willalso be selectable, but that is left up to the user. So, basicallythere are four attributes that can be set by the user on a per-artistbasis:

  • highlightable

  • selectable

  • moveable

  • resizeable

To be moveable (draggable) or resizeable, the object that is thetarget of the mixin must support the following protocols:

  • get_pixel_position_ll(self)

  • get_pixel_size(self)

  • set_pixel_position_and_size(self,x,y,sx,sy)

Note that nonresizeable objects are free to ignore the sx and syparameters. To be highlightable, the object that is the target of themixin must also support the following protocol:

  • get_highlight(self)

Which returns a list of artists that will be used to draw the highlight.

If the object that is the target of the mixin is not an matplotlibartist, the following protocols must also be implemented. Doing so isusually fairly trivial, as there has to be an artistsomewhere thatis being drawn. Typically your object would just route these calls tothat artist.

  • get_figure(self)

  • get_axes(self)

  • contains(self,event)

  • set_animated(self,flag)

  • draw(self,renderer)

  • get_visible(self)

The following notifications are called on the artist, and the artistcan optionally implement these.

  • on_select_begin(self)

  • on_select_end(self)

  • on_drag_begin(self)

  • on_drag_end(self)

  • on_activated(self)

  • on_highlight(self)

  • on_right_click(self,event)

  • on_left_click(self,event)

  • on_middle_click(self,event)

  • on_context_click(self,event)

  • on_key_up(self,event)

  • on_key_down(self,event)

The following notifications are called on the canvas, if nointeractive artist handles the event:

  • on_press(self,event)

  • on_left_click(self,event)

  • on_middle_click(self,event)

  • on_right_click(self,event)

  • on_context_click(self,event)

  • on_key_up(self,event)

  • on_key_down(self,event)

The following functions, if present, can be used to modify thebehavior of the interactive object:

  • press_filter(self,event) # determines if the object wants to havethe press event routed to it

  • handle_unpicked_cursor() # can be used by the object to set a cursoras the cursor passes over the object when it is unpicked.

Supports multiple canvases, maintaining a drag lock, motion notifier,and a global "enabled" flag per canvas. Supports fixed aspect ratioresizings by holding the shift key during the resize.

Known problems:

  • Zorder is not obeyed during the selection/drag operations. Becauseof the blit technique used, I do not believe this can be fixed. Theonly way I can think of is to search for all artists that have azorder greater then me, set them all to animated, and then redrawthem all on top during each drag refresh. This might be very slow;need to try.

  • the mixin only works for wx backends because of two things: 1) thecursors are hardcoded, and 2) there is a call towx.GetMousePosition() Both of these shortcomings are reasonablyfixed by having each backend supply these things.

Backward compatibility#

No problems with backward compatibility, although once this is inplace, it would be appropriate to obsolete some of the existinginteractive functions (like legend.draggable())

Alternatives#

None that I know of.