1.4. Plot Styles#

pycsamt.api.style is the single visual-style layer behind every pycsamt.emtools figure – impedance response curves, multi-station overlays, static-shift before/after pairs, raw-data traces, phase-tensor ellipses, and rose diagrams alike. A plot function never hard-codes a colour: it reads from the package-level PYCSAMT_STYLE singleton unless the caller overrides it, so changing one setting once changes how every subsequent figure looks.

This page walks through each of the six style families the singleton holds, the named presets that switch all of them at once, and the dotted-path convention (shared with every other pycsamt.api family; see API configuration) used to tune individual attributes. All figures below come from the real WILLY AMT line (data/AMT/WILLY_DATA/L18PLT):

>>> from pathlib import Path
>>> from pycsamt.emtools import ensure_sites
>>> from pycsamt.api.style import PYCSAMT_STYLE

>>> edi_dir = Path("data/AMT/WILLY_DATA/L18PLT")
>>> sites = ensure_sites(
...     edi_dir,
...     recursive=True,
...     on_dup="replace",
...     strict=False,
...     verbose=0,
... )
>>> len(sites)
28

The singleton prints as a compact, one-attribute-per-line summary – useful both for a sanity check after configuring it and as a map of what follows on this page:

>>> print(PYCSAMT_STYLE)
PyCSAMTStyle
  rose.bar_style          = 'gradient'
  rose.cmap               = 'YlOrRd'
  rose.compass_labels     = 'NESW'
  rose.show_mean          = True
  rose.show_secondary     = True
  multiline.mode          = 'gradient'
  multiline.base_color    = 'blue'
  multiline.dark/light    = 0.85/0.25
  mt.xy  color='#1f77b4'  marker='o'
  mt.yx  color='#d62728'  marker='s'
  mt.te  color='#1f77b4'  ls='-'
  mt.tm  color='#d62728'  ls='-'
  correction.before  color='#1f77b4'  ls='--'  label='before'
  correction.after   color='#d62728'  ls='-'  label='after'
  raw  color='black'  marker='.'  ls=':'  lw=0.8
  pt_ellipse  normalise_by='cell'  scale=0.85  c_by='skew'  cmap='RdBu_r'
  pt_ellipse  edgecolor='k'  lw=0.2  alpha=0.92  mark_3d=True  skew_thresh=3.0

Six sections, one singleton: rose (RoseStyle), multiline (MultilineStyle), mt (MTComponentStyle), correction (CorrectionStyle), raw (RawDataStyle), and pt_ellipse (PhaseTensorEllipseStyle).

1.4.1. Named Presets#

Four presets are registered out of the box: "pycsamt" (the default – gradient rose bars, blue/red MT colours), "publication" (grayscale, open/filled-marker contrast for print), "dark" (brighter hues for a dark-background notebook or slide), and "modem" (the ModEM colour convention – pure blue/red observed, green/magenta dotted predicted). PYCSAMT_STYLE.use() (or the module-level use_style() shortcut) swaps every section that a preset defines in one call:

>>> from pycsamt.api.style import use_style, reset_style
>>> from pycsamt.emtools.tensor import plot_phase_tensor_rose
>>> import matplotlib.pyplot as plt

>>> fig, axes = plt.subplots(
...     1, 3, figsize=(13, 4.6), subplot_kw={"projection": "polar"}
... )
>>> for ax, preset in zip(axes, ["pycsamt", "publication", "dark"]):
...     use_style(preset)
...     _ = plot_phase_tensor_rose(
...         sites, style=PYCSAMT_STYLE.rose, ax=ax, title=preset, verbose=0
...     )
>>> reset_style()
>>> fig.tight_layout()
The same phase-tensor strike rose drawn under the pycsamt, publication, and dark presets.

One dataset, three presets: "pycsamt" (gradient red/orange, NESW compass), "publication" (grayscale, degree compass, dotted conjugate line), and "dark" (same rose preset as "pycsamt" here, since "dark" only overrides MT and multiline colours).#

"modem" is deliberately partial: its preset spec only defines an "mt" section, so calling use_style() with it leaves whatever rose, multiline, correction, raw, and phase-tensor settings were already active untouched. Applying presets is therefore compounding, not a full reset to that preset’s own defaults:

