Skip to content

Commit eb299ee

Browse files
authored
Merge branch 'main' into add-gallery
2 parents 6edf141 + 8edd694 commit eb299ee

File tree

14 files changed

+1407
-51
lines changed

14 files changed

+1407
-51
lines changed

docs/colorbars_legends.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -469,3 +469,44 @@
469469
ax = axs[1]
470470
ax.legend(hs2, loc="b", ncols=3, center=True, title="centered rows")
471471
axs.format(xlabel="xlabel", ylabel="ylabel", suptitle="Legend formatting demo")
472+
# %% [raw] raw_mimetype="text/restructuredtext"
473+
# .. _ug_guides_decouple:
474+
#
475+
# Decoupling legend content and location
476+
# --------------------------------------
477+
#
478+
# Sometimes you may want to generate a legend using handles from specific axes
479+
# but place it relative to other axes. In UltraPlot, you can achieve this by passing
480+
# both the `ax` and `ref` keywords to :func:`~ultraplot.figure.Figure.legend`
481+
# (or :func:`~ultraplot.figure.Figure.colorbar`). The `ax` keyword specifies the
482+
# axes used to generate the legend handles, while the `ref` keyword specifies the
483+
# reference axes used to determine the legend location.
484+
#
485+
# For example, to draw a legend based on the handles in the second row of subplots
486+
# but place it below the first row of subplots, you can use
487+
# ``fig.legend(ax=axs[1, :], ref=axs[0, :], loc='bottom')``. If ``ref`` is a list
488+
# of axes, UltraPlot intelligently infers the span (width or height) and anchors
489+
# the legend to the appropriate outer edge (e.g., the bottom-most axis for ``loc='bottom'``
490+
# or the right-most axis for ``loc='right'``).
491+
492+
# %%
493+
import numpy as np
494+
495+
import ultraplot as uplt
496+
497+
fig, axs = uplt.subplots(nrows=2, ncols=2, refwidth=2, share=False)
498+
axs.format(abc="A.", suptitle="Decoupled legend location demo")
499+
500+
# Plot data on all axes
501+
state = np.random.RandomState(51423)
502+
data = (state.rand(20, 4) - 0.5).cumsum(axis=0)
503+
for ax in axs:
504+
ax.plot(data, cycle="mplotcolors", labels=list("abcd"))
505+
506+
# Legend 1: Content from Row 2 (ax=axs[1, :]), Location below Row 1 (ref=axs[0, :])
507+
# This places a legend describing the bottom row data underneath the top row.
508+
fig.legend(ax=axs[1, :], ref=axs[0, :], loc="bottom", title="Data from Row 2")
509+
510+
# Legend 2: Content from Row 1 (ax=axs[0, :]), Location below Row 2 (ref=axs[1, :])
511+
# This places a legend describing the top row data underneath the bottom row.
512+
fig.legend(ax=axs[0, :], ref=axs[1, :], loc="bottom", title="Data from Row 1")

docs/stats.py

Lines changed: 176 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -79,9 +79,10 @@
7979
shadedata = np.percentile(data, (25, 75), axis=0) # dark shading
8080

8181
# %%
82-
import ultraplot as uplt
8382
import numpy as np
8483

84+
import ultraplot as uplt
85+
8586
# Loop through "vertical" and "horizontal" versions
8687
varray = [[1], [2], [3]]
8788
harray = [[1, 1], [2, 3], [2, 3]]
@@ -164,10 +165,11 @@
164165
# with the same keywords used for :ref:`on-the-fly error bars <ug_errorbars>`.
165166

166167
# %%
167-
import ultraplot as uplt
168168
import numpy as np
169169
import pandas as pd
170170

171+
import ultraplot as uplt
172+
171173
# Sample data
172174
N = 500
173175
state = np.random.RandomState(51423)
@@ -221,9 +223,10 @@
221223
# will use the same algorithm for kernel density estimation as the `kde` commands.
222224

223225
# %%
224-
import ultraplot as uplt
225226
import numpy as np
226227

228+
import ultraplot as uplt
229+
227230
# Sample data
228231
M, N = 300, 3
229232
state = np.random.RandomState(51423)
@@ -244,9 +247,10 @@
244247
)
245248

