Skip to content

Commit 10795da

Browse files
committed
Figure.paragraph: Initial implementation focusing on input data
1 parent 36cd86e commit 10795da

8 files changed

+177
-0
lines changed

doc/api/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ Plotting map elements
3131
Figure.inset
3232
Figure.legend
3333
Figure.logo
34+
Figure.paragraph
3435
Figure.solar
3536
Figure.text
3637
Figure.timestamp

pygmt/figure.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,7 @@ def _repr_html_(self) -> str:
445445
legend,
446446
logo,
447447
meca,
448+
paragraph,
448449
plot,
449450
plot3d,
450451
psconvert,

pygmt/src/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
from pygmt.src.makecpt import makecpt
3939
from pygmt.src.meca import meca
4040
from pygmt.src.nearneighbor import nearneighbor
41+
from pygmt.src.paragraph import paragraph
4142
from pygmt.src.plot import plot
4243
from pygmt.src.plot3d import plot3d
4344
from pygmt.src.project import project

pygmt/src/paragraph.py

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
"""
2+
paragraph - Typeset one or multiple paragraphs.
3+
"""
4+
5+
import io
6+
from collections.abc import Sequence
7+
from typing import Literal
8+
9+
from pygmt._typing import AnchorCode
10+
from pygmt.alias import Alias, AliasSystem
11+
from pygmt.clib import Session
12+
from pygmt.exceptions import GMTValueError
13+
from pygmt.helpers import (
14+
_check_encoding,
15+
build_arg_list,
16+
is_nonstr_iter,
17+
non_ascii_to_octal,
18+
)
19+
20+
21+
def paragraph(
22+
self,
23+
x: float | str,
24+
y: float | str,
25+
text: str | Sequence[str],
26+
parwidth: float | str,
27+
linespacing: float | str,
28+
font: float | str | None = None,
29+
angle: float | None = None,
30+
justify: AnchorCode | None = None,
31+
alignment: Literal["left", "center", "right", "justified"] = "left",
32+
):
33+
"""
34+
Typeset one or multiple paragraphs.
35+
36+
Parameters
37+
----------
38+
x/y
39+
The x, y coordinates of the paragraph.
40+
text
41+
The paragraph text to typeset. If a sequence of strings is provided, each string
42+
is treated as a separate paragraph.
43+
parwidth
44+
The width of the paragraph.
45+
linespacing
46+
The spacing between lines.
47+
font
48+
The font of the text.
49+
angle
50+
The angle of the text.
51+
justify
52+
The justification of the block of text, relative to the given x, y position.
53+
alignment
54+
The alignment of the text. Valid values are ``"left"``, ``"center"``,
55+
``"right"``, and ``"justified"``.
56+
"""
57+
self._activate_figure()
58+
59+
_valid_alignments = {"left", "center", "right", "justified"}
60+
if alignment not in _valid_alignments:
61+
raise GMTValueError(
62+
alignment,
63+
description="value for parameter 'alignment'",
64+
choices=_valid_alignments,
65+
)
66+
67+
aliasdict = AliasSystem(
68+
F=[
69+
Alias(font, name="font", prefix="+f"),
70+
Alias(angle, name="angle", prefix="+a"),
71+
Alias(justify, name="justify", prefix="+j"),
72+
]
73+
).merge({"M": True})
74+
75+
confdict = {}
76+
# Prepare the text string that will be passed to an io.StringIO object.
77+
# Multiple paragraphs are separated by a blank line "\n\n".
78+
_textstr: str = "\n\n".join(text) if is_nonstr_iter(text) else str(text)
79+
# Check the encoding of the text string and convert it to octal if necessary.
80+
if (encoding := _check_encoding(_textstr)) != "ascii":
81+
_textstr = non_ascii_to_octal(_textstr, encoding=encoding)
82+
confdict["PS_CHAR_ENCODING"] = encoding
83+
84+
with Session() as lib:
85+
with io.StringIO() as buffer: # Prepare the StringIO input.
86+
buffer.write(f"> {x} {y} {linespacing} {parwidth} {alignment[0]}\n")
87+
buffer.write(_textstr)
88+
with lib.virtualfile_in(data=buffer) as vfile:
89+
lib.call_module(
90+
"text",
91+
args=build_arg_list(aliasdict, infile=vfile, confdict=confdict),
92+
)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: c5b1df47e811475defb0db79e49cab3d
3+
size: 27632
4+
hash: md5
5+
path: test_paragraph.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 0df1eb71a781f0b8cc7c48be860dd321
3+
size: 29109
4+
hash: md5
5+
path: test_paragraph_multiple_paragraphs_blankline.png
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
outs:
2+
- md5: 167d4be24bca4e287b2056ecbfbb629a
3+
size: 29076
4+
hash: md5
5+
path: test_paragraph_multiple_paragraphs_list.png

pygmt/tests/test_paragraph.py

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""
2+
Tests for Figure.paragraph.
3+
"""
4+
5+
import pytest
6+
from pygmt import Figure
7+
8+
9+
@pytest.mark.mpl_image_compare
10+
def test_paragraph():
11+
"""
12+
Test typesetting a single paragraph.
13+
"""
14+
fig = Figure()
15+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
16+
fig.paragraph(
17+
x=4,
18+
y=4,
19+
text="This is a long paragraph. " * 10,
20+
parwidth="5c",
21+
linespacing="12p",
22+
)
23+
return fig
24+
25+
26+
@pytest.mark.mpl_image_compare
27+
def test_paragraph_multiple_paragraphs_list():
28+
"""
29+
Test typesetting a single paragraph.
30+
"""
31+
fig = Figure()
32+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
33+
fig.paragraph(
34+
x=4,
35+
y=4,
36+
text=[
37+
"This is the first paragraph. " * 5,
38+
"This is the second paragraph. " * 5,
39+
],
40+
parwidth="5c",
41+
linespacing="12p",
42+
)
43+
return fig
44+
45+
46+
@pytest.mark.mpl_image_compare
47+
def test_paragraph_multiple_paragraphs_blankline():
48+
"""
49+
Test typesetting a single paragraph.
50+
"""
51+
text = """
52+
This is the first paragraph.
53+
This is the first paragraph.
54+
This is the first paragraph.
55+
This is the first paragraph.
56+
This is the first paragraph.
57+
58+
This is the second paragraph.
59+
This is the second paragraph.
60+
This is the second paragraph.
61+
This is the second paragraph.
62+
This is the second paragraph.
63+
"""
64+
fig = Figure()
65+
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
66+
fig.paragraph(x=4, y=4, text=text, parwidth="5c", linespacing="12p")
67+
return fig

0 commit comments

Comments
 (0)