Communitygithub.com

rookieplayerjr/pygmt-plotting-skill

Agent Skill for publication-quality scientific maps with PyGMT: condensed API reference, journal house style, and field-tested gotchas the docs don't mention.

¿Qué es pygmt-plotting-skill?

pygmt-plotting-skill is a Claude Code agent skill that agent Skill for publication-quality scientific maps with PyGMT: condensed API reference, journal house style, and field-tested gotchas the docs don't mention.

Compatible con~Claude Code~Codex CLI~Cursor
npx skills add rookieplayerjr/pygmt-plotting-skill

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

PyGMT Plotting

PyGMT wraps the Generic Mapping Tools. Every method parameter accepts both a Python full name (projection) and the GMT single-letter alias (J) — this skill uses full names. Grid-processing functions return xarray.DataArray when outgrid=None, so results chain directly into plotting functions.

Required figure format (house style)

All scientific figures MUST follow these format rules:

  • Frame: MAP_FRAME_TYPE='plain', MAP_FRAME_PEN='1p,black'. Never draw frame twice (no basemap + plot both setting frame).
  • Panel labels: UPPERCASE letter (A, B, C...) in top-left corner inside frame, no parentheses, bold ≥10p, square box (white fill + 0.8p black pen, clearance='1.5p/1.5p/1.5p/1.5p'). Equal distance from left and top borders. Title text (e.g., "East") centered at top separately.
  • Tick labels: Only on left (W) and bottom (S) edges.
  • Colorbar: Horizontal at bottom, with unit label, enough offset from axis labels (+o0c/0.8c).
  • Color scale: vik for diverging displacement, sequential: inferno or roma.
  • Depth axes read POSITIVE, down (hard rule for fault/cross-section/3D figures): the depth axis must be labelled 0, 20, 40 … (positive kilometres), never 0, -20, -40. Store depth as a positive value (depth increases downward), set the region depth range to [0, zmax], and flip the axis with a negative height: 2D cross-sections use projection="X<w>c/-<h>c" (negative height → y increases downward); 3D perspective plots use zsize="-<h>c" (negative → deep at the bottom, labels positive). Never emit negative z data just to get "up = positive" — that puts minus signs on the depth axis. When you flip a 3D z-axis this way, any in-plane up-dip vector's vertical component also flips sign (up-dip now means decreasing positive depth).
  • No double frames: subplot handles frame → do NOT call basemap(frame=) inside panels.
  • Self-check: After generating each figure, read/inspect it. Fix double frames, clipped content, inconsistent labels before showing to user.

Quick start

import pygmt

fig = pygmt.Figure()
with pygmt.config(MAP_FRAME_TYPE="plain", MAP_FRAME_PEN="1p,black"):
    fig.basemap(region=[-125, -114, 32, 42], projection="M12c", frame=["WSne", "xaf", "yaf"])
    fig.coast(land="gray90", water="white", shorelines="0.5p,black")
    pygmt.makecpt(cmap="vik", series=[-10, 10])         # sets the session CPT
    fig.plot(x=[-119.5], y=[35.3], style="c0.3c", fill=[5.0], cmap=True, pen="0.5p,black")
    fig.colorbar(frame="x+lVelocity (mm/yr)", position="JBC+w8c/0.4c+h+o0c/0.8c")
fig.savefig("map.png", dpi=300, crop=True)

fig.show() previews; fig.savefig("f.pdf") (vector) or .png/.tif (raster, pass dpi=).

The three universal parameters

Every map command takes these; the first command sets them and later commands reuse them.

  • region (R) — [W, E, S, N] (note order: West, East, South, North). "g"/"d" = global; "JP" = ISO country code; append +r for corner coordinates.
  • projection (J) — code[params/]width, width must carry a unit (c/i/p): "M12c" Mercator, "X10c/6c" linear, "L-100/35/33/45/12c" Lambert conic, "G-120/40/12c" orthographic.
  • frame (B) — ["WSne", "xaf", "yaf+lLabel", "+tTitle"]. Uppercase edge = axis+ticks+annotations, lowercase = no annotations. a=annotation/major, f=minor tick, g=grid. Axis labels +l work only on Cartesian (X) projections, not geographic.

Choosing a projection / CPT (cheat sheet)

NeedProjection
Region map (low–mid lat, InSAR/swarm)M Mercator "M12c"
Continental, low distortionL Lambert conic "L lon0/lat0/lat1/lat2/w"
Locator inset / hemisphereG orthographic "G lon0/lat0/w"
GlobalW Mollweide / N Robinson / H Hammer
Depth/distance sectionX linear, negative height → y points down: "X12c/-4c"
Data kindCPT
Diverging (displacement, velocity, anomaly; zero-centered)vik, roma, broc, polar
Sequential scalarinferno, roma, batlow, viridis, turbo
Topographygeo (land), oleron/relief (land+ocean), gray (hillshade)
Cyclic (wrapped phase, azimuth)romaO, vikO

