Skip to content

Pareto Viewer

pareto_viewer

Interactive Pareto front viewer with advanced features.

Provides 2D and 3D Pareto front visualization with: - Multiple set highlighting - Hover information showing all objectives - Trade-off curves and gradients - Reference points and ideal/nadir visualization

Classes

Functions

plot_pareto_front_2d

plot_pareto_front_2d(result: OptimizationResult, obj_x: int | str = 0, obj_y: int | str = 1, pareto_set: str | None = None, additional_sets: list[str] | None = None, show_dominated: bool = True, show_ideal_nadir: bool = True, show_reference_point: ndarray | None = None, width: int = 900, height: int = 700, title: str | None = None) -> Figure

Create interactive 2D Pareto front visualization.

Parameters:

Name Type Description Default
result OptimizationResult

OptimizationResult object

required
obj_x int | str

Index or name of objective for x-axis

0
obj_y int | str

Index or name of objective for y-axis

1
pareto_set str | None

Name of result set containing Pareto front

None
additional_sets list[str] | None

Additional sets to display with different markers

None
show_dominated bool

Show dominated solutions in background

True
show_ideal_nadir bool

Show ideal and nadir points

True
show_reference_point ndarray | None

Reference point for preference-based analysis

None
width int

Figure width

900
height int

Figure height

700
title str | None

Plot title

None

Returns:

Type Description
Figure

Plotly Figure object with interactive features

