Skip to content

UC 1.1 Callbacks

uc_1_1_callbacks

UC-1.1 Callbacks - Database Overlap Analysis.

This module implements callback functions for database overlap and unique contributions analysis using UpSet plots.

Functions:

Name Description
register_uc_1_1_callbacks

Register all UC-1.1 callbacks with Dash app.

Notes
  • Refer to official documentation for use case details
  • Uses UpSetStrategy for set intersection visualization
  • Implements on-demand rendering for performance optimization

Version: 1.0.0

Functions

register_uc_1_1_callbacks

register_uc_1_1_callbacks(app, plot_service) -> None

Register UC-1.1 callbacks with Dash app.

Parameters:

Name Type Description Default
app Dash

Dash application instance.

required
plot_service PlotService

Singleton PlotService instance (shared across all callbacks).

required
Notes
  • Registers panel toggle and plot rendering callbacks
  • Refer to official documentation for processing logic details
Source code in src/presentation/callbacks/module1/uc_1_1_callbacks.py
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 ==========")