Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

Added a GTK3 implementation of the SubplotTool window. #1035

Closed
wants to merge 1 commit into from

4 participants

@jrjohansson

This pull request contains a GTK3 implementation of the SubplotTool window in the GTK3 backend. Currently, when selecting the GTK3 backend (GTK3Cairo or GTK3Agg) and clicking on the subplot configuration menu button, a non-GTK based GUI window appears. For consistency I think that this configuration window should follow the GTK look-and-feel if the GTK3 backend is selected. Tested on python2.7 and python3.2.

@efiring
Owner

I appreciate the work that went into this, but after testing it, I cannot support merging the PR. The Gtk3 Tool adds code that needs to be maintained, and provides no functional advantage. On the contrary, it lacks the red lines in the mpl generic tool which provide a nice reference point, and it is strangely sluggish and unresponsive. (As a side note, I think the qt4-specific tool is also less functional than the mpl generic one, and should be removed.)

@jrjohansson

OK, fair enough.

But I think your logic goes against the purpose of having toolkit specific backends in the first place.

In my opinion it is a great advantage of having a consistent GUI experience in for example the Qt and GTK backends. This is especially important when embedding a matplotlib figure in external applications. Then you really don't want configuration GUIs that appear to pop out of the 90s when clicking on the some of the toolbar buttons. So I think you'd do yourself a big disservice by removing the Qt specific improvements and discourage improvements in the GTK backend.

Also, I think the red line in the generic GUI that marks the old settings is nice, but quite marginal to the function of the subplot configuration window, and its function is in some sense duplicated by the reset button.

As for the sluggishness, I have not noticed that it is any more sluggish than the generic configuration window on my system, but this could probably be improved on.

@efiring
Owner
@WeatherGod
Collaborator

Just as a data point, I met with the OP author at the scipy sprints and discussed with him his idea. Personally, I had never used the QT4Agg backend enough to have noticed that there was a different subplot adjust tool for that backend. That tool has a bunch of additional features that are interesting (including a color-picker) and allows for modifying various other plot properties. In this way, it is more feature-ful than the default tool. Furthermore, the default tool is -- face it -- butt ugly.

That being said, I did point out a few things that were missing in the QT4Agg's tool that was in the basic tool, plus we noticed a few bugs with QT4Agg's tool. There were a few other things I didn't quite like about it and preferred in the default tool.

I commend the effort to bring gtk3 features to the gtk3agg backend, but I think the effort would be better placed in refactoring QT4Agg's tool to bring more of its features over to the default tool in a GUI-agnostic manner so that all backends can have its features. In addition, efforts can be made to create more base widgets that the user would create, but then the specific backend would substitute their native widget, much like how we do right now with Figure and Canvas objects.

@jrjohansson

Rather than putting development effort in having matplotlib roll it's own widget set, I think it would be wiser to create some design guidelines for the GUI features and then using the proven and well established GUI toolkits to implement it. With the matplotlibs own widget toolkit there is only so much you can do, and to extend this cross-platform widget collection seems outside the scope of matplotlib (especially since there already is an abundance of good GUI toolkits).

If you only want to keep the GUI features in the matplotlib's figure window to a bare minumum, taking a least common denominator approach, then it might make sense to stick with matplotlib's own widget sets (althougth this will result in an old-fashioned looking GUI). But on the other hand, if you are interested developing the GUI features in the figure window to something more useful in the future (such as the line-property editing window in the Qt backend), then allowing some extended features in the backends using full-featured widget sets sounds like a better idea to me. This would not minimize the divergence across the backends but could lead to better a matplotlib GUI experience.

@mdboom
Owner

Using something like the approach in Enthought's pyface comes to mind.

http://code.enthought.com/projects/traits_ui/

It allows writing GUI's in a non-toolkit-specific way. Being complete with something like that is a lot of work, but if we can limit it to a particular set of the most important widgets and simple layouts it may be a feasible approach.

As an aside, I have long thought that the toolbar code in the backends could be refactored so that the set of buttons is defined in a single place each backend uses that list to build the toolbar dynamically using native GUI calls.

EDIT: Of course, @pelson addressed this toolbar issue recently.

@jrjohansson

I'm closing this pull request since there seems to be very little interest in changes directly to the gtk3 backend. But thanks a lot for the feedback all of you.

