Skip to content

topoviz.py

TopoViz

TopoViz(dpi: int = 100, font_size: int = 11)

              flowchart TD
              erosionfront.visualization.topoviz.TopoViz[TopoViz]
              erosionfront.visualization.base.GraphingBase[GraphingBase]

                              erosionfront.visualization.base.GraphingBase --> erosionfront.visualization.topoviz.TopoViz
                


              click erosionfront.visualization.topoviz.TopoViz href "" "erosionfront.visualization.topoviz.TopoViz"
              click erosionfront.visualization.base.GraphingBase href "" "erosionfront.visualization.base.GraphingBase"
            

Visualization of 3D topo data.

Methods:

  • create_figure

    Initialize a :mod:Pyplot <matplotlib.pyplot> figure.

  • get_aspect

    Get aspect ratio of graph.

  • get_fonts

    Fetch the names of all the font families available on the system.

  • naturalize

    Adjust graph aspect ratio into 'natural' ratio.

  • plot_3d

    Display (possibly) interactive 3D view using PyVista

  • plot_profiles

    X

  • plot_slopes

    X

  • stretch

    Stretch graph axes by respective factors.

Source code in erosionfront/visualization/base.py
def __init__(self, dpi: int = 100, font_size: int = 11) -> None:
    """Initialize."""
    self.dpi = dpi
    self.font_size = font_size
    self.fdict: dict[Any, Any] = {}
    prop_cycle = plt.rcParams["axes.prop_cycle"]
    self.colors = prop_cycle.by_key()["color"]  # type: ignore
    self.n_colors = len(self.colors)  # type: ignore
    self.color_cycle = cycle(self.colors)  # type: ignore
    self.markers = ("o", "s", "v", "p", "*", "D", "X", "^", "h", "P")
    self.n_markers = len(self.markers)
    self.marker_cycle = cycle(self.markers)
    self.linestyle_list = ("solid", "dashdot", "dashed", (0, (3, 1, 1, 1)))

    color_ = lambda i_: self.colors[i_ % self.n_colors]  # type: ignore
    marker_ = lambda i_: self.markers[i_ % self.n_markers]  # type: ignore
    self.color = color_  # type: ignore
    self.marker = marker_  # type: ignore
    #if "Arial" in self.get_fonts() else "Helvetica"
    self.font_family = "Arial" 
    try:
        mpl.rc("font", size=self.font_size, family=self.font_family)
    except:
        mpl.rc("font", size=self.font_size, family="")

create_figure

create_figure(
    fig_name: str, fig_size: tuple[float, float] | None = None, dpi: int | None = None
) -> Figure

Initialize a :mod:Pyplot <matplotlib.pyplot> figure.

Set its size and dpi, set the font size, choose the Arial font family if possible, and append it to the figures dictionary.

Parameters

fig_name: name of figure; used as key in figures dictionary fig_size: optional width and height of figure in inches dpi: rasterization resolution

Returns

:obj:Pyplot figure <matplotlib.figure.Figure>: reference to :mod:MatPlotLib/Pyplot <matplotlib.pyplot> figure

Source code in erosionfront/visualization/base.py
def create_figure(
    self,
    fig_name: str,
    fig_size: tuple[float, float] | None = None,
    dpi: int | None = None,
) -> Figure:
    """
    Initialize a :mod:`Pyplot <matplotlib.pyplot>` figure.

    Set its size and dpi, set the font size,
    choose the Arial font family if possible,
    and append it to the figures dictionary.

    Parameters
    ----------
    fig_name:
        name of figure; used as key in figures dictionary
    fig_size:
        optional width and height of figure in inches
    dpi:
        rasterization resolution

    Returns
    -------
    :obj:`Pyplot figure <matplotlib.figure.Figure>`:
        reference to :mod:`MatPlotLib/Pyplot <matplotlib.pyplot>`
        figure
    """
    fig_size_: tuple[float, float] = (
        (8, 8) if fig_size is None else fig_size
    )
    dpi_: float = self.dpi if dpi is None else dpi
    logging.info(
        "gmplib.plot.GraphingBase:\n   "
        + f"Creating plot: {fig_name} size={fig_size_} @ {dpi_} dpi"
    )
    fig = plt.figure()
    self.fdict.update({fig_name: fig})
    fig.set_size_inches(fig_size_)
    fig.set_dpi(dpi_)
    return fig

get_aspect

get_aspect(axes: Axes) -> float

Get aspect ratio of graph.

Parameters

axes: the axes object of the figure

Returns

float: aspect ratio

