From 4b2ab889b425c8666c711010530d0fae6c9c32e2 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 15:19:19 +0200 Subject: [PATCH 01/29] update nplinker version --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3851f50..088b222 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -nplinker==2.0.0a3 +nplinker==2.0.0a4 dash dash-bootstrap-components dash_bootstrap_templates From 1f620f8e95bc2021ff00bd16e9119401c0991a12 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 15:47:40 +0200 Subject: [PATCH 02/29] move callbacks to file --- app/app.py | 64 +++--------------------------------------------- app/callbacks.py | 60 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 61 deletions(-) diff --git a/app/app.py b/app/app.py index 2a64efd..a805e80 100644 --- a/app/app.py +++ b/app/app.py @@ -1,13 +1,9 @@ -import os -import pickle import tempfile import uuid import dash_bootstrap_components as dbc import dash_uploader as du +from callbacks import register_callbacks 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 @@ -130,62 +126,8 @@ # ------------------ 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" - +# Register callbacks +register_callbacks(app) if __name__ == "__main__": app.run_server(debug=True) diff --git a/app/callbacks.py b/app/callbacks.py index e69de29..e17a5e2 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -0,0 +1,60 @@ +import os +import pickle +import dash_uploader as du +from dash import Input +from dash import Output +from dash import clientside_callback + + +def register_callbacks(app): # noqa: D103 + 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" From 3771cd5f3bd75c05bbbaaa124ffe6ff94b5ee5b9 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 16:03:59 +0200 Subject: [PATCH 03/29] fix tests --- app/app.py | 13 +---- app/callbacks.py | 115 ++++++++++++++++++++++------------------ tests/test_callbacks.py | 4 +- 3 files changed, 66 insertions(+), 66 deletions(-) diff --git a/app/app.py b/app/app.py index a805e80..6da9036 100644 --- a/app/app.py +++ b/app/app.py @@ -1,16 +1,11 @@ -import tempfile import uuid import dash_bootstrap_components as dbc import dash_uploader as du -from callbacks import register_callbacks -from dash import Dash +from callbacks import app 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( [ @@ -49,9 +44,6 @@ ) # ------------------ Uploader ------------------ # -# Configure the upload folder -TEMP_DIR = tempfile.mkdtemp() -du.configure_upload(app, TEMP_DIR) uploader = html.Div( [ @@ -126,8 +118,5 @@ # ------------------ App Layout ------------------ # app.layout = dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0") -# Register callbacks -register_callbacks(app) - if __name__ == "__main__": app.run_server(debug=True) diff --git a/app/callbacks.py b/app/callbacks.py index e17a5e2..7c46d8b 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -1,60 +1,71 @@ import os import pickle +import tempfile +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 -def register_callbacks(app): # noqa: D103 - 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" +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]) +# Configure the upload folder +TEMP_DIR = tempfile.mkdtemp() +du.configure_upload(app, TEMP_DIR) + +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" diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 5861ea0..99f246a 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -1,6 +1,6 @@ from dash_uploader import UploadStatus -from app.app import disable_tabs -from app.app import upload_data +from app.callbacks import disable_tabs +from app.callbacks import upload_data from . import DATA_DIR From 8b90bfedfc0a35f7945877183b8aaba37a38d036 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 16:10:23 +0200 Subject: [PATCH 04/29] move layouts to specific file --- app/app.py | 117 +------------------------------------------ app/layouts.py | 131 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 133 insertions(+), 115 deletions(-) diff --git a/app/app.py b/app/app.py index 6da9036..b5da86c 100644 --- a/app/app.py +++ b/app/app.py @@ -1,122 +1,9 @@ -import uuid -import dash_bootstrap_components as dbc -import dash_uploader as du from callbacks import app -from dash import dcc -from dash import html +from layouts import create_layout -# ------------------ 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 ------------------ # - -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") +app.layout = create_layout() if __name__ == "__main__": app.run_server(debug=True) diff --git a/app/layouts.py b/app/layouts.py index e69de29..4b05d90 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -0,0 +1,131 @@ +import uuid +import dash_bootstrap_components as dbc +import dash_uploader as du +from dash import dcc +from dash import html + + +# Define the navigation bar +def create_navbar(): + 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", + ) + + 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, + ), + ), + ) + return navbar + + +# Define the file uploader +def create_uplader(): + 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", + ) + return uploader + + +# Define the tabs +def create_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", + ) + return tabs + + +def create_layout(): + return dbc.Container( + [create_navbar(), create_uplader(), create_tabs()], fluid=True, className="p-0" + ) From b3088bb103681bb6054429ced4c5c393d7eb5021 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 16:43:02 +0200 Subject: [PATCH 05/29] remove unused init and comment --- app/__init__.py | 0 app/app.py | 1 - 2 files changed, 1 deletion(-) delete mode 100644 app/__init__.py diff --git a/app/__init__.py b/app/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/app/app.py b/app/app.py index b5da86c..c683d3c 100644 --- a/app/app.py +++ b/app/app.py @@ -2,7 +2,6 @@ from layouts import create_layout -# ------------------ App Layout ------------------ # app.layout = create_layout() if __name__ == "__main__": From 586205dfc5f89a2ffa2f3c2557613d648f5c6212 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 16:50:23 +0200 Subject: [PATCH 06/29] improve layout structure by removing the functions --- app/layouts.py | 212 +++++++++++++++++++++++-------------------------- 1 file changed, 100 insertions(+), 112 deletions(-) diff --git a/app/layouts.py b/app/layouts.py index 4b05d90..01cbf07 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -5,127 +5,115 @@ from dash import html -# Define the navigation bar -def create_navbar(): - 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", - ) - - 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, - ), +# ------------------ 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, ), - ) - return navbar - + dbc.Label(className="fa fa-sun", html_for="color-mode-switch"), + ], + className="p-2", +) -# Define the file uploader -def create_uplader(): - 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", - ) - return uploader +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, + ), + ), +) -# Define the tabs -def create_tabs(): - # gm tab content - gm_content = dbc.Row( - dbc.Col( - dbc.Card( - dbc.CardBody([html.Div(id="file-content-gm")]), +# ------------------ Uploader ------------------ # +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, + ), ) - ) - ) - # mg tab content - mg_content = dbc.Row( - dbc.Col( - dbc.Card( - dbc.CardBody([html.Div(id="file-content-mg")]), + ), + 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")]), + ) ) - # 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", - ), - ], - ), +) +# 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", - ) - return tabs + ), + className="p-5", +) def create_layout(): - return dbc.Container( - [create_navbar(), create_uplader(), create_tabs()], fluid=True, className="p-0" - ) + return dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0") From ccf44c47f4178b1c7cb99b5e1a5484a916587c35 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 16:59:51 +0200 Subject: [PATCH 07/29] fix linting error --- app/layouts.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/layouts.py b/app/layouts.py index 01cbf07..4a62e4a 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -115,5 +115,5 @@ ) -def create_layout(): - return dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0") +def create_layout(): # noqa: D103 + return dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0 dbc") From cbd1ab6daa3a4197894ffa4d2df01c5fbc82d2e6 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 29 Jul 2024 18:01:10 +0200 Subject: [PATCH 08/29] fix mypy issues --- app/__init__.py | 0 mypy.ini | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) create mode 100644 app/__init__.py diff --git a/app/__init__.py b/app/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/mypy.ini b/mypy.ini index a34b1a1..202ac4e 100644 --- a/mypy.ini +++ b/mypy.ini @@ -1,5 +1,5 @@ [mypy] -python_version = "3.10" +python_version = 3.10 warn_return_any = true warn_unused_configs = true ignore_missing_imports = true \ No newline at end of file From f3446188e58a638cbc9924b3bd3669a0421544c0 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Tue, 30 Jul 2024 11:00:09 +0200 Subject: [PATCH 09/29] add gcfs filters to gm tab --- app/callbacks.py | 48 ++++++++++++++++++++++++++++++++++++++++++------ app/layouts.py | 40 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 7c46d8b..0b9a021 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -1,6 +1,7 @@ import os import pickle import tempfile +import dash import dash_bootstrap_components as dbc import dash_uploader as du from dash import Dash @@ -44,21 +45,28 @@ def upload_data(status: du.UploadStatus): # noqa: D103 @app.callback( - [Output("gm-tab", "disabled"), Output("mg-tab", "disabled")], + [ + Output("gm-tab", "disabled"), + Output("mg-tab", "disabled"), + Output("gcf-ids-dropdown-menu", "disabled"), + Output("gcf-ids-dropdown-input", "disabled"), + Output("gcf-bigscape-dropdown-menu", "disabled"), + Output("gcf-bigscape-dropdown-input", "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 + return True, True, True, True, True, True # Enable the tabs - return False, False + return False, False, False, False, 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")], + Output("file-content-mg", "children"), [Input("file-store", "data")], ) def display_file_contents(file_path): # noqa: D103 @@ -67,5 +75,33 @@ def display_file_contents(file_path): # noqa: D103 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" + return content # Display same content in both tabs + return "No data available" + + +@app.callback( + Output("gcf-ids-dropdown-input", "value"), + Output("gcf-bigscape-dropdown-input", "value"), + [ + Input("gcf-ids-dropdown-input", "value"), + Input("gcf-ids-dropdown-clear", "n_clicks"), + Input("gcf-bigscape-dropdown-input", "value"), + Input("gcf-bigscape-dropdown-clear", "n_clicks"), + ], +) +def filter_gm(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear): + ctx = dash.callback_context + + if not ctx.triggered: + return "", "" + else: + button_id = ctx.triggered[0]["prop_id"].split(".")[0] + + if button_id == "gcf-ids-dropdown-clear": + return "", gcf_bigscape + elif button_id == "gcf-ids-dropdown-input": + return gcf_ids, gcf_bigscape + elif button_id == "gcf-bigscape-dropdown-clear": + return gcf_ids, "" + elif button_id == "gcf-bigscape-dropdown-input": + return gcf_ids, gcf_bigscape diff --git a/app/layouts.py b/app/layouts.py index 4a62e4a..9b22385 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -71,11 +71,49 @@ # ------------------ Tabs ------------------ # +# gcfs ids dropdown menu items +gcf_ids_dropdown_menu_items = [ + dbc.DropdownMenuItem("Clear", id="gcf-ids-dropdown-clear"), +] +gcf_ids_input_group = dbc.InputGroup( + [ + dbc.DropdownMenu( + gcf_ids_dropdown_menu_items, id="gcf-ids-dropdown-menu", label="GCF ID", disabled=True + ), + dbc.Input( + id="gcf-ids-dropdown-input", placeholder="Enter one or more GCF IDs", disabled=True + ), + ] +) +# gcfs bigscape class dropdown menu items +gcf_bigscape_dropdown_menu_items = [ + dbc.DropdownMenuItem("Clear", id="gcf-bigscape-dropdown-clear"), +] +gcf_bigscape_input_group = dbc.InputGroup( + [ + dbc.DropdownMenu( + gcf_bigscape_dropdown_menu_items, + id="gcf-bigscape-dropdown-menu", + label="BiG-SCAPE Class", + disabled=True, + ), + dbc.Input( + id="gcf-bigscape-dropdown-input", + placeholder="Enter one or more GCF BiG-SCAPE classes", + disabled=True, + ), + ] +) # gm tab content gm_content = dbc.Row( dbc.Col( dbc.Card( - dbc.CardBody([html.Div(id="file-content-gm")]), + dbc.CardBody( + [ + gcf_ids_input_group, + gcf_bigscape_input_group, + ] + ), ) ) ) From 7683c3a4af29c36b0a1c990db2b78b581832b6c7 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Tue, 30 Jul 2024 14:41:34 +0200 Subject: [PATCH 10/29] add collapse functionality --- app/assets/style.css | 26 +++++++++++++++++++++++++- app/callbacks.py | 21 +++++++++++++++++---- app/layouts.py | 40 +++++++++++++++++++++++++--------------- 3 files changed, 67 insertions(+), 20 deletions(-) diff --git a/app/assets/style.css b/app/assets/style.css index d0f8ffb..8dfc83e 100644 --- a/app/assets/style.css +++ b/app/assets/style.css @@ -8,4 +8,28 @@ .nav-tabs .nav-item .nav-link.disabled { color: #888888 !important; /* Set your desired color here */ -} \ No newline at end of file +} + +.filter-button { + background-color: #FF6E42 !important; /* Change to your primary color */ + border-color: #FF6E42 !important; /* Ensure the border color matches */ + color: white !important; /* Ensure text color is readable */ +} + +.filter-button:focus, +.filter-button:active { + outline: none !important; /* Remove the default outline */ + box-shadow: 0 0 0 0.2rem rgba(255, 110, 66, 0.5) !important; /* Add a custom focus shadow */ +} + +.custom-dropdown-toggle { + background-color: #FF6E42 !important; /* Primary color */ + border-color: #FF6E42 !important; /* Match border color */ + color: white !important; /* Ensure text color is readable */ +} + +.custom-dropdown-toggle:focus, +.custom-dropdown-toggle:active { + outline: none !important; /* Remove default outline */ + box-shadow: 0 0 0 0.2rem rgba(255, 110, 66, 0.5) !important; /* Custom focus shadow */ +} diff --git a/app/callbacks.py b/app/callbacks.py index 0b9a021..89fb13a 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -7,6 +7,7 @@ from dash import Dash from dash import Input from dash import Output +from dash import State from dash import clientside_callback @@ -47,11 +48,12 @@ def upload_data(status: du.UploadStatus): # noqa: D103 @app.callback( [ Output("gm-tab", "disabled"), - Output("mg-tab", "disabled"), + Output("gm-filter-button", "disabled"), Output("gcf-ids-dropdown-menu", "disabled"), Output("gcf-ids-dropdown-input", "disabled"), Output("gcf-bigscape-dropdown-menu", "disabled"), Output("gcf-bigscape-dropdown-input", "disabled"), + Output("mg-tab", "disabled"), ], [Input("file-store", "data")], prevent_initial_call=True, @@ -59,9 +61,9 @@ def upload_data(status: du.UploadStatus): # noqa: D103 def disable_tabs(file_name): # noqa: D103 if file_name is None: # Disable the tabs - return True, True, True, True, True, True + return True, True, True, True, True, True, True # Enable the tabs - return False, False, False, False, False, False + return False, False, False, False, False, False, False # Define another callback to access the stored file path and read the file @@ -79,6 +81,17 @@ def display_file_contents(file_path): # noqa: D103 return "No data available" +@app.callback( + Output("gm-filter-collapse", "is_open"), + [Input("gm-filter-button", "n_clicks")], + [State("gm-filter-collapse", "is_open")], +) +def toggle_gm_filter_collapse(n, is_open): # noqa: D103 + if n: + return not is_open + return is_open + + @app.callback( Output("gcf-ids-dropdown-input", "value"), Output("gcf-bigscape-dropdown-input", "value"), @@ -89,7 +102,7 @@ def display_file_contents(file_path): # noqa: D103 Input("gcf-bigscape-dropdown-clear", "n_clicks"), ], ) -def filter_gm(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear): +def gm_filter(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear): # noqa: D103 ctx = dash.callback_context if not ctx.triggered: diff --git a/app/layouts.py b/app/layouts.py index 9b22385..5647035 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -78,12 +78,17 @@ gcf_ids_input_group = dbc.InputGroup( [ dbc.DropdownMenu( - gcf_ids_dropdown_menu_items, id="gcf-ids-dropdown-menu", label="GCF ID", disabled=True + gcf_ids_dropdown_menu_items, + id="gcf-ids-dropdown-menu", + label="GCF ID", + disabled=True, + toggleClassName="custom-dropdown-toggle", ), dbc.Input( id="gcf-ids-dropdown-input", placeholder="Enter one or more GCF IDs", disabled=True ), - ] + ], + className="mt-3 mb-3", ) # gcfs bigscape class dropdown menu items gcf_bigscape_dropdown_menu_items = [ @@ -96,27 +101,32 @@ id="gcf-bigscape-dropdown-menu", label="BiG-SCAPE Class", disabled=True, + toggleClassName="custom-dropdown-toggle", ), dbc.Input( id="gcf-bigscape-dropdown-input", placeholder="Enter one or more GCF BiG-SCAPE classes", disabled=True, ), - ] + ], + className="mt-3 mb-3", ) -# gm tab content -gm_content = dbc.Row( - dbc.Col( - dbc.Card( - dbc.CardBody( - [ - gcf_ids_input_group, - gcf_bigscape_input_group, - ] - ), - ) - ) +# gm filter card +gm_filter_button = dbc.Button( + "Genomics filter", id="gm-filter-button", disabled=True, className="filter-button" +) +gm_filter_body = dbc.CardBody( + [ + gcf_ids_input_group, + gcf_bigscape_input_group, + ], +) +gm_filter_collapse = dbc.Card( + [gm_filter_button, dbc.Collapse(gm_filter_body, id="gm-filter-collapse", is_open=False)], + className="mt-5 mb-3", ) +# gm tab content +gm_content = dbc.Row(dbc.Col(gm_filter_collapse, width=10, className="mx-auto")) # mg tab content mg_content = dbc.Row( dbc.Col( From 67d83557d99d952e4ea54ba1791dfcb4afc9fadd Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Tue, 30 Jul 2024 15:44:46 +0200 Subject: [PATCH 11/29] add tests for gm_filter --- app/layouts.py | 1 - tests/test_callbacks.py | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 1 deletion(-) diff --git a/app/layouts.py b/app/layouts.py index 5647035..860e068 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -19,7 +19,6 @@ ], className="p-2", ) - navbar = dbc.Row( dbc.Col( dbc.NavbarSimple( diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 99f246a..19007e6 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -1,5 +1,10 @@ +from contextvars import copy_context +import pytest +from dash._callback_context import context_value +from dash._utils import AttributeDict from dash_uploader import UploadStatus from app.callbacks import disable_tabs +from app.callbacks import gm_filter from app.callbacks import upload_data from . import DATA_DIR @@ -29,3 +34,50 @@ def test_disable_tabs(): 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 + + +@pytest.mark.parametrize( + "gcf_ids, gcf_bigscape, triggered_prop_id, expected_result", + [ + ( + "10, 34, 56", + "", + "gcf-ids-dropdown-input.value", + ("10, 34, 56", ""), + ), # gcf-ids-dropdown-input triggered + ( + "", + "class1", + "gcf-bigscape-dropdown-input.value", + ("", "class1"), + ), # gcf-bigscape-dropdown-input triggered + ( + "10, 34, 56", + "class1", + "gcf-ids-dropdown-clear.n_clicks", + ("", "class1"), + ), # gcf-ids-dropdown-clear triggered + ( + "10, 34, 56", + "class1", + "gcf-bigscape-dropdown-clear.n_clicks", + ("10, 34, 56", ""), + ), # gcf-bigscape-dropdown-clear triggered + ("", "", "no_triggering_context", ("", "")), # No triggering context + ], +) +def test_gm_filter(gcf_ids, gcf_bigscape, triggered_prop_id, expected_result): + def run_callback(): + gcf_ids_clear = None + gcf_bigscape_clear = None + if triggered_prop_id == "no_triggering_context": + context_value.set(AttributeDict(**{"triggered_inputs": []})) + else: + context_value.set( + AttributeDict(**{"triggered_inputs": [{"prop_id": triggered_prop_id}]}) + ) + return gm_filter(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear) + + ctx = copy_context() + output = ctx.run(run_callback) + assert output == expected_result From db55eeafb7229d46320cc5c240cb7a4a97d0018d Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Tue, 30 Jul 2024 17:55:35 +0200 Subject: [PATCH 12/29] add graph for gm tab --- app/callbacks.py | 37 +++++++++++++++++++++++++++++++++---- app/layouts.py | 9 ++++++++- 2 files changed, 41 insertions(+), 5 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 89fb13a..94ab898 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -1,9 +1,11 @@ import os import pickle import tempfile +from collections import defaultdict import dash import dash_bootstrap_components as dbc import dash_uploader as du +import plotly.graph_objects as go from dash import Dash from dash import Input from dash import Output @@ -68,17 +70,44 @@ def disable_tabs(file_name): # noqa: D103 # Define another callback to access the stored file path and read the file @app.callback( + Output("gm-graph", "figure"), + Output("gm-graph", "style"), Output("file-content-mg", "children"), [Input("file-store", "data")], ) -def display_file_contents(file_path): # noqa: D103 +def gm_plot(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 # Display same content in both tabs - return "No data available" + _, gcfs, _, _, _, _ = data + n_bgcs = {} + for gcf in gcfs: + n_bgcs[gcf.id] = len(gcf.bgcs) + + keys_by_value = defaultdict(list) + + for key, value in n_bgcs.items(): + keys_by_value[value].append(key) + + x_values = list(keys_by_value.keys()) + x_values.sort() + y_values = [len(keys_by_value[x]) for x in x_values] + hover_texts = [", ".join(keys_by_value[x]) for x in x_values] + + # Create the bar plot + fig = go.Figure( + data=[ + go.Bar( + x=x_values, y=y_values, text=hover_texts, hoverinfo="text", textposition="none" + ) + ] + ) + + # Update layout + fig.update_layout(xaxis_title="# BGCs", yaxis_title="# GCFs", xaxis=dict(type="category")) + return fig, {"display": "block"}, "uploaded!!" + return {}, {"display": "none"}, "No data available" @app.callback( diff --git a/app/layouts.py b/app/layouts.py index 860e068..f87d9d5 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -124,8 +124,15 @@ [gm_filter_button, dbc.Collapse(gm_filter_body, id="gm-filter-collapse", is_open=False)], className="mt-5 mb-3", ) +# gm graph +gm_graph = dcc.Graph(id="gm-graph", className="mt-5 mb-3", style={"display": "none"}) # gm tab content -gm_content = dbc.Row(dbc.Col(gm_filter_collapse, width=10, className="mx-auto")) +gm_content = dbc.Row( + [ + dbc.Col(gm_filter_collapse, width=10, className="mx-auto"), + dbc.Col(gm_graph, width=10, className="mx-auto"), + ] +) # mg tab content mg_content = dbc.Row( dbc.Col( From ef027545624f6faa1f02d4000f8582605c72ddba Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 5 Aug 2024 12:11:08 +0200 Subject: [PATCH 13/29] use accordion instead of button --- app/callbacks.py | 17 ++++------------- app/layouts.py | 32 +++++++++++++++++++++++--------- requirements.txt | 1 + 3 files changed, 28 insertions(+), 22 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 89fb13a..e68af02 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -7,10 +7,12 @@ from dash import Dash from dash import Input from dash import Output -from dash import State from dash import clientside_callback +dash._dash_renderer._set_react_version("18.2.0") + + 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]) # Configure the upload folder @@ -48,7 +50,7 @@ def upload_data(status: du.UploadStatus): # noqa: D103 @app.callback( [ Output("gm-tab", "disabled"), - Output("gm-filter-button", "disabled"), + Output("gm-accordion-control", "disabled"), Output("gcf-ids-dropdown-menu", "disabled"), Output("gcf-ids-dropdown-input", "disabled"), Output("gcf-bigscape-dropdown-menu", "disabled"), @@ -81,17 +83,6 @@ def display_file_contents(file_path): # noqa: D103 return "No data available" -@app.callback( - Output("gm-filter-collapse", "is_open"), - [Input("gm-filter-button", "n_clicks")], - [State("gm-filter-collapse", "is_open")], -) -def toggle_gm_filter_collapse(n, is_open): # noqa: D103 - if n: - return not is_open - return is_open - - @app.callback( Output("gcf-ids-dropdown-input", "value"), Output("gcf-bigscape-dropdown-input", "value"), diff --git a/app/layouts.py b/app/layouts.py index 860e068..bee283b 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -1,5 +1,6 @@ import uuid import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import dash_uploader as du from dash import dcc from dash import html @@ -110,22 +111,33 @@ ], className="mt-3 mb-3", ) -# gm filter card -gm_filter_button = dbc.Button( - "Genomics filter", id="gm-filter-button", disabled=True, className="filter-button" -) -gm_filter_body = dbc.CardBody( +# gm accordion (filter) card +gm_accordion_body = dbc.CardBody( [ gcf_ids_input_group, gcf_bigscape_input_group, ], ) -gm_filter_collapse = dbc.Card( - [gm_filter_button, dbc.Collapse(gm_filter_body, id="gm-filter-collapse", is_open=False)], +gm_accordion = dmc.Accordion( + [ + dmc.AccordionItem( + [ + dmc.AccordionControl( + "Genomics filter", + disabled=True, + id="gm-accordion-control", + ), + dmc.AccordionPanel( + [gm_accordion_body], + ), + ], + value="gm-accordion", + ), + ], className="mt-5 mb-3", ) # gm tab content -gm_content = dbc.Row(dbc.Col(gm_filter_collapse, width=10, className="mx-auto")) +gm_content = dbc.Row(dbc.Col(gm_accordion, width=10, className="mx-auto")) # mg tab content mg_content = dbc.Row( dbc.Col( @@ -163,4 +175,6 @@ def create_layout(): # noqa: D103 - return dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0 dbc") + return dmc.MantineProvider( + [dbc.Container([navbar, uploader, tabs], fluid=True, className="p-0 dbc")] + ) diff --git a/requirements.txt b/requirements.txt index 088b222..d69e881 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,6 +1,7 @@ nplinker==2.0.0a4 dash dash-bootstrap-components +dash-mantine-components dash_bootstrap_templates numpy dash-uploader==0.7.0a1 From 95d4457bad06a12e00bcca0726958befe28b1aff Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 5 Aug 2024 15:49:18 +0200 Subject: [PATCH 14/29] use dmc grid for filter --- app/assets/style.css | 33 +++++++++---------- app/callbacks.py | 44 ++++++++++--------------- app/layouts.py | 76 ++++++++++++++++++-------------------------- 3 files changed, 64 insertions(+), 89 deletions(-) diff --git a/app/assets/style.css b/app/assets/style.css index 8dfc83e..120aeb0 100644 --- a/app/assets/style.css +++ b/app/assets/style.css @@ -10,26 +10,27 @@ color: #888888 !important; /* Set your desired color here */ } -.filter-button { - background-color: #FF6E42 !important; /* Change to your primary color */ - border-color: #FF6E42 !important; /* Ensure the border color matches */ - color: white !important; /* Ensure text color is readable */ +.btn-primary { + /* Use theme color or styles explicitly */ + background-color: #FF6E42 !important; /* Primary color in the UNITED theme */ + border-color: #f19e85 !important; } -.filter-button:focus, -.filter-button:active { - outline: none !important; /* Remove the default outline */ - box-shadow: 0 0 0 0.2rem rgba(255, 110, 66, 0.5) !important; /* Add a custom focus shadow */ +.btn-primary:hover { + background-color: #e0400f !important; /* Hover color */ + border-color: #f19e85 !important; } -.custom-dropdown-toggle { - background-color: #FF6E42 !important; /* Primary color */ - border-color: #FF6E42 !important; /* Match border color */ - color: white !important; /* Ensure text color is readable */ +.btn-primary:active { + background-color: #e0400f !important; + border-color: #f19e85 !important; } -.custom-dropdown-toggle:focus, -.custom-dropdown-toggle:active { - outline: none !important; /* Remove default outline */ - box-shadow: 0 0 0 0.2rem rgba(255, 110, 66, 0.5) !important; /* Custom focus shadow */ +.btn-primary:focus { + box-shadow: 0 0 0 0.2rem #e0400f !important; /* Focus outline color */ + border-color: #f19e85 !important; /* Focus border color */ } + +.custom-textinput .mantine-TextInput-input:focus { + border-color: #FF6E42 !important; /* Customize border color */ +} \ No newline at end of file diff --git a/app/callbacks.py b/app/callbacks.py index e68af02..62206bf 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -51,10 +51,6 @@ def upload_data(status: du.UploadStatus): # noqa: D103 [ Output("gm-tab", "disabled"), Output("gm-accordion-control", "disabled"), - Output("gcf-ids-dropdown-menu", "disabled"), - Output("gcf-ids-dropdown-input", "disabled"), - Output("gcf-bigscape-dropdown-menu", "disabled"), - Output("gcf-bigscape-dropdown-input", "disabled"), Output("mg-tab", "disabled"), ], [Input("file-store", "data")], @@ -63,9 +59,9 @@ def upload_data(status: du.UploadStatus): # noqa: D103 def disable_tabs(file_name): # noqa: D103 if file_name is None: # Disable the tabs - return True, True, True, True, True, True, True + return True, True, True # Enable the tabs - return False, False, False, False, False, False, False + return False, False, False # Define another callback to access the stored file path and read the file @@ -84,28 +80,20 @@ def display_file_contents(file_path): # noqa: D103 @app.callback( - Output("gcf-ids-dropdown-input", "value"), - Output("gcf-bigscape-dropdown-input", "value"), - [ - Input("gcf-ids-dropdown-input", "value"), - Input("gcf-ids-dropdown-clear", "n_clicks"), - Input("gcf-bigscape-dropdown-input", "value"), - Input("gcf-bigscape-dropdown-clear", "n_clicks"), - ], + Output("gm-dropdown-input", "placeholder"), + Output("gm-dropdown-input", "value"), + [Input("gm-dropdown-menu", "value"), Input("gm-dropdown-input", "value")], + allow_duplicate=True, ) -def gm_filter(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear): # noqa: D103 - ctx = dash.callback_context +def update_placeholder(selected_dropdown, input_value): # noqa: D103 + if selected_dropdown == "GCF_ID": + placeholder = "Enter one or more GCF IDs" + elif selected_dropdown == "BSC_CLASS": + placeholder = "Enter one or more GCF BiG-SCAPE classes" - if not ctx.triggered: - return "", "" - else: - button_id = ctx.triggered[0]["prop_id"].split(".")[0] + # Clear the text input when dropdown selection changes + ctx = dash.callback_context + if ctx.triggered and ctx.triggered[0]["prop_id"] == "gm-dropdown-menu.value": + input_value = "" - if button_id == "gcf-ids-dropdown-clear": - return "", gcf_bigscape - elif button_id == "gcf-ids-dropdown-input": - return gcf_ids, gcf_bigscape - elif button_id == "gcf-bigscape-dropdown-clear": - return gcf_ids, "" - elif button_id == "gcf-bigscape-dropdown-input": - return gcf_ids, gcf_bigscape + return placeholder, input_value diff --git a/app/layouts.py b/app/layouts.py index bee283b..a996219 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -71,53 +71,42 @@ # ------------------ Tabs ------------------ # -# gcfs ids dropdown menu items -gcf_ids_dropdown_menu_items = [ - dbc.DropdownMenuItem("Clear", id="gcf-ids-dropdown-clear"), -] -gcf_ids_input_group = dbc.InputGroup( +# dropdown menu items +gm_input_group = dmc.Grid( [ - dbc.DropdownMenu( - gcf_ids_dropdown_menu_items, - id="gcf-ids-dropdown-menu", - label="GCF ID", - disabled=True, - toggleClassName="custom-dropdown-toggle", + dmc.GridCol( + dbc.Button( + [html.I(className="fas fa-plus")], # FontAwesome plus icon + id="add-button", + className="btn-primary", + ), + span=1, ), - dbc.Input( - id="gcf-ids-dropdown-input", placeholder="Enter one or more GCF IDs", disabled=True + dmc.GridCol( + dcc.Dropdown( + options=[ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + ], + value="GCF_ID", + placeholder="Enter one or more GCF IDs", + id="gm-dropdown-menu", + clearable=False, + ), + span=6, ), - ], - className="mt-3 mb-3", -) -# gcfs bigscape class dropdown menu items -gcf_bigscape_dropdown_menu_items = [ - dbc.DropdownMenuItem("Clear", id="gcf-bigscape-dropdown-clear"), -] -gcf_bigscape_input_group = dbc.InputGroup( - [ - dbc.DropdownMenu( - gcf_bigscape_dropdown_menu_items, - id="gcf-bigscape-dropdown-menu", - label="BiG-SCAPE Class", - disabled=True, - toggleClassName="custom-dropdown-toggle", - ), - dbc.Input( - id="gcf-bigscape-dropdown-input", - placeholder="Enter one or more GCF BiG-SCAPE classes", - disabled=True, + dmc.GridCol( + dmc.TextInput( + id="gm-dropdown-input", + placeholder="", + className="custom-textinput ", + ), + span=5, ), ], - className="mt-3 mb-3", + gutter="md", ) # gm accordion (filter) card -gm_accordion_body = dbc.CardBody( - [ - gcf_ids_input_group, - gcf_bigscape_input_group, - ], -) gm_accordion = dmc.Accordion( [ dmc.AccordionItem( @@ -126,10 +115,9 @@ "Genomics filter", disabled=True, id="gm-accordion-control", + className="mt-5 mb-3", ), - dmc.AccordionPanel( - [gm_accordion_body], - ), + dmc.AccordionPanel(gm_input_group), ], value="gm-accordion", ), @@ -157,7 +145,6 @@ activeTabClassName="fw-bold", disabled=True, id="gm-tab", - className="disabled-tab", ), dbc.Tab( mg_content, @@ -165,7 +152,6 @@ activeTabClassName="fw-bold", disabled=True, id="mg-tab", - className="disabled-tab", ), ], ), From 76842a3119b13542ce5c9e615fb11a91eac62a7c Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 5 Aug 2024 16:15:40 +0200 Subject: [PATCH 15/29] add the add filter functionality --- app/callbacks.py | 90 +++++++++++++++++++++++++++++++++++++++--------- app/layouts.py | 39 +++++---------------- 2 files changed, 82 insertions(+), 47 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 62206bf..6af8f42 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -1,13 +1,19 @@ import os import pickle import tempfile +import uuid import dash import dash_bootstrap_components as dbc +import dash_mantine_components as dmc import dash_uploader as du +from dash import MATCH from dash import Dash from dash import Input from dash import Output +from dash import State from dash import clientside_callback +from dash import dcc +from dash import html dash._dash_renderer._set_react_version("18.2.0") @@ -80,20 +86,72 @@ def display_file_contents(file_path): # noqa: D103 @app.callback( - Output("gm-dropdown-input", "placeholder"), - Output("gm-dropdown-input", "value"), - [Input("gm-dropdown-menu", "value"), Input("gm-dropdown-input", "value")], - allow_duplicate=True, + Output("block-store", "data"), Input("add-button", "n_clicks"), State("block-store", "data") ) -def update_placeholder(selected_dropdown, input_value): # noqa: D103 - if selected_dropdown == "GCF_ID": - placeholder = "Enter one or more GCF IDs" - elif selected_dropdown == "BSC_CLASS": - placeholder = "Enter one or more GCF BiG-SCAPE classes" - - # Clear the text input when dropdown selection changes - ctx = dash.callback_context - if ctx.triggered and ctx.triggered[0]["prop_id"] == "gm-dropdown-menu.value": - input_value = "" - - return placeholder, input_value +def add_block(n_clicks, blocks_data): # noqa: D103 + if n_clicks is None: + raise dash.exceptions.PreventUpdate + + # Create a unique ID for the new block + new_block_id = str(uuid.uuid4()) + + blocks_data.append(new_block_id) + return blocks_data + + +@app.callback(Output("blocks-container", "children"), Input("block-store", "data")) +def display_blocks(blocks_data): # noqa: D103 + blocks = [] + + for block_id in blocks_data: + blocks.append( + dmc.Grid( + id={"type": "gm-block", "index": block_id}, + children=[ + dmc.GridCol( + dbc.Button( + [html.I(className="fas fa-plus")], + className="btn-primary", + ), + span=1, + ), + dmc.GridCol( + dcc.Dropdown( + options=[ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + ], + value="GCF_ID", + placeholder="Enter one or more GCF IDs", + id={"type": "gm-dropdown-menu", "index": block_id}, + clearable=False, + ), + span=6, + ), + dmc.GridCol( + dmc.TextInput( + id={"type": "gm-dropdown-input", "index": block_id}, + placeholder="", + className="custom-textinput", + ), + span=5, + ), + ], + gutter="md", + ) + ) + + return blocks + + +@app.callback( + Output({"type": "gm-dropdown-input", "index": MATCH}, "placeholder"), + Output({"type": "gm-dropdown-input", "index": MATCH}, "value"), + Input({"type": "gm-dropdown-menu", "index": MATCH}, "value"), +) +def update_placeholder(selected_value): # noqa: D103 + if selected_value == "GCF_ID": + return "Enter one or more GCF IDs", "" + elif selected_value == "BSC_CLASS": + return "Enter one or more GCF BiG-SCAPE classes", "" + return "", "" diff --git a/app/layouts.py b/app/layouts.py index a996219..dae8036 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -72,39 +72,16 @@ # ------------------ Tabs ------------------ # # dropdown menu items -gm_input_group = dmc.Grid( +gm_input_group = html.Div( [ - dmc.GridCol( - dbc.Button( - [html.I(className="fas fa-plus")], # FontAwesome plus icon - id="add-button", - className="btn-primary", - ), - span=1, + dcc.Store(id="block-store", data=[]), + html.Div(id="blocks-container"), + dbc.Button( + [html.I(className="fas fa-plus")], # FontAwesome plus icon + id="add-button", + className="btn-primary", ), - dmc.GridCol( - dcc.Dropdown( - options=[ - {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, - ], - value="GCF_ID", - placeholder="Enter one or more GCF IDs", - id="gm-dropdown-menu", - clearable=False, - ), - span=6, - ), - dmc.GridCol( - dmc.TextInput( - id="gm-dropdown-input", - placeholder="", - className="custom-textinput ", - ), - span=5, - ), - ], - gutter="md", + ] ) # gm accordion (filter) card gm_accordion = dmc.Accordion( From 1ef908dbb039a087c9c544b0a3dfc071242d1cab Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Mon, 5 Aug 2024 16:40:11 +0200 Subject: [PATCH 16/29] start with one filter block and keep button only on the last ones --- app/callbacks.py | 82 +++++++++++++++++++++++++----------------------- app/layouts.py | 46 +++++++++++++++++++++++---- 2 files changed, 83 insertions(+), 45 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 6af8f42..b3fde99 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -6,6 +6,7 @@ import dash_bootstrap_components as dbc import dash_mantine_components as dmc import dash_uploader as du +from dash import ALL from dash import MATCH from dash import Dash from dash import Input @@ -86,61 +87,64 @@ def display_file_contents(file_path): # noqa: D103 @app.callback( - Output("block-store", "data"), Input("add-button", "n_clicks"), State("block-store", "data") + Output("block-store", "data"), + Input({"type": "gm-add-button", "index": ALL}, "n_clicks"), + State("block-store", "data"), ) def add_block(n_clicks, blocks_data): # noqa: D103 - if n_clicks is None: + if not any(n_clicks): raise dash.exceptions.PreventUpdate # Create a unique ID for the new block new_block_id = str(uuid.uuid4()) - blocks_data.append(new_block_id) return blocks_data @app.callback(Output("blocks-container", "children"), Input("block-store", "data")) def display_blocks(blocks_data): # noqa: D103 - blocks = [] - - for block_id in blocks_data: - blocks.append( - dmc.Grid( - id={"type": "gm-block", "index": block_id}, - children=[ - dmc.GridCol( - dbc.Button( - [html.I(className="fas fa-plus")], - className="btn-primary", - ), - span=1, + # Start with one block in the layout and then add additional blocks dynamically + blocks = [ + dmc.Grid( + id={"type": "gm-block", "index": block_id}, + children=[ + dmc.GridCol( + dbc.Button( + [html.I(className="fas fa-plus")], + id={"type": "gm-add-button", "index": block_id}, + className="btn-primary", + style={ + "display": "block" if i == len(blocks_data) - 1 else "none" + }, # Show button only on the latest block ), - dmc.GridCol( - dcc.Dropdown( - options=[ - {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, - ], - value="GCF_ID", - placeholder="Enter one or more GCF IDs", - id={"type": "gm-dropdown-menu", "index": block_id}, - clearable=False, - ), - span=6, + span=1, + ), + dmc.GridCol( + dcc.Dropdown( + options=[ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + ], + value="GCF_ID", + placeholder="Enter one or more GCF IDs", + id={"type": "gm-dropdown-menu", "index": block_id}, + clearable=False, ), - dmc.GridCol( - dmc.TextInput( - id={"type": "gm-dropdown-input", "index": block_id}, - placeholder="", - className="custom-textinput", - ), - span=5, + span=6, + ), + dmc.GridCol( + dmc.TextInput( + id={"type": "gm-dropdown-input", "index": block_id}, + placeholder="", + className="custom-textinput", ), - ], - gutter="md", - ) + span=5, + ), + ], + gutter="md", ) - + for i, block_id in enumerate(blocks_data) + ] return blocks diff --git a/app/layouts.py b/app/layouts.py index dae8036..9f4c0cb 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -74,12 +74,46 @@ # dropdown menu items gm_input_group = html.Div( [ - dcc.Store(id="block-store", data=[]), - html.Div(id="blocks-container"), - dbc.Button( - [html.I(className="fas fa-plus")], # FontAwesome plus icon - id="add-button", - className="btn-primary", + dcc.Store(id="block-store", data=[str(uuid.uuid4())]), # Start with one block + html.Div( + id="blocks-container", + children=[ + dmc.Grid( + id={"type": "gm-block", "index": str(uuid.uuid4())}, # Start with one block + children=[ + dmc.GridCol( + dbc.Button( + [html.I(className="fas fa-plus")], + id={"type": "gm-add-button", "index": str(uuid.uuid4())}, + className="btn-primary", + ), + span=1, + ), + dmc.GridCol( + dcc.Dropdown( + options=[ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + ], + value="GCF_ID", + placeholder="Enter one or more GCF IDs", + id={"type": "gm-dropdown-menu", "index": str(uuid.uuid4())}, + clearable=False, + ), + span=6, + ), + dmc.GridCol( + dmc.TextInput( + id={"type": "gm-dropdown-input", "index": str(uuid.uuid4())}, + placeholder="", + className="custom-textinput", + ), + span=5, + ), + ], + gutter="md", + ) + ], ), ] ) From 17b79acd18eeb488a5a4df44ba9140e9e87034c0 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Tue, 6 Aug 2024 17:16:25 +0200 Subject: [PATCH 17/29] improve ids name, add default filter value, improve gcf ids hint --- app/callbacks.py | 26 ++++++++++++++------------ app/layouts.py | 5 ++--- 2 files changed, 16 insertions(+), 15 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index b3fde99..4803b5a 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -87,22 +87,25 @@ def display_file_contents(file_path): # noqa: D103 @app.callback( - Output("block-store", "data"), + Output("blocks-id", "data"), Input({"type": "gm-add-button", "index": ALL}, "n_clicks"), - State("block-store", "data"), + State("blocks-id", "data"), ) -def add_block(n_clicks, blocks_data): # noqa: D103 +def add_block(n_clicks, blocks_id): # noqa: D103 if not any(n_clicks): raise dash.exceptions.PreventUpdate - # Create a unique ID for the new block new_block_id = str(uuid.uuid4()) - blocks_data.append(new_block_id) - return blocks_data + blocks_id.append(new_block_id) + return blocks_id -@app.callback(Output("blocks-container", "children"), Input("block-store", "data")) -def display_blocks(blocks_data): # noqa: D103 +@app.callback( + Output("blocks-container", "children"), + Input("blocks-id", "data"), + Input("blocks-container", "children"), +) +def display_blocks(blocks_id, blocks): # noqa: D103 # Start with one block in the layout and then add additional blocks dynamically blocks = [ dmc.Grid( @@ -114,7 +117,7 @@ def display_blocks(blocks_data): # noqa: D103 id={"type": "gm-add-button", "index": block_id}, className="btn-primary", style={ - "display": "block" if i == len(blocks_data) - 1 else "none" + "display": "block" if i == len(blocks_id) - 1 else "none" }, # Show button only on the latest block ), span=1, @@ -126,7 +129,6 @@ def display_blocks(blocks_data): # noqa: D103 {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, ], value="GCF_ID", - placeholder="Enter one or more GCF IDs", id={"type": "gm-dropdown-menu", "index": block_id}, clearable=False, ), @@ -143,7 +145,7 @@ def display_blocks(blocks_data): # noqa: D103 ], gutter="md", ) - for i, block_id in enumerate(blocks_data) + for i, block_id in enumerate(blocks_id) ] return blocks @@ -155,7 +157,7 @@ def display_blocks(blocks_data): # noqa: D103 ) def update_placeholder(selected_value): # noqa: D103 if selected_value == "GCF_ID": - return "Enter one or more GCF IDs", "" + return "1, 2, 3, ...", "" elif selected_value == "BSC_CLASS": return "Enter one or more GCF BiG-SCAPE classes", "" return "", "" diff --git a/app/layouts.py b/app/layouts.py index 9f4c0cb..3910e1f 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -74,7 +74,7 @@ # dropdown menu items gm_input_group = html.Div( [ - dcc.Store(id="block-store", data=[str(uuid.uuid4())]), # Start with one block + dcc.Store(id="blocks-id", data=[str(uuid.uuid4())]), # Start with one block html.Div( id="blocks-container", children=[ @@ -96,7 +96,6 @@ {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, ], value="GCF_ID", - placeholder="Enter one or more GCF IDs", id={"type": "gm-dropdown-menu", "index": str(uuid.uuid4())}, clearable=False, ), @@ -105,7 +104,7 @@ dmc.GridCol( dmc.TextInput( id={"type": "gm-dropdown-input", "index": str(uuid.uuid4())}, - placeholder="", + placeholder="1, 2, 3, ...", className="custom-textinput", ), span=5, From d0d3bef3247eb151887024dfe4adf53a71c8a752 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Wed, 7 Aug 2024 13:01:50 +0200 Subject: [PATCH 18/29] use unique id for the first block --- app/layouts.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/app/layouts.py b/app/layouts.py index 3910e1f..0ca69a7 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -72,19 +72,20 @@ # ------------------ Tabs ------------------ # # dropdown menu items +initial_block_id = str(uuid.uuid4()) gm_input_group = html.Div( [ - dcc.Store(id="blocks-id", data=[str(uuid.uuid4())]), # Start with one block + dcc.Store(id="blocks-id", data=[initial_block_id]), # Start with one block html.Div( id="blocks-container", children=[ dmc.Grid( - id={"type": "gm-block", "index": str(uuid.uuid4())}, # Start with one block + id={"type": "gm-block", "index": initial_block_id}, # Start with one block children=[ dmc.GridCol( dbc.Button( [html.I(className="fas fa-plus")], - id={"type": "gm-add-button", "index": str(uuid.uuid4())}, + id={"type": "gm-add-button", "index": initial_block_id}, className="btn-primary", ), span=1, @@ -96,14 +97,14 @@ {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, ], value="GCF_ID", - id={"type": "gm-dropdown-menu", "index": str(uuid.uuid4())}, + id={"type": "gm-dropdown-menu", "index": initial_block_id}, clearable=False, ), span=6, ), dmc.GridCol( dmc.TextInput( - id={"type": "gm-dropdown-input", "index": str(uuid.uuid4())}, + id={"type": "gm-dropdown-input", "index": initial_block_id}, placeholder="1, 2, 3, ...", className="custom-textinput", ), From d87c0f7219a221662950298f1c08ceb099212e5c Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Wed, 7 Aug 2024 13:03:03 +0200 Subject: [PATCH 19/29] keep previous dropdown and input text values when a new block is added --- app/callbacks.py | 88 +++++++++++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 42 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 4803b5a..6fe35c0 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -12,6 +12,7 @@ from dash import Input from dash import Output from dash import State +from dash import callback_context as ctx from dash import clientside_callback from dash import dcc from dash import html @@ -103,51 +104,52 @@ def add_block(n_clicks, blocks_id): # noqa: D103 @app.callback( Output("blocks-container", "children"), Input("blocks-id", "data"), - Input("blocks-container", "children"), + State("blocks-container", "children"), ) -def display_blocks(blocks_id, blocks): # noqa: D103 - # Start with one block in the layout and then add additional blocks dynamically - blocks = [ - dmc.Grid( - id={"type": "gm-block", "index": block_id}, - children=[ - dmc.GridCol( - dbc.Button( - [html.I(className="fas fa-plus")], - id={"type": "gm-add-button", "index": block_id}, - className="btn-primary", - style={ - "display": "block" if i == len(blocks_id) - 1 else "none" - }, # Show button only on the latest block - ), - span=1, +def display_blocks(blocks_id, existing_blocks): # noqa: D103 + new_block_id = blocks_id[-1] + + new_block = dmc.Grid( + id={"type": "gm-block", "index": new_block_id}, + children=[ + dmc.GridCol( + dbc.Button( + [html.I(className="fas fa-plus")], + id={"type": "gm-add-button", "index": new_block_id}, + className="btn-primary", ), - dmc.GridCol( - dcc.Dropdown( - options=[ - {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, - ], - value="GCF_ID", - id={"type": "gm-dropdown-menu", "index": block_id}, - clearable=False, - ), - span=6, + span=1, + ), + dmc.GridCol( + dcc.Dropdown( + options=[ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + ], + value="GCF_ID", + id={"type": "gm-dropdown-menu", "index": new_block_id}, + clearable=False, ), - dmc.GridCol( - dmc.TextInput( - id={"type": "gm-dropdown-input", "index": block_id}, - placeholder="", - className="custom-textinput", - ), - span=5, + span=6, + ), + dmc.GridCol( + dmc.TextInput( + id={"type": "gm-dropdown-input", "index": new_block_id}, + placeholder="1, 2, 3, ...", + className="custom-textinput", ), - ], - gutter="md", - ) - for i, block_id in enumerate(blocks_id) - ] - return blocks + span=5, + ), + ], + gutter="md", + ) + + # Hide the add button on the previous last block + existing_blocks[-1]["props"]["children"][0]["props"]["children"]["props"]["style"] = { + "display": "none" + } + + return existing_blocks + [new_block] @app.callback( @@ -156,8 +158,10 @@ def display_blocks(blocks_id, blocks): # noqa: D103 Input({"type": "gm-dropdown-menu", "index": MATCH}, "value"), ) def update_placeholder(selected_value): # noqa: D103 + if not ctx.triggered: + # Callback was not triggered by user interaction, don't change anything + raise dash.exceptions.PreventUpdate if selected_value == "GCF_ID": return "1, 2, 3, ...", "" elif selected_value == "BSC_CLASS": return "Enter one or more GCF BiG-SCAPE classes", "" - return "", "" From 34fb975ed0576d9176cda52971fd80005d9ab6e5 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Wed, 7 Aug 2024 15:45:59 +0200 Subject: [PATCH 20/29] change bigscape class to multivalue bgc class --- app/callbacks.py | 50 +++++++++++++++++++++++++++++++++++++----------- app/layouts.py | 36 ++++++++++++++++++++++++++++------ 2 files changed, 69 insertions(+), 17 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 6fe35c0..59dc840 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -124,7 +124,7 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 dcc.Dropdown( options=[ {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + {"label": "BGC Class", "value": "BGC_CLASS"}, ], value="GCF_ID", id={"type": "gm-dropdown-menu", "index": new_block_id}, @@ -133,11 +133,28 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 span=6, ), dmc.GridCol( - dmc.TextInput( - id={"type": "gm-dropdown-input", "index": new_block_id}, - placeholder="1, 2, 3, ...", - className="custom-textinput", - ), + [ + dmc.TextInput( + id={"type": "gm-dropdown-ids-text-input", "index": new_block_id}, + placeholder="1, 2, 3, ...", + className="custom-textinput", + ), + dcc.Dropdown( + id={"type": "gm-dropdown-bgc-class-dropdown", "index": new_block_id}, + options=[ + {"label": "NRP", "value": "NRP"}, + {"label": "Polyketide", "value": "POLYKETIDE"}, + {"label": "RiPP", "value": "RIPP"}, + {"label": "Terpene", "value": "TERPENE"}, + {"label": "Saccharide", "value": "SAACCHARIDE"}, + {"label": "Alkaloid", "value": "ALKALOID"}, + {"label": "Other", "value": "OTHER"}, + {"label": "Unknown", "value": "UNKNOWN"}, + ], + multi=True, + style={"display": "none"}, + ), + ], span=5, ), ], @@ -153,8 +170,12 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 @app.callback( - Output({"type": "gm-dropdown-input", "index": MATCH}, "placeholder"), - Output({"type": "gm-dropdown-input", "index": MATCH}, "value"), + Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "style"), + Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "style"), + Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "placeholder"), + Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "placeholder"), + Output({"type": "gm-dropdown-ids-text-input", "index": MATCH}, "value"), + Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "value"), Input({"type": "gm-dropdown-menu", "index": MATCH}, "value"), ) def update_placeholder(selected_value): # noqa: D103 @@ -162,6 +183,13 @@ def update_placeholder(selected_value): # noqa: D103 # Callback was not triggered by user interaction, don't change anything raise dash.exceptions.PreventUpdate if selected_value == "GCF_ID": - return "1, 2, 3, ...", "" - elif selected_value == "BSC_CLASS": - return "Enter one or more GCF BiG-SCAPE classes", "" + return {"display": "block"}, {"display": "none"}, "1, 2, 3, ...", "", "", [] + elif selected_value == "BGC_CLASS": + return ( + {"display": "none"}, + {"display": "block"}, + "", + "Select one or more BGC classes", + "", + [], + ) diff --git a/app/layouts.py b/app/layouts.py index 0ca69a7..6b66d1d 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -94,7 +94,7 @@ dcc.Dropdown( options=[ {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BiG-SCAPE Class", "value": "BSC_CLASS"}, + {"label": "BGC Class", "value": "BGC_CLASS"}, ], value="GCF_ID", id={"type": "gm-dropdown-menu", "index": initial_block_id}, @@ -103,11 +103,35 @@ span=6, ), dmc.GridCol( - dmc.TextInput( - id={"type": "gm-dropdown-input", "index": initial_block_id}, - placeholder="1, 2, 3, ...", - className="custom-textinput", - ), + [ + dmc.TextInput( + id={ + "type": "gm-dropdown-ids-text-input", + "index": initial_block_id, + }, + placeholder="1, 2, 3, ...", + className="custom-textinput", + ), + dcc.Dropdown( + id={ + "type": "gm-dropdown-bgc-class-dropdown", + "index": initial_block_id, + }, + options=[ + {"label": "NRP", "value": "NRP"}, + {"label": "Polyketide", "value": "POLYKETIDE"}, + {"label": "RiPP", "value": "RIPP"}, + {"label": "Terpene", "value": "TERPENE"}, + {"label": "Saccharide", "value": "SAACCHARIDE"}, + {"label": "Alkaloid", "value": "ALKALOID"}, + {"label": "Other", "value": "OTHER"}, + {"label": "Unknown", "value": "UNKNOWN"}, + ], + placeholder="Select one or more BGC classes", + multi=True, + style={"display": "none"}, + ), + ], span=5, ), ], From 5bcabfe120fafb40a382941a2850d37ffd4bcc03 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Wed, 7 Aug 2024 16:50:29 +0200 Subject: [PATCH 21/29] update tests --- tests/test_callbacks.py | 69 +++++++++++++++-------------------------- 1 file changed, 25 insertions(+), 44 deletions(-) diff --git a/tests/test_callbacks.py b/tests/test_callbacks.py index 19007e6..2f7974e 100644 --- a/tests/test_callbacks.py +++ b/tests/test_callbacks.py @@ -1,10 +1,9 @@ -from contextvars import copy_context +import uuid +import dash import pytest -from dash._callback_context import context_value -from dash._utils import AttributeDict from dash_uploader import UploadStatus +from app.callbacks import add_block from app.callbacks import disable_tabs -from app.callbacks import gm_filter from app.callbacks import upload_data from . import DATA_DIR @@ -36,48 +35,30 @@ def test_disable_tabs(): assert result[1] is False # MG tab should be enabled +@pytest.fixture +def mock_uuid(monkeypatch): + def mock_uuid4(): + return "test-uuid" + + monkeypatch.setattr(uuid, "uuid4", mock_uuid4) + + @pytest.mark.parametrize( - "gcf_ids, gcf_bigscape, triggered_prop_id, expected_result", + "n_clicks, initial_blocks, expected_result", [ + ([], ["block1"], pytest.raises(dash.exceptions.PreventUpdate)), # no buttons clicked + ([1], ["block1", "block2"], ["block1", "block2", "test-uuid"]), # one button clicked once ( - "10, 34, 56", - "", - "gcf-ids-dropdown-input.value", - ("10, 34, 56", ""), - ), # gcf-ids-dropdown-input triggered - ( - "", - "class1", - "gcf-bigscape-dropdown-input.value", - ("", "class1"), - ), # gcf-bigscape-dropdown-input triggered - ( - "10, 34, 56", - "class1", - "gcf-ids-dropdown-clear.n_clicks", - ("", "class1"), - ), # gcf-ids-dropdown-clear triggered - ( - "10, 34, 56", - "class1", - "gcf-bigscape-dropdown-clear.n_clicks", - ("10, 34, 56", ""), - ), # gcf-bigscape-dropdown-clear triggered - ("", "", "no_triggering_context", ("", "")), # No triggering context + [1, 1, 1], + ["block1", "block2"], + ["block1", "block2", "test-uuid"], + ), # three buttons, each clicked once ], ) -def test_gm_filter(gcf_ids, gcf_bigscape, triggered_prop_id, expected_result): - def run_callback(): - gcf_ids_clear = None - gcf_bigscape_clear = None - if triggered_prop_id == "no_triggering_context": - context_value.set(AttributeDict(**{"triggered_inputs": []})) - else: - context_value.set( - AttributeDict(**{"triggered_inputs": [{"prop_id": triggered_prop_id}]}) - ) - return gm_filter(gcf_ids, gcf_ids_clear, gcf_bigscape, gcf_bigscape_clear) - - ctx = copy_context() - output = ctx.run(run_callback) - assert output == expected_result +def test_add_block(mock_uuid, n_clicks, initial_blocks, expected_result): + if isinstance(expected_result, list): + result = add_block(n_clicks, initial_blocks) + assert result == expected_result + else: + with expected_result: + add_block(n_clicks, initial_blocks) From 56d32a4f8e8a5109745c705f68f9a725220c5b55 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 15:37:50 +0200 Subject: [PATCH 22/29] order BGC classes and add config --- app/callbacks.py | 26 +++++++++----------------- app/config.py | 19 +++++++++++++++++++ app/layouts.py | 24 ++++++++---------------- 3 files changed, 36 insertions(+), 33 deletions(-) create mode 100644 app/config.py diff --git a/app/callbacks.py b/app/callbacks.py index 59dc840..3a03ade 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -6,6 +6,10 @@ import dash_bootstrap_components as dbc import dash_mantine_components as dmc import dash_uploader as du +from config import GM_DROPDOWN_BGC_CLASS_OPTIONS +from config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER +from config import GM_DROPDOWN_MENU_OPTIONS +from config import GM_TEXT_INPUT_IDS_PLACEHOLDER from dash import ALL from dash import MATCH from dash import Dash @@ -122,10 +126,7 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 ), dmc.GridCol( dcc.Dropdown( - options=[ - {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BGC Class", "value": "BGC_CLASS"}, - ], + options=GM_DROPDOWN_MENU_OPTIONS, value="GCF_ID", id={"type": "gm-dropdown-menu", "index": new_block_id}, clearable=False, @@ -136,21 +137,12 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 [ dmc.TextInput( id={"type": "gm-dropdown-ids-text-input", "index": new_block_id}, - placeholder="1, 2, 3, ...", + placeholder=GM_TEXT_INPUT_IDS_PLACEHOLDER, className="custom-textinput", ), dcc.Dropdown( id={"type": "gm-dropdown-bgc-class-dropdown", "index": new_block_id}, - options=[ - {"label": "NRP", "value": "NRP"}, - {"label": "Polyketide", "value": "POLYKETIDE"}, - {"label": "RiPP", "value": "RIPP"}, - {"label": "Terpene", "value": "TERPENE"}, - {"label": "Saccharide", "value": "SAACCHARIDE"}, - {"label": "Alkaloid", "value": "ALKALOID"}, - {"label": "Other", "value": "OTHER"}, - {"label": "Unknown", "value": "UNKNOWN"}, - ], + options=GM_DROPDOWN_BGC_CLASS_OPTIONS, multi=True, style={"display": "none"}, ), @@ -183,13 +175,13 @@ def update_placeholder(selected_value): # noqa: D103 # Callback was not triggered by user interaction, don't change anything raise dash.exceptions.PreventUpdate if selected_value == "GCF_ID": - return {"display": "block"}, {"display": "none"}, "1, 2, 3, ...", "", "", [] + return {"display": "block"}, {"display": "none"}, GM_TEXT_INPUT_IDS_PLACEHOLDER, "", "", [] elif selected_value == "BGC_CLASS": return ( {"display": "none"}, {"display": "block"}, "", - "Select one or more BGC classes", + GM_DROPDOWN_BGC_CLASS_PLACEHOLDER, "", [], ) diff --git a/app/config.py b/app/config.py new file mode 100644 index 0000000..d1dea1b --- /dev/null +++ b/app/config.py @@ -0,0 +1,19 @@ +GM_DROPDOWN_MENU_OPTIONS = [ + {"label": "GCF ID", "value": "GCF_ID"}, + {"label": "BGC Class", "value": "BGC_CLASS"}, +] + +GM_TEXT_INPUT_IDS_PLACEHOLDER = "1, 2, 3, ..." + +GM_DROPDOWN_BGC_CLASS_OPTIONS = [ + {"label": "Alkaloid", "value": "ALKALOID"}, + {"label": "NRP", "value": "NRP"}, + {"label": "Polyketide", "value": "POLYKETIDE"}, + {"label": "RiPP", "value": "RIPP"}, + {"label": "Saccharide", "value": "SAACCHARIDE"}, + {"label": "Terpene", "value": "TERPENE"}, + {"label": "Other", "value": "OTHER"}, + {"label": "Unknown", "value": "UNKNOWN"}, +] + +GM_DROPDOWN_BGC_CLASS_PLACEHOLDER = "Select one or more BGC classes" diff --git a/app/layouts.py b/app/layouts.py index 6b66d1d..a633f43 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -2,6 +2,10 @@ import dash_bootstrap_components as dbc import dash_mantine_components as dmc import dash_uploader as du +from config import GM_DROPDOWN_BGC_CLASS_OPTIONS +from config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER +from config import GM_DROPDOWN_MENU_OPTIONS +from config import GM_TEXT_INPUT_IDS_PLACEHOLDER from dash import dcc from dash import html @@ -92,10 +96,7 @@ ), dmc.GridCol( dcc.Dropdown( - options=[ - {"label": "GCF ID", "value": "GCF_ID"}, - {"label": "BGC Class", "value": "BGC_CLASS"}, - ], + options=GM_DROPDOWN_MENU_OPTIONS, value="GCF_ID", id={"type": "gm-dropdown-menu", "index": initial_block_id}, clearable=False, @@ -109,7 +110,7 @@ "type": "gm-dropdown-ids-text-input", "index": initial_block_id, }, - placeholder="1, 2, 3, ...", + placeholder=GM_TEXT_INPUT_IDS_PLACEHOLDER, className="custom-textinput", ), dcc.Dropdown( @@ -117,17 +118,8 @@ "type": "gm-dropdown-bgc-class-dropdown", "index": initial_block_id, }, - options=[ - {"label": "NRP", "value": "NRP"}, - {"label": "Polyketide", "value": "POLYKETIDE"}, - {"label": "RiPP", "value": "RIPP"}, - {"label": "Terpene", "value": "TERPENE"}, - {"label": "Saccharide", "value": "SAACCHARIDE"}, - {"label": "Alkaloid", "value": "ALKALOID"}, - {"label": "Other", "value": "OTHER"}, - {"label": "Unknown", "value": "UNKNOWN"}, - ], - placeholder="Select one or more BGC classes", + options=GM_DROPDOWN_BGC_CLASS_OPTIONS, + placeholder=GM_DROPDOWN_BGC_CLASS_PLACEHOLDER, multi=True, style={"display": "none"}, ), From 0e232f4cbe36663ca2f3bf6649a6413a80a3a7d1 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 15:50:03 +0200 Subject: [PATCH 23/29] handle fake file upload --- app/callbacks.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 3a03ade..3f23d45 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -50,12 +50,18 @@ 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), - ) + try: + 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), + ) + except (pickle.UnpicklingError, EOFError, AttributeError): + return f"Error: {os.path.basename(latest_file)} is not a valid pickle file.", None + except Exception as e: + # Handle any other unexpected errors + return f"Error uploading file: {str(e)}", None return "No file uploaded", None From 981817de3eed6ca6a1137eba5d53597e0b1e20f5 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:01:02 +0200 Subject: [PATCH 24/29] add type hints and doc stringas --- app/callbacks.py | 68 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 62 insertions(+), 6 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 3f23d45..226e4b4 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -2,6 +2,8 @@ import pickle import tempfile import uuid +from typing import Any +from typing import Optional import dash import dash_bootstrap_components as dbc import dash_mantine_components as dmc @@ -47,7 +49,15 @@ id="dash-uploader", output=[Output("dash-uploader-output", "children"), Output("file-store", "data")], ) -def upload_data(status: du.UploadStatus): # noqa: D103 +def upload_data(status: du.UploadStatus) -> tuple[str, Optional[str]]: + """Handle file upload and validate pickle files. + + Args: + status: The upload status object. + + Returns: + A tuple containing a message string and the file path (if successful). + """ if status.is_completed: latest_file = status.latest_file try: @@ -74,7 +84,15 @@ def upload_data(status: du.UploadStatus): # noqa: D103 [Input("file-store", "data")], prevent_initial_call=True, ) -def disable_tabs(file_name): # noqa: D103 +def disable_tabs(file_name: Optional[str]) -> tuple[bool, bool, bool]: + """Enable or disable tabs based on whether a file has been uploaded. + + Args: + file_name: The name of the uploaded file. + + Returns: + A tuple of boolean values indicating whether each tab should be disabled. + """ if file_name is None: # Disable the tabs return True, True, True @@ -87,7 +105,15 @@ def disable_tabs(file_name): # noqa: D103 Output("file-content-mg", "children"), [Input("file-store", "data")], ) -def display_file_contents(file_path): # noqa: D103 +def display_file_contents(file_path: Optional[str]) -> str: + """Read and display the contents of the uploaded file. + + Args: + file_path: The path to the uploaded file. + + Returns: + A string representation of the file contents. + """ if file_path is not None: with open(file_path, "rb") as f: data = pickle.load(f) @@ -102,7 +128,16 @@ def display_file_contents(file_path): # noqa: D103 Input({"type": "gm-add-button", "index": ALL}, "n_clicks"), State("blocks-id", "data"), ) -def add_block(n_clicks, blocks_id): # noqa: D103 +def add_block(n_clicks: list[int], blocks_id: list[str]) -> list[str]: + """Add a new block to the layout when the add button is clicked. + + Args: + n_clicks: List of number of clicks for each add button. + blocks_id: Current list of block IDs. + + Returns: + Updated list of block IDs. + """ if not any(n_clicks): raise dash.exceptions.PreventUpdate # Create a unique ID for the new block @@ -116,7 +151,18 @@ def add_block(n_clicks, blocks_id): # noqa: D103 Input("blocks-id", "data"), State("blocks-container", "children"), ) -def display_blocks(blocks_id, existing_blocks): # noqa: D103 +def display_blocks( + blocks_id: list[str], existing_blocks: list[dict[str, Any]] +) -> list[dict[str, Any]]: + """Update the display of blocks based on the current block IDs. + + Args: + blocks_id: List of block IDs. + existing_blocks: Current list of block components. + + Returns: + Updated list of block components. + """ new_block_id = blocks_id[-1] new_block = dmc.Grid( @@ -176,7 +222,17 @@ def display_blocks(blocks_id, existing_blocks): # noqa: D103 Output({"type": "gm-dropdown-bgc-class-dropdown", "index": MATCH}, "value"), Input({"type": "gm-dropdown-menu", "index": MATCH}, "value"), ) -def update_placeholder(selected_value): # noqa: D103 +def update_placeholder( + selected_value: str, +) -> tuple[dict[str, str], dict[str, str], str, str, str, list[Any]]: + """Update the visibility and placeholders of input fields based on the selected dropdown value. + + Args: + selected_value: The value selected in the dropdown menu. + + Returns: + A tuple containing style, placeholder, and value updates for the input fields. + """ if not ctx.triggered: # Callback was not triggered by user interaction, don't change anything raise dash.exceptions.PreventUpdate From c11da79d196347e221bcb87461bb1f876b83439e Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:04:31 +0200 Subject: [PATCH 25/29] fix mypy error --- app/callbacks.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/callbacks.py b/app/callbacks.py index 226e4b4..1592607 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -247,3 +247,6 @@ def update_placeholder( "", [], ) + else: + # This case should never occur due to the Literal type, but it satisfies mypy + return {"display": "none"}, {"display": "none"}, "", "", "", [] From 18ebd60c1bf5706ef9331e248861c120119c0d9d Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:08:52 +0200 Subject: [PATCH 26/29] fix tests --- app/callbacks.py | 8 ++++---- app/layouts.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 1592607..44e1392 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -8,10 +8,6 @@ import dash_bootstrap_components as dbc import dash_mantine_components as dmc import dash_uploader as du -from config import GM_DROPDOWN_BGC_CLASS_OPTIONS -from config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER -from config import GM_DROPDOWN_MENU_OPTIONS -from config import GM_TEXT_INPUT_IDS_PLACEHOLDER from dash import ALL from dash import MATCH from dash import Dash @@ -22,6 +18,10 @@ from dash import clientside_callback from dash import dcc from dash import html +from .config import GM_DROPDOWN_BGC_CLASS_OPTIONS +from .config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER +from .config import GM_DROPDOWN_MENU_OPTIONS +from .config import GM_TEXT_INPUT_IDS_PLACEHOLDER dash._dash_renderer._set_react_version("18.2.0") diff --git a/app/layouts.py b/app/layouts.py index a633f43..81a32ca 100644 --- a/app/layouts.py +++ b/app/layouts.py @@ -2,12 +2,12 @@ import dash_bootstrap_components as dbc import dash_mantine_components as dmc import dash_uploader as du -from config import GM_DROPDOWN_BGC_CLASS_OPTIONS -from config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER -from config import GM_DROPDOWN_MENU_OPTIONS -from config import GM_TEXT_INPUT_IDS_PLACEHOLDER from dash import dcc from dash import html +from .config import GM_DROPDOWN_BGC_CLASS_OPTIONS +from .config import GM_DROPDOWN_BGC_CLASS_PLACEHOLDER +from .config import GM_DROPDOWN_MENU_OPTIONS +from .config import GM_TEXT_INPUT_IDS_PLACEHOLDER # ------------------ Nav Bar ------------------ # From 81c258cfff3ab41e6fea3687fb31379a5369e62c Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:21:59 +0200 Subject: [PATCH 27/29] narrow bars for fewer number of bars --- app/callbacks.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 94ab898..9814813 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -95,17 +95,32 @@ def gm_plot(file_path): # noqa: D103 y_values = [len(keys_by_value[x]) for x in x_values] hover_texts = [", ".join(keys_by_value[x]) for x in x_values] + # Adjust bar width based on number of data points + if len(x_values) <= 5: + bar_width = 0.4 + else: + bar_width = None + # Create the bar plot fig = go.Figure( data=[ go.Bar( - x=x_values, y=y_values, text=hover_texts, hoverinfo="text", textposition="none" + x=x_values, + y=y_values, + text=hover_texts, + hoverinfo="text", + textposition="none", + width=bar_width, # Set the bar width ) ] ) # Update layout - fig.update_layout(xaxis_title="# BGCs", yaxis_title="# GCFs", xaxis=dict(type="category")) + fig.update_layout( + xaxis_title="# BGCs", + yaxis_title="# GCFs", + xaxis=dict(type="category"), + ) return fig, {"display": "block"}, "uploaded!!" return {}, {"display": "none"}, "No data available" From 7f34eea6c5f09c25aebfe1b6db442022678f547c Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:25:04 +0200 Subject: [PATCH 28/29] simplify defaultdict --- app/callbacks.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/app/callbacks.py b/app/callbacks.py index 9814813..888f6e8 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -1,7 +1,6 @@ import os import pickle import tempfile -from collections import defaultdict import dash import dash_bootstrap_components as dbc import dash_uploader as du @@ -83,17 +82,16 @@ def gm_plot(file_path): # noqa: D103 _, gcfs, _, _, _, _ = data n_bgcs = {} for gcf in gcfs: - n_bgcs[gcf.id] = len(gcf.bgcs) + n = len(gcf.bgcs) + if n not in n_bgcs: + n_bgcs[n] = [gcf.id] + else: + n_bgcs[n].append(gcf.id) - keys_by_value = defaultdict(list) - - for key, value in n_bgcs.items(): - keys_by_value[value].append(key) - - x_values = list(keys_by_value.keys()) + x_values = list(n_bgcs.keys()) x_values.sort() - y_values = [len(keys_by_value[x]) for x in x_values] - hover_texts = [", ".join(keys_by_value[x]) for x in x_values] + y_values = [len(n_bgcs[x]) for x in x_values] + hover_texts = [", ".join(n_bgcs[x]) for x in x_values] # Adjust bar width based on number of data points if len(x_values) <= 5: From 18d424a3986840793ec5741a3c88f1f76cfc1934 Mon Sep 17 00:00:00 2001 From: gcroci2 Date: Fri, 9 Aug 2024 16:28:20 +0200 Subject: [PATCH 29/29] add GCF IDs to hover texts --- app/callbacks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/callbacks.py b/app/callbacks.py index 888f6e8..663b3d9 100644 --- a/app/callbacks.py +++ b/app/callbacks.py @@ -91,7 +91,7 @@ def gm_plot(file_path): # noqa: D103 x_values = list(n_bgcs.keys()) x_values.sort() y_values = [len(n_bgcs[x]) for x in x_values] - hover_texts = [", ".join(n_bgcs[x]) for x in x_values] + hover_texts = [f"GCF IDs: {', '.join(n_bgcs[x])}" for x in x_values] # Adjust bar width based on number of data points if len(x_values) <= 5: