Skip to content

Plotting API

Pluggable plotting backends for result visualization.

PlotBackend Protocol

idfkit.simulation.plotting.PlotBackend

Bases: Protocol

Protocol for plotting backends used by the simulation module.

Implementations must provide methods for common chart types. Each method returns a figure object native to the backend (e.g. matplotlib Figure or plotly Figure).

Source code in src/idfkit/simulation/plotting/__init__.py
@runtime_checkable
class PlotBackend(Protocol):
    """Protocol for plotting backends used by the simulation module.

    Implementations must provide methods for common chart types. Each method
    returns a figure object native to the backend (e.g. matplotlib ``Figure``
    or plotly ``Figure``).
    """

    def line(
        self,
        x: Sequence[Any],
        y: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
        label: str | None = None,
    ) -> Any:
        """Create a single line plot.

        Args:
            x: X-axis values (e.g. timestamps).
            y: Y-axis values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.
            label: Optional line label for legend.

        Returns:
            A figure object native to the backend.
        """
        ...

    def multi_line(
        self,
        x: Sequence[Any],
        y_series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a multi-line plot with legend.

        Args:
            x: Shared X-axis values.
            y_series: Mapping of label to Y values for each line.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A figure object native to the backend.
        """
        ...

    def heatmap(
        self,
        data: Sequence[Sequence[float]],
        *,
        x_labels: Sequence[str] | None = None,
        y_labels: Sequence[str] | None = None,
        title: str | None = None,
        colorbar_label: str | None = None,
    ) -> Any:
        """Create a 2D heatmap.

        Args:
            data: 2D array of values (rows, columns).
            x_labels: Optional labels for columns.
            y_labels: Optional labels for rows.
            title: Optional plot title.
            colorbar_label: Optional label for the colorbar.

        Returns:
            A figure object native to the backend.
        """
        ...

    def bar(
        self,
        categories: Sequence[str],
        values: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a bar chart.

        Args:
            categories: Category labels for each bar.
            values: Values for each bar.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A figure object native to the backend.
        """
        ...

    def stacked_bar(
        self,
        categories: Sequence[str],
        series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a stacked bar chart.

        Args:
            categories: Category labels for each bar group.
            series: Mapping of series label to values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A figure object native to the backend.
        """
        ...

bar(categories, values, *, title=None, xlabel=None, ylabel=None)

Create a bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar.

required
values Sequence[float]

Values for each bar.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A figure object native to the backend.

Source code in src/idfkit/simulation/plotting/__init__.py
def bar(
    self,
    categories: Sequence[str],
    values: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a bar chart.

    Args:
        categories: Category labels for each bar.
        values: Values for each bar.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A figure object native to the backend.
    """
    ...

heatmap(data, *, x_labels=None, y_labels=None, title=None, colorbar_label=None)

Create a 2D heatmap.

Parameters:

Name Type Description Default
data Sequence[Sequence[float]]

2D array of values (rows, columns).

required
x_labels Sequence[str] | None

Optional labels for columns.

None
y_labels Sequence[str] | None

Optional labels for rows.

None
title str | None

Optional plot title.

None
colorbar_label str | None

Optional label for the colorbar.

None

Returns:

Type Description
Any

A figure object native to the backend.

Source code in src/idfkit/simulation/plotting/__init__.py
def heatmap(
    self,
    data: Sequence[Sequence[float]],
    *,
    x_labels: Sequence[str] | None = None,
    y_labels: Sequence[str] | None = None,
    title: str | None = None,
    colorbar_label: str | None = None,
) -> Any:
    """Create a 2D heatmap.

    Args:
        data: 2D array of values (rows, columns).
        x_labels: Optional labels for columns.
        y_labels: Optional labels for rows.
        title: Optional plot title.
        colorbar_label: Optional label for the colorbar.

    Returns:
        A figure object native to the backend.
    """
    ...

line(x, y, *, title=None, xlabel=None, ylabel=None, label=None)

Create a single line plot.

Parameters:

Name Type Description Default
x Sequence[Any]

X-axis values (e.g. timestamps).

required
y Sequence[float]

Y-axis values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None
label str | None

Optional line label for legend.

None

Returns:

Type Description
Any

A figure object native to the backend.

Source code in src/idfkit/simulation/plotting/__init__.py
def line(
    self,
    x: Sequence[Any],
    y: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    label: str | None = None,
) -> Any:
    """Create a single line plot.

    Args:
        x: X-axis values (e.g. timestamps).
        y: Y-axis values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.
        label: Optional line label for legend.

    Returns:
        A figure object native to the backend.
    """
    ...

multi_line(x, y_series, *, title=None, xlabel=None, ylabel=None)

Create a multi-line plot with legend.

Parameters:

Name Type Description Default
x Sequence[Any]

Shared X-axis values.

required
y_series dict[str, Sequence[float]]

Mapping of label to Y values for each line.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A figure object native to the backend.

Source code in src/idfkit/simulation/plotting/__init__.py
def multi_line(
    self,
    x: Sequence[Any],
    y_series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a multi-line plot with legend.

    Args:
        x: Shared X-axis values.
        y_series: Mapping of label to Y values for each line.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A figure object native to the backend.
    """
    ...

stacked_bar(categories, series, *, title=None, xlabel=None, ylabel=None)

Create a stacked bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar group.

required
series dict[str, Sequence[float]]

Mapping of series label to values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A figure object native to the backend.

Source code in src/idfkit/simulation/plotting/__init__.py
def stacked_bar(
    self,
    categories: Sequence[str],
    series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a stacked bar chart.

    Args:
        categories: Category labels for each bar group.
        series: Mapping of series label to values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A figure object native to the backend.
    """
    ...

get_default_backend

idfkit.simulation.plotting.get_default_backend()

Auto-detect and return an available plotting backend.

Tries matplotlib first, then plotly. Raises ImportError if neither is available.

Returns:

Type Description
PlotBackend

A PlotBackend instance.

Raises:

Type Description
ImportError

If neither matplotlib nor plotly is installed.

Source code in src/idfkit/simulation/plotting/__init__.py
def get_default_backend() -> PlotBackend:
    """Auto-detect and return an available plotting backend.

    Tries matplotlib first, then plotly. Raises ImportError if neither
    is available.

    Returns:
        A PlotBackend instance.

    Raises:
        ImportError: If neither matplotlib nor plotly is installed.
    """
    # Try matplotlib first (more common)
    try:
        from .matplotlib import MatplotlibBackend

        return MatplotlibBackend()
    except ImportError:
        pass

    # Fall back to plotly
    try:
        from .plotly import PlotlyBackend

        return PlotlyBackend()
    except ImportError:
        pass

    msg = (
        "No plotting backend available. Install matplotlib or plotly: "
        "pip install idfkit[plot] or pip install idfkit[plotly]"
    )
    raise ImportError(msg)

Built-in Visualizations

plot_temperature_profile

idfkit.simulation.plotting.visualizations.plot_temperature_profile(sql, zones, *, backend=None, title='Zone Air Temperatures', frequency=None)

Create a multi-line plot of zone air temperatures.

Queries Zone Mean Air Temperature for each specified zone and plots them on a shared time axis.

Parameters:

Name Type Description Default
sql SQLResult

An open SQLResult database.

required
zones Sequence[str]

Zone names to plot.

required
backend PlotBackend | None

Plotting backend to use. Auto-detects if not provided.

None
title str

Plot title.

'Zone Air Temperatures'
frequency str | None

Optional frequency filter (e.g. "Hourly").

None

Returns:

Type Description
Any

A figure object from the backend.

Source code in src/idfkit/simulation/plotting/visualizations.py
def plot_temperature_profile(
    sql: SQLResult,
    zones: Sequence[str],
    *,
    backend: PlotBackend | None = None,
    title: str = "Zone Air Temperatures",
    frequency: str | None = None,
) -> Any:
    """Create a multi-line plot of zone air temperatures.

    Queries ``Zone Mean Air Temperature`` for each specified zone and
    plots them on a shared time axis.

    Args:
        sql: An open SQLResult database.
        zones: Zone names to plot.
        backend: Plotting backend to use. Auto-detects if not provided.
        title: Plot title.
        frequency: Optional frequency filter (e.g. ``"Hourly"``).

    Returns:
        A figure object from the backend.
    """
    if backend is None:
        from . import get_default_backend

        backend = get_default_backend()

    timestamps: Sequence[Any] = ()
    y_series: dict[str, Sequence[float]] = {}

    for zone in zones:
        ts = sql.get_timeseries("Zone Mean Air Temperature", zone, frequency=frequency)
        if not timestamps:
            timestamps = ts.timestamps
        y_series[zone] = ts.values

    return backend.multi_line(
        timestamps,
        y_series,
        title=title,
        xlabel="Time",
        ylabel="Temperature (C)",
    )

plot_energy_balance

idfkit.simulation.plotting.visualizations.plot_energy_balance(sql, *, backend=None, title='End-Use Energy by Category')

Create a bar chart of end-use energy consumption.

Extracts data from the AnnualBuildingUtilityPerformanceSummary report and plots energy consumption by end-use category.

Parameters:

Name Type Description Default
sql SQLResult

An open SQLResult database.

required
backend PlotBackend | None

Plotting backend to use. Auto-detects if not provided.

None
title str

Plot title.

'End-Use Energy by Category'

Returns:

Type Description
Any

A figure object from the backend.

Source code in src/idfkit/simulation/plotting/visualizations.py
def plot_energy_balance(
    sql: SQLResult,
    *,
    backend: PlotBackend | None = None,
    title: str = "End-Use Energy by Category",
) -> Any:
    """Create a bar chart of end-use energy consumption.

    Extracts data from the ``AnnualBuildingUtilityPerformanceSummary`` report
    and plots energy consumption by end-use category.

    Args:
        sql: An open SQLResult database.
        backend: Plotting backend to use. Auto-detects if not provided.
        title: Plot title.

    Returns:
        A figure object from the backend.
    """
    if backend is None:
        from . import get_default_backend

        backend = get_default_backend()

    # Query end-use breakdown from the tabular report
    rows = sql.get_tabular_data(
        report_name="AnnualBuildingUtilityPerformanceSummary",
        table_name="End Uses",
    )

    # Aggregate by row_name (end-use category), summing across fuels
    # We want column_name "Total Energy" or sum numeric values from fuel columns
    energy_by_use: dict[str, float] = {}
    for row in rows:
        # Skip header rows and non-numeric values
        if row.row_name in ("", "Total End Uses"):
            continue
        try:
            value = float(row.value)
        except ValueError:
            continue
        # Skip zero or empty values
        if value == 0:
            continue
        # Accumulate by end-use category
        if row.row_name not in energy_by_use:
            energy_by_use[row.row_name] = 0.0
        energy_by_use[row.row_name] += value

    # Sort by value descending
    sorted_items = sorted(energy_by_use.items(), key=lambda x: x[1], reverse=True)
    categories = [item[0] for item in sorted_items]
    values = [item[1] for item in sorted_items]

    return backend.bar(
        categories,
        values,
        title=title,
        xlabel="End Use",
        ylabel="Energy (GJ)",
    )

plot_comfort_hours

idfkit.simulation.plotting.visualizations.plot_comfort_hours(sql, zones, *, comfort_min=20.0, comfort_max=26.0, backend=None, title='Comfort Hours by Zone and Month')

Create a heatmap of comfort hours by zone and month.

For each zone, calculates the percentage of hours within the comfort range for each month and displays as a heatmap.

Parameters:

Name Type Description Default
sql SQLResult

An open SQLResult database.

required
zones Sequence[str]

Zone names to analyze.

required
comfort_min float

Minimum comfort temperature (default 20C).

20.0
comfort_max float

Maximum comfort temperature (default 26C).

26.0
backend PlotBackend | None

Plotting backend to use. Auto-detects if not provided.

None
title str

Plot title.

'Comfort Hours by Zone and Month'

Returns:

Type Description
Any

A figure object from the backend.

Source code in src/idfkit/simulation/plotting/visualizations.py
def plot_comfort_hours(
    sql: SQLResult,
    zones: Sequence[str],
    *,
    comfort_min: float = 20.0,
    comfort_max: float = 26.0,
    backend: PlotBackend | None = None,
    title: str = "Comfort Hours by Zone and Month",
) -> Any:
    """Create a heatmap of comfort hours by zone and month.

    For each zone, calculates the percentage of hours within the comfort
    range for each month and displays as a heatmap.

    Args:
        sql: An open SQLResult database.
        zones: Zone names to analyze.
        comfort_min: Minimum comfort temperature (default 20C).
        comfort_max: Maximum comfort temperature (default 26C).
        backend: Plotting backend to use. Auto-detects if not provided.
        title: Plot title.

    Returns:
        A figure object from the backend.
    """
    if backend is None:
        from . import get_default_backend

        backend = get_default_backend()

    months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]

    # data[zone_idx][month_idx] = comfort percentage
    data: list[list[float]] = []

    for zone in zones:
        ts = sql.get_timeseries("Zone Mean Air Temperature", zone)

        # Count hours per month
        month_hours: dict[int, int] = dict.fromkeys(range(1, 13), 0)
        month_comfort: dict[int, int] = dict.fromkeys(range(1, 13), 0)

        for timestamp, value in zip(ts.timestamps, ts.values, strict=True):
            month = timestamp.month
            month_hours[month] += 1
            if comfort_min <= value <= comfort_max:
                month_comfort[month] += 1

        # Calculate percentage for each month
        row: list[float] = []
        for m in range(1, 13):
            pct = 100.0 * month_comfort[m] / month_hours[m] if month_hours[m] > 0 else 0.0
            row.append(pct)
        data.append(row)

    return backend.heatmap(
        data,
        x_labels=months,
        y_labels=list(zones),
        title=title,
        colorbar_label="Comfort Hours (%)",
    )

Backend Implementations

MatplotlibBackend

idfkit.simulation.plotting.matplotlib.MatplotlibBackend

Plotting backend using matplotlib.

Lazily imports matplotlib when methods are called. Each method returns a matplotlib Figure object.

Raises:

Type Description
ImportError

If matplotlib is not installed.

Source code in src/idfkit/simulation/plotting/matplotlib.py
class MatplotlibBackend:
    """Plotting backend using matplotlib.

    Lazily imports matplotlib when methods are called. Each method returns
    a matplotlib ``Figure`` object.

    Raises:
        ImportError: If matplotlib is not installed.
    """

    def __init__(self) -> None:
        """Initialize the backend, verifying matplotlib is available."""
        try:
            import matplotlib.pyplot  # type: ignore[import-not-found]  # noqa: F401
        except ImportError:
            msg = "matplotlib is required for MatplotlibBackend. Install it with: pip install idfkit[plot]"
            raise ImportError(msg) from None

    def _get_pyplot(self) -> Any:
        """Get matplotlib.pyplot module."""
        import matplotlib.pyplot as plt  # type: ignore[import-not-found]

        return plt

    def line(
        self,
        x: Sequence[Any],
        y: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
        label: str | None = None,
    ) -> Any:
        """Create a single line plot.

        Args:
            x: X-axis values.
            y: Y-axis values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.
            label: Optional line label for legend.

        Returns:
            A matplotlib Figure.
        """
        plt = self._get_pyplot()
        fig, ax = plt.subplots()
        ax.plot(x, y, label=label)
        if title:
            ax.set_title(title)
        if xlabel:
            ax.set_xlabel(xlabel)
        if ylabel:
            ax.set_ylabel(ylabel)
        if label:
            ax.legend()
        fig.tight_layout()
        return fig

    def multi_line(
        self,
        x: Sequence[Any],
        y_series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a multi-line plot with legend.

        Args:
            x: Shared X-axis values.
            y_series: Mapping of label to Y values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A matplotlib Figure.
        """
        plt = self._get_pyplot()
        fig, ax = plt.subplots()
        for line_label, y in y_series.items():
            ax.plot(x, y, label=line_label)
        if title:
            ax.set_title(title)
        if xlabel:
            ax.set_xlabel(xlabel)
        if ylabel:
            ax.set_ylabel(ylabel)
        if y_series:
            ax.legend()
        fig.tight_layout()
        return fig

    def heatmap(
        self,
        data: Sequence[Sequence[float]],
        *,
        x_labels: Sequence[str] | None = None,
        y_labels: Sequence[str] | None = None,
        title: str | None = None,
        colorbar_label: str | None = None,
    ) -> Any:
        """Create a 2D heatmap.

        Args:
            data: 2D array of values (rows, columns).
            x_labels: Optional labels for columns.
            y_labels: Optional labels for rows.
            title: Optional plot title.
            colorbar_label: Optional label for the colorbar.

        Returns:
            A matplotlib Figure.
        """
        plt = self._get_pyplot()
        fig, ax = plt.subplots()
        im = ax.imshow(data, aspect="auto")
        cbar = fig.colorbar(im, ax=ax)
        if colorbar_label:
            cbar.set_label(colorbar_label)
        if x_labels is not None:
            ax.set_xticks(range(len(x_labels)))
            ax.set_xticklabels(x_labels, rotation=45, ha="right")
        if y_labels is not None:
            ax.set_yticks(range(len(y_labels)))
            ax.set_yticklabels(y_labels)
        if title:
            ax.set_title(title)
        fig.tight_layout()
        return fig

    def bar(
        self,
        categories: Sequence[str],
        values: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a bar chart.

        Args:
            categories: Category labels for each bar.
            values: Values for each bar.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A matplotlib Figure.
        """
        plt = self._get_pyplot()
        fig, ax = plt.subplots()
        ax.bar(categories, values)
        if title:
            ax.set_title(title)
        if xlabel:
            ax.set_xlabel(xlabel)
        if ylabel:
            ax.set_ylabel(ylabel)
        ax.tick_params(axis="x", rotation=45)
        fig.tight_layout()
        return fig

    def stacked_bar(
        self,
        categories: Sequence[str],
        series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a stacked bar chart.

        Args:
            categories: Category labels for each bar group.
            series: Mapping of series label to values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A matplotlib Figure.
        """
        plt = self._get_pyplot()
        fig, ax = plt.subplots()
        # Build x positions and bottom accumulator manually to avoid numpy
        x_positions = list(range(len(categories)))
        bottom: list[float] = [0.0] * len(categories)

        for bar_label, bar_values in series.items():
            ax.bar(x_positions, bar_values, bottom=bottom, label=bar_label)
            for i, v in enumerate(bar_values):
                bottom[i] += v

        ax.set_xticks(x_positions)
        ax.set_xticklabels(categories, rotation=45, ha="right")
        if title:
            ax.set_title(title)
        if xlabel:
            ax.set_xlabel(xlabel)
        if ylabel:
            ax.set_ylabel(ylabel)
        if series:
            ax.legend()
        fig.tight_layout()
        return fig

__init__()

Initialize the backend, verifying matplotlib is available.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def __init__(self) -> None:
    """Initialize the backend, verifying matplotlib is available."""
    try:
        import matplotlib.pyplot  # type: ignore[import-not-found]  # noqa: F401
    except ImportError:
        msg = "matplotlib is required for MatplotlibBackend. Install it with: pip install idfkit[plot]"
        raise ImportError(msg) from None

bar(categories, values, *, title=None, xlabel=None, ylabel=None)

Create a bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar.

required
values Sequence[float]

Values for each bar.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A matplotlib Figure.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def bar(
    self,
    categories: Sequence[str],
    values: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a bar chart.

    Args:
        categories: Category labels for each bar.
        values: Values for each bar.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A matplotlib Figure.
    """
    plt = self._get_pyplot()
    fig, ax = plt.subplots()
    ax.bar(categories, values)
    if title:
        ax.set_title(title)
    if xlabel:
        ax.set_xlabel(xlabel)
    if ylabel:
        ax.set_ylabel(ylabel)
    ax.tick_params(axis="x", rotation=45)
    fig.tight_layout()
    return fig

heatmap(data, *, x_labels=None, y_labels=None, title=None, colorbar_label=None)

Create a 2D heatmap.

Parameters:

Name Type Description Default
data Sequence[Sequence[float]]

2D array of values (rows, columns).

required
x_labels Sequence[str] | None

Optional labels for columns.

None
y_labels Sequence[str] | None

Optional labels for rows.

None
title str | None

Optional plot title.

None
colorbar_label str | None

Optional label for the colorbar.

None

Returns:

Type Description
Any

A matplotlib Figure.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def heatmap(
    self,
    data: Sequence[Sequence[float]],
    *,
    x_labels: Sequence[str] | None = None,
    y_labels: Sequence[str] | None = None,
    title: str | None = None,
    colorbar_label: str | None = None,
) -> Any:
    """Create a 2D heatmap.

    Args:
        data: 2D array of values (rows, columns).
        x_labels: Optional labels for columns.
        y_labels: Optional labels for rows.
        title: Optional plot title.
        colorbar_label: Optional label for the colorbar.

    Returns:
        A matplotlib Figure.
    """
    plt = self._get_pyplot()
    fig, ax = plt.subplots()
    im = ax.imshow(data, aspect="auto")
    cbar = fig.colorbar(im, ax=ax)
    if colorbar_label:
        cbar.set_label(colorbar_label)
    if x_labels is not None:
        ax.set_xticks(range(len(x_labels)))
        ax.set_xticklabels(x_labels, rotation=45, ha="right")
    if y_labels is not None:
        ax.set_yticks(range(len(y_labels)))
        ax.set_yticklabels(y_labels)
    if title:
        ax.set_title(title)
    fig.tight_layout()
    return fig

line(x, y, *, title=None, xlabel=None, ylabel=None, label=None)

Create a single line plot.

Parameters:

Name Type Description Default
x Sequence[Any]

X-axis values.

required
y Sequence[float]

Y-axis values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None
label str | None

Optional line label for legend.

None

Returns:

Type Description
Any

A matplotlib Figure.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def line(
    self,
    x: Sequence[Any],
    y: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    label: str | None = None,
) -> Any:
    """Create a single line plot.

    Args:
        x: X-axis values.
        y: Y-axis values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.
        label: Optional line label for legend.

    Returns:
        A matplotlib Figure.
    """
    plt = self._get_pyplot()
    fig, ax = plt.subplots()
    ax.plot(x, y, label=label)
    if title:
        ax.set_title(title)
    if xlabel:
        ax.set_xlabel(xlabel)
    if ylabel:
        ax.set_ylabel(ylabel)
    if label:
        ax.legend()
    fig.tight_layout()
    return fig

multi_line(x, y_series, *, title=None, xlabel=None, ylabel=None)

Create a multi-line plot with legend.

Parameters:

Name Type Description Default
x Sequence[Any]

Shared X-axis values.

required
y_series dict[str, Sequence[float]]

Mapping of label to Y values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A matplotlib Figure.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def multi_line(
    self,
    x: Sequence[Any],
    y_series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a multi-line plot with legend.

    Args:
        x: Shared X-axis values.
        y_series: Mapping of label to Y values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A matplotlib Figure.
    """
    plt = self._get_pyplot()
    fig, ax = plt.subplots()
    for line_label, y in y_series.items():
        ax.plot(x, y, label=line_label)
    if title:
        ax.set_title(title)
    if xlabel:
        ax.set_xlabel(xlabel)
    if ylabel:
        ax.set_ylabel(ylabel)
    if y_series:
        ax.legend()
    fig.tight_layout()
    return fig

stacked_bar(categories, series, *, title=None, xlabel=None, ylabel=None)

Create a stacked bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar group.

required
series dict[str, Sequence[float]]

Mapping of series label to values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A matplotlib Figure.

Source code in src/idfkit/simulation/plotting/matplotlib.py
def stacked_bar(
    self,
    categories: Sequence[str],
    series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a stacked bar chart.

    Args:
        categories: Category labels for each bar group.
        series: Mapping of series label to values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A matplotlib Figure.
    """
    plt = self._get_pyplot()
    fig, ax = plt.subplots()
    # Build x positions and bottom accumulator manually to avoid numpy
    x_positions = list(range(len(categories)))
    bottom: list[float] = [0.0] * len(categories)

    for bar_label, bar_values in series.items():
        ax.bar(x_positions, bar_values, bottom=bottom, label=bar_label)
        for i, v in enumerate(bar_values):
            bottom[i] += v

    ax.set_xticks(x_positions)
    ax.set_xticklabels(categories, rotation=45, ha="right")
    if title:
        ax.set_title(title)
    if xlabel:
        ax.set_xlabel(xlabel)
    if ylabel:
        ax.set_ylabel(ylabel)
    if series:
        ax.legend()
    fig.tight_layout()
    return fig

PlotlyBackend

idfkit.simulation.plotting.plotly.PlotlyBackend

Plotting backend using plotly.

Lazily imports plotly when methods are called. Each method returns a plotly Figure object.

Raises:

Type Description
ImportError

If plotly is not installed.

Source code in src/idfkit/simulation/plotting/plotly.py
class PlotlyBackend:
    """Plotting backend using plotly.

    Lazily imports plotly when methods are called. Each method returns
    a plotly ``Figure`` object.

    Raises:
        ImportError: If plotly is not installed.
    """

    def __init__(self) -> None:
        """Initialize the backend, verifying plotly is available."""
        try:
            import plotly.graph_objects  # type: ignore[import-not-found]  # noqa: F401
        except ImportError:
            msg = "plotly is required for PlotlyBackend. Install it with: pip install idfkit[plotly]"
            raise ImportError(msg) from None

    def _get_go(self) -> Any:
        """Get plotly.graph_objects module."""
        import plotly.graph_objects as go  # type: ignore[import-not-found]

        return go

    def line(
        self,
        x: Sequence[Any],
        y: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
        label: str | None = None,
    ) -> Any:
        """Create a single line plot.

        Args:
            x: X-axis values.
            y: Y-axis values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.
            label: Optional line label for legend.

        Returns:
            A plotly Figure.
        """
        go = self._get_go()
        fig = go.Figure()
        fig.add_trace(go.Scatter(x=list(x), y=list(y), mode="lines", name=label or ""))
        fig.update_layout(
            title=title,
            xaxis_title=xlabel,
            yaxis_title=ylabel,
            showlegend=label is not None,
        )
        return fig

    def multi_line(
        self,
        x: Sequence[Any],
        y_series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a multi-line plot with legend.

        Args:
            x: Shared X-axis values.
            y_series: Mapping of label to Y values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A plotly Figure.
        """
        go = self._get_go()
        fig = go.Figure()
        for name, y in y_series.items():
            fig.add_trace(go.Scatter(x=list(x), y=list(y), mode="lines", name=name))
        fig.update_layout(
            title=title,
            xaxis_title=xlabel,
            yaxis_title=ylabel,
            showlegend=bool(y_series),
        )
        return fig

    def heatmap(
        self,
        data: Sequence[Sequence[float]],
        *,
        x_labels: Sequence[str] | None = None,
        y_labels: Sequence[str] | None = None,
        title: str | None = None,
        colorbar_label: str | None = None,
    ) -> Any:
        """Create a 2D heatmap.

        Args:
            data: 2D array of values (rows, columns).
            x_labels: Optional labels for columns.
            y_labels: Optional labels for rows.
            title: Optional plot title.
            colorbar_label: Optional label for the colorbar.

        Returns:
            A plotly Figure.
        """
        go = self._get_go()
        heatmap_args: dict[str, Any] = {
            "z": [list(row) for row in data],
        }
        if x_labels is not None:
            heatmap_args["x"] = list(x_labels)
        if y_labels is not None:
            heatmap_args["y"] = list(y_labels)
        if colorbar_label:
            heatmap_args["colorbar"] = {"title": colorbar_label}

        fig = go.Figure(data=go.Heatmap(**heatmap_args))
        fig.update_layout(title=title)
        return fig

    def bar(
        self,
        categories: Sequence[str],
        values: Sequence[float],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a bar chart.

        Args:
            categories: Category labels for each bar.
            values: Values for each bar.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A plotly Figure.
        """
        go = self._get_go()
        fig = go.Figure(data=go.Bar(x=list(categories), y=list(values)))
        fig.update_layout(
            title=title,
            xaxis_title=xlabel,
            yaxis_title=ylabel,
        )
        return fig

    def stacked_bar(
        self,
        categories: Sequence[str],
        series: dict[str, Sequence[float]],
        *,
        title: str | None = None,
        xlabel: str | None = None,
        ylabel: str | None = None,
    ) -> Any:
        """Create a stacked bar chart.

        Args:
            categories: Category labels for each bar group.
            series: Mapping of series label to values.
            title: Optional plot title.
            xlabel: Optional X-axis label.
            ylabel: Optional Y-axis label.

        Returns:
            A plotly Figure.
        """
        go = self._get_go()
        fig = go.Figure()
        for name, bar_values in series.items():
            fig.add_trace(go.Bar(x=list(categories), y=list(bar_values), name=name))
        fig.update_layout(
            title=title,
            xaxis_title=xlabel,
            yaxis_title=ylabel,
            barmode="stack",
        )
        return fig

__init__()

Initialize the backend, verifying plotly is available.

Source code in src/idfkit/simulation/plotting/plotly.py
def __init__(self) -> None:
    """Initialize the backend, verifying plotly is available."""
    try:
        import plotly.graph_objects  # type: ignore[import-not-found]  # noqa: F401
    except ImportError:
        msg = "plotly is required for PlotlyBackend. Install it with: pip install idfkit[plotly]"
        raise ImportError(msg) from None

bar(categories, values, *, title=None, xlabel=None, ylabel=None)

Create a bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar.

required
values Sequence[float]

Values for each bar.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A plotly Figure.

Source code in src/idfkit/simulation/plotting/plotly.py
def bar(
    self,
    categories: Sequence[str],
    values: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a bar chart.

    Args:
        categories: Category labels for each bar.
        values: Values for each bar.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A plotly Figure.
    """
    go = self._get_go()
    fig = go.Figure(data=go.Bar(x=list(categories), y=list(values)))
    fig.update_layout(
        title=title,
        xaxis_title=xlabel,
        yaxis_title=ylabel,
    )
    return fig

heatmap(data, *, x_labels=None, y_labels=None, title=None, colorbar_label=None)

Create a 2D heatmap.

Parameters:

Name Type Description Default
data Sequence[Sequence[float]]

2D array of values (rows, columns).

required
x_labels Sequence[str] | None

Optional labels for columns.

None
y_labels Sequence[str] | None

Optional labels for rows.

None
title str | None

Optional plot title.

None
colorbar_label str | None

Optional label for the colorbar.

None

Returns:

Type Description
Any

A plotly Figure.

Source code in src/idfkit/simulation/plotting/plotly.py
def heatmap(
    self,
    data: Sequence[Sequence[float]],
    *,
    x_labels: Sequence[str] | None = None,
    y_labels: Sequence[str] | None = None,
    title: str | None = None,
    colorbar_label: str | None = None,
) -> Any:
    """Create a 2D heatmap.

    Args:
        data: 2D array of values (rows, columns).
        x_labels: Optional labels for columns.
        y_labels: Optional labels for rows.
        title: Optional plot title.
        colorbar_label: Optional label for the colorbar.

    Returns:
        A plotly Figure.
    """
    go = self._get_go()
    heatmap_args: dict[str, Any] = {
        "z": [list(row) for row in data],
    }
    if x_labels is not None:
        heatmap_args["x"] = list(x_labels)
    if y_labels is not None:
        heatmap_args["y"] = list(y_labels)
    if colorbar_label:
        heatmap_args["colorbar"] = {"title": colorbar_label}

    fig = go.Figure(data=go.Heatmap(**heatmap_args))
    fig.update_layout(title=title)
    return fig

line(x, y, *, title=None, xlabel=None, ylabel=None, label=None)

Create a single line plot.

Parameters:

Name Type Description Default
x Sequence[Any]

X-axis values.

required
y Sequence[float]

Y-axis values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None
label str | None

Optional line label for legend.

None

Returns:

Type Description
Any

A plotly Figure.

Source code in src/idfkit/simulation/plotting/plotly.py
def line(
    self,
    x: Sequence[Any],
    y: Sequence[float],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
    label: str | None = None,
) -> Any:
    """Create a single line plot.

    Args:
        x: X-axis values.
        y: Y-axis values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.
        label: Optional line label for legend.

    Returns:
        A plotly Figure.
    """
    go = self._get_go()
    fig = go.Figure()
    fig.add_trace(go.Scatter(x=list(x), y=list(y), mode="lines", name=label or ""))
    fig.update_layout(
        title=title,
        xaxis_title=xlabel,
        yaxis_title=ylabel,
        showlegend=label is not None,
    )
    return fig

multi_line(x, y_series, *, title=None, xlabel=None, ylabel=None)

Create a multi-line plot with legend.

Parameters:

Name Type Description Default
x Sequence[Any]

Shared X-axis values.

required
y_series dict[str, Sequence[float]]

Mapping of label to Y values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A plotly Figure.

Source code in src/idfkit/simulation/plotting/plotly.py
def multi_line(
    self,
    x: Sequence[Any],
    y_series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a multi-line plot with legend.

    Args:
        x: Shared X-axis values.
        y_series: Mapping of label to Y values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A plotly Figure.
    """
    go = self._get_go()
    fig = go.Figure()
    for name, y in y_series.items():
        fig.add_trace(go.Scatter(x=list(x), y=list(y), mode="lines", name=name))
    fig.update_layout(
        title=title,
        xaxis_title=xlabel,
        yaxis_title=ylabel,
        showlegend=bool(y_series),
    )
    return fig

stacked_bar(categories, series, *, title=None, xlabel=None, ylabel=None)

Create a stacked bar chart.

Parameters:

Name Type Description Default
categories Sequence[str]

Category labels for each bar group.

required
series dict[str, Sequence[float]]

Mapping of series label to values.

required
title str | None

Optional plot title.

None
xlabel str | None

Optional X-axis label.

None
ylabel str | None

Optional Y-axis label.

None

Returns:

Type Description
Any

A plotly Figure.

Source code in src/idfkit/simulation/plotting/plotly.py
def stacked_bar(
    self,
    categories: Sequence[str],
    series: dict[str, Sequence[float]],
    *,
    title: str | None = None,
    xlabel: str | None = None,
    ylabel: str | None = None,
) -> Any:
    """Create a stacked bar chart.

    Args:
        categories: Category labels for each bar group.
        series: Mapping of series label to values.
        title: Optional plot title.
        xlabel: Optional X-axis label.
        ylabel: Optional Y-axis label.

    Returns:
        A plotly Figure.
    """
    go = self._get_go()
    fig = go.Figure()
    for name, bar_values in series.items():
        fig.add_trace(go.Bar(x=list(categories), y=list(bar_values), name=name))
    fig.update_layout(
        title=title,
        xaxis_title=xlabel,
        yaxis_title=ylabel,
        barmode="stack",
    )
    return fig