Source code in erosionfront/visualization/base.py
def get_aspect(self, axes: plt.Axes) -> float:
    """
    Get aspect ratio of graph.

    Parameters
    ----------
    axes:
        the `axes` object of the figure

    Returns
    -------
    float:
        aspect ratio
    """
    # Total figure size
    figWH: tuple[float, float] \
        = axes.get_figure().get_size_inches() #type: ignore
    figW, figH = figWH
    # Axis size on figure
    bounds: tuple[float, float, float, float] = axes.get_position().bounds
    _, _, w, h = bounds
    # Ratio of display units
    disp_ratio: float = (figH * h) / (figW * w)
    # Ratio of data units
    # Negative over negative because of the order of subtraction
    # logging.info(axes.get_ylim(),axes.get_xlim())
    data_ratio: float = op.sub(*axes.get_ylim()) / op.sub(*axes.get_xlim())
    aspect_ratio: float = disp_ratio / data_ratio
    return aspect_ratio

get_fonts

get_fonts() -> list[str]

Fetch the names of all the font families available on the system.

Source code in erosionfront/visualization/base.py
def get_fonts(self) -> list[str]:
    """Fetch the names of all the font families available on the system."""
    fpaths = matplotlib.font_manager.findSystemFonts()
    fonts: list[str] = []
    for fpath in fpaths:
        try:
            font = matplotlib.font_manager.get_font(fpath).family_name
            fonts.append(font)
        except RuntimeError as re:
            logging.debug(f"{re}: failed to get font name for {fpath}")
            pass
    return fonts

naturalize

naturalize(fig: Figure) -> None

Adjust graph aspect ratio into 'natural' ratio.

Source code in erosionfront/visualization/base.py
def naturalize(self, fig: Figure) -> None:
    """Adjust graph aspect ratio into 'natural' ratio."""
    axes: plt.Axes = fig.gca()
    # x_lim, y_lim = axes.get_xlim(), axes.get_ylim()
    # axes.set_aspect((y_lim[1]-y_lim[0])/(x_lim[1]-x_lim[0]))
    axes.set_aspect(1 / self.get_aspect(axes))

plot_3d

plot_3d(mesh: MultiBlock, geometry: Geometry, profiles: Profiles) -> Plotter

Display (possibly) interactive 3D view using PyVista

Parameters

mesh: MultiBlock X geometry: Geometry X profiles: Profiles X

Returns

Plotter: An instance of a PyVista 3D viewer.

Source code in erosionfront/visualization/topoviz.py
def plot_3d(
    self,
    mesh: MultiBlock, 
    geometry: Geometry,
    profiles: Profiles,
) -> Plotter:
    """
    Display (possibly) interactive 3D view using PyVista

    Parameters
    ----------
    mesh: MultiBlock
        X
    geometry: Geometry
        X
    profiles: Profiles
        X

    Returns
    -------
    Plotter:
        An instance of a PyVista 3D viewer.
    """
    pl = Plotter(window_size=(1024, 768,),) #, notebook=True
    grid: tuple[dict[float, MultiBlock]] = geometry.grid
    grid_colors: tuple[str,str] = ("k","k",) # ("coral","limegreen",)
    for lines_, color_ in zip(grid, grid_colors,):
        for step_, line_ in lines_.items():
            pl.add_mesh(
                line_, 
                show_edges=False, 
                color=color_, 
                line_width=2 if step_==0 else 1, 
                render_lines_as_tubes=False,
            )
    # Profile slices
    profiles: dict[Any: ProfileData] = profiles.data
    colors = list(mcolors.TABLEAU_COLORS.values())
    for color_, profile_ in zip(colors,profiles.values()):
        slice_ = profile_.slice
        pl.add_mesh(
            slice_, show_edges=False, color=color_, 
            line_width=5, render_lines_as_tubes=True,
        )
    # 3D DEM
    pl.add_mesh(mesh, show_edges=False, color="lightgray",)
    pl.show_axes()
    return pl

plot_profiles

plot_profiles(
    fig_name: str,
    profiles: Profiles,
    choices: tuple[str, ...] | None,
    marker_size: float | None,
    line_width: float | None,
    xlim: tuple[float, float] | None = None,
    ylim: tuple[float, float] | None = None,
    scale: float = 1,
    do_equal: bool = True,
    do_normals: bool = False,
    vector_sf: float = 1,
    θ_clip: tuple[float, float] = (0, 90),
    title_font_size: int = 12,
    fig_size: tuple[float, float] = (7, 5),
) -> None

X

Parameters

