Title: New Styling for Sliders · Issue #19256 · matplotlib/matplotlib · GitHub
Open Graph Title: New Styling for Sliders · Issue #19256 · matplotlib/matplotlib
X Title: New Styling for Sliders · Issue #19256 · matplotlib/matplotlib
Description: Problem I've never loved the way Matplotlib Slider widgets look (and per jupyter-widgets/ipywidgets#3025 (comment) I am apparently not alone). However, beyond that I think that the current way the slider widgets are drawn introduces two ...
Open Graph Description: Problem I've never loved the way Matplotlib Slider widgets look (and per jupyter-widgets/ipywidgets#3025 (comment) I am apparently not alone). However, beyond that I think that the current way the ...
X Description: Problem I've never loved the way Matplotlib Slider widgets look (and per jupyter-widgets/ipywidgets#3025 (comment) I am apparently not alone). However, beyond that I think that the current way ...
Opengraph URL: https://github.com/matplotlib/matplotlib/issues/19256
X: @github
Domain: github.com
{"@context":"https://schema.org","@type":"DiscussionForumPosting","headline":"New Styling for Sliders","articleBody":"\u003c!--\r\nWelcome! Thanks for thinking of a way to improve Matplotlib.\r\n\r\n\r\nBefore creating a new feature request please search the issues for relevant feature requests.\r\n--\u003e\r\n\r\n### Problem\r\nI've never loved the way Matplotlib Slider widgets look (and per https://github.com/jupyter-widgets/ipywidgets/issues/3025#issuecomment-756263435 I am apparently not alone). However, beyond that I think that the current way the slider widgets are drawn introduces two issue that hamper usability.\r\n\r\n1. There is no clear handle for the user to grab\r\n2. No visual feedback that you've grabbed the slider\r\n\r\n`\u003cspeculation\u003e` More broadly I suspect that most people's expectation of what a slider UI element will look like is strongly shaped by how they look on the web. So if Matplotlib sliders look more similar to web sliders then they will be more natural for users. `\u003c/speculation\u003e`\r\n\r\n### Proposed Solution\r\nSomething to the effect of this:\r\n\r\n\u003cdetails\u003e\r\n \u003csummary\u003e\u003cb\u003escript with new slider definition + an example\u003c/b\u003e\u003c/summary\u003e\r\n\u003cp\u003e\r\n\r\n```python\r\nimport numpy as np\r\nfrom matplotlib.widgets import SliderBase\r\nfrom matplotlib import _api\r\nimport matplotlib.patches as mpatches\r\nfrom matplotlib import transforms\r\n\r\nclass newSlider(SliderBase):\r\n cnt = _api.deprecated(\"3.4\")(property(# Not real, but close enough.\r\n lambda self: len(self._observers.callbacks['changed'])))\r\n observers = _api.deprecated(\"3.4\")(property( lambda self: self._observers.callbacks['changed']))\r\n\r\n def __init__(self, ax, label, valmin, valmax, valinit=0.5, valfmt=None,\r\n closedmin=True, closedmax=True, slidermin=None,\r\n slidermax=None, dragging=True, valstep=None,\r\n orientation='horizontal', *, initcolor='r',s=10, **kwargs):\r\n super().__init__(ax, orientation, closedmin, closedmax,\r\n valmin, valmax, valfmt, dragging, valstep)\r\n\r\n if slidermin is not None and not hasattr(slidermin, 'val'):\r\n raise ValueError(\r\n f\"Argument slidermin ({type(slidermin)}) has no 'val'\")\r\n if slidermax is not None and not hasattr(slidermax, 'val'):\r\n raise ValueError(\r\n f\"Argument slidermax ({type(slidermax)}) has no 'val'\")\r\n self.slidermin = slidermin\r\n self.slidermax = slidermax\r\n valinit = self._value_in_bounds(valinit)\r\n if valinit is None:\r\n valinit = valmin\r\n self.val = valinit\r\n self.valinit = valinit\r\n\r\n ax.axis('off')\r\n self.dot, = ax.plot([valinit],[.5], 'o', markersize=s, color='C0')\r\n trans = transforms.blended_transform_factory(\r\n ax.transData, ax.transAxes)\r\n self.rect = mpatches.Rectangle((valmin, .25), width=valinit-valmin, height=.5, transform=trans,\r\n color='C0', alpha=0.75)\r\n self.above_rect = mpatches.Rectangle((valinit, .25), width=valmax-valinit, height=.5, transform=trans,\r\n color='grey', alpha=0.5)\r\n ax.add_patch(self.rect)\r\n ax.add_patch(self.above_rect)\r\n\r\n self.label = ax.text(-0.02, 0.5, label, transform=ax.transAxes,\r\n verticalalignment='center',\r\n horizontalalignment='right')\r\n\r\n self.valtext = ax.text(1.02, 0.5, self._format(valinit),\r\n transform=ax.transAxes,\r\n verticalalignment='center',\r\n horizontalalignment='left')\r\n\r\n self.set_val(valinit)\r\n\r\n\r\n def _format(self, val):\r\n \"\"\"Pretty-print *val*.\"\"\"\r\n if self.valfmt is not None:\r\n return self.valfmt % val\r\n else:\r\n _, s, _ = self._fmt.format_ticks([self.valmin, val, self.valmax])\r\n # fmt.get_offset is actually the multiplicative factor, if any.\r\n return s + self._fmt.get_offset()\r\n \r\n def _value_in_bounds(self, val):\r\n \"\"\"Makes sure *val* is with given bounds.\"\"\"\r\n val = self._stepped_value(val)\r\n\r\n if val \u003c= self.valmin:\r\n if not self.closedmin:\r\n return\r\n val = self.valmin\r\n elif val \u003e= self.valmax:\r\n if not self.closedmax:\r\n return\r\n val = self.valmax\r\n\r\n if self.slidermin is not None and val \u003c= self.slidermin.val:\r\n if not self.closedmin:\r\n return\r\n val = self.slidermin.val\r\n\r\n if self.slidermax is not None and val \u003e= self.slidermax.val:\r\n if not self.closedmax:\r\n return\r\n val = self.slidermax.val\r\n return val\r\n\r\n def _update(self, event):\r\n \"\"\"Update the slider position.\"\"\"\r\n if self.ignore(event) or event.button != 1:\r\n return\r\n\r\n if event.name == 'button_press_event' and event.inaxes == self.ax:\r\n self.drag_active = True\r\n event.canvas.grab_mouse(self.ax)\r\n\r\n if not self.drag_active:\r\n return\r\n\r\n elif ((event.name == 'button_release_event') or\r\n (event.name == 'button_press_event' and\r\n event.inaxes != self.ax)):\r\n self.drag_active = False\r\n event.canvas.release_mouse(self.ax)\r\n self.dot.set_markeredgecolor('grey')\r\n self.dot.set_markerfacecolor('grey')\r\n self.ax.figure.canvas.draw_idle()\r\n return\r\n\r\n if event.name == 'button_press_event':\r\n self.dot.set_markeredgecolor('C0')\r\n self.dot.set_markerfacecolor('C0')\r\n if self.orientation == 'vertical':\r\n val = self._value_in_bounds(event.ydata)\r\n else:\r\n val = self._value_in_bounds(event.xdata)\r\n if val not in [None, self.val]:\r\n self.set_val(val)\r\n def set_val(self, val):\r\n \"\"\"\r\n Set slider value to *val*.\r\n Parameters\r\n ----------\r\n val : float\r\n \"\"\"\r\n self.dot.set_xdata([val])\r\n self.rect.set_width(val)\r\n self.above_rect.set_x(val)\r\n self.valtext.set_text(self._format(val))\r\n if self.drawon:\r\n self.ax.figure.canvas.draw_idle()\r\n self.val = val\r\n if self.eventson:\r\n self._observers.process('changed', val)\r\n def on_changed(self, func):\r\n \"\"\"\r\n Connect *func* as callback function to changes of the slider value.\r\n Parameters\r\n ----------\r\n func : callable\r\n Function to call when slider is changed.\r\n The function must accept a single float as its arguments.\r\n Returns\r\n -------\r\n int\r\n Connection id (which can be used to disconnect *func*).\r\n \"\"\"\r\n return self._observers.connect('changed', lambda val: func(val))\r\n\r\ndef connnect_slider_to_ax(slider, ax):\r\n t = np.arange(0.0, 1.0, 0.001)\r\n f0 = 3\r\n delta_f = 5.0\r\n amp = 5\r\n s = amp * np.sin(2 * np.pi * f0 * t)\r\n l, = ax.plot(t, s, lw=2)\r\n def update(val):\r\n freq = slider.val\r\n l.set_ydata(amp*np.sin(2*np.pi*freq*t))\r\n # ax.figure.canvas.draw_idle()\r\n slider.on_changed(update)\r\n\r\nif __name__ == '__main__':\r\n import matplotlib.pyplot as plt\r\n from matplotlib.widgets import Slider\r\n # new style\r\n fig, ax = plt.subplots()\r\n plt.subplots_adjust(bottom=0.25)\r\n new_slider_ax = plt.axes([0.25, 0.1, 0.65, 0.03])\r\n sNew = newSlider(new_slider_ax, 'Freq', 0.1, 30.0,valinit=5)\r\n connnect_slider_to_ax(sNew, ax)\r\n\r\n fig2, ax2 = plt.subplots()\r\n plt.subplots_adjust(bottom=0.25)\r\n old_slider_ax = plt.axes([0.25, 0.1, 0.65, 0.03])\r\n sOld = Slider(old_slider_ax, 'Freq', 0.1, 30.0, valinit=5)\r\n connnect_slider_to_ax(sOld, ax2)\r\n\r\n plt.show()\r\n```\r\n\u003c/p\u003e\u003c/details\u003e\r\n\r\nThe above script will generate these two figures for comparison. On the left is my new proposed style, and on the right is the current style:\r\n\r\n\r\n\r\n\r\n### Additional context and prior art\r\n\r\nhttps://www.smashingmagazine.com/2017/07/designing-perfect-slider/\r\nhttps://material-ui.com/components/slider/\r\nhttps://ipywidgets.readthedocs.io/en/stable/examples/Widget%20List.html#IntSlider\r\n\u003c!-- Add any other context or screenshots about the feature request here. You can also include links to examples of other programs that have something similar to your request. For example:\r\n\r\n* Another project [...] solved this by [...]\r\n--\u003e","author":{"url":"https://github.com/ianhi","@type":"Person","name":"ianhi"},"datePublished":"2021-01-08T04:12:54.000Z","interactionStatistic":{"@type":"InteractionCounter","interactionType":"https://schema.org/CommentAction","userInteractionCount":4},"url":"https://github.com/19256/matplotlib/issues/19256"}
| route-pattern | /_view_fragments/issues/show/:user_id/:repository/:id/issue_layout(.:format) |
| route-controller | voltron_issues_fragments |
| route-action | issue_layout |
| fetch-nonce | v2:f68feb43-683e-ad7e-3bbc-07b1bbe1f01f |
| current-catalog-service-hash | 81bb79d38c15960b92d99bca9288a9108c7a47b18f2423d0f6438c5b7bcd2114 |
| request-id | 99C6:95C7C:1D0129F:27BE074:6A5485E4 |
| html-safe-nonce | ca2c839b4277ab51c763f3f8d7abfc3ce4c088f3f688490f8d88efd29d3c64e7 |
| visitor-payload | eyJyZWZlcnJlciI6IiIsInJlcXVlc3RfaWQiOiI5OUM2Ojk1QzdDOjFEMDEyOUY6MjdCRTA3NDo2QTU0ODVFNCIsInZpc2l0b3JfaWQiOiI4ODU5NzUzNjg5NTUzNjQ4MzYiLCJyZWdpb25fZWRnZSI6ImlhZCIsInJlZ2lvbl9yZW5kZXIiOiJpYWQifQ== |
| visitor-hmac | 2e45070e3ea82ae7f897c1640c27690be79454de8a75cf1108bfd10dac4965c4 |
| hovercard-subject-tag | issue:781828969 |
| github-keyboard-shortcuts | repository,issues,copilot |
| google-site-verification | Apib7-x98H0j5cPqHWwSMm6dNU4GmODRoqxLiDzdx9I |
| octolytics-url | https://collector.github.com/github/collect |
| analytics-location | / |
| fb:app_id | 1401488693436528 |
| apple-itunes-app | app-id=1477376905, app-argument=https://github.com/_view_fragments/issues/show/matplotlib/matplotlib/19256/issue_layout |
| twitter:image | https://opengraph.githubassets.com/88f9b7f0a3ec7bc8e6c0e3da156c5c649805057d65424fdeffd71a0e1aaa9f34/matplotlib/matplotlib/issues/19256 |
| twitter:card | summary_large_image |
| og:image | https://opengraph.githubassets.com/88f9b7f0a3ec7bc8e6c0e3da156c5c649805057d65424fdeffd71a0e1aaa9f34/matplotlib/matplotlib/issues/19256 |
| og:image:alt | Problem I've never loved the way Matplotlib Slider widgets look (and per jupyter-widgets/ipywidgets#3025 (comment) I am apparently not alone). However, beyond that I think that the current way the ... |
| og:image:width | 1200 |
| og:image:height | 600 |
| og:site_name | GitHub |
| og:type | object |
| og:author:username | ianhi |
| hostname | github.com |
| expected-hostname | github.com |
| None | b9a586c06a05a7a86fc7e3f4dbd03e42f6869085879aa184aa6369456dbd50fb |
| turbo-cache-control | no-preview |
| go-import | github.com/matplotlib/matplotlib git https://github.com/matplotlib/matplotlib.git |
| octolytics-dimension-user_id | 215947 |
| octolytics-dimension-user_login | matplotlib |
| octolytics-dimension-repository_id | 1385122 |
| octolytics-dimension-repository_nwo | matplotlib/matplotlib |
| octolytics-dimension-repository_public | true |
| octolytics-dimension-repository_is_fork | false |
| octolytics-dimension-repository_network_root_id | 1385122 |
| octolytics-dimension-repository_network_root_nwo | matplotlib/matplotlib |
| turbo-body-classes | logged-out env-production page-responsive |
| disable-turbo | false |
| browser-stats-url | https://api.github.com/_private/browser/stats |
| browser-errors-url | https://api.github.com/_private/browser/errors |
| release | 03ccb116376fd46e8ba2a4cdc541e35f13f70734 |
| ui-target | full |
| theme-color | #1e2327 |
| color-scheme | light dark |
Links:
Viewport: width=device-width