Skip to content

Network Strategy

network_strategy

Network Strategy - Interaction Network Visualizations.

This module implements the NetworkStrategy for creating network (graph) diagrams that visualize relationships between entities such as genes, compounds, samples, and pathways.

Classes:

Name Description
NetworkStrategy

Strategy for network diagram generation using Plotly and NetworkX.

Notes
  • Supports bipartite networks (two node types)
  • Supports similarity networks (weighted edges based on shared attributes)
  • Multiple layout algorithms available (spring, circular, kamada_kawai)

For supported use cases, refer to the official documentation.

Classes

NetworkStrategy

NetworkStrategy(config: Dict[str, Any])

Bases: BasePlotStrategy

Strategy for network diagram interaction visualizations.

This strategy creates network graphs showing relationships between entities where nodes represent entities and edges represent connections.

Parameters:

Name Type Description Default
config Dict[str, Any]

Complete configuration from YAML file.

required

Attributes:

Name Type Description
data_config Dict[str, Any]

Data processing configuration.

plotly_config Dict[str, Any]

Plotly-specific configuration.

mode str

Processing mode: 'bipartite' or 'similarity'.

source_column str

Column name for source/primary entities.

target_column str

Column name for target/secondary entities.

node_type_column Optional[str]

Column indicating node type (for bipartite mode).

shared_column Optional[str]

Column for computing similarity (for similarity mode).

Methods:

Name Description
validate_data

Validate input data for network diagram requirements

process_data

Process data and create graph structure

create_figure

Create network diagram figure from processed graph data

Notes
  • Supports bipartite and similarity network modes
  • Multiple layout algorithms available
  • Visual encoding: node size/color by degree, edge width by weight

Initialize strategy with configuration.

Parameters:

Name Type Description Default
config Dict[str, Any]

Complete configuration from YAML file.

required
Source code in src/domain/plot_strategies/charts/network_strategy.py
def __init__(self, config: Dict[str, Any]):
    """
    Initialize strategy with configuration.

    Parameters
    ----------
    config : Dict[str, Any]
        Complete configuration from YAML file.
    """
    super().__init__(config)
    self.data_config = config.get("data", {})
    self.plotly_config = self.viz_config.get("plotly", {})

    # Processing mode
    self.mode: str = self.plotly_config.get("mode", "bipartite")

    # Column configuration
    self.source_column: str = self.plotly_config.get("source_column", "genesymbol")
    self.target_column: str = self.plotly_config.get(
        "target_column", "compoundname"
    )

    # Similarity mode configuration
    self.group_by_column: Optional[str] = self.plotly_config.get(
        "group_by_column", None
    )
    self.shared_column: Optional[str] = self.plotly_config.get(
        "shared_column", None
    )

    # Layout algorithm configuration
    layout_algo_config = self.plotly_config.get("layout_algorithm", {})
    self.layout_algorithm: str = layout_algo_config.get("algorithm", "spring")
    self.layout_k: float = layout_algo_config.get("k", 0.15)
    self.layout_iterations: int = layout_algo_config.get("iterations", 100)
    self.layout_seed: int = layout_algo_config.get("seed", 42)

    # Visual configuration
    self.node_size: int = self.plotly_config.get("node_size", 10)
    self.edge_width: float = self.plotly_config.get("edge_width", 0.5)
    self.edge_color: str = self.plotly_config.get("edge_color", "#888")
    self.colorscale: str = self.plotly_config.get("colorscale", "YlGnBu")

    # Node type colors (for bipartite mode)
    node_colors = self.plotly_config.get("node_colors", {})
    self.source_color: str = node_colors.get("source", "darkblue")
    self.target_color: str = node_colors.get("target", "darkgreen")

    # Minimum edge weight (for similarity mode)
    self.min_edge_weight: int = self.plotly_config.get("min_edge_weight", 1)

    logger.info(
        f"NetworkStrategy initialized for "
        f"{self.metadata.get('use_case_id', 'unknown')}: "
        f"mode='{self.mode}', "
        f"source='{self.source_column}', target='{self.target_column}'"
    )