fig_name: str Name used as key in figure dictionary. profiles: dict[Any: Profile] X choices: tuple[str,...] | None X marker_size: float | None X line_width: float | None X xlim: tuple[float,float] | None = None X ylim: tuple[float,float] | None = None X scale: float=1 X do_equal: bool=True X

use_normals: bool=False

X

do_normals: bool=False X vector_sf: float=1 X

do_flip_orientation: bool=False

X

θ_clip: tuple[float,float]=(0,90,) X title_font_size: int=12 Size of title text. fig_size: tuple[float, float]=(7, 5,) Figure size (in inches!).

Attributes

Uses create_figure (adds to fig dict).

Source code in erosionfront/visualization/topoviz.py
def plot_profiles(
    self,
    fig_name: str,
    profiles: Profiles,
    choices: tuple[str,...] | None,
    marker_size: float | None,
    line_width: float | None,
    xlim: tuple[float,float] | None = None,
    ylim: tuple[float,float] | None = None,
    scale: float=1,
    do_equal: bool=True,
    # use_normals: bool=False,
    do_normals: bool=False,
    vector_sf: float=1,
    # do_flip_orientation: bool=False,
    θ_clip: tuple[float,float]=(0,90,),
    title_font_size: int=12,
    fig_size: tuple[float, float]=(7, 5,),
) -> None:
    """
    X

    Parameters
    ----------
    fig_name: str
        Name used as key in figure dictionary.
    profiles: dict[Any: Profile]
        X
    choices: tuple[str,...] | None
        X
    marker_size: float | None
        X
    line_width: float | None
        X
    xlim: tuple[float,float] | None = None
        X
    ylim: tuple[float,float] | None = None
        X
    scale: float=1
        X
    do_equal: bool=True
        X
    # use_normals: bool=False
        X
    do_normals: bool=False
        X
    vector_sf: float=1
        X
    # do_flip_orientation: bool=False
        X
    θ_clip: tuple[float,float]=(0,90,)
        X
    title_font_size: int=12
        Size of title text.
    fig_size: tuple[float, float]=(7, 5,)
        Figure size (in inches!).

    Attributes
    ----------
    Uses create_figure (adds to fig dict).
    """
    self.create_figure(fig_name, fig_size=np.array(fig_size)*scale,)
    # title: str = ""
    # plt.title(
    #     title,
    #     fontdict={"fontsize": title_font_size,},
    # )
    profiles: dict[Any: ProfileData] = profiles.data
    colors = list(mcolors.TABLEAU_COLORS.values())
    for j_, profile_ in enumerate(profiles.values()):
        if choices is not None and profile_.name not in choices:
            continue
        chords_ = profile_.chords
        ψ_deg = profile_.orientation
        mid_points_ = profile_.mid_points
        parallels_ = profile_.parallels
        normals_ = profile_.normals
        tilts_ = profile_.tilts
        tilts_r_ = profile_.tilts_r
        for (i_, 
             (chord_, mid_point_, parallel_, normal_, tilt_, tilt_r_,),) \
            in enumerate(zip(
                chords_, mid_points_,parallels_,normals_,tilts_,tilts_r_
            ),):
            # Don't bother plotting chords whose endpts lie beyond the 
            # limits
            if xlim is not None:
                if chord_[0][0]<xlim[0] or chord_[0][1]>xlim[1]:
                    continue
            plt.plot(
                *chord_, 
                ("." if marker_size is not None else "") 
                    + ("-" if line_width is not None else ""),
                color=colors[j_] if not do_normals else "Gray",
                ms=marker_size,
                lw=line_width,
                label=rf"$\psi=${ψ_deg}$\degree$" if i_==0 else None,
            )
            if do_normals:
                normal_ #if use_normals else parallel_
                is_in_bounds = lambda tilt: \
                    ((tilt>=θ_clip[0] and tilt<=θ_clip[1]) 
                        or (-tilt>=θ_clip[0] and -tilt<=θ_clip[1])) 
                if tilt_ is not None and is_in_bounds(tilt_):
                    plt.plot(
                        (mid_point_[0], 
                         mid_point_[0]+normal_[0]*vector_sf,), 
                        (mid_point_[1], 
                         mid_point_[1]+normal_[1]*vector_sf,), 
                        "-",
                        color="k" if tilt_>0 else "b",
                        lw=0.5,
                    )
                elif tilt_r_ is not None and is_in_bounds(tilt_r_):
                    plt.plot(
                        (mid_point_[0], 
                         mid_point_[0]+normal_[0]*vector_sf,), 
                        (mid_point_[1], 
                         mid_point_[1]+normal_[1]*vector_sf,), 
                        "-",
                        color="g" if tilt_r_>0 else "r",
                        lw=0.5,
                    )
    plt.xlabel("Distance along profile  [m]")
    plt.ylabel(r"Elevation $z$  [m]")
    plt.grid(ls=":",)
    plt.legend()
    axes = plt.gca()
    if do_equal:
        axes.set_aspect("equal")
    if xlim is not None:
        plt.xlim(*xlim)
    if ylim is not None:
        plt.ylim(*ylim)