246249
# %%
247-
import ultraplot as uplt
248250
import numpy as np
249251

252+
import ultraplot as uplt
253+
250254
# Sample data
251255
N = 500
252256
state = np.random.RandomState(51423)
@@ -284,3 +288,171 @@
284288
px = ax.panel("t", space=0)
285289
px.hist(x, bins, color=color, fill=True, ec="k")
286290
px.format(grid=False, ylocator=[], title=title, titleloc="l")
291+
292+
293+
# %% [raw] raw_mimetype="text/restructuredtext"
294+
# .. _ug_ridgeline:
295+
#
296+
# Ridgeline plots
297+
# ---------------
298+
#
299+
# Ridgeline plots (also known as joyplots) visualize distributions of multiple
300+
# datasets as stacked, overlapping density curves. They are useful for comparing
301+
# distributions across categories or over time. UltraPlot provides
302+
# :func:`~ultraplot.axes.PlotAxes.ridgeline` and :func:`~ultraplot.axes.PlotAxes.ridgelineh`
303+
# for creating vertical and horizontal ridgeline plots.
304+
#
305+
# Ridgeline plots support two display modes: smooth kernel density estimation (KDE)
306+
# by default, or histograms with the `hist` keyword. They also support two positioning
307+
# modes: categorical positioning with evenly-spaced ridges (traditional joyplots),
308+
# or continuous positioning where ridges are anchored to specific physical coordinates
309+
# (useful for scientific plots like depth profiles or time series).
310+
311+
# %%
312+
import numpy as np
313+
314+
import ultraplot as uplt
315+
316+
# Sample data with different distributions
317+
state = np.random.RandomState(51423)
318+
data = [state.normal(i, 1, 500) for i in range(5)]
319+
labels = [f"Distribution {i+1}" for i in range(5)]
320+
321+
# Create figure with two subplots
322+
fig, axs = uplt.subplots(ncols=2, figsize=(10, 5))
323+
axs.format(
324+
abc="A.", abcloc="ul", grid=False, suptitle="Ridgeline plots: KDE vs Histogram"
325+
)
326+
327+
# KDE ridgeline (default)
328+
axs[0].ridgeline(
329+
data, labels=labels, overlap=0.6, cmap="viridis", alpha=0.7, linewidth=1.5
330+
)
331+
axs[0].format(title="Kernel Density Estimation", xlabel="Value")
332+
333+
# Histogram ridgeline
334+
axs[1].ridgeline(
335+
data,
336+
labels=labels,
337+
overlap=0.6,
338+
cmap="plasma",
339+
alpha=0.7,
340+
hist=True,
341+
bins=20,
342+
linewidth=1.5,
343+
)
344+
axs[1].format(title="Histogram", xlabel="Value")
345+
346+
# %%
347+
import numpy as np
348+
349+
import ultraplot as uplt
350+
351+
# Sample data
352+
state = np.random.RandomState(51423)
353+
data1 = [state.normal(i * 0.5, 1, 400) for i in range(6)]
354+
data2 = [state.normal(i, 0.8, 400) for i in range(4)]
355+
labels1 = [f"Group {i+1}" for i in range(6)]
356+
labels2 = ["Alpha", "Beta", "Gamma", "Delta"]
357+
358+
# Create figure with vertical and horizontal orientations
359+
fig, axs = uplt.subplots(ncols=2, figsize=(10, 5))
360+
axs.format(abc="A.", abcloc="ul", grid=False, suptitle="Ridgeline plot orientations")
361+
362+
# Vertical ridgeline (default - ridges are horizontal)
363+
axs[0].ridgeline(
364+
data1, labels=labels1, overlap=0.7, cmap="coolwarm", alpha=0.8, linewidth=2
365+
)
366+
axs[0].format(title="Vertical (ridgeline)", xlabel="Value")
367+
368+
# Horizontal ridgeline (ridges are vertical)
369+
axs[1].ridgelineh(
370+
data2, labels=labels2, overlap=0.6, facecolor="skyblue", alpha=0.7, linewidth=1.5
371+
)
372+
axs[1].format(title="Horizontal (ridgelineh)", ylabel="Value")
373+
374+
375+
# %% [raw] raw_mimetype="text/restructuredtext"
376+
# .. _ug_ridgeline_continuous:
377+
#
378+
# Continuous positioning
379+
# ^^^^^^^^^^^^^^^^^^^^^^
380+
#
381+
# For scientific applications, ridgeline plots can use continuous (coordinate-based)
382+
# positioning where each ridge is anchored to a specific numerical coordinate along
383+
# the axis. This is useful for visualizing how distributions change with physical
384+
# variables like depth, time, altitude, or redshift. Use the `positions` parameter
385+
# to specify coordinates, and optionally the `height` parameter to control ridge height
386+
# in axis units.
387+
388+
# %%
389+
import numpy as np
390+
391+
import ultraplot as uplt
392+
393+
# Simulate ocean temperature data at different depths
394+
state = np.random.RandomState(51423)
395+
depths = [0, 10, 25, 50, 100] # meters
396+
mean_temps = [25, 22, 18, 12, 8] # decreasing with depth
397+
data = [state.normal(temp, 2, 400) for temp in mean_temps]
398+
labels = ["Surface", "10m", "25m", "50m", "100m"]
399+
400+
fig, ax = uplt.subplots(figsize=(8, 6))
401+
ax.ridgeline(
402+
data,
403+
labels=labels,
404+
positions=depths,
405+
height=8, # height in axis units
406+
cmap="coolwarm",
407+
alpha=0.75,
408+
linewidth=2,
409+
)
410+
ax.format(
411+
title="Ocean Temperature Distribution by Depth",
412+
xlabel="Temperature (°C)",
413+
ylabel="Depth (m)",
414+
yreverse=True, # depth increases downward
415+
grid=True,
416+
gridcolor="gray5",
417+
gridalpha=0.3,
418+
)
419+
420+
# %%
421+
import numpy as np
422+
423+
import ultraplot as uplt
424+
425+
# Simulate climate data over time
426+
state = np.random.RandomState(51423)
427+
years = [1950, 1970, 1990, 2010, 2030]
428+
mean_temps = [14.0, 14.2, 14.5, 15.0, 15.5] # warming trend
429+
data = [state.normal(temp, 0.8, 500) for temp in mean_temps]
430+
431+
fig, axs = uplt.subplots(ncols=2, figsize=(11, 5))
432+
axs.format(abc="A.", abcloc="ul", suptitle="Categorical vs Continuous positioning")
433+
434+
# Categorical positioning (default)
435+
axs[0].ridgeline(
436+
data, labels=[str(y) for y in years], overlap=0.6, cmap="fire", alpha=0.7
437+
)
438+
axs[0].format(
439+
title="Categorical (traditional joyplot)", xlabel="Temperature (°C)", grid=False
440+
)
441+
442+
# Continuous positioning
443+
axs[1].ridgeline(
444+
data,
445+
labels=[str(y) for y in years],
446+
positions=years,
447+
height=15, # height in year units
448+
cmap="fire",
449+
alpha=0.7,
450+
)
451+
axs[1].format(
452+
title="Continuous (scientific)",
453+
xlabel="Temperature (°C)",
454+
ylabel="Year",
455+
grid=True,
456+
gridcolor="gray5",
457+
gridalpha=0.3,
458+
)

