Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
88bcf72
Implement the Perspective class for specifying viewpoint
seisman Nov 11, 2025
02ae339
Improve docstrings
seisman Nov 12, 2025
3be6a08
Add a doctest
seisman Nov 12, 2025
648c823
Merge branch 'main' into AliasSystem/perspective
seisman Nov 15, 2025
202cf22
Improve the class
seisman Nov 15, 2025
dd31cec
Merge branch 'main' into AliasSystem/perspective
seisman Jun 25, 2026
9807b0b
Remove some parameters
seisman Jun 25, 2026
e9aa78f
Rename zlevel to level
seisman Jun 25, 2026
ad7e338
Skip doctest and use _validate
seisman Jun 25, 2026
af31b7a
Update type hints for the perspective parameter
seisman Jun 25, 2026
637806e
Revert unrelated changes in text
seisman Jun 25, 2026
adf1cc1
Fix styling
seisman Jun 25, 2026
edfd30d
Improve the docstrings for the perspective parameter
seisman Jun 25, 2026
9b01b2a
Merge branch 'main' into AliasSystem/perspective
seisman Jun 30, 2026
53354d6
Merge branch 'main' into AliasSystem/perspective
seisman Aug 13, 2026
28de054
Improve docstrings
seisman Aug 13, 2026
8068b10
Update docstrings
seisman Aug 13, 2026
4148a76
Merge branch 'main' into AliasSystem/perspective
seisman Aug 14, 2026
4363537
Merge branch 'main' into AliasSystem/perspective
seisman Aug 14, 2026
ac8d867
Merge branch 'main' into AliasSystem/perspective
seisman Aug 22, 2026
d9a9374
Merge branch 'main' into AliasSystem/perspective
seisman Aug 23, 2026
7f5ca3c
Fix Figure.basemap
seisman Aug 23, 2026
94e5564
Update pygmt/params/perspective.py
seisman Aug 23, 2026
cb5a1f5
Implement +w and +v modifiers
seisman Aug 24, 2026
aa92f00
Update the pygmtlogo source code
seisman Aug 24, 2026
3f2bb3d
Improve docstrings
seisman Aug 24, 2026
a6c8b82
Improve docstrings
seisman Aug 24, 2026
ec34ead
Use Frame in the doctest
seisman Aug 24, 2026
17cce8d
Shorten tests
seisman Aug 24, 2026
bb29b3a
Make prefix and size more readable
seisman Aug 24, 2026
11d5692
More compact docstring
seisman Aug 24, 2026
50e83f9
Merge branch 'main' into AliasSystem/perspective
seisman Aug 25, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -217,6 +217,7 @@ Class-style Parameters
Box
Frame
Pattern
Perspective
Position

Enums
Expand Down
23 changes: 6 additions & 17 deletions pygmt/helpers/decorators.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,24 +274,13 @@
Set pen attributes for lines or the outline of symbols.""",
"perspective": r"""
perspective
Select perspective view and set the azimuth and elevation of the viewpoint.

Accepts a single value or a sequence of two or three values: *azimuth*,
(*azimuth*, *elevation*), or (*azimuth*, *elevation*, *zlevel*).

- *azimuth*: Azimuth angle of the viewpoint in degrees [Default is 180,
i.e., looking from south to north].
- *elevation*: Elevation angle of the viewpoint above the horizon [Default
is 90, i.e., looking straight down at nadir].
- *zlevel*: Z-level at which 2-D elements (e.g., the plot frame) are drawn.
Only applied when used together with ``zsize`` or ``zscale``. [Default is
at the bottom of the z-axis].

Select perspective view and set the viewpoint.
Pass a :class:`pygmt.params.Perspective` object to control the viewpoint.
Alternatively, set ``perspective=True`` to reuse the perspective setting
from the previous plotting method, or pass a string following the full
GMT syntax for finer control (e.g., adding ``+w`` or ``+v`` modifiers to
select an axis location other than the plot origin). See
:gmt-docs:`gmt.html#perspective-full` for details.""",
from the previous plotting method. It also accepts following shortcuts: a
single value *azimuth*, a sequence of two values (*azimuth*, *elevation*),
or a sequence of three values (*azimuth*, *elevation*, *level*). Refer to
:class:`pygmt.params.Perspective` for meaning of the parameters.""",
"projection": r"""
projection
*projcode*\[*projparams*/]\ *width*\|\ *scale*.
Expand Down
1 change: 1 addition & 0 deletions pygmt/params/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@
from pygmt.params.box import Box
from pygmt.params.frame import Axis, Frame
from pygmt.params.pattern import Pattern
from pygmt.params.perspective import Perspective
from pygmt.params.position import Position
110 changes: 110 additions & 0 deletions pygmt/params/perspective.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
"""
The Perspective class for setting perspective view.
"""

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

from pygmt.alias import Alias
from pygmt.exceptions import GMTValueError
from pygmt.params.base import BaseParam

__doctest_skip__ = ["Perspective"]


@dataclasses.dataclass(repr=False)
class Perspective(BaseParam):
"""
Class for setting perspective view.