plot_slopes

plot_slopes(
    fig_name: str,
    profiles: Profiles,
    choices: tuple[str, ...] | None,
    w_filter: int = 15,
    scale: float = 1,
    line_width: float = 1,
    do_points: bool = False,
    title_font_size: int = 12,
    fig_size: tuple[float, float] = (6, 3.5),
) -> None

X

Parameters

fig_name: str Name used as key in figure dictionary. profiles: Profiles X choices: tuple[str,...] | None X w_filter: int=15 X scale: float=1 X line_width: float=1 X do_points: bool=False X title_font_size: int=12 Size of title text. fig_size: tuple[float, float]=(7, 5,) Figure size (in inches!).

Attributes

Uses create_figure (adds to fig dict).

Source code in erosionfront/visualization/topoviz.py
def plot_slopes(
    self,
    fig_name: str,
    profiles: Profiles,
    choices: tuple[str,...] | None,
    w_filter: int=15,
    scale: float=1,
    line_width: float=1,
    do_points: bool=False,
    title_font_size: int=12,
    fig_size: tuple[float,float]=(6,3.5,),
) -> None:
    """
    X

    Parameters
    ----------
    fig_name: str
        Name used as key in figure dictionary.
    profiles: Profiles
        X
    choices: tuple[str,...] | None
        X
    w_filter: int=15
        X
    scale: float=1
        X
    line_width: float=1
        X
    do_points: bool=False
        X
    title_font_size: int=12
        Size of title text.
    fig_size: tuple[float, float]=(7, 5,)
        Figure size (in inches!).

    Attributes
    ----------
    Uses create_figure (adds to fig dict).
    """
    self.create_figure(fig_name, fig_size=np.array(fig_size)*scale,)
    # title: str = ""
    # plt.title(
    #     title,
    #     fontdict={"fontsize": title_font_size,},
    # )
    profiles: dict[Any: ProfileData] = profiles.data
    colors = list(mcolors.TABLEAU_COLORS.values())
    for j_, profile_ in enumerate(profiles.values()):
        if choices is not None and profile_.name not in choices:
            continue
        xg_ = np.array([
            ( 
                (chord_[0][1]+chord_[0][0])/2, 
                np.rad2deg(np.atan2( 
                    (chord_[1][1]-chord_[1][0]), 
                    (chord_[0][1]-chord_[0][0]) 
                ))
            )
            for chord_ in profile_.chords   
        ])
        xgs_ = np.array(sorted(xg_, key=lambda x: x[0]))
        xgf_ = medfilt(xgs_[:,1], w_filter,)
        ψ_deg = profile_.orientation
        plt.plot(
            xgs_[:,0], xgf_, 
            "." if do_points else "-", 
            ms=3, 
            lw=line_width,
            color=colors[j_],
            label=rf"$\psi=${ψ_deg}$\degree$",
        )
    plt.xlabel("Distance along profile  [m]")
    plt.ylabel(r"Slope angle  [$\degree$]")
    axes = plt.gca()
    # axes.set_aspect("equal")
    plt.yticks(np.arange(-90,+90+15,15))
    plt.grid(ls=":",)
    plt.legend()

stretch

stretch(
    fig: Figure,
    xs: tuple[float, float] | None = None,
    ys: tuple[float, float] | None = None,
) -> None

Stretch graph axes by respective factors.

Source code in erosionfront/visualization/base.py
def stretch(
    self,
    fig: Figure,
    xs: tuple[float, float] | None = None,
    ys: tuple[float, float] | None = None,
) -> None:
    """Stretch graph axes by respective factors."""
    axes: plt.Axes = fig.gca()
    if xs is not None:
        x_lim = axes.get_xlim()
        x_range = x_lim[1] - x_lim[0]
        axes.set_xlim(
            x_lim[0] - x_range * xs[0], x_lim[1] + x_range * xs[1]
        )
    if ys is not None:
        y_lim = axes.get_ylim()
        y_range = y_lim[1] - y_lim[0]
        axes.set_ylim(
            y_lim[0] - y_range * ys[0], y_lim[1] + y_range * ys[1]
        )