Source code in optiscope/plotting/pareto_viewer.py
def plot_pareto_front_2d(
    result: OptimizationResult,
    obj_x: int | str = 0,
    obj_y: int | str = 1,
    pareto_set: str | None = None,
    additional_sets: list[str] | None = None,
    show_dominated: bool = True,
    show_ideal_nadir: bool = True,
    show_reference_point: np.ndarray | None = None,
    width: int = 900,
    height: int = 700,
    title: str | None = None,
) -> go.Figure:
    """
    Create interactive 2D Pareto front visualization.

    Args:
        result: OptimizationResult object
        obj_x: Index or name of objective for x-axis
        obj_y: Index or name of objective for y-axis
        pareto_set: Name of result set containing Pareto front
        additional_sets: Additional sets to display with different markers
        show_dominated: Show dominated solutions in background
        show_ideal_nadir: Show ideal and nadir points
        show_reference_point: Reference point for preference-based analysis
        width: Figure width
        height: Figure height
        title: Plot title

    Returns:
        Plotly Figure object with interactive features
    """
    if result.objectives.empty:
        raise ValueError("No objectives found in result")

    # Get objective columns
    obj_cols = result.objectives.columns
    if isinstance(obj_x, int):
        obj_x_name = obj_cols[obj_x]
    else:
        obj_x_name = obj_x

    if isinstance(obj_y, int):
        obj_y_name = obj_cols[obj_y]
    else:
        obj_y_name = obj_y

    # Get objective metadata for labels
    meta_x = result.get_variable_metadata(obj_x_name)
    meta_y = result.get_variable_metadata(obj_y_name)

    x_label = _get_axis_label(obj_x_name, meta_x)
    y_label = _get_axis_label(obj_y_name, meta_y)

    # Extract data
    x_data = result.objectives[obj_x_name].values
    y_data = result.objectives[obj_y_name].values

    # Create figure
    fig = go.Figure()

    # Add dominated solutions if requested
    if show_dominated:
        # Determine which points to show as dominated
        if pareto_set and pareto_set in result.sets:
            pareto_indices = result.get_set(pareto_set).indices
            dominated_mask = np.ones(result.n_points, dtype=bool)
            dominated_mask[pareto_indices] = False
        else:
            dominated_mask = np.ones(result.n_points, dtype=bool)

        if dominated_mask.any():
            hover_text = _create_hover_text(result, np.where(dominated_mask)[0])

            fig.add_trace(
                go.Scatter(
                    x=x_data[dominated_mask],
                    y=y_data[dominated_mask],
                    mode="markers",
                    marker=dict(
                        size=6,
                        color="lightgray",
                        opacity=0.4,
                        line=dict(width=0.5, color="darkgray"),
                    ),
                    name="Dominated",
                    text=hover_text,
                    hovertemplate="<b>Dominated Solution</b><br>%{text}<extra></extra>",
                )
            )

    # Add additional sets
    if additional_sets:
        colors = ["#EF553B", "#00CC96", "#AB63FA", "#FFA15A"]
        symbols = ["diamond", "square", "star", "hexagon"]

        for idx, set_name in enumerate(additional_sets):
            if set_name in result.sets:
                set_indices = result.get_set(set_name).indices
                hover_text = _create_hover_text(result, set_indices)

                fig.add_trace(
                    go.Scatter(
                        x=x_data[set_indices],
                        y=y_data[set_indices],
                        mode="markers",
                        marker=dict(
                            size=10,
                            color=colors[idx % len(colors)],
                            line=dict(width=1.5, color="white"),
                            symbol=symbols[idx % len(symbols)],
                        ),
                        name=set_name,
                        text=hover_text,
                        hovertemplate=f"<b>{set_name}</b><br>%{{text}}<extra></extra>",
                    )
                )

    # Add Pareto front
    if pareto_set and pareto_set in result.sets:
        pareto_indices = np.array(result.get_set(pareto_set).indices)
        pareto_x = x_data[pareto_indices]
        pareto_y = y_data[pareto_indices]

        # Sort for line connection
        sort_idx = np.argsort(pareto_x)
        pareto_x_sorted = pareto_x[sort_idx]
        pareto_y_sorted = pareto_y[sort_idx]
        pareto_indices_sorted = pareto_indices[sort_idx]

        hover_text = _create_hover_text(result, pareto_indices_sorted)

        # Add line
        # fig.add_trace(
        #     go.Scatter(
        #         x=pareto_x_sorted,
        #         y=pareto_y_sorted,
        #         mode="lines",
        #         line=dict(color="rgba(99, 110, 250, 0.3)", width=2),
        #         showlegend=False,
        #         hoverinfo="skip",
        #     )
        # )

        # Add points
        fig.add_trace(
            go.Scatter(
                x=pareto_x_sorted,
                y=pareto_y_sorted,
                mode="markers",
                marker=dict(
                    # size=10, color="#636EFA", line=dict(width=0.5, color="white"), symbol="circle"
                    size=10,
                    color="#636EFA",
                    symbol="circle",
                ),
                name="Pareto Front",
                text=hover_text,
                hovertemplate="<b>Pareto Optimal</b><br>%{text}<extra></extra>",
            )
        )

    # Add ideal and nadir points
    if show_ideal_nadir:
        ideal_x = result.objectives[obj_x_name].min()
        ideal_y = result.objectives[obj_y_name].min()
        nadir_x = result.objectives[obj_x_name].max()
        nadir_y = result.objectives[obj_y_name].max()

        # Ideal point
        fig.add_trace(
            go.Scatter(
                x=[ideal_x],
                y=[ideal_y],
                mode="markers+text",
                marker=dict(
                    size=15, color="green", symbol="star", line=dict(width=2, color="white")
                ),
                text=["Ideal"],
                textposition="top center",
                name="Ideal Point",
                hovertemplate=f"<b>Ideal Point</b><br>{x_label}: %{{x:.4f}}<br>{y_label}: %{{y:.4f}}<extra></extra>",
            )
        )

        # Nadir point
        fig.add_trace(
            go.Scatter(
                x=[nadir_x],
                y=[nadir_y],
                mode="markers+text",
                marker=dict(size=15, color="red", symbol="x", line=dict(width=2, color="white")),
                text=["Nadir"],
                textposition="bottom center",
                name="Nadir Point",
                hovertemplate=f"<b>Nadir Point</b><br>{x_label}: %{{x:.4f}}<br>{y_label}: %{{y:.4f}}<extra></extra>",
            )
        )

    # Add reference point if provided
    if show_reference_point is not None:
        ref_x, ref_y = show_reference_point[0], show_reference_point[1]
        fig.add_trace(
            go.Scatter(
                x=[ref_x],
                y=[ref_y],
                mode="markers+text",
                marker=dict(
                    size=15, color="purple", symbol="diamond", line=dict(width=2, color="white")
                ),
                text=["Reference"],
                textposition="top right",
                name="Reference Point",
                hovertemplate=f"<b>Reference Point</b><br>{x_label}: %{{x:.4f}}<br>{y_label}: %{{y:.4f}}<extra></extra>",
            )
        )

    # Update layout
    title_text = title or f"Pareto Front: {result.problem_metadata.name}"

    fig.update_layout(
        title=title_text,
        xaxis_title=x_label,
        yaxis_title=y_label,
        width=width,
        height=height,
        hovermode="closest",
        legend=dict(
            yanchor="top", y=0.99, xanchor="right", x=0.99, bgcolor="rgba(255, 255, 255, 0.8)"
        ),
        template="plotly_white",
    )

    return fig

plot_pareto_front_3d

plot_pareto_front_3d(result: OptimizationResult, obj_x: int | str = 0, obj_y: int | str = 1, obj_z: int | str = 2, pareto_set: str | None = None, show_dominated: bool = True, show_surface: bool = False, width: int = 900, height: int = 800, title: str | None = None) -> Figure

Create interactive 3D Pareto front visualization.

Parameters:

Name Type Description Default
result OptimizationResult

OptimizationResult object

required
obj_x int | str

Index or name of objective for x-axis

0
obj_y int | str

Index or name of objective for y-axis

1
obj_z int | str

Index or name of objective for z-axis

2
pareto_set str | None

Name of result set containing Pareto front

None
show_dominated bool

Show dominated solutions

True
show_surface bool

Show interpolated surface through Pareto points

False
width int

Figure width

900
height int

Figure height

800
title str | None

Plot title

None

Returns:

Type Description
Figure

Plotly Figure object with 3D visualization