>>> reset_style()
>>> use_style("dark")
>>> PYCSAMT_STYLE.rose.cmap, PYCSAMT_STYLE.mt.xy.color
('YlOrRd', '#74b9ff')

>>> use_style("modem")
>>> PYCSAMT_STYLE.rose.cmap, PYCSAMT_STYLE.mt.xy.color
('YlOrRd', '#0000FF')

The rose colormap carries over from "dark" (which reuses the "pycsamt" rose) because "modem" never touches it; only mt.xy changed, to the ModEM blue. Call PYCSAMT_STYLE.reset() first if a clean, fully-defined preset is what’s actually wanted:

>>> reset_style()
>>> PYCSAMT_STYLE.rose.cmap, PYCSAMT_STYLE.mt.xy.color
('YlOrRd', '#1f77b4')

1.4.2. MT Component Colours#

MTComponentStyle gives every impedance component or mode (xy, yx, xx, yy, te, tm, det) a consistent colour, marker, and line style, exposed through plot_kwargs() and errorbar_kwargs(). plot_response_tipper() uses it automatically for every component it draws:

>>> from pycsamt.emtools.plot import plot_response_tipper

>>> use_style("pycsamt")
>>> fig_a = plot_response_tipper(
...     sites,
...     stations=["18-001A"],
...     components=("xy", "yx"),
...     tipper_components=(),
...     ncols_groups=1,
...     verbose=0,
... )

>>> use_style("publication")
>>> fig_b = plot_response_tipper(
...     sites,
...     stations=["18-001A"],
...     components=("xy", "yx"),
...     tipper_components=(),
...     ncols_groups=1,
...     verbose=0,
... )
>>> reset_style()

tipper_components=() is passed because L18PLT is an AMT line with no vertical-field channel; ncols_groups=1 keeps the figure sized for a single station instead of the function’s default three-wide grouping.

The two panels are the same station and the same data; only PYCSAMT_STYLE.mt changed between the calls. "pycsamt" keeps the package’s blue-XY/red-YX convention with hollow markers on both; the grayscale "publication" preset instead distinguishes XY from YX by marker fill (hollow circles vs. solid squares) so the two components still read apart once colour is unavailable.

Reach for a single component’s kwargs directly when writing a custom plot function rather than calling a package one:

>>> PYCSAMT_STYLE.mt.xy.plot_kwargs()
{'color': '#1f77b4', 'ls': '-', 'lw': 1.5, 'marker': 'o', 'ms': 4.0, 'mfc': 'white', 'mew': 1.2, 'alpha': 0.9, 'label': 'XY'}

1.4.3. Multiline Gradient Colours#

MultilineStyle colours a group of related lines – typically one per station along a profile – as shades of one hue running from dark (line 0) to light (the last line), so the group reads as one coherent family without needing a categorical colour cycle. colors(n) returns the n RGBA shades directly; line_kwargs(idx, n) is the convenience form for a loop, combining colour with the style’s default line width and alpha:

>>> ms = PYCSAMT_STYLE.multiline
>>> [round(float(c), 3) for c in ms.colors(4)[0]]
[0.05, 0.342, 0.631, 1.0]
>>> ms.line_kwargs(0, 4)["lw"], ms.line_kwargs(0, 4)["alpha"]
(1.5, 0.85)

Overlaying four real WILLY stations’ XY apparent resistivity shows the gradient in practice – darkest line first, lightest last, regardless of plotting order:

>>> import numpy as np

>>> stations = ["18-001A", "18-002U", "18-003A", "18-004A"]
>>> n = len(stations)
>>> fig, ax = plt.subplots(figsize=(6.4, 4.4))
>>> for idx, name in enumerate(stations):
...     st = sites[name]
...     period = 1.0 / st.freq
...     rho_xy = st.rho[:, 0, 1]
...     order = np.argsort(period)
...     _ = ax.plot(
...         period[order], rho_xy[order], **ms.line_kwargs(idx, n, label=name)
...     )
>>> _ = ax.set_xscale("log")
>>> _ = ax.set_yscale("log")
>>> _ = ax.legend(fontsize=8)
Four real WILLY stations' apparent-resistivity curves overlaid in a blue gradient, dark to light.

Station 18-001A (plotted first, darkest) through 18-004A (plotted last, lightest). The gradient is keyed to plotting order, not to any property of the data – reorder the stations list to change which curve is darkest.#

