Skip to content

Commit df330c9

Browse files
authored
Refactor format in sensible blocks (#484)
* Refactor format in sensible blocks * Refactor format in sensible blocks
1 parent 27b64d4 commit df330c9

File tree

1 file changed

+150
-114
lines changed

1 file changed

+150
-114
lines changed

ultraplot/axes/cartesian.py

Lines changed: 150 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -1273,6 +1273,149 @@ def _format_axis(self, s: str, config: _AxisFormatConfig, fixticks: bool):
12731273
# Ensure ticks are within axis bounds
12741274
self._fix_ticks(s, fixticks=fixticks)
12751275

1276+
def _resolve_axis_format(self, axis, params, rc_kw):
1277+
"""
1278+
Resolve formatting parameters for a single axis (x or y).
1279+
"""
1280+
p = params
1281+
1282+
# Color resolution
1283+
color = p.get("color")
1284+
axis_color = _not_none(p.get(f"{axis}color"), color)
1285+
1286+
# Helper to get axis-specific or generic param
1287+
def get(name):
1288+
return p.get(f"{axis}{name}")
1289+
1290+
# Resolve colors
1291+
tickcolor = get("tickcolor")
1292+
if "tick.color" not in rc_kw:
1293+
tickcolor = _not_none(tickcolor, axis_color)
1294+
1295+
ticklabelcolor = get("ticklabelcolor")
1296+
if "tick.labelcolor" not in rc_kw:
1297+
ticklabelcolor = _not_none(ticklabelcolor, axis_color)
1298+
1299+
labelcolor = get("labelcolor")
1300+
if "label.color" not in rc_kw:
1301+
labelcolor = _not_none(labelcolor, axis_color)
1302+
1303+
# Flexible keyword args
1304+
margin = _not_none(
1305+
get("margin"), p.get("margin"), rc.find(f"axes.{axis}margin", context=True)
1306+
)
1307+
1308+
tickdir = _not_none(
1309+
get("tickdir"), rc.find(f"{axis}tick.direction", context=True)
1310+
)
1311+
1312+
locator = _not_none(get("locator"), p.get(f"{axis}ticks"))
1313+
minorlocator = _not_none(get("minorlocator"), p.get(f"{axis}minorticks"))
1314+
1315+
formatter = _not_none(get("formatter"), p.get(f"{axis}ticklabels"))
1316+
1317+
# Tick minor default logic
1318+
tickminor = get("tickminor")
1319+
tickminor_default = None
1320+
if (
1321+
isinstance(formatter, mticker.FixedFormatter)
1322+
or np.iterable(formatter)
1323+
and not isinstance(formatter, str)
1324+
):
1325+
tickminor_default = False
1326+
1327+
tickminor = _not_none(
1328+
tickminor,
1329+
tickminor_default,
1330+
rc.find(f"{axis}tick.minor.visible", context=True),
1331+
)
1332+
1333+
# Tick label dir logic
1334+
ticklabeldir = p.get("ticklabeldir")
1335+
axis_ticklabeldir = _not_none(get("ticklabeldir"), ticklabeldir)
1336+
tickdir = _not_none(tickdir, axis_ticklabeldir)
1337+
1338+
# Spine locations
1339+
loc = get("loc")
1340+
spineloc = get("spineloc")
1341+
spineloc = _not_none(loc, spineloc)
1342+
1343+
# Spine side inference
1344+
side = self._get_spine_side(axis, spineloc)
1345+
1346+
tickloc = get("tickloc")
1347+
if side is not None and side not in ("zero", "center", "both"):
1348+
tickloc = _not_none(tickloc, side)
1349+
1350+
# Infer other locations
1351+
ticklabelloc = get("ticklabelloc")
1352+
labelloc = get("labelloc")
1353+
offsetloc = get("offsetloc")
1354+
1355+
if tickloc != "both":
1356+
ticklabelloc = _not_none(ticklabelloc, tickloc)
1357+
valid_sides = ("bottom", "top") if axis == "x" else ("left", "right")
1358+
1359+
if ticklabelloc in valid_sides:
1360+
labelloc = _not_none(labelloc, ticklabelloc)
1361+
# Note: original code likely had typo relating xoffset to yticklabels
1362+
# We assume standard behavior here: follow ticklabelloc
1363+
offsetloc = _not_none(offsetloc, ticklabelloc)
1364+
1365+
tickloc = _not_none(tickloc, rc._get_loc_string(axis, f"{axis}tick"))
1366+
spineloc = _not_none(spineloc, rc._get_loc_string(axis, "axes.spines"))
1367+
1368+
# Map to config fields
1369+
# Note: min_/max_ map to xmin/xmax etc
1370+
config_kwargs = {}
1371+
for field in _AxisFormatConfig.__dataclass_fields__:
1372+
val = None
1373+
match field:
1374+
case "min_":
1375+
val = p.get(f"{axis}min")
1376+
case "max_":
1377+
val = p.get(f"{axis}max")
1378+
case "color":
1379+
val = axis_color
1380+
case "tickcolor":
1381+
val = tickcolor
1382+
case "ticklabelcolor":
1383+
val = ticklabelcolor
1384+
case "labelcolor":
1385+
val = labelcolor
1386+
case "margin":
1387+
val = margin
1388+
case "tickdir":
1389+
val = tickdir
1390+
case "locator":
1391+
val = locator
1392+
case "minorlocator":
1393+
val = minorlocator
1394+
case "formatter":
1395+
val = formatter
1396+
case "tickminor":
1397+
val = tickminor
1398+
case "ticklabeldir":
1399+
val = axis_ticklabeldir
1400+
case "spineloc":
1401+
val = spineloc
1402+
case "tickloc":
1403+
val = tickloc
1404+
case "ticklabelloc":
1405+
val = ticklabelloc
1406+
case "labelloc":
1407+
val = labelloc
1408+
case "offsetloc":
1409+
val = offsetloc
1410+
case _:
1411+
# Direct mapping (e.g. xlinewidth -> linewidth)
1412+
val = get(field)
1413+
1414+
if val is not None:
1415+
config_kwargs[field] = val
1416+
1417+
return _AxisFormatConfig(**config_kwargs)
1418+
12761419
@docstring._snippet_manager
12771420
def format(
12781421
self,
@@ -1409,120 +1552,13 @@ def format(
14091552
"""
14101553
rc_kw, rc_mode = _pop_rc(kwargs)
14111554
with rc.context(rc_kw, mode=rc_mode):
1412-
# No mutable default args
1413-
xlabel_kw = xlabel_kw or {}
1414-
ylabel_kw = ylabel_kw or {}
1415-
xscale_kw = xscale_kw or {}
1416-
yscale_kw = yscale_kw or {}
1417-
xlocator_kw = xlocator_kw or {}
1418-
ylocator_kw = ylocator_kw or {}
1419-
xformatter_kw = xformatter_kw or {}
1420-
yformatter_kw = yformatter_kw or {}
1421-
xminorlocator_kw = xminorlocator_kw or {}
1422-
yminorlocator_kw = yminorlocator_kw or {}
1423-
1424-
# Color keyword arguments. Inherit from 'color' when necessary
1425-
color = kwargs.pop("color", None)
1426-
xcolor = _not_none(xcolor, color)
1427-
ycolor = _not_none(ycolor, color)
1428-
if "tick.color" not in rc_kw:
1429-
xtickcolor = _not_none(xtickcolor, xcolor)
1430-
ytickcolor = _not_none(ytickcolor, ycolor)
1431-
if "tick.labelcolor" not in rc_kw:
1432-
xticklabelcolor = _not_none(xticklabelcolor, xcolor)
1433-
yticklabelcolor = _not_none(yticklabelcolor, ycolor)
1434-
if "label.color" not in rc_kw:
1435-
xlabelcolor = _not_none(xlabelcolor, xcolor)
1436-
ylabelcolor = _not_none(ylabelcolor, ycolor)
1437-
1438-
# Flexible keyword args, declare defaults
1439-
# NOTE: 'xtickdir' and 'ytickdir' read from 'tickdir' arguments here
1440-
xmargin = _not_none(xmargin, rc.find("axes.xmargin", context=True))
1441-
ymargin = _not_none(ymargin, rc.find("axes.ymargin", context=True))
1442-
xtickdir = _not_none(xtickdir, rc.find("xtick.direction", context=True))
1443-
ytickdir = _not_none(ytickdir, rc.find("ytick.direction", context=True))
1444-
xlocator = _not_none(xlocator=xlocator, xticks=xticks)
1445-
ylocator = _not_none(ylocator=ylocator, yticks=yticks)
1446-
xminorlocator = _not_none(
1447-
xminorlocator=xminorlocator, xminorticks=xminorticks
1448-
) # noqa: E501
1449-
yminorlocator = _not_none(
1450-
yminorlocator=yminorlocator, yminorticks=yminorticks
1451-
) # noqa: E501
1452-
xformatter = _not_none(xformatter=xformatter, xticklabels=xticklabels)
1453-
yformatter = _not_none(yformatter=yformatter, yticklabels=yticklabels)
1454-
xtickminor_default = ytickminor_default = None
1455-
if (
1456-
isinstance(xformatter, mticker.FixedFormatter)
1457-
or np.iterable(xformatter)
1458-
and not isinstance(xformatter, str)
1459-
): # noqa: E501
1460-
xtickminor_default = False
1461-
if (
1462-
isinstance(yformatter, mticker.FixedFormatter)
1463-
or np.iterable(yformatter)
1464-
and not isinstance(yformatter, str)
1465-
): # noqa: E501
1466-
ytickminor_default = False
1467-
xtickminor = _not_none(
1468-
xtickminor,
1469-
xtickminor_default,
1470-
rc.find("xtick.minor.visible", context=True),
1471-
) # noqa: E501
1472-
ytickminor = _not_none(
1473-
ytickminor,
1474-
ytickminor_default,
1475-
rc.find("ytick.minor.visible", context=True),
1476-
) # noqa: E501
1477-
ticklabeldir = kwargs.pop("ticklabeldir", None)
1478-
xticklabeldir = _not_none(xticklabeldir, ticklabeldir)
1479-
yticklabeldir = _not_none(yticklabeldir, ticklabeldir)
1480-
xtickdir = _not_none(xtickdir, xticklabeldir)
1481-
ytickdir = _not_none(ytickdir, yticklabeldir)
1482-
1483-
# Sensible defaults for spine, tick, tick label, and label locs
1484-
# NOTE: Allow tick labels to be present without ticks! User may
1485-
# want this sometimes! Same goes for spines!
1486-
xspineloc = _not_none(xloc=xloc, xspineloc=xspineloc)
1487-
yspineloc = _not_none(yloc=yloc, yspineloc=yspineloc)
1488-
xside = self._get_spine_side("x", xspineloc)
1489-
yside = self._get_spine_side("y", yspineloc)
1490-
if xside is not None and xside not in ("zero", "center", "both"):
1491-
xtickloc = _not_none(xtickloc, xside)
1492-
if yside is not None and yside not in ("zero", "center", "both"):
1493-
ytickloc = _not_none(ytickloc, yside)
1494-
if xtickloc != "both": # then infer others
1495-
xticklabelloc = _not_none(xticklabelloc, xtickloc)
1496-
if xticklabelloc in ("bottom", "top"):
1497-
xlabelloc = _not_none(xlabelloc, xticklabelloc)
1498-
xoffsetloc = _not_none(xoffsetloc, yticklabelloc)
1499-
if ytickloc != "both": # then infer others
1500-
yticklabelloc = _not_none(yticklabelloc, ytickloc)
1501-
if yticklabelloc in ("left", "right"):
1502-
ylabelloc = _not_none(ylabelloc, yticklabelloc)
1503-
yoffsetloc = _not_none(yoffsetloc, yticklabelloc)
1504-
xtickloc = _not_none(xtickloc, rc._get_loc_string("x", "xtick"))
1505-
ytickloc = _not_none(ytickloc, rc._get_loc_string("y", "ytick"))
1506-
xspineloc = _not_none(xspineloc, rc._get_loc_string("x", "axes.spines"))
1507-
yspineloc = _not_none(yspineloc, rc._get_loc_string("y", "axes.spines"))
1508-
1509-
# Create config objects dynamically by introspecting the dataclass fields
1510-
x_kwargs, y_kwargs = {}, {}
1511-
l_vars = locals()
1512-
for name in _AxisFormatConfig.__dataclass_fields__:
1513-
# Handle exceptions to the "x" + name pattern for local variables
1514-
if name == "min_":
1515-
x_var, y_var = "xmin", "ymin"
1516-
elif name == "max_":
1517-
x_var, y_var = "xmax", "ymax"
1518-
else:
1519-
x_var = "x" + name
1520-
y_var = "y" + name
1521-
x_kwargs[name] = l_vars.get(x_var, None)
1522-
y_kwargs[name] = l_vars.get(y_var, None)
1523-
1524-
x_config = _AxisFormatConfig(**x_kwargs)
1525-
y_config = _AxisFormatConfig(**y_kwargs)
1555+
# Resolve parameters for x and y axes
1556+
# We capture locals() to pass all named arguments to the helper
1557+
params = locals()
1558+
params.update(kwargs) # Include any extras in kwargs
1559+
1560+
x_config = self._resolve_axis_format("x", params, rc_kw)
1561+
y_config = self._resolve_axis_format("y", params, rc_kw)
15261562

15271563
# Format axes
15281564
self._format_axis("x", x_config, fixticks=fixticks)

0 commit comments

Comments
 (0)