Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 2fb5ce6

Browse files
ianhitimhoffm
andcommitted
Change styling of slider widgets
Goal is to add a clear handle for the user to grab and to provide visual feedback via color change of when the slider has been grabbed. Simplify slider background rectangles rename bkd -> track + change default color to lightgrey lint remove facecolor from snap demo example Don't change handle color on grab. correct errors in slider docstrings Co-Authored-By: Tim Hoffmann <[email protected]>
1 parent 6f92db0 commit 2fb5ce6

File tree

4 files changed

+137
-35
lines changed

4 files changed

+137
-35
lines changed

examples/widgets/slider_demo.py

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,11 @@ def f(t, amplitude, frequency):
3232
line, = plt.plot(t, f(t, init_amplitude, init_frequency), lw=2)
3333
ax.set_xlabel('Time [s]')
3434

35-
axcolor = 'lightgoldenrodyellow'
36-
ax.margins(x=0)
37-
3835
# adjust the main plot to make room for the sliders
3936
plt.subplots_adjust(left=0.25, bottom=0.25)
4037

4138
# Make a horizontal slider to control the frequency.
42-
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
39+
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03])
4340
freq_slider = Slider(
4441
ax=axfreq,
4542
label='Frequency [Hz]',
@@ -49,7 +46,7 @@ def f(t, amplitude, frequency):
4946
)
5047

5148
# Make a vertically oriented slider to control the amplitude
52-
axamp = plt.axes([0.1, 0.25, 0.0225, 0.63], facecolor=axcolor)
49+
axamp = plt.axes([0.1, 0.25, 0.0225, 0.63])
5350
amp_slider = Slider(
5451
ax=axamp,
5552
label="Amplitude",
@@ -72,7 +69,7 @@ def update(val):
7269

7370
# Create a `matplotlib.widgets.Button` to reset the sliders to initial values.
7471
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
75-
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
72+
button = Button(resetax, 'Reset', hovercolor='0.975')
7673

7774

7875
def reset(event):

examples/widgets/slider_snap_demo.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,8 @@
2828
plt.subplots_adjust(bottom=0.25)
2929
l, = plt.plot(t, s, lw=2)
3030

31-
slider_bkd_color = 'lightgoldenrodyellow'
32-
ax_freq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=slider_bkd_color)
33-
ax_amp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=slider_bkd_color)
31+
ax_freq = plt.axes([0.25, 0.1, 0.65, 0.03])
32+
ax_amp = plt.axes([0.25, 0.15, 0.65, 0.03])
3433

3534
# define the values to use for snapping
3635
allowed_amplitudes = np.concatenate([np.linspace(.1, 5, 100), [6, 7, 8, 9]])
@@ -60,7 +59,7 @@ def update(val):
6059
samp.on_changed(update)
6160

6261
ax_reset = plt.axes([0.8, 0.025, 0.1, 0.04])
63-
button = Button(ax_reset, 'Reset', color=slider_bkd_color, hovercolor='0.975')
62+
button = Button(ax_reset, 'Reset', hovercolor='0.975')
6463

6564

6665
def reset(event):

lib/matplotlib/tests/test_widgets.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -332,7 +332,7 @@ def test_slider_horizontal_vertical():
332332
assert slider.val == 10
333333
# check the dimension of the slider patch in axes units
334334
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
335-
assert_allclose(box.bounds, [0, 0, 10/24, 1])
335+
assert_allclose(box.bounds, [0, .25, 10/24, .5])
336336

337337
fig, ax = plt.subplots()
338338
slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
@@ -341,7 +341,7 @@ def test_slider_horizontal_vertical():
341341
assert slider.val == 10
342342
# check the dimension of the slider patch in axes units
343343
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
344-
assert_allclose(box.bounds, [0, 0, 1, 10/24])
344+
assert_allclose(box.bounds, [.25, 0, .5, 10/24])
345345

346346

347347
@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
@@ -358,15 +358,15 @@ def test_range_slider(orientation):
358358
valinit=[0.1, 0.34]
359359
)
360360
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
361-
assert_allclose(box.get_points().flatten()[idx], [0.1, 0, 0.34, 1])
361+
assert_allclose(box.get_points().flatten()[idx], [0.1, 0.25, 0.34, 0.75])
362362

363363
# Check initial value is set correctly
364364
assert_allclose(slider.val, (0.1, 0.34))
365365

366366
slider.set_val((0.2, 0.6))
367367
assert_allclose(slider.val, (0.2, 0.6))
368368
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
369-
assert_allclose(box.get_points().flatten()[idx], [0.2, 0, 0.6, 1])
369+
assert_allclose(box.get_points().flatten()[idx], [0.2, .25, 0.6, .75])
370370

