GenericMappingTools / pygmt

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

Figure.shift_origin() does not work #514

Closed MarkWieczorek closed 4 years ago

MarkWieczorek commented 4 years ago

The method shift origin does not work. This simple example never terminates in ipython

import pygmt
fig = pygmt.Figure()
fig.shift_origin(xshift='1i', yshift='2i')

In a jupyter notebook, using shift origin instead kills the kernel and provides this message:

gmtinfo [ERROR]: No input data found!

In addition to this, there is a broken link in the web documentation to a this page in gmt:

pygmt: last commit gmt: 6.1

seisman commented 4 years ago

Yes, that's a big limitation of the shift_origin() function. You can't use it as your first plot command, because it needs to know projection (-J) and region (-R) from the history.

seisman commented 4 years ago

shift_origin() is a wrapper of GMT command gmt plot -T -Xxshift -Yyshift, so it required -J and -R set before calling the function.

I thought the function can be improved by wrapping another GMT command

gmt plot -JX1c -R0/1/0/1 -T -Xxshift -Yyshift

then shift_origin() will have its own -J and -R, so it can be used as the first call of a PyGMT figure. However, the command above will overwrite the -J and *-R** settings in gmt.history.

Setting GMT_HISTORY to false, i.e., the command below, should avoid overwriting the old settings, but it doesn't works as I expect.

gmt plot -JX1c -R0/1/0/1 -T -Xxshift -Yyshift --GMT_HISTORY=false

I've opened an issue (https://github.com/GenericMappingTools/gmt/issues/3643) and see if it's a GMT bug.

MarkWieczorek commented 4 years ago

For my use case, I would be ok if shift_origin did nothing if it was operating on a blank figure for the first time.

I have a custom plotting routine, and it takes as input a prexisting fig and optional X and Y offsets. In practice, whenever you would call the function the first time, X and Y would be None, or zero. Of course, I can easily work around this in my own code, but as a fallback option, pygmt doing nothing would be better than crashing!

seisman commented 4 years ago

Yes, I agree. It need to be improved. Let's wait for the feedback from upstream GMT first.

seisman commented 4 years ago

There is no workaround for GMT 6.1.0. PR #536 fixes the broken link in the documentation and also documents the limitation of this function.

