def register_uc_1_1_callbacks(app, plot_service) -> None:
"""
Register UC-1.1 callbacks with Dash app.
Parameters
----------
app : Dash
Dash application instance.
plot_service : PlotService
Singleton PlotService instance (shared across all callbacks).
Notes
-----
- Registers panel toggle and plot rendering callbacks
- Refer to official documentation for processing logic details
"""
logger.info("[UC-1.1] ========== REGISTERING UC-1.1 CALLBACKS ==========")
logger.info("[UC-1.1] Using shared PlotService singleton instance")
@app.callback(
Output("uc-1-1-collapse", "is_open"),
Input("uc-1-1-collapse-button", "n_clicks"),
State("uc-1-1-collapse", "is_open"),
prevent_initial_call=True,
)
def toggle_uc_1_1_info_panel(n_clicks: Optional[int], is_open: bool) -> bool:
"""
Toggle UC-1.1 informative panel visibility.
Parameters
----------
n_clicks : int, optional
Number of times collapse button was clicked.
is_open : bool
Current collapse state (True = open, False = closed).
Returns
-------
bool
New collapse state (inverted from current state).
"""
if n_clicks is None:
raise PreventUpdate
return not is_open
@app.callback(
Output("uc-1-1-chart", "children"),
Input("uc-1-1-accordion-group", "active_item"),
State("merged-result-store", "data"),
prevent_initial_call=True,
)
def render_uc_1_1(active_item: Optional[str], merged_data: Optional[list]) -> Any:
"""
Generate UpSet plot for database overlap analysis.
Parameters
----------
active_item : str, optional
Active accordion item ID.
merged_data : list, optional
Merged result data from store.
Returns
-------
dcc.Graph or html.Div
UpSet plot or error message.
Notes
-----
- Extracts and normalizes KO sets from databases
- Generates visualization using UpSetStrategy via PlotService
"""
logger.info(
"UC-1.1 render_uc_1_1 callback triggered",
extra={"active_item": active_item, "has_data": bool(merged_data)},
)
# Validate trigger
if not active_item or active_item != "uc-1-1-accordion":
logger.debug(
"Preventing update: accordion not active",
extra={"active_item": active_item},
)
raise PreventUpdate
# Validate data
if not merged_data:
logger.warning("No merged data available in store")
return html.Div(
"No data available. Please load data first.",
className="alert alert-warning",
)
# Extract database DataFrames
logger.debug("Extracting database DataFrames from merged store")
biorempp_df, hadeg_df, kegg_df = _extract_database_data(merged_data)
if biorempp_df is None:
logger.error(
"Failed to extract database DataFrames",
extra={"merged_data_len": len(merged_data)},
)
return html.Div(
"Error extracting database data. Please check data format.",
className="alert alert-danger",
)
logger.info(
"DataFrames extracted successfully",
extra={
"biorempp_rows": len(biorempp_df),
"hadeg_rows": len(hadeg_df),
"kegg_rows": len(kegg_df),
},
)
# Build KO sets
logger.debug("Building KO sets for each database")
ko_sets = _build_ko_sets(biorempp_df, hadeg_df, kegg_df)
if ko_sets is None:
logger.error("Failed to build KO sets - KO column not found")
return html.Div(
"Error: Could not find KO column in one or more databases.",
className="alert alert-danger",
)
logger.info(
"KO sets built successfully",
extra={
"biorempp_kos": len(ko_sets.get("BioRemPP", set())),
"hadeg_kos": len(ko_sets.get("HADEG", set())),
"kegg_kos": len(ko_sets.get("KEGG", set())),
},
)
# Validate sets
if not any(ko_sets.values()):
logger.warning("No KO identifiers found after normalization")
return html.Div(
"No KO identifiers found in any database.",
className="alert alert-warning",
)
# Prepare data for UpSet plot
# Convert to DataFrame with category and identifier columns
logger.debug("Converting KO sets to DataFrame format for UpSetStrategy")
upset_data = []
for db_name, ko_set in ko_sets.items():
for ko in ko_set:
upset_data.append({"category": db_name, "identifier": ko})
upset_df = pd.DataFrame(upset_data)
logger.debug(
"UpSet DataFrame prepared",
extra={"total_rows": len(upset_df), "columns": list(upset_df.columns)},
)
# Generate plot
try:
logger.info(
"Calling PlotService to generate UpSet plot",
extra={"use_case_id": "UC-1.1"},
)
fig = plot_service.generate_plot(use_case_id="UC-1.1", data=upset_df)
logger.info("UpSet plot generated successfully")
logger.debug(
"Returning dcc.Graph component",
extra={"figure_type": type(fig).__name__},
)
# Prepare a sanitized base filename for image downloads (no extension)
try:
suggested = sanitize_filename("UC-1.1", "upset", "png")
base_filename = os.path.splitext(suggested)[0]
except Exception:
base_filename = "UC-1-1_upset"
return dcc.Graph(
figure=fig,
config={
"displayModeBar": True,
"displaylogo": False,
"toImageButtonOptions": {
"format": "svg",
"filename": base_filename,
"height": 800,
"width": 1000,
"scale": 2,
},
},
)
except Exception as e:
logger.error(f"Error generating UpSet plot: {str(e)}", exc_info=True)
return html.Div(
f"Error generating UpSet plot: {str(e)}", className="alert alert-danger"
)
logger.info("[UC-1.1] ========== CALLBACKS REGISTERED SUCCESSFULLY ==========")