371371
slider.set_val((0.2, 0.1))
372372
assert_allclose(slider.val, (0.1, 0.2))

lib/matplotlib/widgets.py

Lines changed: 127 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,9 +267,9 @@ def __init__(self, ax, orientation, closedmin, closedmax,
267267
self._fmt.set_useOffset(False) # No additive offset.
268268
self._fmt.set_useMathText(True) # x sign before multiplicative offset.
269269

270-
ax.set_xticks([])
271-
ax.set_yticks([])
270+
ax.set_axis_off()
272271
ax.set_navigate(False)
272+
273273
self.connect_event("button_press_event", self._update)
274274
self.connect_event("button_release_event", self._update)
275275
if dragging:
@@ -329,7 +329,9 @@ class Slider(SliderBase):
329329
def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
330330
closedmin=True, closedmax=True, slidermin=None,
331331
slidermax=None, dragging=True, valstep=None,
332-
orientation='horizontal', *, initcolor='r', **kwargs):
332+
orientation='horizontal', *, initcolor='r',
333+
track_color='lightgrey', handle_facecolor='white',
334+
handle_edgecolor='.75', handle_size=10, **kwargs):
333335
"""
334336
Parameters
335337
----------
@@ -380,6 +382,19 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
380382
The color of the line at the *valinit* position. Set to ``'none'``
381383
for no line.
382384
385+
track_color : color, default: 'lightgrey'
386+
The color of the background track. The track is accessible for
387+
further styling via the *track* attribute.
388+
389+
handle_facecolor : color, default: 'white'
390+
The facecolor of the circular slider handle.
391+
392+
handle_edgecolor : color, default: '.75'
393+
The edgecolor of the circle slider handle.
394+
395+
handle_size : int, default: 10
396+
The size of the circular slider handle in points.
397+
383398
Notes
384399
-----
385400
Additional kwargs are passed on to ``self.poly`` which is the
@@ -404,11 +419,33 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
404419
self.val = valinit
405420
self.valinit = valinit
406421
if orientation == 'vertical':
407-
self.poly = ax.axhspan(valmin, valinit, 0, 1, **kwargs)
408-
self.hline = ax.axhline(valinit, 0, 1, color=initcolor, lw=1)
422+
self.track = Rectangle(
423+
(.25, 0), .5, 1,
424+
transform=ax.transAxes,
425+
facecolor=track_color
426+
)
427+
ax.add_patch(self.track)
428+
self.poly = ax.axhspan(valmin, valinit, .25, .75, **kwargs)
429+
self.hline = ax.axhline(valinit, .25, .75, color=initcolor, lw=1)
430+
handleXY = [[0.5], [valinit]]
409431
else:
410-
self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs)
411-
self.vline = ax.axvline(valinit, 0, 1, color=initcolor, lw=1)
432+
self.track = Rectangle(
433+
(0, .25), 1, .5,
434+
transform=ax.transAxes,
435+
facecolor=track_color
436+
)
437+
ax.add_patch(self.track)
438+
self.poly = ax.axvspan(valmin, valinit, .25, .75, **kwargs)
439+
self.vline = ax.axvline(valinit, .25, .75, color=initcolor, lw=1)
440+
handleXY = [[valinit], [0.5]]
441+
self._handle, = ax.plot(
442+
*handleXY,
443+
"o",
444+
markersize=handle_size,
445+
markeredgecolor=handle_edgecolor,
446+
markerfacecolor=handle_facecolor,
447+
clip_on=False
448+
)
412449

413450
if orientation == 'vertical':
414451
self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes,
@@ -499,11 +536,13 @@ def set_val(self, val):
499536
"""
500537
xy = self.poly.xy
501538
if self.orientation == 'vertical':
502-
xy[1] = 0, val
503-
xy[2] = 1, val
539+
xy[1] = .25, val
540+
xy[2] = .75, val
541+
self._handle.set_ydata([val])
504542
else:
505-
xy[2] = val, 1
506-
xy[3] = val, 0
543+
xy[2] = val, .75
544+
xy[3] = val, .25
545+
self._handle.set_xdata([val])
507546
self.poly.xy = xy
508547
self.valtext.set_text(self._format(val))
509548
if self.drawon:
@@ -558,6 +597,10 @@ def __init__(
558597
dragging=True,
559598
valstep=None,
560599
orientation="horizontal",
600+
track_color='lightgrey',
601+
handle_facecolor='white',
602+
handle_edgecolor='.75',
603+
handle_size=10,
561604
**kwargs,
562605
):
563606
"""
@@ -598,6 +641,19 @@ def __init__(
598641
orientation : {'horizontal', 'vertical'}, default: 'horizontal'
599642
The orientation of the slider.
600643
644+
track_color : color, default: 'lightgrey'
645+
The color of the background track. The track is accessible for
646+
further styling via the *track* attribute.
647+
648+
handle_facecolor : color, default: 'white'
649+
The facecolor of the circular slider handle.
650+
651+
handle_edgecolor : color, default: '.75'
652+
The edgecolor of the circular slider handles.
653+
654+
handle_size : int, default: 10
655+
The size of the circular slider handles in points.
656+
601657
Notes
602658
-----
603659
Additional kwargs are passed on to ``self.poly`` which is the
@@ -620,9 +676,43 @@ def __init__(
620676
self.val = valinit
621677
self.valinit = valinit
622678
if orientation == "vertical":
679+
self.track = Rectangle(
680+
(.25, 0), .5, 2,
681+
transform=ax.transAxes,
682+
facecolor=track_color
683+
)
684+
ax.add_patch(self.track)
623685
self.poly = ax.axhspan(valinit[0], valinit[1], 0, 1, **kwargs)
686+
handleXY_1 = [.5, valinit[0]]
687+
handleXY_2 = [.5, valinit[1]]
624688
else:
689+
self.track = Rectangle(
690+
(0, .25), 1, .5,
691+
transform=ax.transAxes,
692+
facecolor=track_color
693+
)
694+
ax.add_patch(self.track)
625695
self.poly = ax.axvspan(valinit[0], valinit[1], 0, 1, **kwargs)
696+
handleXY_1 = [valinit[0], .5]
697+
handleXY_2 = [valinit[1], .5]
698+
self._handles = [
699+
ax.plot(
700+
*handleXY_1,
701+
"o",
702+
markersize=handle_size,
703+
markeredgecolor=handle_edgecolor,
704+
markerfacecolor=handle_facecolor,
705+
clip_on=False
706+
)[0],
707+
ax.plot(
708+
*handleXY_2,
709+
"o",
710+
markersize=handle_size,
711+
markeredgecolor=handle_edgecolor,
712+
markerfacecolor=handle_facecolor,
713+
clip_on=False
714+
)[0]
715+
]
626716

627717
if orientation == "vertical":
628718
self.label = ax.text(
@@ -661,6 +751,7 @@ def __init__(
661751
horizontalalignment="left",
662752
)
663753

754+
self._active_handle = None
664755
self.set_val(valinit)
665756

666757
def _min_in_bounds(self, min):
@@ -698,6 +789,8 @@ def _update_val_from_pos(self, pos):
698789
else:
699790
val = self._max_in_bounds(pos)
700791
self.set_max(val)
792+
if self._active_handle:
793+
self._active_handle.set_xdata([val])
701794

702795
def _update(self, event):
703796
"""Update the slider position."""
@@ -716,7 +809,20 @@ def _update(self, event):
716809
):
717810
self.drag_active = False
718811
event.canvas.release_mouse(self.ax)
812+
self._active_handle = None
719813
return
814+
815+
# determine which handle was grabbed
816+
handle = self._handles[
817+
np.argmin(
818+
np.abs([h.get_xdata()[0] - event.xdata for h in self._handles])
819+
)
820+
]
821+
# these checks ensure smooth behavior if the handles swap which one
822+
# has a higher value. i.e. if one is dragged over and past the other.
823+
if handle is not self._active_handle:
824+
self._active_handle = handle
825+
720826
if self.orientation == "vertical":
721827
self._update_val_from_pos(event.ydata)
722828
else:
@@ -773,17 +879,17 @@ def set_val(self, val):
773879
val[1] = self._max_in_bounds(val[1])
774880
xy = self.poly.xy
775881
if self.orientation == "vertical":
776-
xy[0] = 0, val[0]
777-
xy[1] = 0, val[1]
778-
xy[2] = 1, val[1]
779-
xy[3] = 1, val[0]
780-
xy[4] = 0, val[0]
882+
xy[0] = .25, val[0]
883+
xy[1] = .25, val[1]
884+
xy[2] = .75, val[1]
885+
xy[3] = .75, val[0]
886+
xy[4] = .25, val[0]
781887
else:
782-
xy[0] = val[0], 0
783-
xy[1] = val[0], 1
784-
xy[2] = val[1], 1
785-
xy[3] = val[1], 0
786-
xy[4] = val[0], 0
888+
xy[0] = val[0], .25
889+
xy[1] = val[0], .75
890+
xy[2] = val[1], .75
891+
xy[3] = val[1], .25
892+
xy[4] = val[0], .25
787893
self.poly.xy = xy
788894
self.valtext.set_text(self._format(val))
789895
if self.drawon:

0 commit comments

Comments
 (0)