Functions
validate_data
validate_data(df: DataFrame) -> None

Validate input data for network diagram requirements.

Parameters:

Name Type Description Default
df DataFrame

Input data to validate.

required

Raises:

Type Description
ValueError

If DataFrame is empty, required columns missing, or mode configuration invalid.

Source code in src/domain/plot_strategies/charts/network_strategy.py
def validate_data(self, df: pd.DataFrame) -> None:
    """
    Validate input data for network diagram requirements.

    Parameters
    ----------
    df : pd.DataFrame
        Input data to validate.

    Raises
    ------
    ValueError
        If DataFrame is empty, required columns missing, or mode
        configuration invalid.
    """
    logger.debug(
        f"Validating data - Shape: {df.shape}, " f"Columns: {df.columns.tolist()}"
    )

    if df.empty:
        raise ValueError("Input DataFrame is empty")

    # Mode-specific validation
    if self.mode == "bipartite":
        required_cols = [self.source_column, self.target_column]
        missing = [c for c in required_cols if c not in df.columns]
        if missing:
            raise ValueError(
                f"Missing columns for bipartite mode: {missing}. "
                f"Available: {df.columns.tolist()}"
            )

    elif self.mode == "similarity":
        if not self.group_by_column or not self.shared_column:
            raise ValueError(
                "Similarity mode requires 'group_by_column' and "
                "'shared_column' configuration."
            )
        required_cols = [self.group_by_column, self.shared_column]
        missing = [c for c in required_cols if c not in df.columns]
        if missing:
            raise ValueError(
                f"Missing columns for similarity mode: {missing}. "
                f"Available: {df.columns.tolist()}"
            )

    else:
        raise ValueError(
            f"Unknown mode: '{self.mode}'. " f"Supported: 'bipartite', 'similarity'"
        )

    logger.info(f"Data validation passed - {len(df)} records, mode='{self.mode}'")
process_data
process_data(df: DataFrame) -> pd.DataFrame

Process data and create graph structure for network visualization.

Creates edge list based on configured mode (bipartite or similarity).

Parameters:

Name Type Description Default
df DataFrame

Input data with required columns.

required

Returns:

Type Description
DataFrame

Graph data as DataFrame with source, target, and weight columns.

Source code in src/domain/plot_strategies/charts/network_strategy.py
def process_data(self, df: pd.DataFrame) -> pd.DataFrame:
    """
    Process data and create graph structure for network visualization.

    Creates edge list based on configured mode (bipartite or similarity).

    Parameters
    ----------
    df : pd.DataFrame
        Input data with required columns.

    Returns
    -------
    pd.DataFrame
        Graph data as DataFrame with source, target, and weight columns.
    """
    logger.info(f"Processing data in '{self.mode}' mode...")

    if self.mode == "bipartite":
        graph_data = self._process_bipartite(df)
    elif self.mode == "similarity":
        graph_data = self._process_similarity(df)
    else:
        raise ValueError(f"Unknown mode: {self.mode}")

    if graph_data.empty:
        raise ValueError(
            "No valid edges after processing. "
            "Check data or adjust min_edge_weight."
        )

    logger.info(f"Graph data created - {len(graph_data)} edges")

    return graph_data
create_figure
create_figure(processed_df: DataFrame) -> go.Figure

Create network diagram figure from processed graph data.

Parameters:

Name Type Description Default
processed_df DataFrame

Processed edge list data.

required

Returns:

Type Description
Figure

Configured Plotly figure with network diagram.

