Skip to content

Commit bd1dd63

Browse files
Add the Pattern class for specifying bit and hachure patterns to fill symbols and polygons (#4020)
Co-authored-by: Yvonne Fröhlich <94163266+yvonnefroehlich@users.noreply.github.com>
1 parent ca93fb5 commit bd1dd63

File tree

10 files changed

+202
-69
lines changed

10 files changed

+202
-69
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ Class-style Parameters
212212
:toctree: generated
213213

214214
Box
215+
Pattern
215216

216217
Enums
217218
-----

doc/techref/index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ common_parameters.md
1212
projections.md
1313
fonts.md
1414
text_formatting.md
15-
patterns.md
1615
encodings.md
1716
justification_codes.md
1817
environment_variables.md

doc/techref/patterns.md

Lines changed: 0 additions & 25 deletions
This file was deleted.

examples/gallery/symbols/patterns.py

Lines changed: 25 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
Bit and hachure patterns
33
========================
44
5-
In addition to colors, PyGMT also allows using bit and hachure patterns to fill
6-
symbols, polygons, and other areas, via the ``fill`` parameter or similar parameters.
5+
In addition to colors, PyGMT also allows using bit and hachure patterns to fill symbols,
6+
polygons, and other areas, via the ``fill`` parameter or similar parameters.
77
88
Example method parameters that support bit and hachure patterns include:
99
@@ -19,49 +19,47 @@
1919
``uncertaintyfill``
2020
- :meth:`pygmt.Figure.wiggle`: Anomalies via ``fillpositive`` and ``fillnegative``
2121
22-
GMT provides 90 predefined patterns that can be used in PyGMT. The patterns are numbered
23-
from 1 to 90, and can be colored and inverted. The resolution of the pattern
24-
can be changed, and the background and foreground colors can be set. For a complete list
25-
of available patterns and the full syntax to specify a pattern, refer to the
26-
:doc:`/techref/patterns`.
22+
GMT provides 90 predefined 1-bit patterns, which are numbered from 1 to 90. In addition,
23+
custom 1-, 8-, or 24-bit image raster files can also be used as patterns.
24+
25+
These patterns can be specified via the :class:`pygmt.params.Pattern` class. The
26+
patterns can be customized with different resolution and different foreground and
27+
background colors. The foreground and background colors can also be inverted.
2728
"""
2829

2930
# %%
3031
import pygmt
32+
from pygmt.params import Pattern
3133

3234
# A list of patterns that will be demonstrated.
33-
# To use a pattern as fill append "p" and the number of the desired pattern.
34-
# By default, the pattern is plotted in black and white with a resolution of 300 dpi.
35+
# By default, a pattern is plotted in black and white with a resolution of 300 dpi.
3536
patterns = [
36-
# Plot a hachted pattern via pattern number 8
37-
"p8",
38-
# Plot a dotted pattern via pattern number 19
39-
"p19",
40-
# Set the background color ("+b") to "red3" and the foreground color ("+f") to
41-
# "lightgray"
42-
"p19+bred3+flightbrown",
43-
# Invert the pattern by using a capitalized "P"
44-
"P19+bred3+flightbrown",
45-
# Change the resolution ("+r") to 100 dpi
46-
"p19+bred3+flightbrown+r100",
47-
# Make the background transparent by not giving a color after "+b";
48-
# works analogous for the foreground
49-
"p19+b+flightbrown+r100",
37+
# Predefined 1-bit pattern 8.
38+
Pattern(8),
39+
# Predefined 1-bit pattern 19.
40+
Pattern(19),
41+
# Pattern 19 with custom background ("red3") and foreground ("lightbrown").
42+
Pattern(19, bgcolor="red3", fgcolor="lightbrown"),
43+
# Invert the background and foreground.
44+
Pattern(19, invert=True, bgcolor="red3", fgcolor="lightbrown"),
45+
# Same as above, but with a 100 dpi resolution.
46+
Pattern(19, bgcolor="red3", fgcolor="lightbrown", dpi=100),
47+
# Same as above, but with a transparent background by setting bgcolor to "".
48+
Pattern(19, bgcolor="", fgcolor="lightbrown", dpi=100),
5049
]
5150

5251
fig = pygmt.Figure()
5352
fig.basemap(
5453
region=[0, 10, 0, 12],
55-
projection="X10c",
54+
projection="X18c/10c",
5655
frame="rlbt+glightgray+tBit and Hachure Patterns",
5756
)
58-
5957
y = 11
6058
for pattern in patterns:
6159
# Plot a square with the pattern as fill.
6260
# The square has a size of 2 centimeters with a 1 point thick, black outline.
63-
fig.plot(x=2, y=y, style="s2c", pen="1p,black", fill=pattern)
61+
fig.plot(x=1, y=y, style="s2c", pen="1p,black", fill=pattern)
6462
# Add a description of the pattern.
65-
fig.text(x=4, y=y, text=pattern, font="Courier-Bold", justify="ML")
63+
fig.text(x=2, y=y, text=str(repr(pattern)), font="Courier-Bold", justify="ML")
6664
y -= 2
6765
fig.show()

examples/tutorials/advanced/cartesian_histograms.py

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# Import the required packages
1919
import numpy as np
2020
import pygmt
21+
from pygmt.params import Pattern
2122

2223
# %%
2324
# Generate random data from a normal distribution:
@@ -174,10 +175,10 @@
174175
# Cumulative values
175176
# -----------------
176177
#
177-
# To create a histogram showing the cumulative values set ``cumulative=True``. Here,
178-
# the bars of the cumulative histogram are filled with a pattern via the ``fill``
179-
# parameter. Annotate each bar with the counts it represents using the ``annotate``
180-
# parameter.
178+
# To create a histogram showing the cumulative values set ``cumulative=True``. Here, the
179+
# bars of the cumulative histogram are filled with a :class:`pygmt.params.Pattern` via
180+
# the ``fill`` parameter. Annotate each bar with the counts it represents using the
181+
# ``annotate`` parameter.
181182

182183
fig = pygmt.Figure()
183184

@@ -204,10 +205,8 @@
204205
frame=["wSnE", "xaf10", "ya5f1+lCumulative counts"],
205206
data=data01,
206207
series=10,
207-
# Use pattern ("p") number 8 as fill for the bars
208-
# Set the background ("+b") to white [Default]
209-
# Set the foreground ("+f") to black [Default]
210-
fill="p8+bwhite+fblack",
208+
# Fill bars with GMT pattern 8, with white background and black foreground.
209+
fill=Pattern(8, bgcolor="white", fgcolor="black"),
211210
pen="1p,darkgray,solid",
212211
histtype=0,
213212
# Show cumulative counts

examples/tutorials/advanced/focal_mechanisms.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
# %%
1919
import pandas as pd
2020
import pygmt
21+
from pygmt.params import Pattern
2122

2223
# Set up arguments for basemap
2324
region = [-5, 5, -5, 5]
@@ -99,10 +100,7 @@
99100
# ---------------------
100101
#
101102
# Use the parameters ``compressionfill`` and ``extensionfill`` to fill the quadrants
102-
# with different colors or patterns. Regarding patterns see the gallery example
103-
# :doc:`Bit and hachure patterns </gallery/symbols/patterns>` and the Technical
104-
# Reference :doc:`Bit and hachure patterns </techref/patterns>`.
105-
103+
# with different colors or :class:`patterns <pygmt.params.Pattern>`.
106104
fig = pygmt.Figure()
107105
fig.basemap(region=region, projection=projection, frame=frame)
108106

@@ -122,8 +120,8 @@
122120
longitude=2,
123121
latitude=0,
124122
depth=0,
125-
compressionfill="p8",
126-
extensionfill="p31",
123+
compressionfill=Pattern(8),
124+
extensionfill=Pattern(31),
127125
outline=True,
128126
)
129127

examples/tutorials/basics/polygons.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050

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

5757
fig = pygmt.Figure()

pygmt/params/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@
33
"""
44

55
from pygmt.params.box import Box
6+
from pygmt.params.pattern import Pattern

pygmt/params/pattern.py

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
"""
2+
The Pattern class for specifying bit and hachure patterns.
3+
"""
4+
5+
import dataclasses
6+
7+
from pygmt._typing import PathLike
8+
from pygmt.alias import Alias
9+
from pygmt.exceptions import GMTValueError
10+
from pygmt.params.base import BaseParam
11+
12+
__doctest_skip__ = ["Pattern"]
13+
14+
15+
@dataclasses.dataclass(repr=False)
16+
class Pattern(BaseParam):
17+
"""
18+
Class for specifying bit and hachure patterns.
19+
20+
This class allows users to specify predefined bit-patterns or custom 1-, 8-, or
21+
24-bit image raster files to fill symbols and polygons in various PyGMT plotting
22+
methods. The patterns can be customized with different resolution and different
23+
foreground and background colors. The foreground and background colors can also be
24+
inverted.
25+
26+
GMT provides 90 predefined patterns that can be used in PyGMT. The patterns are
27+
numbered from 1 to 90, and shown below:
28+
29+
.. figure:: https://docs.generic-mapping-tools.org/6.5/_images/GMT_App_E.png
30+
:alt: The 90 predefined bit-patterns provided with GMT
31+
:width: 75%
32+
:align: center
33+
34+
Parameters
35+
----------
36+
pattern
37+
The pattern to use. It can be specified in two forms:
38+
39+
- An integer in the range of 1-90, corresponding to one of 90 predefined 64x64
40+
bit-patterns
41+
- Name of a 1-, 8-, or 24-bit image raster file, to create customized, repeating
42+
images using image raster files.
43+
dpi
44+
Resolution of the pattern in dots per inch (DPI) [Default is 300].
45+
bgcolor/fgcolor
46+
The background/foreground color for predefined bit-patterns or 1-bit images.
47+
Setting either to an empty string will yield a transparent background/foreground
48+
where only the foreground/background pixels will be painted. [Default is white
49+
for background and black for foreground].
50+
invert
51+
If ``True``, the pattern will be bit-inverted, i.e., white and black areas will
52+
be interchanged (only applies to predefined bit-patterns or 1-bit images).
53+
54+
Examples
55+
--------
56+
Draw a global map with land areas filled with pattern 15 in a light red background
57+
and 200 dpi resolution:
58+
59+
>>> import pygmt
60+
>>> from pygmt.params import Pattern
61+
>>> fig = pygmt.Figure()
62+
>>> fig.coast(
63+
... region="g",
64+
... projection="H10c",
65+
... frame=True,
66+
... land=Pattern(15, bgcolor="lightred", dpi=200),
67+
... shorelines=True,
68+
... )
69+
>>> fig.show()
70+
"""
71+
72+
pattern: int | PathLike
73+
dpi: int | None = None
74+
bgcolor: str | None = None
75+
fgcolor: str | None = None
76+
invert: bool = False
77+
78+
def _validate(self):
79+
"""
80+
Validate the parameters.
81+
"""
82+
# Integer pattern number must be in the range 1-90.
83+
if isinstance(self.pattern, int) and not (1 <= self.pattern <= 90):
84+
raise GMTValueError(
85+
self.pattern,
86+
description="pattern number",
87+
reason=(
88+
"Parameter 'pattern' must be an integer in the range 1-90 "
89+
"or the name of a 1-, 8-, or 24-bit image raster file."
90+
),
91+
)
92+
# fgcolor and bgcolor cannot both be empty.
93+
if self.fgcolor == "" and self.bgcolor == "":
94+
_value = f"{self.fgcolor=}, {self.bgcolor=}"
95+
raise GMTValueError(
96+
_value,
97+
description="fgcolor and bgcolor",
98+
reason="fgcolor and bgcolor cannot both be empty.",
99+
)
100+
101+
@property
102+
def _aliases(self):
103+
"""
104+
Aliases for the Pattern class.
105+
"""
106+
return [
107+
Alias(self.pattern, name="pattern", prefix="P" if self.invert else "p"),
108+
Alias(self.bgcolor, name="bgcolor", prefix="+b"),
109+
Alias(self.fgcolor, name="fgcolor", prefix="+f"),
110+
Alias(self.dpi, name="dpi", prefix="+r"),
111+
]

pygmt/tests/test_params_pattern.py

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
"""
2+
Test the Pattern class.
3+
"""
4+
5+
import pytest
6+
from pygmt.exceptions import GMTValueError
7+
from pygmt.params import Pattern
8+
9+
10+
def test_pattern():
11+
"""
12+
Test the Pattern class.
13+
"""
14+
assert str(Pattern(1)) == "p1"
15+
assert str(Pattern(pattern=1)) == "p1"
16+
17+
assert str(Pattern("pattern.png")) == "ppattern.png"
18+
19+
assert str(Pattern(10, bgcolor="red")) == "p10+bred"
20+
assert str(Pattern(20, fgcolor="blue")) == "p20+fblue"
21+
assert str(Pattern(30, bgcolor="red", fgcolor="blue")) == "p30+bred+fblue"
22+
assert str(Pattern(30, fgcolor="blue", bgcolor="")) == "p30+b+fblue"
23+
assert str(Pattern(30, fgcolor="", bgcolor="red")) == "p30+bred+f"
24+
25+
assert str(Pattern(40, dpi=300)) == "p40+r300"
26+
27+
assert str(Pattern(50, invert=True)) == "P50"
28+
29+
pattern = Pattern(90, invert=True, bgcolor="red", fgcolor="blue", dpi=300)
30+
assert str(pattern) == "P90+bred+fblue+r300"
31+
32+
pattern = Pattern("pattern.png", bgcolor="red", fgcolor="blue", dpi=300)
33+
assert str(pattern) == "ppattern.png+bred+fblue+r300"
34+
35+
36+
def test_pattern_invalid_pattern():
37+
"""
38+
Test that an invalid pattern number raises a GMTValueError.
39+
"""
40+
with pytest.raises(GMTValueError):
41+
_ = str(Pattern(0))
42+
with pytest.raises(GMTValueError):
43+
_ = str(Pattern(91))
44+
45+
46+
def test_pattern_invalid_colors():
47+
"""
48+
Test that both fgcolor and bgcolor cannot be empty strings.
49+
"""
50+
with pytest.raises(GMTValueError):
51+
_ = str(Pattern(10, fgcolor="", bgcolor=""))

0 commit comments

Comments
 (0)