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

Skip to content

Commit ca058d1

Browse files
committed
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.
1 parent 5417682 commit ca058d1

File tree

3 files changed

+193
-30
lines changed

3 files changed

+193
-30
lines changed

examples/widgets/slider_demo.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@
2323
l, = plt.plot(t, s, lw=2)
2424
ax.margins(x=0)
2525

26-
axcolor = 'lightgoldenrodyellow'
27-
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03], facecolor=axcolor)
28-
axamp = plt.axes([0.25, 0.15, 0.65, 0.03], facecolor=axcolor)
26+
axfreq = plt.axes([0.25, 0.1, 0.65, 0.03])
27+
axamp = plt.axes([0.25, 0.15, 0.65, 0.03])
2928

3029
sfreq = Slider(axfreq, 'Freq', 0.1, 30.0, valinit=f0, valstep=delta_f)
3130
samp = Slider(axamp, 'Amp', 0.1, 10.0, valinit=a0)
@@ -42,15 +41,15 @@ def update(val):
4241
samp.on_changed(update)
4342

4443
resetax = plt.axes([0.8, 0.025, 0.1, 0.04])
45-
button = Button(resetax, 'Reset', color=axcolor, hovercolor='0.975')
44+
button = Button(resetax, 'Reset', hovercolor='0.975')
4645

4746

4847
def reset(event):
4948
sfreq.reset()
5049
samp.reset()
5150
button.on_clicked(reset)
5251

53-
rax = plt.axes([0.025, 0.5, 0.15, 0.15], facecolor=axcolor)
52+
rax = plt.axes([0.025, 0.5, 0.15, 0.15])
5453
radio = RadioButtons(rax, ('red', 'blue', 'green'), active=0)
5554

5655

lib/matplotlib/tests/test_widgets.py

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

291291
fig, ax = plt.subplots()
292292
slider = widgets.Slider(ax=ax, label='', valmin=0, valmax=24,
@@ -295,7 +295,7 @@ def test_slider_horizontal_vertical():
295295
assert slider.val == 10
296296
# check the dimension of the slider patch in axes units
297297
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
298-
assert_allclose(box.bounds, [0, 0, 1, 10/24])
298+
assert_allclose(box.bounds, [.25, 0, .5, 10/24])
299299

300300

301301
@pytest.mark.parametrize("orientation", ["horizontal", "vertical"])
@@ -311,12 +311,12 @@ def test_range_slider(orientation):
311311
ax=ax, label="", valmin=0.0, valmax=1.0, orientation=orientation
312312
)
313313
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
314-
assert_allclose(box.get_points().flatten()[idx], [0.25, 0, 0.75, 1])
314+
assert_allclose(box.get_points().flatten()[idx], [0.25, .25, 0.75, .75])
315315

316316
slider.set_val((0.2, 0.6))
317317
assert_allclose(slider.val, (0.2, 0.6))
318318
box = slider.poly.get_extents().transformed(ax.transAxes.inverted())
319-
assert_allclose(box.get_points().flatten()[idx], [0.2, 0, 0.6, 1])
319+
assert_allclose(box.get_points().flatten()[idx], [0.2, .25, 0.6, .75])
320320

321321
slider.set_val((0.2, 0.1))
322322
assert_allclose(slider.val, (0.1, 0.2))

lib/matplotlib/widgets.py

