39 |
47 |
48 | {this.props.children}
49 |
50 |
51 |
52 | );
53 | }
54 | }
55 |
56 | dash_draggable.defaultProps = {};
57 |
58 | dash_draggable.propTypes = {
59 | /**
60 | * The ID used to identify this component in Dash callbacks
61 | */
62 | id: PropTypes.string,
63 |
64 | /**
65 | * Dash-assigned callback that should be called whenever any of the
66 | * properties change
67 | */
68 | setProps: PropTypes.func,
69 |
70 | /**
71 | * ...
72 | */
73 | onStop: PropTypes.func,
74 |
75 | /**
76 | * ...
77 | */
78 | axis: PropTypes.string,
79 |
80 | /**
81 | * ...
82 | */
83 | handle: PropTypes.string,
84 |
85 | /**
86 | * ...
87 | */
88 | defaultPosition: PropTypes.object,
89 |
90 | /**
91 | * ...
92 | */
93 | position: PropTypes.object,
94 |
95 | /**
96 | * ...
97 | */
98 | grid: PropTypes.array,
99 |
100 | /**
101 | * ...
102 | */
103 | lastX: PropTypes.number,
104 |
105 | /**
106 | * ...
107 | */
108 | lastY: PropTypes.number,
109 |
110 | /**
111 | * ...
112 | */
113 | deltaX: PropTypes.number,
114 |
115 | /**
116 | * ...
117 | */
118 | deltaY: PropTypes.number,
119 |
120 | /**
121 | * ...
122 | */
123 | moved: PropTypes.bool,
124 |
125 | /**
126 | * ...
127 | */
128 | children: PropTypes.node,
129 |
130 | /**
131 | * ...
132 | */
133 | disabled: PropTypes.bool
134 | };
135 |
136 | export default dash_draggable;
137 |
--------------------------------------------------------------------------------
/src/lib/index.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable import/prefer-default-export */
2 | import dash_draggable from './components/dash_draggable.react';
3 |
4 | export {
5 | dash_draggable
6 | };
7 |
--------------------------------------------------------------------------------
/tests/__init__.py:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xhluca/dash-draggable/0e8324cedd66fc78d6131708e8106a3a038f8d5c/tests/__init__.py
--------------------------------------------------------------------------------
/tests/requirements.txt:
--------------------------------------------------------------------------------
1 | # Packages needed to run the tests.
2 | # Switch into a virtual environment
3 | # pip install -r requirements.txt
4 |
5 | chromedriver-binary
6 | dash
7 | dash-core-components
8 | dash-html-components
9 | dash-renderer
10 | ipdb
11 | percy
12 | selenium
13 | flake8
14 | pylint
15 | pytest-dash
16 |
--------------------------------------------------------------------------------
/tests/test_usage.py:
--------------------------------------------------------------------------------
1 | # Basic test for the component rendering.
2 | def test_render_component(dash_app, selenium):
3 | # Start a dash app contained in `usage.py`
4 | # dash_app is a fixture by pytest-dash
5 | # It will load a py file containing a Dash instance named `app`
6 | # and start it in a thread.
7 | app = dash_app('usage.py')
8 |
9 | # Get the generated component with selenium
10 | my_component = selenium.find_element_by_id('input')
11 |
--------------------------------------------------------------------------------
/usage.py:
--------------------------------------------------------------------------------
1 | import dash_draggable
2 | import dash
3 | from dash.dependencies import Input, Output
4 | import dash_html_components as html
5 | import dash_core_components as dcc
6 | import plotly.graph_objs as go
7 | import dash_daq as daq
8 |
9 |
10 | app = dash.Dash(__name__)
11 |
12 | app.scripts.config.serve_locally = True
13 | app.css.config.serve_locally = True
14 | app.config['suppress_callback_exceptions'] = True
15 |
16 | app.layout = html.Div([
17 | daq.BooleanSwitch(id='toggle-drag', on=True),
18 | html.Div(id='status'),
19 | html.Div(id='print'),
20 | html.Div(
21 | style={'width': '30vw', 'display': 'inline-flex'},
22 | children=dash_draggable.dash_draggable(
23 | id='draggable',
24 | axis="both",
25 | handle=".handle",
26 | defaultPosition={'x': 0, 'y': 100},
27 | position=None,
28 | grid=[12, 12],
29 | children=[
30 | html.Div(
31 | id='a-div',
32 | className='handle',
33 | children=dcc.Graph(
34 | id='example-graph',
35 | figure={
36 | 'data': [
37 | {'x': [1, 2, 3], 'y': [4, 1, 2], 'type': 'bar',
38 | 'name': 'SF'},
39 | {'x': [1, 2, 3], 'y': [2, 4, 5], 'type': 'bar',
40 | 'name': u'Montréal'},
41 | ],
42 | 'layout': {
43 | 'title': 'Drag anywhere'
44 | }
45 | }
46 | )
47 | )
48 | ]
49 | )),
50 |
51 | html.Div(
52 | style={'width': '30vw', 'display': 'inline-flex'},
53 | children=dash_draggable.dash_draggable(
54 | id='draggable-2',
55 | axis='x',
56 | handle='.handle',
57 | defaultPosition={'x': 200, 'y': 100},
58 | position=None,
59 | grid=[25, 25],
60 | children=[
61 | html.Div(
62 | id='another-div',
63 | className='handle',
64 | children=[
65 | dcc.Graph(
66 | id='example2',
67 | figure=dict(
68 | data=[go.Pie(
69 | labels=['Oxygen', 'Hydrogen',
70 | 'Carbon_Dioxide',
71 | 'Nitrogen'],
72 | values=[4500, 2500, 1053, 500],
73 | )],
74 | layout=dict(
75 | title='Drag horizontally'
76 | )
77 | )
78 | )
79 | ]
80 | )
81 | ]
82 | ))
83 | ])
84 |
85 |
86 | @app.callback(
87 | Output('print', 'children'),
88 | [Input('draggable', 'deltaX'),
89 | Input('draggable', 'deltaY')]
90 | )
91 | def print_test(event, position):
92 | return html.Div([html.P("{}".format(position)),
93 | html.P("{}".format(event))])
94 |
95 |
96 | # Disable/Enable dragging on component
97 | @app.callback(
98 | Output('draggable', 'disabled'),
99 | [Input('toggle-drag', 'on')]
100 | )
101 | def toggle_drag(toggle_status):
102 | # True/False
103 | return not toggle_status
104 |
105 |
106 | # Tell user if dragging is enabled and for which component
107 | @app.callback(
108 | Output('status', 'children'),
109 | [Input('toggle-drag', 'on')]
110 | )
111 | def can_drag(toggle_status):
112 | return html.P("'Drag Anywhere' Component Draggable: {}".format(toggle_status))
113 |
114 |
115 | if __name__ == '__main__':
116 | app.run_server(debug=True)
117 |
--------------------------------------------------------------------------------
/usage_prettyhandle.py:
--------------------------------------------------------------------------------
1 | import dash
2 | import dash_html_components as html
3 | from dash.dependencies import Output, Input, State
4 | import dash_core_components as dcc
5 | import dash_daq as daq
6 | import dash_draggable as drag
7 | from dash.exceptions import PreventUpdate
8 | import sd_material_ui
9 |
10 | external_stylesheets = ['https://codepen.io/chriddyp/pen/bWLwgP.css']
11 |
12 | app = dash.Dash(external_stylesheets=external_stylesheets)
13 | app.config['suppress_callback_exceptions'] = True
14 |
15 | app.layout = html.Div([
16 |
17 | dcc.Store(id='position-store', storage_type='session', data={'lastXs': [200], 'lastYs': [0]}),
18 |
19 | drag.dash_draggable(id='dragger',
20 | handle='.handle',
21 | defaultPosition={'x': 200, 'y': 0},
22 | children=[
23 | html.Div([
24 | sd_material_ui.Paper(children=[
25 | sd_material_ui.IconButton(
26 | id='button',
27 | iconClassName='glyphicon glyphicon-menu-hamburger',
28 | iconStyle={'color': 'grey',
29 | 'width': 50,
30 | 'height': 50,
31 | 'position': 'relative',
32 | 'top': '2px',
33 | 'left': '-12px'},
34 | tooltip='Drag Me', touch=True,
35 | tooltipPosition='bottom-right')],
36 | zDepth=3,
37 | circle=True,
38 | style=dict(height=50,
39 | width=50,
40 | textAlign='center',
41 | position='relative',
42 | display='inline-block',
43 | top='25px',
44 | left='-25px')
45 | )], className='handle'),
46 |
47 | html.Div(id='graph-container')
48 | ]),
49 | ])
50 |
51 |
52 | ############################################################################
53 | # Commit last known position to memory
54 | @app.callback(
55 | Output('position-store', 'data'),
56 | [Input('dragger', 'lastX'),
57 | Input('dragger', 'lastY')],
58 | [State('position-store', 'data')]
59 | )
60 | def remember_position(lastX, lastY, data):
61 | data = data or {}
62 | x_history = data['lastXs'] or []
63 | y_history = data['lastYs'] or []
64 | if lastX is not None:
65 | x_history.append(lastX)
66 | if lastY is not None:
67 | y_history.append(lastY)
68 |
69 | data = {'lastXs': x_history, 'lastYs': y_history}
70 | return data
71 |
72 |
73 | ############################################################################
74 |
75 | @app.callback(
76 | Output('graph-container', 'children'),
77 | [Input('button', 'n_clicks'),
78 | Input('position-store', 'modified_timestamp')],
79 | [State('position-store', 'data'),
80 | State('graph-container', 'children')]
81 | )
82 | def hide_graph(clicked: int, timestamp, data, content):
83 | if timestamp is None:
84 | raise PreventUpdate
85 |
86 | xs = data.get('lastXs')
87 | ys = data.get('lastYs')
88 |
89 | if len(xs) > 1:
90 | x_prev = xs[-2]
91 | y_prev = ys[-2]
92 | x = xs[-1]
93 | y = ys[-1]
94 | else:
95 | x_prev = y_prev = x = y = None
96 |
97 | KNOB = sd_material_ui.Paper(zDepth=1,
98 | style=dict(height=625,
99 | width=750),
100 | children=[
101 | html.Div([
102 | daq.Knob(
103 | label="Gradient Ranges",
104 | value=7,
105 | size=500,
106 | color={"gradient": True,
107 | "ranges": {"red": [0, 5], "yellow": [5, 9],
108 | "green": [9, 10]}},
109 | style=dict(position='relative',
110 | top='25px',
111 | left='0px'))
112 | ])
113 | ]),
114 |
115 | if x_prev != x or y_prev != y: # Moved
116 | return content
117 | else:
118 | if content is None or content == []:
119 | return KNOB
120 | elif content:
121 | return []
122 |
123 |
124 |
125 | if __name__ == '__main__':
126 | app.css.append_css({'external_url': 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css'})
127 | app.run_server(debug=True)
128 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const path = require('path');
2 | const packagejson = require('./package.json');
3 |
4 | const dashLibraryName = packagejson.name.replace(/-/g, '_');
5 |
6 | module.exports = (env, argv) => {
7 |
8 | let mode;
9 |
10 |
11 | // if user specified mode flag take that value
12 | if (argv && argv.mode) {
13 | mode = argv.mode;
14 | }
15 |
16 | // else if configuration object is already set (module.exports) use that value
17 | else if (module.exports && module.exports.mode) {
18 | mode = module.exports = mode;
19 | }
20 |
21 | // else take webpack default
22 | else {
23 | mode = 'production'; // webpack default
24 | }
25 |
26 | return {
27 | entry: {main: './src/lib/index.js'},
28 | output: {
29 | path: path.resolve(__dirname, dashLibraryName),
30 | filename: `${dashLibraryName}.${mode === 'development' ? 'dev' : 'min'}.js`,
31 | library: dashLibraryName,
32 | libraryTarget: 'window',
33 | },
34 | externals: {
35 | react: 'React',
36 | 'react-dom': 'ReactDOM',
37 | 'plotly.js': 'Plotly',
38 | },
39 | module: {
40 | rules: [
41 | {
42 | test: /\.js$/,
43 | exclude: /node_modules/,
44 | use: {
45 | loader: 'babel-loader',
46 | },
47 | },
48 | {
49 | test: /\.css$/,
50 | use: [
51 | {
52 | loader: 'style-loader',
53 | },
54 | {
55 | loader: 'css-loader',
56 | },
57 | ],
58 | },
59 | ],
60 | },
61 | devtool: mode === 'development' ? "eval-source-map" : 'none'
62 | }
63 | };
64 |
--------------------------------------------------------------------------------
/webpack.serve.config.js:
--------------------------------------------------------------------------------
1 | const config = require('./webpack.config.js');
2 |
3 | config.entry = {main: './src/demo/index.js'};
4 | config.output = {filename: 'output.js'};
5 | config.mode = 'development';
6 | config.externals = undefined; // eslint-disable-line
7 | config.devtool = 'inline-source-map';
8 | module.exports = config;
9 |
--------------------------------------------------------------------------------