Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Figure.timestamp to plot the GMT timestamp logo #2208

Merged
merged 44 commits into from
Mar 3, 2023
Merged
Show file tree
Hide file tree
Changes from 42 commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
c8206ec
Add Figure.timestamp to plot the GMT timestamp logo
seisman Nov 24, 2022
2096760
Merge branch 'main' into timestamp
seisman Jan 1, 2023
e0bb6bb
Add Figure.timestamp to the API reference documentation
seisman Jan 1, 2023
4d6d777
Improve the timestamp function
seisman Jan 1, 2023
fffe3cd
Add the timefmt parmater to set time timestamp format
seisman Jan 1, 2023
54f4307
Fix formattingg
seisman Jan 1, 2023
fe8eeac
Merge branch 'main' into timestamp
seisman Jan 29, 2023
97574ac
Merge branch 'main' into timestamp
seisman Jan 30, 2023
1e8de17
Fix the configurations
seisman Jan 31, 2023
79460fb
Add the first test
seisman Jan 31, 2023
7031448
Add the first test
seisman Jan 31, 2023
7ad112c
Merge branch 'main' into timestamp
seisman Feb 4, 2023
b057ad8
Pass GMT parameters directly to the build_arg_string function
seisman Feb 4, 2023
f92d272
fix
seisman Feb 4, 2023
cecf6de
Merge branch 'main' into timestamp
seisman Feb 12, 2023
7dd74d7
Fix the dvc-diff workflow
seisman Feb 12, 2023
06049a6
Update the baseline image
seisman Feb 12, 2023
876d963
Revert "Fix the dvc-diff workflow"
seisman Feb 12, 2023
752273e
Fix the dvc-diff workflow
seisman Feb 12, 2023
f6795a9
Fix linting errors
seisman Feb 12, 2023
9eba2a9
Add more timestamp tests
seisman Feb 12, 2023
92c285a
Add a test for the 'text' parameter
seisman Feb 12, 2023
776981b
Fix docstrings
seisman Feb 12, 2023
c771183
Fix docstrings
seisman Feb 12, 2023
386882e
Add the missing r prefix
seisman Feb 12, 2023
e3580e7
Merge branch 'main' into timestamp
seisman Feb 12, 2023
41f5419
Check if the text string is longer than 64 characters
seisman Feb 13, 2023
4def360
Merge branch 'main' into timestamp
seisman Feb 14, 2023
bc64e8c
Fix docstring
seisman Feb 15, 2023
78c2307
Fix docstring
seisman Feb 15, 2023
e6248c2
Apply suggestions from code review
seisman Feb 15, 2023
d989b22
Merge branch 'main' into timestamp
michaelgrund Feb 15, 2023
960508f
Apply suggestions from code review
seisman Feb 16, 2023
16c0368
Merge branch 'main' into timestamp
seisman Feb 17, 2023
98b6762
Use the package-level variable __gmt_version__
seisman Feb 17, 2023
b8a7fbb
Merge branch 'main' into timestamp
seisman Feb 21, 2023
688f6b0
Fix formatting
seisman Feb 21, 2023
6e168e1
Merge branch 'main' into timestamp
seisman Feb 28, 2023
5857866
Use the __gmt_version__ variable
seisman Feb 28, 2023
01a3cbb
Merge branch 'main' into timestamp
seisman Feb 28, 2023
4fa2e72
Remove unused imports
seisman Mar 1, 2023
0c07a48
Minor changes to the docstrings
seisman Mar 1, 2023
e4bd94c
Update pygmt/src/timestamp.py
seisman Mar 2, 2023
0079643
Fix docstrings for font
seisman Mar 2, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions doc/api/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ Plotting map elements
Figure.logo
Figure.solar
Figure.text
Figure.timestamp

Plotting tabular data
~~~~~~~~~~~~~~~~~~~~~
Expand Down
1 change: 1 addition & 0 deletions pygmt/figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,7 @@ def _repr_html_(self):
subplot,
ternary,
text,
timestamp,
velo,
wiggle,
)
Expand Down
1 change: 1 addition & 0 deletions pygmt/src/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
from pygmt.src.surface import surface
from pygmt.src.ternary import ternary
from pygmt.src.text import text_ as text # "text" is an argument within "text_"
from pygmt.src.timestamp import timestamp
from pygmt.src.triangulate import triangulate
from pygmt.src.velo import velo
from pygmt.src.which import which
Expand Down
102 changes: 102 additions & 0 deletions pygmt/src/timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
"""
timestamp - Plot the GMT timestamp logo.
"""
from packaging.version import Version
from pygmt import __gmt_version__
from pygmt.clib import Session
from pygmt.exceptions import GMTInvalidInput
from pygmt.helpers import build_arg_string, is_nonstr_iter