Switch the base hue, or select an explicit colormap, with configure():

>>> from pycsamt.api.style import configure_style
>>> configure_style(multiline__base_color="red")
>>> PYCSAMT_STYLE.multiline.cmap is None
True
>>> reset_style()

Leaving cmap unset lets base_color drive an automatic colormap lookup ("red" -> a red sequential colormap); setting cmap explicitly takes precedence over that lookup.

1.4.4. Before/After Correction Pairs#

Every 1-D correction workflow in pyCSAMT – static shift, near-field, noise removal – draws its uncorrected and corrected curves with the same CorrectionStyle pair: blue dashed for before, red solid for after, deliberately reusing the mt.xy and mt.yx colours so the whole package stays visually coherent. plot_ss_station_curves() reads it directly:

>>> from pycsamt.emtools.ss import correct_ss_ama, plot_ss_station_curves

>>> corrected = correct_ss_ama(sites, verbose=0)
>>> fig, ax = plt.subplots(figsize=(6.4, 4.2))
>>> _ = plot_ss_station_curves(sites, corrected, station="18-001A", ax=ax)
Before and after AMA static-shift correction curves for station 18-001A.

correct_ss_ama()’s real spatial-trend correction applied to 18-001A – the AMA method estimated this station’s determinant resistivity as offset high relative to its neighbours and scaled it down accordingly. See Static-Shift Correction for the correction method itself; this page is only about the pairing convention.#

Change both curves’ colours together, or just one, with the same dotted paths used everywhere else:

>>> configure_style(
...     correction__before__color="#444444",
...     correction__after__color="#000000",
... )
>>> PYCSAMT_STYLE.correction.before.color, PYCSAMT_STYLE.correction.after.color
('#444444', '#000000')
>>> reset_style()

1.4.5. Raw Data Style#

RawDataStyle is the package’s convention for unprocessed observations: a restrained black, thin, dotted trace with small markers, visually distinct from any processed-data colour so a raw curve can never be mistaken for a modelled or QC-passed one. plot_response_tipper() switches to it automatically whenever it is called with raw=True (unless force_style=True asks it to keep the component colours instead):

>>> use_style("pycsamt")
>>> fig_raw = plot_response_tipper(
...     sites,
...     stations=["18-001A"],
...     components=("xy", "yx"),
...     tipper_components=(),
...     raw=True,
...     ncols_groups=1,
...     verbose=0,
... )
>>> reset_style()
Station 18-001A response drawn in the black dotted raw-data style instead of MT component colours.

The same station and components as the MT-colours figure above, drawn with raw=True. Colour alone now tells a reader whether a given panel is showing raw or component-styled data, without reading any title or legend text.#

1.4.6. Phase-Tensor Ellipse Style#

PhaseTensorEllipseStyle controls how plot_phase_tensor_psection() and its sibling phase-tensor figures encode five quantities at once per ellipse – size, aspect ratio, rotation, fill colour, and a 3-D border flag. The parameter-by-parameter tour of that encoding, including real WILLY pseudo-sections, lives in Phase Tensor And Impedance Tensor Tools; this page only covers the persistent-configuration angle.

c_by selects which scalar drives the fill colour, and resolve_cmap() looks up a sensible default colormap for it when cmap itself is left unset – a diverging map for signed quantities like skew, a sequential one for magnitudes like ellipticity:

>>> es = PYCSAMT_STYLE.pt_ellipse
>>> es.normalise_by, es.scale, es.c_by, es.resolve_cmap()
('cell', 0.85, 'skew', 'RdBu_r')

>>> configure_style(pt_ellipse__c_by="ellipt", pt_ellipse__cmap=None)
>>> PYCSAMT_STYLE.pt_ellipse.resolve_cmap()
'viridis'

>>> reset_style()

Any plot call can still override cmap explicitly, in which case the c_by-based lookup above never runs – the explicit value always wins.

1.4.7. Rose Diagrams#

plot_phase_tensor_rose() and plot_strike_rose() share RoseStyle through their own style keyword, which is resolved by resolve_rose_style(). That keyword defaults to the literal string "pycsamt" rather than to None/the package singleton, which matters as soon as PYCSAMT_STYLE.rose has been configured: a bare call still resolves the named "pycsamt" preset from resolve_rose_style’s own table, ignoring whatever the singleton currently holds. Passing style=PYCSAMT_STYLE.rose explicitly is what makes a rose figure track global configuration:

