Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Dash UI customization example #273

Merged
merged 5 commits into from
Aug 29, 2024
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
131 changes: 131 additions & 0 deletions examples/dash/dash-customization/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
# Comprehensive Guide to Dash UI Customization

Explore various methods to customize your Dash app UI, from using styling with CSS and Dash Bootstrap Components, to building custom Dash components to achieve advanced customization using React.

1. Inline CSS Styling
2. Using Your Own CSS Files
3. Dash Bootstrap Components
4. Custom Callbacks for Dynamic Styling
5. Building Custom Dash Components

Each section includes simple scripts that feature a title and a button, demonstrating how these methods can be practically applied to enhance your application's aesthetics and functionality.

## Methods for UI Customization in Dash
### 1. Inline CSS Styling
#### Example Code
- `app_inline.py`

### 2. Using Your Own CSS Files
#### Example Code
- `app_css.py`
- `assets/style.css`

### 3. Dash Bootstrap Components
#### Prerequisites
- Run `pip install dash-bootstrap-components`
#### Example Code
- `app_bootstrap.py`

### 4. Custom Callbacks for Dynamic Styling
#### Prerequisites
- Run `pip install dash-bootstrap-components` (note: `dash-bootstrap-components` is not required to implement custom callback functions, but it is used in `app_callback.py` as it is implemented on top of `app_bootstrap.py`.)
#### Example Code
- `app_callback.py`