__doctest_skip__ = ["timestamp"]


def timestamp(
self,
text=None,
label=None,
justification="BL",
offset=("-54p", "-54p"),
font="Helvetica,black",
timefmt="%Y %b %d %H:%M:%S",
):
r"""
Plot the GMT timestamp logo.
yvonnefroehlich marked this conversation as resolved.
Show resolved Hide resolved

Parameters
----------
text : None or str
If ``None``, the current UNIX timestamp is shown in the GMT timestamp
logo. Set this parameter to replace the UNIX timestamp with a
custom text string instead. The text must be less than 64 characters.
*Requires GMT>=6.5.0*.
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm still unsure what's the best way to document features that require new GMT versions.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm. Is there a similar situation for the Julia wrapper, which can serve as an orientation?

label : None or str
The text string shown after the GMT timestamp logo.
yvonnefroehlich marked this conversation as resolved.
Show resolved Hide resolved
justification : str
Justification of the timestamp. The *justification* is a two-character
code that is a combination of a horizontal (**L**\ (eft),
**C**\ (enter), or **R**\ (ight)) and a vertical (**T**\ (op),
**M**\ (iddle), or **B**\ (ottom)) code.
offset : str or tuple
*offset* or (*offset_x*, *offset_y*).
Offset the anchor point of the timestamp by *offset_x* and *offset_y*.
yvonnefroehlich marked this conversation as resolved.
Show resolved Hide resolved
If a single value *offset* is given, *offset_y* = *offset_x* =
*offset*.
font : str
Font of the timestamp and the optional label. The parameter can't
change the font color for GMT<=6.4.0.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should a GMTInvalidInput error be raised when users pass font as a parameter with GMT 6.3.0? Or is the error clear enough from GMT?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The font parameter works for any GMT versions.

For GMT>6.4.0, it can change both the font ID and font color, but for GMT<=6.4.0, the font color is always black so it can only change the font ID.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, maybe reword it into something like this?

Suggested change
Font of the timestamp and the optional label. The parameter can't
change the font color for GMT<=6.4.0.
Font of the timestamp and the optional label. The parameter can't
change the font color for GMT<=6.4.0, only the font label.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm afraid "font label" is incorrect here. It should be "font ID" or "font name".

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Owh, you mean like font style (e.g. Helvetica)? Feel free to edit the suggested changes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added "only the font ID" in 0079643.

seisman marked this conversation as resolved.
Show resolved Hide resolved
timefmt : str
Format string for the UNIX timestamp. The format string is parsed by
the C function ``strftime``, so that virtually any text can be used
(even not containing any time information).

Examples
--------
>>> # Plot the GMT timestamp logo.
yvonnefroehlich marked this conversation as resolved.
Show resolved Hide resolved
>>> import pygmt
>>> fig = pygmt.Figure()
>>> fig.timestamp()
>>> fig.show()
<IPython.core.display.Image object>

>>> # Plot the GMT timestamp logo with a custom label.
yvonnefroehlich marked this conversation as resolved.
Show resolved Hide resolved
>>> fig = pygmt.Figure()
>>> fig.timestamp(label="Powered by PyGMT")
>>> fig.show()
<IPython.core.display.Image object>
"""
self._preprocess() # pylint: disable=protected-access

# Build the options passed to the "plot" module
kwdict = {"T": True, "U": ""}
if label is not None:
kwdict["U"] += f"{label}"
kwdict["U"] += f"+j{justification}"

if is_nonstr_iter(offset): # given a tuple
kwdict["U"] += "+o" + "/".join(f"{item}" for item in offset)
else: # given a single value
if "/" not in offset and Version(__gmt_version__) <= Version("6.4.0"):
# Giving a single offset doesn't work in GMT <= 6.4.0.
# See https://github.com/GenericMappingTools/gmt/issues/7107.
kwdict["U"] += f"+o{offset}/{offset}"
else:
kwdict["U"] += f"+o{offset}"

# The +t modifier was added in GMT 6.5.0.
# See https://github.com/GenericMappingTools/gmt/pull/7127.
if text is not None:
if Version(__gmt_version__) <= Version("6.4.0"):
raise GMTInvalidInput("The parameter 'text' requires GMT>=6.5.0.")
seisman marked this conversation as resolved.
Show resolved Hide resolved
if len(str(text)) > 64:
raise GMTInvalidInput(
"The parameter 'text' must be less than 64 characters."
)
kwdict["U"] += f"+t{text}"

