Skip to content
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1fd9d4a
Implement the Pattern class for specifying pattern as fills
seisman Sep 7, 2025
30d6b92
Add tests for the Pattern class
seisman Sep 7, 2025
db82469
Use the Pattern class in examples
seisman Sep 7, 2025
3031c59
Improve the gallery example for patterns
seisman Sep 7, 2025
ed1ba89
Remove unnecessary content from the techref for patterns
seisman Sep 7, 2025
57e9c4e
Improve docstrings
seisman Sep 8, 2025
3ba2006
Remove the doc/techref/patterns.md doc
seisman Sep 8, 2025
5dfa0d2
Fix a typo in URL
seisman Sep 8, 2025
0ca48c6
Smaller image size
seisman Sep 8, 2025
43bf9ff
Fix a typo in examples/gallery/symbols/patterns.py
seisman Sep 8, 2025
2a18366
Use the figure directive
seisman Sep 8, 2025
5cda812
Polish examples/gallery/symbols/patterns.py
seisman Sep 8, 2025
dcbcaa8
Fix the text for each pattern
seisman Sep 8, 2025
ff3419c
Fix the image width to show the pattern string
seisman Sep 8, 2025
1b17ab0
Validate that fgcolor and fgcolor cannot both be empty
seisman Sep 8, 2025
994c3be
Fix styling
seisman Sep 8, 2025
8e560c3
Fix typos
seisman Sep 9, 2025
f627d28
Rename id to pattern
seisman Sep 9, 2025
28be123
Rename reversed to invert
seisman Sep 9, 2025
a8af0e7
One more fix for reverse->invert
seisman Sep 9, 2025
56bbf3c
Merge branch 'main' into feature/pattern
seisman Sep 9, 2025
74da7a8
Fix a typo
seisman Sep 9, 2025
e9eca8e
DOC: Fix the default resolution from 1200 to 300 for patterns
seisman Sep 9, 2025
43036c5
Polish docstrings
seisman Sep 9, 2025
4431649
Merge branch 'main' into feature/pattern
seisman Sep 10, 2025
1b453a9
Merge branch 'main' into feature/pattern
seisman Sep 13, 2025
938b5fb
Merge branch 'main' into feature/pattern
seisman Sep 15, 2025
b2b1250
Merge branch 'main' into feature/pattern
seisman Sep 18, 2025
6dfff08
Merge branch 'main' into feature/pattern
seisman Sep 18, 2025
e715fdb
Merge branch 'main' into feature/pattern
seisman Sep 20, 2025
806c912
Merge branch 'main' into feature/pattern
seisman Sep 22, 2025
61dedd2
Merge branch 'main' into feature/pattern
seisman Sep 24, 2025
5774b35
Add intersphinx link to Pattern
seisman Sep 24, 2025
8131163
Merge branch 'main' into feature/pattern
seisman Sep 25, 2025
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 @@ -212,6 +212,7 @@ Class-style Parameters
:toctree: generated

Box
Pattern

Enums
-----
Expand Down
1 change: 0 additions & 1 deletion doc/techref/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ common_parameters.md
projections.md
fonts.md
text_formatting.md
patterns.md
encodings.md
justification_codes.md
environment_variables.md
Expand Down
25 changes: 0 additions & 25 deletions doc/techref/patterns.md

This file was deleted.

