"""
Base class with plot generating commands.
Does not define any special non-GMT methods (savefig, show, etc).
"""
import contextlib
import csv
import numpy as np
import pandas as pd
from .clib import Session
from .exceptions import GMTError, GMTInvalidInput
from .helpers import (
build_arg_string,
dummy_context,
data_kind,
fmt_docstring,
GMTTempFile,
use_alias,
kwargs_to_strings,
)
class BasePlotting:
"""
Base class for Figure and Subplot.
Defines the plot generating methods and a hook for subclasses to insert
special arguments (the _preprocess method).
"""
def _preprocess(self, **kwargs): # pylint: disable=no-self-use
"""
Make any changes to kwargs or required actions before plotting.
This method is run before all plotting commands and can be used to
insert special arguments into the kwargs or make any actions that are
required before ``call_module``.
For example, the :class:`pygmt.Figure` needs this to tell the GMT
modules to plot to a specific figure.
This is a dummy method that does nothing.
Returns
-------
kwargs : dict
The same input kwargs dictionary.
Examples
--------
>>> base = BasePlotting()
>>> base._preprocess(resolution='low')
{'resolution': 'low'}
"""
return kwargs
@fmt_docstring
@use_alias(
R="region",
J="projection",
A="area_thresh",
B="frame",
D="resolution",
I="rivers",
L="map_scale",
N="borders",
W="shorelines",
G="land",
S="water",
U="timestamp",
)
@kwargs_to_strings(R="sequence")
def coast(self, **kwargs):
"""
Plot continents, shorelines, rivers, and borders on maps
Plots grayshaded, colored, or textured land-masses [or water-masses] on
maps and [optionally] draws coastlines, rivers, and political
boundaries. Alternatively, it can (1) issue clip paths that will
contain all land or all water areas, or (2) dump the data to an ASCII
table. The data files come in 5 different resolutions: (**f**)ull,
(**h**)igh, (**i**)ntermediate, (**l**)ow, and (**c**)rude. The full
resolution files amount to more than 55 Mb of data and provide great
detail; for maps of larger geographical extent it is more economical to
use one of the other resolutions. If the user selects to paint the
land-areas and does not specify fill of water-areas then the latter
will be transparent (i.e., earlier graphics drawn in those areas will
not be overwritten). Likewise, if the water-areas are painted and no
land fill is set then the land-areas will be transparent.
A map projection must be supplied.
Full option list at :gmt-docs:`coast.html`
{aliases}
Parameters
----------
{J}
{R}
area_thresh : int, float, or str
``'min_area[/min_level/max_level][+ag|i|s|S][+r|l][+ppercent]'``
Features with an area smaller than min_area in km^2 or of
hierarchical level that is lower than min_level or higher than
max_level will not be plotted.
{B}
C : str
Set the shade, color, or pattern for lakes and river-lakes.
resolution : str
Selects the resolution of the data set to use ((f)ull, (h)igh,
(i)ntermediate, (l)ow, and (c)rude).
land : str
Select filling or clipping of “dry” areas.
rivers : str
``'river[/pen]'``
Draw rivers. Specify the type of rivers and [optionally] append pen
attributes.
map_scale : str
``'[g|j|J|n|x]refpoint'``
Draws a simple map scale centered on the reference point specified.
borders : str
``'border[/pen]'``
Draw political boundaries. Specify the type of boundary and
[optionally] append pen attributes
water : str
Select filling or clipping of “wet” areas.
{U}
shorelines : str
``'[level/]pen'``
Draw shorelines [Default is no shorelines]. Append pen attributes.
"""
kwargs = self._preprocess(**kwargs)
with Session() as lib:
lib.call_module("coast", build_arg_string(kwargs))
@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
C="cmap",
D="position",
F="box",
G="truncate",
W="scale",
)
@kwargs_to_strings(R="sequence", G="sequence")
def colorbar(self, **kwargs):
"""
Plot a gray or color scale-bar on maps.
Both horizontal and vertical scales are supported. For CPTs with
gradational colors (i.e., the lower and upper boundary of an interval
have different colors) we will interpolate to give a continuous scale.
Variations in intensity due to shading/illumination may be displayed by
setting the option -I. Colors may be spaced according to a linear
scale, all be equal size, or by providing a file with individual tile
widths.
Full option list at :gmt-docs:`colorbar.html`
{aliases}
Parameters
----------
position : str
``[g|j|J|n|x]refpoint[+wlength[/width]][+e[b|f][length]][+h|v]
[+jjustify][+m[a|c|l|u]][+n[txt]][+odx[/dy]]``. Defines the
reference point on the map for the color scale using one of four
coordinate systems: (1) Use *g* for map (user) coordinates, (2) use
*j* or *J* for setting refpoint via a 2-char justification code
that refers to the (invisible) map domain rectangle, (3) use *n*
for normalized (0-1) coordinates, or (4) use *x* for plot
coordinates (inches, cm, etc.). All but *x* requires both *region*
and *projection* to be specified. Append +w followed by the length
and width of the color bar. If width is not specified then it is
set to 4% of the given length. Give a negative length to reverse
the scale bar. Append +h to get a horizontal scale
[Default is vertical (+v)]. By default, the anchor point on the
scale is assumed to be the bottom left corner (BL), but this can be
changed by appending +j followed by a 2-char justification code
*justify*.
box : bool or str
``[+cclearances][+gfill][+i[[gap/]pen]][+p[pen]][+r[radius]]
[+s[[dx/dy/][shade]]]``. If set to True, draws a rectangular
border around the color scale. Alternatively, specify a different
pen with +ppen. Add +gfill to fill the scale panel [no fill].
Append +cclearance where clearance is either gap, xgap/ygap, or
lgap/rgap/bgap/tgap where these items are uniform, separate in x-
and y-direction, or individual side spacings between scale and
border. Append +i to draw a secondary, inner border as well. We use
a uniform gap between borders of 2p and the MAP_DEFAULTS_PEN unless
other values are specified. Append +r to draw rounded rectangular
borders instead, with a 6p corner radius. You can override this
radius by appending another value. Finally, append +s to draw an
offset background shaded region. Here, dx/dy indicates the shift
relative to the foreground frame [4p/-4p] and shade sets the fill
style to use for shading [gray50].
truncate : list or str
``zlo/zhi`` Truncate the incoming CPT so that the lowest and
highest z-levels are to zlo and zhi. If one of these equal NaN then
we leave that end of the CPT alone. The truncation takes place
before the plotting.
scale : float
Multiply all z-values in the CPT by the provided scale. By default
the CPT is used as is.
"""
kwargs = self._preprocess(**kwargs)
with Session() as lib:
lib.call_module("colorbar", build_arg_string(kwargs))
@fmt_docstring
@use_alias(
A="annotation",
B="frame",
C="interval",
G="label_placement",
J="projection",
L="limit",
Q="cut",
R="region",
S="resample",
U="timestamp",
W="pen",
l="label",
)
@kwargs_to_strings(R="sequence", L="sequence", A="sequence_plus")
def grdcontour(self, grid, **kwargs):
"""
Convert grids or images to contours and plot them on maps
Takes a grid file name or an xarray.DataArray object as input.
Full option list at :gmt-docs:`grdcontour.html`
{aliases}
Parameters
----------
grid : str or xarray.DataArray
The file name of the input grid or the grid loaded as a DataArray.
interval : str or int
Specify the contour lines to generate.
- The filename of a `CPT` file where the color boundaries will
be used as contour levels.
- The filename of a 2 (or 3) column file containing the contour
levels (col 1), (C)ontour or (A)nnotate (col 2), and optional
angle (col 3)
- A fixed contour interval ``cont_int`` or a single contour with
``+[cont_int]``
annotation : str, int, or list
Specify or disable annotated contour levels, modifies annotated
contours specified in ``-C``.
- Specify a fixed annotation interval ``annot_int`` or a
single annotation level ``+[annot_int]``
- Disable all annotation with ``'-'``
- Optional label modifiers can be specified as a single string
``'[annot_int]+e'`` or with a list of options
``([annot_int], 'e', 'f10p', 'gred')``.
limit : str or list of 2 ints
Do no draw contours below `low` or above `high`, specify as string
``'[low]/[high]'`` or list ``[low,high]``.
cut : str or int
Do not draw contours with less than `cut` number of points.
resample : str or int
Resample smoothing factor.
{J}
{R}
{B}
{G}
{U}
{W}
label : str
Add a legend entry for the contour being plotted. Normally, the
annotated contour is selected for the legend. You can select the
regular contour instead, or both of them, by considering the label
to be of the format [*annotcontlabel*][/*contlabel*]. If either
label contains a slash (/) character then use ``|`` as the
separator for the two labels instead.
"""
kwargs = self._preprocess(**kwargs)
kind = data_kind(grid, None, None)
with Session() as lib:
if kind == "file":
file_context = dummy_context(grid)
elif kind == "grid":
file_context = lib.virtualfile_from_grid(grid)
else:
raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid)))
with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("grdcontour", arg_str)
@fmt_docstring
@use_alias(R="region", J="projection", W="pen", B="frame", I="shading", C="cmap")
@kwargs_to_strings(R="sequence")
def grdimage(self, grid, **kwargs):
"""
Project grids or images and plot them on maps.
Takes a grid file name or an xarray.DataArray object as input.
Full option list at :gmt-docs:`grdimage.html`
{aliases}
Parameters
----------
grid : str or xarray.DataArray
The file name of the input grid or the grid loaded as a DataArray.
"""
kwargs = self._preprocess(**kwargs)
kind = data_kind(grid, None, None)
with Session() as lib:
if kind == "file":
file_context = dummy_context(grid)
elif kind == "grid":
file_context = lib.virtualfile_from_grid(grid)
else:
raise GMTInvalidInput("Unrecognized data type: {}".format(type(grid)))
with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("grdimage", arg_str)
@fmt_docstring
@use_alias(
R="region",
J="projection",
Jz="zscale",
JZ="zsize",
B="frame",
C="cmap",
G="drapegrid",
N="plane",
Q="surftype",
Wc="contourpen",
Wm="meshpen",
Wf="facadepen",
p="perspective",
I="shading",
)
@kwargs_to_strings(R="sequence", p="sequence")
def grdview(self, grid, **kwargs):
"""
Create 3-D perspective image or surface mesh from a grid.
Reads a 2-D grid file and produces a 3-D perspective plot by drawing a
mesh, painting a colored/gray-shaded surface made up of polygons, or by
scanline conversion of these polygons to a raster image. Options
include draping a data set on top of a surface, plotting of contours on
top of the surface, and apply artificial illumination based on
intensities provided in a separate grid file.
Full option list at :gmt-docs:`grdview.html`
{aliases}
Parameters
----------
grid : str or xarray.DataArray
The file name of the input relief grid or the grid loaded as a
DataArray.
zscale/zsize : float or str
Set z-axis scaling or z-axis size.
cmap : str
The name of the color palette table to use.
drapegrid : str or xarray.DataArray
The file name or a DataArray of the image grid to be draped on top
of the relief provided by grid. [Default determines colors from
grid]. Note that -Jz and -N always refers to the grid. The
drapegrid only provides the information pertaining to colors, which
(if drapegrid is a grid) will be looked-up via the CPT (see -C).
plane : float or str
``level[+gfill]``.
Draws a plane at this z-level. If the optional color is provided
via the +g modifier, and the projection is not oblique, the frontal
facade between the plane and the data perimeter is colored.
surftype : str
Specifies cover type of the grid.
Select one of following settings:
1. 'm' for mesh plot [Default].
2. 'mx' or 'my' for waterfall plots (row or column profiles).
3. 's' for surface plot.
4. 'i' for image plot.
5. 'c'. Same as 'i' but will make nodes with z = NaN transparent.
For any of these choices, you may force a monochrome image by
appending the modifier +m.
contourpen : str
Draw contour lines on top of surface or mesh (not image). Append
pen attributes used for the contours.
meshpen : str
Sets the pen attributes used for the mesh. You must also select -Qm
or -Qsm for meshlines to be drawn.
facadepen :str
Sets the pen attributes used for the facade. You must also select
-N for the facade outline to be drawn.
perspective : list or str
``'[x|y|z]azim[/elev[/zlevel]][+wlon0/lat0[/z0]][+vx0/y0]'``.
Select perspective view.
shading : str
Provide the name of a grid file with intensities in the (-1,+1)
range, or a constant intensity to apply everywhere (affects the
ambient light). Alternatively, derive an intensity grid from the
input data grid reliefgrid via a call to ``grdgradient``; append
``+aazimuth``, ``+nargs``, and ``+mambient`` to specify azimuth,
intensity, and ambient arguments for that module, or just give
``+d`` to select the default arguments (``+a-45+nt1+m0``).
"""
kwargs = self._preprocess(**kwargs)
kind = data_kind(grid, None, None)
with Session() as lib:
if kind == "file":
file_context = dummy_context(grid)
elif kind == "grid":
file_context = lib.virtualfile_from_grid(grid)
else:
raise GMTInvalidInput(f"Unrecognized data type for grid: {type(grid)}")
with contextlib.ExitStack() as stack:
fname = stack.enter_context(file_context)
if "G" in kwargs:
drapegrid = kwargs["G"]
if data_kind(drapegrid) in ("file", "grid"):
if data_kind(drapegrid) == "grid":
drape_context = lib.virtualfile_from_grid(drapegrid)
drapefile = stack.enter_context(drape_context)
kwargs["G"] = drapefile
else:
raise GMTInvalidInput(
f"Unrecognized data type for drapegrid: {type(drapegrid)}"
)
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("grdview", arg_str)
@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
S="style",
G="color",
W="pen",
i="columns",
l="label",
C="cmap",
U="timestamp",
)
@kwargs_to_strings(R="sequence", i="sequence_comma")
def plot(self, x=None, y=None, data=None, sizes=None, direction=None, **kwargs):
"""
Plot lines, polygons, and symbols on maps.
Used to be psxy.
Takes a matrix, (x,y) pairs, or a file name as input and plots lines,
polygons, or symbols at those locations on a map.
Must provide either *data* or *x* and *y*.
If providing data through *x* and *y*, *color* (G) can be a 1d array
that will be mapped to a colormap.
If a symbol is selected and no symbol size given, then psxy will
interpret the third column of the input data as symbol size. Symbols
whose size is <= 0 are skipped. If no symbols are specified then the
symbol code (see *S* below) must be present as last column in the
input. If *S* is not used, a line connecting the data points will be
drawn instead. To explicitly close polygons, use *L*. Select a fill
with *G*. If *G* is set, *W* will control whether the polygon outline
is drawn or not. If a symbol is selected, *G* and *W* determines the
fill and outline/no outline, respectively.
Full option list at :gmt-docs:`plot.html`
{aliases}
Parameters
----------
x/y : float or 1d arrays
The x and y coordinates, or arrays of x and y coordinates of the
data points
data : str or 2d array
Either a data file name or a 2d numpy array with the tabular data.
Use option *columns* (i) to choose which columns are x, y, color,
and size, respectively.
sizes : 1d array
The sizes of the data points in units specified in *style* (S).
Only valid if using *x* and *y*.
direction : list of two 1d arrays
If plotting vectors (using ``style='V'`` or ``style='v'``), then
should be a list of two 1d arrays with the vector directions. These
can be angle and length, azimuth and length, or x and y components,
depending on the style options chosen.
{J}
{R}
A : bool or str
``'[m|p|x|y]'``
By default, geographic line segments are drawn as great circle
arcs. To draw them as straight lines, use *A*.
{B}
{CPT}
D : str
``'dx/dy'``: Offset the plot symbol or line locations by the given
amounts dx/dy.
E : bool or str
``'[x|y|X|Y][+a][+cl|f][+n][+wcap][+ppen]'``.
Draw symmetrical error bars.
{G}
style : str
Plot symbols (including vectors, pie slices, fronts, decorated or
quoted lines).
{W}
{U}
label : str
Add a legend entry for the symbol or line being plotted.
"""
kwargs = self._preprocess(**kwargs)
kind = data_kind(data, x, y)
extra_arrays = []
if "S" in kwargs and kwargs["S"][0] in "vV" and direction is not None:
extra_arrays.extend(direction)
if "G" in kwargs and not isinstance(kwargs["G"], str):
if kind != "vectors":
raise GMTInvalidInput(
"Can't use arrays for color if data is matrix or file."
)
extra_arrays.append(kwargs["G"])
del kwargs["G"]
if sizes is not None:
if kind != "vectors":
raise GMTInvalidInput(
"Can't use arrays for sizes if data is matrix or file."
)
extra_arrays.append(sizes)
with Session() as lib:
# Choose how data will be passed in to the module
if kind == "file":
file_context = dummy_context(data)
elif kind == "matrix":
file_context = lib.virtualfile_from_matrix(data)
elif kind == "vectors":
file_context = lib.virtualfile_from_vectors(
np.atleast_1d(x), np.atleast_1d(y), *extra_arrays
)
with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("plot", arg_str)
@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
S="skip",
G="label_placement",
W="pen",
L="triangular_mesh_pen",
i="columns",
l="label",
C="levels",
)
@kwargs_to_strings(R="sequence", i="sequence_comma")
def contour(self, x=None, y=None, z=None, data=None, **kwargs):
"""
Contour table data by direct triangulation.
Takes a matrix, (x,y,z) pairs, or a file name as input and plots lines,
polygons, or symbols at those locations on a map.
Must provide either *data* or *x*, *y*, and *z*.
[TODO: Insert more documentation]
Full option list at :gmt-docs:`contour.html`
{aliases}
Parameters
----------
x/y/z : 1d arrays
Arrays of x and y coordinates and values z of the data points.
data : str or 2d array
Either a data file name or a 2d numpy array with the tabular data.
{J}
{R}
A : bool or str
``'[m|p|x|y]'``
By default, geographic line segments are drawn as great circle
arcs. To draw them as straight lines, use *A*.
{B}
levels : str
Contour file or level(s)
D : str
Dump contour coordinates
E : str
Network information
label_placement : str
Placement of labels
I : bool
Color the triangles using CPT
triangular_mesh_pen : str
Pen to draw the underlying triangulation (default none)
N : bool
Do not clip contours
Q : float or str
Do not draw contours with less than cut number of points.
``'[cut[unit]][+z]'``
skip : bool or str
Skip input points outside region ``'[p|t]'``
{W}
label : str
Add a legend entry for the contour being plotted. Normally, the
annotated contour is selected for the legend. You can select the
regular contour instead, or both of them, by considering the label
to be of the format [*annotcontlabel*][/*contlabel*]. If either
label contains a slash (/) character then use ``|`` as the
separator for the two labels instead.
"""
kwargs = self._preprocess(**kwargs)
kind = data_kind(data, x, y, z)
if kind == "vectors" and z is None:
raise GMTInvalidInput("Must provided both x, y, and z.")
with Session() as lib:
# Choose how data will be passed in to the module
if kind == "file":
file_context = dummy_context(data)
elif kind == "matrix":
file_context = lib.virtualfile_from_matrix(data)
elif kind == "vectors":
file_context = lib.virtualfile_from_vectors(x, y, z)
with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("contour", arg_str)
@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
L="map_scale",
Td="rose",
Tm="compass",
U="timestamp",
)
@kwargs_to_strings(R="sequence")
def basemap(self, **kwargs):
"""
Produce a basemap for the figure.
Several map projections are available, and the user may specify
separate tick-mark intervals for boundary annotation, ticking, and
[optionally] gridlines. A simple map scale or directional rose may also
be plotted.
At least one of the options *frame*, *map_scale*, *rose* or *compass*
must be specified.
Full option list at :gmt-docs:`basemap.html`
{aliases}
Parameters
----------
{J}
{R}
{B}
map_scale : str
``'[g|j|J|n|x]refpoint'``
Draws a simple map scale centered on the reference point specified.
rose : str
Draws a map directional rose on the map at the location defined by
the reference and anchor points.
compass : str
Draws a map magnetic rose on the map at the location defined by the
reference and anchor points
{U}
"""
kwargs = self._preprocess(**kwargs)
if not ("B" in kwargs or "L" in kwargs or "T" in kwargs):
raise GMTInvalidInput("At least one of B, L, or T must be specified.")
with Session() as lib:
lib.call_module("basemap", build_arg_string(kwargs))
@fmt_docstring
@use_alias(R="region", J="projection", U="timestamp", D="position", F="box")
@kwargs_to_strings(R="sequence")
def logo(self, **kwargs):
"""
Place the GMT graphics logo on a map.
By default, the GMT logo is 2 inches wide and 1 inch high and
will be positioned relative to the current plot origin.
Use various options to change this and to place a transparent or
opaque rectangular map panel behind the GMT logo.
Full option list at :gmt-docs:`logo.html`
{aliases}
Parameters
----------
{J}
{R}
position : str
``'[g|j|J|n|x]refpoint+wwidth[+jjustify][+odx[/dy]]'``.
Sets reference point on the map for the image.
box : bool or str
Without further options, draws a rectangular border around the
GMT logo.
{U}
"""
kwargs = self._preprocess(**kwargs)
if "D" not in kwargs:
raise GMTInvalidInput("Option D must be specified.")
with Session() as lib:
lib.call_module("logo", build_arg_string(kwargs))
@fmt_docstring
@use_alias(R="region", J="projection", D="position", F="box", M="monochrome")
@kwargs_to_strings(R="sequence")
def image(self, imagefile, **kwargs):
"""
Place images or EPS files on maps.
Reads an Encapsulated PostScript file or a raster image file and plots
it on a map.
Full option list at :gmt-docs:`image.html`
{aliases}
Parameters
----------
imagefile : str
This must be an Encapsulated PostScript (EPS) file or a raster
image. An EPS file must contain an appropriate BoundingBox. A
raster file can have a depth of 1, 8, 24, or 32 bits and is read
via GDAL. Note: If GDAL was not configured during GMT installation
then only EPS files are supported.
{J}
{R}
position : str
``'[g|j|J|n|x]refpoint+rdpi+w[-]width[/height][+jjustify]
[+nnx[/ny]][+odx[/dy]]'`` Sets reference point on the map for the
image.
box : bool or str
``'[+cclearances][+gfill][+i[[gap/]pen]][+p[pen]][+r[radius]]
[+s[[dx/dy/][shade]]]'`` Without further options, draws a
rectangular border around the image using **MAP_FRAME_PEN**.
monochrome : bool
Convert color image to monochrome grayshades using the (television)
YIQ-transformation.
"""
kwargs = self._preprocess(**kwargs)
with Session() as lib:
arg_str = " ".join([imagefile, build_arg_string(kwargs)])
lib.call_module("image", arg_str)
@fmt_docstring
@use_alias(R="region", J="projection", D="position", F="box")
@kwargs_to_strings(R="sequence")
def legend(self, spec=None, position="JTR+jTR+o0.2c", box="+gwhite+p1p", **kwargs):
"""
Plot legends on maps.
Makes legends that can be overlaid on maps. Reads specific
legend-related information from an input file, or automatically creates
legend entries from plotted symbols that have labels. Unless otherwise
noted, annotations will be made using the primary annotation font and
size in effect (i.e., FONT_ANNOT_PRIMARY).
Full option list at :gmt-docs:`legend.html`
{aliases}
Parameters
----------
spec : None or str
Either None (default) for using the automatically generated legend
specification file, or a filename pointing to the legend
specification file.
{J}
{R}
position : str
``'[g|j|J|n|x]refpoint+wwidth[/height][+jjustify][+lspacing]
[+odx[/dy]]'`` Defines the reference point on the map for the
legend. By default, uses 'JTR+jTR+o0.2c' which places the legend at
the top-right corner inside the map frame, with a 0.2 cm offset.
box : bool or str
``'[+cclearances][+gfill][+i[[gap/]pen]][+p[pen]][+r[radius]]
[+s[[dx/dy/][shade]]]'`` Without further options, draws a
rectangular border around the legend using **MAP_FRAME_PEN**. By
default, uses '+gwhite+p1p' which draws a box around the legend
using a 1 point black pen and adds a white background.
"""
kwargs = self._preprocess(**kwargs)
if "D" not in kwargs:
kwargs["D"] = position
if "F" not in kwargs:
kwargs["F"] = box
with Session() as lib:
if spec is None:
specfile = ""
elif data_kind(spec) == "file":
specfile = spec
else:
raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec)))
arg_str = " ".join([specfile, build_arg_string(kwargs)])
lib.call_module("legend", arg_str)
@fmt_docstring
@use_alias(
R="region",
J="projection",
B="frame",
C="clearance",
D="offset",
G="fill",
W="pen",
)
@kwargs_to_strings(
R="sequence",
textfiles="sequence_space",
angle="sequence_comma",
font="sequence_comma",
justify="sequence_comma",
)
def text(
self,
textfiles=None,
x=None,
y=None,
position=None,
text=None,
angle=None,
font=None,
justify=None,
**kwargs,
):
"""
Plot or typeset text strings of variable size, font type, and
orientation.
Must provide at least one of the following combinations as input:
- *textfiles*
- *x*, *y*, and *text*
- *position* and *text*
Full option list at :gmt-docs:`text.html`
{aliases}
Parameters
----------
textfiles : str or list
A text data file name, or a list of filenames containing 1 or more
records with (x, y[, angle, font, justify], text).
x/y : float or 1d arrays
The x and y coordinates, or an array of x and y coordinates to plot
the text
position : str
Sets reference point on the map for the text by using x,y
coordinates extracted from *region* instead of providing them
through *x* and *y*. Specify with a two letter (order independent)
code, chosen from:
* Horizontal: L(eft), C(entre), R(ight)
* Vertical: T(op), M(iddle), B(ottom)
For example, position="TL" plots the text at the Upper Left corner
of the map.
text : str or 1d array
The text string, or an array of strings to plot on the figure
angle: int, float, str or bool
Set the angle measured in degrees counter-clockwise from
horizontal. E.g. 30 sets the text at 30 degrees. If no angle is
explicitly given (i.e. angle=True) then the input textfile(s) must
have this as a column.
font : str or bool
Set the font specification with format "size,font,color" where size
is text size in points, font is the font to use, and color sets the
font color. E.g. "12p,Helvetica-Bold,red" selects a 12p red
Helvetica-Bold font. If no font info is explicitly given (i.e.
font=True), then the input textfile(s) must have this information
in one of its columns.
justify : str or bool
Set the alignment which refers to the part of the text string that
will be mapped onto the (x,y) point. Choose a 2 character
combination of L, C, R (for left, center, or right) and T, M, B for
top, middle, or bottom. E.g., BL for lower left. If no
justification is explicitly given (i.e. justify=True), then the
input textfile(s) must have this as a column.
{J}
{R}
clearance : str
``[dx/dy][+to|O|c|C]``
Adjust the clearance between the text and the surrounding box
[15%]. Only used if *pen* or *fill* are specified. Append the unit
you want ('c' for cm, 'i' for inch, or 'p' for point; if not given
we consult 'PROJ_LENGTH_UNIT') or '%' for a percentage of the
font size. Optionally, use modifier '+t' to set the shape of the
textbox when using *fill* and/or *pen*. Append lower case 'o' to
get a straight rectangle [Default]. Append upper case 'O' to get a
rounded rectangle. In paragraph mode (*paragraph*) you can also
append lower case 'c' to get a concave rectangle or append upper
case 'C' to get a convex rectangle.
fill : str
Sets the shade or color used for filling the text box [Default is
no fill].
offset : str
``[j|J]dx[/dy][+v[pen]]``
Offsets the text from the projected (x,y) point by dx,dy [0/0]. If
dy is not specified then it is set equal to dx. Use offset='j' to
offset the text away from the point instead (i.e., the text
justification will determine the direction of the shift). Using
offset='J' will shorten diagonal offsets at corners by sqrt(2).
Optionally, append '+v' which will draw a line from the original
point to the shifted point; append a pen to change the attributes
for this line.
pen : str
Sets the pen used to draw a rectangle around the text string
(see *clearance*) [Default is width = default, color = black,
style = solid].
"""
kwargs = self._preprocess(**kwargs)
# Ensure inputs are either textfiles, x/y/text, or position/text
if position is None:
kind = data_kind(textfiles, x, y, text)
else:
if x is not None or y is not None:
raise GMTInvalidInput(
"Provide either position only, or x/y pairs, not both"
)
kind = "vectors"
if kind == "vectors" and text is None:
raise GMTInvalidInput("Must provide text with x/y pairs or position")
# Build the `-F` argument in gmt text.
if "F" not in kwargs.keys() and (
(
position is not None
or angle is not None
or font is not None
or justify is not None
)
):
kwargs.update({"F": ""})
if angle is not None and isinstance(angle, (int, float, str)):
kwargs["F"] += f"+a{str(angle)}"
if font is not None and isinstance(font, str):
kwargs["F"] += f"+f{font}"
if justify is not None and isinstance(justify, str):
kwargs["F"] += f"+j{justify}"
if position is not None and isinstance(position, str):
kwargs["F"] += f'+c{position}+t"{text}"'
with GMTTempFile(suffix=".txt") as tmpfile:
with Session() as lib:
fname = textfiles if kind == "file" else ""
if kind == "vectors":
if position is not None:
fname = ""
else:
pd.DataFrame.from_dict(
{
"x": np.atleast_1d(x),
"y": np.atleast_1d(y),
"text": np.atleast_1d(text),
}
).to_csv(
tmpfile.name,
sep="\t",
header=False,
index=False,
quoting=csv.QUOTE_NONE,
)
fname = tmpfile.name
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("text", arg_str)
@fmt_docstring
@use_alias(R="region", J="projection", B="frame", C="offset")
@kwargs_to_strings(R="sequence")
def meca(
self,
spec,
scale,
longitude=None,
latitude=None,
depth=None,
convention=None,
component="full",
plot_longitude=None,
plot_latitude=None,
**kwargs,
):
"""
Plot focal mechanisms.
Full option list at :gmt-docs:`supplements/seis/meca.html`
Note
----
Currently, labeling of beachballs with text strings is only
supported via providing a file to `spec` as input.
{aliases}
Parameters
----------
spec: dict, 1D array, 2D array, pd.DataFrame, or str
Either a filename containing focal mechanism parameters as columns,
a 1- or 2-D array with the same, or a dictionary. If a filename or
array, `convention` is required so we know how to interpret the
columns/entries. If a dictionary, the following combinations of
keys are supported; these determine the convention. Dictionary
may contain values for a single focal mechanism or lists of
values for many focal mechanisms. A Pandas DataFrame may
optionally contain columns latitude, longitude, depth,
plot_longitude,
and/or plot_latitude instead of passing them to the meca method.
- ``"aki"`` — *strike, dip, rake, magnitude*
- ``"gcmt"`` — *strike1, dip1, rake1, strike2, dip2, rake2,
mantissa, exponent*
- ``"mt"`` — *mrr, mtt, mff, mrt, mrf, mtf, exponent*
- ``"partial"`` — *strike1, dip1, strike2, fault_type, magnitude*
- ``"principal_axis"`` — *t_exponent, t_azimuth, t_plunge,
n_exponent, n_azimuth, n_plunge, p_exponent, p_azimuth, p_plunge,
exponent*
scale: str
Adjusts the scaling of the radius of the beachball, which is
proportional to the magnitude. Scale defines the size for
magnitude = 5 (i.e. scalar seismic moment M0 = 4.0E23 dynes-cm)
longitude: int, float, list, or 1d numpy array
Longitude(s) of event location. Ignored if `spec` is not a
dictionary. List must be the length of the number of events.
Ignored if `spec` is a DataFrame and contains a 'longitude' column.
latitude: int, float, list, or 1d numpy array
Latitude(s) of event location. Ignored if `spec` is not a
dictionary. List must be the length of the number of events.
Ignored if `spec` is a DataFrame and contains a 'latitude' column.
depth: int, float, list, or 1d numpy array
Depth(s) of event location in kilometers. Ignored if `spec` is
not a dictionary. List must be the length of the number of events.
Ignored if `spec` is a DataFrame and contains a 'depth' column.
convention: str
``"aki"`` (Aki & Richards), ``"gcmt"`` (global CMT), ``"mt"``
(seismic moment tensor), ``"partial"`` (partial focal mechanism),
or ``"principal_axis"`` (principal axis). Ignored if `spec` is a
dictionary or dataframe.
component: str
The component of the seismic moment tensor to plot. ``"full"`` (the
full seismic moment tensor), ``"dc"`` (the closest double couple
with zero trace and zero determinant), ``"deviatoric"`` (zero
trace)
plot_longitude: int, float, list, or 1d numpy array
Longitude(s) at which to place beachball, only used if `spec` is a
dictionary. List must be the length of the number of events.
Ignored if `spec` is a DataFrame and contains a 'plot_longitude'
column.
plot_latitude: int, float, list, or 1d numpy array
Latitude(s) at which to place beachball, only used if `spec` is a
dictionary. List must be the length of the number of events.
Ignored if `spec` is a DataFrame and contains a 'plot_latitude'
column.
offset: bool or str
Offsets beachballs to the longitude, latitude specified in
the last two columns of the input file or array,
or by `plot_longitude` and `plot_latitude` if provided. A small
circle is plotted at the initial location and a line connects
the beachball to the circle. Specify pen and optionally append
``+ssize`` to change the line style and/or size of the circle.
{J}
{R}
{B}
"""
# pylint warnings that need to be fixed
# pylint: disable=too-many-locals
# pylint: disable=too-many-nested-blocks
# pylint: disable=too-many-branches
# pylint: disable=no-self-use
# pylint: disable=too-many-statements
def set_pointer(data_pointers, spec):
"""Set optional parameter pointers based on DataFrame or dict, if
those parameters are present in the DataFrame or dict."""
for param in list(data_pointers.keys()):
if param in spec:
# set pointer based on param name
data_pointers[param] = spec[param]
def update_pointers(data_pointers):
"""Updates variables based on the location of data, as the
following data can be passed as parameters or it can be
contained in `spec`."""
# update all pointers
longitude = data_pointers["longitude"]
latitude = data_pointers["latitude"]
depth = data_pointers["depth"]
plot_longitude = data_pointers["plot_longitude"]
plot_latitude = data_pointers["plot_latitude"]
return (longitude, latitude, depth, plot_longitude, plot_latitude)
# Check the spec and parse the data according to the specified
# convention
if isinstance(spec, (dict, pd.DataFrame)):
# dicts and DataFrames are handed similarly but not identically
if (
longitude is None or latitude is None or depth is None
) and not isinstance(spec, (dict, pd.DataFrame)):
raise GMTError("Location not fully specified.")
param_conventions = {
"AKI": ["strike", "dip", "rake", "magnitude"],
"GCMT": ["strike1", "dip1", "dip2", "rake2", "mantissa", "exponent"],
"MT": ["mrr", "mtt", "mff", "mrt", "mrf", "mtf", "exponent"],
"PARTIAL": ["strike1", "dip1", "strike2", "fault_type", "magnitude"],
"PRINCIPAL_AXIS": [
"t_exponent",
"t_azimuth",
"t_plunge",
"n_exponent",
"n_azimuth",
"n_plunge",
"p_exponent",
"p_azimuth",
"p_plunge",
"exponent",
],
}
# to keep track of where optional parameters exist
data_pointers = {
"longitude": longitude,
"latitude": latitude,
"depth": depth,
"plot_longitude": plot_longitude,
"plot_latitude": plot_latitude,
}
# make a DataFrame copy to check convention if it contains
# other parameters
if isinstance(spec, (dict, pd.DataFrame)):
# check if a copy is necessary
copy = False
drop_list = []
for pointer in data_pointers:
if pointer in spec:
copy = True
drop_list.append(pointer)
if copy:
spec_conv = spec.copy()
# delete optional parameters from copy for convention check
for item in drop_list:
del spec_conv[item]
else:
spec_conv = spec
# set convention and focal parameters based on spec convention
convention_assigned = False
for conv in param_conventions:
if set(spec_conv.keys()) == set(param_conventions[conv]):
convention = conv.lower()
foc_params = param_conventions[conv]
convention_assigned = True
break
if not convention_assigned:
raise GMTError(
"Parameters in spec dictionary do not match known " "conventions."
)
# create a dict type pointer for easier to read code
if isinstance(spec, dict):
dict_type_pointer = list(spec.values())[0]
elif isinstance(spec, pd.DataFrame):
# use df.values as pointer for DataFrame behavior
dict_type_pointer = spec.values
# assemble the 1D array for the case of floats and ints as values
if isinstance(dict_type_pointer, (int, float)):
# update pointers
set_pointer(data_pointers, spec)
# look for optional parameters in the right place
(
longitude,
latitude,
depth,
plot_longitude,
plot_latitude,
) = update_pointers(data_pointers)
# Construct the array (order matters)
spec = [longitude, latitude, depth] + [spec[key] for key in foc_params]
# Add in plotting options, if given, otherwise add 0s
for arg in plot_longitude, plot_latitude:
if arg is None:
spec.append(0)
else:
if "C" not in kwargs:
kwargs["C"] = True
spec.append(arg)
# or assemble the 2D array for the case of lists as values
elif isinstance(dict_type_pointer, list):
# update pointers
set_pointer(data_pointers, spec)
# look for optional parameters in the right place
(
longitude,
latitude,
depth,
plot_longitude,
plot_latitude,
) = update_pointers(data_pointers)
# before constructing the 2D array lets check that each key
# of the dict has the same quantity of values to avoid bugs
list_length = len(list(spec.values())[0])
for value in list(spec.values()):
if len(value) != list_length:
raise GMTError(
"Unequal number of focal mechanism "
"parameters supplied in 'spec'."
)
# lets also check the inputs for longitude, latitude,
# and depth if it is a list or array
if (
isinstance(longitude, (list, np.ndarray))
or isinstance(latitude, (list, np.ndarray))
or isinstance(depth, (list, np.ndarray))
):
if (len(longitude) != len(latitude)) or (
len(longitude) != len(depth)
):
raise GMTError(
"Unequal number of focal mechanism "
"locations supplied."
)
# values are ok, so build the 2D array
spec_array = []
for index in range(list_length):
# Construct the array one row at a time (note that order
# matters here, hence the list comprehension!)
row = [longitude[index], latitude[index], depth[index]] + [
spec[key][index] for key in foc_params
]
# Add in plotting options, if given, otherwise add 0s as
# required by GMT
for arg in plot_longitude, plot_latitude:
if arg is None:
row.append(0)
else:
if "C" not in kwargs:
kwargs["C"] = True
row.append(arg[index])
spec_array.append(row)
spec = spec_array
# or assemble the array for the case of pd.DataFrames
elif isinstance(dict_type_pointer, np.ndarray):
# update pointers
set_pointer(data_pointers, spec)
# look for optional parameters in the right place
(
longitude,
latitude,
depth,
plot_longitude,
plot_latitude,
) = update_pointers(data_pointers)
# lets also check the inputs for longitude, latitude, and depth
# just in case the user entered different length lists
if (
isinstance(longitude, (list, np.ndarray))
or isinstance(latitude, (list, np.ndarray))
or isinstance(depth, (list, np.ndarray))
):
if (len(longitude) != len(latitude)) or (
len(longitude) != len(depth)
):
raise GMTError(
"Unequal number of focal mechanism locations supplied."
)
# values are ok, so build the 2D array in the correct order
spec_array = []
for index in range(len(spec)):
# Construct the array one row at a time (note that order
# matters here, hence the list comprehension!)
row = [longitude[index], latitude[index], depth[index]] + [
spec[key][index] for key in foc_params
]
# Add in plotting options, if given, otherwise add 0s as
# required by GMT
for arg in plot_longitude, plot_latitude:
if arg is None:
row.append(0)
else:
if "C" not in kwargs:
kwargs["C"] = True
row.append(arg[index])
spec_array.append(row)
spec = spec_array
else:
raise GMTError(
"Parameter 'spec' contains values of an unsupported type."
)
# Add condition and scale to kwargs
if convention == "aki":
data_format = "a"
elif convention == "gcmt":
data_format = "c"
elif convention == "mt":
# Check which component of mechanism the user wants plotted
if component == "deviatoric":
data_format = "z"
elif component == "dc":
data_format = "d"
else: # component == 'full'
data_format = "m"
elif convention == "partial":
data_format = "p"
elif convention == "principal_axis":
# Check which component of mechanism the user wants plotted
if component == "deviatoric":
data_format = "t"
elif component == "dc":
data_format = "y"
else: # component == 'full'
data_format = "x"
# Support old-school GMT format options
elif convention in ["a", "c", "m", "d", "z", "p", "x", "y", "t"]:
data_format = convention
else:
raise GMTError("Convention not recognized.")
# Assemble -S flag
kwargs["S"] = data_format + scale
kind = data_kind(spec)
with Session() as lib:
if kind == "matrix":
file_context = lib.virtualfile_from_matrix(np.atleast_2d(spec))
elif kind == "file":
file_context = dummy_context(spec)
else:
raise GMTInvalidInput("Unrecognized data type: {}".format(type(spec)))
with file_context as fname:
arg_str = " ".join([fname, build_arg_string(kwargs)])
lib.call_module("meca", arg_str)