Skip to content

Commit 89020a2

Browse files
committed
Add the Pattern class for filling symbols/areas with GMT patterns
1 parent a337503 commit 89020a2

File tree

5 files changed

+138
-6
lines changed

5 files changed

+138
-6
lines changed

examples/tutorials/advanced/cartesian_histograms.py

Lines changed: 3 additions & 4 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:
@@ -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: 3 additions & 2 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]
@@ -122,8 +123,8 @@
122123
longitude=2,
123124
latitude=0,
124125
depth=0,
125-
compressionfill="p8",
126-
extensionfill="p31",
126+
compressionfill=Pattern(8),
127+
extensionfill=Pattern(31),
127128
outline=True,
128129
)
129130

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: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
"""
2+
The Pattern class for specifying GMT filling patterns.
3+
"""
4+
5+
import dataclasses
6+
7+
from pygmt.alias import Alias
8+
from pygmt.exceptions import GMTValueError
9+
from pygmt.params.base import BaseParam
10+
11+
__doctest_skip__ = ["Pattern"]
12+
13+
14+
@dataclasses.dataclass(repr=False)
15+
class Pattern(BaseParam):
16+
"""
17+
Class for GMT filling patterns.
18+
19+
Parameters
20+
----------
21+
id
22+
The pattern identifier. It can be in two forms:
23+
24+
- An integer in the range 1-90, corresponding to one of
25+
:doc:`the 90 predefined 64x64 bit-patterns </techref/patterns>` provided with
26+
GMT.
27+
- The name of a 1-, 8-, or 24-bit image raster file, to create customized,
28+
repeating images using image raster files.
29+
dpi
30+
Resolution of the pattern in dots per inch (DPI) [Default is 1200].
31+
bgcolor/fgcolor
32+
The background/foreground color for predefined bit-patterns or 1-bit images.
33+
[Default is white for background and black for foreground]. Setting either to
34+
an empty string will yield a transparent background/foreground where only the
35+
foreground or background pixels will be painted.
36+
reversed
37+
If True, the pattern will be bit-reversed, i.e., white and black areas will be
38+
interchanged (only applies to 1-bit images or predefined bit-image patterns).
39+
40+
Examples
41+
--------
42+
Draw a global map with land areas filled with pattern 15 in a light red background
43+
and 300 dpi resolution:
44+
45+
>>> import pygmt
46+
>>> from pygmt.params import Pattern
47+
>>> fig = pygmt.Figure()
48+
>>> fig.coast(
49+
... region="g",
50+
... projection="H10c",
51+
... frame=True,
52+
... land=Pattern(15, bgcolor="lightred", dpi=300),
53+
... shorelines=True,
54+
... )
55+
>>> fig.show()
56+
"""
57+
58+
id: int | str
59+
dpi: int | None = None
60+
bgcolor: str | None = None
61+
fgcolor: str | None = None
62+
reversed: bool = False
63+
64+
def _validate(self):
65+
"""
66+
Validate the parameters.
67+
"""
68+
if isinstance(self.id, int) and not (1 <= self.id <= 90):
69+
raise GMTValueError(
70+
self.id,
71+
description="pattern id",
72+
reason=(
73+
"Pattern id must be an integer in the range 1-90 "
74+
"or the name of a 1-, 8-, or 24-bit image raster file."
75+
),
76+
)
77+
78+
@property
79+
def _aliases(self):
80+
"""
81+
Aliases for the Pattern class.
82+
"""
83+
return [
84+
Alias(self.id, name="id", prefix="P" if self.reversed else "p"),
85+
Alias(self.bgcolor, name="bgcolor", prefix="+b"),
86+
Alias(self.fgcolor, name="fgcolor", prefix="+f"),
87+
Alias(self.dpi, name="dpi", prefix="+r"),
88+
]

pygmt/tests/test_params_pattern.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
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(id=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, reversed=True)) == "P50"
28+
29+
pattern = Pattern(90, reversed=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_id():
37+
"""
38+
Test that an invalid pattern id raises a GMTValueError.
39+
"""
40+
with pytest.raises(GMTValueError):
41+
_ = str(Pattern(91))
42+
with pytest.raises(GMTValueError):
43+
_ = str(Pattern(0))

0 commit comments

Comments
 (0)