52 changes: 25 additions & 27 deletions examples/gallery/symbols/patterns.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
Bit and hachure patterns
========================
In addition to colors, PyGMT also allows using bit and hachure patterns to fill
symbols, polygons, and other areas, via the ``fill`` parameter or similar parameters.
In addition to colors, PyGMT also allows using bit and hachure patterns to fill symbols,
polygons, and other areas, via the ``fill`` parameter or similar parameters.
Example method parameters that support bit and hachure patterns include:
Expand All @@ -19,49 +19,47 @@
``uncertaintyfill``
- :meth:`pygmt.Figure.wiggle`: Anomalies via ``fillpositive`` and ``fillnegative``
GMT provides 90 predefined patterns that can be used in PyGMT. The patterns are numbered
from 1 to 90, and can be colored and inverted. The resolution of the pattern
can be changed, and the background and foreground colors can be set. For a complete list
of available patterns and the full syntax to specify a pattern, refer to the
:doc:`/techref/patterns`.
GMT provides 90 predefined 1-bit patterns, which are numbered from 1 to 90. In addition,
custom 1-, 8-, or 24-bit image raster files can also be used as patterns.
Theses patterns can be specified via the :class:`pygmt.params.Pattern` class. The
patterns can be customized with different resolution and different foreground and
background colors. The foreground and background colors can also be reversed.
"""

# %%
import pygmt
from pygmt.params import Pattern

# A list of patterns that will be demonstrated.
# To use a pattern as fill append "p" and the number of the desired pattern.
# By default, the pattern is plotted in black and white with a resolution of 300 dpi.
# By default, a pattern is plotted in black and white with a resolution of 1200 dpi.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did the default change from 300 dpi to 1200 dpi? Or was 300 dpi wrong / a typo?

Copy link
Member Author

@seisman seisman Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The upstream documentation says 1200, but the source code says 300. Actually it's a typo in the upstream documentation. See PR GenericMappingTools/gmt#8789 for details.

Fixed in e9eca8e.

patterns = [
# Plot a hachted pattern via pattern number 8
"p8",
# Plot a dotted pattern via pattern number 19
"p19",
# Set the background color ("+b") to "red3" and the foreground color ("+f") to
# "lightgray"
"p19+bred3+flightbrown",
# Invert the pattern by using a capitalized "P"
"P19+bred3+flightbrown",
# Change the resolution ("+r") to 100 dpi
"p19+bred3+flightbrown+r100",
# Make the background transparent by not giving a color after "+b";
# works analogous for the foreground
"p19+b+flightbrown+r100",
# Predefined 1-bit pattern 8.
Pattern(8),
# Predefined 1-bit pattern 19.
Pattern(19),
# Pattern 19 with custom backgroud ("red3") and foreground ("lightbrown").
Pattern(19, bgcolor="red3", fgcolor="lightbrown"),
# Reverse the background and foreground.
Pattern(19, reversed=True, bgcolor="red3", fgcolor="lightbrown"),
# Same as above, but with a 100 dpi resolution.
Pattern(19, bgcolor="red3", fgcolor="lightbrown", dpi=100),
# Same as above, but with a transparent background by setting bgcolor to "".
Pattern(19, bgcolor="", fgcolor="lightbrown", dpi=100),
]

fig = pygmt.Figure()
fig.basemap(
region=[0, 10, 0, 12],
projection="X10c",
projection="X18c/10c",
frame="rlbt+glightgray+tBit and Hachure Patterns",
)

y = 11
for pattern in patterns:
# Plot a square with the pattern as fill.
# The square has a size of 2 centimeters with a 1 point thick, black outline.
fig.plot(x=2, y=y, style="s2c", pen="1p,black", fill=pattern)
fig.plot(x=1, y=y, style="s2c", pen="1p,black", fill=pattern)
# Add a description of the pattern.
fig.text(x=4, y=y, text=pattern, font="Courier-Bold", justify="ML")
fig.text(x=2, y=y, text=str(repr(pattern)), font="Courier-Bold", justify="ML")
y -= 2
fig.show()
7 changes: 3 additions & 4 deletions examples/tutorials/advanced/cartesian_histograms.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# Import the required packages
import numpy as np
import pygmt
from pygmt.params import Pattern

# %%
# Generate random data from a normal distribution:
Expand Down Expand Up @@ -204,10 +205,8 @@
frame=["wSnE", "xaf10", "ya5f1+lCumulative counts"],
data=data01,
series=10,
# Use pattern ("p") number 8 as fill for the bars
# Set the background ("+b") to white [Default]
# Set the foreground ("+f") to black [Default]
fill="p8+bwhite+fblack",
# Fill bars with GMT pattern 8, with white background and black foreground.
fill=Pattern(8, bgcolor="white", fgcolor="black"),
Comment on lines +208 to +209
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At L149 above, could you add an intersphinx link to :class:`pygmt.params.Pattern` ? Then we can properly close #2323.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You meant L179, right? Done in 5774b35.

pen="1p,darkgray,solid",
histtype=0,
# Show cumulative counts
Expand Down
10 changes: 4 additions & 6 deletions examples/tutorials/advanced/focal_mechanisms.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
# %%
import pandas as pd
import pygmt
from pygmt.params import Pattern

# Set up arguments for basemap
region = [-5, 5, -5, 5]
Expand Down Expand Up @@ -99,10 +100,7 @@
# ---------------------
#
# Use the parameters ``compressionfill`` and ``extensionfill`` to fill the quadrants
# with different colors or patterns. Regarding patterns see the gallery example
# :doc:`Bit and hachure patterns </gallery/symbols/patterns>` and the Technical
# Reference :doc:`Bit and hachure patterns </techref/patterns>`.

# with different colors or :class:`patterns <pygmt.params.Pattern>`.
fig = pygmt.Figure()
fig.basemap(region=region, projection=projection, frame=frame)

Expand All @@ -122,8 +120,8 @@
longitude=2,
latitude=0,
depth=0,
compressionfill="p8",
extensionfill="p31",
compressionfill=Pattern(8),
extensionfill=Pattern(31),
Comment on lines +123 to +124
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just want to flag that we need to remember to update the type-hints for various *fill parameters to include pygmt.params.Pattern later.

outline=True,
)

Expand Down
4 changes: 2 additions & 2 deletions examples/tutorials/basics/polygons.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,8 @@

# %%
# Use the ``fill`` parameter to fill the polygon with a color or
# :doc:`pattern </techref/patterns>`. Note, that there are no lines drawn between the
# data points by default if ``fill`` is used. Use the ``pen`` parameter to add an
# :class:`pattern <pygmt.params.Pattern>`. Note, that there are no lines drawn between
# the data points by default if ``fill`` is used. Use the ``pen`` parameter to add an
# outline around the polygon.

fig = pygmt.Figure()
Expand Down
1 change: 1 addition & 0 deletions pygmt/params/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
"""

