From 961785d246f4950a6e2ee5a4991f83078383159e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 24 Oct 2024 17:20:40 +0800 Subject: [PATCH 01/13] Figure.paragraph: Initial implementation focusing on input data --- doc/api/index.rst | 1 + pygmt/figure.py | 1 + pygmt/src/__init__.py | 1 + pygmt/src/paragraph.py | 122 ++++++++++++++++++ pygmt/src/text.py | 3 + pygmt/tests/baseline/test_paragraph.png.dvc | 5 + ...raph_multiple_paragraphs_blankline.png.dvc | 5 + ...paragraph_multiple_paragraphs_list.png.dvc | 5 + pygmt/tests/test_paragraph.py | 67 ++++++++++ 9 files changed, 210 insertions(+) create mode 100644 pygmt/src/paragraph.py create mode 100644 pygmt/tests/baseline/test_paragraph.png.dvc create mode 100644 pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc create mode 100644 pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc create mode 100644 pygmt/tests/test_paragraph.py diff --git a/doc/api/index.rst b/doc/api/index.rst index 5824dc328e1..b0fc25798f3 100644 --- a/doc/api/index.rst +++ b/doc/api/index.rst @@ -34,6 +34,7 @@ Plotting map elements Figure.legend Figure.logo Figure.magnetic_rose + Figure.paragraph Figure.scalebar Figure.solar Figure.text diff --git a/pygmt/figure.py b/pygmt/figure.py index 136f0008b9b..1434b0d3779 100644 --- a/pygmt/figure.py +++ b/pygmt/figure.py @@ -426,6 +426,7 @@ def _repr_html_(self) -> str: logo, magnetic_rose, meca, + paragraph, plot, plot3d, psconvert, diff --git a/pygmt/src/__init__.py b/pygmt/src/__init__.py index 3c3fb2c39e3..8ac65a3a301 100644 --- a/pygmt/src/__init__.py +++ b/pygmt/src/__init__.py @@ -42,6 +42,7 @@ from pygmt.src.makecpt import makecpt from pygmt.src.meca import meca from pygmt.src.nearneighbor import nearneighbor +from pygmt.src.paragraph import paragraph from pygmt.src.plot import plot from pygmt.src.plot3d import plot3d from pygmt.src.project import project diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py new file mode 100644 index 00000000000..151bb3f4bf0 --- /dev/null +++ b/pygmt/src/paragraph.py @@ -0,0 +1,122 @@ +""" +paragraph - Typeset one or multiple paragraphs. +""" + +import io +from collections.abc import Sequence +from typing import Literal + +from pygmt._typing import AnchorCode +from pygmt.alias import Alias, AliasSystem +from pygmt.clib import Session +from pygmt.exceptions import GMTValueError +from pygmt.helpers import ( + _check_encoding, + build_arg_list, + is_nonstr_iter, + non_ascii_to_octal, +) + +__doctest_skip__ = ["paragraph"] + + +def paragraph( + self, + x: float | str, + y: float | str, + text: str | Sequence[str], + parwidth: float | str, + linespacing: float | str, + font: float | str | None = None, + angle: float | None = None, + justify: AnchorCode | None = None, + alignment: Literal["left", "center", "right", "justified"] = "left", +): + """ + Typeset one or multiple paragraphs. + + This method typesets one or multiple paragraphs of text at a given position on the + figure. The text is flowed within a given paragraph width and with a specified line + spacing. The text can be aligned left, center, right, or justified. + + Multiple paragraphs can be provided as a sequence of strings, where each string + represents a separate paragraph, or as a single string with newline characters + separating the paragraphs. + + Full GMT docs at :gmt-docs:`text.html`. + + Parameters + ---------- + x/y + The x, y coordinates of the paragraph. + text + The paragraph text to typeset. If a sequence of strings is provided, each string + is treated as a separate paragraph. + parwidth + The width of the paragraph. + linespacing + The spacing between lines. + font + The font of the text. + angle + The angle of the text. + justify + Set the alignment of the block of text, relative to the given x, y position. + Choose a :doc:`2-character justification code `. + alignment + Set the alignment of the text. Valid values are ``"left"``, ``"center"``, + ``"right"``, and ``"justified"``. + + Examples + -------- + >>> import pygmt + >>> + >>> fig = pygmt.Figure() + >>> fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + >>> fig.paragraph( + ... x=4, + ... y=4, + ... text="This is a long paragraph. " * 10, + ... parwidth="5c", + ... linespacing="12p", + ... font="12p", + ... ) + >>> fig.show() + """ + self._activate_figure() + + _valid_alignments = {"left", "center", "right", "justified"} + if alignment not in _valid_alignments: + raise GMTValueError( + alignment, + description="value for parameter 'alignment'", + choices=_valid_alignments, + ) + + aliasdict = AliasSystem( + F=[ + Alias(font, name="font", prefix="+f"), + Alias(angle, name="angle", prefix="+a"), + Alias(justify, name="justify", prefix="+j"), + ] + ) + aliasdict.merge({"M": True}) + + confdict = {} + # Prepare the text string that will be passed to an io.StringIO object. + # Multiple paragraphs are separated by a blank line "\n\n". + _textstr: str = "\n\n".join(text) if is_nonstr_iter(text) else str(text) + # Check the encoding of the text string and convert it to octal if necessary. + if (encoding := _check_encoding(_textstr)) != "ascii": + _textstr = non_ascii_to_octal(_textstr, encoding=encoding) + confdict["PS_CHAR_ENCODING"] = encoding + + with Session() as lib: + with io.StringIO() as buffer: # Prepare the StringIO input. + buffer.write(f"> {x} {y} {linespacing} {parwidth} {alignment[0]}\n") + buffer.write(_textstr) + with lib.virtualfile_in(data=buffer) as vfile: + lib.call_module( + "text", + args=build_arg_list(aliasdict, infile=vfile, confdict=confdict), + ) diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 8557feab311..9d9c35eb6a8 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -69,6 +69,9 @@ def text_( # noqa: PLR0912, PLR0913 ZapfDingbats and ISO-8859-x (x can be 1-11, 13-16) encodings. Refer to :doc:`/techref/encodings` for the full list of supported non-ASCII characters. + For typesetting one or multiple paragraphs of text, see + :meth:`pygmt.Figure.paragraph`. + Full GMT docs at :gmt-docs:`text.html`. $aliases diff --git a/pygmt/tests/baseline/test_paragraph.png.dvc b/pygmt/tests/baseline/test_paragraph.png.dvc new file mode 100644 index 00000000000..82906933e1d --- /dev/null +++ b/pygmt/tests/baseline/test_paragraph.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: c5b1df47e811475defb0db79e49cab3d + size: 27632 + hash: md5 + path: test_paragraph.png diff --git a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc new file mode 100644 index 00000000000..a131677880d --- /dev/null +++ b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 0df1eb71a781f0b8cc7c48be860dd321 + size: 29109 + hash: md5 + path: test_paragraph_multiple_paragraphs_blankline.png diff --git a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc new file mode 100644 index 00000000000..879799cc5db --- /dev/null +++ b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 167d4be24bca4e287b2056ecbfbb629a + size: 29076 + hash: md5 + path: test_paragraph_multiple_paragraphs_list.png diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py new file mode 100644 index 00000000000..2193dc1384a --- /dev/null +++ b/pygmt/tests/test_paragraph.py @@ -0,0 +1,67 @@ +""" +Tests for Figure.paragraph. +""" + +import pytest +from pygmt import Figure + + +@pytest.mark.mpl_image_compare +def test_paragraph(): + """ + Test typesetting a single paragraph. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.paragraph( + x=4, + y=4, + text="This is a long paragraph. " * 10, + parwidth="5c", + linespacing="12p", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_paragraph_multiple_paragraphs_list(): + """ + Test typesetting a single paragraph. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.paragraph( + x=4, + y=4, + text=[ + "This is the first paragraph. " * 5, + "This is the second paragraph. " * 5, + ], + parwidth="5c", + linespacing="12p", + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_paragraph_multiple_paragraphs_blankline(): + """ + Test typesetting a single paragraph. + """ + text = """ +This is the first paragraph. +This is the first paragraph. +This is the first paragraph. +This is the first paragraph. +This is the first paragraph. + +This is the second paragraph. +This is the second paragraph. +This is the second paragraph. +This is the second paragraph. +This is the second paragraph. +""" + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.paragraph(x=4, y=4, text=text, parwidth="5c", linespacing="12p") + return fig From 3d3466352b503050db3dabc192b7d745b6e7516f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:37:10 +0800 Subject: [PATCH 02/13] Update pygmt/src/text.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- pygmt/src/text.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/text.py b/pygmt/src/text.py index 9d9c35eb6a8..0e76738b39b 100644 --- a/pygmt/src/text.py +++ b/pygmt/src/text.py @@ -69,7 +69,7 @@ def text_( # noqa: PLR0912, PLR0913 ZapfDingbats and ISO-8859-x (x can be 1-11, 13-16) encodings. Refer to :doc:`/techref/encodings` for the full list of supported non-ASCII characters. - For typesetting one or multiple paragraphs of text, see + For typesetting one or more paragraphs of text, see :meth:`pygmt.Figure.paragraph`. Full GMT docs at :gmt-docs:`text.html`. From 4da6010a8ddaf0e2699dea11d1eb98b1ccc5f8b6 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:33:27 +0800 Subject: [PATCH 03/13] Merge two tests into one --- ...est_paragraph_multiple_paragraphs.png.dvc} | 2 +- ...raph_multiple_paragraphs_blankline.png.dvc | 5 -- pygmt/tests/test_paragraph.py | 46 +++++++------------ 3 files changed, 17 insertions(+), 36 deletions(-) rename pygmt/tests/baseline/{test_paragraph_multiple_paragraphs_list.png.dvc => test_paragraph_multiple_paragraphs.png.dvc} (58%) delete mode 100644 pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc diff --git a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs.png.dvc similarity index 58% rename from pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc rename to pygmt/tests/baseline/test_paragraph_multiple_paragraphs.png.dvc index 879799cc5db..664e741540b 100644 --- a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_list.png.dvc +++ b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs.png.dvc @@ -2,4 +2,4 @@ outs: - md5: 167d4be24bca4e287b2056ecbfbb629a size: 29076 hash: md5 - path: test_paragraph_multiple_paragraphs_list.png + path: test_paragraph_multiple_paragraphs.png diff --git a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc b/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc deleted file mode 100644 index a131677880d..00000000000 --- a/pygmt/tests/baseline/test_paragraph_multiple_paragraphs_blankline.png.dvc +++ /dev/null @@ -1,5 +0,0 @@ -outs: -- md5: 0df1eb71a781f0b8cc7c48be860dd321 - size: 29109 - hash: md5 - path: test_paragraph_multiple_paragraphs_blankline.png diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py index 2193dc1384a..78573298c5b 100644 --- a/pygmt/tests/test_paragraph.py +++ b/pygmt/tests/test_paragraph.py @@ -24,44 +24,30 @@ def test_paragraph(): @pytest.mark.mpl_image_compare -def test_paragraph_multiple_paragraphs_list(): +@pytest.mark.parametrize("inputtype", ["list", "string"]) +def test_paragraph_multiple_paragraphs(inputtype): """ - Test typesetting a single paragraph. + Test typesetting multiple paragraphs. """ + if inputtype == "list": + text = [ + "This is the first paragraph. " * 5, + "This is the second paragraph. " * 5, + ] + else: + text = ( + "This is the first paragraph.\n" * 5 + + "\n\n" + + "This is the second paragraph.\n" * 5 + ) + fig = Figure() fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) fig.paragraph( x=4, y=4, - text=[ - "This is the first paragraph. " * 5, - "This is the second paragraph. " * 5, - ], + text=text, parwidth="5c", linespacing="12p", ) return fig - - -@pytest.mark.mpl_image_compare -def test_paragraph_multiple_paragraphs_blankline(): - """ - Test typesetting a single paragraph. - """ - text = """ -This is the first paragraph. -This is the first paragraph. -This is the first paragraph. -This is the first paragraph. -This is the first paragraph. - -This is the second paragraph. -This is the second paragraph. -This is the second paragraph. -This is the second paragraph. -This is the second paragraph. -""" - fig = Figure() - fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) - fig.paragraph(x=4, y=4, text=text, parwidth="5c", linespacing="12p") - return fig From 539fd0c2c4b758e39f05c01b8a4ac994a47f263f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:37:27 +0800 Subject: [PATCH 04/13] Apply copilot suggestions --- pygmt/src/paragraph.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index 151bb3f4bf0..db58a2016d8 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -32,7 +32,7 @@ def paragraph( justify: AnchorCode | None = None, alignment: Literal["left", "center", "right", "justified"] = "left", ): - """ + r""" Typeset one or multiple paragraphs. This method typesets one or multiple paragraphs of text at a given position on the @@ -40,7 +40,7 @@ def paragraph( spacing. The text can be aligned left, center, right, or justified. Multiple paragraphs can be provided as a sequence of strings, where each string - represents a separate paragraph, or as a single string with newline characters + represents a separate paragraph, or as a single string with a blank line (``\n\n``) separating the paragraphs. Full GMT docs at :gmt-docs:`text.html`. @@ -106,6 +106,14 @@ def paragraph( # Prepare the text string that will be passed to an io.StringIO object. # Multiple paragraphs are separated by a blank line "\n\n". _textstr: str = "\n\n".join(text) if is_nonstr_iter(text) else str(text) + + if _textstr == "": + raise GMTValueError( + text, + description="text", + reason="'text' must be a non-empty string or sequence of strings.", + ) + # Check the encoding of the text string and convert it to octal if necessary. if (encoding := _check_encoding(_textstr)) != "ascii": _textstr = non_ascii_to_octal(_textstr, encoding=encoding) From 4a0552c09865595a5936eefa1ce154187629fd33 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:42:38 +0800 Subject: [PATCH 05/13] Use a tuple instead of a set --- pygmt/src/paragraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index db58a2016d8..f7b7e60db8c 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -85,7 +85,7 @@ def paragraph( """ self._activate_figure() - _valid_alignments = {"left", "center", "right", "justified"} + _valid_alignments = ("left", "center", "right", "justified") if alignment not in _valid_alignments: raise GMTValueError( alignment, From 83218fda1e04729e56334a9fc2517188b35a50b1 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:43:45 +0800 Subject: [PATCH 06/13] Need to set filename in tests --- pygmt/tests/test_paragraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py index 78573298c5b..69da92e622c 100644 --- a/pygmt/tests/test_paragraph.py +++ b/pygmt/tests/test_paragraph.py @@ -23,7 +23,7 @@ def test_paragraph(): return fig -@pytest.mark.mpl_image_compare +@pytest.mark.mpl_image_compare(filename="test_paragraph_multiple_paragraphs.png") @pytest.mark.parametrize("inputtype", ["list", "string"]) def test_paragraph_multiple_paragraphs(inputtype): """ From d11a9be5984c09f8fd1c0b929d67ba16587f405c Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 18:53:20 +0800 Subject: [PATCH 07/13] Fix a test --- pygmt/tests/test_paragraph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py index 69da92e622c..387007cdcaa 100644 --- a/pygmt/tests/test_paragraph.py +++ b/pygmt/tests/test_paragraph.py @@ -36,9 +36,9 @@ def test_paragraph_multiple_paragraphs(inputtype): ] else: text = ( - "This is the first paragraph.\n" * 5 - + "\n\n" - + "This is the second paragraph.\n" * 5 + "This is the first paragraph. \n" * 5 + + "\n" # Separate the paragraphs with a blank line. + + "This is the second paragraph. \n" * 5 ) fig = Figure() From bce2d2d28cb1793ce377e2a0b550c9e1f42ed49f Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 19:16:29 +0800 Subject: [PATCH 08/13] Add two more tests --- .../baseline/test_paragraph_alignment.png.dvc | 5 +++ .../test_paragraph_font_angle_justify.png.dvc | 5 +++ pygmt/tests/test_paragraph.py | 44 +++++++++++++++++++ 3 files changed, 54 insertions(+) create mode 100644 pygmt/tests/baseline/test_paragraph_alignment.png.dvc create mode 100644 pygmt/tests/baseline/test_paragraph_font_angle_justify.png.dvc diff --git a/pygmt/tests/baseline/test_paragraph_alignment.png.dvc b/pygmt/tests/baseline/test_paragraph_alignment.png.dvc new file mode 100644 index 00000000000..4a148fac914 --- /dev/null +++ b/pygmt/tests/baseline/test_paragraph_alignment.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: a0ef6e989b11a252ec2a7ef497f3c789 + size: 36274 + hash: md5 + path: test_paragraph_alignment.png diff --git a/pygmt/tests/baseline/test_paragraph_font_angle_justify.png.dvc b/pygmt/tests/baseline/test_paragraph_font_angle_justify.png.dvc new file mode 100644 index 00000000000..483021faf2d --- /dev/null +++ b/pygmt/tests/baseline/test_paragraph_font_angle_justify.png.dvc @@ -0,0 +1,5 @@ +outs: +- md5: 6f55167eb6bc626b2bfee89ffe73faad + size: 48604 + hash: md5 + path: test_paragraph_font_angle_justify.png diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py index 387007cdcaa..f1723c02535 100644 --- a/pygmt/tests/test_paragraph.py +++ b/pygmt/tests/test_paragraph.py @@ -51,3 +51,47 @@ def test_paragraph_multiple_paragraphs(inputtype): linespacing="12p", ) return fig + + +@pytest.mark.mpl_image_compare +def test_paragraph_alignment(): + """ + Test typesetting a single paragraph with different alignments. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 8], projection="X10c/8c", frame=True) + for x, y, alignment in [ + (5, 1, "left"), + (5, 3, "right"), + (5, 5, "center"), + (5, 7, "justified"), + ]: + fig.paragraph( + x=x, + y=y, + text= alignment.upper() + " : " + "This is a long paragraph. " * 5, + parwidth="8c", + linespacing="12p", + alignment=alignment, + ) + return fig + + +@pytest.mark.mpl_image_compare +def test_paragraph_font_angle_justify(): + """ + Test typesetting a single paragraph with font, angle, and justify options. + """ + fig = Figure() + fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True) + fig.paragraph( + x=1, + y=4, + text="This is a long paragraph. " * 10, + parwidth="8c", + linespacing="12p", + font="10p,Helvetica-Bold,red", + angle=45, + justify="TL", + ) + return fig From 815b69bea26420e097e6c0b6a30f6b409465845e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 19:46:35 +0800 Subject: [PATCH 09/13] Fix type hints for font --- pygmt/src/paragraph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index f7b7e60db8c..b633df4b1b4 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -27,7 +27,7 @@ def paragraph( text: str | Sequence[str], parwidth: float | str, linespacing: float | str, - font: float | str | None = None, + font: str | None = None, angle: float | None = None, justify: AnchorCode | None = None, alignment: Literal["left", "center", "right", "justified"] = "left", From 9589ecffe40b8bafe22aa727c528b9ffba87954d Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Thu, 2 Apr 2026 19:47:50 +0800 Subject: [PATCH 10/13] Fix test --- pygmt/tests/test_paragraph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/pygmt/tests/test_paragraph.py b/pygmt/tests/test_paragraph.py index f1723c02535..ed25f0d2e58 100644 --- a/pygmt/tests/test_paragraph.py +++ b/pygmt/tests/test_paragraph.py @@ -36,9 +36,9 @@ def test_paragraph_multiple_paragraphs(inputtype): ] else: text = ( - "This is the first paragraph. \n" * 5 - + "\n" # Separate the paragraphs with a blank line. - + "This is the second paragraph. \n" * 5 + "This is the first paragraph. " * 5 + + "\n\n" # Separate the paragraphs with a blank line. + + "This is the second paragraph. " * 5 ) fig = Figure() @@ -69,7 +69,7 @@ def test_paragraph_alignment(): fig.paragraph( x=x, y=y, - text= alignment.upper() + " : " + "This is a long paragraph. " * 5, + text=alignment.upper() + " : " + "This is a long paragraph. " * 5, parwidth="8c", linespacing="12p", alignment=alignment, From 42c617a91eb074bafe6ce3f92aa9fa101dd6a779 Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Apr 2026 10:48:54 +0800 Subject: [PATCH 11/13] Add support for -G and -W --- pygmt/src/paragraph.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index b633df4b1b4..ce32a7f5d08 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -20,7 +20,7 @@ __doctest_skip__ = ["paragraph"] -def paragraph( +def paragraph( # noqa: PLR0913 self, x: float | str, y: float | str, @@ -30,6 +30,8 @@ def paragraph( font: str | None = None, angle: float | None = None, justify: AnchorCode | None = None, + fill: str | None = None, + pen: str | None = None, alignment: Literal["left", "center", "right", "justified"] = "left", ): r""" @@ -63,6 +65,11 @@ def paragraph( justify Set the alignment of the block of text, relative to the given x, y position. Choose a :doc:`2-character justification code `. + fill + Set color for filling the paragraph box [Default is no fill]. + pen + Set the pen used to draw a rectangle around the paragraph [Default is + ``"0.25p,black,solid"``]. alignment Set the alignment of the text. Valid values are ``"left"``, ``"center"``, ``"right"``, and ``"justified"``. @@ -98,7 +105,9 @@ def paragraph( Alias(font, name="font", prefix="+f"), Alias(angle, name="angle", prefix="+a"), Alias(justify, name="justify", prefix="+j"), - ] + ], + G=fill, + W=pen, ) aliasdict.merge({"M": True}) From 569fd5d930bb09e9589cee99d08a4c4d2dc0037a Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Apr 2026 11:41:20 +0800 Subject: [PATCH 12/13] Fix the alias for -G and -W --- pygmt/src/paragraph.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index ce32a7f5d08..398a9cebc52 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -106,8 +106,8 @@ def paragraph( # noqa: PLR0913 Alias(angle, name="angle", prefix="+a"), Alias(justify, name="justify", prefix="+j"), ], - G=fill, - W=pen, + G=Alias(fill, name="fill"), + W=Alias(pen, name="pen"), ) aliasdict.merge({"M": True}) From d15d5fa02ca6af11741a98bb732c1a0a5c833c7e Mon Sep 17 00:00:00 2001 From: Dongdong Tian Date: Fri, 3 Apr 2026 12:01:30 +0800 Subject: [PATCH 13/13] Support options -V, -c and -t --- pygmt/src/paragraph.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pygmt/src/paragraph.py b/pygmt/src/paragraph.py index 398a9cebc52..94416b7ac7c 100644 --- a/pygmt/src/paragraph.py +++ b/pygmt/src/paragraph.py @@ -13,6 +13,7 @@ from pygmt.helpers import ( _check_encoding, build_arg_list, + fmt_docstring, is_nonstr_iter, non_ascii_to_octal, ) @@ -20,6 +21,7 @@ __doctest_skip__ = ["paragraph"] +@fmt_docstring def paragraph( # noqa: PLR0913 self, x: float | str, @@ -33,6 +35,10 @@ def paragraph( # noqa: PLR0913 fill: str | None = None, pen: str | None = None, alignment: Literal["left", "center", "right", "justified"] = "left", + verbose: Literal["quiet", "error", "warning", "timing", "info", "compat", "debug"] + | bool = False, + panel: int | Sequence[int] | bool = False, + transparency: float | Sequence[float] | bool | None = None, ): r""" Typeset one or multiple paragraphs. @@ -73,6 +79,9 @@ def paragraph( # noqa: PLR0913 alignment Set the alignment of the text. Valid values are ``"left"``, ``"center"``, ``"right"``, and ``"justified"``. + $verbose + $panel + $transparency Examples -------- @@ -108,6 +117,10 @@ def paragraph( # noqa: PLR0913 ], G=Alias(fill, name="fill"), W=Alias(pen, name="pen"), + ).add_common( + V=verbose, + c=panel, + t=transparency, ) aliasdict.merge({"M": True})