In GMT>=6.1.1, gmt plot -T no longer required -R and -J (see the fix in https://github.com/GenericMappingTools/gmt/pull/3672), then it should work as expected.

This issue is marked as "upstream". When PyGMT bumps the minimum required GMT version to GMT>=6.1.1, we should add a test for it and close the issue.

MarkWieczorek commented 4 years ago

I just tested shift_origin using the new GMT PRs, and it is mostly working. Nevertheless, there is still a problem when calling shift origin with values of zero before creating the first image. In essence, it appears that shift_origin() in this case performs an absolute shift, as opposed to a relative shift. Even when you append r to force this to be a relative shift, shift_origin performs an absolute shift.

And as far as I know, the current origin of the first plot is not (0, 0), but is rather shifted a small amount (0+dx, 0+dy) so that the labels aren't cropped. The documentation states that

prepend r [Default] to move the origin relative to its current location.

fig = pygmt.Figure()
fig.basemap(J='X10/5', R='0/360/-90/90', B='a30f30')

fig2 = pygmt.Figure()
fig2.shift_origin(xshift='0i', yshift='0i')
fig2.basemap(J='X10/5', R='0/360/-90/90', B='a30f30')

fig3 = pygmt.Figure()
fig3.shift_origin(xshift='r0i', yshift='r0i')
fig3.basemap(J='X10/5', R='0/360/-90/90', B='a30f30')

fig.savefig('default.png')
fig2.savefig('shift-zero.png')
fig3.savefig('shift-zero-r.png')

No shift

default

Shift '0i' before creating image

shift-zero

Shift 'r0i' before creating image

shift-zero-r

seisman commented 4 years ago

And as far as I know, the current origin of the first plot is not (0, 0), but is rather shifted a small amount (0+dx, 0+dy) so that the labels aren't cropped.

You're right. In classic mode, the dx and dy are 72 points (i.e., 1 inch). In modern mode, I believe they're 5 inches.

The three figures you shown are expected from GMT CLI.

For GMT, the first plotting command is very special. If no -X and -Y are used, the default origin is (72p, 72p). You can think that -Xr1i and -Yr1i are automatically added to the first plotting command in this case. However, if you gives -X and/or -Y in the first plotting command, they override the default values. So your -Xr0i and -Yr0i actually put the plot origin at (0,0), and the left and bottom labels are all cropped.

This is how GMT does for a long time. PyGMT can do nothing here, because PyGMT don't know if this is the first plotting command or not (although maybe we can add the counter). I'm not sure what GMT can do without backward incompatibility. @PaulWessel

PaulWessel commented 4 years ago

Yep. Classic GMT is tied to the concept of a sheet of paper, and if you move off it that things get clipped. With modern mode we basically have no paper size (but it is there, just limited to 11x11 meters by ghostscript) and we decided to let the origin be at (5i,5i) to ensure no surprises. But even that is not foolproof. If you make your first plot and then move negatively down/left you will eventually hit the paper edge, and 5 inches is not particularly large if you are thinking in terms of paper dimensions. Perhaps we should use (1m,1m) instead so any surprise would only affect people making plots that are several meters in dimensions. We also had trouble with various bugs in ghostscript that defeated our margin settings but I think we are past that now. I think the GMT developers should consider setting that margin to (1m,1m).

MarkWieczorek commented 4 years ago

However, if you gives -X and/or -Y in the first plotting command, they override the default values.

Ok. But that behavior is undocumented and not consistent with the documentation:

Prepend r for shift relative to current point (default)... prepend f to position relative to lower left corner of page.

The default is thus to always use relative offsets, so why not just use relative offsets with respect to the default origin for the first plot ? If you really wanted to place the plot at (0, 0), you would use -Xf0 -Yf0.

PaulWessel commented 4 years ago

I just looked at the docs and to me I think we state the case that the default shift is 0,0 unless it is the first command and then it is 72p. Those are the defaults. If you give -X0 - Y0 on the first plot then stuff fill fall off the page in classic mode. Your suggestion is not wrong, but it would be a major backwards compatibility break. We dealt with many of those breaks by introducing modern mode.

seisman commented 4 years ago

Your suggestion is not wrong, but it would be a major backwards compatibility break. We dealt with many of those breaks by introducing modern mode.

Can we introduce the change for modern mode (non-PS output) only?

PaulWessel commented 4 years ago

Sorry if I missed the point but with a 5 inch margin already being cropped when making non-PS output, the initial origin is arbitrary anyway now so what would be achieved?

seisman commented 4 years ago

the initial origin is arbitrary anyway

I believe that's not true if you use -X0 and -Y0 in the first command. -X0 -Y0 moves the default plot origin from (5i,5i) to (0,0) in modern mode.

PaulWessel commented 4 years ago

OK, if that is the case then we have a problem.

MarkWieczorek commented 4 years ago

I just looked at the docs and to me I think we state the case that the default shift is 0,0 unless it is the first command and then it is 72p. Those are the defaults. If you give -X0 - Y0 on the first plot then stuff fill fall off the page in classic mode. Your suggestion is not wrong, but it would be a major backwards compatibility break. We dealt with many of those breaks by introducing modern mode.

Ok. I think I understand what is happening. For the first plot, the default origin is set to (0,0). Then, if you don't specify -X and -Y (which you probably wouldn't under normal circumstances) the values of -Xr72p -Yr72p are passed in order to shift the plot origin to (72p, 72p).

My mistake was thinking that the default origin of the first plot was set to (72p, 72p), and that by specifying -Xr0 nothing would happen as there would be a 0p relative shift with respect to (72p, 72p).

Let me stress that this issue is not important to me! Nevertheless, one potential way to clarify things might be to (1) set the default origin to (72p, 72p), and (2) not to pass -Xr72p -Yr72p as default values for the first plot. Please don't waste your time on this unless you think it is important :)

PaulWessel commented 4 years ago

No worries, Mark.