Skip to content

Enhanced plotting option (latex backend and/or unit change) #4414

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

Closed
sdat2 opened this issue Sep 10, 2020 · 5 comments
Closed

Enhanced plotting option (latex backend and/or unit change) #4414

sdat2 opened this issue Sep 10, 2020 · 5 comments

Comments

@sdat2
Copy link

sdat2 commented Sep 10, 2020

One problem with xarray's default plotting functionality is that the plots look quite mediocre, and so if you want to present your results, then you may have to go in and change all the defaults, and relabel the graphs. This seems like wasted effort.

xarray could support pdflatex backend usage by matplotlib by sanitizing the labels.

It could also change units from degrees_north to the more presentable$^{\circ}$N.

An example of doing this is shown below:

import numpy as np
import re
import datetime
import matplotlib
import matplotlib.pyplot as plt
import xarray as xr

def mpl_params(quality='high'):
    """
    Apply my plotting style to produce nice looking figures.
    Call this at the start of a script which uses matplotlib,
    and choose the correct setting.
    :return:
    """
    if quality == 'high':
        matplotlib.style.use('seaborn-colorblind')
        param_set = {"pgf.texsystem": "pdflatex",
                     "text.usetex": True,
                     "font.family": "serif",
                     "font.serif": [],
                     "font.sans-serif": ["DejaVu Sans"],
                     "font.monospace": [],
                     "lines.linewidth": 0.75,
                     "pgf.preamble": [r"\usepackage[utf8x]{inputenc} \usepackage[T1]{fontenc}"]
                     }
    else:
        matplotlib.style.use('seaborn-colorblind')
        param_set = {"text.usetex": False,
                     "lines.linewidth": 0.75,
                    "font.family": "sans-serif",
                     "font.serif": [],
                     "font.sans-serif": ["DejaVu Sans"],
                     }

    matplotlib.rcParams.update(param_set)

def tex_escape(text):
    """
    It is better to plot in TeX, but this involves escaping strings.
    from:
        https://stackoverflow.com/questions/16259923/
        how-can-i-escape-latex-special-characters-inside-django-templates
        :param text: a plain text message
        :return: the message escaped to appear correctly in LaTeX
    # removed unicode(key) from re.escape because this seemed an unnecessary,
      and was throwing an error.
    """
    conv = {
            '&': r'\&',
            '%': r'\%',
            '$': r'\$',
            '#': r'\#',
            '_': r'\_',
            '{': r'\{',
            '}': r'\}',
            '~': r'\textasciitilde{}',
            '^': r'\^{}',
            '\\': r'\textbackslash{}',
            '<': r'\textless{}',
            '>': r'\textgreater{}',
            }
    regex = re.compile('|'.join(re.escape(key) for key in sorted(conv.keys(), key=lambda item: - len(item))))
    return regex.sub(lambda match: conv[match.group()], text)

def proper_units(text):
    conv = {
            'degC': r'$^{\circ}$C',
            'degrees\_celsius': r'$^{\circ}$C',
            'degrees\_north': r'$^{\circ}$N',
            'degrees\_east': r'$^{\circ}$E',
            }
    regex = re.compile('|'.join(re.escape(key) for key in sorted(conv.keys(), key=lambda item: - len(item))))
    return regex.sub(lambda match: conv[match.group()], text)
    
mpl_params(quality='high')
ds = xr.tutorial.load_dataset('air_temperature')
plot_data = ds.air.isel(time=120)
plot = plot_data.plot(center=273.15, cbar_kwargs={'label': 'K'})
plt.title(tex_escape('time = '+ np.datetime_as_string(plot_data.coords['time'].data, unit='D')))
plt.xlabel(proper_units(tex_escape(ds.coords['lon'].long_name + ' [' + ds.coords['lon'].units + ']')))
plt.ylabel(proper_units(tex_escape(ds.coords['lat'].long_name + ' ['+ ds.coords['lat'].units + ']')))
plt.show()

@sdat2 sdat2 changed the title Beautiful plotting option with pdflatex and re Enhanced plotting option with pdflatex and re Sep 10, 2020
@sdat2 sdat2 changed the title Enhanced plotting option with pdflatex and re Enhanced plotting option with latex and re Sep 10, 2020
@sdat2 sdat2 changed the title Enhanced plotting option with latex and re Enhanced plotting option Sep 10, 2020
@sdat2 sdat2 changed the title Enhanced plotting option Enhanced plotting option (latex backend and/or unit change) Sep 10, 2020
@fmaussion
Copy link
Member

Thanks a lot for this suggestion! I'll let the others chime in as well, but there are a few things to consider here:

  • xarray's plotting capability is not meant to be "publication ready" but "as close as possible to the data". I.e. units like degrees_north, etc. have to stay like this because it unambiguously shows where the data came from and how users can access it from the file, etc.
  • in general, making the plots nicer with opinionated defaults like yours would be a nice addition to the xarray ecosystem. It could live in a separate package, and use the accessor syntax to extend xarray. For example, my own little extension works this way and does opinionated stuff with the accessor: https://salem.readthedocs.io

@keewis
Copy link
Collaborator

keewis commented Sep 10, 2020

pint already supports printing units with latex, so you might be able to use pint and pint-xarray (and possibly cf-xarray for the definitions of degrees_north, etc.) in the future. See also xarray-contrib/pint-xarray#26.

@sdat2
Copy link
Author

sdat2 commented Sep 10, 2020

Hi @fmaussion and @keewis,

Thank you for your comments, I agree that these changes are opinionated, and so probably inappropriate for inclusion in xarray itself.

I guess my motivation was that I could not see a way to loop through the text in a graph and apply some function to it. It might be easier to directly apply these functions to the text before labelling, rather than changing the labels after they are written, but I hope that this will be possible by using the accessor syntax as you suggest.

A more general version of this issue would be a method to pass a list of text-sanitizing-functions to xarray.plot.

Thank you for linking to salem and pint-xarray, I hadn't discovered those packages before, hopefully I will find some useful functionality there.

@sdat2 sdat2 closed this as completed Sep 10, 2020
@dcherian
Copy link
Contributor

The plotting routines use da.attrs["long_name"] and da.attrs["units"].

For publication figures, I set these manually when reading data:
da.attrs["long_name"] = "$J_q^t$" and da.attrs["units"] = "$W/m²$". Every plot after that is then nicely labeled.

@sdat2
Copy link
Author

sdat2 commented Sep 10, 2020

Hi @dcherian,

Thanks for the good idea! Changing the dataset that you want to plot is probably the easiest option.

Here is my implementation of that to go with my original code.

def change_ds_for_graph(dsA):
    
    ds = dsA.copy()
    
    for varname, da in ds.data_vars.items():
        for attr in da.attrs:
            if attr in ['units', 'long_name']:
                da.attrs[attr] = proper_units(tex_escape(da.attrs[attr]))
                
    for coord in ds.coords:
        for attr in ds.coords[coord].attrs:
            if attr in ['units', 'long_name']:
                da.coords[coord].attrs[attr] = proper_units(tex_escape(da.coords[coord].attrs[attr]))
    
    return ds

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants