pygmt icon indicating copy to clipboard operation
pygmt copied to clipboard

Figure.text(): Support paragraph mode

Open yebo-liu opened this issue 5 years ago • 14 comments

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

yebo-liu avatar Mar 19 '21 03:03 yebo-liu

👋 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.

welcome[bot] avatar Mar 19 '21 03:03 welcome[bot]

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?

weiji14 avatar Mar 19 '21 03:03 weiji14

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.

yebo-liu avatar Mar 19 '21 03:03 yebo-liu

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.

weiji14 avatar Mar 19 '21 03:03 weiji14

@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:

  • You have to prepare a plaintext following the text manpage
  • Use M=True when calling Figure.text()
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()

seisman avatar Mar 19 '21 04:03 seisman

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

yebo-liu avatar Mar 19 '21 06:03 yebo-liu

Reopen the issue as it's a useful feature.

seisman avatar Mar 19 '21 13:03 seisman

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()
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".

seisman avatar Mar 20 '21 04:03 seisman

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

munzekm avatar Apr 02 '21 18:04 munzekm

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

seisman avatar Apr 03 '21 00:04 seisman

@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!

noorbuchi avatar Apr 06 '21 19:04 noorbuchi

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

weiji14 avatar Apr 06 '21 19:04 weiji14

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

noorbuchi avatar Apr 06 '21 19:04 noorbuchi

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()
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.

weiji14 avatar Oct 29 '21 02:10 weiji14