ultraplot/axes/base.py

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -317,9 +317,9 @@
317317
The axes title. Can optionally be a sequence strings, in which case
318318
the title will be selected from the sequence according to `~Axes.number`.
319319
abc : bool or str or sequence, default: :rc:`abc`
320-
The "a-b-c" subplot label style. Must contain the character ``a`` or ``A``,
320+
The "a-b-c" subplot label style. Must contain the character `a` or `A`,
321321
for example ``'a.'``, or ``'A'``. If ``True`` then the default style of
322-
``'a'`` is used. The ``a`` or ``A`` is replaced with the alphabetic character
322+
``'a'`` is used. The `a` or ``A`` is replaced with the alphabetic character
323323
matching the `~Axes.number`. If `~Axes.number` is greater than 26, the
324324
characters loop around to a, ..., z, aa, ..., zz, aaa, ..., zzz, etc.
325325
Can also be a sequence of strings, in which case the "a-b-c" label will be selected sequentially from the list. For example `axs.format(abc = ["X", "Y"])` for a two-panel figure, and `axes[3:5].format(abc = ["X", "Y"])` for a two-panel subset of a larger figure.
@@ -341,8 +341,8 @@
341341
upper left inside axes ``'upper left'``, ``'ul'``
342342
lower left inside axes ``'lower left'``, ``'ll'``
343343
lower right inside axes ``'lower right'``, ``'lr'``
344-
left of y axis ```'outer left'``, ``'ol'``
345-
right of y axis ```'outer right'``, ``'or'``
344+
left of y axis ``'outer left'``, ``'ol'``
345+
right of y axis ``'outer right'``, ``'or'``
346346
======================== ============================
347347
348348
abcborder, titleborder : bool, default: :rc:`abc.border` and :rc:`title.border`
@@ -370,16 +370,15 @@
370370
abctitlepad : float, default: :rc:`abc.titlepad`
371371
The horizontal padding between a-b-c labels and titles in the same location.
372372
%(units.pt)s
373-
ltitle, ctitle, rtitle, ultitle, uctitle, urtitle, lltitle, lctitle, lrtitle \\
374-
: str or sequence, optional
373+
ltitle, ctitle, rtitle, ultitle, uctitle, urtitle, lltitle, lctitle, lrtitle : str or sequence, optional \\
375374
Shorthands for the below keywords.
376-
lefttitle, centertitle, righttitle, upperlefttitle, uppercentertitle, upperrighttitle, \\
375+
lefttitle, centertitle, righttitle, upperlefttitle, uppercentertitle, upperrighttitle : str or sequence, optional
377376
lowerlefttitle, lowercentertitle, lowerrighttitle : str or sequence, optional
378377
Additional titles in specific positions (see `title` for details). This works as
379378
an alternative to the ``ax.format(title='Title', titleloc=loc)`` workflow and
380379
permits adding more than one title-like label for a single axes.
381-
a, alpha, fc, facecolor, ec, edgecolor, lw, linewidth, ls, linestyle : default: \\
382-
:rc:`axes.alpha`, :rc:`axes.facecolor`, :rc:`axes.edgecolor`, :rc:`axes.linewidth`, '-'
380+
a, alpha, fc, facecolor, ec, edgecolor, lw, linewidth, ls, linestyle : default:
381+
:rc:`axes.alpha` (default: 1.0), :rc:`axes.facecolor` (default: white), :rc:`axes.edgecolor` (default: black), :rc:`axes.linewidth` (default: 0.6), -
383382
Additional settings applied to the background patch, and their
384383
shorthands. Their defaults values are the ``'axes'`` properties.
385384
"""
@@ -3646,7 +3645,7 @@ def colorbar(self, mappable, values=None, loc=None, location=None, **kwargs):
36463645
width or height (default is :rcraw:`colorbar.length`). For inset
36473646
colorbars, floats interpreted as em-widths and strings interpreted
36483647
by `~ultraplot.utils.units` (default is :rcraw:`colorbar.insetlength`).
3649-
width : unit-spec, default: :rc:`colorbar.width` or :rc:`colorbar.insetwidth
3648+
width : unit-spec, default: :rc:`colorbar.width` or :rc:`colorbar.insetwidth`
36503649
The colorbar width. For outer colorbars, floats are interpreted as inches
36513650
(default is :rcraw:`colorbar.width`). For inset colorbars, floats are
36523651
interpreted as em-widths (default is :rcraw:`colorbar.insetwidth`).

ultraplot/axes/geo.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@
210210
must be passed to `~ultraplot.constructor.Proj` instead.
211211
color : color-spec, default: :rc:`meta.color`
212212
The color for the axes edge. Propagates to `labelcolor` unless specified
213-
otherwise (similar to :func:`ultraplot.axes.CartesianAxes.format`).
213+
otherwise (similar to :func:`~ultraplot.axes.CartesianAxes.format`).
214214
gridcolor : color-spec, default: :rc:`grid.color`
215215
The color for the gridline labels.
216216
labelcolor : color-spec, default: `color` or :rc:`grid.labelcolor`

0 commit comments

Comments
 (0)