with Session() as lib:
lib.call_module(
module="plot",
args=build_arg_string(
kwdict, confdict={"FONT_LOGO": font, "FORMAT_TIME_STAMP": timefmt}
),
)
4 changes: 4 additions & 0 deletions pygmt/tests/baseline/test_timestamp.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
outs:
- md5: b4ca6cb54d80463606bb3d28eb077227
size: 1668
path: test_timestamp.png
4 changes: 4 additions & 0 deletions pygmt/tests/baseline/test_timestamp_font.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
outs:
- md5: e1c3c80a85ebaadc0a820bcc7954a539
size: 2840
path: test_timestamp_font.png
4 changes: 4 additions & 0 deletions pygmt/tests/baseline/test_timestamp_justification.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
outs:
- md5: c2bfca8624e4bf966eeb57bbc4aa0726
size: 8843
path: test_timestamp_justification.png
4 changes: 4 additions & 0 deletions pygmt/tests/baseline/test_timestamp_label.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
outs:
- md5: 868d6a5912b586bc7e095b9923ce8d83
size: 3036
path: test_timestamp_label.png
4 changes: 4 additions & 0 deletions pygmt/tests/baseline/test_timestamp_offset.png.dvc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
outs:
- md5: be0021731b881e32e3d8a46d6fa8abc3
size: 9450
path: test_timestamp_offset.png
88 changes: 88 additions & 0 deletions pygmt/tests/test_timestamp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
"""
Tests for Figure.timestamp.
"""
import pytest
from packaging.version import Version
from pygmt import Figure, __gmt_version__


@pytest.fixture(scope="module", name="faketime")
def fixture_faketime():
"""
Fake datetime that will be passed to the "timefmt" parameter, so that the
timestamp string always has a fixed value.
"""
return "9999-99-99T99:99:99"


@pytest.mark.mpl_image_compare
def test_timestamp(faketime):
"""
Test that the simplest timestamp() call works.
"""
fig = Figure()
fig.timestamp(timefmt=faketime)
return fig


@pytest.mark.mpl_image_compare
def test_timestamp_label(faketime):
"""
Check if the "label" parameter works.
"""
fig = Figure()
fig.timestamp(label="Powered by PyGMT", timefmt=faketime)
return fig


@pytest.mark.mpl_image_compare
def test_timestamp_justification():
"""
Check if the "justification" parameter works.

Only a subset of justification codes are tested to avoid overlapping
timestamps.
"""
fig = Figure()
fig.basemap(projection="X10c/5c", region=[0, 10, 0, 5], frame=0)
for just in ["BL", "BR", "TL", "TR"]:
fig.timestamp(justification=just, timefmt=just)
return fig
Comment on lines +39 to +50
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The output of the justification image doesn't look correct?

All of the timestamps are on the bottom left (BL). Shouldn't they be at the four different corners?

Copy link
Member Author

@seisman seisman Mar 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unlike other embellishments (e.g., inset, legend or direction rose), the timestamp logo is always located at the lower left corner of the map. I believe this is GMT's design.

See https://docs.generic-mapping-tools.org/dev/gmt.conf.html#term-MAP_LOGO_POS

Sets the justification and the position of the logo/timestamp box relative to the current plot’s lower left corner (i.e., map origin)

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting design choice 😅 Actually, I just realized I was thinking of position rather than justification. Maybe we should document the default offset="-54p/-54p" under the offset docstring?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The default values are clear from the function definition:

Figure.timestamp((text=None, label=None, justification='BL', offset=('-54p', '-54p'), font='Helvetica,black', timefmt='%Y %b %d %H:%M:%S')

I'm hesitate to document the defaults in the docstring.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah ok, was thinking of adding [Default is ('-54p', '-54p')], but it's not really needed I guess.



@pytest.mark.mpl_image_compare
def test_timestamp_offset():
"""
Check if the "offset" parameter works.
"""
fig = Figure()
fig.basemap(projection="X10c/5c", region=[0, 10, 0, 5], frame="g1")
for offset in ["1c", "1c/2c", ("1c", "3c")]:
fig.timestamp(offset=offset, timefmt=f"offset={offset}")
return fig


@pytest.mark.mpl_image_compare
def test_timestamp_font(faketime):
"""
Test if the "font" parameter works.
"""
fig = Figure()
fig.timestamp(font="Times-Roman", label="Powered by GMT", timefmt=faketime)
return fig


@pytest.mark.skipif(
Version(__gmt_version__) < Version("6.5.0"),
reason="The 'text' parameter requires GMT>=6.5.0",
)
@pytest.mark.mpl_image_compare(filename="test_timestamp.png")
def test_timestamp_text(faketime):
"""
Test if the "text" parameter works.

Requires GMT>=6.5.0.
"""
fig = Figure()
fig.timestamp(text=faketime)
return fig