Examples
--------
>>> import pygmt
>>> from pygmt.params import Axis, Frame, Perspective
>>> fig = pygmt.Figure()
>>> fig.basemap(
... region=[0, 10, 0, 10, 0, 20],
... projection="X3c",
... zsize="3c",
... frame=Frame(axes="WSenZ", title="Perspective View", axis=Axis(grid=True)),
... perspective=Perspective(azimuth=135, elevation=40, level=10),
... )
>>> fig.show()
"""

#: Azimuth angle of the viewpoint in degrees. Default is 180.0, i.e., looking from
#: south to north.
azimuth: float | None = None

#: Elevation angle of the viewpoint in degrees above the horizon. Default is 90.0,
#: i.e., looking straight down at nadir.
elevation: float | None = None

#: The level at which all 2-D elements, (e.g., the plot frame), are drawn. Only
#: valid when used together with parameters ``zsize`` or ``zscale``. Default is at
#: the bottom of the selected axis.
level: float | None = None

#: Set which constant-coordinate plane is used as the plotting plane. Use ``"x"``,
#: ``"y"``, or ``"z"`` for the x-plane, y-plane, or horizontal z-plane,
#: respectively [Default is ``"z"``].
plane: Literal["x", "y", "z"] | None = None

#: Reference point for the perspective view. By default, the view rotates about the
#: plotting origin. Use ``refpoint`` and ``cstype`` to rotate about a different
#: point instead. The format of ``refpoint`` depends on the value of ``cstype``:
#:
#: - ``cstype="mapcoords"``: (*longitude*, *latitude*) or
#: (*longitude*, *latitude*, *z*)
#: - ``cstype="plotcoords"``: (*x*, *y*)
refpoint: Sequence[float | str] | None = None

#: Coordinate system type of ``refpoint``. Valid values are:
#:
#: - ``"mapcoords"``: Map/data coordinates
#: - ``"plotcoords"``: Plot coordinates
#:
#: Defaults to ``"mapcoords"``.
cstype: Literal["mapcoords", "plotcoords"] = "mapcoords"

def _validate(self):
"""
Post-initialization processing to validate parameters.
"""
# azimuth is required, so it must be set to the default if not specified.
if self.azimuth is None:
self.azimuth = 180.0 # Default azimuth is 180.0

# Set default elevation if level is set but elevation is not.
if self.level is not None and self.elevation is None:
self.elevation = 90.0 # Default elevation is 90.0

if self.plane is not None and self.plane not in {"x", "y", "z"}:
raise GMTValueError(
self.plane, description="plane", choices={"x", "y", "z"}
)

if self.cstype not in {"mapcoords", "plotcoords"}:
raise GMTValueError(
self.cstype, description="cstype", choices={"mapcoords", "plotcoords"}
)

@property
def _aliases(self):
"""
Aliases for the parameters.
"""
return [
Alias(self.plane, name="plane"),
Alias(self.azimuth, name="azimuth"),
Alias(self.elevation, name="elevation", prefix="/"),
Alias(self.level, name="level", prefix="/"),
Alias(
self.refpoint,
name="refpoint",
sep="/",
prefix={"mapcoords": "+w", "plotcoords": "+v"}[self.cstype],
size={"mapcoords": {2, 3}, "plotcoords": 2}[self.cstype],
),
]
4 changes: 2 additions & 2 deletions pygmt/src/basemap.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.helpers import build_arg_list, fmt_docstring, is_given
from pygmt.params import Axis, Box, Frame
from pygmt.params import Axis, Box, Frame, Perspective


@fmt_docstring
Expand All @@ -28,7 +28,7 @@ def basemap(
box: Box | str | bool = False,
panel: int | Sequence[int] | bool = False,
coltypes: str | None = None,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/coast.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
is_nonstr_iter,
use_alias,
)
from pygmt.params import Axis, Box, Frame
from pygmt.params import Axis, Box, Frame, Perspective

__doctest_skip__ = ["coast"]

Expand Down Expand Up @@ -90,7 +90,7 @@ def coast(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/colorbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pygmt.exceptions import GMTValueError
from pygmt.helpers import build_arg_list, fmt_docstring, is_given, use_alias
from pygmt.helpers.utils import is_nonstr_iter
from pygmt.params import Axis, Box, Frame, Position
from pygmt.params import Axis, Box, Frame, Perspective, Position
from pygmt.src._common import _parse_position

__doctest_skip__ = ["colorbar"]
Expand Down Expand Up @@ -278,7 +278,7 @@ def colorbar(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/contour.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
is_nonstr_iter,
use_alias,
)
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective


@fmt_docstring
Expand Down Expand Up @@ -45,7 +45,7 @@ def contour(
| bool = False,
panel: int | Sequence[int] | bool = False,
incols: int | str | Sequence[int | str] | None = None,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
coltypes: str | None = None,
**kwargs,
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/directional_rose.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.helpers import build_arg_list, fmt_docstring
from pygmt.params import Box, Position
from pygmt.params import Box, Perspective, Position
from pygmt.src._common import _parse_position

__doctest_skip__ = ["directional_rose"]
Expand All @@ -26,7 +26,7 @@ def directional_rose(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
):
"""
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/fill_between.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pygmt.clib import Session
from pygmt.exceptions import GMTValueError
from pygmt.helpers import build_arg_list, fmt_docstring
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective

