diff --git a/doc/api/api_changes/2016-08-02-toggle-grids.rst b/doc/api/api_changes/2016-08-02-toggle-grids.rst new file mode 100644 index 000000000000..fb70385afd0d --- /dev/null +++ b/doc/api/api_changes/2016-08-02-toggle-grids.rst @@ -0,0 +1,9 @@ +Improved toggling of the axes grids +----------------------------------- +The `g` key binding now switches the states of the `x` and `y` grids +independently (by cycling through all four on/off combinations). + +The new `G` key binding switches the states of the minor grids. + +Both bindings are disabled if only a subset of the grid lines (in either +direction) is visible, to avoid making irreversible changes to the figure. diff --git a/doc/users/navigation_toolbar.rst b/doc/users/navigation_toolbar.rst index caa2387e0089..3722f2efe615 100644 --- a/doc/users/navigation_toolbar.rst +++ b/doc/users/navigation_toolbar.rst @@ -99,7 +99,8 @@ Close all plots **shift** + **w** Constrain pan/zoom to x axis hold **x** when panning/zooming with mouse Constrain pan/zoom to y axis hold **y** when panning/zooming with mouse Preserve aspect ratio hold **CONTROL** when panning/zooming with mouse -Toggle grid **g** when mouse is over an axes +Toggle major grids **g** when mouse is over an axes +Toggle minor grids **G** when mouse is over an axes Toggle x axis scale (log/linear) **L** or **k** when mouse is over an axes Toggle y axis scale (log/linear) **l** when mouse is over an axes ================================== ================================================= diff --git a/lib/matplotlib/backend_bases.py b/lib/matplotlib/backend_bases.py index d5335dfed6f0..41d908367968 100644 --- a/lib/matplotlib/backend_bases.py +++ b/lib/matplotlib/backend_bases.py @@ -2482,9 +2482,10 @@ def key_press_handler(event, canvas, toolbar=None): save_keys = rcParams['keymap.save'] quit_keys = rcParams['keymap.quit'] grid_keys = rcParams['keymap.grid'] + grid_minor_keys = rcParams['keymap.grid_minor'] toggle_yscale_keys = rcParams['keymap.yscale'] toggle_xscale_keys = rcParams['keymap.xscale'] - all = rcParams['keymap.all_axes'] + all_keys = rcParams['keymap.all_axes'] # toggle fullscreen mode (default key 'f') if event.key in fullscreen_keys: @@ -2524,33 +2525,72 @@ def key_press_handler(event, canvas, toolbar=None): return # these bindings require the mouse to be over an axes to trigger + def _get_uniform_gridstate(ticks): + # Return True/False if all grid lines are on or off, None if they are + # not all in the same state. + if all(tick.gridOn for tick in ticks): + return True + elif not any(tick.gridOn for tick in ticks): + return False + else: + return None - # switching on/off a grid in current axes (default key 'g') + ax = event.inaxes + # toggle major grids in current axes (default key 'g') + # Both here and below (for 'G'), we do nothing is the grids are not in a + # uniform state, to avoid messing up user customization. if event.key in grid_keys: - event.inaxes.grid() - canvas.draw() + x_state = _get_uniform_gridstate(ax.xaxis.majorTicks) + y_state = _get_uniform_gridstate(ax.yaxis.majorTicks) + cycle = [(False, False), (True, False), (True, True), (False, True)] + try: + x_state, y_state = ( + cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) + except ValueError: + # Exclude major grids not in a uniform state. + pass + else: + ax.grid(x_state, which="major", axis="x") + ax.grid(y_state, which="major", axis="y") + canvas.draw_idle() + # toggle major and minor grids in current axes (default key 'G') + if (event.key in grid_minor_keys + # Exclude major grids not in a uniform state. + and None not in [_get_uniform_gridstate(ax.xaxis.majorTicks), + _get_uniform_gridstate(ax.yaxis.majorTicks)]): + x_state = _get_uniform_gridstate(ax.xaxis.minorTicks) + y_state = _get_uniform_gridstate(ax.yaxis.minorTicks) + cycle = [(False, False), (True, False), (True, True), (False, True)] + try: + x_state, y_state = ( + cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) + except ValueError: + # Exclude minor grids not in a uniform state. + pass + else: + ax.grid(x_state, which="both", axis="x") + ax.grid(y_state, which="both", axis="y") + canvas.draw_idle() # toggle scaling of y-axes between 'log and 'linear' (default key 'l') elif event.key in toggle_yscale_keys: - ax = event.inaxes scale = ax.get_yscale() if scale == 'log': ax.set_yscale('linear') - ax.figure.canvas.draw() + ax.figure.canvas.draw_idle() elif scale == 'linear': ax.set_yscale('log') - ax.figure.canvas.draw() + ax.figure.canvas.draw_idle() # toggle scaling of x-axes between 'log and 'linear' (default key 'k') elif event.key in toggle_xscale_keys: - ax = event.inaxes scalex = ax.get_xscale() if scalex == 'log': ax.set_xscale('linear') - ax.figure.canvas.draw() + ax.figure.canvas.draw_idle() elif scalex == 'linear': ax.set_xscale('log') - ax.figure.canvas.draw() + ax.figure.canvas.draw_idle() - elif (event.key.isdigit() and event.key != '0') or event.key in all: + elif (event.key.isdigit() and event.key != '0') or event.key in all_keys: # keys in list 'all' enables all axes (default key 'a'), # otherwise if key is a number only enable this particular axes # if it was the axes, where the event was raised diff --git a/lib/matplotlib/backend_tools.py b/lib/matplotlib/backend_tools.py index c896689bc610..73db242638cd 100644 --- a/lib/matplotlib/backend_tools.py +++ b/lib/matplotlib/backend_tools.py @@ -399,24 +399,74 @@ def trigger(self, sender, event, data=None): a.set_navigate(i == n) -class ToolGrid(ToolToggleBase): - """Tool to toggle the grid of the figure""" +class _ToolGridBase(ToolBase): + """Common functionality between ToolGrid and ToolMinorGrid.""" - description = 'Toogle Grid' - default_keymap = rcParams['keymap.grid'] + _cycle = [(False, False), (True, False), (True, True), (False, True)] def trigger(self, sender, event, data=None): - if event.inaxes is None: + ax = event.inaxes + if ax is None: return - ToolToggleBase.trigger(self, sender, event, data) + try: + x_state, y_state, which = self._get_next_grid_states(ax) + except ValueError: + pass + else: + ax.grid(x_state, which=which, axis="x") + ax.grid(y_state, which=which, axis="y") + ax.figure.canvas.draw_idle() - def enable(self, event): - event.inaxes.grid(True) - self.figure.canvas.draw_idle() + @staticmethod + def _get_uniform_grid_state(ticks): + """ + Check whether all grid lines are in the same visibility state. - def disable(self, event): - event.inaxes.grid(False) - self.figure.canvas.draw_idle() + Returns True/False if all grid lines are on or off, None if they are + not all in the same state. + """ + if all(tick.gridOn for tick in ticks): + return True + elif not any(tick.gridOn for tick in ticks): + return False + else: + return None + + +class ToolGrid(_ToolGridBase): + """Tool to toggle the major grids of the figure""" + + description = 'Toogle major grids' + default_keymap = rcParams['keymap.grid'] + + def _get_next_grid_states(self, ax): + x_state, y_state = map(self._get_uniform_grid_state, + [ax.xaxis.majorTicks, ax.yaxis.majorTicks]) + cycle = self._cycle + # Bail out (via ValueError) if major grids are not in a uniform state. + x_state, y_state = ( + cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) + return x_state, y_state, "major" + + +class ToolMinorGrid(_ToolGridBase): + """Tool to toggle the major and minor grids of the figure""" + + description = 'Toogle major and minor grids' + default_keymap = rcParams['keymap.grid_minor'] + + def _get_next_grid_states(self, ax): + if None in map(self._get_uniform_grid_state, + [ax.xaxis.majorTicks, ax.yaxis.majorTicks]): + # Bail out if major grids are not in a uniform state. + raise ValueError + x_state, y_state = map(self._get_uniform_grid_state, + [ax.xaxis.minorTicks, ax.yaxis.minorTicks]) + cycle = self._cycle + # Bail out (via ValueError) if minor grids are not in a uniform state. + x_state, y_state = ( + cycle[(cycle.index((x_state, y_state)) + 1) % len(cycle)]) + return x_state, y_state, "both" class ToolFullScreen(ToolToggleBase): @@ -944,6 +994,7 @@ def _mouse_move(self, event): 'subplots': 'ToolConfigureSubplots', 'save': 'ToolSaveFigure', 'grid': ToolGrid, + 'grid_minor': ToolMinorGrid, 'fullscreen': ToolFullScreen, 'quit': ToolQuit, 'quit_all': ToolQuitAll, diff --git a/lib/matplotlib/rcsetup.py b/lib/matplotlib/rcsetup.py index ec65a13bc273..e02b85e78e3b 100644 --- a/lib/matplotlib/rcsetup.py +++ b/lib/matplotlib/rcsetup.py @@ -1313,6 +1313,7 @@ def validate_animation_writer_path(p): 'keymap.quit': [['ctrl+w', 'cmd+w', 'q'], validate_stringlist], 'keymap.quit_all': [['W', 'cmd+W', 'Q'], validate_stringlist], 'keymap.grid': [['g'], validate_stringlist], + 'keymap.grid_minor': [['G'], validate_stringlist], 'keymap.yscale': [['l'], validate_stringlist], 'keymap.xscale': [['k', 'L'], validate_stringlist], 'keymap.all_axes': [['a'], validate_stringlist], diff --git a/matplotlibrc.template b/matplotlibrc.template index 3889f75f7c5a..faf5d2de6876 100644 --- a/matplotlibrc.template +++ b/matplotlibrc.template @@ -584,7 +584,8 @@ backend : $TEMPLATE_BACKEND #keymap.zoom : o # zoom mnemonic #keymap.save : s # saving current figure #keymap.quit : ctrl+w, cmd+w # close the current figure -#keymap.grid : g # switching on/off a grid in current axes +#keymap.grid : g # switching on/off major grids in current axes +#keymap.grid_minor : G # switching on/off minor grids in current axes #keymap.yscale : l # toggle scaling of y-axes ('log'/'linear') #keymap.xscale : L, k # toggle scaling of x-axes ('log'/'linear') #keymap.all_axes : a # enable all axes