>>> configure_style(rose__compass_labels="degrees", rose__show_secondary=False)
>>> fig, axes = plt.subplots(
...     1, 2, figsize=(10, 4.6), subplot_kw={"projection": "polar"}
... )
>>> _ = plot_phase_tensor_rose(
...     sites,
...     style="pycsamt",
...     ax=axes[0],
...     title='style="pycsamt" (ignores singleton)',
...     verbose=0,
... )
>>> _ = plot_phase_tensor_rose(
...     sites,
...     style=PYCSAMT_STYLE.rose,
...     ax=axes[1],
...     title="style=PYCSAMT_STYLE.rose",
...     verbose=0,
... )
>>> reset_style()
Two phase-tensor roses from the same call site; only the right one reflects a configure_style change.

Both panels come from calls made after configure_style(rose__compass_labels="degrees", rose__show_secondary=False). The left panel still shows a NESW compass and the dashed conjugate line because style="pycsamt" re-resolves the named preset from scratch; the right panel shows the degree compass and no conjugate line, because it was handed the singleton itself.#

The three named-preset strings ("pycsamt"/"pycsamt-rose", "minimal", "publication") documented on RoseStyle remain available as style= values regardless of what PYCSAMT_STYLE.rose currently holds, since they are resolved independently of the singleton in the same way.

1.4.8. Contour And Station-Guide Overlays#

Raster and pseudosection overlays use a separate configuration family because they describe spatial structure rather than data-series identity. The live pycsamt.api.PYCSAMT_CONTOUR configuration supplies contour levels, colour, width, line style, opacity, and optional labels to compatible plots. For example, pycsamt.emtools.plot_survey_fingerprint() uses the visible "review" contour preset by default:

>>> from pycsamt.api import configure_contour
>>> configure_contour(linewidths=0.9, colors="#202020", alpha=0.85)
>>> fig = plot_survey_fingerprint(
...     sites,
...     render="imshow",
...     station_grid=True,
...     station_grid_kws={
...         "color": "white",
...         "linewidth": 0.65,
...         "linestyle": ":",
...         "alpha": 0.75,
...     },
... )

The contour configuration is persistent; station_grid_kws is deliberately local to the plot call. This keeps survey-dependent station density from silently changing unrelated figures. See Contour overlays for presets, temporary contexts, labels, override precedence, and interpretation guidance.

1.4.9. Configuring And Sharing Styles#

Every section above is reachable through the same three entry points as every other pycsamt.api family (see API configuration for the general pattern): PYCSAMT_STYLE.configure() (or the module-level configure_style()) for dotted-path edits, direct attribute assignment for a single value, and PYCSAMT_STYLE.context() for a temporary change scoped to one block:

>>> PYCSAMT_STYLE.mt.xy.color
'#1f77b4'

>>> with PYCSAMT_STYLE.context("publication", mt__xy__lw=2.5):
...     PYCSAMT_STYLE.mt.xy.color, PYCSAMT_STYLE.mt.xy.lw
('black', 2.5)

>>> PYCSAMT_STYLE.mt.xy.color, PYCSAMT_STYLE.mt.xy.lw
('#1f77b4', 1.5)

The context manager restores every section (not just the ones the preset/kw arguments touched) to exactly what it held before the with block, even if the block raises – the same restore-on-exit contract as PYCSAMT_MESH.context() on Mesh Display. It is the right tool for a single figure that needs a different look without disturbing the rest of a script or notebook session. For a change meant to last the whole process – a batch run producing only publication figures, for instance – call use_style() or configure_style() once near the top of the script instead, and reset with reset_style() only if a later cell or step needs package defaults back.

1.4.10. Next Steps#

  • API configuration for how the style family fits alongside every other pycsamt.api configuration family.

  • Contour overlays for persistent contour presets, per-call overrides, and station-aligned vertical guides. Contours describe equal-value structure; station guides identify the shared sampling column across stacked panels.

  • Phase Tensor And Impedance Tensor Tools for the phase-tensor ellipse encoding itself, independent of styling.

  • Static-Shift Correction for the static-shift correction methods behind the before/after figure on this page.

  • Mesh Display for the equivalent preset system used by computational and inversion mesh figures.