@jrjohansson jrjohansson closed this
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
This page is out of date. Refresh to see the latest.
Showing with 203 additions and 30 deletions.
  1. +203 −30 lib/matplotlib/backends/backend_gtk3.py
View
233 lib/matplotlib/backends/backend_gtk3.py
@@ -27,11 +27,6 @@ def fn_name(): return sys._getframe(1).f_code.co_name
backend_version = "%s.%s.%s" % (Gtk.get_major_version(), Gtk.get_micro_version(), Gtk.get_minor_version())
_debug = False
-#_debug = True
-
-# the true dots per inch on the screen; should be display dependent
-# see http://groups.google.com/groups?q=screen+dpi+x11&hl=en&lr=&ie=UTF-8&oe=UTF-8&safe=off&selm=7077.26e81ad5%40swift.cs.tcd.ie&rnum=5 for some info about screen dpi
-PIXELS_PER_INCH = 96
cursord = {
cursors.MOVE : Gdk.Cursor.new(Gdk.CursorType.FLEUR),
@@ -343,6 +338,208 @@ def stop_event_loop(self):
FigureCanvas = FigureCanvasGTK3
+class SubplotToolGTK3(SubplotTool, Gtk.Window):
+ def __init__(self, targetfig, toolfig):
+ Gtk.Window.__init__(self, title="Subplot Configuration Tool")
+
+ if _debug: print 'SubplotToolGTK3.%s' % fn_name()
+
+ self.targetfig = targetfig
+ self.set_border_width(10)
+
+ # main vertical box
+ vbox = Gtk.Box()
+ vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
+ self.add(vbox)
+
+ # cache current configuration
+ self.stored_left = targetfig.subplotpars.left
+ self.stored_bottom = targetfig.subplotpars.bottom
+ self.stored_right = targetfig.subplotpars.right
+ self.stored_top = targetfig.subplotpars.top
+ self.stored_hspace = targetfig.subplotpars.hspace
+ self.stored_wspace = targetfig.subplotpars.wspace
+
+ # left slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("Left")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_left = Gtk.Adjustment(targetfig.subplotpars.left, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_left.connect("value-changed", self.on_adj_value_changed)
+ self.slider_left = Gtk.Scale()
+ self.slider_left.set_adjustment(self.adj_left)
+ self.slider_left.set_digits(2)
+ self.slider_left.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_left, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # bottom slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("Bottom")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_bottom = Gtk.Adjustment(targetfig.subplotpars.bottom, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_bottom.connect("value-changed", self.on_adj_value_changed)
+ self.slider_bottom = Gtk.Scale()
+ self.slider_bottom.set_adjustment(self.adj_bottom)
+ self.slider_bottom.set_digits(2)
+ self.slider_bottom.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_bottom, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # right slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("Right")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_right = Gtk.Adjustment(targetfig.subplotpars.right, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_right.connect("value-changed", self.on_adj_value_changed)
+ self.slider_right = Gtk.Scale()
+ self.slider_right.set_adjustment(self.adj_right)
+ self.slider_right.set_digits(2)
+ self.slider_right.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_right, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # top slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("Top")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_top = Gtk.Adjustment(targetfig.subplotpars.top, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_top.connect("value-changed", self.on_adj_value_changed)
+ self.slider_top = Gtk.Scale()
+ self.slider_top.set_adjustment(self.adj_top)
+ self.slider_top.set_digits(2)
+ self.slider_top.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_top, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # wspace slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("wspace")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_wspace = Gtk.Adjustment(targetfig.subplotpars.wspace, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_wspace.connect("value-changed", self.on_adj_value_changed)
+ self.slider_wspace = Gtk.Scale()
+ self.slider_wspace.set_adjustment(self.adj_wspace)
+ self.slider_wspace.set_digits(2)
+ self.slider_wspace.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_wspace, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # hspace slider
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ label = Gtk.Label()
+ label.set_size_request(100, 10)
+ label.set_text("hspace")
+ hbox.pack_start(label, False, False, 0)
+ self.adj_hspace = Gtk.Adjustment(targetfig.subplotpars.hspace, 0.0, 1.0, 0.01, 0.1, 0)
+ self.adj_hspace.connect("value-changed", self.on_adj_value_changed)
+ self.slider_hspace = Gtk.Scale()
+ self.slider_hspace.set_adjustment(self.adj_hspace)
+ self.slider_hspace.set_digits(2)
+ self.slider_hspace.set_value_pos(Gtk.PositionType.RIGHT)
+ hbox.pack_start(self.slider_hspace, True, True, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ # reset and close buttons
+ hbox = Gtk.Box()
+ hbox.set_property("orientation", Gtk.Orientation.HORIZONTAL)
+ button = Gtk.Button(" Close ")
+ button.connect("clicked", self.on_close_clicked)
+ hbox.pack_end(button, False, False, 5)
+ button = Gtk.Button(" Reset ")
+ button.connect("clicked", self.on_reset_clicked)
+ hbox.pack_end(button, False, False, 5)
+ vbox.pack_start(hbox, False, False, 5)
+
+ self.set_default_size(500, 200)
+ self.show_all()
+
+ def on_adj_value_changed(self, adj):
+
+ val = float(adj.get_value())
+
+ if adj == self.adj_left:
+
+ val_right = float(self.adj_right.get_value())
+
+ if val < val_right:
+ self.targetfig.subplots_adjust(left=val)
+ if self.drawon: self.targetfig.canvas.draw()
+ else:
+ self.adj_left.set_value(val_right)
+
+ elif adj == self.adj_bottom:
+
+ val_top = float(self.adj_top.get_value())
+
+ if val < val_top:
+ self.targetfig.subplots_adjust(bottom=val)
+ if self.drawon: self.targetfig.canvas.draw()
+ else:
+ self.adj_bottom.set_value(val_top)
+
+ elif adj == self.adj_right:
+
+ val_left = float(self.adj_left.get_value())
+
+ if val > val_left:
+ self.targetfig.subplots_adjust(right=val)
+ if self.drawon: self.targetfig.canvas.draw()
+ else:
+ self.adj_right.set_value(val_left)
+
+ elif adj == self.adj_top:
+
+ val_bottom = float(self.adj_bottom.get_value())
+
+ if val > val_bottom:
+ self.targetfig.subplots_adjust(top=val)
+ if self.drawon: self.targetfig.canvas.draw()
+ else:
+ self.adj_top.set_value(val_bottom)
+
+ elif adj == self.adj_wspace:
+ self.targetfig.subplots_adjust(wspace=val)
+ if self.drawon: self.targetfig.canvas.draw()
+
+ elif adj == self.adj_hspace:
+ self.targetfig.subplots_adjust(hspace=val)
+ if self.drawon: self.targetfig.canvas.draw()
+
+
+ def on_reset_clicked(self, widget):
+
+ self.drawon = False
+ self.adj_left.set_value(self.stored_left)
+ self.adj_bottom.set_value(self.stored_bottom)
+ self.adj_right.set_value(self.stored_right)
+ self.adj_top.set_value(self.stored_top)
+ self.adj_wspace.set_value(self.stored_wspace)
+ self.adj_hspace.set_value(self.stored_hspace)
+ self.drawon = True
+ self.targetfig.canvas.draw()
+
+ def on_close_clicked(self, widget):
+ self.destroy()
+
+SubplotTool = SubplotToolGTK3
+
class FigureManagerGTK3(FigureManagerBase):
"""
Public attributes
@@ -552,32 +749,8 @@ def save_figure(self, *args):
error_msg_gtk(str(e), parent=self)
def configure_subplots(self, button):
- toolfig = Figure(figsize=(6,3))
- canvas = self._get_canvas(toolfig)
- toolfig.subplots_adjust(top=0.9)
- tool = SubplotTool(self.canvas.figure, toolfig)
-
- w = int (toolfig.bbox.width)
- h = int (toolfig.bbox.height)
-
-
- window = Gtk.Window()
- if (window_icon):
- try: window.set_icon_from_file(window_icon)
- except:
- # we presumably already logged a message on the
- # failure of the main plot, don't keep reporting
- pass
- window.set_title("Subplot Configuration Tool")
- window.set_default_size(w, h)
- vbox = Gtk.Box()
- vbox.set_property("orientation", Gtk.Orientation.VERTICAL)
- window.add(vbox)
- vbox.show()
- canvas.show()
- vbox.pack_start(canvas, True, True, 0)
- window.show()
+ tool = SubplotToolGTK3(self.canvas.figure, None)
def _get_canvas(self, fig):
return self.canvas.__class__(fig)
Something went wrong with that request. Please try again.