From 4868c8ae2f50e609c91ac5e99b75e418e226579f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yvonne=20Fr=C3=B6hlich?= <94163266+yvonnefroehlich@users.noreply.github.com> Date: Sat, 2 May 2026 10:17:19 +0200 Subject: [PATCH 1/4] Figure.pygmtlogo: Initial implementation for the circular, colored PyGMT logo (#3849) Co-authored-by: Dongdong Tian Co-authored-by: Michael Grund <23025878+michaelgrund@users.noreply.github.com> Co-authored-by: Wei Ji <23487320+weiji14@users.noreply.github.com> --- doc/api/index.rst | 1 + pygmt/figure.py | 2 + pygmt/src/pygmtlogo.py | 357 ++++++++++++++++++++ pygmt/tests/baseline/test_pygmtlogo.png.dvc | 5 + pygmt/tests/test_pygmtlogo.py | 19 ++ 5 files changed, 384 insertions(+) create mode 100644 pygmt/src/pygmtlogo.py create mode 100644 pygmt/tests/baseline/test_pygmtlogo.png.dvc create mode 100644 pygmt/tests/test_pygmtlogo.py diff --git a/doc/api/index.rst b/doc/api/index.rst index a760615228b..60285216d73 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -35,6 +35,7 @@ Plotting figure elements Figure.logo Figure.magnetic_rose Figure.paragraph + Figure.pygmtlogo Figure.scalebar Figure.solar Figure.text diff --git a/pygmt/figure.py b/pygmt/figure.py index a1494c29fd8..ccf3a21d292 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -30,6 +30,7 @@ from pygmt.src.plot import plot as _plot from pygmt.src.plot3d import plot3d as _plot3d from pygmt.src.psconvert import psconvert as _psconvert +from pygmt.src.pygmtlogo import pygmtlogo as _pygmtlogo from pygmt.src.rose import rose as _rose from pygmt.src.scalebar import scalebar as _scalebar from pygmt.src.shift_origin import shift_origin as _shift_origin @@ -464,6 +465,7 @@ def _repr_html_(self) -> str: plot = _plot plot3d = _plot3d psconvert = _psconvert + pygmtlogo = _pygmtlogo rose = _rose scalebar = _scalebar set_panel = _set_panel diff --git a/pygmt/src/pygmtlogo.py b/pygmt/src/pygmtlogo.py new file mode 100644 index 00000000000..8da5e008f16 --- /dev/null +++ b/pygmt/src/pygmtlogo.py @@ -0,0 +1,357 @@ +""" +pygmtlogo - Plot the PyGMT logo. + +The initial design of the logo is kindly provided by `@sfrooti `_ +and consists of a visual and the wordmark "PyGMT". +""" + +from collections.abc import Sequence +from typing import Literal + +import numpy as np +from pygmt._typing import AnchorCode, PathLike +from pygmt.helpers import GMTTempFile, fmt_docstring +from pygmt.params import Box, Position + +__doctest_skip__ = ["pygmtlogo"] + + +def _create_logo( # noqa: PLR0915 + shape: Literal["circle", "hexagon"] = "circle", + theme: Literal["light", "dark"] = "light", + wordmark: Literal["none", "horizontal", "vertical"] = "none", + color: bool = True, + figname: PathLike = "pygmt_logo.eps", + debug: bool = False, +): + """ + Create the PyGMT logo using PyGMT. + """ + from pygmt.figure import Figure # noqa: PLC0415 + + # Helpful definitions + size = 4 + region = [-size, size] * 2 + proj = "x1c" + # Rotation around z-axis by 30 degrees counter-clockwise placed in the center. + perspective = "30+w0/0" + + # Radii (make sure that r4-r5 == r2-r3) + r0, r1, r2, r3, r4, r5 = size * np.array([128, 112, 75, 61, 53, 39]) / 128 + # Pen thicknesses + thick_shape = r0 - r1 # for shape + thick_gt = r4 - r5 # for letters G and T + thick_m = r4 / 5 # for letter M + thick_comp = thick_shape / 3 # for compass lines + thick_gap = thick_shape / 4 + + # Define colors + color_light = "white" + color_dark = "gray20" + + blue = "48/105/152" # Python blue + yellow = "255/212/59" # Python yellow + red = "238/86/52" # GMT red + if not color: + blue = yellow = red = color_dark + if theme == "dark": + blue = yellow = red = color_light + + # Background and wordmark + match theme: + case "light": + color_bg = color_light + color_py = blue + color_gmt = color_dark + case "dark": + color_bg = color_dark + color_py = yellow + color_gmt = color_light + + # Define shape + match shape: + case "circle": + symbol = "c" + size_shape = r0 + r1 + hex_factor = 1.0 + case "hexagon": + symbol = "h" + size_shape = (r0 + 0.34) * 2 + hex_factor = 1.1 + + # Define wordmark + font = "AvantGarde-Book" + match wordmark: + case "vertical": + args_text_wm = {"x": 0, "y": -4.5, "justify": "CT", "font": f"2.5c,{font}"} + case "horizontal": + args_text_wm = {"x": 4.5, "y": 0.8, "justify": "LM", "font": f"8c,{font}"} + + def _letter_g_coords(): + """Coordinates for letter G.""" + outer_angles = np.deg2rad(np.arange(90, 361)) + inner_angles = outer_angles[::-1] + offset = thick_gt / 2 + # Outer arc (r4) + arc_outer_x, arc_outer_y = np.cos(outer_angles) * r4, np.sin(outer_angles) * r4 + # Connecting lines + connector_x, connector_y = [r4, 0, 0, r5], [offset, offset, -offset, -offset] + # Inner arc (r5) + arc_inner_x, arc_inner_y = np.cos(inner_angles) * r5, np.sin(inner_angles) * r5 + # Combine all coordinates (outer arc, connectors, inner arc) + g_x = np.concatenate([arc_outer_x, connector_x, arc_inner_x]) + g_y = np.concatenate([arc_outer_y, connector_y, arc_inner_y]) + return {"x": g_x, "y": g_y} + + def _letter_m_coords(): + """Coordinates for letter M.""" + # X-coordinates from left to right. + x1 = thick_gap # Left edge of left vertical line of M. + x5 = r4 # Right edge of right vertical line of M. + x2 = x1 + thick_m # Right edge of left vertical line of M. + x3 = (x1 + x5) / 2 # The middle of M. + x4 = x5 - thick_m # Left edge of right vertical line of M. + # Y-coordinates from bottom to top. + y1 = thick_gt / 2 + thick_gap # Bottom of the letter M. + y2 = r5 - thick_gt # Bottom of the middle peak of M. + y3 = r5 # Top of the middle peak of M. + y4 = r4 # Top of letter M. + # X- and Y-coordinates of the letter M, starting from the left edge of the left + # vertical line and going clockwise. + m_x = [x1, x1, x2, x3, x4, x5, x5, x4, x4, x3, x2, x2] + m_y = [y1, y4, y4, y3, y4, y4, y1, y1, y3, y2, y3, y1] + return {"x": m_x, "y": m_y} + + def _letter_t_coords(): + """Coordinates for letter T.""" + outer_angles = np.deg2rad(np.arange(240, 300, 0.5)) + inner_angles = outer_angles[::-1] + arc_outer_x, arc_outer_y = np.cos(outer_angles) * r2, np.sin(outer_angles) * r2 + arc_inner_x, arc_inner_y = np.cos(inner_angles) * r3, np.sin(inner_angles) * r3 + # The arrowhead is an equilateral triangle + x0 = thick_gt / 2 # Extra half-width for arrow head + y0 = 1.8 * x0 * np.sqrt(3) # Height for arrow head + arrow_x = [-x0, -x0, -x0 * 2.0, 0, x0 * 2.0, x0, x0] + arrow_y = [-r2, -r0 + y0, -r0 + y0, -r0, -r0 + y0, -r0 + y0, -r2] + mask_left = arc_outer_x < -x0 + mask_right = arc_outer_x > x0 + t_x = np.concatenate( + [arc_inner_x, arc_outer_x[mask_left], arrow_x, arc_outer_x[mask_right]] + ) + t_y = np.concatenate( + [arc_inner_y, arc_outer_y[mask_left], arrow_y, arc_outer_y[mask_right]] + ) + # Ensure the same X-coordinate for the right edge of T and the middle of M. + mask = np.abs(t_x) <= (thick_gap + r4) / 2 + return {"x": t_x[mask], "y": t_y[mask]} + + def _bg_arrow_coords(): + """Coordinates for the background arrow.""" + # x0, y0 is the same as in _letter_t_coords(). + x0 = thick_gt / 2 + y0 = 1.8 * x0 * np.sqrt(3) + # The background arrow is thick_comp wider than the letter T. + x1 = x0 + thick_comp / 2.0 # Half-width of the arrow tail + x2 = 2 * x0 + thick_comp / np.sqrt(3) # Half-width of the arrow head + + arrow_x = [-x1, -x1, -x2, -(x2 - 2 * x0), (x2 - 2 * x0), x2, x1, x1] + arrow_y = [r0, -r0 + y0, -r0 + y0, -r0, -r0, -r0 + y0, -r0 + y0, r0] + return {"x": arrow_x, "y": arrow_y} + + def _compass_lines(): + """Coordinates of compass lines.""" + sqrt2 = np.sqrt(2) / 2 + x1, x2, x3 = r0 * sqrt2, r3 * sqrt2, (r2 + (r3 - r4)) * sqrt2 + # Coordinates of vectors in the format of (x_start, y_start, x_end, y_end). + return [ + (-r0 * hex_factor, 0, -r3, 0), # left horizontal + (r3, 0, r0 * hex_factor, 0), # right horizontal + (-x1, x1, -x2, x2), # upper left + (-x1, -x1, -x2, -x2), # lower left + (x1, x1, x3, x3), # upper right + (x1, -x1, x2, -x2), # lower right + ] + + def _vline_coords(): + """ + Coordinates for the vertical line at the top. + """ + x0 = thick_gt / 2 + return {"x": [-x0, -x0, x0, x0], "y": [r0, r3, r3, r0]} + + fig = Figure() + fig.basemap(region=region, projection=proj, perspective=perspective, frame="none") + + # Earth - circle / hexagon + args_shape = { + "style": f"{symbol}{size_shape}c", + "perspective": True, + "no_clip": True, # Needed for corners of hexagon shape + } + # Shape fill + fig.plot(x=0, y=0, fill=color_bg, **args_shape) + + # Compass lines + fig.plot( + data=_compass_lines(), + pen=f"{thick_comp}c,{yellow}", + style="v0c+s", + perspective=True, + no_clip=True, + ) + + # Shape outline (over ends of compass lines for hexagon shape) + fig.plot(x=0, y=0, pen=f"{thick_shape}c,{blue}", **args_shape) + + # Arrow in background color (over shape outline but under letters) + fig.plot(data=_bg_arrow_coords(), fill=color_bg, perspective=True) + + # Letters G, M, and T + fig.plot(data=_letter_g_coords(), fill=red, perspective=True) + fig.plot(data=_letter_m_coords(), fill=red, perspective=True) + fig.plot(data=_letter_t_coords(), fill=red, perspective=True) + + # Upper vertical line + fig.plot(data=_vline_coords(), fill=red, perspective=True) + + # Outline around the shape for black and white color with dark theme + if not color and theme == "dark": + fig.plot( + x=0, + y=0, + style=f"{symbol}{size_shape + thick_shape}c", + pen=f"1p,{color_dark}", + perspective=True, + no_clip=True, + ) + + # Add wordmark "PyGMT" + if wordmark != "none": + text_wm = f"@;{color_py};Py@;;@;{color_gmt};GMT@;;" + fig.text(text=text_wm, no_clip=True, **args_text_wm) + + # Helpful for implementing the logo; not included in the logo + if debug: + from pygmt import config # noqa: PLC0415 + + # Gridlines + with config(MAP_FRAME_TYPE="inside", MAP_GRID_PEN="0.1p,gray30"): + fig.basemap(frame="g1") + # Circles for the different radii + for r in [r0, r1, r2, r3, r4, r5]: + fig.plot(x=0, y=0, style=f"c{2 * r}c", pen="0.3p,gray30") + pen = "0.3p,gray30,2_2" + fig.plot(x=0, y=0, style=f"c{2 * (r2 + (r3 - r4))}c", pen=pen) + # Lines for letter M + fig.hlines(y=[r4, r5], xmin=-3, pen=pen, perspective=True) + fig.vlines(x=[r4, (thick_gap + r4) / 2], ymax=3, pen=pen, perspective=True) + + fig.savefig(fname=figname) + + +@fmt_docstring +def pygmtlogo( # noqa: PLR0913 + self, + shape: Literal["circle", "hexagon"] = "circle", + theme: Literal["light", "dark"] = "light", + wordmark: Literal["none", "horizontal", "vertical"] = "none", + color: bool = True, + width: float | str | None = None, + height: float | str | None = None, + position: Position | Sequence[float | str] | AnchorCode | None = None, + box: Box | bool = False, + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + panel: int | Sequence[int] | bool = False, + perspective: float | Sequence[float] | str | bool = False, + transparency: float | None = None, +): + """ + Plot the PyGMT logo. + + The design of the logo is kindly provided by `@sfrooti `_ + and consists of a visual and the wordmark "PyGMT". + + Parameters + ---------- + shape + Shape of the visual logo. Use ``"circle"`` for a circle shape [Default] or + ``"hexagon"`` for a hexagon shape. + theme + Use ``"light"`` for light mode (i.e., a white background) [Default] and + ``"dark"`` for dark mode (i.e., a darkgray background). + wordmark + Add the wordmark "PyGMT" and adjust its orientation relative to the visual. + Valid values are: + + - ``"none"``: no wordmark [Default]. + - ``"horizontal"``: wordmark at the right side of the visual. + - ``"vertical"``: wordmark below the visual. + color + ``True`` for a color logo, and ``False`` for a black and white logo. + position + Position of the GMT logo 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 ` 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 (position + ``(0, 0)`` with anchor ``"BL"``). + width + height + Width or height of the PyGMT logo. Since the aspect ratio is fixed, only one of + the two can be specified. + box + Draw a background box behind the logo. 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. + $verbose + $panel + $perspective + $transparency + + Examples + -------- + >>> import pygmt + + The simplest way to plot the PyGMT logo is to call the method without any arguments. + + >>> fig = pygmt.Figure() + >>> fig.pygmtlogo() + >>> fig.show() + + Plot the PyGMT logo with the wordmark "PyGMT" with a height of 1 centimeter at the + right side in the Bottom Right corner on an existing basemap: + + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[-90, -70, 0, 20], projection="M10c", frame=True) + >>> fig.pygmtlogo(wordmark="horizontal", position="BR", height="1c") + >>> fig.show() + """ + with GMTTempFile(suffix=".eps") as logofile: + # Create logo file + _create_logo( + color=color, + theme=theme, + shape=shape, + wordmark=wordmark, + figname=logofile.name, + ) + + # Add to existing Figure instance + self.image( + imagefile=logofile.name, + position=position, + width=width, + height=height, + box=box, + verbose=verbose, + panel=panel, + perspective=perspective, + transparency=transparency, + ) diff --git a/pygmt/tests/baseline/test_pygmtlogo.png.dvc b/pygmt/tests/baseline/test_pygmtlogo.png.dvc new file mode 100644 index 00000000000..010f8e4c4db --- /dev/null +++ b/pygmt/tests/baseline/test_pygmtlogo.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 35c59c31c92f13c705a24933465ff551 + size: 14374 + hash: md5 + path: test_pygmtlogo.png diff --git a/pygmt/tests/test_pygmtlogo.py b/pygmt/tests/test_pygmtlogo.py new file mode 100644 index 00000000000..fb117b9b097 --- /dev/null +++ b/pygmt/tests/test_pygmtlogo.py @@ -0,0 +1,19 @@ +""" +Test Figure.pygmtlogo. +""" + +import pytest +from pygmt import Figure + + +@pytest.mark.benchmark +@pytest.mark.mpl_image_compare +def test_pygmtlogo(): + """ + Plot the default PyGMT logo, colored, light and dark themes, without wordmark. + """ + fig = Figure() + fig.pygmtlogo() + fig.shift_origin(xshift="+w") + fig.pygmtlogo(theme="dark") + return fig From bd24b5a12aa58662c9596557ff07aa8879f30b60 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Wed, 6 May 2026 19:16:27 +0800 Subject: [PATCH 2/4] Force the default logo size to 2-cm and rename test_pygmtlogo to test_pygmtlogo_circle_no_wordmark (#4617) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com> --- pygmt/src/pygmtlogo.py | 18 +++++++++++++++++- pygmt/tests/baseline/test_pygmtlogo.png.dvc | 5 ----- .../test_pygmtlogo_circle_no_wordmark.png.dvc | 5 +++++ pygmt/tests/test_pygmtlogo.py | 19 +++++++++++++------ 4 files changed, 35 insertions(+), 12 deletions(-) delete mode 100644 pygmt/tests/baseline/test_pygmtlogo.png.dvc create mode 100644 pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc diff --git a/pygmt/src/pygmtlogo.py b/pygmt/src/pygmtlogo.py index 8da5e008f16..93286689cd7 100644 --- a/pygmt/src/pygmtlogo.py +++ b/pygmt/src/pygmtlogo.py @@ -10,6 +10,7 @@ import numpy as np from pygmt._typing import AnchorCode, PathLike +from pygmt.exceptions import GMTValueError from pygmt.helpers import GMTTempFile, fmt_docstring from pygmt.params import Box, Position @@ -304,7 +305,8 @@ def pygmtlogo( # noqa: PLR0913 width height Width or height of the PyGMT logo. Since the aspect ratio is fixed, only one of - the two can be specified. + the two can be specified. If not specified, the default size of the visual logo + is set to 2 cm. box Draw a background box behind the logo. If set to ``True``, a simple rectangular box is drawn using :gmt-term:`MAP_FRAME_PEN`. To customize the box appearance, @@ -333,6 +335,20 @@ def pygmtlogo( # noqa: PLR0913 >>> fig.pygmtlogo(wordmark="horizontal", position="BR", height="1c") >>> fig.show() """ + # Set the default size of the visual logo to 2 cm. + if width is None and height is None: + match wordmark: + case "none" | "vertical": + width = width or "2c" + case "horizontal": + height = height or "2c" + case _: + raise GMTValueError( + wordmark, + description="value for wordmark", + choices={"none", "horizontal", "vertical"}, + ) + with GMTTempFile(suffix=".eps") as logofile: # Create logo file _create_logo( diff --git a/pygmt/tests/baseline/test_pygmtlogo.png.dvc b/pygmt/tests/baseline/test_pygmtlogo.png.dvc deleted file mode 100644 index 010f8e4c4db..00000000000 --- a/pygmt/tests/baseline/test_pygmtlogo.png.dvc +++ /dev/null @@ -1,5 +0,0 @@ -outs: -- md5: 35c59c31c92f13c705a24933465ff551 - size: 14374 - hash: md5 - path: test_pygmtlogo.png diff --git a/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc b/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc new file mode 100644 index 00000000000..d33a5c5bd35 --- /dev/null +++ b/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: b1dee02932b292335cf5f8b95676a258 + size: 19161 + hash: md5 + path: test_pygmtlogo_circle_no_wordmark.png diff --git a/pygmt/tests/test_pygmtlogo.py b/pygmt/tests/test_pygmtlogo.py index fb117b9b097..2495a9a3a1a 100644 --- a/pygmt/tests/test_pygmtlogo.py +++ b/pygmt/tests/test_pygmtlogo.py @@ -4,16 +4,23 @@ import pytest from pygmt import Figure +from pygmt.params import Axis, Position -@pytest.mark.benchmark @pytest.mark.mpl_image_compare -def test_pygmtlogo(): +def test_pygmtlogo_circle_no_wordmark(): """ - Plot the default PyGMT logo, colored, light and dark themes, without wordmark. + Test the PyGMT circular logo without the wordmark, including both light/dark themes, + and colored/black-and-white versions. """ fig = Figure() - fig.pygmtlogo() - fig.shift_origin(xshift="+w") - fig.pygmtlogo(theme="dark") + fig.basemap(region=[-0.5, 5.0, -0.5, 5.0], projection="x1c", frame=Axis(grid=0.5)) + fig.pygmtlogo( + position=Position((1, 3.5), anchor="CM", cstype="mapcoords"), + theme="light", + ) + fig.pygmtlogo( + position=Position((3.5, 3.5), anchor="CM", cstype="mapcoords"), + theme="dark", + ) return fig From 091d62c60efc207bb4f537a93a61b5def0960f29 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 7 May 2026 15:53:57 +0800 Subject: [PATCH 3/4] Add a test to check the design details of the circular logo (#4623) --- pygmt/src/pygmtlogo.py | 13 ++++++++----- .../baseline/test_pygmtlogo_circle_design.png.dvc | 5 +++++ pygmt/tests/test_pygmtlogo.py | 14 ++++++++++++++ 3 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 pygmt/tests/baseline/test_pygmtlogo_circle_design.png.dvc diff --git a/pygmt/src/pygmtlogo.py b/pygmt/src/pygmtlogo.py index 93286689cd7..e9351b0edad 100644 --- a/pygmt/src/pygmtlogo.py +++ b/pygmt/src/pygmtlogo.py @@ -17,12 +17,12 @@ __doctest_skip__ = ["pygmtlogo"] -def _create_logo( # noqa: PLR0915 +def _create_logo( # noqa: PLR0915, PLR0912 shape: Literal["circle", "hexagon"] = "circle", theme: Literal["light", "dark"] = "light", wordmark: Literal["none", "horizontal", "vertical"] = "none", color: bool = True, - figname: PathLike = "pygmt_logo.eps", + figname: PathLike | None = None, debug: bool = False, ): """ @@ -236,8 +236,8 @@ def _vline_coords(): from pygmt import config # noqa: PLC0415 # Gridlines - with config(MAP_FRAME_TYPE="inside", MAP_GRID_PEN="0.1p,gray30"): - fig.basemap(frame="g1") + with config(MAP_GRID_PEN="0.1p,gray30"): + fig.basemap(frame="00g1") # Circles for the different radii for r in [r0, r1, r2, r3, r4, r5]: fig.plot(x=0, y=0, style=f"c{2 * r}c", pen="0.3p,gray30") @@ -247,7 +247,10 @@ def _vline_coords(): fig.hlines(y=[r4, r5], xmin=-3, pen=pen, perspective=True) fig.vlines(x=[r4, (thick_gap + r4) / 2], ymax=3, pen=pen, perspective=True) - fig.savefig(fname=figname) + if figname: + fig.savefig(fname=figname) + return None + return fig @fmt_docstring diff --git a/pygmt/tests/baseline/test_pygmtlogo_circle_design.png.dvc b/pygmt/tests/baseline/test_pygmtlogo_circle_design.png.dvc new file mode 100644 index 00000000000..7a81601eafe --- /dev/null +++ b/pygmt/tests/baseline/test_pygmtlogo_circle_design.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 2d9fd4fdb1189514d374500d5f4add73 + size: 149683 + hash: md5 + path: test_pygmtlogo_circle_design.png diff --git a/pygmt/tests/test_pygmtlogo.py b/pygmt/tests/test_pygmtlogo.py index 2495a9a3a1a..924ff965a18 100644 --- a/pygmt/tests/test_pygmtlogo.py +++ b/pygmt/tests/test_pygmtlogo.py @@ -5,6 +5,20 @@ import pytest from pygmt import Figure from pygmt.params import Axis, Position +from pygmt.src.pygmtlogo import _create_logo + + +@pytest.mark.mpl_image_compare(savefig_kwargs={"dpi": 600}) +def test_pygmtlogo_circle_design(): + """ + Test the design details of the PyGMT circular logo. + + This is a regression test to ensure that the design of the logo does not change + unintentionally. The debugging lines (gridlines and circles) are helpful for + implementing the logo, but they are not included in the final logo design. + """ + fig = _create_logo(debug=True) + return fig @pytest.mark.mpl_image_compare From c63767fbc085616741fef1265ad30bd9522ea63a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 8 May 2026 06:39:35 +0800 Subject: [PATCH 4/4] Figure.pygmtlogo: Add tests for the circular, black/white PyGMT logo (#4625) --- pygmt/src/pygmtlogo.py | 2 +- .../baseline/test_pygmtlogo_circle_no_wordmark.png.dvc | 4 ++-- pygmt/tests/test_pygmtlogo.py | 10 ++++++++++ 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/pygmt/src/pygmtlogo.py b/pygmt/src/pygmtlogo.py index e9351b0edad..6691ebd8bd8 100644 --- a/pygmt/src/pygmtlogo.py +++ b/pygmt/src/pygmtlogo.py @@ -221,7 +221,7 @@ def _vline_coords(): x=0, y=0, style=f"{symbol}{size_shape + thick_shape}c", - pen=f"1p,{color_dark}", + pen=f"{thick_comp / 2.0}c,{color_bg}", perspective=True, no_clip=True, ) diff --git a/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc b/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc index d33a5c5bd35..3530063c1f5 100644 --- a/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc +++ b/pygmt/tests/baseline/test_pygmtlogo_circle_no_wordmark.png.dvc @@ -1,5 +1,5 @@ outs: -- md5: b1dee02932b292335cf5f8b95676a258 - size: 19161 +- md5: dcb54b2c28e02c4fc83d38278b2700c6 + size: 31454 hash: md5 path: test_pygmtlogo_circle_no_wordmark.png diff --git a/pygmt/tests/test_pygmtlogo.py b/pygmt/tests/test_pygmtlogo.py index 924ff965a18..e4d6f4aff5e 100644 --- a/pygmt/tests/test_pygmtlogo.py +++ b/pygmt/tests/test_pygmtlogo.py @@ -37,4 +37,14 @@ def test_pygmtlogo_circle_no_wordmark(): position=Position((3.5, 3.5), anchor="CM", cstype="mapcoords"), theme="dark", ) + fig.pygmtlogo( + position=Position((1, 1), anchor="CM", cstype="mapcoords"), + theme="light", + color=False, + ) + fig.pygmtlogo( + position=Position((3.5, 1), anchor="CM", cstype="mapcoords"), + theme="dark", + color=False, + ) return fig