__doctest_skip__ = ["fill_between"]

Expand All @@ -36,7 +36,7 @@ def fill_between(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
):
"""
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/grdcontour.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
kwargs_to_strings,
use_alias,
)
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective

__doctest_skip__ = ["grdcontour"]

Expand All @@ -42,7 +42,7 @@ def grdcontour(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
coltypes: str | None = None,
**kwargs,
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/grdimage.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.helpers import build_arg_list, fmt_docstring, use_alias
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective

__doctest_skip__ = ["grdimage"]

Expand All @@ -36,7 +36,7 @@ def grdimage(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
cores: int | bool = False,
coltypes: str | None = None,
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/grdview.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
is_given,
use_alias,
)
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective
from pygmt.src.grdinfo import grdinfo

__doctest_skip__ = ["grdview"]
Expand Down Expand Up @@ -141,7 +141,7 @@ def grdview(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
coltypes: str | None = None,
**kwargs,
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/histogram.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
kwargs_to_strings,
use_alias,
)
from pygmt.params import Axis, Frame
from pygmt.params import Axis, Frame, Perspective


@fmt_docstring
Expand Down Expand Up @@ -52,7 +52,7 @@ def histogram(
| bool = False,
panel: int | Sequence[int] | bool = False,
incols: int | str | Sequence[int | str] | None = None,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
3 changes: 2 additions & 1 deletion pygmt/src/hlines.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

import numpy as np
from pygmt.exceptions import GMTValueError
from pygmt.params import Perspective

__doctest_skip__ = ["hlines"]

Expand All @@ -18,7 +19,7 @@ def hlines(
pen: str | None = None,
label: str | None = None,
no_clip: bool = False,
perspective: str | bool | None = None,
perspective: Perspective | float | Sequence[float] | bool | None = None,
):
"""
Plot one or multiple horizontal line(s).
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/image.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from pygmt.alias import Alias, AliasSystem
from pygmt.clib import Session
from pygmt.helpers import build_arg_list, fmt_docstring, use_alias
from pygmt.params import Axis, Box, Frame, Position
from pygmt.params import Axis, Box, Frame, Perspective, Position
from pygmt.src._common import _parse_position


Expand All @@ -32,7 +32,7 @@ def image(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
4 changes: 2 additions & 2 deletions pygmt/src/legend.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
from pygmt.clib import Session
from pygmt.exceptions import GMTTypeError
from pygmt.helpers import build_arg_list, data_kind, fmt_docstring, is_nonstr_iter
from pygmt.params import Axis, Box, Frame, Position
from pygmt.params import Axis, Box, Frame, Perspective, Position
from pygmt.src._common import _parse_position


Expand All @@ -31,7 +31,7 @@ def legend(
verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"]
| bool = False,
panel: int | Sequence[int] | bool = False,
perspective: float | Sequence[float] | str | bool = False,
perspective: Perspective | float | Sequence[float] | bool = False,
transparency: float | None = None,
**kwargs,
):
Expand Down
Loading
Loading