Source code in src/domain/plot_strategies/charts/network_strategy.py
def create_figure(self, processed_df: pd.DataFrame) -> go.Figure:
    """
    Create network diagram figure from processed graph data.

    Parameters
    ----------
    processed_df : pd.DataFrame
        Processed edge list data.

    Returns
    -------
    go.Figure
        Configured Plotly figure with network diagram.
    """
    logger.debug("Creating network diagram figure...")

    chart_config = self.plotly_config.get("chart", {})
    layout_config = self.plotly_config.get("layout", {})

    # Build NetworkX graph
    G = self._build_graph(processed_df)

    # Calculate node positions
    pos = self._calculate_layout(G)

    # Handle title configuration (support both string and dict)
    title_config = chart_config.get("title", {})
    if isinstance(title_config, str):
        # Backward compatibility: string title
        show_title = True
        title_text = title_config
        title_font_size = 16
    else:
        # New format: dict with show, text, font
        show_title = title_config.get("show", True)
        title_text = (
            title_config.get("text", "Network Diagram") if show_title else ""
        )
        title_font_size = title_config.get("font", {}).get("size", 16)

    # Create traces
    traces = []

    # Create edge traces
    edge_traces = self._create_edge_traces(G, pos, processed_df)
    traces.extend(edge_traces)

    # Create node traces
    if self.mode == "bipartite":
        node_traces = self._create_bipartite_node_traces(G, pos, processed_df)
    else:
        node_traces = self._create_similarity_node_traces(G, pos)

    traces.extend(node_traces)

    # Create figure
    fig = go.Figure(data=traces)

    # Layout configuration
    height = layout_config.get("height", 800)
    use_autosize = layout_config.get("autosize", False)

    # Show legend configuration
    show_legend_config = chart_config.get("show_legend", None)
    if show_legend_config is not None:
        show_legend = show_legend_config
    else:
        # Default: show legend only for bipartite mode
        show_legend = self.mode == "bipartite"

    # Get margin configuration
    margin_config = layout_config.get("margin", {})
    margin = dict(
        l=margin_config.get("l", 20),
        r=margin_config.get("r", 20),
        t=margin_config.get("t", 60),
        b=margin_config.get("b", 20),
    )

    # Get background colors
    plot_bgcolor = layout_config.get("plot_bgcolor", "white")
    paper_bgcolor = layout_config.get("paper_bgcolor", "white")

    # Get template
    template = layout_config.get("template", "simple_white")

    # Build layout update dict
    layout_update = {
        "height": height,
        "showlegend": show_legend,
        "legend": dict(x=1, y=1, bordercolor="Gainsboro", borderwidth=1),
        "hovermode": "closest",
        "xaxis": dict(showgrid=False, zeroline=False, showticklabels=False),
        "yaxis": dict(showgrid=False, zeroline=False, showticklabels=False),
        "plot_bgcolor": plot_bgcolor,
        "paper_bgcolor": paper_bgcolor,
        "template": template,
        "margin": margin,
    }

    # Add title if enabled
    if show_title and title_text:
        layout_update["title"] = dict(
            text=title_text,
            x=0.5,
            xanchor="center",
            font=dict(size=title_font_size),
        )

    # Add autosize or width
    if use_autosize:
        layout_update["autosize"] = True
    else:
        if layout_config.get("width"):
            layout_update["width"] = layout_config.get("width")

    fig.update_layout(**layout_update)

    logger.info(
        f"Network diagram created - "
        f"{G.number_of_nodes()} nodes, {G.number_of_edges()} edges, "
        f"Layout: {self.layout_algorithm}"
    )

    return fig
apply_filters
apply_filters(df: DataFrame, filters: Optional[Dict[str, Any]] = None) -> pd.DataFrame

Apply filters to data.

This is a common implementation that can be overridden by subclasses if needed.

Parameters:

Name Type Description Default
df DataFrame

Data to filter.

required
filters Optional[Dict[str, Any]]

Filter specifications.

None

Returns:

Type Description
DataFrame

