diff --git a/CHANGELOG.md b/CHANGELOG.md index 01c16c55fa..873b4b1143 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ This project adheres to [Semantic Versioning](https://semver.org/). ## Added - [#2695](https://github.com/plotly/dash/pull/2695) Adds `triggered_id` to `dash_clientside.callback_context`. Fixes [#2692](https://github.com/plotly/dash/issues/2692) +- [#2723](https://github.com/plotly/dash/pull/2723) Improve dcc Slider/RangeSlider tooltips. Fixes [#1846](https://github.com/plotly/dash/issues/1846) + - Add `tooltip.format` a string for the format template, {value} will be formatted with the actual value. + - Add `tooltip.style` a style object to give to the div of the tooltip. - [#2732](https://github.com/plotly/dash/pull/2732) Add special key `_dash_error` to `setProps`, allowing component developers to send error without throwing in render. Usage `props.setProps({_dash_error: new Error("custom error")})` ## Fixed diff --git a/components/dash-core-components/src/components/RangeSlider.react.js b/components/dash-core-components/src/components/RangeSlider.react.js index 6db2a43b76..93dbf015d6 100644 --- a/components/dash-core-components/src/components/RangeSlider.react.js +++ b/components/dash-core-components/src/components/RangeSlider.react.js @@ -125,6 +125,31 @@ RangeSlider.propTypes = { 'bottomLeft', 'bottomRight', ]), + /** + * Template string to display the tooltip in. + * Must contain `{value}`, which will be replaced with either + * the default string representation of the value or the result of the + * transform function if there is one. + */ + template: PropTypes.string, + /** + * Custom style for the tooltip. + */ + style: PropTypes.object, + /** + * Reference to a function in the `window.dccFunctions` namespace. + * This can be added in a script in the asset folder. + * + * For example, in `assets/tooltip.js`: + * ``` + * window.dccFunctions = window.dccFunctions || {}; + * window.dccFunctions.multByTen = function(value) { + * return value * 10; + * } + * ``` + * Then in the component `tooltip={'transform': 'multByTen'}` + */ + transform: PropTypes.string, }), /** diff --git a/components/dash-core-components/src/components/Slider.react.js b/components/dash-core-components/src/components/Slider.react.js index 00c6c41a18..7e642591c2 100644 --- a/components/dash-core-components/src/components/Slider.react.js +++ b/components/dash-core-components/src/components/Slider.react.js @@ -2,6 +2,8 @@ import React, {Component, lazy, Suspense} from 'react'; import PropTypes from 'prop-types'; import slider from '../utils/LazyLoader/slider'; +import './css/sliders.css'; + const RealSlider = lazy(slider); /** @@ -105,6 +107,31 @@ Slider.propTypes = { 'bottomLeft', 'bottomRight', ]), + /** + * Template string to display the tooltip in. + * Must contain `{value}`, which will be replaced with either + * the default string representation of the value or the result of the + * transform function if there is one. + */ + template: PropTypes.string, + /** + * Custom style for the tooltip. + */ + style: PropTypes.object, + /** + * Reference to a function in the `window.dccFunctions` namespace. + * This can be added in a script in the asset folder. + * + * For example, in `assets/tooltip.js`: + * ``` + * window.dccFunctions = window.dccFunctions || {}; + * window.dccFunctions.multByTen = function(value) { + * return value * 10; + * } + * ``` + * Then in the component `tooltip={'transform': 'multByTen'}` + */ + transform: PropTypes.string, }), /** diff --git a/components/dash-core-components/src/components/css/sliders.css b/components/dash-core-components/src/components/css/sliders.css new file mode 100644 index 0000000000..664ddc6a50 --- /dev/null +++ b/components/dash-core-components/src/components/css/sliders.css @@ -0,0 +1,5 @@ +/* Fix the default tooltip style height conflicting with the actual size of the tooltip. */ +.rc-slider-tooltip-content > .rc-slider-tooltip-inner { + height: unset; + min-height: 20px; +} diff --git a/components/dash-core-components/src/fragments/RangeSlider.react.js b/components/dash-core-components/src/fragments/RangeSlider.react.js index 5e93772a0b..3c4f79c942 100644 --- a/components/dash-core-components/src/fragments/RangeSlider.react.js +++ b/components/dash-core-components/src/fragments/RangeSlider.react.js @@ -1,5 +1,5 @@ import React, {Component} from 'react'; -import {assoc, pick, isNil} from 'ramda'; +import {assoc, pick, isNil, pipe, omit} from 'ramda'; import {Range, createSliderWithTooltip} from 'rc-slider'; import computeSliderStyle from '../utils/computeSliderStyle'; @@ -11,6 +11,10 @@ import { setUndefined, } from '../utils/computeSliderMarkers'; import {propTypes, defaultProps} from '../components/RangeSlider.react'; +import { + formatSliderTooltip, + transformSliderTooltip, +} from '../utils/formatSliderTooltip'; const sliderProps = [ 'min', @@ -72,17 +76,33 @@ export default class RangeSlider extends Component { } = this.props; const value = this.state.value; - let tipProps; - if (tooltip && tooltip.always_visible) { + let tipProps, tipFormatter; + if (tooltip) { /** * clone `tooltip` but with renamed key `always_visible` -> `visible` - * the rc-tooltip API uses `visible`, but `always_visible is more semantic + * the rc-tooltip API uses `visible`, but `always_visible` is more semantic * assigns the new (renamed) key to the old key and deletes the old key */ - tipProps = assoc('visible', tooltip.always_visible, tooltip); - delete tipProps.always_visible; - } else { - tipProps = tooltip; + tipProps = pipe( + assoc('visible', tooltip.always_visible), + omit(['always_visible', 'template', 'style', 'transform']) + )(tooltip); + if (tooltip.template || tooltip.style || tooltip.transform) { + tipFormatter = tipValue => { + let t = tipValue; + if (tooltip.transform) { + t = transformSliderTooltip(tooltip.transform, tipValue); + } + return ( +