From 7b59aea8f9a7f89bf0725624b4d44ab2a0feefaf Mon Sep 17 00:00:00 2001 From: Will Handley Date: Fri, 9 Mar 2018 22:42:03 +0000 Subject: [PATCH 1/9] Adjusted matplotlib.widgets.Slider to have optional vertical orientation. The slider widget now takes an optional argument (orientation) with values 'h' or 'v, which defaults to 'h'. Argument checking is in keeping with the existing code, and the actual changes to the source are minimal, replacing hspans, hlines and xdata with a if switch to vspans and vlines and ydata. --- lib/matplotlib/widgets.py | 60 ++++++++++++++++++++++++++++++--------- 1 file changed, 46 insertions(+), 14 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index ca6f544ada4d..52e05902896b 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -272,7 +272,7 @@ class Slider(AxesWidget): """ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', closedmin=True, closedmax=True, slidermin=None, - slidermax=None, dragging=True, valstep=None, **kwargs): + slidermax=None, dragging=True, valstep=None, orientation='h', **kwargs): """ Parameters ---------- @@ -314,6 +314,9 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valstep : float, optional, default: None If given, the slider will snap to multiples of `valstep`. + orientation : str, optional, default: 'h' + If 'h' the slider is horizontal. If 'v' it is vertical + Notes ----- Additional kwargs are passed on to ``self.poly`` which is the @@ -329,6 +332,11 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', if slidermax is not None and not hasattr(slidermax, 'val'): raise ValueError("Argument slidermax ({}) has no 'val'" .format(type(slidermax))) + if orientation not in ['h', 'v']: + raise ValueError("Argument orientation ({}) must be either 'h' or 'v'" + .format(orientation)) + + self.orientation = orientation self.closedmin = closedmin self.closedmax = closedmax self.slidermin = slidermin @@ -342,12 +350,19 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valinit = valmin self.val = valinit self.valinit = valinit - self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs) - self.vline = ax.axvline(valinit, 0, 1, color='r', lw=1) + if orientation is 'v': + self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs) + self.hline = ax.axhline(valinit, 0, 1, color='r', lw=1) + else: + self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs) + self.vline = ax.axvline(valinit, 0, 1, color='r', lw=1) self.valfmt = valfmt ax.set_yticks([]) - ax.set_xlim((valmin, valmax)) + if orientation is 'v': + ax.set_ylim((valmin, valmax)) + else: + ax.set_xlim((valmin, valmax)) ax.set_xticks([]) ax.set_navigate(False) @@ -355,14 +370,24 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.connect_event('button_release_event', self._update) if dragging: self.connect_event('motion_notify_event', self._update) - self.label = ax.text(-0.02, 0.5, label, transform=ax.transAxes, - verticalalignment='center', - horizontalalignment='right') + if orientation is 'v': + self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes, + verticalalignment='bottom', + horizontalalignment='center') + + self.valtext = ax.text(0.5, -0.02, valfmt % valinit, + transform=ax.transAxes, + verticalalignment='top', + horizontalalignment='center') + else: + self.label = ax.text(-0.02, 0.5, label, transform=ax.transAxes, + verticalalignment='center', + horizontalalignment='right') - self.valtext = ax.text(1.02, 0.5, valfmt % valinit, - transform=ax.transAxes, - verticalalignment='center', - horizontalalignment='left') + self.valtext = ax.text(1.02, 0.5, valfmt % valinit, + transform=ax.transAxes, + verticalalignment='center', + horizontalalignment='left') self.cnt = 0 self.observers = {} @@ -416,7 +441,10 @@ def _update(self, event): self.drag_active = False event.canvas.release_mouse(self.ax) return - val = self._value_in_bounds(event.xdata) + if self.orientation is 'v': + val = self._value_in_bounds(event.ydata) + else: + val = self._value_in_bounds(event.xdata) if (val is not None) and (val != self.val): self.set_val(val) @@ -429,8 +457,12 @@ def set_val(self, val): val : float """ xy = self.poly.xy - xy[2] = val, 1 - xy[3] = val, 0 + if self.orientation is 'v': + xy[1] = 0, val + xy[2] = 1, val + else: + xy[2] = val, 1 + xy[3] = val, 0 self.poly.xy = xy self.valtext.set_text(self.valfmt % val) if self.drawon: From 0f57e7ec8405e5c07410b5bd38feedcccec1d8b2 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Fri, 9 Mar 2018 23:28:42 +0000 Subject: [PATCH 2/9] Changed 'h' and 'v' to 'horizontal' and 'vertical' --- lib/matplotlib/widgets.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 52e05902896b..bad1a48f3631 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -272,7 +272,8 @@ class Slider(AxesWidget): """ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', closedmin=True, closedmax=True, slidermin=None, - slidermax=None, dragging=True, valstep=None, orientation='h', **kwargs): + slidermax=None, dragging=True, valstep=None, + orientation='horizontal', **kwargs): """ Parameters ---------- @@ -314,8 +315,9 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valstep : float, optional, default: None If given, the slider will snap to multiples of `valstep`. - orientation : str, optional, default: 'h' - If 'h' the slider is horizontal. If 'v' it is vertical + orientation : str, optional, default: 'horizontal' + 'horizontal' : horizontal slider + 'vertical' : vertical slider Notes ----- @@ -332,8 +334,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', if slidermax is not None and not hasattr(slidermax, 'val'): raise ValueError("Argument slidermax ({}) has no 'val'" .format(type(slidermax))) - if orientation not in ['h', 'v']: - raise ValueError("Argument orientation ({}) must be either 'h' or 'v'" + if orientation not in ['horizontal', 'vertical']: + raise ValueError("Argument orientation ({}) must be either 'horizontal' or 'vertical'" .format(orientation)) self.orientation = orientation @@ -350,7 +352,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valinit = valmin self.val = valinit self.valinit = valinit - if orientation is 'v': + if orientation is 'vertical': self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs) self.hline = ax.axhline(valinit, 0, 1, color='r', lw=1) else: @@ -359,7 +361,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.valfmt = valfmt ax.set_yticks([]) - if orientation is 'v': + if orientation is 'vertical': ax.set_ylim((valmin, valmax)) else: ax.set_xlim((valmin, valmax)) @@ -370,7 +372,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.connect_event('button_release_event', self._update) if dragging: self.connect_event('motion_notify_event', self._update) - if orientation is 'v': + if orientation is 'vertical': self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes, verticalalignment='bottom', horizontalalignment='center') @@ -441,7 +443,7 @@ def _update(self, event): self.drag_active = False event.canvas.release_mouse(self.ax) return - if self.orientation is 'v': + if self.orientation is 'vertical': val = self._value_in_bounds(event.ydata) else: val = self._value_in_bounds(event.xdata) @@ -457,7 +459,7 @@ def set_val(self, val): val : float """ xy = self.poly.xy - if self.orientation is 'v': + if self.orientation is 'vertical': xy[1] = 0, val xy[2] = 1, val else: From 257c4dc1a3a42de2c4b6e70d201310631ac7d86f Mon Sep 17 00:00:00 2001 From: Will Handley Date: Sat, 10 Mar 2018 14:00:03 +0000 Subject: [PATCH 3/9] Typo corrections so that code is PEP-8 compliant I forgot that I had not yet installed pyflakes and pylint on my new system, so my vim syntax checker didn't pick it up. --- lib/matplotlib/widgets.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index bad1a48f3631..a166a83b366c 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -272,7 +272,7 @@ class Slider(AxesWidget): """ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', closedmin=True, closedmax=True, slidermin=None, - slidermax=None, dragging=True, valstep=None, + slidermax=None, dragging=True, valstep=None, orientation='horizontal', **kwargs): """ Parameters @@ -335,8 +335,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', raise ValueError("Argument slidermax ({}) has no 'val'" .format(type(slidermax))) if orientation not in ['horizontal', 'vertical']: - raise ValueError("Argument orientation ({}) must be either 'horizontal' or 'vertical'" - .format(orientation)) + raise ValueError("Argument orientation ({}) must be either" + "'horizontal' or 'vertical'".format(orientation)) self.orientation = orientation self.closedmin = closedmin From 25a9d02b3d89c10d643cbbf48ff9ff0ba9218f7e Mon Sep 17 00:00:00 2001 From: Will Handley Date: Mon, 12 Mar 2018 20:36:12 +0000 Subject: [PATCH 4/9] Added test for horizontal and vertical orientations --- lib/matplotlib/tests/test_widgets.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index afd50ef24e3c..801506504bfd 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -308,6 +308,20 @@ def test_slider_valmin_valmax(): assert slider.val == slider.valmax +def test_slider_horizontal_vertical(): + fig, ax = plt.subplots() + slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, + valinit=12.0, orientation='horizontal') + slider.set_val(10.0): + assert slider.val == 10.0 + + fig, ax = plt.subplots() + slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, + valinit=12.0, orientation='vertical') + slider.set_val(10.0): + assert slider.val == 10.0 + + def check_polygon_selector(event_sequence, expected_result, selections_count): """Helper function to test Polygon Selector From e8d20b051c70cae40ccf26aec8de637412b52358 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Sun, 25 Mar 2018 15:46:58 +0100 Subject: [PATCH 5/9] Corrected typo in my modifications, and made test_widgets.py pep compliant regarding line-continuing indentation --- lib/matplotlib/tests/test_widgets.py | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/lib/matplotlib/tests/test_widgets.py b/lib/matplotlib/tests/test_widgets.py index 801506504bfd..a8544c850eb8 100644 --- a/lib/matplotlib/tests/test_widgets.py +++ b/lib/matplotlib/tests/test_widgets.py @@ -90,8 +90,9 @@ def onselect(epress, erelease): if kwargs.get('drawtype', None) not in ['line', 'none']: assert_allclose(tool.geometry, - [[100., 100, 199, 199, 100], [100, 199, 199, 100, 100]], - err_msg=tool.geometry) + [[100., 100, 199, 199, 100], + [100, 199, 199, 100, 100]], + err_msg=tool.geometry) assert ax._got_onselect @@ -118,40 +119,40 @@ def onselect(epress, erelease): # drag the rectangle do_event(tool, 'press', xdata=10, ydata=10, button=1, - key=' ') + key=' ') do_event(tool, 'onmove', xdata=30, ydata=30, button=1) do_event(tool, 'release', xdata=30, ydata=30, button=1) assert tool.extents == (120, 170, 120, 170) # create from center do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1, - key='control') + key='control') do_event(tool, 'press', xdata=100, ydata=100, button=1) do_event(tool, 'onmove', xdata=125, ydata=125, button=1) do_event(tool, 'release', xdata=125, ydata=125, button=1) do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1, - key='control') + key='control') assert tool.extents == (75, 125, 75, 125) # create a square do_event(tool, 'on_key_press', xdata=10, ydata=10, button=1, - key='shift') + key='shift') do_event(tool, 'press', xdata=10, ydata=10, button=1) do_event(tool, 'onmove', xdata=35, ydata=30, button=1) do_event(tool, 'release', xdata=35, ydata=30, button=1) do_event(tool, 'on_key_release', xdata=10, ydata=10, button=1, - key='shift') + key='shift') extents = [int(e) for e in tool.extents] assert extents == [10, 35, 10, 34] # create a square from center do_event(tool, 'on_key_press', xdata=100, ydata=100, button=1, - key='ctrl+shift') + key='ctrl+shift') do_event(tool, 'press', xdata=100, ydata=100, button=1) do_event(tool, 'onmove', xdata=125, ydata=130, button=1) do_event(tool, 'release', xdata=125, ydata=130, button=1) do_event(tool, 'on_key_release', xdata=100, ydata=100, button=1, - key='ctrl+shift') + key='ctrl+shift') extents = [int(e) for e in tool.extents] assert extents == [70, 129, 70, 130] @@ -312,13 +313,13 @@ def test_slider_horizontal_vertical(): fig, ax = plt.subplots() slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, valinit=12.0, orientation='horizontal') - slider.set_val(10.0): + slider.set_val(10.0) assert slider.val == 10.0 fig, ax = plt.subplots() slider = widgets.Slider(ax=ax, label='', valmin=0.0, valmax=24.0, valinit=12.0, orientation='vertical') - slider.set_val(10.0): + slider.set_val(10.0) assert slider.val == 10.0 From 220ca16ed91318a95e6ad68ec6c8674519101cf1 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Mon, 26 Mar 2018 06:18:46 +0100 Subject: [PATCH 6/9] Adjusted docstring to follow numpydoc --- lib/matplotlib/widgets.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index 2305ae80b8f7..128530d1340e 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -310,9 +310,8 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valstep : float, optional, default: None If given, the slider will snap to multiples of `valstep`. - orientation : str, optional, default: 'horizontal' - 'horizontal' : horizontal slider - 'vertical' : vertical slider + orientation : {'horizontal', 'vertical'} + The orientation of the slider. Notes ----- @@ -329,7 +328,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', if slidermax is not None and not hasattr(slidermax, 'val'): raise ValueError("Argument slidermax ({}) has no 'val'" .format(type(slidermax))) - if orientation not in ['horizontal', 'vertical']: + if orientation not in {'horizontal', 'vertical'}: raise ValueError("Argument orientation ({}) must be either" "'horizontal' or 'vertical'".format(orientation)) From adf84171c1d12f6396b1030b18057bb562afcacd Mon Sep 17 00:00:00 2001 From: Will Handley Date: Tue, 10 Jul 2018 09:33:57 +0100 Subject: [PATCH 7/9] Added whats new --- doc/users/next_whats_new/vertical_oriented_slider.rst | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 doc/users/next_whats_new/vertical_oriented_slider.rst diff --git a/doc/users/next_whats_new/vertical_oriented_slider.rst b/doc/users/next_whats_new/vertical_oriented_slider.rst new file mode 100644 index 000000000000..359a38f9768f --- /dev/null +++ b/doc/users/next_whats_new/vertical_oriented_slider.rst @@ -0,0 +1,10 @@ +Adjusted ``matplotlib.widgets.Slider`` to have vertical orientation +------------------------------------------------------------------- + +The :class:`matplotlib.widgets.Slider` widget now takes an optional argument +`orientation` which indicates the direction (`'horizontal'` or `'vertical'`) +that the slider should take. + +Argument checking is in keeping with the existing code, and the actual changes +to the source are minimal, replacing `hspan`s, `hline`s and `xdata` with an if +switch to `vspan`, `vline`s and `ydata`. From 2ad05cf18452e5074b66c3eb9725abcded7c7b44 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Fri, 13 Jul 2018 17:00:24 +0100 Subject: [PATCH 8/9] Moved what's new section to doc/users/whats_new.rst --- .../next_whats_new/vertical_oriented_slider.rst | 12 ------------ doc/users/whats_new.rst | 6 ++++++ 2 files changed, 6 insertions(+), 12 deletions(-) delete mode 100644 doc/users/next_whats_new/vertical_oriented_slider.rst diff --git a/doc/users/next_whats_new/vertical_oriented_slider.rst b/doc/users/next_whats_new/vertical_oriented_slider.rst deleted file mode 100644 index 49e58bcfc27e..000000000000 --- a/doc/users/next_whats_new/vertical_oriented_slider.rst +++ /dev/null @@ -1,12 +0,0 @@ -Adjusted ``matplotlib.widgets.Slider`` to have vertical orientation -------------------------------------------------------------------- - -The :class:`matplotlib.widgets.Slider` widget now takes an optional argument -``orientation`` which indicates the direction (``'horizontal'`` or ``'vertical'``) -that the slider should take. - -Argument checking is in keeping with the existing code, and the actual changes -to the source are minimal, replacing ``hspan``, ``hline`` and ``xdata`` with an if -switch to ``vspan``, ``vline`` and ``ydata``. - -Inspired by https://stackoverflow.com/questions/25934279/add-a-vertical-slider-with-matplotlib diff --git a/doc/users/whats_new.rst b/doc/users/whats_new.rst index 967b82a7a8c2..e82eb9237fd5 100644 --- a/doc/users/whats_new.rst +++ b/doc/users/whats_new.rst @@ -190,6 +190,12 @@ specify a number that is close (i.e. ``ax.title.set_position(0.5, 1.01)``) and the title will not be moved via this algorithm. +Adjusted ``matplotlib.widgets.Slider`` to have vertical orientation +------------------------------------------------------------------- + +The :class:`matplotlib.widgets.Slider` widget now takes an optional argument +``orientation`` which indicates the direction (``'horizontal'`` or +``'vertical'``) that the slider should take. From db065bf0d3f47a3bd8095405a0396ee0b7c924c9 Mon Sep 17 00:00:00 2001 From: Will Handley Date: Mon, 8 Oct 2018 16:48:32 +0100 Subject: [PATCH 9/9] Changed is to ==, and sets to lists --- lib/matplotlib/widgets.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/matplotlib/widgets.py b/lib/matplotlib/widgets.py index cf80a2588f44..8fe0837c2493 100644 --- a/lib/matplotlib/widgets.py +++ b/lib/matplotlib/widgets.py @@ -311,7 +311,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valstep : float, optional, default: None If given, the slider will snap to multiples of `valstep`. - orientation : {'horizontal', 'vertical'} + orientation : str, 'horizontal' or 'vertical', default: 'horizontal' The orientation of the slider. Notes @@ -329,7 +329,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', if slidermax is not None and not hasattr(slidermax, 'val'): raise ValueError("Argument slidermax ({}) has no 'val'" .format(type(slidermax))) - if orientation not in {'horizontal', 'vertical'}: + if orientation not in ['horizontal', 'vertical']: raise ValueError("Argument orientation ({}) must be either" "'horizontal' or 'vertical'".format(orientation)) @@ -347,7 +347,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', valinit = valmin self.val = valinit self.valinit = valinit - if orientation is 'vertical': + if orientation == 'vertical': self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs) self.hline = ax.axhline(valinit, 0, 1, color='r', lw=1) else: @@ -356,7 +356,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.valfmt = valfmt ax.set_yticks([]) - if orientation is 'vertical': + if orientation == 'vertical': ax.set_ylim((valmin, valmax)) else: ax.set_xlim((valmin, valmax)) @@ -367,7 +367,7 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt='%1.2f', self.connect_event('button_release_event', self._update) if dragging: self.connect_event('motion_notify_event', self._update) - if orientation is 'vertical': + if orientation == 'vertical': self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes, verticalalignment='bottom', horizontalalignment='center') @@ -438,7 +438,7 @@ def _update(self, event): self.drag_active = False event.canvas.release_mouse(self.ax) return - if self.orientation is 'vertical': + if self.orientation == 'vertical': val = self._value_in_bounds(event.ydata) else: val = self._value_in_bounds(event.xdata) @@ -454,7 +454,7 @@ def set_val(self, val): val : float """ xy = self.poly.xy - if self.orientation is 'vertical': + if self.orientation == 'vertical': xy[1] = 0, val xy[2] = 1, val else: