Skip to content
This repository has been archived by the owner on Jun 3, 2024. It is now read-only.

Slider tooltips #564

Merged
merged 15 commits into from
Jun 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ All notable changes to this project will be documented in this file.
This project adheres to [Semantic Versioning](http://semver.org/).

## [Unreleased]
### Added
- Ability to add tooltips to `Slider` and `RangeSlider`, which can be visible always or on hover. Tooltips also take a position argument. [#564](https://github.com/plotly/dash-core-components/pull/564)

### Fixed
- Fixed `min_date_allowed` and `max_date_allowed` bug in `DatePickerRange` [#551]https://github.com/plotly/dash-core-components/issues/551)
- Fixed unwanted `resize()` calls on unmounted `Graph`s [#534](https://github.com/plotly/dash-core-components/issues/534)
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
"moment": "^2.20.1",
"prop-types": "^15.6.0",
"ramda": "^0.24.1",
"rc-slider": "^8.3.1",
"rc-slider": "^8.6.11",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Necessary minor upgrade that adds the tipProps API (undocumented; last update to the changelog / HISTORY.MD is from 2016).

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like its repo has moved to https://github.com/react-component/slider - HISTORY.MD is still pretty sparse there, but tipProps appears to have been added back in 8.1.something. Anyway, I'm happy to have this up to date!

"react-addons-shallow-compare": "^15.6.0",
"react-dates": "^20.1.0",
"react-docgen": "^3.0.0",
Expand Down
54 changes: 52 additions & 2 deletions src/components/RangeSlider.react.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import React, {Component} from 'react';
import PropTypes from 'prop-types';
import {omit} from 'ramda';
import {Range} from 'rc-slider';
import {Range, createSliderWithTooltip} from 'rc-slider';

/**
* A double slider with two handles.
Expand All @@ -11,13 +11,21 @@ export default class RangeSlider extends Component {
constructor(props) {
super(props);
this.propsToState = this.propsToState.bind(this);
this.DashSlider = props.tooltip
? createSliderWithTooltip(Range)
: Range;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will break if someone starts out with no tooltips and adds them later.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK — I added this back to the render method; that should be fine right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

On second thought, putting this in the render() method will break dragging behavior (unfortunately my screen recorder can't record a cursor; I'm dragging after hovering on the handle):

drag

I'm not sure I actually understand the problem with this in the constructor. If I have a hot-reload app, and I add/remove the tooltip prop in Range/RangeSlider, Dash will handle it fine. The only thing that will be reset is any value that has been changed via user interaction (i.e. dragging/clicking the handle).

This is a simple app I'm using to test
import dash
import dash_core_components as dcc
import dash_html_components as html

app = dash.Dash(__name__)
server = app.server

app.layout = html.Div([
    html.H1("Tooltips"),
    html.Br(),
    html.H1("Slider, always visible"),
    dcc.Slider(
        id='my-slider',
        min=0,
        max=20,
        step=0.5,
        value=10,
        tooltip={'always_visible': True}
    ),

    html.Br(),

    html.H1("RangeSlider, on hover"),
    dcc.RangeSlider(
        id='my-range-slider',
        min=0,
        max=20,
        step=0.5,
        value=[5,10],
        tooltip={'placement': 'bottom'}
    ),
])

if __name__ == '__main__':
  app.run_server(debug=True)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The problem is if tooltip is the output of a callback

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah right, of course. I've added the same conditional to componentWillReceiveProps and tooltip callabacks + normal drag behaviour appear to be working correctly now.

}

propsToState(newProps) {
this.setState({value: newProps.value});
}

componentWillReceiveProps(newProps) {
if (newProps.tooltip !== this.props.tooltip) {
this.DashSlider = newProps.tooltip
? createSliderWithTooltip(Range)
: Range;
}
this.propsToState(newProps);
}

Expand All @@ -31,11 +39,27 @@ export default class RangeSlider extends Component {
id,
loading_state,
setProps,
tooltip,
updatemode,
vertical,
} = this.props;
const value = this.state.value;

let tipProps;
if (tooltip && tooltip.always_visible) {
/**
* clone `tooltip` but with renamed key `always_visible` -> `visible`
* 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 = Object.assign(tooltip, {
visible: tooltip.always_visible,
});
delete tipProps.always_visible;
} else {
tipProps = tooltip;
}

return (
<div
id={id}
Expand All @@ -45,7 +69,7 @@ export default class RangeSlider extends Component {
className={className}
style={vertical ? {height: '100%'} : {}}
>
<Range
<this.DashSlider
onChange={value => {
if (updatemode === 'drag') {
setProps({value});
Expand All @@ -58,6 +82,7 @@ export default class RangeSlider extends Component {
setProps({value});
}
}}
tipProps={tipProps}
value={value}
{...omit(
['className', 'value', 'setProps', 'updatemode'],
Expand Down Expand Up @@ -152,6 +177,31 @@ RangeSlider.propTypes = {
*/
pushable: PropTypes.oneOfType([PropTypes.bool, PropTypes.number]),

tooltip: PropTypes.exact({
/**
* Determines whether tooltips should always be visible
* (as opposed to the default, visible on hover)
*/
always_visible: PropTypes.bool,

/**
* Determines the placement of tooltips
* See https://github.com/react-component/tooltip#api
* top/bottom{*} sets the _origin_ of the tooltip, so e.g. `topLeft` will
* in reality appear to be on the top right of the handle
*/
placement: PropTypes.oneOf([
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made a mistake before setting the key as position, it should be placement

'left',
'right',
'top',
'bottom',
'topLeft',
'topRight',
'bottomLeft',
'bottomRight',
]),
}),

/**
* Value by which increments or decrements are made
*/
Expand Down
54 changes: 52 additions & 2 deletions src/components/Slider.react.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {Component} from 'react';
import ReactSlider from 'rc-slider';
import ReactSlider, {createSliderWithTooltip} from 'rc-slider';
import PropTypes from 'prop-types';
import {omit} from 'ramda';
import './css/[email protected]';
Expand All @@ -11,13 +11,21 @@ export default class Slider extends Component {
constructor(props) {
super(props);
this.propsToState = this.propsToState.bind(this);
this.DashSlider = props.tooltip
? createSliderWithTooltip(ReactSlider)
: ReactSlider;
}

propsToState(newProps) {
this.setState({value: newProps.value});
}

componentWillReceiveProps(newProps) {
if (newProps.tooltip !== this.props.tooltip) {
this.DashSlider = newProps.tooltip
? createSliderWithTooltip(ReactSlider)
: ReactSlider;
}
this.propsToState(newProps);
}

Expand All @@ -31,11 +39,27 @@ export default class Slider extends Component {
id,
loading_state,
setProps,
tooltip,
updatemode,
vertical,
} = this.props;
const value = this.state.value;

let tipProps;
if (tooltip && tooltip.always_visible) {
/**
* clone `tooltip` but with renamed key `always_visible` -> `visible`
* 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 = Object.assign(tooltip, {
visible: tooltip.always_visible,
});
delete tipProps.always_visible;
} else {
tipProps = tooltip;
}

return (
<div
id={id}
Expand All @@ -45,7 +69,7 @@ export default class Slider extends Component {
className={className}
style={vertical ? {height: '100%'} : {}}
>
<ReactSlider
<this.DashSlider
onChange={value => {
if (updatemode === 'drag') {
setProps({value});
Expand All @@ -58,6 +82,7 @@ export default class Slider extends Component {
setProps({value});
}
}}
tipProps={tooltip}
value={value}
{...omit(
['className', 'setProps', 'updatemode', 'value'],
Expand Down Expand Up @@ -133,6 +158,31 @@ Slider.propTypes = {
*/
max: PropTypes.number,

tooltip: PropTypes.exact({
/**
* Determines whether tooltips should always be visible
* (as opposed to the default, visible on hover)
*/
always_visible: PropTypes.bool,

/**
* Determines the placement of tooltips
* See https://github.com/react-component/tooltip#api
* top/bottom{*} sets the _origin_ of the tooltip, so e.g. `topLeft` will
* in reality appear to be on the top right of the handle
*/
placement: PropTypes.oneOf([
'left',
'right',
'top',
'bottom',
'topLeft',
'topRight',
'bottomLeft',
'bottomRight',
]),
}),

/**
* Value by which increments or decrements are made
*/
Expand Down