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

Wrap text #321

Merged
merged 6 commits into from
Oct 31, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 @@ -32,6 +32,7 @@ Plotting data and laying out the map:
Figure.logo
Figure.image
Figure.shift_origin
Figure.text

Color palette table generation:

Expand Down
110 changes: 110 additions & 0 deletions pygmt/base_plotting.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,19 @@
Base class with plot generating commands.
Does not define any special non-GMT methods (savefig, show, etc).
"""
import csv
import os
import numpy as np
import pandas as pd

from .clib import Session
from .exceptions import GMTInvalidInput
from .helpers import (
build_arg_string,
dummy_context,
data_kind,
fmt_docstring,
GMTTempFile,
use_alias,
kwargs_to_strings,
)
Expand Down Expand Up @@ -574,3 +580,107 @@ def legend(self, spec=None, **kwargs):
raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec)))
arg_str = " ".join([specfile, build_arg_string(kwargs)])
lib.call_module("legend", arg_str)

@fmt_docstring
@use_alias(R="region", J="projection")
@kwargs_to_strings(
R="sequence",
textfiles="sequence_space",
angle="sequence_comma",
font="sequence_comma",
justify="sequence_comma",
)
def text(
self,
textfiles=None,
x=None,
y=None,
text=None,
angle=None,
font=None,
justify=None,
**kwargs,
):
"""
Plot or typeset text on maps

Used to be pstext.

Takes in textfile(s) or (x,y,text) triples as input.

Must provide at least *textfiles* or *x*, *y*, and *text*.

Full option list at :gmt-docs:`text.html`

{aliases}