from pygmt.params.box import Box
from pygmt.params.pattern import Pattern
111 changes: 111 additions & 0 deletions pygmt/params/pattern.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"""
The Pattern class for specifying bit and hachure patterns.
"""

import dataclasses

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

__doctest_skip__ = ["Pattern"]


@dataclasses.dataclass(repr=False)
class Pattern(BaseParam):
"""
Class for specifying bit and hachure patterns.
This class allows users to specify predefined bit-patterns or custom 1-, 8-, or
24-bit image raster files to fill symbols and polygons in various PyGMT plotting
methods. The patterns can be customized with different resolution and different
foreground and background colors. The foreground and background colors can also be
reversed.
GMT provides 90 predefined patterns that can be used in PyGMT. The patterns are
numbered from 1 to 90, and shown below:
.. figure:: https://docs.generic-mapping-tools.org/6.5/_images/GMT_App_E.png
:alt: The 90 predefined bit-patterns provided with GMT
:width: 75%
:align: center
Parameters
----------
id
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For this parameter, is id good? Or should we use pattern?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would use pattern, as it looks like that id is already reserved by Python for something else.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in f627d28.

The pattern ID. It can be specified in two forms:
- An integer in the range of 1-90, corresponding to one of 90 predefined 64x64
bit-patterns
- Name of a 1-, 8-, or 24-bit image raster file, to create customized, repeating
images using image raster files.
dpi
Resolution of the pattern in dots per inch (DPI) [Default is 1200].
bgcolor/fgcolor
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type hints for these not showing in the docs. Does it matter?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried to make it work but was not successful.

The background/foreground color for predefined bit-patterns or 1-bit images.
[Default is white for background and black for foreground]. Setting either to
an empty string will yield a transparent background/foreground where only the
foreground or background pixels will be painted.
reversed
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think I like invert. For me, it describes better that the fore- and background colors of the pattern are flipped, and it is shorter than inverted.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed in 28be123.

If ``True``, the pattern will be bit-reversed, i.e., white and black areas will
be interchanged (only applies to predefined bit-patterns or 1-bit images).
Examples
--------
Draw a global map with land areas filled with pattern 15 in a light red background
and 300 dpi resolution:
>>> import pygmt
>>> from pygmt.params import Pattern
>>> fig = pygmt.Figure()
>>> fig.coast(
... region="g",
... projection="H10c",
... frame=True,
... land=Pattern(15, bgcolor="lightred", dpi=300),
... shorelines=True,
... )
>>> fig.show()
"""