Lines changed: 185 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -267,8 +267,7 @@ 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)
273272
self.connect_event("button_press_event", self._update)
274273
self.connect_event("button_release_event", self._update)
@@ -329,7 +328,9 @@ class Slider(SliderBase):
329328
def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
330329
closedmin=True, closedmax=True, slidermin=None,
331330
slidermax=None, dragging=True, valstep=None,
332-
orientation='horizontal', *, initcolor='r', **kwargs):
331+
orientation='horizontal', *, initcolor='r',
332+
handle_color_grabbed=None, handle_color_released='white',
333+
handle_edgecolor='.75', handle_size=10, **kwargs):
333334
"""
334335
Parameters
335336
----------
@@ -380,6 +381,19 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
380381
The color of the line at the *valinit* position. Set to ``'none'``
381382
for no line.
382383
384+
handle_color_grabbed : color or None, default: None
385+
The color of the circular slider handle when grabbed. If *None*
386+
the same color as active part of the slider will be used.
387+
388+
handle_color_released : color, default: 'white'
389+
The color of the circular slider handle when released.
390+
391+
handle_edgecolor : color, default: '.75'
392+
The edgecolor of the circle slider handle.
393+
394+
handle_size : int, deafult: 10
395+
The size of circular slider handle.
396+
383397
Notes
384398
-----
385399
Additional kwargs are passed on to ``self.poly`` which is the
@@ -404,11 +418,41 @@ def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,
404418
self.val = valinit
405419
self.valinit = valinit
406420
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)
421+
self.poly = ax.axhspan(valmin, valinit, .25, .75, **kwargs)
422+
self.hline = ax.axhline(valinit, .25, .75, color=initcolor, lw=1)
423+
handleXY = [[0.5], [valinit]]
424+
self._above_rect = Rectangle(
425+
(0.25, valinit),
426+
height=valmax - valinit,
427+
width=0.5,
428+
transform=ax.get_yaxis_transform(),
429+
)
409430
else:
410-
self.poly = ax.axvspan(valmin, valinit, 0, 1, **kwargs)
411-
self.vline = ax.axvline(valinit, 0, 1, color=initcolor, lw=1)
431+
self.poly = ax.axvspan(valmin, valinit, .25, .75, **kwargs)
432+
self.vline = ax.axvline(valinit, .25, .75, color=initcolor, lw=1)
433+
handleXY = [[valinit], [0.5]]
434+
self._above_rect = Rectangle(
435+
(valinit, 0.25),
436+
width=valmax - valinit,
437+
height=0.5,
438+
transform=ax.get_xaxis_transform(),
439+
)
440+
self._above_rect.set_facecolor('grey')
441+
self._handle, = ax.plot(
442+
*handleXY,
443+
"o",
444+
markersize=handle_size,
445+
markeredgecolor=handle_edgecolor,
446+
markerfacecolor=handle_color_released,
447+
clip_on=False
448+
)
449+
ax.add_patch(self._above_rect)
450+
451+
self._handle_color_released = handle_color_released
452+
self._handle_edgecolor = handle_edgecolor
453+
if handle_color_grabbed is None:
454+
handle_color_grabbed = self.poly.get_facecolor()
455+
self._handle_color_grabbed = handle_color_grabbed
412456

413457
if orientation == 'vertical':
414458
self.label = ax.text(0.5, 1.02, label, transform=ax.transAxes,
@@ -472,7 +516,13 @@ def _update(self, event):
472516
event.inaxes != self.ax)):
473517
self.drag_active = False
474518
event.canvas.release_mouse(self.ax)
519+
self._handle.set_markeredgecolor(self._handle_edgecolor)
520+
self._handle.set_markerfacecolor(self._handle_color_released)
521+
self.ax.figure.canvas.draw_idle()
475522
return
523+
if event.name == 'button_press_event':
524+
self._handle.set_markeredgecolor(self._handle_color_grabbed)
525+
self._handle.set_markerfacecolor(self._handle_color_grabbed)
476526
if self.orientation == 'vertical':
477527
val = self._value_in_bounds(event.ydata)
478528
else:
@@ -499,11 +549,17 @@ def set_val(self, val):
499549
"""
500550
xy = self.poly.xy
501551
if self.orientation == 'vertical':
502-
xy[1] = 0, val
503-
xy[2] = 1, val
552+
xy[1] = .25, val
553+
xy[2] = .75, val
554+
self._above_rect.set_y(val)
555+
self._above_rect.set_height(self.valmax-val)
556+
self._handle.set_ydata([val])
504557
else:
505-
xy[2] = val, 1
506-
xy[3] = val, 0
558+
xy[2] = val, .75
559+
xy[3] = val, .25
560+
self._above_rect.set_x(val)
561+
self._above_rect.set_width(self.valmax-val)
562+
self._handle.set_xdata([val])
507563
self.poly.xy = xy
508564
self.valtext.set_text(self._format(val))
509565
if self.drawon:
@@ -558,6 +614,10 @@ def __init__(
558614
dragging=True,
559615
valstep=None,
560616
orientation="horizontal",
617+
handle_color_grabbed=None,
618+
handle_color_released='white',
619+
handle_edgecolor='.75',
620+
handle_size=10,
561621
**kwargs,
562622
):
563623
"""
@@ -598,6 +658,19 @@ def __init__(
598658
orientation : {'horizontal', 'vertical'}, default: 'horizontal'
599659
The orientation of the slider.
600660
661+
handle_color_grabbed : color or None, default: None
662+
The color of the circular slider handles when grabbed. If *None*
663+
the same color as active part of the slider will be used.
664+
665+
handle_color_released : color, default: 'white'
666+
The color of the circular slider handles when released.
667+
668+
handle_edgecolor : color, default: '.75'
669+
The edgecolor of the circular slider handles.
670+
671+
handle_size : int, deafult: 10
672+
The size of circular slider handles.
673+
601674
Notes
602675
-----
603676
Additional kwargs are passed on to ``self.poly`` which is the
@@ -620,8 +693,64 @@ def __init__(
620693
self.valinit = valinit
621694
if orientation == "vertical":
622695
self.poly = ax.axhspan(valinit[0], valinit[1], 0, 1, **kwargs)
696+
handleXY_1 = [.5, valinit[0]]
697+
handleXY_2 = [.5, valinit[1]]
698+
self._above_rect = Rectangle(
699+
(.25, valinit[1]),
700+
height=valmax - valinit[1],
701+
width=0.5,
702+
transform=ax.get_yaxis_transform(),
703+
)
704+
self._below_rect = Rectangle(
705+
(valmin, 0.25),
706+
width=valinit[0] - valmin,
707+
height=0.5,
708+
transform=ax.get_xaxis_transform(),
709+
)
623710
else:
624711
self.poly = ax.axvspan(valinit[0], valinit[1], 0, 1, **kwargs)
712+
handleXY_1 = [valinit[0], .5]
713+
handleXY_2 = [valinit[1], .5]
714+
self._above_rect = Rectangle(
715+
(valinit[1], 0.25),
716+
width=valmax - valinit[1],
717+
height=0.5,
718+
transform=ax.get_xaxis_transform(),
719+
)
720+
self._below_rect = Rectangle(
721+
(valmin, 0.25),
722+
width=valinit[0] - valmin,
723+
height=0.5,
724+
transform=ax.get_xaxis_transform(),
725+
)
726+
self._above_rect.set_facecolor('grey')
727+
self._below_rect.set_facecolor('grey')
728+
ax.add_patch(self._above_rect)
729+
ax.add_patch(self._below_rect)
730+
self._handles = [
731+
ax.plot(
732+
*handleXY_1,
733+
"o",
734+
markersize=handle_size,
735+
markeredgecolor=handle_edgecolor,
736+
markerfacecolor=handle_color_released,
737+
clip_on=False
738+
)[0],
739+
ax.plot(
740+
*handleXY_2,
741+
"o",
742+
markersize=handle_size,
743+
markeredgecolor=handle_edgecolor,
744+
markerfacecolor=handle_color_released,
745+
clip_on=False
746+
)[0]
747+
]
748+
749+
self._handle_color_released = handle_color_released
750+
self._handle_edgecolor = handle_edgecolor
751+
if handle_color_grabbed is None:
752+
handle_color_grabbed = self.poly.get_facecolor()
753+
self._handle_color_grabbed = handle_color_grabbed
625754

626755
if orientation == "vertical":
627756
self.label = ax.text(
@@ -660,6 +789,7 @@ def __init__(
660789
horizontalalignment="left",
661790
)
662791

792+
self._active_handle = None
663793
self.set_val(valinit)
664794

665795
def _min_in_bounds(self, min):
@@ -696,6 +826,8 @@ def _update_val_from_pos(self, pos):
696826
else:
697827
val = self._max_in_bounds(pos)
698828
self.set_max(val)
829+
if self._active_handle:
830+
self._active_handle.set_xdata([val])
699831

700832
def _update(self, event):
701833
"""Update the slider position."""
@@ -714,7 +846,33 @@ def _update(self, event):
714846
):
715847
self.drag_active = False
716848
event.canvas.release_mouse(self.ax)
849+
self.ax.figure.canvas.draw_idle()
850+
for handle in self._handles:
851+
handle.set_markeredgecolor(self._handle_edgecolor)
852+
handle.set_markerfacecolor(self._handle_color_released)
853+
self._active_handle = None
717854
return
855+
856+
# determine which handle was grabbed
857+
handle = self._handles[
858+
np.argmin(
859+
np.abs([h.get_xdata()[0] - event.xdata for h in self._handles])
860+
)
861+
]
862+
# these checks ensure smooth behavior if the handles swap which one
863+
# has a higher value. i.e. if one is dragged over and past the other.
864+
if handle is not self._active_handle:
865+
if self._active_handle is not None:
866+
self._active_handle.set_markeredgecolor(
867+
self._handle_edgecolor
868+
)
869+
self._active_handle.set_markerfacecolor(
870+
self._handle_color_released
871+
)
872+
self._active_handle = handle
873+
self._active_handle.set_markeredgecolor(self._handle_color_grabbed)
874+
self._active_handle.set_markerfacecolor(self._handle_color_grabbed)
875+
718876
if self.orientation == "vertical":
719877
self._update_val_from_pos(event.ydata)
720878
else:
@@ -771,17 +929,23 @@ def set_val(self, val):
771929
val[1] = self._max_in_bounds(val[1])
772930
xy = self.poly.xy
773931
if self.orientation == "vertical":
774-
xy[0] = 0, val[0]
775-
xy[1] = 0, val[1]
776-
xy[2] = 1, val[1]
777-
xy[3] = 1, val[0]
778-
xy[4] = 0, val[0]
932+
xy[0] = .25, val[0]
933+
xy[1] = .25, val[1]
934+
xy[2] = .75, val[1]
935+
xy[3] = .75, val[0]
936+
xy[4] = .25, val[0]
937+
self._above_rect.set_height(self.valmax - val[1])
938+
self._above_rect.set_y(val[1])
939+
self._below_rect.set_height(val[0] - self.valmin)
779940
else:
780-
xy[0] = val[0], 0
781-
xy[1] = val[0], 1
782-
xy[2] = val[1], 1
783-
xy[3] = val[1], 0
784-
xy[4] = val[0], 0
941+
xy[0] = val[0], .25
942+
xy[1] = val[0], .75
943+
xy[2] = val[1], .75
944+
xy[3] = val[1], .25
945+
xy[4] = val[0], .25
946+
self._above_rect.set_width(self.valmax - val[1])
947+
self._above_rect.set_x(val[1])
948+
self._below_rect.set_width(val[0] - self.valmin)
785949
self.poly.xy = xy
786950
self.valtext.set_text(self._format(val))
787951
if self.drawon:

0 commit comments

Comments
 (0)