GenericMappingTools / pygmt

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

Allow Figure.legend to read from StringIO #571

Closed dr-glenn closed 1 month ago

dr-glenn commented 4 years ago

I need to display a legend of symbols for the magnitude of earthquakes. Currently the legend specification must be a file on disk. The Pythonic implementation should allow Figure.legend(spec=<buffer|file>,...). My preference is to write the legend specification to io.StringIO buffer and then use the buffer for Figure.legend. Instead I have to generate the legend spec in program and write to a file and then specify spec='my_legend.txt'.

Figure.legend has default arg spec=None. I have no idea how this default works, that's a separate issue: improve documentation.

I am willing to implement, but have no experience with such participation.

welcome[bot] commented 4 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.

seisman commented 4 years ago

That's a good point. Currently PyGMT doesn't support StringIO, but I agree it should.

Figure.legend has default arg spec=None. I have no idea how this default works, that's a separate issue: improve documentation.

A quick answer to your question:

GMT6 supports a feature called "auto-legend". For Figure.plot() function, you can add the argument label="XXX", then when you call Figure.legend(), it will automatically add a legend for you.

See this example for how it works. For detailed syntax, please refer to the GMT -l option.

weiji14 commented 4 years ago

Hi @dr-glenn, could you provide an example on how you are writing to the io.StringIO buffer? I know pandas has a to_string function that can write dataframes/tables to StringIO, but not sure if this is what you're referring to. Some lines of code to show how you're doing this would be great!

Currently PyGMT doesn't support StringIO, but I agree it should.

I'll second this. There's at least two ways this can be implemented:

  1. Write the StringIO to a temporary .txt file behind the scenes, and pass that onto GMT. Quite hacky, and might not be good for large StringIO buffers, but it should be doable.
  2. Find a way to let the GMT C API read from the StringIO buffer directly (without an intermediate temporary txt file). I'm not too familiar with how StringIO buffers actually work, but I think someone with the right skills (or willing to learn them) can do it.
dr-glenn commented 4 years ago

I apologize for not answering the request for example much sooner. This example is quite trivial and you may well wonder why I care at all - it's just that I don't like writing files when not needed, plus many Python functions expect a file pointer/buffer object rather than a filename. I note that others have already moved forward with RFC on #576.

# create a map legend for earthquake magnitude
# find magnitude limits for a legend
mag_min = int(data.mag.min())
mag_max = int(data.mag.max() + 1.0)
mags = range(mag_min, mag_max)
mag_sym_size = [0.02 * 2 ** m for m in mags]
# write symbols to internal file
#sio = io.StringIO()    #  NOTE - this is the code that doesn't work with PyGMT
sio = open('quake_mag_sym.txt','w')    # NOTE: must use actual file with PyGMT
for mag,ms in zip(mags,mag_sym_size):
    # S 0.1i c 0.15i p300/12 0.25p 0.3i This circle is hachured
    # S 0.1i e 0.15i yellow 0.25p 0.3i This ellipse is yellow
    if ms > 1.0:    # need extra space above big symbol_ex
        sio.write('G 1l\n')
    sio.write('S 0.1i c %.1f yellow 0.25p 0.3i M=%d\n' % (ms,mag))
#sio.seek(0)
sio.close()
# legend in the upper right
fig.legend(spec='quake_mag_sym.txt')    # NOTE: would prefer to use a buffer/StringIO
weiji14 commented 1 month ago

FYI, @seisman has implemented option 2 from https://github.com/GenericMappingTools/pygmt/issues/571#issuecomment-681139099 ("let the GMT C API read from the StringIO buffer directly") at #3438 (there's an example in that PR on how to pass in an io.StringIO buffer to fig.legend. There's an open issue to document this in a gallery example at https://github.com/GenericMappingTools/pygmt/issues/3444.