Make a CPT with pygmt.makecpt(cmap="vik", series=[-50, 50, 5]) — with no output= it becomes the session CPT, so subsequent grdimage/plot(cmap=True)/colorbar use it automatically. Histogram-equalize a grid's colors with pygmt.grd2cpt(grid=g, cmap="geo").

House style (enforce on every figure)

These are the skill's opinionated defaults — override them when a target journal says otherwise:

  1. Wrap drawing in with pygmt.config(MAP_FRAME_TYPE="plain", MAP_FRAME_PEN="1p,black"):. Never draw the frame twice (subplot/basemap handles it — don't re-pass frame= inside panels).
  2. Tick labels only on W (left) and S (bottom) edges → frame=["WSne", ...].
  3. Panel labels: UPPERCASE letter (A, B, C…), no parentheses, bold ≥10p, in a square white box (white fill + 0.8p black pen), equal offset from left and top:
    fig.text(position="TL", text="A", font="12p,Helvetica-Bold,black",
             justify="TL", offset="j0.2c", fill="white", pen="0.8p,black", clearance="1.5p/1.5p")
    
  4. Colorbar: horizontal at bottom, with unit label, generous offset from axis labels: position="JBC+w8c/0.4c+h+o0c/0.8c", frame="x+lQuantity (unit)".
  5. Diverging displacement → vik; sequential → inferno/roma.
  6. Raster resolution — no moiré (hard rule): when imaging fringe/speckle grids (wrapped interferograms, coherence), the rendered panel pixel count MUST be ≥ the grid's native columns: panel_cm × dpi / 2.54 >= n_columns. Default: single panel ≥16 cm at dpi=400 (GMT modern mode: gmt begin fig png E400). If a multi-panel layout would undersample, split into single-panel figures instead of shrinking. Undersampled dense fringes alias into moiré ripples — a recurring failure mode on ScanSAR wrapped-phase panels.
  7. Self-check: after saving, Read the output image and fix double frames, clipped content, inconsistent labels, or moiré/grid-like false texture in dense-fringe areas before showing the user.

Workflow for a new figure

  1. Decide region, projection, and CPT from the cheat sheet above.
  2. Pick the closest template in GALLERY.md or scripts/ and adapt it.
  3. Draw inside the house-style config block; build bottom layer → top layer (relief → coast → data → annotations → colorbar).
  4. fig.savefig(..., dpi=300) (dpi=400+ and check the pixel-count rule for fringe/speckle rasters), then Read the image and self-check (step 7 above).

References

  • REFERENCE.md — complete API: figure lifecycle, coast/plot/text, grdimage/grdcontour/grdview, makecpt/colorbar, datasets, grid processing (grdcut/grdgradient/grdtrack/surface…), xarray, meca, velo, project, subplot/inset/shift_origin/legend.
  • GOTCHAS.md — community-sourced pitfalls the API docs don't mention: region/dateline/projection traps, session-CPT overwrite & "no z-slices" error, xarray gtype/registration reset after arithmetic, grdimage-all-black, earth_relief cache, font-config scope, multi-panel colorbars, 0.x parameter renames, Ghostscript/conda install fixes. §8 field-tested traps: inset basemap drops annotations/labels (draw manually or use shift_origin); inset forces a default frame so box="+p" makes a double frame; meca +m Mw-scaling is invisible (size beachballs manually); meca(cmap=True) on negative depths segfaults; hand-written rasterio tif → grdimage segfault (use xarray instead); shading dimension mismatch (grdsample first); inset colorbar label overflow (+mal); fig.plot(fill="white") adds a stray black border. §9 vector arrowheads (style="v"): +g/+h are NOT broken — a head only appears if you also pass +e/+b, a vector shorter than the head length silently loses its head (fix with +n<len>), and +h near 1 degenerates the head so the fill vanishes. Read this when a PyGMT figure misbehaves or errors unexpectedly.
  • CRAFT.md — community publication-craft: painter-model layer order, scientific colormaps (diverging must be zero-centered & symmetric), hillshade azimuth conventions, scalebar/compass/graticule, multi-panel alignment & shared colorbars, vector-vs-raster export, seismotectonic layering. Read this to make a figure look good and submission-ready.
  • GALLERY.md — 13 ready-to-adapt scenario templates (basemap, multi-panel, inset, classified scatter, velocity field, histogram, rose, 3D, contours, cross-section, dual-axis).
  • scripts/ — runnable, parameterized templates: displacement_map.py, seismicity_map.py, cross_section.py, multipanel_components.py. Each runs standalone with demo data; edit the CONFIG block at the top.

Skills relacionados