Source code in optiscope/plotting/pareto_viewer.py
def plot_pareto_front_3d(
    result: OptimizationResult,
    obj_x: int | str = 0,
    obj_y: int | str = 1,
    obj_z: int | str = 2,
    pareto_set: str | None = None,
    show_dominated: bool = True,
    show_surface: bool = False,
    width: int = 900,
    height: int = 800,
    title: str | None = None,
) -> go.Figure:
    """
    Create interactive 3D Pareto front visualization.

    Args:
        result: OptimizationResult object
        obj_x: Index or name of objective for x-axis
        obj_y: Index or name of objective for y-axis
        obj_z: Index or name of objective for z-axis
        pareto_set: Name of result set containing Pareto front
        show_dominated: Show dominated solutions
        show_surface: Show interpolated surface through Pareto points
        width: Figure width
        height: Figure height
        title: Plot title

    Returns:
        Plotly Figure object with 3D visualization
    """
    if result.objectives.empty:
        raise ValueError("No objectives found in result")

    if result.objectives.shape[1] < 3:
        raise ValueError("At least 3 objectives required for 3D plot")

    # Get objective columns
    obj_cols = result.objectives.columns
    if isinstance(obj_x, int):
        obj_x_name = obj_cols[obj_x]
    else:
        obj_x_name = obj_x

    if isinstance(obj_y, int):
        obj_y_name = obj_cols[obj_y]
    else:
        obj_y_name = obj_y

    if isinstance(obj_z, int):
        obj_z_name = obj_cols[obj_z]
    else:
        obj_z_name = obj_z

    # Get labels
    meta_x = result.get_variable_metadata(obj_x_name)
    meta_y = result.get_variable_metadata(obj_y_name)
    meta_z = result.get_variable_metadata(obj_z_name)

    x_label = _get_axis_label(obj_x_name, meta_x)
    y_label = _get_axis_label(obj_y_name, meta_y)
    z_label = _get_axis_label(obj_z_name, meta_z)

    # Extract data
    x_data = result.objectives[obj_x_name].to_numpy()
    y_data = result.objectives[obj_y_name].to_numpy()
    z_data = result.objectives[obj_z_name].to_numpy()

    # Create figure
    fig = go.Figure()

    # Add dominated solutions
    if show_dominated:
        if pareto_set and pareto_set in result.sets:
            pareto_indices = result.get_set(pareto_set).indices
            dominated_mask = np.ones(result.n_points, dtype=bool)
            dominated_mask[pareto_indices] = False
        else:
            dominated_mask = np.ones(result.n_points, dtype=bool)

        if dominated_mask.any():
            hover_text = _create_hover_text(result, np.where(dominated_mask)[0])

            fig.add_trace(
                go.Scatter3d(
                    x=x_data[dominated_mask],
                    y=y_data[dominated_mask],
                    z=z_data[dominated_mask],
                    mode="markers",
                    marker=dict(size=4, color="lightgray", opacity=0.3),
                    name="Dominated",
                    text=hover_text,
                    hovertemplate="<b>Dominated</b><br>%{text}<extra></extra>",
                )
            )

    # Add Pareto front
    if pareto_set and pareto_set in result.sets:
        pareto_indices = np.array(result.get_set(pareto_set).indices)
        hover_text = _create_hover_text(result, pareto_indices)

        fig.add_trace(
            go.Scatter3d(
                x=x_data[pareto_indices],
                y=y_data[pareto_indices],
                z=z_data[pareto_indices],
                mode="markers",
                marker=dict(
                    size=6,
                    color=z_data[pareto_indices],
                    colorscale="Viridis",
                    showscale=True,
                    colorbar=dict(title=z_label),
                    # line=dict(width=1, color="white"),
                ),
                name="Pareto Front",
                text=hover_text,
                hovertemplate="<b>Pareto Optimal</b><br>%{text}<extra></extra>",
            )
        )

        # Add surface if requested
        if show_surface:
            from scipy.interpolate import griddata

            # Create grid
            xi = np.linspace(x_data[pareto_indices].min(), x_data[pareto_indices].max(), 50)
            yi = np.linspace(y_data[pareto_indices].min(), y_data[pareto_indices].max(), 50)
            xi, yi = np.meshgrid(xi, yi)

            # Interpolate
            zi = griddata(
                (x_data[pareto_indices], y_data[pareto_indices]),
                z_data[pareto_indices],
                (xi, yi),
                method="cubic",
            )

            fig.add_trace(
                go.Surface(
                    x=xi,
                    y=yi,
                    z=zi,
                    opacity=0.3,
                    colorscale="Viridis",
                    showscale=False,
                    name="Pareto Surface",
                    hoverinfo="skip",
                )
            )

    # Update layout
    title_text = title or f"3D Pareto Front: {result.problem_metadata.name}"

    fig.update_layout(
        title=title_text,
        scene=dict(
            xaxis_title=x_label,
            yaxis_title=y_label,
            zaxis_title=z_label,
            camera=dict(eye=dict(x=1.5, y=1.5, z=1.3)),
        ),
        width=width,
        height=height,
        showlegend=True,
        template="plotly_white",
    )

    return fig