Filtered data.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def apply_filters(
    self, df: pd.DataFrame, filters: Optional[Dict[str, Any]] = None
) -> pd.DataFrame:
    """
    Apply filters to data.

    This is a common implementation that can be overridden
    by subclasses if needed.

    Parameters
    ----------
    df : pd.DataFrame
        Data to filter.
    filters : Optional[Dict[str, Any]], default=None
        Filter specifications.

    Returns
    -------
    pd.DataFrame
        Filtered data.
    """
    import logging

    logger = logging.getLogger(__name__)

    if not filters:
        logger.debug("No filters provided, returning original data")
        return df

    logger.info(
        f"Applying filters - Input shape: {df.shape}, "
        f"Columns: {df.columns.tolist()}"
    )
    logger.info(f"Filters to apply: {filters}")

    filtered_df = df.copy()

    # Get filter configurations
    filter_configs = self.config.get("filters", [])

    for filter_config in filter_configs:
        filter_id = filter_config.get("filter_id")
        filter_type = filter_config.get("type")

        if filter_id not in filters:
            continue

        filter_value = filters[filter_id]
        data_binding = filter_config.get("data_binding", {})
        column = data_binding.get("column")

        if not column or column not in filtered_df.columns:
            logger.warning(
                f"Filter '{filter_id}': Column '{column}' not found. "
                f"Available: {filtered_df.columns.tolist()}"
            )
            continue

        # Apply range filter
        if filter_type == "range" and isinstance(filter_value, list):
            min_val, max_val = filter_value
            logger.info(
                f"Applying range filter on '{column}': " f"[{min_val}, {max_val}]"
            )
            filtered_df = filtered_df[
                (filtered_df[column] >= min_val) & (filtered_df[column] <= max_val)
            ]
            logger.info(f"After filter: {len(filtered_df)} rows remaining")

    logger.info(f"Final filtered shape: {filtered_df.shape}")
    return filtered_df
apply_customizations
apply_customizations(fig: Figure, customizations: Optional[Any] = None) -> go.Figure

Apply custom styling to figure.

This is a hook for future customization features (FLEXIVEL and FLEXIVEL2).

Parameters:

Name Type Description Default
fig Figure

Base figure.

required
customizations Optional[Any]

Customization specifications.

None

Returns:

Type Description
Figure

Customized figure.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def apply_customizations(
    self, fig: go.Figure, customizations: Optional[Any] = None
) -> go.Figure:
    """
    Apply custom styling to figure.

    This is a hook for future customization features
    (FLEXIVEL and FLEXIVEL2).

    Parameters
    ----------
    fig : go.Figure
        Base figure.
    customizations : Optional[Any], default=None
        Customization specifications.

    Returns
    -------
    go.Figure
        Customized figure.
    """
    # Hook for future implementation
    return fig
generate_plot
generate_plot(data: DataFrame, filters: Optional[Dict[str, Any]] = None, customizations: Optional[Any] = None) -> go.Figure

Generate complete plot (Template Method).

This method orchestrates the entire plot generation process: 1. Validate input data 2. Process data 3. Apply filters 4. Create figure 5. Apply customizations

Parameters:

Name Type Description Default
data DataFrame

Input data.

required
filters Optional[Dict[str, Any]]

Filters to apply.

None
customizations Optional[Any]

Customizations to apply.

None

Returns:

Type Description
Figure

Complete Plotly figure.

Raises:

Type Description
ValueError

If validation fails.

Source code in src/domain/plot_strategies/base/base_plot_strategy.py
def generate_plot(
    self,
    data: pd.DataFrame,
    filters: Optional[Dict[str, Any]] = None,
    customizations: Optional[Any] = None,
) -> go.Figure:
    """
    Generate complete plot (Template Method).

    This method orchestrates the entire plot generation process:
    1. Validate input data
    2. Process data
    3. Apply filters
    4. Create figure
    5. Apply customizations

    Parameters
    ----------
    data : pd.DataFrame
        Input data.
    filters : Optional[Dict[str, Any]], default=None
        Filters to apply.
    customizations : Optional[Any], default=None
        Customizations to apply.

    Returns
    -------
    go.Figure
        Complete Plotly figure.

    Raises
    ------
    ValueError
        If validation fails.
    """
    # 1. Validate
    self.validate_data(data)

    # 2. Process
    processed_df = self.process_data(data)

    # 3. Filter
    filtered_df = self.apply_filters(processed_df, filters)

    # 4. Create figure
    figure = self.create_figure(filtered_df)

    # 5. Apply customizations (hook for future)
    figure = self.apply_customizations(figure, customizations)

    return figure