Skip to content

Commit

Permalink
Create skeleton of the dashboard (#22)
Browse files Browse the repository at this point in the history
* add app main script
* add css custom styles file
* add requirements
* add light/dark switch and set the same colour as mkdocs
* add upload functionality using dash-uploader
* add mockup data
* add disabled tabs functionality
* add tests

Co-authored-by: Cunliang Geng <[email protected]>
  • Loading branch information
gcroci2 and CunliangGeng authored Jul 29, 2024
1 parent 5f63f1b commit c773794
Show file tree
Hide file tree
Showing 11 changed files with 255 additions and 9 deletions.
10 changes: 9 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

.mypy_cache

# Pycache files
__pycache__/
app/__pycache__/

# Other cache files
*_cache

# Mac
.DS_Store

Expand Down Expand Up @@ -34,4 +41,5 @@ ENV/
env.bak/
venv.bak/

nplinker_quickstart/
nplinker_quickstart/
uploads/
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ pip install -r requirements.txt
Run the application:

```bash
python app.py
python app/app.py
```

Open your web browser and go to `http://127.0.0.1:8050/`.
Expand Down
191 changes: 191 additions & 0 deletions app/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
import os
import pickle
import tempfile
import uuid
import dash_bootstrap_components as dbc
import dash_uploader as du
from dash import Dash
from dash import Input
from dash import Output
from dash import clientside_callback
from dash import dcc
from dash import html


dbc_css = "https://cdn.jsdelivr.net/gh/AnnMarieW/dash-bootstrap-templates/dbc.min.css"
app = Dash(__name__, external_stylesheets=[dbc.themes.UNITED, dbc_css, dbc.icons.FONT_AWESOME])

# ------------------ Nav Bar ------------------ #
color_mode_switch = html.Span(
[
dbc.Label(className="fa fa-moon", html_for="color-mode-switch"),
dbc.Switch(
id="color-mode-switch",
value=False,
className="d-inline-block ms-1",
persistence=True,
),
dbc.Label(className="fa fa-sun", html_for="color-mode-switch"),
],
className="p-2",
)

# Define the navigation bar
navbar = dbc.Row(
dbc.Col(
dbc.NavbarSimple(
children=[
dbc.NavItem(dbc.NavLink("Doc", href="https://nplinker.github.io/nplinker/latest/")),
dbc.NavItem(
dbc.NavLink("About", href="https://github.com/NPLinker/nplinker-webapp"),
),
dbc.NavItem(
color_mode_switch,
className="mt-1 p-1",
),
],
brand="NPLinker Webapp",
color="primary",
className="p-3 mb-2",
dark=True,
),
),
)

# ------------------ Uploader ------------------ #
# Configure the upload folder
TEMP_DIR = tempfile.mkdtemp()
du.configure_upload(app, TEMP_DIR)

uploader = html.Div(
[
dbc.Row(
dbc.Col(
du.Upload(
id="dash-uploader",
text="Import Data",
text_completed="Uploaded: ",
filetypes=["pkl", "pickle"],
upload_id=uuid.uuid1(), # Unique session id
cancel_button=True,
max_files=1,
),
)
),
dbc.Row(
dbc.Col(
html.Div(children="No file uploaded", id="dash-uploader-output", className="p-4"),
className="d-flex justify-content-center",
)
),
dcc.Store(id="file-store"), # Store to keep the file contents
],
className="p-5 ml-5 mr-5",
)

# ------------------ Tabs ------------------ #
# gm tab content
gm_content = dbc.Row(
dbc.Col(
dbc.Card(
dbc.CardBody([html.Div(id="file-content-gm")]),
)
)
)
# mg tab content
mg_content = dbc.Row(
dbc.Col(
dbc.Card(
dbc.CardBody([html.Div(id="file-content-mg")]),
)
),
)
# tabs
tabs = dbc.Row(
dbc.Col(
dbc.Tabs(
[
dbc.Tab(
gm_content,
label="Genomics -> Metabolomics",
activeTabClassName="fw-bold",
disabled=True,
id="gm-tab",
className="disabled-tab",
),
dbc.Tab(
mg_content,
label="Metabolomics -> Genomics",
activeTabClassName="fw-bold",
disabled=True,
id="mg-tab",
className="disabled-tab",
),
],
),
),
className="p-5",
)

# ------------------ App Layout ------------------ #
app.layout = dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0")

# ------------------ Callbacks------------------ #
clientside_callback(
"""
(switchOn) => {
document.documentElement.setAttribute('data-bs-theme', switchOn ? 'light' : 'dark');
return window.dash_clientside.no_update
}
""",
Output("color-mode-switch", "id"),
Input("color-mode-switch", "value"),
)


@du.callback(
id="dash-uploader",
output=[Output("dash-uploader-output", "children"), Output("file-store", "data")],
)
def upload_data(status: du.UploadStatus): # noqa: D103
if status.is_completed:
latest_file = status.latest_file
with open(status.latest_file, "rb") as f:
pickle.load(f)
return (
f"Successfully uploaded: {os.path.basename(latest_file)} [{round(status.uploaded_size_mb, 2)} MB]",
str(latest_file),
)
return "No file uploaded", None


@app.callback(
[Output("gm-tab", "disabled"), Output("mg-tab", "disabled")],
[Input("file-store", "data")],
prevent_initial_call=True,
)
def disable_tabs(file_name): # noqa: D103
if file_name is None:
# Disable the tabs
return True, True
# Enable the tabs
return False, False


# Define another callback to access the stored file path and read the file
@app.callback(
[Output("file-content-gm", "children"), Output("file-content-mg", "children")],
[Input("file-store", "data")],
)
def display_file_contents(file_path): # noqa: D103
if file_path is not None:
with open(file_path, "rb") as f:
data = pickle.load(f)
# Process and display the data as needed
content = f"File contents: {data[0][:2]}"
return content, content # Display same content in both tabs
return "No data available", "No data available"


if __name__ == "__main__":
app.run_server(debug=True)
11 changes: 11 additions & 0 deletions app/assets/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
:root {
--bs-primary: #FF6E42; /* Replace with your desired primary color */
}

.navbar {
background-color: var(--bs-primary) !important;
}

.nav-tabs .nav-item .nav-link.disabled {
color: #888888 !important; /* Set your desired color here */
}
3 changes: 2 additions & 1 deletion requirements.dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ isort
ruff
mypy
yapf
pytest
pytest
dash[testing]
8 changes: 6 additions & 2 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
nplinker==2.0.0a2
nplinker==2.0.0a3
dash
pandas
dash-bootstrap-components
dash_bootstrap_templates
numpy
dash-uploader==0.7.0a1
packaging==21.3.0
4 changes: 4 additions & 0 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
from pathlib import Path


DATA_DIR = Path(__file__).resolve().parent / "data"
Binary file added tests/data/mock_obj_data.pkl
Binary file not shown.
2 changes: 0 additions & 2 deletions tests/test_app.py

This file was deleted.

31 changes: 31 additions & 0 deletions tests/test_callbacks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
from dash_uploader import UploadStatus
from app.app import disable_tabs
from app.app import upload_data
from . import DATA_DIR


MOCK_FILE_PATH = DATA_DIR / "mock_obj_data.pkl"


def test_upload_data():
# Create an UploadStatus object
status = UploadStatus(
uploaded_files=[MOCK_FILE_PATH], n_total=1, uploaded_size_mb=5.39, total_size_mb=5.39
)
upload_string, path_string = upload_data(status)

# Check the result
assert upload_string == f"Successfully uploaded: {MOCK_FILE_PATH.name} [5.39 MB]"
assert path_string == str(MOCK_FILE_PATH)


def test_disable_tabs():
# Test with None as input
result = disable_tabs(None)
assert result[0] is True # GM tab should be disabled
assert result[1] is True # MG tab should be disabled

# Test with a string as input
result = disable_tabs(MOCK_FILE_PATH)
assert result[0] is False # GM tab should be enabled
assert result[1] is False # MG tab should be enabled
2 changes: 0 additions & 2 deletions tests/test_components.py

This file was deleted.

0 comments on commit c773794

Please sign in to comment.