GenericMappingTools / pygmt

A Python interface for the Generic Mapping Tools.
https://www.pygmt.org
BSD 3-Clause "New" or "Revised" License
755 stars 218 forks source link

Figure.text(): Support paragraph mode #1078

Open yebo-liu opened 3 years ago

yebo-liu commented 3 years ago

Description of the desired feature I was trying to place some texts on my plot. The texts are quite long and require to be separated into paragraphs. Maybe I got something wrong, but it looks like the paragraph mode of gmt hasn't been wrapped in pygmt yet?

Are you willing to help implement and maintain this feature? Yes/No

welcome[bot] commented 3 years ago

👋 Thanks for opening your first issue here! Please make sure you filled out the template with as much detail as possible. You might also want to take a look at our contributing guidelines and code of conduct.

weiji14 commented 3 years ago

Hi @yebo-liu, could you provide an example of your PyGMT script and the text that you want to plot? Are you trying to plot the text using fig.text, or in fig.legend, or somewhere else?

yebo-liu commented 3 years ago

Hi @weiji14 , thanks for the prompt reply. Please see below

fig = pygmt.Figure() fig.basemap(projection="W0/12i", region="g", frame=['30g30'],transparency=70) fig.text(text='long texts',x=90,y=0,font='10p') fig.show()

image

I'm not really familiar with PyGMT nor GMT. What I'm trying to achieve is that to get the texts change lines and break it into more than one paragraph if possible. Hopefully I made myself clear.

weiji14 commented 3 years ago

Ok, I won't be able to reply for the next few hours, but maybe someone else can chip in.

There was some work on supporting long titles in fig.basemap at https://github.com/GenericMappingTools/gmt/pull/4562 (but that will be available in GMT 6.2.0, not the current 6.1.1). Alternatively, there might be a way to hack out a solution using fig.legend (see https://docs.generic-mapping-tools.org/6.1/gallery/ex22.html). Is your long paragraph meant to be a sort of description of the map? Or more of a title description? Just want to help you figure out the best solution here.

seisman commented 3 years ago

@yebo-liu I believe it would be very trick to have a Pythonic way to support paragraph mode. Here is an PyGMT example modified from the official text manual (https://docs.generic-mapping-tools.org/dev/text.html#examples). The key points are:

import pygmt

with open("text.txt", "w") as fp:
    fp.write('''\
> 0 0 13p 3i j
@%5%Figure 1.@%% This illustration shows nothing useful, but it still needs
a figure caption. Highlighted in @;255/0/0;red@;; you can see the locations
of cities where it is @\_impossible@\_ to get any good Thai food; these are to be avoided.
''')

fig = pygmt.Figure()
fig.basemap(projection="W0/12i", region="g", frame=['30g30'])
fig.text(textfiles="text.txt", font='10p', M=True)
fig.show()
yebo-liu commented 3 years ago

Thanks @seisman and @weiji14 for your help. Your solutions solved my problem.

seisman commented 3 years ago

Reopen the issue as it's a useful feature.

seisman commented 3 years ago

This feature is not that hard to implement as I expected. The key here is the -M option (https://docs.generic-mapping-tools.org/dev/text.html#m):

Paragraph mode. Files must be multiple segment files. Segments are separated by a special record whose first character must be flag [Default is >]. Starting in the 3rd field, we expect to find information pertaining to the typesetting of a text paragraph (the remaining lines until next segment header). The information expected is (x y [font angle justify] linespace parwidth parjust), where x y font angle justify are defined above (font, angle, and justify can be set via -F), while linespace and parwidth are the linespacing and paragraph width, respectively. The justification of the text paragraph is governed by parjust which may be l(eft), c(enter), r(ight), or j(ustified). The segment header is followed by one or more lines with paragraph text. Text may contain the escape sequences discussed above, although composite characters are not supported. Separate paragraphs with a blank line. Note that here, the justification set via -F+j applies to the box alignment since the text justification is set by parjust. Note: cannot be used with LaTeX expressions.

To support paragraph mode, we need to prepare a file like this one.

> x y [font angle justify] linespace parwidth parjust
This is a long paragraph to show on figures.

Figure.text() already has parameters for x, y, font, angle, and justify, so the only thing we need to do is providing parameters for linespace, parwidth and parjust.

There are two possible syntax:

  1. import pygmt
    fig = pygmt.Figure()
    fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
    fig.text(
    x=2, y=2, font="15p", angle=30, justify="TL", 
    text="This is a very long paragraph and should be in multiple lines",
    paragraph={
        "linespace": "13p"
        "width": "10c",
        "justify": "l" 
    }
    )
    fig.show()
  2. import pygmt
    fig = pygmt.Figure()
    fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
    fig.text(
    x=2, y=2, font="15p", angle=30, justify="TL", 
    text="This is a very long paragraph and should be in multiple lines",
    paragraph_linespace="13p",
    paragraph_width="10c",
    paragraph_justify="l",
    )
    fig.show()

It's even possible to automatically detect and apply paragraph mode when a text string containing line breaks are passed, for example, "This is a long paragrapn\nThis is a newline\nThis is another line".

munzekm commented 3 years ago

Hello! Me and my team of developers would like to know if this issue is still open to contribute to? We currently just worked on pull request #1070 and we would like to continue to work on this open source project for our software innovation class. @noorbuchi @cklima616 @nathandloria

seisman commented 3 years ago

@munzekm Thanks for your interest in this feature. I'm afraid this issue needs more discussion before someone tries to work on it.

noorbuchi commented 3 years ago

@seisman do you have any suggestions for a feature/documentation/fix that our group could work on. Maybe something that has already been discussed. Please let us know. Thanks!

weiji14 commented 3 years ago

May I suggest #549 which is a tutorial for datetime inputs?

noorbuchi commented 3 years ago

@weiji14 thanks for the suggestion! we're currently taking a look at it.

weiji14 commented 2 years ago

This feature is not that hard to implement as I expected. The key here is the -M option (https://docs.generic-mapping-tools.org/dev/text.html#m):

Figure.text() already has parameters for x, y, font, angle, and justify, so the only thing we need to do is providing parameters for linespace, parwidth and parjust.

There are two possible syntax: 1.

import pygmt
fig = pygmt.Figure()
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
fig.text(
    x=2, y=2, font="15p", angle=30, justify="TL", 
    text="This is a very long paragraph and should be in multiple lines",
    paragraph={
        "linespace": "13p"
        "width": "10c",
        "justify": "l" 
    }
)
fig.show()

2.

import pygmt
fig = pygmt.Figure()
fig.basemap(region=[0, 10, 0, 10], projection="X10c/10c", frame=True)
fig.text(
    x=2, y=2, font="15p", angle=30, justify="TL", 
    text="This is a very long paragraph and should be in multiple lines",
    paragraph_linespace="13p",
    paragraph_width="10c",
    paragraph_justify="l",
)
fig.show()

It's even possible to automatically detect and apply paragraph mode when a text string containing line breaks are passed, for example, "This is a long paragrapn\nThis is a newline\nThis is another line".

I would prefer the 2nd functional parameter option since it can be tab-completed, but this also relates to #1082 on whether we should use dictionary/functional/classes for these sort of parameters. For this text 'paragraph' feature, the functional style might be ok since it's a one-off thing that won't be reused in other PyGMT functions. But maybe someone has another idea?

Anyways, to resolve this issue, we'll need to 1) alias M = "paragraph"; 2) handle the linespace/width/justify parsing; and 3) add a gallery example on how text paragraphs can be plotted.