### 5. Building Custom Dash Components
#### Step-by-Step
- Download and install Node.js and npm from the [Node.js official website](https://nodejs.org/en)
- Run `pip install cookiecutter`
- Run `pip install virtualenv`
- Run `cookiecutter gh:plotly/dash-component-boilerplate`
- Navigate into the newly created `<project_shortname>` directory and update `usage.py` for your Dash app and `src/lib/components/<component_name>.react.js` for your custom component
- `npm run build` to compile
- Run `python usage.py` and check out your Dash app

Note: To learn more about the `dash-component-boilerplate`, refer to [here](https://github.com/plotly/dash-component-boilerplate).

#### Example Code for Custom Button Component and Deployment on Ploomber Cloud
- `custom_component/app_custom.py`: Main script for the Dash app (can replace the `usage.py` file)
- `custom_component/CustomButton.react.js`: Main source code for the custom component (can replace the `src/lib/components/<component_name>.react.js` file)
- `custom_component/requirements.txt`: For deployment on Ploomber Cloud
- `custom_component/demo/`: The generated Python scripts required for deployment on Ploomber Cloud


## Deployment on Ploomber Cloud

To deploy your Dash app on Ploomber Cloud, you need:

- `app.py`
- `requirements.txt`

You should ensure your Dash app script is called `app.py`. Rename the one you want to deploy to `app.py`. Also, your `requirements.txt` should be:

```sh
dash
dash-bootstrap-components
```

Note: If you're deploying the last example that includes a custom component, ensure you also include the auto-generated scripts required for proper functionality:
- `<project_shortname>/__init__.py`
- `<project_shortname>/_imports_.py`
- `<project_shortname>/<component_name>.py`
- `<project_shortname>/<project_shortname>.min.js`
- `<project_shortname>/<project_shortname>.min.js.map`
- `<project_shortname>/package-info.json`

### Graphical User Interface (GUI)

Log into your [Ploomber Cloud account](https://www.platform.ploomber.io/applications).

Click the NEW button to start the deployment process:

![GUI Deployment](assets/gui_deploy1.png)

Select the Dash option, and upload your code as a zip file in the source code section:

![GUI Deployment](assets/gui_deploy2.png)

After optionally customizing some settings, click `CREATE`.

### Command Line Interface (CLI)

If you haven't installed `ploomber-cloud`, run:
```sh
pip install ploomber-cloud
```

Set your API key following [this documentation](https://docs.cloud.ploomber.io/en/latest/quickstart/apikey.html).
```sh
ploomber-cloud key YOURKEY
```

Navigate to your project directory where your files are located:
```sh
cd <project-name>
```

Then, initialize the project and confirm the inferred project type (Dash) when prompted:
```sh
(testing_dash_app) ➜ ploomber-cloud init ✭ ✱
Initializing new project...
Inferred project type: 'dash'
Is this correct? [y/N]: y
Your app '<id>' has been configured successfully!
To configure resources for this project, run 'ploomber-cloud resources' or to deploy with default configurations, run 'ploomber-cloud deploy'
```

Deploy your application and monitor the deployment at the provided URL:

```sh
(testing_dash_app) ➜ cloud ploomber-cloud deploy ✭ ✱
Compressing app...
Adding app.py...
Ignoring file: ploomber-cloud.json
Adding requirements.txt...
App compressed successfully!
Deploying project with id: <id>...
The deployment process started! Track its status at: https://www.platform.ploomber.io/applications/<id>/<job_id>
```

For more details, refer to this [documentation](https://docs.cloud.ploomber.io/en/latest/user-guide/cli.html).
13 changes: 13 additions & 0 deletions examples/dash/dash-customization/app_bootstrap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
from dash import Dash, html
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.MINTY])
server = app.server

app.layout = dbc.Container([
dbc.Row(dbc.Col(html.H1('Welcome to Dash Customization Demo!', className='text-center my-4'), width=12)),
dbc.Row(dbc.Col(dbc.Button('Click me!', color='primary', className='w-100 mt-4 mb-4'), width=12))
], fluid=True)

if __name__ == '__main__':
app.run_server(debug=True)
19 changes: 19 additions & 0 deletions examples/dash/dash-customization/app_callback.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from dash import Dash, html, Input, Output
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.MINTY])
server = app.server

app.layout = dbc.Container([
dbc.Row(dbc.Col(html.H1('Welcome to Dash Customization Demo!', className='text-center my-4'), width=12)),
dbc.Row(dbc.Col(dbc.Button('Click me!', id='btn', n_clicks=0, color='primary', className='w-100 mt-4 mb-4'), width=12))
], fluid=True)

@app.callback(Output('btn', 'color'), Input('btn', 'n_clicks'))
def update_style(n_clicks):
colors = ['primary', 'secondary', 'warning']
return colors[n_clicks % 3]


if __name__ == '__main__':
app.run_server(debug=True)
17 changes: 17 additions & 0 deletions examples/dash/dash-customization/app_css.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from dash import html, Dash

app = Dash(__name__)
server = app.server

app.layout = html.Div([
html.H1(
'Welcome to Dash Customization Demo!',
className='center-text'
),
html.Button('Click me 1!', className='button green'),
html.Button('Click me 2!', className='button blue'),
html.Button('Click me 3!', className='button red')
], className='padding-20')

if __name__ == '__main__':
app.run_server(debug=True)
24 changes: 24 additions & 0 deletions examples/dash/dash-customization/app_inline.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from dash import html, Dash

app = Dash(__name__)
server = app.server

app.layout = html.Div([
html.H1(
'Welcome to Dash Customization Demo!',
style={'textAlign': 'center'}
),
html.Button(
'Click me!',
style={
'width': '100%',
'padding': '10px',
'background': 'darkgreen',
'color': 'white',
'fontSize': '16px'
}
)
], style={'padding': '20px'})

if __name__ == '__main__':
app.run_server(debug=True)
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
26 changes: 26 additions & 0 deletions examples/dash/dash-customization/assets/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
.button {
width: 100%;
padding: 10px;
color: white;
font-size: 16px;
}

.green {
background: darkgreen;
}

.blue {
background: blue;
}

.red {
background: red;
}

.center-text {
text-align: center;
}

.padding-20 {
padding: 20px;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';

/**
* CustomButton is an example component that shows a bouncing button.
*/
const CustomButton = (props) => {
// Extract the props
const { id, label, color, className } = props;
const [isBouncing, setIsBouncing] = useState(false);

// Handle the button click event
const handleClick = () => {
setIsBouncing(true);
setTimeout(() => setIsBouncing(false), 500); // Reset after animation
};

// Determine Bootstrap color class based on the color prop
const colorClass = color ? `btn-${color}` : '';

// Render the button
return (
<button
id={id}
onClick={handleClick}
className={`btn ${colorClass} ${className} ${isBouncing ? 'bouncing' : ''}`}
style={{
padding: '10px 20px',
border: 'none',
borderRadius: '5px',
backgroundColor: color ? '' : '#ff8c00', // Custom fallback color if no Bootstrap color is provided
color: 'white',
fontSize: '16px',
cursor: 'pointer',
outline: 'none',
position: 'relative',
transition: 'transform 0.3s ease',
transform: isBouncing ? 'translateY(-10px)' : 'translateY(0)',
}}
>
{label}
</button>
);
}

CustomButton.propTypes = {
/**
* The ID used to identify this component in Dash callbacks.
*/
id: PropTypes.string,

/**
* A label that will be printed when this component is rendered.
*/
label: PropTypes.string.isRequired,

/**
* The color of the button.
* This should be a Bootstrap color name.
*/
color: PropTypes.string,

/**
* The className of the button.
* This is used to apply custom styles to the button.
*/
className: PropTypes.string,
};

export default CustomButton;
17 changes: 17 additions & 0 deletions examples/dash/dash-customization/custom_component/app_custom.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
from demo import CustomButton
from dash import Dash, html
import dash_bootstrap_components as dbc

app = Dash(__name__, external_stylesheets=[dbc.themes.MINTY])
server = app.server

# Define the layout of the app using Bootstrap components
app.layout = dbc.Container([
dbc.Row(dbc.Col(html.H1('Welcome to Dash Customization Demo!', className='text-center my-4'), width=12)),
dbc.Row(dbc.Col(
CustomButton(id='custom-button', label='Click Me!', color='primary', className='btn btn-primary w-100 mt-4 mb-4'), width=12))
], fluid=True)


if __name__ == '__main__':
app.run_server(debug=True)
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# AUTO GENERATED FILE - DO NOT EDIT

from dash.development.base_component import Component, _explicitize_args


class CustomButton(Component):
"""A CustomButton component.
CustomButton is an example component that shows a bouncing button.
Keyword arguments:
- id (string; optional):
The ID used to identify this component in Dash callbacks.
- className (string; optional):
The className of the button. This is used to apply custom styles
to the button.
- color (string; optional):
The color of the button. This should be a Bootstrap color name.
- label (string; required):
A label that will be printed when this component is rendered."""
_children_props = []
_base_nodes = ['children']
_namespace = 'demo'
_type = 'CustomButton'
@_explicitize_args
def __init__(self, id=Component.UNDEFINED, label=Component.REQUIRED, color=Component.UNDEFINED, className=Component.UNDEFINED, **kwargs):
self._prop_names = ['id', 'className', 'color', 'label']
self._valid_wildcard_attributes = []
self.available_properties = ['id', 'className', 'color', 'label']
self.available_wildcard_properties = []
_explicit_args = kwargs.pop('_explicit_args')
_locals = locals()
_locals.update(kwargs) # For wildcard attrs and excess named props
args = {k: _locals[k] for k in _explicit_args}

for k in ['label']:
if k not in args:
raise TypeError(
'Required argument `' + k + '` was not specified.')

super(CustomButton, self).__init__(**args)
Loading