Source code for pygmt.src.inset

"""
inset - Manage figure inset setup and completion.
"""

import contextlib
from collections.abc import Sequence
from typing import Literal

from pygmt._typing import AnchorCode
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import (
    build_arg_list,
    deprecate_parameter,
    fmt_docstring,
    kwargs_to_strings,
    use_alias,
)
from pygmt.params import Box, Position
from pygmt.src._common import _parse_position

__doctest_skip__ = ["inset"]


@fmt_docstring
@deprecate_parameter("margin", "clearance", "v0.18.0", remove_version="v0.20.0")
@use_alias(C="clearance")
@kwargs_to_strings(C="sequence")
@contextlib.contextmanager
def inset(
    self,
    position: Position | Sequence[float | str] | AnchorCode | None = None,
    width: float | str | None = None,
    height: float | str | None = None,
    box: Box | bool = False,
    no_clip: bool = False,
    projection: str | None = None,
    region: Sequence[float | str] | str | None = None,
    verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
    | bool = False,
    **kwargs,
):
    r"""
    Manage figure inset setup and completion.


    This method carves out a sub-region of the current plot canvas and restrict further
    plotting to that section of the canvas. Plotting methods that are called within the
    context manager are added to the inset figure.

    Full GMT docs at :gmt-docs:`inset.html`.

    $aliases
       - D = position, **+w**: width/height
       - F = box
       - J = projection
       - N = no_clip
       - R = region
       - V = verbose

    Parameters
    ----------
    position
        Position of the inset on the plot. It can be specified in multiple ways:

        - A :class:`pygmt.params.Position` object to fully control the reference point,
          anchor point, and offset.
        - A sequence of two values representing the x- and y-coordinates in plot
          coordinates, e.g., ``(1, 2)`` or ``("1c", "2c")``.
        - A :doc:`2-character justification code </techref/justification_codes>` for a
          position inside the plot, e.g., ``"TL"`` for Top Left corner inside the plot.

        If not specified, defaults to the Bottom Left corner of the plot.
    width
    height
        Width and height of the inset. Width must be specified unless ``projection`` and
        ``region`` are given, and height is set to be equal to width if not specified.
    box
        Draw a background box behind the inset. If set to ``True``, a simple rectangular
        box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance,
        pass a :class:`pygmt.params.Box` object to control style, fill, pen, and other
        box properties.
    clearance : float, str, or list
        This is clearance that is added around the inside of the inset.
        Plotting will take place within the inner region only. The margins
        can be a single value, a pair of values separated (for setting
        separate horizontal and vertical margins), or the full set of four
        margins (for setting separate left, right, bottom, and top
        margins). When passing multiple values, it can be either a list or
        a string with the values separated by forward
        slashes [Default is no margins].
    no_clip
        Do **not** clip features extruding outside the inset frame boundaries [Default
        is ``False``].

    $projection
    $region
    $verbose

    Examples
    --------
    >>> import pygmt
    >>> from pygmt.params import Box, Position
    >>>
    >>> fig = pygmt.Figure()
    >>> fig.coast(region="MG+r2", water="lightblue", shorelines="thin")
    >>> # Use a "with" statement to initialize the inset context manager.
    >>> # Setting the position to Top Left and a width of 3.5 centimeters
    >>> with fig.inset(
    ...     position=Position("TL", offset=0.2),
    ...     width="3.5c",
    ...     clearance=0,
    ...     box=Box(pen="green"),
    ... ):  # Map elements under the "with" statement are plotted in the inset
    ...     fig.coast(
    ...         region="g",
    ...         projection="G47/-20/?",
    ...         land="gray",
    ...         water="white",
    ...         dcw="MG+gred",
    ...     )
    >>> # Map elements outside the "with" statement are plotted in the main figure
    >>> fig.logo(position=Position("BR", offset=0.2), width="3c")
    >>> fig.show()
    """
    self._activate_figure()

    position = _parse_position(
        position,
        kwdict={"width": width, "height": height},
        default=Position((0, 0), cstype="plotcoords"),  # Default to (0,0) in plotcoords
    )

    # width is mandatory unless both projection and region are given.
    if (
        not isinstance(position, str)
        and width is None
        and (projection is None or region is None)
    ):
        msg = "Parameter 'width' must be specified."
        raise GMTInvalidInput(msg)

    aliasdict = AliasSystem(
        D=[
            Alias(position, name="position"),
            Alias(width, name="width", prefix="+w"),  # +wwidth/height
            Alias(height, name="height", prefix="/"),
        ],
        F=Alias(box, name="box"),
        N=Alias(no_clip, name="no_clip"),
    ).add_common(
        J=projection,
        R=region,
        V=verbose,
    )
    aliasdict.merge(kwargs)

    with Session() as lib:
        try:
            lib.call_module(module="inset", args=["begin", *build_arg_list(aliasdict)])
            yield
        finally:
            lib.call_module(
                module="inset", args=["end", *build_arg_list({"V": aliasdict.get("V")})]
            )