"""
Main Visualizer class for QWARD metrics using strategy pattern.
This module provides the unified Visualizer class that acts as the main entry point
for visualizing metrics from Scanner or custom data sources using pluggable strategies.
"""
from typing import TYPE_CHECKING, Any, Dict, List, Optional, Type, Union
import pandas as pd
import matplotlib.pyplot as plt
from .base import VisualizationStrategy, PlotConfig, PlotMetadata, PlotResult
if TYPE_CHECKING:
from qward.scanner import Scanner
[docs]
class Visualizer:
"""
Unified visualizer for QWARD metrics using strategy pattern.
This class provides a single entry point for visualizing metrics from Scanner
or custom data sources. It automatically detects available metrics and provides
appropriate visualizations using pluggable strategies.
"""
def __init__(
self,
scanner: Optional["Scanner"] = None,
metrics_data: Optional[Dict[str, pd.DataFrame]] = None,
config: Optional[PlotConfig] = None,
output_dir: str = "qward/examples/img",
):
"""
Initialize the Visualizer.
Args:
scanner: Scanner instance containing metrics strategies
metrics_data: Custom metrics data as DataFrames
config: Global plot configuration for all visualizations
output_dir: Directory to save plots
Raises:
ValueError: If neither scanner nor metrics_data is provided
"""
if scanner is None and metrics_data is None:
raise ValueError("Either scanner or metrics_data must be provided")
self.scanner = scanner
self.config = config or PlotConfig()
self.output_dir = output_dir
self.registered_strategies: Dict[str, Type[VisualizationStrategy]] = {}
# Get metrics data
if metrics_data is not None:
self.metrics_data = metrics_data
else:
self.metrics_data = self.scanner.calculate_metrics()
# Auto-register default strategies
self._auto_register_default_strategies()
def _auto_register_default_strategies(self) -> None:
"""Auto-register default visualization strategies based on available metrics."""
# Register CircuitPerformance strategy if data is available
if any(key.startswith("CircuitPerformance") for key in self.metrics_data.keys()):
from .circuit_performance_visualizer import CircuitPerformanceVisualizer
self.register_strategy("CircuitPerformance", CircuitPerformanceVisualizer)
# Register Qiskit strategy if data is available
if "QiskitMetrics" in self.metrics_data:
from .qiskit_metrics_visualizer import QiskitVisualizer
self.register_strategy("QiskitMetrics", QiskitVisualizer)
# Register Complexity strategy if data is available
if "ComplexityMetrics" in self.metrics_data:
from .complexity_metrics_visualizer import ComplexityVisualizer
self.register_strategy("ComplexityMetrics", ComplexityVisualizer)
# Register ElementMetrics strategy if data is available
if "ElementMetrics" in self.metrics_data:
from .element_metrics_visualizer import ElementMetricsVisualizer
self.register_strategy("ElementMetrics", ElementMetricsVisualizer)
# Register StructuralMetrics strategy if data is available
if "StructuralMetrics" in self.metrics_data:
from .structural_metrics_visualizer import StructuralMetricsVisualizer
self.register_strategy("StructuralMetrics", StructuralMetricsVisualizer)
# Register BehavioralMetrics strategy if data is available
if "BehavioralMetrics" in self.metrics_data:
from .behavioral_metrics_visualizer import BehavioralMetricsVisualizer
self.register_strategy("BehavioralMetrics", BehavioralMetricsVisualizer)
# Register QuantumSpecificMetrics strategy if data is available
if "QuantumSpecificMetrics" in self.metrics_data:
from .quantum_specific_metrics_visualizer import QuantumSpecificMetricsVisualizer
self.register_strategy("QuantumSpecificMetrics", QuantumSpecificMetricsVisualizer)
[docs]
def register_strategy(
self, metric_name: str, strategy_class: Type[VisualizationStrategy]
) -> None:
"""
Register a visualization strategy for a specific metric.
Args:
metric_name: Name of the metric (e.g., "QiskitMetrics", "ComplexityMetrics")
strategy_class: Strategy class that inherits from VisualizationStrategy
"""
self.registered_strategies[metric_name] = strategy_class
[docs]
def get_available_metrics(self) -> List[str]:
"""
Get list of available metrics that can be visualized.
Returns:
List[str]: List of metric names available for visualization
"""
available = []
for metric_name in self.registered_strategies:
if self._has_metric_data(metric_name):
available.append(metric_name)
return available
[docs]
def get_available_plots(self, metric_name: str = None) -> Dict[str, List[str]]:
"""
Get available plots for each metric or specific metric.
Args:
metric_name: Optional specific metric name to get plots for
Returns:
Dictionary mapping metric names to lists of available plot names
"""
if metric_name:
if metric_name not in self.registered_strategies:
raise ValueError(f"Metric '{metric_name}' not registered")
if not self._has_metric_data(metric_name):
raise ValueError(f"No data available for metric '{metric_name}'")
strategy_class = self.registered_strategies[metric_name]
return {metric_name: strategy_class.get_available_plots()}
# Return all available plots
result = {}
for name, strategy_class in self.registered_strategies.items():
if self._has_metric_data(name):
result[name] = strategy_class.get_available_plots()
return result
[docs]
def generate_plot(
self, *, metric_name: str, plot_name: str, save: bool = False, show: bool = False, **kwargs
) -> plt.Figure:
"""
Generate a single specific plot.
Args:
metric_name: Name of the metric (e.g., "CircuitPerformance", "QiskitMetrics")
plot_name: Name of the plot to generate
save: Whether to save the plot to file
show: Whether to display the plot
**kwargs: Additional arguments passed to the plot method
Returns:
matplotlib Figure object
Example:
fig = visualizer.generate_plot(
metric_name="CircuitPerformance",
plot_name="success_error_comparison",
save=True,
show=True
)
"""
if not self._has_metric_data(metric_name):
raise ValueError(f"No data available for metric '{metric_name}'")
strategy_class = self.registered_strategies[metric_name]
metric_data = self._get_metric_data(metric_name)
strategy = strategy_class(
metrics_dict=metric_data, output_dir=self.output_dir, config=self.config
)
return strategy.generate_plot(plot_name, save=save, show=show, **kwargs)
[docs]
def generate_plots(
self, *, selections: Dict[str, List[str]], save: bool = False, show: bool = False, **kwargs
) -> Dict[str, PlotResult]:
"""
Generate selected plots for each metric.
Args:
selections: Dictionary mapping metric names to lists of plot names.
Use None as plot list to generate all plots for a metric.
save: Whether to save the plots to files
show: Whether to display the plots
**kwargs: Additional arguments passed to plot methods
Returns:
Dictionary mapping metric names to PlotResult dictionaries
Example:
results = visualizer.generate_plots(
selections={
"CircuitPerformance": ["success_error_comparison", "shot_distribution"],
"QiskitMetrics": ["basic_metrics_bar"]
},
save=True,
show=False
)
"""
results = {}
for metric_name, plot_names in selections.items():
if not self._has_metric_data(metric_name):
print(f"Warning: No data available for metric '{metric_name}'")
continue
try:
strategy_class = self.registered_strategies[metric_name]
metric_data = self._get_metric_data(metric_name)
strategy = strategy_class(
metrics_dict=metric_data, output_dir=self.output_dir, config=self.config
)
results[metric_name] = strategy.generate_plots(
plot_names, save=save, show=show, **kwargs
)
except Exception as e:
print(f"Warning: Failed to generate plots for {metric_name}: {e}")
continue
return results
def _has_metric_data(self, metric_name: str) -> bool:
"""Check if data is available for a specific metric."""
if metric_name == "CircuitPerformance":
return any(key.startswith("CircuitPerformance") for key in self.metrics_data.keys())
return metric_name in self.metrics_data
def _get_metric_data(self, metric_name: str) -> Dict[str, pd.DataFrame]:
"""Get data for a specific metric."""
if metric_name == "CircuitPerformance":
# Return all CircuitPerformance-related data
return {
key: df
for key, df in self.metrics_data.items()
if key.startswith("CircuitPerformance")
}
else:
# Return single metric data
return {metric_name: self.metrics_data[metric_name]}
[docs]
def create_dashboard(
self, *, save: bool = False, show: bool = False, **kwargs
) -> Dict[str, plt.Figure]:
"""
Create a comprehensive dashboard with all available metrics.
Args:
save: Whether to save the dashboard to files
show: Whether to display the dashboard
**kwargs: Additional arguments passed to strategies
Returns:
Dict[str, plt.Figure]: Dictionary mapping metric names to dashboard figures
Example:
dashboards = visualizer.create_dashboard(save=True, show=True)
"""
dashboards: Dict[str, plt.Figure] = {}
available_metrics = self.get_available_metrics()
if not available_metrics:
print("No metrics available for visualization")
return dashboards
print(f"Creating dashboard for metrics: {', '.join(available_metrics)}")
for metric_name in available_metrics:
try:
# Get the strategy class and create instance
strategy_class = self.registered_strategies[metric_name]
metric_data = self._get_metric_data(metric_name)
# Create strategy instance
strategy = strategy_class(
metrics_dict=metric_data, output_dir=self.output_dir, config=self.config
)
# Create dashboard for this metric
dashboard_fig = strategy.create_dashboard(save=save, show=show, **kwargs)
dashboards[metric_name] = dashboard_fig
except Exception as e:
print(f"Warning: Failed to create dashboard for {metric_name}: {e}")
continue
return dashboards
[docs]
def get_metric_summary(self) -> Dict[str, Dict[str, Any]]:
"""
Get a summary of available metrics and their key statistics.
Returns:
Dict[str, Dict[str, Any]]: Summary information for each metric
"""
summary = {}
for metric_name, df in self.metrics_data.items():
summary[metric_name] = {
"shape": df.shape,
"columns": list(df.columns),
"has_data": not df.empty,
"sample_values": df.iloc[0].to_dict() if not df.empty else {},
}
return summary
[docs]
def print_available_metrics(self) -> None:
"""Print information about available metrics and visualizations."""
print("=== QWARD Visualizer Summary ===")
print(f"Output directory: {self.output_dir}")
print(f"Total metrics datasets: {len(self.metrics_data)}")
available_metrics = self.get_available_metrics()
print(f"Available visualizations: {len(available_metrics)}")
for metric_name in available_metrics:
metric_data = self._get_metric_data(metric_name)
total_rows = sum(df.shape[0] for df in metric_data.values())
available_plots = self.get_available_plots(metric_name)[metric_name]
print(
f" - {metric_name}: {len(metric_data)} dataset(s), {total_rows} total rows, {len(available_plots)} plots"
)
if not available_metrics:
print(" No visualizations available. Register strategies or check data.")
[docs]
def list_registered_strategies(self) -> Dict[str, str]:
"""
List all registered visualization strategies.
Returns:
Dict[str, str]: Dictionary mapping metric names to strategy class names
"""
return {
metric_name: strategy_class.__name__
for metric_name, strategy_class in self.registered_strategies.items()
}