Skip to content

Commit 0e84fa4

Browse files
authored
Merge branch 'main' into theme-v2
2 parents e9877b5 + 4ff9a38 commit 0e84fa4

3 files changed

Lines changed: 1145 additions & 293 deletions

File tree

.github/workflows/build-ultraplot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,9 +136,9 @@ jobs:
136136
with:
137137
path: ./ultraplot/tests/baseline # The directory to cache
138138
# Key is based on OS, Python/Matplotlib versions, and the base commit SHA
139-
key: ${{ runner.os }}-baseline-base-v4-hs${{ env.PYTHONHASHSEED }}-${{ steps.baseline-ref.outputs.base_sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}
139+
key: ${{ runner.os }}-baseline-base-v5-hs${{ env.PYTHONHASHSEED }}-${{ steps.baseline-ref.outputs.base_sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}
140140
restore-keys: |
141-
${{ runner.os }}-baseline-base-v4-hs${{ env.PYTHONHASHSEED }}-${{ steps.baseline-ref.outputs.base_sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}-
141+
${{ runner.os }}-baseline-base-v5-hs${{ env.PYTHONHASHSEED }}-${{ steps.baseline-ref.outputs.base_sha }}-${{ inputs.python-version }}-${{ inputs.matplotlib-version }}-
142142
143143
# Conditional Baseline Generation (Only runs on cache miss)
144144
- name: Generate baseline from main

ultraplot/axes/base.py

Lines changed: 68 additions & 291 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,17 @@
4040
from .. import constructor
4141
from .. import legend as plegend
4242
from .. import ticker as pticker
43+
from ..colorbar import (
44+
UltraColorbar,
45+
_apply_inset_colorbar_layout,
46+
_determine_label_rotation,
47+
_get_axis_for,
48+
_get_colorbar_long_axis,
49+
_legacy_inset_colorbar_bounds,
50+
_reflow_inset_colorbar_frame,
51+
_register_inset_colorbar_reflow,
52+
_solve_inset_colorbar_bounds,
53+
)
4354
from ..config import rc
4455
from ..internals import (
4556
_kwargs_to_args,
@@ -1148,302 +1159,68 @@ def _add_colorbar(
11481159
center_levels=None,
11491160
**kwargs,
11501161
):
1151-
"""
1152-
The driver function for adding axes colorbars.
1153-
"""
1154-
# Parse input arguments and apply defaults
1155-
# TODO: Get the 'best' inset colorbar location using the legend algorithm
1156-
# and implement inset colorbars the same as inset legends.
1157-
grid = _not_none(
1158-
grid=grid, edges=edges, drawedges=drawedges, default=rc["colorbar.grid"]
1159-
) # noqa: E501
1160-
length = _not_none(length=length, shrink=shrink)
1161-
label = _not_none(title=title, label=label)
1162-
labelloc = _not_none(labelloc=labelloc, labellocation=labellocation)
1163-
locator = _not_none(ticks=ticks, locator=locator)
1164-
formatter = _not_none(ticklabels=ticklabels, formatter=formatter, format=format)
1165-
minorlocator = _not_none(minorticks=minorticks, minorlocator=minorlocator)
1166-
color = _not_none(c=c, color=color, default=rc["axes.edgecolor"])
1167-
linewidth = _not_none(lw=lw, linewidth=linewidth)
1168-
ticklen = units(_not_none(ticklen, rc["tick.len"]), "pt")
1169-
tickdir = _not_none(tickdir=tickdir, tickdirection=tickdirection)
1170-
tickwidth = units(_not_none(tickwidth, linewidth, rc["tick.width"]), "pt")
1171-
linewidth = units(_not_none(linewidth, default=rc["axes.linewidth"]), "pt")
1172-
ticklenratio = _not_none(ticklenratio, rc["tick.lenratio"])
1173-
tickwidthratio = _not_none(tickwidthratio, rc["tick.widthratio"])
1174-
rasterized = _not_none(rasterized, rc["colorbar.rasterized"])
1175-
center_levels = _not_none(center_levels, rc["colorbar.center_levels"])
1176-
1177-
# Build label and locator keyword argument dicts
1178-
# NOTE: This carefully handles the 'maxn' and 'maxn_minor' deprecations
1179-
kw_label = {}
1180-
locator_kw = locator_kw or {}
1181-
formatter_kw = formatter_kw or {}
1182-
minorlocator_kw = minorlocator_kw or {}
1183-
for key, value in (
1184-
("size", labelsize),
1185-
("weight", labelweight),
1186-
("color", labelcolor),
1187-
):
1188-
if value is not None:
1189-
kw_label[key] = value
1190-
kw_ticklabels = {}
1191-
for key, value in (
1192-
("size", ticklabelsize),
1193-
("weight", ticklabelweight),
1194-
("color", ticklabelcolor),
1195-
("rotation", rotation),
1196-
):
1197-
if value is not None:
1198-
kw_ticklabels[key] = value
1199-
for b, kw in enumerate((locator_kw, minorlocator_kw)):
1200-
key = "maxn_minor" if b else "maxn"
1201-
name = "minorlocator" if b else "locator"
1202-
nbins = kwargs.pop("maxn_minor" if b else "maxn", None)
1203-
if nbins is not None:
1204-
kw["nbins"] = nbins
1205-
warnings._warn_ultraplot(
1206-
f"The colorbar() keyword {key!r} was deprecated in v0.10. To "
1207-
"achieve the same effect, you can pass 'nbins' to the new default "
1208-
f"locator DiscreteLocator using {name}_kw={{'nbins': {nbins}}}. "
1209-
)
1210-
1211-
# Generate and prepare the colorbar axes
1212-
# NOTE: The inset axes function needs 'label' to know how to pad the box
1213-
# TODO: Use seperate keywords for frame properties vs. colorbar edge properties?
1214-
if loc in ("fill", "left", "right", "top", "bottom"):
1215-
length = _not_none(length, rc["colorbar.length"]) # for _add_guide_panel
1216-
kwargs.update({"align": align, "length": length})
1217-
extendsize = _not_none(extendsize, rc["colorbar.extend"])
1218-
ax = self._add_guide_panel(
1219-
loc,
1220-
align,
1221-
length=length,
1222-
width=width,
1223-
space=space,
1224-
pad=pad,
1225-
span=span,
1226-
row=row,
1227-
col=col,
1228-
rows=rows,
1229-
cols=cols,
1230-
) # noqa: E501
1231-
cax, kwargs = ax._parse_colorbar_filled(**kwargs)
1232-
else:
1233-
kwargs.update({"label": label, "length": length, "width": width})
1234-
extendsize = _not_none(extendsize, rc["colorbar.insetextend"])
1235-
cax, kwargs = self._parse_colorbar_inset(
1236-
loc=loc,
1237-
labelloc=labelloc,
1238-
labelrotation=labelrotation,
1239-
labelsize=labelsize,
1240-
pad=pad,
1241-
**kwargs,
1242-
) # noqa: E501
1243-
1244-
# Parse the colorbar mappable
1245-
# NOTE: Account for special case where auto colorbar is generated from 1D
1246-
# methods that construct an 'artist list' (i.e. colormap scatter object)
1247-
if (
1248-
np.iterable(mappable)
1249-
and len(mappable) == 1
1250-
and isinstance(mappable[0], mcm.ScalarMappable)
1251-
): # noqa: E501
1252-
mappable = mappable[0]
1253-
if not isinstance(mappable, mcm.ScalarMappable):
1254-
mappable, kwargs = cax._parse_colorbar_arg(mappable, values, **kwargs)
1255-
else:
1256-
pop = _pop_params(kwargs, cax._parse_colorbar_arg, ignore_internal=True)
1257-
if pop:
1258-
warnings._warn_ultraplot(
1259-
f"Input is already a ScalarMappable. "
1260-
f"Ignoring unused keyword arg(s): {pop}"
1261-
)
1262-
1263-
# Parse 'extendsize' and 'extendfrac' keywords
1264-
# TODO: Make this auto-adjust to the subplot size
1265-
vert = kwargs["orientation"] == "vertical"
1266-
if extendsize is not None and extendfrac is not None:
1267-
warnings._warn_ultraplot(
1268-
f"You cannot specify both an absolute extendsize={extendsize!r} "
1269-
f"and a relative extendfrac={extendfrac!r}. Ignoring 'extendfrac'."
1270-
)
1271-
extendfrac = None
1272-
if extendfrac is None:
1273-
width, height = cax._get_size_inches()
1274-
scale = height if vert else width
1275-
extendsize = units(extendsize, "em", "in")
1276-
extendfrac = extendsize / max(scale - 2 * extendsize, units(1, "em", "in"))
1277-
1278-
# Parse the tick locators and formatters
1279-
# NOTE: In presence of BoundaryNorm or similar handle ticks with special
1280-
# DiscreteLocator or else get issues (see mpl #22233).
1281-
norm = mappable.norm
1282-
formatter = _not_none(formatter, getattr(norm, "_labels", None), "auto")
1283-
formatter_kw.setdefault("tickrange", (norm.vmin, norm.vmax))
1284-
formatter = constructor.Formatter(formatter, **formatter_kw)
1285-
categorical = isinstance(formatter, mticker.FixedFormatter)
1286-
if locator is not None:
1287-
locator = constructor.Locator(locator, **locator_kw)
1288-
if minorlocator is not None: # overrides tickminor
1289-
minorlocator = constructor.Locator(minorlocator, **minorlocator_kw)
1290-
elif tickminor is None:
1291-
tickminor = False if categorical else rc["xy"[vert] + "tick.minor.visible"]
1292-
if isinstance(norm, mcolors.BoundaryNorm): # DiscreteNorm or BoundaryNorm
1293-
ticks = getattr(norm, "_ticks", norm.boundaries)
1294-
segmented = isinstance(getattr(norm, "_norm", None), pcolors.SegmentedNorm)
1295-
if locator is None:
1296-
if categorical or segmented:
1297-
locator = mticker.FixedLocator(ticks)
1298-
else:
1299-
locator = pticker.DiscreteLocator(ticks)
1300-
1301-
if tickminor and minorlocator is None:
1302-
minorlocator = pticker.DiscreteLocator(ticks, minor=True)
1303-
1304-
# Special handling for colorbar keyword arguments
1305-
# WARNING: Critical to not pass empty major locators in matplotlib < 3.5
1306-
# See this issue: https://github.com/ultraplot-dev/ultraplot/issues/301
1307-
# WARNING: ultraplot 'supports' passing one extend to a mappable function
1308-
# then overwriting by passing another 'extend' to colobar. But contour
1309-
# colorbars break when you try to change its 'extend'. Matplotlib gets
1310-
# around this by just silently ignoring 'extend' passed to colorbar() but
1311-
# we issue warning. Also note ContourSet.extend existed in matplotlib 3.0.
1312-
# WARNING: Confusingly the only default way to have auto-adjusting
1313-
# colorbar ticks is to specify no locator. Then _get_ticker_locator_formatter
1314-
# uses the default ScalarFormatter on the axis that already has a set axis.
1315-
# Otherwise it sets a default axis with locator.create_dummy_axis() in
1316-
# update_ticks() which does not track axis size. Workaround is to manually
1317-
# set the locator and formatter axis... however this messes up colorbar lengths
1318-
# in matplotlib < 3.2. So we only apply this conditionally and in earlier
1319-
# verisons recognize that DiscreteLocator will behave like FixedLocator.
1320-
axis = cax.yaxis if vert else cax.xaxis
1321-
if not isinstance(mappable, mcontour.ContourSet):
1322-
extend = _not_none(extend, "neither")
1323-
kwargs["extend"] = extend
1324-
elif extend is not None and extend != mappable.extend:
1325-
warnings._warn_ultraplot(
1326-
"Ignoring extend={extend!r}. ContourSet extend cannot be changed."
1327-
)
1328-
if (
1329-
isinstance(locator, mticker.NullLocator)
1330-
or hasattr(locator, "locs")
1331-
and len(locator.locs) == 0
1332-
):
1333-
minorlocator, tickminor = None, False # attempted fix
1334-
for ticker in (locator, formatter, minorlocator):
1335-
if version.parse(str(_version_mpl)) < version.parse("3.2"):
1336-
pass # see notes above
1337-
elif isinstance(ticker, mticker.TickHelper):
1338-
ticker.set_axis(axis)
1339-
1340-
# Create colorbar and update ticks and axis direction
1341-
# NOTE: This also adds the guides._update_ticks() monkey patch that triggers
1342-
# updates to DiscreteLocator when parent axes is drawn.
1343-
orientation = _not_none(
1344-
kwargs.pop("orientation", None), kwargs.pop("vert", None)
1345-
)
1346-
1347-
obj = cax._colorbar_fill = cax.figure.colorbar(
1162+
return UltraColorbar(self).add(
13481163
mappable,
1349-
cax=cax,
1350-
ticks=locator,
1351-
format=formatter,
1352-
drawedges=grid,
1164+
values=values,
1165+
loc=loc,
1166+
align=align,
1167+
space=space,
1168+
pad=pad,
1169+
width=width,
1170+
length=length,
1171+
span=span,
1172+
row=row,
1173+
col=col,
1174+
rows=rows,
1175+
cols=cols,
1176+
shrink=shrink,
1177+
label=label,
1178+
title=title,
1179+
reverse=reverse,
1180+
rotation=rotation,
1181+
grid=grid,
1182+
edges=edges,
1183+
drawedges=drawedges,
1184+
extend=extend,
1185+
extendsize=extendsize,
13531186
extendfrac=extendfrac,
1354-
orientation=orientation,
1355-
**kwargs,
1356-
)
1357-
outline = _not_none(outline, rc["colorbar.outline"])
1358-
obj.outline.set_visible(outline)
1359-
obj.ax.grid(False)
1360-
# obj.minorlocator = minorlocator # backwards compatibility
1361-
obj.update_ticks = guides._update_ticks.__get__(obj) # backwards compatible
1362-
if minorlocator is not None:
1363-
# Note we make use of mpl's setters and getters
1364-
current = obj.minorlocator
1365-
if current != minorlocator:
1366-
obj.minorlocator = minorlocator
1367-
obj.update_ticks()
1368-
elif tickminor:
1369-
obj.minorticks_on()
1370-
else:
1371-
obj.minorticks_off()
1372-
if getattr(norm, "descending", None):
1373-
axis.set_inverted(True)
1374-
if reverse: # potentially double reverse, although that would be weird...
1375-
axis.set_inverted(True)
1376-
1377-
# Update other colorbar settings
1378-
# WARNING: Must use the colorbar set_label to set text. Calling set_label
1379-
# on the actual axis will do nothing!
1380-
if center_levels:
1381-
# Center the ticks to the center of the colorbar
1382-
# rather than showing them on the edges
1383-
if hasattr(obj.norm, "boundaries"):
1384-
# Only apply to discrete norms
1385-
bounds = obj.norm.boundaries
1386-
centers = 0.5 * (bounds[:-1] + bounds[1:])
1387-
axis.set_ticks(centers)
1388-
ticklenratio = 0
1389-
tickwidthratio = 0
1390-
axis.set_tick_params(which="both", color=color, direction=tickdir)
1391-
axis.set_tick_params(which="major", length=ticklen, width=tickwidth)
1392-
axis.set_tick_params(
1393-
which="minor",
1394-
length=ticklen * ticklenratio,
1395-
width=tickwidth * tickwidthratio,
1396-
) # noqa: E501
1397-
1398-
# Set label and label location
1399-
long_or_short_axis = _get_axis_for(
1400-
labelloc, loc, orientation=orientation, ax=obj
1401-
)
1402-
if labelloc is None:
1403-
labelloc = long_or_short_axis.get_ticks_position()
1404-
long_or_short_axis.set_label_text(label)
1405-
long_or_short_axis.set_label_position(labelloc)
1406-
1407-
labelrotation = _not_none(labelrotation, rc["colorbar.labelrotation"])
1408-
# Note kw_label is updated in place
1409-
_determine_label_rotation(
1410-
labelrotation,
1187+
ticks=ticks,
1188+
locator=locator,
1189+
locator_kw=locator_kw,
1190+
format=format,
1191+
formatter=formatter,
1192+
ticklabels=ticklabels,
1193+
formatter_kw=formatter_kw,
1194+
minorticks=minorticks,
1195+
minorlocator=minorlocator,
1196+
minorlocator_kw=minorlocator_kw,
1197+
tickminor=tickminor,
1198+
ticklen=ticklen,
1199+
ticklenratio=ticklenratio,
1200+
tickdir=tickdir,
1201+
tickdirection=tickdirection,
1202+
tickwidth=tickwidth,
1203+
tickwidthratio=tickwidthratio,
1204+
ticklabelsize=ticklabelsize,
1205+
ticklabelweight=ticklabelweight,
1206+
ticklabelcolor=ticklabelcolor,
14111207
labelloc=labelloc,
1412-
orientation=orientation,
1413-
kw_label=kw_label,
1208+
labellocation=labellocation,
1209+
labelsize=labelsize,
1210+
labelweight=labelweight,
1211+
labelcolor=labelcolor,
1212+
c=c,
1213+
color=color,
1214+
lw=lw,
1215+
linewidth=linewidth,
1216+
edgefix=edgefix,
1217+
rasterized=rasterized,
1218+
outline=outline,
1219+
labelrotation=labelrotation,
1220+
center_levels=center_levels,
1221+
**kwargs,
14141222
)
14151223

1416-
long_or_short_axis.label.update(kw_label)
1417-
# Assume ticks are set on the long axis(!))
1418-
if hasattr(obj, "_long_axis"):
1419-
# mpl <=3.9
1420-
longaxis = obj._long_axis()
1421-
else:
1422-
# mpl >=3.10
1423-
longaxis = obj.long_axis
1424-
for label in longaxis.get_ticklabels():
1425-
label.update(kw_ticklabels)
1426-
if KIWI_AVAILABLE and getattr(cax, "_inset_colorbar_layout", None):
1427-
_reflow_inset_colorbar_frame(obj, labelloc=labelloc, ticklen=ticklen)
1428-
cax._inset_colorbar_obj = obj
1429-
cax._inset_colorbar_labelloc = labelloc
1430-
cax._inset_colorbar_ticklen = ticklen
1431-
_register_inset_colorbar_reflow(self.figure)
1432-
kw_outline = {"edgecolor": color, "linewidth": linewidth}
1433-
if obj.outline is not None:
1434-
obj.outline.update(kw_outline)
1435-
if obj.dividers is not None:
1436-
obj.dividers.update(kw_outline)
1437-
if obj.solids:
1438-
from . import PlotAxes
1439-
1440-
obj.solids.set_rasterized(rasterized)
1441-
PlotAxes._fix_patch_edges(obj.solids, edgefix=edgefix)
1442-
1443-
# Register location and return
1444-
self._register_guide("colorbar", obj, (loc, align)) # possibly replace another
1445-
return obj
1446-
14471224
def _add_legend(
14481225
self,
14491226
handles=None,

0 commit comments

Comments
 (0)