Parameters
----------
textfiles : str or list
A text data file name, or a list of filenames containing 1 or more records
with (x, y[, font, angle, justify], text).
x, y : float or 1d arrays
weiji14 marked this conversation as resolved.
Show resolved Hide resolved
The x and y coordinates, or an array of x and y coordinates to plot the text
text : str or 1d array
The text string, or an array of strings to plot on the figure
angle: int/float or bool
Set the angle measured in degrees counter-clockwise from horizontal. E.g. 30
sets the text at 30 degrees. If no angle is given then the input textfile(s)
must have this as a column.
font : str or bool
Set the font specification with format "size,font,color" where size is text
size in points, font is the font to use, and color sets the font color. E.g.
"12p,Helvetica-Bold,red" selects a 12p red Helvetica-Bold font. If no font
info is given then the input textfile(s) must have this information in one
of its columns.
justify: str or bool
Set the alignment which refers to the part of the text string that will be
mapped onto the (x,y) point. Choose a 2 character combination of L, C, R
(for left, center, or right) and T, M, B for top, middle, or bottom. E.g.,
BL for lower left. If no justification is given then the input textfile(s)
must have this as a column.
{J}
{R}
"""
kwargs = self._preprocess(**kwargs)

kind = data_kind(textfiles, x, y, text)
if kind == "vectors" and text is None:
raise GMTInvalidInput("Must provide text with x and y.")
if kind == "file":
for textfile in textfiles.split(" "): # ensure that textfile(s) exist
if not os.path.exists(textfile):
raise GMTInvalidInput(f"Cannot find the file: {textfile}")

if angle is not None or font is not None or justify is not None:
if "F" not in kwargs.keys():
kwargs.update({"F": ""})
if angle is not None and isinstance(angle, (int, float)):
kwargs["F"] += f"+a{str(angle)}"
if font is not None and isinstance(font, str):
kwargs["F"] += f"+f{font}"
if justify is not None and isinstance(justify, str):
kwargs["F"] += f"+j{justify}"

with GMTTempFile(suffix=".txt") as tmpfile:
Copy link
Member

Choose a reason for hiding this comment

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

We should look into passing text though virtual files, actually. I was avoiding this a while ago because the API had changed but that has been standardized now in 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.

Sorry, I don't quite get your intention here? Do you mean passing text (as in the command) through virtual files or 'text' (as in plain old text)? Either way I'm confused...

with Session() as lib:
if kind == "file":
fname = textfiles
elif kind == "vectors":
pd.DataFrame.from_dict(
{
"x": np.atleast_1d(x),
"y": np.atleast_1d(y),
"text": np.atleast_1d(text),
}
).to_csv(
tmpfile.name,
sep="\t",
header=False,
index=False,
quoting=csv.QUOTE_NONE,
)
fname = tmpfile.name

arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("text", arg_str)
Binary file added pygmt/tests/baseline/test_text_angle_30.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added pygmt/tests/baseline/test_text_font_bold.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
5 changes: 5 additions & 0 deletions pygmt/tests/data/cities.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
105.87 21.02 LM HANOI
282.95 -12.1 LM LIMA
178.42 -18.13 LM SUVA
237.67 47.58 RM SEATTLE
28.20 -25.75 LM PRETORIA
173 changes: 173 additions & 0 deletions pygmt/tests/test_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
# pylint: disable=redefined-outer-name
"""
Tests text
"""
import os

import pytest

from .. import Figure
from ..exceptions import GMTInvalidInput

TEST_DATA_DIR = os.path.join(os.path.dirname(__file__), "data")
POINTS_DATA = os.path.join(TEST_DATA_DIR, "points.txt")
CITIES_DATA = os.path.join(TEST_DATA_DIR, "cities.txt")


@pytest.fixture(scope="module")
def projection():
"The projection system"
return "x4i"


@pytest.fixture(scope="module")
def region():
"The data region"
return [0, 5, 0, 2.5]


@pytest.mark.mpl_image_compare
def test_text_single_line_of_text(region, projection):
"""
Place a single line text of text at some x, y location
"""
fig = Figure()
fig.text(
region=region,
projection=projection,
x=1.2,
y=2.4,
text="This is a line of text",
)
return fig


@pytest.mark.mpl_image_compare
def test_text_multiple_lines_of_text(region, projection):
"""
Place multiple lines of text at their respective x, y locations
"""
fig = Figure()
fig.text(
region=region,
projection=projection,
x=[1.2, 1.6],
y=[0.6, 0.3],
text=["This is a line of text", "This is another line of text"],
)
return fig


def test_text_without_text_input(region, projection):
"""
Run text by passing in x and y, but no text
"""
fig = Figure()
with pytest.raises(GMTInvalidInput):
fig.text(region=region, projection=projection, x=1.2, y=2.4)


@pytest.mark.mpl_image_compare
def test_text_input_single_filename():
"""
Run text by passing in one filename to textfiles
"""
fig = Figure()
fig.text(region=[10, 70, -5, 10], textfiles=POINTS_DATA)
return fig


@pytest.mark.mpl_image_compare
def test_text_input_multiple_filenames():
"""
Run text by passing in multiple filenames to textfiles
"""
fig = Figure()
fig.text(region=[10, 70, -30, 10], textfiles=[POINTS_DATA, CITIES_DATA])
return fig


def test_text_nonexistent_filename():
"""
Run text by passing in a list of filenames with one that does not exist
"""
fig = Figure()
with pytest.raises(GMTInvalidInput):
fig.text(region=[10, 70, -5, 10], textfiles=[POINTS_DATA, "notexist.txt"])


@pytest.mark.mpl_image_compare
def test_text_angle_30(region, projection):
"""
Print text at 30 degrees counter-clockwise from horizontal
"""
fig = Figure()
fig.text(
region=region,
projection=projection,
x=1.2,
y=2.4,
text="text angle 30 degrees",
angle=30,
)
return fig


@pytest.mark.mpl_image_compare
def test_text_font_bold(region, projection):
"""
Print text with a bold font
"""
fig = Figure()
fig.text(
region=region,
projection=projection,
x=1.2,
y=2.4,
text="text in bold",
font="Helvetica-Bold",
)
return fig


@pytest.mark.mpl_image_compare
def test_text_justify_bottom_right_and_top_left(region, projection):
"""
Print text justified at bottom right and top left
"""
fig = Figure()
fig.text(
region=region,
projection=projection,
x=1.2,
y=0.2,
text="text justified bottom right",
justify="BR",
)
fig.text(
region=region,
projection=projection,
x=1.2,
y=0.2,
text="text justified top left",
justify="TL",
)
return fig


@pytest.mark.mpl_image_compare
def test_text_justify_parsed_from_textfile():
"""
Print text justified based on a column from textfile, using justify=True boolean
operation. Loosely based on "All great-circle paths lead to Rome" gallery example at
https://gmt.soest.hawaii.edu/doc/latest/gallery/ex23.html
"""
fig = Figure()
fig.text(
region="g",
projection="H90/9i",
justify=True,
textfiles=CITIES_DATA,
D="j0.45/0+vred", # draw red-line from xy point to text label (city name)
)
return fig