id: int | PathLike
dpi: int | None = None
bgcolor: str | None = None
fgcolor: str | None = None
reversed: bool = False

def _validate(self):
"""
Validate the parameters.
"""
# Integer pattern id must be in the range 1-90.
if isinstance(self.id, int) and not (1 <= self.id <= 90):
raise GMTValueError(
self.id,
description="pattern id",
reason=(
"Pattern id must be an integer in the range 1-90 "
"or the name of a 1-, 8-, or 24-bit image raster file."
),
)
# fgcolor and bgcolor cannot both be empty.
if self.fgcolor == "" and self.bgcolor == "":
_value = f"{self.fgcolor=}, {self.bgcolor=}"
raise GMTValueError(
_value,
description="fgcolor/",
Copy link
Preview

Copilot AI Sep 9, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description parameter has a typo - it should be 'fgcolor/bgcolor' not 'fgcolor/' to properly describe both parameters being validated.

Suggested change
description="fgcolor/",
description="fgcolor/bgcolor",

Copilot uses AI. Check for mistakes.

reason="fgcolor and bgcolor cannot both be empty.",
)

@property
def _aliases(self):
"""
Aliases for the Pattern class.
"""
return [
Alias(self.id, name="id", prefix="P" if self.reversed else "p"),
Alias(self.bgcolor, name="bgcolor", prefix="+b"),
Alias(self.fgcolor, name="fgcolor", prefix="+f"),
Alias(self.dpi, name="dpi", prefix="+r"),
]
51 changes: 51 additions & 0 deletions pygmt/tests/test_params_pattern.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
"""
Test the Pattern class.
"""

import pytest
from pygmt.exceptions import GMTValueError
from pygmt.params import Pattern


def test_pattern():
"""
Test the Pattern class.
"""
assert str(Pattern(1)) == "p1"
assert str(Pattern(id=1)) == "p1"

assert str(Pattern("pattern.png")) == "ppattern.png"

assert str(Pattern(10, bgcolor="red")) == "p10+bred"
assert str(Pattern(20, fgcolor="blue")) == "p20+fblue"
assert str(Pattern(30, bgcolor="red", fgcolor="blue")) == "p30+bred+fblue"
assert str(Pattern(30, fgcolor="blue", bgcolor="")) == "p30+b+fblue"
assert str(Pattern(30, fgcolor="", bgcolor="red")) == "p30+bred+f"

assert str(Pattern(40, dpi=300)) == "p40+r300"

assert str(Pattern(50, reversed=True)) == "P50"

pattern = Pattern(90, reversed=True, bgcolor="red", fgcolor="blue", dpi=300)
assert str(pattern) == "P90+bred+fblue+r300"

pattern = Pattern("pattern.png", bgcolor="red", fgcolor="blue", dpi=300)
assert str(pattern) == "ppattern.png+bred+fblue+r300"


def test_pattern_invalid_id():
"""
Test that an invalid pattern id raises a GMTValueError.
"""
with pytest.raises(GMTValueError):
_ = str(Pattern(91))
with pytest.raises(GMTValueError):
_ = str(Pattern(0))


def test_pattern_invalid_colors():
"""
Test that both fgcolor and bgcolor cannot be empty strings.
"""
with pytest.raises(GMTValueError):
_ = str(Pattern(10, fgcolor="", bgcolor=""))
Loading