diff --git a/doc/api/index.rst b/doc/api/index.rst index ccb049f996a..a8cad413095 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -212,6 +212,7 @@ Class-style Parameters :toctree: generated Box + Pattern Enums ----- diff --git a/doc/techref/patterns.md b/doc/techref/patterns.md index ee53a5c87ee..bbbc6f8b0f4 100644 --- a/doc/techref/patterns.md +++ b/doc/techref/patterns.md @@ -1,24 +1,7 @@ -# Bit and Hachure Patterns +# 90 Predefined Bit and Hachure Patterns PyGMT supports a variety of bit and hachure patterns that can be used to fill polygons. - -These patterns can be defined using the following syntax: - -**P**|**p**_pattern_[**+b**_color_][**+f**_color_][**+r**_dpi_] - -*pattern* can either be a number in the range 1-90 or the name of a 1-, 8-, or 24-bit -image raster file. The former will result in one of the 90 predefined 64x64 bit-patterns -provided by GMT (see the figure below). The latter allows the user to create customized, -repeating images using image raster files. - -By specifying uppercase **P** instead of **p** the image will be bit-reversed, i.e., -white and black areas will be interchanged (only applies to 1-bit images or predefined -bit-image patterns). For these patterns and other 1-bit images one may specify -alternative **b**ackground and **f**oreground colors (by appending **+b**_color_ and/or -**+f**_color_) that will replace the default white and black pixels, respectively. -Excluding *color* from a fore- or background specification yields a transparent image -where only the back- or foreground pixels will be painted. The **+r**_dpi_ modifier sets -the resolution in dpi. +These patterns can be specified via the {class}`pygmt.params.Pattern` class. The image below shows the 90 predefined bit patterns that can be used in PyGMT. diff --git a/examples/gallery/symbols/patterns.py b/examples/gallery/symbols/patterns.py index a518ac7f034..adff7c35034 100644 --- a/examples/gallery/symbols/patterns.py +++ b/examples/gallery/symbols/patterns.py @@ -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: @@ -20,33 +20,33 @@ - :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 +from 1 to 90. For a complete list of available patterns refer to the :doc:`/techref/patterns`. + +In PyGMT, 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. 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() diff --git a/examples/tutorials/advanced/cartesian_histograms.py b/examples/tutorials/advanced/cartesian_histograms.py index ad32abfc6e2..5f089227bc5 100644 --- a/examples/tutorials/advanced/cartesian_histograms.py +++ b/examples/tutorials/advanced/cartesian_histograms.py @@ -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: @@ -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"), pen="1p,darkgray,solid", histtype=0, # Show cumulative counts diff --git a/examples/tutorials/advanced/focal_mechanisms.py b/examples/tutorials/advanced/focal_mechanisms.py index b44286b6397..9c9eaa45145 100644 --- a/examples/tutorials/advanced/focal_mechanisms.py +++ b/examples/tutorials/advanced/focal_mechanisms.py @@ -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] @@ -122,8 +123,8 @@ longitude=2, latitude=0, depth=0, - compressionfill="p8", - extensionfill="p31", + compressionfill=Pattern(8), + extensionfill=Pattern(31), outline=True, ) diff --git a/pygmt/params/__init__.py b/pygmt/params/__init__.py index f2904afba94..b80b921407a 100644 --- a/pygmt/params/__init__.py +++ b/pygmt/params/__init__.py @@ -3,3 +3,4 @@ """ from pygmt.params.box import Box +from pygmt.params.pattern import Pattern diff --git a/pygmt/params/pattern.py b/pygmt/params/pattern.py new file mode 100644 index 00000000000..1b389463c81 --- /dev/null +++ b/pygmt/params/pattern.py @@ -0,0 +1,88 @@ +""" +The Pattern class for specifying GMT filling patterns. +""" + +import dataclasses + +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 GMT filling patterns. + + Parameters + ---------- + id + The pattern identifier. It can be in two forms: + + - An integer in the range 1-90, corresponding to one of + :doc:`the 90 predefined 64x64 bit-patterns ` provided with + GMT. + - The 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 + 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 + If True, the pattern will be bit-reversed, i.e., white and black areas will be + interchanged (only applies to 1-bit images or predefined bit-image patterns). + + 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 | str + dpi: int | None = None + bgcolor: str | None = None + fgcolor: str | None = None + reversed: bool = False + + def _validate(self): + """ + Validate the parameters. + """ + 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." + ), + ) + + @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"), + ] diff --git a/pygmt/tests/test_params_pattern.py b/pygmt/tests/test_params_pattern.py new file mode 100644 index 00000000000..41ec4468859 --- /dev/null +++ b/pygmt/tests/test_params_pattern.py @@ -0,0 +1,43 @@ +""" +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))