diff --git a/.gitignore b/.gitignore
index fc0755a70..8e1d33efc 100644
--- a/.gitignore
+++ b/.gitignore
@@ -22,6 +22,9 @@ docs/_build
docs/_static/ultraplotrc
docs/_static/rctable.rst
docs/_static/*
+!docs/_static/alias-explorer-v3.css
+!docs/_static/alias-explorer-v3.js
+!docs/_static/alias-map.svg
!docs/_static/why_plots/
*.html
docs/gallery/
diff --git a/docs/_static/alias-explorer-v3.css b/docs/_static/alias-explorer-v3.css
new file mode 100644
index 000000000..33af42934
--- /dev/null
+++ b/docs/_static/alias-explorer-v3.css
@@ -0,0 +1,438 @@
+.uplt-alias-explorer {
+ --alias-ink: var(--uplt-color-text-main, #24323b);
+ --alias-muted: var(--uplt-color-text-secondary, #59656d);
+ --alias-panel: var(--uplt-color-panel-bg, #fff);
+ --alias-soft: var(--uplt-color-sidebar-bg, #f3f6f7);
+ --alias-border: var(--uplt-color-border-muted, #d9e1e5);
+ --alias-accent: var(--uplt-color-accent, #0f766e);
+ --alias-blue: #3976d3;
+ margin: 1.5rem 0 2.25rem;
+ color: var(--alias-ink);
+}
+
+.uplt-alias-summary {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 0.45rem;
+ margin-bottom: 0.75rem;
+}
+
+.uplt-alias-summary span {
+ padding: 0.24rem 0.62rem;
+ border: 1px solid var(--alias-border);
+ border-radius: 999px;
+ background: var(--alias-soft);
+ color: var(--alias-muted);
+ font-size: 0.76rem;
+ letter-spacing: 0.015em;
+}
+
+.uplt-alias-summary strong {
+ color: var(--alias-accent);
+}
+
+.uplt-alias-map-layout {
+ display: grid;
+ grid-template-columns: minmax(0, 3fr) minmax(14rem, 1fr);
+ align-items: stretch;
+ border: 1px solid var(--alias-border);
+ border-radius: 0.85rem;
+ overflow: visible;
+ background: var(--alias-panel);
+ box-shadow: 0 8px 30px var(--uplt-color-shadow, rgba(15, 23, 42, 0.08));
+}
+
+.uplt-alias-visual-column {
+ min-width: 0;
+ overflow: visible;
+}
+
+.uplt-alias-map {
+ position: relative;
+ z-index: 2;
+ min-width: 0;
+ padding: 0.8rem;
+ overflow: visible;
+ background:
+ radial-gradient(circle at 38% 46%, rgba(57, 118, 211, 0.08), transparent 34%),
+ linear-gradient(145deg, var(--alias-soft), var(--alias-panel));
+}
+
+.uplt-alias-map svg {
+ display: block;
+ width: 100%;
+ height: auto;
+ min-height: 25rem;
+ overflow: visible;
+}
+
+.uplt-alias-real-figure {
+ filter: drop-shadow(0 3px 8px rgba(15, 23, 42, 0.12));
+}
+
+.uplt-alias-target {
+ fill: var(--alias-accent);
+ fill-opacity: 0;
+ stroke: var(--alias-accent);
+ stroke-opacity: 0;
+ stroke-width: 2.5;
+ pointer-events: none;
+}
+
+.uplt-alias-target-line {
+ fill: none;
+ stroke-width: 13;
+}
+
+.uplt-alias-target.is-preview,
+.uplt-alias-target.is-active {
+ fill-opacity: 0.13;
+ stroke-opacity: 0.9;
+}
+
+.uplt-alias-target-line.is-preview,
+.uplt-alias-target-line.is-active {
+ stroke-opacity: 0.5;
+}
+
+.uplt-alias-wire {
+ fill: none;
+ stroke: color-mix(in srgb, var(--callout-color) 72%, var(--alias-border));
+ stroke-width: 3;
+ stroke-linecap: round;
+ pointer-events: none;
+ vector-effect: non-scaling-stroke;
+}
+
+.uplt-alias-node {
+ --callout-color: var(--alias-accent);
+ cursor: pointer;
+ outline: none;
+ touch-action: none;
+}
+
+.uplt-alias-node[data-layout-key="titles"] {
+ --callout-color: #f472b6;
+}
+
+.uplt-alias-node[data-layout-key="layout"] {
+ --callout-color: #a78bfa;
+}
+
+.uplt-alias-node[data-layout-key="axes"] {
+ --callout-color: #60a5fa;
+}
+
+.uplt-alias-node[data-layout-key="plot"] {
+ --callout-color: #34d399;
+}
+
+.uplt-alias-node[data-layout-key="legend"] {
+ --callout-color: #fbbf24;
+}
+
+.uplt-alias-node[data-layout-key="colorbar"] {
+ --callout-color: #fb7185;
+}
+
+.uplt-alias-node[data-layout-key="style"] {
+ --callout-color: #c084fc;
+}
+
+.uplt-alias-node.is-dragging {
+ cursor: grabbing;
+}
+
+.uplt-alias-node rect {
+ fill: color-mix(in srgb, var(--callout-color) 18%, var(--alias-panel));
+ stroke: color-mix(in srgb, var(--callout-color) 72%, var(--alias-border));
+ stroke-width: 2.25;
+ filter: drop-shadow(0 5px 9px color-mix(in srgb, var(--callout-color) 24%, transparent));
+}
+
+.uplt-alias-node text {
+ fill: var(--alias-ink);
+ font-family: ui-rounded, "Arial Rounded MT Bold", system-ui, sans-serif;
+ font-size: 16px;
+ font-weight: 700;
+ text-anchor: middle;
+ pointer-events: none;
+}
+
+.uplt-alias-node:hover rect,
+.uplt-alias-node:focus-visible rect,
+.uplt-alias-node.is-preview rect,
+.uplt-alias-node.is-active rect {
+ fill: color-mix(in srgb, var(--callout-color) 30%, var(--alias-panel));
+ stroke: var(--callout-color);
+ stroke-width: 2.25;
+}
+
+.uplt-alias-node:hover .uplt-alias-wire,
+.uplt-alias-node:focus-visible .uplt-alias-wire,
+.uplt-alias-node.is-preview .uplt-alias-wire,
+.uplt-alias-node.is-active .uplt-alias-wire {
+ stroke: var(--callout-color);
+ stroke-width: 3;
+}
+
+.uplt-alias-detail {
+ min-width: 0;
+ min-height: 0;
+ padding: 1rem 1.05rem;
+ border-left: 1px solid var(--alias-border);
+ background: var(--alias-panel);
+ overflow-y: auto;
+ overscroll-behavior: contain;
+ scrollbar-gutter: stable;
+}
+
+.uplt-alias-detail-help {
+ margin: 0 0 0.75rem !important;
+ color: var(--alias-muted);
+ font-family: inherit;
+ font-size: 0.86rem;
+ font-weight: 400;
+ line-height: 1.45;
+}
+
+.uplt-alias-detail h3 {
+ margin: 0.1rem 0 0.3rem;
+ font-size: 1.25rem;
+}
+
+.uplt-alias-detail > p:not(.uplt-alias-detail-help) {
+ margin: 0 0 0.75rem;
+ color: var(--alias-muted);
+ font-size: 0.86rem;
+}
+
+.uplt-alias-preview {
+ display: grid;
+ gap: 0.38rem;
+ min-height: 9rem;
+}
+
+.uplt-alias-pair {
+ display: grid;
+ grid-template-columns: minmax(0, 1fr) auto minmax(0, 1fr);
+ align-items: center;
+ gap: 0.38rem;
+ padding: 0.3rem 0.4rem;
+ border-radius: 0.35rem;
+ background: var(--alias-soft);
+}
+
+.uplt-alias-pair code {
+ overflow: hidden;
+ color: var(--alias-ink);
+ font-size: 0.72rem;
+ text-overflow: ellipsis;
+}
+
+.uplt-alias-keyword-link {
+ min-width: 0;
+ border-bottom: 0 !important;
+ border-radius: 0.2rem;
+ text-decoration: none !important;
+}
+
+.uplt-alias-keyword-link:hover,
+.uplt-alias-keyword-link:focus-visible {
+ background: color-mix(in srgb, var(--alias-accent) 13%, transparent);
+ outline: 2px solid color-mix(in srgb, var(--alias-accent) 35%, transparent);
+ outline-offset: 1px;
+ text-decoration: none !important;
+}
+
+.uplt-alias-table .uplt-alias-keyword-link {
+ display: inline-block;
+ padding: 0.08rem 0.16rem;
+}
+
+.uplt-alias-table .uplt-alias-keyword-link code {
+ color: inherit;
+}
+
+.uplt-alias-pair span {
+ color: var(--alias-accent);
+}
+
+.uplt-alias-pair small {
+ grid-column: 1 / -1;
+ color: var(--alias-muted);
+ font-size: 0.62rem;
+}
+
+.uplt-alias-more {
+ margin: 0.2rem 0 0 !important;
+ color: var(--alias-muted);
+ font-size: 0.72rem;
+}
+
+.uplt-alias-api-link {
+ display: inline-flex;
+ gap: 0.35rem;
+ align-items: center;
+ margin-top: 0.85rem;
+ font-size: 0.82rem;
+ font-weight: 700;
+}
+
+.uplt-alias-categories {
+ display: grid;
+ grid-template-columns: repeat(auto-fit, minmax(6.6rem, 1fr));
+ gap: 0.45rem;
+ margin: 0;
+ padding: 0.7rem;
+ border-top: 1px solid var(--alias-border);
+}
+
+.uplt-alias-categories button,
+.uplt-alias-filter button {
+ border: 1px solid var(--alias-border);
+ border-radius: 0.55rem;
+ background: var(--alias-panel);
+ color: var(--alias-ink);
+ cursor: pointer;
+ font: inherit;
+}
+
+.uplt-alias-categories button {
+ display: flex;
+ flex-direction: column;
+ align-items: flex-start;
+ justify-content: space-between;
+ min-height: 4.25rem;
+ padding: 0.55rem 0.6rem;
+ font-size: 0.72rem;
+ font-weight: 700;
+ text-align: left;
+}
+
+.uplt-alias-categories button span {
+ color: var(--alias-accent);
+ font-size: 1.25rem;
+ line-height: 1;
+}
+
+.uplt-alias-categories button:hover,
+.uplt-alias-categories button:focus-visible,
+.uplt-alias-categories button.is-preview,
+.uplt-alias-categories button.is-active,
+.uplt-alias-filter button:hover,
+.uplt-alias-filter button:focus-visible {
+ border-color: var(--alias-accent);
+ background: color-mix(in srgb, var(--alias-accent) 9%, var(--alias-panel));
+ outline: none;
+}
+
+.uplt-alias-filter {
+ margin: 0 0.7rem 0.7rem;
+ padding: 0.75rem;
+ border: 1px solid var(--alias-border);
+ border-radius: 0.7rem;
+ background: var(--alias-soft);
+}
+
+.uplt-alias-filter label {
+ display: block;
+ margin-bottom: 0.35rem;
+ color: var(--alias-muted);
+ font-size: 0.75rem;
+ font-weight: 700;
+}
+
+.uplt-alias-filter > div {
+ display: flex;
+ gap: 0.45rem;
+}
+
+.uplt-alias-filter input {
+ flex: 1 1 auto;
+ min-width: 0;
+ padding: 0.48rem 0.62rem;
+ border: 1px solid var(--alias-border);
+ border-radius: 0.45rem;
+ background: var(--alias-panel);
+ color: var(--alias-ink);
+ font: inherit;
+ font-size: 0.84rem;
+}
+
+.uplt-alias-filter input:focus {
+ border-color: var(--alias-accent);
+ outline: 2px solid color-mix(in srgb, var(--alias-accent) 24%, transparent);
+ outline-offset: 1px;
+}
+
+.uplt-alias-filter button {
+ padding: 0.42rem 0.72rem;
+ font-size: 0.76rem;
+ font-weight: 700;
+}
+
+.uplt-alias-filter-status {
+ min-height: 1.2em;
+ margin: 0.4rem 0 0 !important;
+ color: var(--alias-muted);
+ font-size: 0.72rem;
+}
+
+.uplt-alias-table tbody tr.is-alias-match td {
+ background: color-mix(in srgb, var(--alias-accent) 8%, transparent);
+}
+
+@media (max-width: 1000px) {
+ .uplt-alias-map-layout {
+ grid-template-columns: 1fr;
+ }
+
+ .uplt-alias-detail {
+ border-top: 1px solid var(--alias-border);
+ border-left: 0;
+ }
+
+ .uplt-alias-preview {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ min-height: 0;
+ }
+
+ .uplt-alias-categories {
+ grid-template-columns: repeat(auto-fit, minmax(7rem, 1fr));
+ }
+}
+
+@media (max-width: 640px) {
+ .uplt-alias-map {
+ padding: 0.2rem;
+ overflow: visible;
+ }
+
+ .uplt-alias-map svg {
+ min-width: 39rem;
+ }
+
+ .uplt-alias-categories {
+ grid-template-columns: repeat(2, minmax(0, 1fr));
+ }
+
+ .uplt-alias-preview {
+ grid-template-columns: 1fr;
+ }
+
+ .uplt-alias-filter > div {
+ align-items: stretch;
+ flex-direction: column;
+ }
+}
+
+@media (prefers-reduced-motion: reduce) {
+ .uplt-alias-explorer *,
+ .uplt-alias-explorer *::before,
+ .uplt-alias-explorer *::after {
+ scroll-behavior: auto !important;
+ transition: none !important;
+ }
+}
+ overflow: visible;
diff --git a/docs/_static/alias-explorer-v3.js b/docs/_static/alias-explorer-v3.js
new file mode 100644
index 000000000..e3af4e5b0
--- /dev/null
+++ b/docs/_static/alias-explorer-v3.js
@@ -0,0 +1,482 @@
+(function () {
+ "use strict";
+
+ const apiTargets = {
+ "figure.init": "api/ultraplot.figure.Figure.html#ultraplot.figure.Figure",
+ "figure.format": "api/ultraplot.figure.Figure.html#ultraplot.figure.Figure.format",
+ "axes.format": "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.format",
+ "cartesian.format": "api/ultraplot.axes.CartesianAxes.html#ultraplot.axes.CartesianAxes.format",
+ "geo.format": "api/ultraplot.axes.GeoAxes.html#ultraplot.axes.GeoAxes.format",
+ "polar.format": "api/ultraplot.axes.PolarAxes.html#ultraplot.axes.PolarAxes.format",
+ "taylor.format": "api/ultraplot.axes.TaylorAxes.html#ultraplot.axes.TaylorAxes.format",
+ colorbar: "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.colorbar",
+ legend: "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.legend",
+ gridspec: "api/ultraplot.gridspec.GridSpec.html#ultraplot.gridspec.GridSpec",
+ subplot: "api/ultraplot.ui.subplots.html#ultraplot.ui.subplots",
+ inset: "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.inset",
+ cycle: "api/ultraplot.constructor.Cycle.html#ultraplot.constructor.Cycle",
+ projection: "api/ultraplot.constructor.Proj.html#ultraplot.constructor.Proj",
+ "scale.log": "api/ultraplot.scale.LogScale.html#ultraplot.scale.LogScale",
+ "scale.symlog": "api/ultraplot.scale.SymmetricalLogScale.html#ultraplot.scale.SymmetricalLogScale",
+ "plot.labels": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes",
+ "plot.text": "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.text",
+ "plot.contour_labels": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.contour",
+ "plot.error_bars": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.plot",
+ "plot.error_shading": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.plot",
+ "plot.colormap": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.contour",
+ "plot.levels": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.contour",
+ "plot.stacked": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.bar",
+ "plot.statistics": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.boxplot",
+ "plot.boxplot": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.boxplot",
+ "plot.violinplot": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.violinplot",
+ "plot.hist": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.hist",
+ "plot.pie": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.pie",
+ "style.rgba": "api.html#colormaps-and-normalizers",
+ "style.hsla": "api.html#colormaps-and-normalizers",
+ "style.patch": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.bar",
+ "style.line": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.plot",
+ "style.collection": "api/ultraplot.axes.PlotAxes.html#ultraplot.axes.PlotAxes.scatter",
+ "style.text": "api/ultraplot.axes.Axes.html#ultraplot.axes.Axes.text",
+ "rc (dotless)": "api/ultraplot.config.Configurator.html#ultraplot.config.Configurator",
+ };
+
+ function splitContexts(value) {
+ return String(value || "")
+ .split(",")
+ .map((item) => item.trim())
+ .filter(Boolean);
+ }
+
+ function contextMatches(context, pattern) {
+ if (pattern.endsWith(".*")) {
+ return context.startsWith(pattern.slice(0, -1));
+ }
+ return context === pattern;
+ }
+
+ function rowMatchesContexts(row, patterns) {
+ return (
+ !patterns.length ||
+ patterns.some((pattern) => contextMatches(row.context, pattern))
+ );
+ }
+
+ function makeCode(value) {
+ const code = document.createElement("code");
+ code.textContent = value;
+ return code;
+ }
+
+ function highlightedApiUrl(context, canonical) {
+ const target = apiTargets[context];
+ if (!target) return "";
+ const hashIndex = target.indexOf("#");
+ const base = hashIndex === -1 ? target : target.slice(0, hashIndex);
+ const hash = hashIndex === -1 ? "" : target.slice(hashIndex);
+ const joiner = base.includes("?") ? "&" : "?";
+ return `${base}${joiner}highlight=${encodeURIComponent(canonical)}${hash}`;
+ }
+
+ function makeKeywordLink(row, value) {
+ const target = highlightedApiUrl(row.context, row.canonical);
+ if (!target) return makeCode(value);
+ const link = document.createElement("a");
+ link.className = "uplt-alias-keyword-link";
+ link.href = target;
+ link.title = `Open the canonical API documentation for ${row.canonical}`;
+ link.appendChild(makeCode(value));
+ return link;
+ }
+
+ function initializeAliasExplorer(root) {
+ const sectionIds = [
+ "function-keyword-aliases",
+ "artist-property-aliases",
+ "dotless-rc-aliases",
+ ];
+ const sections = sectionIds
+ .map((id) => document.getElementById(id))
+ .filter(Boolean);
+ const rows = [];
+
+ sections.forEach((section) => {
+ const table = section.querySelector("table");
+ if (!table) return;
+ table.classList.add("uplt-alias-table");
+ Array.from(table.querySelectorAll("tbody tr")).forEach((element) => {
+ const cells = Array.from(element.querySelectorAll("td"));
+ if (cells.length < 3) return;
+ const row = {
+ element,
+ context: cells[0].textContent.trim(),
+ accepted: cells[1].textContent.trim(),
+ canonical: cells[2].textContent.trim(),
+ };
+ element.dataset.aliasContext = row.context;
+ rows.push(row);
+ });
+ });
+
+ if (!rows.length) return;
+
+ const contextCount = new Set(rows.map((row) => row.context)).size;
+ const totalNode = root.querySelector("[data-alias-total]");
+ const contextTotalNode = root.querySelector("[data-alias-context-total]");
+ const titleNode = root.querySelector("[data-alias-detail-title]");
+ const copyNode = root.querySelector("[data-alias-detail-copy]");
+ const previewNode = root.querySelector("[data-alias-preview]");
+ const apiNode = root.querySelector("[data-alias-api]");
+ const searchNode = root.querySelector("#uplt-alias-search");
+ const resetNode = root.querySelector("[data-alias-reset]");
+ const statusNode = root.querySelector("[data-alias-filter-status]");
+ const controls = Array.from(
+ root.querySelectorAll("[data-contexts][data-label]"),
+ );
+ const targets = Array.from(root.querySelectorAll("[data-alias-target]"));
+ const svg = root.querySelector(".uplt-alias-map svg");
+ const draggableNodes = Array.from(
+ root.querySelectorAll(".uplt-alias-node[data-layout-key]"),
+ );
+ const suppressedClicks = new WeakSet();
+
+ function svgPoint(event) {
+ const point = svg.createSVGPoint();
+ point.x = event.clientX;
+ point.y = event.clientY;
+ return point.matrixTransform(svg.getScreenCTM().inverse());
+ }
+
+ function nodePosition(node) {
+ const match = /translate\(\s*([-+\d.]+)[ ,]+([-+\d.]+)\s*\)/.exec(
+ node.getAttribute("transform") || "",
+ );
+ return {
+ x: match ? Number(match[1]) : 0,
+ y: match ? Number(match[2]) : 0,
+ };
+ }
+
+ function updateLeader(node, x, y) {
+ const rect = node.querySelector("rect");
+ const wire = node.querySelector(".uplt-alias-wire");
+ if (!rect || !wire) return;
+ const width = Number(rect.getAttribute("width"));
+ const height = Number(rect.getAttribute("height"));
+ const targetX = Number(node.dataset.anchorX);
+ const targetY = Number(node.dataset.anchorY);
+ const centerX = x + width / 2;
+ const centerY = y + height / 2;
+ const dx = targetX - centerX;
+ const dy = targetY - centerY;
+ if (!dx && !dy) {
+ wire.setAttribute("d", "");
+ return;
+ }
+ const factor = Math.min(
+ dx ? width / 2 / Math.abs(dx) : Infinity,
+ dy ? height / 2 / Math.abs(dy) : Infinity,
+ );
+ const length = Math.hypot(dx, dy) || 1;
+ const overlap = 3;
+ const startX = centerX + dx * factor - (dx / length) * overlap;
+ const startY = centerY + dy * factor - (dy / length) * overlap;
+ wire.setAttribute(
+ "d",
+ `M${(startX - x).toFixed(1)} ${(startY - y).toFixed(1)} ` +
+ `L${(targetX - x).toFixed(1)} ${(targetY - y).toFixed(1)}`,
+ );
+ }
+
+ function setNodePosition(node, x, y) {
+ node.setAttribute("transform", `translate(${x.toFixed(1)} ${y.toFixed(1)})`);
+ updateLeader(node, x, y);
+ }
+
+ function layoutSnapshot() {
+ return Object.fromEntries(
+ draggableNodes.map((node) => {
+ const position = nodePosition(node);
+ return [
+ node.dataset.layoutKey,
+ [Number(position.x.toFixed(1)), Number(position.y.toFixed(1))],
+ ];
+ }),
+ );
+ }
+
+ function saveDraftLayout() {
+ fetch("/__alias_layout", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ version: 1, nodes: layoutSnapshot() }),
+ }).catch(() => {});
+ }
+
+ function restoreDraftLayout() {
+ fetch("/__alias_layout")
+ .then((response) => (response.ok ? response.json() : null))
+ .then((draft) => {
+ if (!draft || !draft.nodes) return;
+ draggableNodes.forEach((node) => {
+ const position = draft.nodes[node.dataset.layoutKey];
+ if (
+ Array.isArray(position) &&
+ position.length === 2 &&
+ position.every(Number.isFinite)
+ ) {
+ setNodePosition(node, position[0], position[1]);
+ }
+ });
+ })
+ .catch(() => {});
+ }
+
+ draggableNodes.forEach((node) => {
+ const rect = node.querySelector("rect");
+ const width = Number(rect.getAttribute("width"));
+ const height = Number(rect.getAttribute("height"));
+ let drag = null;
+
+ updateLeader(node, nodePosition(node).x, nodePosition(node).y);
+ node.addEventListener("pointerdown", (event) => {
+ if (event.button !== 0) return;
+ const point = svgPoint(event);
+ const position = nodePosition(node);
+ drag = {
+ pointerId: event.pointerId,
+ offsetX: point.x - position.x,
+ offsetY: point.y - position.y,
+ originX: point.x,
+ originY: point.y,
+ moved: false,
+ };
+ node.setPointerCapture(event.pointerId);
+ node.classList.add("is-dragging");
+ event.preventDefault();
+ });
+ node.addEventListener("pointermove", (event) => {
+ if (!drag || event.pointerId !== drag.pointerId) return;
+ const point = svgPoint(event);
+ drag.moved ||= Math.hypot(
+ point.x - drag.originX,
+ point.y - drag.originY,
+ ) > 2;
+ const viewBox = svg.viewBox.baseVal;
+ const grip = 16;
+ const x = Math.max(
+ viewBox.x + grip - width,
+ Math.min(viewBox.x + viewBox.width - grip, point.x - drag.offsetX),
+ );
+ const y = Math.max(
+ viewBox.y + grip - height,
+ Math.min(viewBox.y + viewBox.height - grip, point.y - drag.offsetY),
+ );
+ setNodePosition(node, x, y);
+ });
+ node.addEventListener("pointerup", (event) => {
+ if (!drag || event.pointerId !== drag.pointerId) return;
+ if (drag.moved) {
+ suppressedClicks.add(node);
+ saveDraftLayout();
+ }
+ node.classList.remove("is-dragging");
+ node.releasePointerCapture(event.pointerId);
+ drag = null;
+ });
+ node.addEventListener("pointercancel", () => {
+ node.classList.remove("is-dragging");
+ drag = null;
+ });
+ });
+
+ restoreDraftLayout();
+
+ rows.forEach((row) => {
+ const cells = Array.from(row.element.querySelectorAll("td"));
+ cells[1].replaceChildren(makeKeywordLink(row, row.accepted));
+ cells[2].replaceChildren(makeKeywordLink(row, row.canonical));
+ });
+
+ totalNode.textContent = rows.length.toLocaleString();
+ contextTotalNode.textContent = contextCount.toLocaleString();
+
+ let selected = {
+ patterns: [],
+ targets: [],
+ label: "All aliases",
+ api: "",
+ };
+
+ function getRows(patterns) {
+ return rows.filter((row) => rowMatchesContexts(row, patterns));
+ }
+
+ function renderDetail(state, temporary) {
+ const matches = getRows(state.patterns);
+ titleNode.textContent = state.label;
+ copyNode.textContent = matches.length
+ ? `${matches.length.toLocaleString()} accepted ${
+ matches.length === 1 ? "spelling" : "spellings"
+ } in this area${temporary ? " — click to keep this view" : ""}.`
+ : "No compatibility aliases are registered for this area.";
+ previewNode.replaceChildren();
+
+ matches.slice(0, 8).forEach((row) => {
+ const item = document.createElement("div");
+ item.className = "uplt-alias-pair";
+ item.appendChild(makeKeywordLink(row, row.accepted));
+ const arrow = document.createElement("span");
+ arrow.setAttribute("aria-hidden", "true");
+ arrow.textContent = "→";
+ item.appendChild(arrow);
+ item.appendChild(makeKeywordLink(row, row.canonical));
+ const context = document.createElement("small");
+ context.textContent = row.context;
+ item.appendChild(context);
+ previewNode.appendChild(item);
+ });
+
+ if (matches.length > 8) {
+ const more = document.createElement("p");
+ more.className = "uplt-alias-more";
+ more.textContent = `+ ${(matches.length - 8).toLocaleString()} more below`;
+ previewNode.appendChild(more);
+ }
+
+ if (state.api) {
+ apiNode.href = state.api;
+ apiNode.hidden = false;
+ } else {
+ apiNode.removeAttribute("href");
+ apiNode.hidden = true;
+ }
+
+ targets.forEach((target) => {
+ const name = target.dataset.aliasTarget;
+ target.classList.toggle("is-active", selected.targets.includes(name));
+ target.classList.toggle(
+ "is-preview",
+ temporary && state.targets.includes(name),
+ );
+ });
+ }
+
+ function updateControlState() {
+ controls.forEach((control) => {
+ const patterns = splitContexts(control.dataset.contexts);
+ const active =
+ selected.patterns.length > 0 &&
+ patterns.some((pattern) =>
+ getRows(selected.patterns).some((row) =>
+ contextMatches(row.context, pattern),
+ ),
+ );
+ control.classList.toggle("is-active", active);
+ control.setAttribute("aria-pressed", active ? "true" : "false");
+ });
+ }
+
+ function applyFilter() {
+ const query = searchNode.value.trim().toLowerCase();
+ let visible = 0;
+ rows.forEach((row) => {
+ const inArea = rowMatchesContexts(row, selected.patterns);
+ const inSearch =
+ !query ||
+ `${row.context} ${row.accepted} ${row.canonical}`
+ .toLowerCase()
+ .includes(query);
+ const show = inArea && inSearch;
+ row.element.hidden = !show;
+ row.element.classList.toggle("is-alias-match", show && Boolean(query));
+ if (show) visible += 1;
+ });
+
+ sections.forEach((section) => {
+ section.hidden = !section.querySelector("tbody tr:not([hidden])");
+ });
+
+ const area = selected.patterns.length ? ` · ${selected.label}` : "";
+ statusNode.textContent = `${visible.toLocaleString()} of ${rows.length.toLocaleString()} mappings shown${area}`;
+ root.classList.toggle("has-filter", Boolean(query || selected.patterns.length));
+ updateControlState();
+ }
+
+ function stateForControl(control) {
+ return {
+ patterns: splitContexts(control.dataset.contexts),
+ targets: splitContexts(control.dataset.targets),
+ label: control.dataset.label,
+ api: control.dataset.api || "",
+ };
+ }
+
+ function previewControl(control) {
+ control.classList.add("is-preview");
+ renderDetail(stateForControl(control), true);
+ }
+
+ function stopPreview(control) {
+ control.classList.remove("is-preview");
+ renderDetail(selected, false);
+ }
+
+ function selectControl(control) {
+ const next = stateForControl(control);
+ const same =
+ next.patterns.join("|") === selected.patterns.join("|");
+ selected = same
+ ? { patterns: [], targets: [], label: "All aliases", api: "" }
+ : next;
+ renderDetail(selected, false);
+ applyFilter();
+ }
+
+ controls.forEach((control) => {
+ control.addEventListener("pointerenter", () => previewControl(control));
+ control.addEventListener("pointerleave", () => stopPreview(control));
+ control.addEventListener("focus", () => previewControl(control));
+ control.addEventListener("blur", () => stopPreview(control));
+ control.addEventListener("click", (event) => {
+ if (suppressedClicks.has(control)) {
+ suppressedClicks.delete(control);
+ event.preventDefault();
+ return;
+ }
+ selectControl(control);
+ });
+ if (control.tagName.toLowerCase() !== "button") {
+ control.addEventListener("keydown", (event) => {
+ if (event.key === "Enter" || event.key === " ") {
+ event.preventDefault();
+ selectControl(control);
+ }
+ });
+ }
+ });
+
+ searchNode.addEventListener("input", applyFilter);
+ resetNode.addEventListener("click", () => {
+ selected = {
+ patterns: [],
+ targets: [],
+ label: "All aliases",
+ api: "",
+ };
+ searchNode.value = "";
+ renderDetail(selected, false);
+ applyFilter();
+ searchNode.focus();
+ });
+
+ renderDetail(selected, false);
+ applyFilter();
+ root.dataset.aliasExplorerReady = "true";
+ }
+
+ document.addEventListener("DOMContentLoaded", function () {
+ document
+ .querySelectorAll(".uplt-alias-explorer")
+ .forEach(initializeAliasExplorer);
+ });
+})();
diff --git a/docs/_static/alias-map.svg b/docs/_static/alias-map.svg
new file mode 100644
index 000000000..59c283463
--- /dev/null
+++ b/docs/_static/alias-map.svg
@@ -0,0 +1,3505 @@
+
+
+
diff --git a/docs/aliases.rst b/docs/aliases.rst
new file mode 100644
index 000000000..e4020e628
--- /dev/null
+++ b/docs/aliases.rst
@@ -0,0 +1,1082 @@
+=============================
+Compatibility alias reference
+=============================
+
+UltraPlot's official documentation and function signatures exclusively use
+canonical parameter names. Alternative shorthand spellings (aliases) remain fully
+supported through silent, behind-the-scenes keyword translation.
+
+* **Context-dependent:** A single shorthand alias might map to different underlying
+ Matplotlib properties depending on the artist type.
+* **Silent translation:** Using aliases will not trigger deprecation warnings in
+ the current version, though these may be enabled in a future release.
+* **RC settings:** Entries marked ``rc (dotless)`` are generated from the rc registry.
+ To bypass compatibility aliases entirely, pass the canonical dotted spellings
+ through ``rc_kw``.
+
+Visual alias explorer
+---------------------
+
+Use the interactive diagram below to discover which aliases apply to different
+parts of your plot:
+
+* **Hover or Focus:** Target a labeled part of the figure to preview its aliases.
+* **Click:** Select a label to keep that area highlighted and filter the complete mapping table below.
+* **Learn More:** Click the API link in the detail panel to view the canonical documentation.
+
+
+.. raw:: html
+
+
+
+ 0 accepted spellings
+ 0 contexts
+ one canonical API
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+.. alias-table-start
+
+Function keyword aliases
+------------------------
+
+These mappings apply only in the listed call context.
+
+=================== ================= ==================
+Context Accepted spelling Canonical spelling
+=================== ================= ==================
+figure.init ref refnum
+figure.init aspect refaspect
+figure.init axwidth refwidth
+figure.init axheight refheight
+figure.init width figwidth
+figure.init height figheight
+axes.format ltitle lefttitle
+axes.format ctitle centertitle
+axes.format rtitle righttitle
+axes.format ultitle upperlefttitle
+axes.format uctitle uppercentertitle
+axes.format urtitle upperrighttitle
+axes.format lltitle lowerlefttitle
+axes.format lctitle lowercentertitle
+axes.format lrtitle lowerrighttitle
+cartesian.format xloc xspineloc
+cartesian.format yloc yspineloc
+cartesian.format xticklabels xformatter
+cartesian.format yticklabels yformatter
+cartesian.format xticks xlocator
+cartesian.format yticks ylocator
+cartesian.format xminorticks xminorlocator
+cartesian.format yminorticks yminorlocator
+geo.format lonlines lonlocator
+geo.format latlines latlocator
+geo.format lonminorlines lonminorlocator
+geo.format latminorlines latminorlocator
+geo.format lonlines_kw lonlocator_kw
+geo.format latlines_kw latlocator_kw
+geo.format lonminorlines_kw lonminorlocator_kw
+geo.format latminorlines_kw latminorlocator_kw
+polar.format thetalines thetalocator
+polar.format rlines rlocator
+polar.format thetaminorlines thetaminorlocator
+polar.format rminorlines rminorlocator
+polar.format thetalabels thetaformatter
+polar.format rlabels rformatter
+taylor.format corrlines corrlocator
+taylor.format corrticks corrlocator
+figure.format figtitle suptitle
+figure.format llabels leftlabels
+figure.format rowlabels leftlabels
+figure.format rlabels rightlabels
+figure.format blabels bottomlabels
+figure.format tlabels toplabels
+figure.format collabels toplabels
+colorbar location loc
+colorbar grid drawedges
+colorbar edges drawedges
+colorbar shrink length
+colorbar title label
+colorbar labelloc labellocation
+colorbar locator ticks
+colorbar formatter format
+colorbar ticklabels format
+colorbar minorlocator minorticks
+colorbar c color
+colorbar lw linewidth
+colorbar tickdir tickdirection
+colorbar frame frameon
+legend location loc
+legend ncol ncols
+legend frame frameon
+gridspec wratios width_ratios
+gridspec hratios height_ratios
+subplot proj projection
+subplot proj_kw projection_kw
+inset proj projection
+cycle N samples
+projection lon_0 lon0
+projection lat_0 lat0
+scale.log basex base
+scale.log basey base
+scale.log nonposx nonpos
+scale.log nonposy nonpos
+scale.log subsx subs
+scale.log subsy subs
+scale.symlog basex base
+scale.symlog basey base
+scale.symlog linthreshx linthresh
+scale.symlog linthreshy linthresh
+scale.symlog linscalex linscale
+scale.symlog linscaley linscale
+scale.symlog subsx subs
+scale.symlog subsy subs
+plot.labels fmt formatter
+plot.text c color
+plot.text colors color
+plot.text size fontsize
+plot.contour_labels c colors
+plot.contour_labels color colors
+plot.contour_labels size fontsize
+plot.error_bars bars barstds
+plot.error_bars barstd barstds
+plot.error_bars barpctile barpctiles
+plot.error_bars boxes boxstds
+plot.error_bars boxstd boxstds
+plot.error_bars boxpctile boxpctiles
+plot.error_shading shade shadestds
+plot.error_shading shadestd shadestds
+plot.error_shading shadepctile shadepctiles
+plot.error_shading fade fadestds
+plot.error_shading fadestd fadestds
+plot.error_shading fadepctile fadepctiles
+plot.colormap c colors
+plot.colormap color colors
+plot.levels N levels
+plot.stacked stack stacked
+plot.statistics mean means
+plot.statistics median medians
+plot.boxplot showmeans means
+plot.boxplot filled fill
+plot.violinplot showmeans means
+plot.violinplot showmedians medians
+plot.hist width rwidth
+plot.hist stack stacked
+plot.hist filled fill
+plot.pie labelpad labeldistance
+=================== ================= ==================
+
+Artist property aliases
+-----------------------
+
+These are Matplotlib-style shorthand properties accepted while styling artists.
+
+================ ================= ==================
+Context Accepted spelling Canonical spelling
+================ ================= ==================
+style.rgba r red
+style.rgba g green
+style.rgba b blue
+style.rgba a alpha
+style.hsla h hue
+style.hsla s saturation
+style.hsla c saturation
+style.hsla chroma saturation
+style.hsla l luminance
+style.hsla a alpha
+style.patch a alpha
+style.patch alphas alpha
+style.patch fa alpha
+style.patch facealpha alpha
+style.patch facealphas alpha
+style.patch fillalpha alpha
+style.patch fillalphas alpha
+style.patch c color
+style.patch colors color
+style.patch ec edgecolor
+style.patch edgecolors edgecolor
+style.patch fc facecolor
+style.patch facecolors facecolor
+style.patch fillcolor facecolor
+style.patch fillcolors facecolor
+style.patch h hatch
+style.patch hatching hatch
+style.patch ls linestyle
+style.patch linestyles linestyle
+style.patch lw linewidth
+style.patch linewidths linewidth
+style.patch ew linewidth
+style.patch edgewidth linewidth
+style.patch edgewidths linewidth
+style.patch z zorder
+style.patch zorders zorder
+style.line a alpha
+style.line alphas alpha
+style.line c color
+style.line colors color
+style.line d dashes
+style.line dash dashes
+style.line ds drawstyle
+style.line drawstyles drawstyle
+style.line fs fillstyle
+style.line fillstyles fillstyle
+style.line mfs fillstyle
+style.line markerfillstyle fillstyle
+style.line markerfillstyles fillstyle
+style.line ls linestyle
+style.line linestyles linestyle
+style.line lw linewidth
+style.line linewidths linewidth
+style.line m marker
+style.line markers marker
+style.line s markersize
+style.line ms markersize
+style.line markersizes markersize
+style.line ew markeredgewidth
+style.line edgewidth markeredgewidth
+style.line edgewidths markeredgewidth
+style.line mew markeredgewidth
+style.line markeredgewidths markeredgewidth
+style.line ec markeredgecolor
+style.line edgecolor markeredgecolor
+style.line edgecolors markeredgecolor
+style.line mec markeredgecolor
+style.line markeredgecolors markeredgecolor
+style.line fc markerfacecolor
+style.line facecolor markerfacecolor
+style.line facecolors markerfacecolor
+style.line fillcolor markerfacecolor
+style.line fillcolors markerfacecolor
+style.line mc markerfacecolor
+style.line markercolor markerfacecolor
+style.line markercolors markerfacecolor
+style.line mfc markerfacecolor
+style.line markerfacecolors markerfacecolor
+style.line z zorder
+style.line zorders zorder
+style.collection a alpha
+style.collection alphas alpha
+style.collection c colors
+style.collection color colors
+style.collection ec edgecolors
+style.collection edgecolor edgecolors
+style.collection mec edgecolors
+style.collection markeredgecolor edgecolors
+style.collection markeredgecolors edgecolors
+style.collection fc facecolors
+style.collection facecolor facecolors
+style.collection fillcolor facecolors
+style.collection fillcolors facecolors
+style.collection mc facecolors
+style.collection markercolor facecolors
+style.collection markercolors facecolors
+style.collection mfc facecolors
+style.collection markerfacecolor facecolors
+style.collection markerfacecolors facecolors
+style.collection ls linestyles
+style.collection linestyle linestyles
+style.collection lw linewidths
+style.collection linewidth linewidths
+style.collection ew linewidths
+style.collection edgewidth linewidths
+style.collection edgewidths linewidths
+style.collection mew linewidths
+style.collection markeredgewidth linewidths
+style.collection markeredgewidths linewidths
+style.collection m marker
+style.collection markers marker
+style.collection s sizes
+style.collection ms sizes
+style.collection markersize sizes
+style.collection markersizes sizes
+style.collection z zorder
+style.collection zorders zorder
+style.text c color
+style.text fontcolor color
+style.text family fontfamily
+style.text name fontfamily
+style.text fontname fontfamily
+style.text size fontsize
+style.text stretch fontstretch
+style.text style fontstyle
+style.text variant fontvariant
+style.text weight fontweight
+style.text fp fontproperties
+style.text font fontproperties
+style.text font_properties fontproperties
+style.text z zorder
+style.text zorders zorder
+================ ================= ==================
+
+Dotless rc aliases
+------------------
+
+Use the dotted canonical spelling through ``rc_kw`` when avoiding the accepted shorthand.
+
+============ ================================= ==================================
+Context Accepted spelling Canonical spelling
+============ ================================= ==================================
+rc (dotless) _internalclassic_mode _internal.classic_mode
+rc (dotless) abcbbox abc.bbox
+rc (dotless) abcbboxalpha abc.bboxalpha
+rc (dotless) abcbboxcolor abc.bboxcolor
+rc (dotless) abcbboxpad abc.bboxpad
+rc (dotless) abcbboxstyle abc.bboxstyle
+rc (dotless) abcborder abc.border
+rc (dotless) abcborderwidth abc.borderwidth
+rc (dotless) abccolor abc.color
+rc (dotless) abcformat abc.format
+rc (dotless) abcloc abc.loc
+rc (dotless) abcsize abc.size
+rc (dotless) abcstyle abc.style
+rc (dotless) abctitlepad abc.titlepad
+rc (dotless) abcweight abc.weight
+rc (dotless) aggpathchunksize agg.path.chunksize
+rc (dotless) animationbitrate animation.bitrate
+rc (dotless) animationcodec animation.codec
+rc (dotless) animationconvert_args animation.convert_args
+rc (dotless) animationconvert_path animation.convert_path
+rc (dotless) animationembed_limit animation.embed_limit
+rc (dotless) animationffmpeg_args animation.ffmpeg_args
+rc (dotless) animationffmpeg_path animation.ffmpeg_path
+rc (dotless) animationframe_format animation.frame_format
+rc (dotless) animationhtml animation.html
+rc (dotless) animationwriter animation.writer
+rc (dotless) axes3dautomargin axes3d.automargin
+rc (dotless) axes3dgrid axes3d.grid
+rc (dotless) axes3dmouserotationstyle axes3d.mouserotationstyle
+rc (dotless) axes3dtrackballborder axes3d.trackballborder
+rc (dotless) axes3dtrackballsize axes3d.trackballsize
+rc (dotless) axes3dxaxispanecolor axes3d.xaxis.panecolor
+rc (dotless) axes3dyaxispanecolor axes3d.yaxis.panecolor
+rc (dotless) axes3dzaxispanecolor axes3d.zaxis.panecolor
+rc (dotless) axesalpha axes.alpha
+rc (dotless) axesautolimit_mode axes.autolimit_mode
+rc (dotless) axesaxisbelow axes.axisbelow
+rc (dotless) axesedgecolor axes.edgecolor
+rc (dotless) axesfacealpha axes.facealpha
+rc (dotless) axesfacecolor axes.facecolor
+rc (dotless) axesformatterlimits axes.formatter.limits
+rc (dotless) axesformattermin_exponent axes.formatter.min_exponent
+rc (dotless) axesformatteroffset_threshold axes.formatter.offset_threshold
+rc (dotless) axesformattertimerotation axes.formatter.timerotation
+rc (dotless) axesformatteruse_locale axes.formatter.use_locale
+rc (dotless) axesformatteruse_mathtext axes.formatter.use_mathtext
+rc (dotless) axesformatteruseoffset axes.formatter.useoffset
+rc (dotless) axesformatterzerotrim axes.formatter.zerotrim
+rc (dotless) axesgrid axes.grid
+rc (dotless) axesgridaxis axes.grid.axis
+rc (dotless) axesgridwhich axes.grid.which
+rc (dotless) axesinbounds axes.inbounds
+rc (dotless) axeslabelcolor axes.labelcolor
+rc (dotless) axeslabelpad axes.labelpad
+rc (dotless) axeslabelsize axes.labelsize
+rc (dotless) axeslabelweight axes.labelweight
+rc (dotless) axeslinewidth axes.linewidth
+rc (dotless) axesmargin axes.margin
+rc (dotless) axesprop_cycle axes.prop_cycle
+rc (dotless) axesspinesbottom axes.spines.bottom
+rc (dotless) axesspinesleft axes.spines.left
+rc (dotless) axesspinesright axes.spines.right
+rc (dotless) axesspinestop axes.spines.top
+rc (dotless) axessticky_edges axes.sticky_edges
+rc (dotless) axestitlecolor axes.titlecolor
+rc (dotless) axestitlelocation axes.titlelocation
+rc (dotless) axestitlepad axes.titlepad
+rc (dotless) axestitlesize axes.titlesize
+rc (dotless) axestitleweight axes.titleweight
+rc (dotless) axestitley axes.titley
+rc (dotless) axesunicode_minus axes.unicode_minus
+rc (dotless) axesxmargin axes.xmargin
+rc (dotless) axesymargin axes.ymargin
+rc (dotless) axeszmargin axes.zmargin
+rc (dotless) barbar_labels bar.bar_labels
+rc (dotless) bordersalpha borders.alpha
+rc (dotless) borderscolor borders.color
+rc (dotless) borderslinewidth borders.linewidth
+rc (dotless) bordersrasterized borders.rasterized
+rc (dotless) borderszorder borders.zorder
+rc (dotless) bottomlabelcolor bottomlabel.color
+rc (dotless) bottomlabelpad bottomlabel.pad
+rc (dotless) bottomlabelrotation bottomlabel.rotation
+rc (dotless) bottomlabelsharedpad bottomlabel.sharedpad
+rc (dotless) bottomlabelsize bottomlabel.size
+rc (dotless) bottomlabelweight bottomlabel.weight
+rc (dotless) boxplotbootstrap boxplot.bootstrap
+rc (dotless) boxplotboxpropscolor boxplot.boxprops.color
+rc (dotless) boxplotboxpropslinestyle boxplot.boxprops.linestyle
+rc (dotless) boxplotboxpropslinewidth boxplot.boxprops.linewidth
+rc (dotless) boxplotcappropscolor boxplot.capprops.color
+rc (dotless) boxplotcappropslinestyle boxplot.capprops.linestyle
+rc (dotless) boxplotcappropslinewidth boxplot.capprops.linewidth
+rc (dotless) boxplotflierpropscolor boxplot.flierprops.color
+rc (dotless) boxplotflierpropslinestyle boxplot.flierprops.linestyle
+rc (dotless) boxplotflierpropslinewidth boxplot.flierprops.linewidth
+rc (dotless) boxplotflierpropsmarker boxplot.flierprops.marker
+rc (dotless) boxplotflierpropsmarkeredgecolor boxplot.flierprops.markeredgecolor
+rc (dotless) boxplotflierpropsmarkeredgewidth boxplot.flierprops.markeredgewidth
+rc (dotless) boxplotflierpropsmarkerfacecolor boxplot.flierprops.markerfacecolor
+rc (dotless) boxplotflierpropsmarkersize boxplot.flierprops.markersize
+rc (dotless) boxplotmeanline boxplot.meanline
+rc (dotless) boxplotmeanpropscolor boxplot.meanprops.color
+rc (dotless) boxplotmeanpropslinestyle boxplot.meanprops.linestyle
+rc (dotless) boxplotmeanpropslinewidth boxplot.meanprops.linewidth
+rc (dotless) boxplotmeanpropsmarker boxplot.meanprops.marker
+rc (dotless) boxplotmeanpropsmarkeredgecolor boxplot.meanprops.markeredgecolor
+rc (dotless) boxplotmeanpropsmarkerfacecolor boxplot.meanprops.markerfacecolor
+rc (dotless) boxplotmeanpropsmarkersize boxplot.meanprops.markersize
+rc (dotless) boxplotmedianpropscolor boxplot.medianprops.color
+rc (dotless) boxplotmedianpropslinestyle boxplot.medianprops.linestyle
+rc (dotless) boxplotmedianpropslinewidth boxplot.medianprops.linewidth
+rc (dotless) boxplotnotch boxplot.notch
+rc (dotless) boxplotpatchartist boxplot.patchartist
+rc (dotless) boxplotshowbox boxplot.showbox
+rc (dotless) boxplotshowcaps boxplot.showcaps
+rc (dotless) boxplotshowfliers boxplot.showfliers
+rc (dotless) boxplotshowmeans boxplot.showmeans
+rc (dotless) boxplotvertical boxplot.vertical
+rc (dotless) boxplotwhiskerpropscolor boxplot.whiskerprops.color
+rc (dotless) boxplotwhiskerpropslinestyle boxplot.whiskerprops.linestyle
+rc (dotless) boxplotwhiskerpropslinewidth boxplot.whiskerprops.linewidth
+rc (dotless) boxplotwhiskers boxplot.whiskers
+rc (dotless) cartopyautoextent cartopy.autoextent
+rc (dotless) cartopycircular cartopy.circular
+rc (dotless) cftimemax_display_ticks cftime.max_display_ticks
+rc (dotless) cftimeresolution cftime.resolution
+rc (dotless) cftimetime_resolution_format cftime.time_resolution_format
+rc (dotless) cftimetime_unit cftime.time_unit
+rc (dotless) chordend chord.end
+rc (dotless) chordendspace chord.endspace
+rc (dotless) chordorder chord.order
+rc (dotless) chordr_lim chord.r_lim
+rc (dotless) chordspace chord.space
+rc (dotless) chordstart chord.start
+rc (dotless) chordticks_interval chord.ticks_interval
+rc (dotless) cmapautodiverging cmap.autodiverging
+rc (dotless) cmapcyclic cmap.cyclic
+rc (dotless) cmapdiscrete cmap.discrete
+rc (dotless) cmapdiverging cmap.diverging
+rc (dotless) cmapedgefix cmap.edgefix
+rc (dotless) cmapinbounds cmap.inbounds
+rc (dotless) cmaplevels cmap.levels
+rc (dotless) cmaplistedthresh cmap.listedthresh
+rc (dotless) cmaplut cmap.lut
+rc (dotless) cmapqualitative cmap.qualitative
+rc (dotless) cmaprobust cmap.robust
+rc (dotless) cmapsequential cmap.sequential
+rc (dotless) coastalpha coast.alpha
+rc (dotless) coastcolor coast.color
+rc (dotless) coastlinewidth coast.linewidth
+rc (dotless) coastrasterized coast.rasterized
+rc (dotless) coastzorder coast.zorder
+rc (dotless) colorbarcenter_levels colorbar.center_levels
+rc (dotless) colorbaredgecolor colorbar.edgecolor
+rc (dotless) colorbarextend colorbar.extend
+rc (dotless) colorbarfacecolor colorbar.facecolor
+rc (dotless) colorbarfancybox colorbar.fancybox
+rc (dotless) colorbarframealpha colorbar.framealpha
+rc (dotless) colorbarframeon colorbar.frameon
+rc (dotless) colorbargrid colorbar.grid
+rc (dotless) colorbarinsetextend colorbar.insetextend
+rc (dotless) colorbarinsetlength colorbar.insetlength
+rc (dotless) colorbarinsetpad colorbar.insetpad
+rc (dotless) colorbarinsetwidth colorbar.insetwidth
+rc (dotless) colorbarlabelrotation colorbar.labelrotation
+rc (dotless) colorbarlength colorbar.length
+rc (dotless) colorbarloc colorbar.loc
+rc (dotless) colorbaroutline colorbar.outline
+rc (dotless) colorbarrasterize colorbar.rasterize
+rc (dotless) colorbarrasterized colorbar.rasterized
+rc (dotless) colorbarshadow colorbar.shadow
+rc (dotless) colorbarwidth colorbar.width
+rc (dotless) contouralgorithm contour.algorithm
+rc (dotless) contourcorner_mask contour.corner_mask
+rc (dotless) contourlinewidth contour.linewidth
+rc (dotless) contournegative_linestyle contour.negative_linestyle
+rc (dotless) curved_quiverarrows_at_end curved_quiver.arrows_at_end
+rc (dotless) curved_quiverarrowsize curved_quiver.arrowsize
+rc (dotless) curved_quiverarrowstyle curved_quiver.arrowstyle
+rc (dotless) curved_quiverdensity curved_quiver.density
+rc (dotless) curved_quivergrains curved_quiver.grains
+rc (dotless) curved_quiverscale curved_quiver.scale
+rc (dotless) dateautoformatterday date.autoformatter.day
+rc (dotless) dateautoformatterhour date.autoformatter.hour
+rc (dotless) dateautoformattermicrosecond date.autoformatter.microsecond
+rc (dotless) dateautoformatterminute date.autoformatter.minute
+rc (dotless) dateautoformattermonth date.autoformatter.month
+rc (dotless) dateautoformattersecond date.autoformatter.second
+rc (dotless) dateautoformatteryear date.autoformatter.year
+rc (dotless) dateconverter date.converter
+rc (dotless) dateepoch date.epoch
+rc (dotless) dateinterval_multiples date.interval_multiples
+rc (dotless) docstringhardcopy docstring.hardcopy
+rc (dotless) errorbarcapsize errorbar.capsize
+rc (dotless) externalshrink external.shrink
+rc (dotless) figureautolayout figure.autolayout
+rc (dotless) figureconstrained_layouth_pad figure.constrained_layout.h_pad
+rc (dotless) figureconstrained_layouthspace figure.constrained_layout.hspace
+rc (dotless) figureconstrained_layoutuse figure.constrained_layout.use
+rc (dotless) figureconstrained_layoutw_pad figure.constrained_layout.w_pad
+rc (dotless) figureconstrained_layoutwspace figure.constrained_layout.wspace
+rc (dotless) figuredpi figure.dpi
+rc (dotless) figureedgecolor figure.edgecolor
+rc (dotless) figurefacecolor figure.facecolor
+rc (dotless) figurefigsize figure.figsize
+rc (dotless) figureframeon figure.frameon
+rc (dotless) figurehooks figure.hooks
+rc (dotless) figurelabelsize figure.labelsize
+rc (dotless) figurelabelweight figure.labelweight
+rc (dotless) figuremax_open_warning figure.max_open_warning
+rc (dotless) figureraise_window figure.raise_window
+rc (dotless) figuresubplotbottom figure.subplot.bottom
+rc (dotless) figuresubplothspace figure.subplot.hspace
+rc (dotless) figuresubplotleft figure.subplot.left
+rc (dotless) figuresubplotright figure.subplot.right
+rc (dotless) figuresubplottop figure.subplot.top
+rc (dotless) figuresubplotwspace figure.subplot.wspace
+rc (dotless) figuretitlesize figure.titlesize
+rc (dotless) figuretitleweight figure.titleweight
+rc (dotless) fontcursive font.cursive
+rc (dotless) fontfamily font.family
+rc (dotless) fontfantasy font.fantasy
+rc (dotless) fontlarge font.large
+rc (dotless) fontlargesize font.largesize
+rc (dotless) fontmonospace font.monospace
+rc (dotless) fontname font.name
+rc (dotless) fontsans-serif font.sans-serif
+rc (dotless) fontserif font.serif
+rc (dotless) fontsize font.size
+rc (dotless) fontsmall font.small
+rc (dotless) fontsmallsize font.smallsize
+rc (dotless) fontstretch font.stretch
+rc (dotless) fontstyle font.style
+rc (dotless) fontvariant font.variant
+rc (dotless) fontweight font.weight
+rc (dotless) formatterlimits formatter.limits
+rc (dotless) formatterlog formatter.log
+rc (dotless) formattermin_exponent formatter.min_exponent
+rc (dotless) formatteroffset_threshold formatter.offset_threshold
+rc (dotless) formattertimerotation formatter.timerotation
+rc (dotless) formatteruse_locale formatter.use_locale
+rc (dotless) formatteruse_mathtext formatter.use_mathtext
+rc (dotless) formatteruse_offset formatter.use_offset
+rc (dotless) formatterzerotrim formatter.zerotrim
+rc (dotless) geoaxesedgecolor geoaxes.edgecolor
+rc (dotless) geoaxesfacealpha geoaxes.facealpha
+rc (dotless) geoaxesfacecolor geoaxes.facecolor
+rc (dotless) geoaxeslinewidth geoaxes.linewidth
+rc (dotless) geobackend geo.backend
+rc (dotless) geochoroplethcountry_reso geo.choropleth.country_reso
+rc (dotless) geochoroplethcountry_territories geo.choropleth.country_territories
+rc (dotless) geochoroplethzorder geo.choropleth.zorder
+rc (dotless) geoextent geo.extent
+rc (dotless) geogridalpha geogrid.alpha
+rc (dotless) geogridcolor geogrid.color
+rc (dotless) geogridlabelpad geogrid.labelpad
+rc (dotless) geogridlabels geogrid.labels
+rc (dotless) geogridlabelsize geogrid.labelsize
+rc (dotless) geogridlatmax geogrid.latmax
+rc (dotless) geogridlatstep geogrid.latstep
+rc (dotless) geogridlinestyle geogrid.linestyle
+rc (dotless) geogridlinewidth geogrid.linewidth
+rc (dotless) geogridlonstep geogrid.lonstep
+rc (dotless) georound geo.round
+rc (dotless) graphaspect graph.aspect
+rc (dotless) graphdraw_edges graph.draw_edges
+rc (dotless) graphdraw_grid graph.draw_grid
+rc (dotless) graphdraw_labels graph.draw_labels
+rc (dotless) graphdraw_nodes graph.draw_nodes
+rc (dotless) graphdraw_spines graph.draw_spines
+rc (dotless) graphfacecolor graph.facecolor
+rc (dotless) graphrescale graph.rescale
+rc (dotless) gridalpha grid.alpha
+rc (dotless) gridbelow grid.below
+rc (dotless) gridcheckoverlap grid.checkoverlap
+rc (dotless) gridcolor grid.color
+rc (dotless) griddmslabels grid.dmslabels
+rc (dotless) gridgeolabels grid.geolabels
+rc (dotless) gridinlinelabels grid.inlinelabels
+rc (dotless) gridlabelcolor grid.labelcolor
+rc (dotless) gridlabelpad grid.labelpad
+rc (dotless) gridlabels grid.labels
+rc (dotless) gridlabelsize grid.labelsize
+rc (dotless) gridlabelweight grid.labelweight
+rc (dotless) gridlatinline grid.latinline
+rc (dotless) gridlinestyle grid.linestyle
+rc (dotless) gridlinewidth grid.linewidth
+rc (dotless) gridloninline grid.loninline
+rc (dotless) gridminoralpha gridminor.alpha
+rc (dotless) gridminorcolor gridminor.color
+rc (dotless) gridminorlatstep gridminor.latstep
+rc (dotless) gridminorlinestyle gridminor.linestyle
+rc (dotless) gridminorlinewidth gridminor.linewidth
+rc (dotless) gridminorlonstep gridminor.lonstep
+rc (dotless) gridminorstyle gridminor.style
+rc (dotless) gridminorwidth gridminor.width
+rc (dotless) gridnsteps grid.nsteps
+rc (dotless) gridpad grid.pad
+rc (dotless) gridratio grid.ratio
+rc (dotless) gridrotatelabels grid.rotatelabels
+rc (dotless) gridstyle grid.style
+rc (dotless) gridwidth grid.width
+rc (dotless) gridwidthratio grid.widthratio
+rc (dotless) hatchcolor hatch.color
+rc (dotless) hatchlinewidth hatch.linewidth
+rc (dotless) histbins hist.bins
+rc (dotless) imageaspect image.aspect
+rc (dotless) imagecmap image.cmap
+rc (dotless) imagecomposite_image image.composite_image
+rc (dotless) imagediscrete image.discrete
+rc (dotless) imageedgefix image.edgefix
+rc (dotless) imageinbounds image.inbounds
+rc (dotless) imageinterpolation image.interpolation
+rc (dotless) imageinterpolation_stage image.interpolation_stage
+rc (dotless) imagelevels image.levels
+rc (dotless) imagelut image.lut
+rc (dotless) imageorigin image.origin
+rc (dotless) imageresample image.resample
+rc (dotless) innerbordersalpha innerborders.alpha
+rc (dotless) innerborderscolor innerborders.color
+rc (dotless) innerborderslinewidth innerborders.linewidth
+rc (dotless) innerborderszorder innerborders.zorder
+rc (dotless) kdepoints kde.points
+rc (dotless) keymapback keymap.back
+rc (dotless) keymapcopy keymap.copy
+rc (dotless) keymapforward keymap.forward
+rc (dotless) keymapfullscreen keymap.fullscreen
+rc (dotless) keymapgrid keymap.grid
+rc (dotless) keymapgrid_minor keymap.grid_minor
+rc (dotless) keymaphelp keymap.help
+rc (dotless) keymaphome keymap.home
+rc (dotless) keymappan keymap.pan
+rc (dotless) keymapquit keymap.quit
+rc (dotless) keymapquit_all keymap.quit_all
+rc (dotless) keymapsave keymap.save
+rc (dotless) keymapxscale keymap.xscale
+rc (dotless) keymapyscale keymap.yscale
+rc (dotless) keymapzoom keymap.zoom
+rc (dotless) labelcolor label.color
+rc (dotless) labelpad label.pad
+rc (dotless) labelsize label.size
+rc (dotless) labelweight label.weight
+rc (dotless) lakesalpha lakes.alpha
+rc (dotless) lakescolor lakes.color
+rc (dotless) lakesrasterized lakes.rasterized
+rc (dotless) lakeszorder lakes.zorder
+rc (dotless) landalpha land.alpha
+rc (dotless) landcolor land.color
+rc (dotless) landrasterized land.rasterized
+rc (dotless) landzorder land.zorder
+rc (dotless) leftlabelcolor leftlabel.color
+rc (dotless) leftlabelpad leftlabel.pad
+rc (dotless) leftlabelrotation leftlabel.rotation
+rc (dotless) leftlabelsharedpad leftlabel.sharedpad
+rc (dotless) leftlabelsize leftlabel.size
+rc (dotless) leftlabelweight leftlabel.weight
+rc (dotless) legendborderaxespad legend.borderaxespad
+rc (dotless) legendborderpad legend.borderpad
+rc (dotless) legendcatalpha legend.cat.alpha
+rc (dotless) legendcatline legend.cat.line
+rc (dotless) legendcatlinestyle legend.cat.linestyle
+rc (dotless) legendcatlinewidth legend.cat.linewidth
+rc (dotless) legendcatmarker legend.cat.marker
+rc (dotless) legendcatmarkeredgecolor legend.cat.markeredgecolor
+rc (dotless) legendcatmarkeredgewidth legend.cat.markeredgewidth
+rc (dotless) legendcatmarkersize legend.cat.markersize
+rc (dotless) legendcolumnspacing legend.columnspacing
+rc (dotless) legendedgecolor legend.edgecolor
+rc (dotless) legendfacecolor legend.facecolor
+rc (dotless) legendfancybox legend.fancybox
+rc (dotless) legendfontsize legend.fontsize
+rc (dotless) legendframealpha legend.framealpha
+rc (dotless) legendframeon legend.frameon
+rc (dotless) legendgeoalpha legend.geo.alpha
+rc (dotless) legendgeocountry_proj legend.geo.country_proj
+rc (dotless) legendgeocountry_reso legend.geo.country_reso
+rc (dotless) legendgeocountry_territories legend.geo.country_territories
+rc (dotless) legendgeoedgecolor legend.geo.edgecolor
+rc (dotless) legendgeofacecolor legend.geo.facecolor
+rc (dotless) legendgeofill legend.geo.fill
+rc (dotless) legendgeohandlesize legend.geo.handlesize
+rc (dotless) legendgeolinewidth legend.geo.linewidth
+rc (dotless) legendhandleheight legend.handleheight
+rc (dotless) legendhandlelength legend.handlelength
+rc (dotless) legendhandletextpad legend.handletextpad
+rc (dotless) legendlabelcolor legend.labelcolor
+rc (dotless) legendlabelspacing legend.labelspacing
+rc (dotless) legendloc legend.loc
+rc (dotless) legendmarkerscale legend.markerscale
+rc (dotless) legendnumalpha legend.num.alpha
+rc (dotless) legendnumcmap legend.num.cmap
+rc (dotless) legendnumedgecolor legend.num.edgecolor
+rc (dotless) legendnumformat legend.num.format
+rc (dotless) legendnumlinewidth legend.num.linewidth
+rc (dotless) legendnumn legend.num.n
+rc (dotless) legendnumpoints legend.numpoints
+rc (dotless) legendscatterpoints legend.scatterpoints
+rc (dotless) legendshadow legend.shadow
+rc (dotless) legendsizealpha legend.size.alpha
+rc (dotless) legendsizearea legend.size.area
+rc (dotless) legendsizecolor legend.size.color
+rc (dotless) legendsizeformat legend.size.format
+rc (dotless) legendsizemarker legend.size.marker
+rc (dotless) legendsizemarkeredgecolor legend.size.markeredgecolor
+rc (dotless) legendsizemarkeredgewidth legend.size.markeredgewidth
+rc (dotless) legendsizeminsize legend.size.minsize
+rc (dotless) legendsizescale legend.size.scale
+rc (dotless) legendtitle_fontsize legend.title_fontsize
+rc (dotless) linesantialiased lines.antialiased
+rc (dotless) linescolor lines.color
+rc (dotless) linesdash_capstyle lines.dash_capstyle
+rc (dotless) linesdash_joinstyle lines.dash_joinstyle
+rc (dotless) linesdashdot_pattern lines.dashdot_pattern
+rc (dotless) linesdashed_pattern lines.dashed_pattern
+rc (dotless) linesdotted_pattern lines.dotted_pattern
+rc (dotless) lineslinestyle lines.linestyle
+rc (dotless) lineslinewidth lines.linewidth
+rc (dotless) linesmarker lines.marker
+rc (dotless) linesmarkeredgecolor lines.markeredgecolor
+rc (dotless) linesmarkeredgewidth lines.markeredgewidth
+rc (dotless) linesmarkerfacecolor lines.markerfacecolor
+rc (dotless) linesmarkersize lines.markersize
+rc (dotless) linesscale_dashes lines.scale_dashes
+rc (dotless) linessolid_capstyle lines.solid_capstyle
+rc (dotless) linessolid_joinstyle lines.solid_joinstyle
+rc (dotless) lollipopmarkersize lollipop.markersize
+rc (dotless) lollipopstemcolor lollipop.stemcolor
+rc (dotless) lollipopstemlinestyle lollipop.stemlinestyle
+rc (dotless) lollipopstemwidth lollipop.stemwidth
+rc (dotless) macosxwindow_mode macosx.window_mode
+rc (dotless) markersfillstyle markers.fillstyle
+rc (dotless) mathtextbf mathtext.bf
+rc (dotless) mathtextbfit mathtext.bfit
+rc (dotless) mathtextcal mathtext.cal
+rc (dotless) mathtextcm_symbols mathtext.cm_symbols
+rc (dotless) mathtextdefault mathtext.default
+rc (dotless) mathtextfallback mathtext.fallback
+rc (dotless) mathtextfontset mathtext.fontset
+rc (dotless) mathtextit mathtext.it
+rc (dotless) mathtextrm mathtext.rm
+rc (dotless) mathtextsf mathtext.sf
+rc (dotless) mathtexttt mathtext.tt
+rc (dotless) metacolor meta.color
+rc (dotless) metaedgecolor meta.edgecolor
+rc (dotless) metalinewidth meta.linewidth
+rc (dotless) metawidth meta.width
+rc (dotless) navigationpreview navigation.preview
+rc (dotless) oceanalpha ocean.alpha
+rc (dotless) oceancolor ocean.color
+rc (dotless) oceanrasterized ocean.rasterized
+rc (dotless) oceanzorder ocean.zorder
+rc (dotless) patchantialiased patch.antialiased
+rc (dotless) patchedgecolor patch.edgecolor
+rc (dotless) patchfacecolor patch.facecolor
+rc (dotless) patchforce_edgecolor patch.force_edgecolor
+rc (dotless) patchlinewidth patch.linewidth
+rc (dotless) patheffects path.effects
+rc (dotless) pathsimplify path.simplify
+rc (dotless) pathsimplify_threshold path.simplify_threshold
+rc (dotless) pathsketch path.sketch
+rc (dotless) pathsnap path.snap
+rc (dotless) pcolormeshsnap pcolormesh.snap
+rc (dotless) pcolorshading pcolor.shading
+rc (dotless) pdfcompression pdf.compression
+rc (dotless) pdffonttype pdf.fonttype
+rc (dotless) pdfinheritcolor pdf.inheritcolor
+rc (dotless) pdfuse14corefonts pdf.use14corefonts
+rc (dotless) pgfpreamble pgf.preamble
+rc (dotless) pgfrcfonts pgf.rcfonts
+rc (dotless) pgftexsystem pgf.texsystem
+rc (dotless) phylogenyalign_leaf_label phylogeny.align_leaf_label
+rc (dotless) phylogenyend phylogeny.end
+rc (dotless) phylogenyformat phylogeny.format
+rc (dotless) phylogenyignore_branch_length phylogeny.ignore_branch_length
+rc (dotless) phylogenyladderize phylogeny.ladderize
+rc (dotless) phylogenyleaf_label_rmargin phylogeny.leaf_label_rmargin
+rc (dotless) phylogenyleaf_label_size phylogeny.leaf_label_size
+rc (dotless) phylogenyouter phylogeny.outer
+rc (dotless) phylogenyr_lim phylogeny.r_lim
+rc (dotless) phylogenyreverse phylogeny.reverse
+rc (dotless) phylogenystart phylogeny.start
+rc (dotless) polaraxesgrid polaraxes.grid
+rc (dotless) psdistillerres ps.distiller.res
+rc (dotless) psfonttype ps.fonttype
+rc (dotless) pspapersize ps.papersize
+rc (dotless) psuseafm ps.useafm
+rc (dotless) psusedistiller ps.usedistiller
+rc (dotless) radarbg_color radar.bg_color
+rc (dotless) radarcircular radar.circular
+rc (dotless) radarfill radar.fill
+rc (dotless) radargrid_interval_ratio radar.grid_interval_ratio
+rc (dotless) radarmarker_size radar.marker_size
+rc (dotless) radarr_lim radar.r_lim
+rc (dotless) radarshow_grid_label radar.show_grid_label
+rc (dotless) radarvmax radar.vmax
+rc (dotless) radarvmin radar.vmin
+rc (dotless) ribbonflowalpha ribbon.flow.alpha
+rc (dotless) ribbonflowcurvature ribbon.flow.curvature
+rc (dotless) ribbonnodewidth ribbon.nodewidth
+rc (dotless) ribbonrowheightratio ribbon.rowheightratio
+rc (dotless) ribbontopic_label_box ribbon.topic_label_box
+rc (dotless) ribbontopic_label_offset ribbon.topic_label_offset
+rc (dotless) ribbontopic_label_size ribbon.topic_label_size
+rc (dotless) ribbontopic_labels ribbon.topic_labels
+rc (dotless) ribbonxmargin ribbon.xmargin
+rc (dotless) ribbonymargin ribbon.ymargin
+rc (dotless) rightlabelcolor rightlabel.color
+rc (dotless) rightlabelpad rightlabel.pad
+rc (dotless) rightlabelrotation rightlabel.rotation
+rc (dotless) rightlabelsharedpad rightlabel.sharedpad
+rc (dotless) rightlabelsize rightlabel.size
+rc (dotless) rightlabelweight rightlabel.weight
+rc (dotless) riversalpha rivers.alpha
+rc (dotless) riverscolor rivers.color
+rc (dotless) riverslinewidth rivers.linewidth
+rc (dotless) riversrasterized rivers.rasterized
+rc (dotless) riverszorder rivers.zorder
+rc (dotless) sankeyalign sankey.align
+rc (dotless) sankeyconnect sankey.connect
+rc (dotless) sankeyflow_label_pos sankey.flow_label_pos
+rc (dotless) sankeyflow_labels sankey.flow_labels
+rc (dotless) sankeyflow_sort sankey.flow_sort
+rc (dotless) sankeyflowalpha sankey.flow.alpha
+rc (dotless) sankeyflowcurvature sankey.flow.curvature
+rc (dotless) sankeymargin sankey.margin
+rc (dotless) sankeynode_label_offset sankey.node_label_offset
+rc (dotless) sankeynode_label_outside sankey.node_label_outside
+rc (dotless) sankeynode_labels sankey.node_labels
+rc (dotless) sankeynodefacecolor sankey.node.facecolor
+rc (dotless) sankeynodepad sankey.nodepad
+rc (dotless) sankeynodewidth sankey.nodewidth
+rc (dotless) sankeyother_label sankey.other_label
+rc (dotless) sankeypathlabel sankey.pathlabel
+rc (dotless) sankeypathlengths sankey.pathlengths
+rc (dotless) sankeyrotation sankey.rotation
+rc (dotless) sankeytrunklength sankey.trunklength
+rc (dotless) savefigbbox savefig.bbox
+rc (dotless) savefigdirectory savefig.directory
+rc (dotless) savefigdpi savefig.dpi
+rc (dotless) savefigedgecolor savefig.edgecolor
+rc (dotless) savefigfacecolor savefig.facecolor
+rc (dotless) savefigformat savefig.format
+rc (dotless) savefigorientation savefig.orientation
+rc (dotless) savefigpad_inches savefig.pad_inches
+rc (dotless) savefigtransparent savefig.transparent
+rc (dotless) scatteredgecolors scatter.edgecolors
+rc (dotless) scattermarker scatter.marker
+rc (dotless) subplotsalign subplots.align
+rc (dotless) subplotsaxpad subplots.axpad
+rc (dotless) subplotsaxwidth subplots.axwidth
+rc (dotless) subplotsequalspace subplots.equalspace
+rc (dotless) subplotsgroupspace subplots.groupspace
+rc (dotless) subplotsinnerpad subplots.innerpad
+rc (dotless) subplotsouterpad subplots.outerpad
+rc (dotless) subplotspad subplots.pad
+rc (dotless) subplotspanelpad subplots.panelpad
+rc (dotless) subplotspanelwidth subplots.panelwidth
+rc (dotless) subplotspixelsnap subplots.pixelsnap
+rc (dotless) subplotsrefwidth subplots.refwidth
+rc (dotless) subplotsshare subplots.share
+rc (dotless) subplotsspan subplots.span
+rc (dotless) subplotstight subplots.tight
+rc (dotless) suptitlecolor suptitle.color
+rc (dotless) suptitlepad suptitle.pad
+rc (dotless) suptitlesize suptitle.size
+rc (dotless) suptitleweight suptitle.weight
+rc (dotless) svgfonttype svg.fonttype
+rc (dotless) svghashsalt svg.hashsalt
+rc (dotless) svgid svg.id
+rc (dotless) svgimage_inline svg.image_inline
+rc (dotless) textalign text.align
+rc (dotless) textalignarrows text.align.arrows
+rc (dotless) textalignmaxiter text.align.maxiter
+rc (dotless) textalignpad text.align.pad
+rc (dotless) textantialiased text.antialiased
+rc (dotless) textborderstyle text.borderstyle
+rc (dotless) textcolor text.color
+rc (dotless) textcurvedavoid_overlap text.curved.avoid_overlap
+rc (dotless) textcurvedcurvature_pad text.curved.curvature_pad
+rc (dotless) textcurvedellipsis text.curved.ellipsis
+rc (dotless) textcurvedmin_advance text.curved.min_advance
+rc (dotless) textcurvedoverlap_tol text.curved.overlap_tol
+rc (dotless) textcurvedupright text.curved.upright
+rc (dotless) texthinting text.hinting
+rc (dotless) texthinting_factor text.hinting_factor
+rc (dotless) textkerning_factor text.kerning_factor
+rc (dotless) textlabelsize text.labelsize
+rc (dotless) textlatexpreamble text.latex.preamble
+rc (dotless) textparse_math text.parse_math
+rc (dotless) texttitlesize text.titlesize
+rc (dotless) textusetex text.usetex
+rc (dotless) tickcolor tick.color
+rc (dotless) tickdir tick.dir
+rc (dotless) ticklabelcolor tick.labelcolor
+rc (dotless) ticklabelpad tick.labelpad
+rc (dotless) ticklabelsize tick.labelsize
+rc (dotless) ticklabelweight tick.labelweight
+rc (dotless) ticklen tick.len
+rc (dotless) ticklenratio tick.lenratio
+rc (dotless) ticklinewidth tick.linewidth
+rc (dotless) tickminor tick.minor
+rc (dotless) tickpad tick.pad
+rc (dotless) tickratio tick.ratio
+rc (dotless) tickwidth tick.width
+rc (dotless) tickwidthratio tick.widthratio
+rc (dotless) titleabove title.above
+rc (dotless) titlebbox title.bbox
+rc (dotless) titlebboxalpha title.bboxalpha
+rc (dotless) titlebboxcolor title.bboxcolor
+rc (dotless) titlebboxpad title.bboxpad
+rc (dotless) titlebboxstyle title.bboxstyle
+rc (dotless) titleborder title.border
+rc (dotless) titleborderwidth title.borderwidth
+rc (dotless) titlecolor title.color
+rc (dotless) titleloc title.loc
+rc (dotless) titlepad title.pad
+rc (dotless) titlesize title.size
+rc (dotless) titleweight title.weight
+rc (dotless) tkwindow_focus tk.window_focus
+rc (dotless) toplabelcolor toplabel.color
+rc (dotless) toplabelpad toplabel.pad
+rc (dotless) toplabelrotation toplabel.rotation
+rc (dotless) toplabelsharedpad toplabel.sharedpad
+rc (dotless) toplabelsize toplabel.size
+rc (dotless) toplabelweight toplabel.weight
+rc (dotless) ultraplotcheck_for_latest_version ultraplot.check_for_latest_version
+rc (dotless) ultraploteager_import ultraplot.eager_import
+rc (dotless) webaggaddress webagg.address
+rc (dotless) webaggopen_in_browser webagg.open_in_browser
+rc (dotless) webaggport webagg.port
+rc (dotless) webaggport_retries webagg.port_retries
+rc (dotless) xaxislabellocation xaxis.labellocation
+rc (dotless) xtickalignment xtick.alignment
+rc (dotless) xtickbottom xtick.bottom
+rc (dotless) xtickcolor xtick.color
+rc (dotless) xtickdirection xtick.direction
+rc (dotless) xticklabelbottom xtick.labelbottom
+rc (dotless) xticklabelcolor xtick.labelcolor
+rc (dotless) xticklabelsize xtick.labelsize
+rc (dotless) xticklabeltop xtick.labeltop
+rc (dotless) xtickmajorbottom xtick.major.bottom
+rc (dotless) xtickmajorpad xtick.major.pad
+rc (dotless) xtickmajorsize xtick.major.size
+rc (dotless) xtickmajortop xtick.major.top
+rc (dotless) xtickmajorwidth xtick.major.width
+rc (dotless) xtickminorbottom xtick.minor.bottom
+rc (dotless) xtickminorndivs xtick.minor.ndivs
+rc (dotless) xtickminorpad xtick.minor.pad
+rc (dotless) xtickminorsize xtick.minor.size
+rc (dotless) xtickminortop xtick.minor.top
+rc (dotless) xtickminorvisible xtick.minor.visible
+rc (dotless) xtickminorwidth xtick.minor.width
+rc (dotless) xticktop xtick.top
+rc (dotless) yaxislabellocation yaxis.labellocation
+rc (dotless) ytickalignment ytick.alignment
+rc (dotless) ytickcolor ytick.color
+rc (dotless) ytickdirection ytick.direction
+rc (dotless) yticklabelcolor ytick.labelcolor
+rc (dotless) yticklabelleft ytick.labelleft
+rc (dotless) yticklabelright ytick.labelright
+rc (dotless) yticklabelsize ytick.labelsize
+rc (dotless) ytickleft ytick.left
+rc (dotless) ytickmajorleft ytick.major.left
+rc (dotless) ytickmajorpad ytick.major.pad
+rc (dotless) ytickmajorright ytick.major.right
+rc (dotless) ytickmajorsize ytick.major.size
+rc (dotless) ytickmajorwidth ytick.major.width
+rc (dotless) ytickminorleft ytick.minor.left
+rc (dotless) ytickminorndivs ytick.minor.ndivs
+rc (dotless) ytickminorpad ytick.minor.pad
+rc (dotless) ytickminorright ytick.minor.right
+rc (dotless) ytickminorsize ytick.minor.size
+rc (dotless) ytickminorvisible ytick.minor.visible
+rc (dotless) ytickminorwidth ytick.minor.width
+rc (dotless) ytickright ytick.right
+============ ================================= ==================================
+
+.. alias-table-end
+
+Supplying a legacy and canonical spelling together is an error. For example,
+``ax.format(xlocator=5, xticks=10)`` does not silently choose one value.
diff --git a/docs/conf.py b/docs/conf.py
index cb17889ef..6bc2931f8 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -550,11 +550,11 @@ def _reset_ultraplot(gallery_conf, fname):
if HAVE_ULTRAPLOT_THEME_EXT:
- html_css_files = []
- html_js_files = []
+ html_css_files = ["alias-explorer-v3.css"]
+ html_js_files = ["alias-explorer-v3.js"]
else:
- html_css_files = ["custom.css"]
- html_js_files = ["custom.js"]
+ html_css_files = ["custom.css", "alias-explorer-v3.css"]
+ html_js_files = ["custom.js", "alias-explorer-v3.js"]
# -- Options for LaTeX output ------------------------------------------------
diff --git a/docs/index.rst b/docs/index.rst
index 5b5ec248d..557956553 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -150,6 +150,7 @@ For more details, check the full :doc:`User guide ` and :doc:`API Referen
:hidden:
api
+ aliases
lazy_loading
external-links
whats_new
diff --git a/docs/subplots.py b/docs/subplots.py
index 5754f9f7d..f899751ac 100644
--- a/docs/subplots.py
+++ b/docs/subplots.py
@@ -555,7 +555,7 @@
label="colorbar",
width="2em",
extendsize="3em",
- shrink=0.8,
+ length=0.8,
)
pax = axs[2].panel_axes("r", width="5en")
axs.format(
diff --git a/tools/generate_alias_figure.py b/tools/generate_alias_figure.py
new file mode 100644
index 000000000..0c038fb40
--- /dev/null
+++ b/tools/generate_alias_figure.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+"""Generate the real UltraPlot figure used by the alias explorer."""
+
+import sys
+from pathlib import Path
+
+import numpy as np
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+
+import ultraplot as uplt # noqa: E402
+
+
+def main():
+ output = ROOT / "docs" / "_static" / "alias-map.svg"
+ preview = Path("/tmp/ultraplot-alias-map.png")
+ x = np.linspace(0, 10, 100)
+ baseline = 0.32 * np.sin(1.15 * x) + 0.07 * x
+ comparison = 0.25 * np.cos(0.9 * x + 0.5) + 0.055 * x + 0.18
+
+ with uplt.rc.context(
+ {
+ "font.size": 10,
+ "axes.grid": True,
+ "svg.hashsalt": "ultraplot-alias-map",
+ }
+ ):
+ fig, ax = uplt.subplots(refwidth=4.6, refheight=2.9)
+ ax.plot(
+ x,
+ baseline,
+ color="#168f83",
+ linewidth=2.2,
+ label="Observed",
+ )
+ ax.plot(
+ x,
+ comparison,
+ color="#3976d3",
+ linewidth=2.0,
+ linestyle="--",
+ label="Reference",
+ )
+ points = ax.scatter(
+ x[::10],
+ baseline[::10],
+ c=x[::10],
+ cmap="batlow",
+ s=28,
+ edgecolor="white",
+ linewidth=0.7,
+ zorder=3,
+ )
+ ax.legend(location="upper right", frameon=True, ncols=1)
+ ax.colorbar(
+ points,
+ location="right",
+ label="Progress",
+ length=0.78,
+ ticks=[0, 5, 10],
+ )
+ inset = ax.inset([0.07, 0.63, 0.27, 0.25])
+ inset.plot(
+ x[:35],
+ baseline[:35],
+ color="#168f83",
+ linewidth=1.3,
+ )
+ inset.format(
+ title="Inset",
+ xlocator=[],
+ ylocator=[],
+ grid=False,
+ )
+ ax.format(
+ xlabel="Time",
+ ylabel="Signal",
+ lefttitle="Primary title",
+ righttitle="Context",
+ xlocator=2,
+ ylocator=0.25,
+ grid=True,
+ gridalpha=0.18,
+ )
+ fig.format(
+ suptitle="Where aliases act",
+ leftlabels=["Left labels"],
+ rightlabels=["Right labels"],
+ )
+ fig.savefig(output, transparent=False, metadata={"Date": None})
+ source = output.read_text()
+ output.write_text("\n".join(line.rstrip() for line in source.splitlines()) + "\n")
+ fig.savefig(preview, dpi=180, transparent=False)
+ uplt.close(fig)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/generate_alias_reference.py b/tools/generate_alias_reference.py
new file mode 100644
index 000000000..89587328b
--- /dev/null
+++ b/tools/generate_alias_reference.py
@@ -0,0 +1,96 @@
+#!/usr/bin/env python3
+"""Generate or verify the compatibility-alias reference table."""
+
+import argparse
+import re
+import sys
+from pathlib import Path
+
+ROOT = Path(__file__).resolve().parents[1]
+sys.path.insert(0, str(ROOT))
+
+from ultraplot.internals.kwargs import ( # noqa: E402
+ _alias_registry,
+ _format_alias_reference,
+)
+
+
+OUTPUT = ROOT / "docs" / "aliases.rst"
+SCRIPT = ROOT / "docs" / "_static" / "alias-explorer-v3.js"
+START = ".. alias-table-start"
+END = ".. alias-table-end"
+
+
+def _validate_visual_contexts(source):
+ """Ensure every registry context is reachable from the visual explorer."""
+ values = re.findall(r'data-contexts="([^"]+)"', source)
+ patterns = {
+ pattern.strip()
+ for value in values
+ for pattern in value.split(",")
+ if pattern.strip()
+ }
+ expected = {*_alias_registry, "rc (dotless)"}
+
+ def covered(context):
+ return context in patterns or any(
+ pattern.endswith(".*") and context.startswith(pattern[:-1])
+ for pattern in patterns
+ )
+
+ missing = sorted(context for context in expected if not covered(context))
+ if missing:
+ raise RuntimeError(
+ "Visual alias explorer is missing contexts: " + ", ".join(missing)
+ )
+
+
+def _validate_api_targets():
+ """Ensure every alias row can link to a canonical public API entry."""
+ source = SCRIPT.read_text()
+ _, marker, rest = source.partition("const apiTargets = {")
+ if not marker:
+ raise RuntimeError(f"Missing apiTargets mapping in {SCRIPT}.")
+ mapping, marker, _ = rest.partition("\n };")
+ if not marker:
+ raise RuntimeError(f"Could not parse apiTargets mapping in {SCRIPT}.")
+ matches = re.findall(
+ r'^\s*(?:"([^"]+)"|([A-Za-z][\w]*)):\s*"', mapping, re.MULTILINE
+ )
+ contexts = {quoted or bare for quoted, bare in matches}
+ expected = {*_alias_registry, "rc (dotless)"}
+ missing = sorted(expected - contexts)
+ if missing:
+ raise RuntimeError(
+ "Alias API link mapping is missing contexts: " + ", ".join(missing)
+ )
+
+
+def _render(source):
+ before, marker, rest = source.partition(START)
+ if not marker:
+ raise RuntimeError(f"Missing {START!r} marker in {OUTPUT}.")
+ _, marker, after = rest.partition(END)
+ if not marker:
+ raise RuntimeError(f"Missing {END!r} marker in {OUTPUT}.")
+ table = _format_alias_reference()
+ return f"{before}{START}\n\n{table}\n\n{END}{after}"
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--check", action="store_true")
+ args = parser.parse_args()
+ source = OUTPUT.read_text()
+ _validate_visual_contexts(source)
+ _validate_api_targets()
+ rendered = _render(source)
+ if args.check:
+ if source != rendered:
+ raise SystemExit("Alias reference is stale; regenerate it.")
+ else:
+ OUTPUT.write_text(rendered)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/tools/serve_alias_docs.py b/tools/serve_alias_docs.py
new file mode 100644
index 000000000..cab95c4fc
--- /dev/null
+++ b/tools/serve_alias_docs.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+"""Serve the local docs preview and retain draggable alias-map positions."""
+
+from __future__ import annotations
+
+import argparse
+import json
+from http.server import SimpleHTTPRequestHandler, ThreadingHTTPServer
+from pathlib import Path
+from urllib.parse import urlsplit
+
+
+LAYOUT_KEYS = {"layout", "titles", "axes", "plot", "legend", "colorbar", "style"}
+
+
+def _valid_layout(payload):
+ if not isinstance(payload, dict) or payload.get("version") != 1:
+ return False
+ nodes = payload.get("nodes")
+ if not isinstance(nodes, dict) or set(nodes) != LAYOUT_KEYS:
+ return False
+ for value in nodes.values():
+ if not isinstance(value, list) or len(value) != 2:
+ return False
+ if not all(isinstance(item, (int, float)) for item in value):
+ return False
+ if not all(-250 <= item <= 1_000 for item in value):
+ return False
+ return True
+
+
+def make_handler(directory, draft_path):
+ class AliasDocsHandler(SimpleHTTPRequestHandler):
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, directory=str(directory), **kwargs)
+
+ def do_GET(self):
+ if urlsplit(self.path).path != "/__alias_layout":
+ return super().do_GET()
+ payload = draft_path.read_bytes() if draft_path.exists() else b'{"version":1,"nodes":{}}'
+ self.send_response(200)
+ self.send_header("Content-Type", "application/json")
+ self.send_header("Content-Length", str(len(payload)))
+ self.send_header("Cache-Control", "no-store")
+ self.end_headers()
+ self.wfile.write(payload)
+
+ def do_POST(self):
+ if urlsplit(self.path).path != "/__alias_layout":
+ self.send_error(404)
+ return
+ try:
+ length = int(self.headers.get("Content-Length", "0"))
+ if not 0 < length <= 16_384:
+ raise ValueError("invalid payload size")
+ payload = json.loads(self.rfile.read(length))
+ if not _valid_layout(payload):
+ raise ValueError("invalid alias layout")
+ temporary = draft_path.with_suffix(".tmp")
+ temporary.write_text(json.dumps(payload, indent=2) + "\n")
+ temporary.replace(draft_path)
+ except (OSError, ValueError, json.JSONDecodeError) as error:
+ self.send_error(400, str(error))
+ return
+ self.send_response(204)
+ self.send_header("Cache-Control", "no-store")
+ self.end_headers()
+
+ return AliasDocsHandler
+
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("--directory", type=Path, required=True)
+ parser.add_argument("--draft", type=Path, required=True)
+ parser.add_argument("--port", type=int, default=8765)
+ args = parser.parse_args()
+ handler = make_handler(args.directory.resolve(), args.draft.resolve())
+ server = ThreadingHTTPServer(("127.0.0.1", args.port), handler)
+ print(f"Serving alias docs at http://127.0.0.1:{args.port}/aliases.html")
+ server.serve_forever()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ultraplot/_subplots.py b/ultraplot/_subplots.py
index be4f3e230..7b04134b2 100644
--- a/ultraplot/_subplots.py
+++ b/ultraplot/_subplots.py
@@ -13,7 +13,14 @@
from . import axes as paxes
from . import constructor
from . import gridspec as pgridspec
-from .internals import _not_none, _pop_params, warnings
+from .internals import (
+ _alias_kwargs,
+ _canonicalize_kwargs,
+ _figure_format_alias_scopes,
+ _not_none,
+ _pop_params,
+ warnings,
+)
if TYPE_CHECKING:
from .figure import Figure
@@ -69,11 +76,10 @@ def parse_backend(backend=None, basemap=None):
constructor._warn_basemap_deprecated()
return backend
+ @_alias_kwargs("subplot")
def parse_proj(
self,
- proj=None,
projection=None,
- proj_kw=None,
projection_kw=None,
backend=None,
basemap=None,
@@ -83,8 +89,8 @@ def parse_proj(
Translate user-input projection into a registered matplotlib axes class.
"""
# Parse arguments
- proj = _not_none(proj=proj, projection=projection, default="cartesian")
- proj_kw = _not_none(proj_kw=proj_kw, projection_kw=projection_kw, default={})
+ proj = _not_none(projection, "cartesian")
+ proj_kw = projection_kw or {}
backend = self.parse_backend(backend, basemap)
if isinstance(proj, str):
proj = proj.lower()
@@ -270,15 +276,14 @@ def add_subplot(self, *args, **kwargs):
self.subplot_dict[ax.number] = ax
return ax
+ @_alias_kwargs("subplot")
def add_subplots(
self,
array=None,
nrows=1,
ncols=1,
order="C",
- proj=None,
projection=None,
- proj_kw=None,
projection_kw=None,
backend=None,
basemap=None,
@@ -288,6 +293,8 @@ def add_subplots(
The driver function for adding multiple subplots.
"""
fig = self.figure
+ kwargs = _canonicalize_kwargs(_figure_format_alias_scopes, kwargs)
+ kwargs = _canonicalize_kwargs("gridspec", kwargs)
# Helper to normalize per-axes arguments into {num: value} dicts.
# Accepts 'string', {1: 'string1', (2, 3): 'string2'}, or lists.
@@ -346,14 +353,18 @@ def _axes_dict(naxs, input, kw=False, default=None):
naxs = len(nums)
if any(num < 0 or not isinstance(num, Integral) for num in nums.flat):
raise ValueError(f"Expected array of positive integers. Got {array}.")
- proj = _not_none(projection=projection, proj=proj)
+ proj = projection
proj = _axes_dict(naxs, proj, kw=False, default="cartesian")
- proj_kw = _not_none(projection_kw=projection_kw, proj_kw=proj_kw) or {}
+ proj_kw = projection_kw or {}
proj_kw = _axes_dict(naxs, proj_kw, kw=True)
backend = self.parse_backend(backend, basemap)
backend = _axes_dict(naxs, backend, kw=False)
axes_kw = {
- num: {"proj": proj[num], "proj_kw": proj_kw[num], "backend": backend[num]}
+ num: {
+ "projection": proj[num],
+ "projection_kw": proj_kw[num],
+ "backend": backend[num],
+ }
for num in proj
}
for key in ("gridspec_kw", "subplot_kw"):
diff --git a/ultraplot/axes/base.py b/ultraplot/axes/base.py
index c08c93d41..65d24414f 100644
--- a/ultraplot/axes/base.py
+++ b/ultraplot/axes/base.py
@@ -55,6 +55,9 @@
)
from ..config import rc
from ..internals import (
+ _alias_kwargs,
+ _canonicalize_kwargs,
+ _format_alias_scopes,
_kwargs_to_args,
_not_none,
_pop_kwargs,
@@ -86,7 +89,7 @@
# Projection docstring
_proj_docstring = """
-proj, projection :
+projection :
str, `cartopy.crs.Projection`, or `~mpl_toolkits.basemap.Basemap`, optional
The map projection specification(s). If ``'cart'`` or ``'cartesian'``
(the default), a `~ultraplot.axes.CartesianAxes` is created. If ``'polar'``,
@@ -97,7 +100,7 @@
instance, or a projection name listed in :ref:`this table `).
"""
_proj_kw_docstring = """
-proj_kw, projection_kw : dict-like, optional
+projection_kw : dict-like, optional
Keyword arguments passed to `~mpl_toolkits.basemap.Basemap` or
cartopy `~cartopy.crs.Projection` classes on instantiation.
"""
@@ -365,9 +368,7 @@
abctitlepad : float, default: :rc:`abc.titlepad`
The horizontal padding between a-b-c labels and titles in the same location.
%(units.pt)s
-ltitle, ctitle, rtitle, ultitle, uctitle, urtitle, lltitle, lctitle, lrtitle : str or sequence, optional
- Shorthands for the below keywords.
- lefttitle, centertitle, righttitle, upperlefttitle, uppercentertitle, upperrighttitle : str or sequence, optional
+lefttitle, centertitle, righttitle, upperlefttitle, uppercentertitle, upperrighttitle : str or sequence, optional
lowerlefttitle, lowercentertitle, lowerrighttitle : str or sequence, optional
Additional titles in specific positions (see `title` for details). This works as
an alternative to the ``ax.format(title='Title', titleloc=loc)`` workflow and
@@ -378,9 +379,6 @@
shorthands. Their defaults values are the ``'axes'`` properties.
"""
_figure_format_docstring = """
-rowlabels, collabels, llabels, tlabels, rlabels, blabels
- Aliases for `leftlabels` and `toplabels`, and for `leftlabels`,
- `toplabels`, `rightlabels`, and `bottomlabels`, respectively.
leftlabels, toplabels, rightlabels, bottomlabels : sequence of str, optional
Labels for the subplots lying along the left, top, right, and
bottom edges of the figure. The length of each list must match
@@ -396,8 +394,6 @@
%(units.pt)s
leftlabels_kw, toplabels_kw, rightlabels_kw, bottomlabels_kw : dict-like, optional
Additional settings used to update the labels with ``text.update()``.
-figtitle
- Alias for `suptitle`.
suptitle : str, optional
The figure "super" title, centered between the left edge of the leftmost
subplot and the right edge of the rightmost subplot.
@@ -485,15 +481,14 @@
vmin, vmax : float, optional
Ignored if `mappable` is a `~matplotlib.cm.ScalarMappable`. These are the minimum
and maximum colorbar values. Passed to `~ultraplot.constructor.Norm`.
-label, title : str, optional
- The colorbar label. The `title` keyword is also accepted for
- consistency with `~matplotlib.axes.Axes.legend`.
+label : str, optional
+ The colorbar label.
reverse : bool, optional
Whether to reverse the direction of the colorbar. This is done automatically
when descending levels are used with `~ultraplot.colors.DiscreteNorm`.
rotation : float, default: 0
The tick label rotation.
-grid, edges, drawedges : bool, default: :rc:`colorbar.grid`
+drawedges : bool, default: :rc:`colorbar.grid`
Whether to draw "grid" dividers between each distinct color.
extend : {'neither', 'both', 'min', 'max'}, optional
Direction for drawing colorbar "extensions" (i.e. color keys for out-of-bounds
@@ -509,36 +504,35 @@
extendrect : bool, default: False
Whether to draw colorbar "extensions" as rectangles. If ``False`` then
the extensions are drawn as triangles.
-locator, ticks : locator-spec, optional
+ticks : locator-spec, optional
Used to determine the colorbar tick positions. Passed to the
`~ultraplot.constructor.Locator` constructor function. By default
`~matplotlib.ticker.AutoLocator` is used for continuous color levels
and `~ultraplot.ticker.DiscreteLocator` is used for discrete color levels.
locator_kw : dict-like, optional
Keyword arguments passed to `matplotlib.ticker.Locator` class.
-minorlocator, minorticks
- As with `locator`, `ticks` but for the minor ticks. By default
+minorticks
+ As with `ticks` but for the minor ticks. By default
`~matplotlib.ticker.AutoMinorLocator` is used for continuous color levels
and `~ultraplot.ticker.DiscreteLocator` is used for discrete color levels.
minorlocator_kw
As with `locator_kw`, but for the minor ticks.
-format, formatter, ticklabels : formatter-spec, optional
+format : formatter-spec, optional
The tick label format. Passed to the `~ultraplot.constructor.Formatter`
constructor function.
formatter_kw : dict-like, optional
Keyword arguments passed to `matplotlib.ticker.Formatter` class.
-frame, frameon : bool, optional
+frameon : bool, optional
For inset colorbars, indicates whether to draw a background "frame",
just like `~matplotlib.axes.Axes.legend`. Defaults to
- :rc:`colorbar.frameon` for inset colorbars. For outer colorbars, this is a
- backwards-compatible alias for `outline`; when omitted, outer colorbars
- still default to :rc:`colorbar.outline`.
+ :rc:`colorbar.frameon` for inset colorbars. For outer colorbars it controls
+ the outline when `outline` is omitted.
tickminor : bool, optional
Whether to add minor ticks using `~matplotlib.colorbar.ColorbarBase.minorticks_on`.
tickloc, ticklocation : {'bottom', 'top', 'left', 'right'}, optional
Where to draw tick marks on the colorbar. Default is toward the outside
of the subplot for outer colorbars and ``'bottom'`` for inset colorbars.
-tickdir, tickdirection : {'out', 'in', 'inout'}, default: :rc:`tick.dir`
+tickdirection : {'out', 'in', 'inout'}, default: :rc:`tick.dir`
Direction of major and minor colorbar ticks.
ticklen : unit-spec, default: :rc:`tick.len`
Major tick lengths for the colorbar ticks.
@@ -551,7 +545,7 @@
Relative scaling of `tickwidth` used to determine minor tick widths.
ticklabelcolor, ticklabelsize, ticklabelweight: default: :rc:`tick.labelcolor`, :rc:`tick.labelsize`, :rc:`tick.labelweight`.
The font color, size, and weight for colorbar tick labels
-labelloc, labellocation : {'bottom', 'top', 'left', 'right'}
+labellocation : {'bottom', 'top', 'left', 'right'}
The colorbar label location. Inherits from `tickloc` by default. Default is toward
the outside of the subplot for outer colorbars and ``'bottom'`` for inset colorbars.
labelcolor, labelsize, labelweight: default: :rc:`label.color`, :rc:`label.size`, and :rc:`label.weight`.
@@ -559,7 +553,7 @@
a, alpha, framealpha, fc, facecolor, framecolor, ec, edgecolor, ew, edgewidth : default: :rc:`colorbar.framealpha`, :rc:`colorbar.framecolor`
For inset colorbars only. Controls the transparency and color of
the background frame.
-lw, linewidth, c, color : optional
+linewidth, color : optional
Controls the line width and edge color for both the colorbar
outline and the level dividers.
%(axes.edgefix)s
@@ -618,12 +612,11 @@
-`__.
"""
_legend_kwargs_docstring = """
-frame, frameon : bool, optional
+frameon : bool, optional
Toggles the legend frame. For centered-row legends, a frame
independent from matplotlib's built-in legend frame is created.
-ncol, ncols : int, optional
- The number of columns. `ncols` is an alias, added
- for consistency with `~matplotlib.pyplot.subplots`.
+ncols : int, optional
+ The number of columns.
order : {'C', 'F'}, optional
Whether legend handles are drawn in row-major (``'C'``) or column-major
(``'F'``) order. Analagous to `numpy.array` ordering. The matplotlib
@@ -973,6 +966,7 @@ def __init__(self, *args, **kwargs):
autoshare = _not_none(autoshare, True)
# Remove format-related args and initialize
+ kwargs = _canonicalize_kwargs(_format_alias_scopes, kwargs)
rc_kw, rc_mode = _pop_rc(kwargs)
kw_format = _pop_props(kwargs, "patch") # background properties
if "zorder" in kw_format: # special case: refers to the entire axes
@@ -1066,12 +1060,12 @@ def __init__(self, *args, **kwargs):
# features which is necessary on first run. Default otherwise is mode '2'
self.format(rc_kw=rc_kw, rc_mode=1, skip_figure=True, **kw_format)
+ @_alias_kwargs("inset")
def _add_inset_axes(
self,
bounds,
transform=None,
*,
- proj=None,
projection=None,
zoom=None,
zoom_kw=None,
@@ -1090,7 +1084,7 @@ def _add_inset_axes(
zorder = _not_none(zorder, 4)
# Parse projection and inherit from the current axes by default
- proj = _not_none(proj=proj, projection=projection)
+ proj = projection
if proj is None:
if self._name in ("cartopy", "basemap"):
proj = copy.copy(self.projection)
@@ -1239,6 +1233,7 @@ def _add_guide_panel(
return ax
@warnings._rename_kwargs("0.10", rasterize="rasterized")
+ @_alias_kwargs("colorbar")
def _add_colorbar(
self,
mappable,
@@ -1249,55 +1244,42 @@ def _add_colorbar(
space: Optional[Union[float, str]] = None,
pad: Optional[Union[float, str]] = None,
width: Optional[Union[float, str]] = None,
- length: Optional[Union[float, str]] = None,
span: Optional[Union[int, Tuple[int, int]]] = None,
row: Optional[int] = None,
col: Optional[int] = None,
rows: Optional[Union[int, Tuple[int, int]]] = None,
cols: Optional[Union[int, Tuple[int, int]]] = None,
- shrink: Optional[Union[float, str]] = None,
+ length: Optional[Union[float, str]] = None,
label=None,
- title=None,
reverse=False,
rotation=None,
- grid=None,
- edges=None,
drawedges=None,
extend=None,
extendsize=None,
extendfrac=None,
ticks=None,
- locator=None,
locator_kw=None,
format=None,
- formatter=None,
- ticklabels=None,
formatter_kw=None,
minorticks=None,
- minorlocator=None,
minorlocator_kw=None,
tickminor=None,
ticklen=None,
ticklenratio=None,
- tickdir=None,
tickdirection=None,
tickwidth=None,
tickwidthratio=None,
ticklabelsize=None,
ticklabelweight=None,
ticklabelcolor=None,
- labelloc=None,
labellocation=None,
labelsize=None,
labelweight=None,
labelcolor=None,
- c=None,
color=None,
- lw=None,
linewidth=None,
edgefix=None,
rasterized=None,
- frame: Optional[bool] = None,
frameon: Optional[bool] = None,
outline: Union[bool, None] = None,
labelrotation: Union[str, float] = None,
@@ -1312,55 +1294,42 @@ def _add_colorbar(
space=space,
pad=pad,
width=width,
- length=length,
span=span,
row=row,
col=col,
rows=rows,
cols=cols,
- shrink=shrink,
+ length=length,
label=label,
- title=title,
reverse=reverse,
rotation=rotation,
- grid=grid,
- edges=edges,
drawedges=drawedges,
extend=extend,
extendsize=extendsize,
extendfrac=extendfrac,
ticks=ticks,
- locator=locator,
locator_kw=locator_kw,
format=format,
- formatter=formatter,
- ticklabels=ticklabels,
formatter_kw=formatter_kw,
minorticks=minorticks,
- minorlocator=minorlocator,
minorlocator_kw=minorlocator_kw,
tickminor=tickminor,
ticklen=ticklen,
ticklenratio=ticklenratio,
- tickdir=tickdir,
tickdirection=tickdirection,
tickwidth=tickwidth,
tickwidthratio=tickwidthratio,
ticklabelsize=ticklabelsize,
ticklabelweight=ticklabelweight,
ticklabelcolor=ticklabelcolor,
- labelloc=labelloc,
labellocation=labellocation,
labelsize=labelsize,
labelweight=labelweight,
labelcolor=labelcolor,
- c=c,
color=color,
- lw=lw,
linewidth=linewidth,
edgefix=edgefix,
rasterized=rasterized,
- frame=frame,
frameon=frameon,
outline=outline,
labelrotation=labelrotation,
@@ -1368,6 +1337,7 @@ def _add_colorbar(
**kwargs,
)
+ @_alias_kwargs("legend")
def _add_legend(
self,
handles=None,
@@ -1378,9 +1348,7 @@ def _add_legend(
width=None,
pad=None,
space=None,
- frame=None,
frameon=None,
- ncol=None,
ncols=None,
alphabetize=False,
center=None,
@@ -1410,9 +1378,7 @@ def _add_legend(
width=width,
pad=pad,
space=space,
- frame=frame,
frameon=frameon,
- ncol=ncol,
ncols=ncols,
alphabetize=alphabetize,
center=center,
@@ -2045,8 +2011,6 @@ def _parse_colorbar_inset(
bbox_to_anchor=None,
width=None,
length=None,
- shrink=None,
- frame=None,
frameon=None,
label=None,
labelsize=None,
@@ -2062,12 +2026,8 @@ def _parse_colorbar_inset(
Return the axes and adjusted keyword args for an inset colorbar.
"""
# Basic colorbar properties
- frame_enabled = _not_none(
- frame=frame, frameon=frameon, default=rc["colorbar.frameon"]
- )
- length = _not_none(
- length=length, shrink=shrink, default=rc["colorbar.insetlength"]
- ) # noqa: E501
+ frame_enabled = _not_none(frameon, rc["colorbar.frameon"])
+ length = _not_none(length, rc["colorbar.insetlength"])
width = _not_none(width, rc["colorbar.insetwidth"])
pad = _not_none(pad, rc["colorbar.insetpad"])
length_raw = length
@@ -2167,7 +2127,6 @@ def _parse_colorbar_inset_side(
align=None,
width=None,
length=None,
- shrink=None,
space=None,
pad=None,
tickloc=None,
@@ -2178,7 +2137,7 @@ def _parse_colorbar_inset_side(
"""
Return the axes and adjusted keyword args for a side colorbar on an inset axes.
"""
- length = _not_none(length=length, shrink=shrink, default=rc["colorbar.length"])
+ length = _not_none(length, rc["colorbar.length"])
width = _not_none(width, rc["colorbar.width"])
pad = _not_none(space, pad, rc["subplots.panelpad"])
side = _translate_loc(loc, "panel")
@@ -3291,29 +3250,22 @@ def _update_share_labels(self, axes=None, target="x"):
ax.yaxis.label = label
@docstring._snippet_manager
+ @_alias_kwargs("axes.format")
+ @_alias_kwargs("figure.format")
def format(
self,
*,
title=None,
title_kw=None,
abc_kw=None,
- ltitle=None,
lefttitle=None,
- ctitle=None,
centertitle=None,
- rtitle=None,
righttitle=None,
- ultitle=None,
upperlefttitle=None,
- uctitle=None,
uppercentertitle=None,
- urtitle=None,
upperrighttitle=None,
- lltitle=None,
lowerlefttitle=None,
- lctitle=None,
lowercentertitle=None,
- lrtitle=None,
lowerrighttitle=None,
share_xlabels=None,
share_ylabels=None,
@@ -3375,51 +3327,15 @@ def format(
self._abc_pad = units(pad)
self._update_abc(**abc_kw)
self._update_title(None, title, **title_kw)
- self._update_title(
- "left",
- _not_none(ltitle=ltitle, lefttitle=lefttitle),
- **title_kw,
- )
- self._update_title(
- "center",
- _not_none(ctitle=ctitle, centertitle=centertitle),
- **title_kw,
- )
- self._update_title(
- "right",
- _not_none(rtitle=rtitle, righttitle=righttitle),
- **title_kw,
- )
- self._update_title(
- "upper left",
- _not_none(ultitle=ultitle, upperlefttitle=upperlefttitle),
- **title_kw,
- )
- self._update_title(
- "upper center",
- _not_none(uctitle=uctitle, uppercentertitle=uppercentertitle),
- **title_kw,
- )
- self._update_title(
- "upper right",
- _not_none(urtitle=urtitle, upperrighttitle=upperrighttitle),
- **title_kw,
- )
- self._update_title(
- "lower left",
- _not_none(lltitle=lltitle, lowerlefttitle=lowerlefttitle),
- **title_kw,
- )
- self._update_title(
- "lower center",
- _not_none(lctitle=lctitle, lowercentertitle=lowercentertitle),
- **title_kw,
- )
- self._update_title(
- "lower right",
- _not_none(lrtitle=lrtitle, lowerrighttitle=lowerrighttitle),
- **title_kw,
- )
+ self._update_title("left", lefttitle, **title_kw)
+ self._update_title("center", centertitle, **title_kw)
+ self._update_title("right", righttitle, **title_kw)
+ self._update_title("upper left", upperlefttitle, **title_kw)
+ self._update_title("upper center", uppercentertitle, **title_kw)
+ self._update_title("upper right", upperrighttitle, **title_kw)
+ self._update_title("lower left", lowerlefttitle, **title_kw)
+ self._update_title("lower center", lowercentertitle, **title_kw)
+ self._update_title("lower right", lowerrighttitle, **title_kw)
# Update the axes style
# NOTE: This will also raise an error if unknown args are encountered
@@ -3674,14 +3590,15 @@ def panel_axes(self, side=None, **kwargs):
@docstring._obfuscate_params
@docstring._snippet_manager
- def colorbar(self, mappable, values=None, loc=None, location=None, **kwargs):
+ @_alias_kwargs("colorbar")
+ def colorbar(self, mappable, values=None, loc=None, **kwargs):
"""
Add an inset colorbar or an outer colorbar along the edge of the axes.
Parameters
----------
%(axes.colorbar_args)s
- loc, location : int or str, default: :rc:`colorbar.loc`
+ loc : int or str, default: :rc:`colorbar.loc`
The colorbar location. Valid location keys are shown in the below table.
.. _colorbar_table:
@@ -3701,12 +3618,10 @@ def colorbar(self, mappable, values=None, loc=None, location=None, **kwargs):
"filled" ``'fill'``
================== =======================================
- shrink
- Alias for `length`. This is included for consistency with
- `matplotlib.figure.Figure.colorbar`.
length : float or unit-spec, default: :rc:`colorbar.length` or :rc:`colorbar.insetlength`
- The colorbar length. For outer colorbars, units are relative to the axes
- width or height (default is :rcraw:`colorbar.length`). For inset
+ The colorbar length (also accepted as ``shrink``). For outer colorbars,
+ units are relative to the axes width or height (default is
+ :rcraw:`colorbar.length`). For inset
colorbars, floats interpreted as em-widths and strings interpreted
by `~ultraplot.utils.units` (default is :rcraw:`colorbar.insetlength`).
width : unit-spec, default: :rc:`colorbar.width` or :rc:`colorbar.insetwidth`
@@ -3734,7 +3649,6 @@ def colorbar(self, mappable, values=None, loc=None, location=None, **kwargs):
# infer align setting from keywords stored on object.
orientation = kwargs.get("orientation", None)
kwargs = guides._flush_guide_kw(mappable, "colorbar", kwargs)
- loc = _not_none(loc=loc, location=location)
if orientation is not None: # possibly infer loc from orientation
if orientation not in ("vertical", "horizontal"):
raise ValueError(
@@ -3756,12 +3670,12 @@ def colorbar(self, mappable, values=None, loc=None, location=None, **kwargs):
@docstring._concatenate_inherited # also obfuscates params
@docstring._snippet_manager
+ @_alias_kwargs("legend")
def legend(
self,
handles=None,
labels=None,
loc=None,
- location=None,
span: Optional[Union[int, Tuple[int, int]]] = None,
row: Optional[int] = None,
col: Optional[int] = None,
@@ -3775,7 +3689,7 @@ def legend(
Parameters
----------
%(axes.legend_args)s
- loc, location : int or str, default: :rc:`legend.loc`
+ loc : int or str, default: :rc:`legend.loc`
The legend location. Valid location keys are shown in the below table.
.. _legend_table:
@@ -3819,7 +3733,6 @@ def legend(
# Translate location and possibly infer from orientation. Also optionally
# infer align setting from keywords stored on object.
kwargs = guides._flush_guide_kw(handles, "legend", kwargs)
- loc = _not_none(loc=loc, location=location)
loc = _translate_loc(loc, "legend", default=rc["legend.loc"])
align = kwargs.pop("align", None)
align = _translate_loc(align, "align", default="center")
diff --git a/ultraplot/axes/cartesian.py b/ultraplot/axes/cartesian.py
index 064c1eed8..795231175 100644
--- a/ultraplot/axes/cartesian.py
+++ b/ultraplot/axes/cartesian.py
@@ -20,6 +20,9 @@
from .. import ticker as pticker
from ..config import rc
from ..internals import (
+ _alias_kwargs,
+ _canonicalize_kwargs,
+ _format_alias_scopes,
_not_none,
_pop_params,
_pop_rc,
@@ -104,8 +107,6 @@
example, ``xwraprange=(0, 3)`` causes the values 0 through 9 to be formatted as
0, 1, 2, 0, 1, 2, 0, 1, 2, 0. See `~ultraplot.ticker.AutoFormatter` for details. This
can be combined with `xtickrange` and `ytickrange` to make "stacked" line plots.
-xloc, yloc : optional
- Shorthands for `xspineloc`, `yspineloc`.
xspineloc, yspineloc : {'b', 't', 'l', 'r', 'bottom', 'top', 'left', 'right', \
'both', 'neither', 'none', 'zero', 'center'} or 2-tuple, optional
The x and y spine locations. Applied with `~matplotlib.spines.Spine.set_position`.
@@ -144,8 +145,6 @@
xtickminor, ytickminor, tickminor : bool, default: :rc:`tick.minor`
Whether to draw minor ticks on the x and y axes.
Use the keyword `tickminor` to toggle both.
-xticks, yticks : optional
- Aliases for `xlocator`, `ylocator`.
xlocator, ylocator : locator-spec, optional
Used to determine the x and y axis tick mark positions. Passed
to the `~ultraplot.constructor.Locator` constructor. Can be float,
@@ -153,14 +152,10 @@
Use ``[]``, ``'null'``, or ``'none'`` for no ticks.
xlocator_kw, ylocator_kw : dict-like, optional
Keyword arguments passed to the `matplotlib.ticker.Locator` class.
-xminorticks, yminorticks : optional
- Aliases for `xminorlocator`, `yminorlocator`.
xminorlocator, yminorlocator : optional
As for `xlocator`, `ylocator`, but for the minor ticks.
xminorlocator_kw, yminorlocator_kw
As for `xlocator_kw`, `ylocator_kw`, but for the minor locator.
-xticklabels, yticklabels : optional
- Aliases for `xformatter`, `yformatter`.
xformatter, yformatter : formatter-spec, optional
Used to determine the x and y axis tick label string format.
Passed to the `~ultraplot.constructor.Formatter` constructor.
@@ -683,16 +678,27 @@ def _add_alt(self, sx, **kwargs):
# To restore matplotlib behavior, which draws "child" artists on top simply
# because the axes was created after the "parent" one, use the inset_axes
# zorder of 4 and make the background transparent.
+ kwargs = _canonicalize_kwargs(_format_alias_scopes, kwargs)
sy = "y" if sx == "x" else "x"
sig = self._format_signatures[CartesianAxes]
keys = tuple(key[1:] for key in sig.parameters if key[0] == sx)
- kwargs = {
- (sx + key if key in keys else key): val for key, val in kwargs.items()
- } # noqa: E501
- if f"{sy}spineloc" not in kwargs: # acccount for aliases
- kwargs.setdefault(f"{sy}loc", "neither")
- if f"{sx}spineloc" not in kwargs: # account for aliases
- kwargs.setdefault(f"{sx}loc", "top" if sx == "x" else "right")
+ normalized = {}
+ for key, value in kwargs.items():
+ if key == "loc":
+ key = f"{sx}spineloc"
+ elif key in keys:
+ key = sx + key
+ else:
+ candidate = _canonicalize_kwargs(
+ _format_alias_scopes, {sx + key: value}
+ )
+ candidate_key = next(iter(candidate))
+ if candidate_key in sig.parameters:
+ key = candidate_key
+ normalized[key] = value
+ kwargs = normalized
+ kwargs.setdefault(f"{sy}spineloc", "neither")
+ kwargs.setdefault(f"{sx}spineloc", "top" if sx == "x" else "right")
kwargs.setdefault(f"autoscale{sy}_on", getattr(self, f"get_autoscale{sy}_on")())
kwargs.setdefault(f"share{sy}", self)
@@ -712,7 +718,9 @@ def _add_alt(self, sx, **kwargs):
self._twinned_axes.join(self, ax)
# Format parent and child axes
- self.format(**{f"{sx}loc": OPPOSITE_SIDE.get(kwargs[f"{sx}loc"], None)})
+ self.format(
+ **{f"{sx}spineloc": OPPOSITE_SIDE.get(kwargs[f"{sx}spineloc"], None)}
+ )
setattr(ax, f"_alt{sx}_parent", self)
getattr(ax, f"{sy}axis").set_visible(False)
getattr(ax, "patch").set_visible(False)
@@ -1443,10 +1451,9 @@ def get(name):
rc.find(f"{axis}tick.direction", context=True),
)
- locator = _not_none(get("locator"), p.get(f"{axis}ticks"))
- minorlocator = _not_none(get("minorlocator"), p.get(f"{axis}minorticks"))
-
- formatter = _not_none(get("formatter"), p.get(f"{axis}ticklabels"))
+ locator = get("locator")
+ minorlocator = get("minorlocator")
+ formatter = get("formatter")
# Tick minor default logic
tickminor = get("tickminor")
@@ -1582,12 +1589,11 @@ def get(name):
return _AxisFormatConfig(**config_kwargs)
@docstring._snippet_manager
+ @_alias_kwargs("cartesian.format")
def format(
self,
*,
aspect=None,
- xloc=None,
- yloc=None,
xspineloc=None,
yspineloc=None,
xoffsetloc=None,
@@ -1612,14 +1618,8 @@ def format(
yrotation=None,
xformatter=None,
yformatter=None,
- xticklabels=None,
- yticklabels=None,
- xticks=None,
- yticks=None,
xlocator=None,
ylocator=None,
- xminorticks=None,
- yminorticks=None,
xminorlocator=None,
yminorlocator=None,
xcolor=None,
diff --git a/ultraplot/axes/geo.py b/ultraplot/axes/geo.py
index c6bd8eb5c..454f7b1b2 100644
--- a/ultraplot/axes/geo.py
+++ b/ultraplot/axes/geo.py
@@ -36,6 +36,7 @@
from .. import ticker as pticker
from ..config import rc
from ..internals import (
+ _alias_kwargs,
_not_none,
_pop_params,
_pop_props,
@@ -557,7 +558,6 @@ def _make_hawkeye_indicator_patch(
The number of interpolation steps used to draw gridlines.
lonlocator, latlocator : locator-spec, optional
Used to determine the longitude and latitude gridline locations.
- Aliases: ``lonlines`` and ``latlines``, respectively.
Passed to the `~ultraplot.constructor.Locator` constructor. Can be
string, float, list of float, or `matplotlib.ticker.Locator` instance.
@@ -569,13 +569,10 @@ def _make_hawkeye_indicator_patch(
at nice degree-minute-second intervals when the map extent is very small.
lonlocator_kw, latlocator_kw : dict-like, optional
Keyword arguments passed to the `matplotlib.ticker.Locator` class.
- Aliases: ``lonlines_kw`` and ``latlines_kw``, respectively.
lonminorlocator, latminorlocator : optional
As with `lonlocator` and `latlocator` but for the "minor" gridlines.
- Aliases: ``lonminorlines`` and ``latminorlines``, respectively.
lonminorlocator_kw, latminorlocator_kw : optional
As with `lonlocator_kw`, and `latlocator_kw` but for the "minor" gridlines.
- Aliases: ``lonminorlines_kw`` and ``latminorlines_kw``, respectively.
lonlabels, latlabels, labels : str, bool, or sequence, :rc:`grid.labels`
Whether to add non-inline longitude and latitude gridline labels, and on
which sides of the map. Use the keyword `labels` to set both at once. The
@@ -2716,33 +2713,19 @@ def _format_update_major_locators(
self,
*,
lonlocator: Any,
- lonlines: Any,
latlocator: Any,
- latlines: Any,
lonlocator_kw: MutableMapping | None,
- lonlines_kw: MutableMapping | None,
latlocator_kw: MutableMapping | None,
- latlines_kw: MutableMapping | None,
) -> None:
"""
Update major longitude/latitude locators.
"""
- lonlocator = _not_none(lonlocator=lonlocator, lonlines=lonlines)
- latlocator = _not_none(latlocator=latlocator, latlines=latlines)
if lonlocator is not None:
- lonlocator_kw = _not_none(
- lonlocator_kw=lonlocator_kw,
- lonlines_kw=lonlines_kw,
- default={},
- )
+ lonlocator_kw = lonlocator_kw or {}
locator = constructor.Locator(lonlocator, **lonlocator_kw)
self._lonaxis.set_major_locator(locator)
if latlocator is not None:
- latlocator_kw = _not_none(
- latlocator_kw=latlocator_kw,
- latlines_kw=latlines_kw,
- default={},
- )
+ latlocator_kw = latlocator_kw or {}
locator = constructor.Locator(latlocator, **latlocator_kw)
self._lataxis.set_major_locator(locator)
@@ -2750,37 +2733,19 @@ def _format_update_minor_locators(
self,
*,
lonminorlocator: Any,
- lonminorlines: Any,
latminorlocator: Any,
- latminorlines: Any,
lonminorlocator_kw: MutableMapping | None,
- lonminorlines_kw: MutableMapping | None,
latminorlocator_kw: MutableMapping | None,
- latminorlines_kw: MutableMapping | None,
) -> None:
"""
Update minor longitude/latitude locators.
"""
- lonminorlocator = _not_none(
- lonminorlocator=lonminorlocator, lonminorlines=lonminorlines
- )
- latminorlocator = _not_none(
- latminorlocator=latminorlocator, latminorlines=latminorlines
- )
if lonminorlocator is not None:
- lonminorlocator_kw = _not_none(
- lonminorlocator_kw=lonminorlocator_kw,
- lonminorlines_kw=lonminorlines_kw,
- default={},
- )
+ lonminorlocator_kw = lonminorlocator_kw or {}
locator = constructor.Locator(lonminorlocator, **lonminorlocator_kw)
self._lonaxis.set_minor_locator(locator)
if latminorlocator is not None:
- latminorlocator_kw = _not_none(
- latminorlocator_kw=latminorlocator_kw,
- latminorlines_kw=latminorlines_kw,
- default={},
- )
+ latminorlocator_kw = latminorlocator_kw or {}
locator = constructor.Locator(latminorlocator, **latminorlocator_kw)
self._lataxis.set_minor_locator(locator)
@@ -2951,6 +2916,7 @@ def _format_apply_ticklen(
# 3) apply extent, features, and gridlines
# 4) apply tick lengths and defer to parent format
@docstring._snippet_manager
+ @_alias_kwargs("geo.format")
def format(
self,
*,
@@ -2971,21 +2937,13 @@ def format(
latmax: float | None = None,
nsteps: int | None = None,
lonlocator: Any = None,
- lonlines: Any = None,
latlocator: Any = None,
- latlines: Any = None,
lonminorlocator: Any = None,
- lonminorlines: Any = None,
latminorlocator: Any = None,
- latminorlines: Any = None,
lonlocator_kw: MutableMapping | None = None,
- lonlines_kw: MutableMapping | None = None,
latlocator_kw: MutableMapping | None = None,
- latlines_kw: MutableMapping | None = None,
lonminorlocator_kw: MutableMapping | None = None,
- lonminorlines_kw: MutableMapping | None = None,
latminorlocator_kw: MutableMapping | None = None,
- latminorlines_kw: MutableMapping | None = None,
lonformatter: Any = None,
latformatter: Any = None,
lonformatter_kw: MutableMapping | None = None,
@@ -3065,23 +3023,15 @@ def format(
self._format_update_latmax(latmax)
self._format_update_major_locators(
lonlocator=lonlocator,
- lonlines=lonlines,
latlocator=latlocator,
- latlines=latlines,
lonlocator_kw=lonlocator_kw,
- lonlines_kw=lonlines_kw,
latlocator_kw=latlocator_kw,
- latlines_kw=latlines_kw,
)
self._format_update_minor_locators(
lonminorlocator=lonminorlocator,
- lonminorlines=lonminorlines,
latminorlocator=latminorlocator,
- latminorlines=latminorlines,
lonminorlocator_kw=lonminorlocator_kw,
- lonminorlines_kw=lonminorlines_kw,
latminorlocator_kw=latminorlocator_kw,
- latminorlines_kw=latminorlines_kw,
)
(
loninline,
@@ -3131,22 +3081,14 @@ def format(
)
self._sync_shared_tick_state(
"x",
- copy_major_locator=_not_none(lonlocator=lonlocator, lonlines=lonlines)
- is not None,
- copy_minor_locator=_not_none(
- lonminorlocator=lonminorlocator, lonminorlines=lonminorlines
- )
- is not None,
+ copy_major_locator=lonlocator is not None,
+ copy_minor_locator=lonminorlocator is not None,
copy_major_formatter=lonformatter is not None,
)
self._sync_shared_tick_state(
"y",
- copy_major_locator=_not_none(latlocator=latlocator, latlines=latlines)
- is not None,
- copy_minor_locator=_not_none(
- latminorlocator=latminorlocator, latminorlines=latminorlines
- )
- is not None,
+ copy_major_locator=latlocator is not None,
+ copy_minor_locator=latminorlocator is not None,
copy_major_formatter=latformatter is not None,
)
self._format_apply_ticklen(
diff --git a/ultraplot/axes/plot.py b/ultraplot/axes/plot.py
index acb4a7a63..0a5481110 100644
--- a/ultraplot/axes/plot.py
+++ b/ultraplot/axes/plot.py
@@ -36,6 +36,8 @@
from .. import constructor, utils
from ..config import rc
from ..internals import (
+ _alias_kwargs,
+ _canonicalize_kwargs,
_get_aliases,
_not_none,
_pop_kwargs,
@@ -519,23 +521,21 @@
See also :rcraw:`axes.inbounds` and :rcraw:`cmap.inbounds`.
"""
_error_means_docstring = """
-mean, means : bool, default: False
+means : bool, default: False
Whether to plot the means of each column for 2D `{y}` coordinates. Means
are calculated with `numpy.nanmean`. If no other arguments are specified,
this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
-median, medians : bool, default: False
+medians : bool, default: False
Whether to plot the medians of each column for 2D `{y}` coordinates. Medians
are calculated with `numpy.nanmedian`. If no other arguments arguments are
specified, this also sets ``barstd=True`` (and ``boxstd=True`` for violin plots).
"""
_error_bars_docstring = """
-bars : bool, default: None
- Shorthand for `barstd`, `barstds`.
-barstd, barstds : bool, float, or 2-tuple of float, optional
+barstds : bool, float, or 2-tuple of float, optional
Valid only if `mean` or `median` is ``True``. Standard deviation multiples for
*thin error bars* with optional whiskers (i.e., caps). If scalar, then +/- that
multiple is used. If ``True``, the default standard deviation range of +/-3 is used.
-barpctile, barpctiles : bool, float, or 2-tuple of float, optional
+barpctiles : bool, float, or 2-tuple of float, optional
Valid only if `mean` or `median` is ``True``. As with `barstd`, but instead
using percentiles for the error bars. If scalar, that percentile range is
used (e.g., ``90`` shows the 5th to 95th percentiles). If ``True``, the default
@@ -544,9 +544,7 @@
Valid only if `mean` and `median` are ``False``. If shape is 2 x N, these
are the lower and upper bounds for the thin error bars. If shape is N, these
are the absolute, symmetric deviations from the central points.
-boxes : bool, default: None
- Shorthand for `boxstd`, `boxstds`.
-boxstd, boxstds, boxpctile, boxpctiles, boxdata : optional
+boxstds, boxpctiles, boxdata : optional
As with `barstd`, `barpctile`, and `bardata`, but for *thicker error bars*
representing a smaller interval than the thin error bars. If `boxstds` is
``True``, the default standard deviation range of +/-1 is used. If `boxpctiles`
@@ -573,16 +571,12 @@
Color, face color, and edge color for the `boxmarker` marker.
"""
_error_shading_docstring = """
-shade : bool, default: None
- Shorthand for `shadestd`.
-shadestd, shadestds, shadepctile, shadepctiles, shadedata : optional
+shadestds, shadepctiles, shadedata : optional
As with `barstd`, `barpctile`, and `bardata`, but using *shading* to indicate
the error range. If `shadestds` is ``True``, the default standard deviation
range of +/-2 is used. If `shadepctiles` is ``True``, the default
percentile range of 10 to 90 is used.
-fade : bool, default: None
- Shorthand for `fadestd`.
-fadestd, fadestds, fadepctile, fadepctiles, fadedata : optional
+fadestds, fadepctiles, fadedata : optional
As with `shadestd`, `shadepctile`, and `shadedata`, but for an additional,
more faded, *secondary* shaded region. If `fadestds` is ``True``, the default
standard deviation range of +/-3 is used. If `fadepctiles` is ``True``,
@@ -631,7 +625,7 @@
Otherwise :rcraw:`cmap.sequential` is used.
cmap_kw : dict-like, optional
Passed to :class:`~ultraplot.constructor.Colormap`.
-c, color, colors : color-spec or sequence of color-spec, optional
+colors : color-spec or sequence of color-spec, optional
The color(s) used to create a :class:`~ultraplot.colors.DiscreteColormap`.
If not passed, `cmap` is used.
norm : norm-spec, default: \
@@ -700,8 +694,6 @@
`vmin` and `vmax` are the minimum and maximum of the data values.
"""
_manual_levels_docstring = """
-N
- Shorthand for `levels`.
levels : int or sequence of float, default: :rc:`cmap.levels`
The number of level edges or a sequence of level edges. If the former, `locator`
is used to generate this many level edges at "nice" intervals. If the latter,
@@ -782,7 +774,7 @@
Ignored if `labels` is ``False``. Extra keyword args for the labels.
For contour plots, this is passed to `~matplotlib.axes.Axes.clabel`.
Otherwise, this is passed to `~matplotlib.axes.Axes.text`.
-formatter, fmt : formatter-spec, optional
+formatter : formatter-spec, optional
The `~matplotlib.ticker.Formatter` used to format number labels.
Passed to the `~ultraplot.constructor.Formatter` constructor.
formatter_kw : dict-like, optional
@@ -911,7 +903,7 @@
Other parameters
----------------
-stack, stacked : bool, default: False
+stacked : bool, default: False
Whether to "stack" lines from successive columns of {y} data
or plot lines on top of each other.
%(plot.cycle)s
@@ -1137,7 +1129,7 @@
absolute_width : bool, default: False
Whether to make the `width` units *absolute*. If ``True``,
this restores the default matplotlib behavior.
-stack, stacked : bool, default: False
+stacked : bool, default: False
Whether to "stack" bars from successive columns of {y}
data or plot bars side-by-side in groups.
bar_labels : bool, default rc["bar.bar_labels"]
@@ -1253,7 +1245,7 @@
Parameters
----------
%(plot.args_1d_multi{y})s
-stack, stacked : bool, default: False
+stacked : bool, default: False
Whether to "stack" area patches from successive columns of {y}
data or plot area patches on top of each other.
%(plot.args_1d_shared)s
@@ -1304,7 +1296,7 @@
----------------
fill : bool, default: True
Whether to fill the box with a color.
-mean, means : bool, default: False
+means : bool, default: False
If ``True``, this passes ``showmeans=True`` and ``meanline=True`` to
`matplotlib.axes.Axes.boxplot`. Adds mean lines alongside the median.
%(plot.cycle)s
@@ -1512,10 +1504,10 @@
%(plot.weights)s
histtype : {{'bar', 'barstacked', 'step', 'stepfilled'}}, optional
The histogram type. See `matplotlib.axes.Axes.hist` for details.
-width, rwidth : float, default: 0.8 or 1
+rwidth : float, default: 0.8 or 1
The bar width(s) for bar-type histograms relative to the bin size. Default
is ``0.8`` for multiple columns of unstacked data and ``1`` otherwise.
-stack, stacked : bool, optional
+stacked : bool, optional
Whether to "stack" successive columns of {y} data for bar-type histograms
or show side-by-side in groups. Setting this to ``False`` is equivalent to
``histtype='bar'`` and to ``True`` is equivalent to ``histtype='barstacked'``.
@@ -1536,7 +1528,7 @@
The remaining keys style the resulting curve and are passed to
`matplotlib.axes.Axes.plot`, e.g. ``color``, ``linestyle``, ``linewidth``.
By default each curve takes the color of its histogram.
-fill, filled : bool, optional
+fill : bool, optional
Whether to "fill" step-type histograms or just plot the edges. Setting
this to ``False`` is equivalent to ``histtype='step'`` and to ``True``
is equivalent to ``histtype='stepfilled'``.
@@ -1627,7 +1619,7 @@
%(artist.patch)s
%(axes.edgefix)s
%(plot.labels_1d)s
-labelpad, labeldistance : float, optional
+labeldistance : float, optional
The distance at which labels are drawn in radial coordinates.
See also
@@ -3056,13 +3048,13 @@ def _call_negpos(
posobj = self._call_native(name, x, *ypos, **kwargs)
return cbook.silent_list(type(negobj).__name__, (negobj, posobj))
+ @_alias_kwargs("plot.labels")
def _add_auto_labels(
self,
obj,
cobj=None,
labels=False,
labels_kw=None,
- fmt=None,
formatter=None,
formatter_kw=None,
precision=None,
@@ -3074,12 +3066,10 @@ def _add_auto_labels(
# TODO: Add quiverkey to this!
if not labels:
return
- labels_kw = labels_kw or {}
+ labels_kw = _canonicalize_kwargs("plot.labels", labels_kw or {})
formatter_kw = formatter_kw or {}
formatter = _not_none(
- fmt_labels_kw=labels_kw.pop("fmt", None),
formatter_labels_kw=labels_kw.pop("formatter", None),
- fmt=fmt,
formatter=formatter,
default="simple",
)
@@ -3101,15 +3091,13 @@ def _add_auto_labels(
case _:
raise RuntimeError(f"Not possible to add labels to object {obj!r}.")
+ @_alias_kwargs("plot.text")
def _add_quadmesh_labels(
self,
obj,
fmt,
*,
- c=None,
color=None,
- colors=None,
- size=None,
fontsize=None,
**kwargs,
):
@@ -3119,8 +3107,7 @@ def _add_quadmesh_labels(
"""
# Parse input args
obj.update_scalarmappable()
- color = _not_none(c=c, color=color, colors=colors)
- fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
+ fontsize = _not_none(fontsize, rc["font.smallsize"])
kwargs.setdefault("ha", "center")
kwargs.setdefault("va", "center")
@@ -3157,15 +3144,13 @@ def _add_quadmesh_labels(
return labs
+ @_alias_kwargs("plot.text")
def _add_collection_labels(
self,
obj,
fmt,
*,
- c=None,
color=None,
- colors=None,
- size=None,
fontsize=None,
**kwargs,
):
@@ -3177,8 +3162,7 @@ def _add_collection_labels(
# NOTE: This function also hides grid boxes filled with NaNs to avoid ugly
# issue where edge colors surround NaNs. Should maybe move this somewhere else.
obj.update_scalarmappable() # update 'edgecolors' list
- color = _not_none(c=c, color=color, colors=colors)
- fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
+ fontsize = _not_none(fontsize, rc["font.smallsize"])
kwargs.setdefault("ha", "center")
kwargs.setdefault("va", "center")
@@ -3209,16 +3193,14 @@ def _add_collection_labels(
obj.set_edgecolors(edgecolors)
return labs
+ @_alias_kwargs("plot.contour_labels")
def _add_contour_labels(
self,
obj,
cobj,
fmt,
*,
- c=None,
- color=None,
colors=None,
- size=None,
fontsize=None,
inline_spacing=None,
**kwargs,
@@ -3231,8 +3213,7 @@ def _add_contour_labels(
# Parse input args
zorder = max(3, obj.get_zorder() + 1)
kwargs.setdefault("zorder", zorder)
- colors = _not_none(c=c, color=color, colors=colors)
- fontsize = _not_none(size=size, fontsize=fontsize, default=rc["font.smallsize"])
+ fontsize = _not_none(fontsize, rc["font.smallsize"])
inline_spacing = _not_none(inline_spacing, 2.5)
# Separate clabel args from text Artist args
@@ -3264,6 +3245,7 @@ def _add_contour_labels(
return labs
+ @_alias_kwargs("plot.error_bars")
def _add_error_bars(
self,
x,
@@ -3275,16 +3257,10 @@ def _add_error_bars(
default_barpctiles=False,
default_boxpctiles=False,
default_marker=False,
- bars=None,
- boxes=None,
- barstd=None,
barstds=None,
- barpctile=None,
barpctiles=None,
bardata=None,
- boxstd=None,
boxstds=None,
- boxpctile=None,
boxpctiles=None,
boxdata=None,
capsize=None,
@@ -3299,10 +3275,6 @@ def _add_error_bars(
# But also want default behavior where some default error indicator is shown
# if user requests means/medians only. Result is the below kludge.
kwargs, vert = _get_vert(**kwargs)
- barstds = _not_none(bars=bars, barstd=barstd, barstds=barstds)
- boxstds = _not_none(boxes=boxes, boxstd=boxstd, boxstds=boxstds)
- barpctiles = _not_none(barpctile=barpctile, barpctiles=barpctiles)
- boxpctiles = _not_none(boxpctile=boxpctile, boxpctiles=boxpctiles)
if distribution is not None and not any(
typ + mode in key
for key in kwargs
@@ -3392,6 +3364,7 @@ def _add_error_bars(
kwargs["distribution"] = distribution
return (*eobjs, kwargs)
+ @_alias_kwargs("plot.error_shading")
def _add_error_shading(
self,
x,
@@ -3399,16 +3372,10 @@ def _add_error_shading(
*_,
distribution=None,
color_key="color",
- shade=None,
- shadestd=None,
shadestds=None,
- shadepctile=None,
shadepctiles=None,
shadedata=None,
- fade=None,
- fadestd=None,
fadestds=None,
- fadepctile=None,
fadepctiles=None,
fadedata=None,
shadelabel=False,
@@ -3419,10 +3386,6 @@ def _add_error_shading(
Add up to 2 error indicators: more opaque "shading" and less opaque "fading".
"""
kwargs, vert = _get_vert(**kwargs)
- shadestds = _not_none(shade=shade, shadestd=shadestd, shadestds=shadestds)
- fadestds = _not_none(fade=fade, fadestd=fadestd, fadestds=fadestds)
- shadepctiles = _not_none(shadepctile=shadepctile, shadepctiles=shadepctiles)
- fadepctiles = _not_none(fadepctile=fadepctile, fadepctiles=fadepctiles)
drawshade = any(
_ is not None and _ is not False
for _ in (shadestds, shadepctiles, shadedata)
@@ -4190,13 +4153,12 @@ def _scatter_c_is_scalar_data(
return False
return values.shape[0] == point_count
+ @_alias_kwargs(("plot.colormap", "plot.levels"))
def _parse_cmap(
self,
*args,
cmap=None,
cmap_kw=None,
- c=None,
- color=None,
colors=None,
norm=None,
norm_kw=None,
@@ -4276,7 +4238,6 @@ def _parse_cmap(
vmin = _not_none(vmin=vmin, norm_kw_vmin=norm_kw.pop("vmin", None))
vmax = _not_none(vmax=vmax, norm_kw_vmax=norm_kw.pop("vmax", None))
extend = _not_none(extend, "neither")
- colors = _not_none(c=c, color=color, colors=colors) # in case untranslated
modes = {
key: kwargs.pop(key, None)
for key in ("sequential", "diverging", "cyclic", "qualitative")
@@ -4763,10 +4724,10 @@ def _parse_level_num(
levels = np.append(levels, levels[-1] + width * np.sign(levels[-1]))
return levels, kwargs
+ @_alias_kwargs("plot.levels")
def _parse_level_vals(
self,
*args,
- N=None,
levels=None,
values=None,
extend=None,
@@ -4789,8 +4750,6 @@ def _parse_level_vals(
----------
*args
The sample data. Passed to `_parse_level_lim`.
- N
- Shorthand for `levels`.
levels : int or sequence of float, optional
The levels list or (approximate) number of levels to create.
values : int or sequence of float, optional
@@ -4854,7 +4813,7 @@ def _sanitize_levels(key, array, minsize):
explicit_limits = vmin is not None or vmax is not None
line_contours = min_levels == 1
keep_explicit_line_limits = line_contours and explicit_limits
- levels = _not_none(N=N, levels=levels, norm_kw_levs=norm_kw.pop("levels", None))
+ levels = _not_none(levels=levels, norm_kw_levs=norm_kw.pop("levels", None))
if positive and negative:
warnings._warn_ultraplot(
"Incompatible args positive=True and negative=True. Using former."
@@ -5614,6 +5573,7 @@ def parametric(self, x, y, c, *, interp=0, scalex=True, scaley=True, **kwargs):
self._update_guide(obj, **guide_kw)
return obj
+ @_alias_kwargs("plot.stacked")
def _apply_lines(
self,
xs,
@@ -5622,7 +5582,6 @@ def _apply_lines(
colors,
*,
vert=True,
- stack=None,
stacked=None,
negpos=False,
**kwargs,
@@ -5637,7 +5596,6 @@ def _apply_lines(
kw["colors"] = colors
kw.update(_pop_props(kw, "collection"))
kw, extents = self._inbounds_extent(**kw)
- stack = _not_none(stack=stack, stacked=stacked)
xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
guide_kw = _pop_params(kw, self._update_guide)
@@ -5648,7 +5606,7 @@ def _apply_lines(
objs, sides = [], []
for _, n, x, y1, y2, kw in self._iter_arg_cols(xs, ys1, ys2, **kw):
kw = self._parse_cycle(n, **kw)
- if stack:
+ if stacked:
y1 = y1 + y0 # avoid in-place modification
y2 = y2 + y0
y0 = y0 + y2 - y1 # irrelevant that we added y0 to both
@@ -5833,6 +5791,7 @@ def scatterx(self, *args, **kwargs):
kwargs = _parse_vert(default_vert=False, **kwargs)
return self._apply_scatter(*args, **kwargs)
+ @_alias_kwargs("plot.stacked")
def _apply_fill(
self,
xs,
@@ -5842,7 +5801,6 @@ def _apply_fill(
*,
vert=True,
negpos=None,
- stack=None,
stacked=None,
**kwargs,
):
@@ -5891,7 +5849,6 @@ def _apply_fill(
kw.update(_pop_props(kw, "patch"))
kw, extents = self._inbounds_extent(**kw)
name = "fill_between" if vert else "fill_betweenx"
- stack = _not_none(stack=stack, stacked=stacked)
xs, ys1, ys2, kw = self._parse_1d_args(xs, ys1, ys2, vert=vert, **kw)
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
guide_kw = _pop_params(kw, self._update_guide)
@@ -5905,7 +5862,7 @@ def _apply_fill(
kw = self._parse_cycle(n, **kw)
# If stacking requested, adjust y arrays
- if stack:
+ if stacked:
y1 = y1 + y0
y2 = y2 + y0
y0 = y0 + y2 - y1
@@ -6156,6 +6113,7 @@ def _convert_bar_width(x, width=1):
x_step = x_step.astype("timedelta64[ns]")
return width * x_step
+ @_alias_kwargs("plot.stacked")
def _apply_bar(
self,
xs,
@@ -6164,7 +6122,6 @@ def _apply_bar(
bs,
*,
absolute_width=None,
- stack=None,
stacked=None,
negpos=False,
orientation="vertical",
@@ -6179,7 +6136,6 @@ def _apply_bar(
bar_labels = kw.pop("bar_labels", rc["bar.bar_labels"])
bar_labels_kw = kw.pop("bar_labels_kw", {})
name = "barh" if orientation == "horizontal" else "bar"
- stack = _not_none(stack=stack, stacked=stacked)
xs, hs, kw = self._parse_1d_args(xs, hs, orientation=orientation, **kw)
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
if absolute_width is None:
@@ -6212,7 +6168,7 @@ def _apply_bar(
) # tolerate scalar `bottom`/`left`
if not absolute_width:
w = self._convert_bar_width(x, w)
- if stack:
+ if stacked:
b = b + b0
b0 = b0 + h
else: # instead "group" the bars (this is no-op if we have 1 column)
@@ -6330,12 +6286,13 @@ def barh(self, *args, **kwargs):
@inputs._preprocess_or_redirect("x", "explode")
@docstring._concatenate_inherited
@docstring._snippet_manager
- def pie(self, x, explode, *, labelpad=None, labeldistance=None, **kwargs):
+ @_alias_kwargs("plot.pie")
+ def pie(self, x, explode, *, labeldistance=None, **kwargs):
"""
%(plot.pie)s
"""
kw = kwargs.copy()
- pad = _not_none(labeldistance=labeldistance, labelpad=labelpad, default=1.15)
+ labeldistance = _not_none(labeldistance, 1.15)
wedge_kw = kw.pop("wedgeprops", None) or {}
wedge_kw.update(_pop_props(kw, "patch"))
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
@@ -6351,7 +6308,7 @@ def pie(self, x, explode, *, labelpad=None, labeldistance=None, **kwargs):
"pie",
x,
explode=explode,
- labeldistance=pad,
+ labeldistance=labeldistance,
wedgeprops=wedge_kw,
**kw,
)
@@ -6413,16 +6370,15 @@ def _apply_boxplot_tick_manager(
mticker.FixedFormatter([str(label) for label in label_values])
)
+ @_alias_kwargs(("plot.statistics", "plot.boxplot"))
def _apply_boxplot(
self,
x,
y,
*,
- mean=None,
means=None,
vert=True,
fill=None,
- filled=None,
marker=None,
markersize=None,
**kwargs,
@@ -6433,8 +6389,6 @@ def _apply_boxplot(
# Global and fill properties
kw = kwargs.copy()
kw.update(_pop_props(kw, "patch"))
- fill = _not_none(fill=fill, filled=filled)
- means = _not_none(mean=mean, means=means, showmeans=kw.get("showmeans"))
linewidth = kw.pop("linewidth", rc["patch.linewidth"])
edgecolor = kw.pop("edgecolor", "black")
fillcolor = kw.pop("facecolor", None)
@@ -6609,17 +6563,14 @@ def boxploth(self, *args, **kwargs):
kwargs = _parse_vert(default_vert=False, **kwargs)
return self._apply_boxplot(*args, **kwargs)
+ @_alias_kwargs(("plot.statistics", "plot.violinplot"))
def _apply_violinplot(
self,
x,
y,
vert=True,
- mean=None,
means=None,
- median=None,
medians=None,
- showmeans=None,
- showmedians=None,
showextrema=None,
**kwargs,
):
@@ -6630,8 +6581,6 @@ def _apply_violinplot(
kw = kwargs.copy()
kw.update(_pop_props(kw, "patch"))
kw.setdefault("capsize", 0) # caps are redundant for violin plots
- means = _not_none(mean=mean, means=means, showmeans=showmeans)
- medians = _not_none(median=median, medians=medians, showmedians=showmedians)
if showextrema:
kw["default_barpctiles"] = True
if not means and not medians:
@@ -7132,17 +7081,15 @@ def ridgelineh(self, data, **kwargs):
kwargs = _parse_vert(default_vert=False, **kwargs)
return self._apply_ridgeline(data, **kwargs)
+ @_alias_kwargs("plot.hist")
def _apply_hist(
self,
xs,
bins,
*,
- width=None,
rwidth=None,
- stack=None,
stacked=None,
fill=None,
- filled=None,
histtype=None,
orientation="vertical",
kde=False,
@@ -7159,15 +7106,13 @@ def _apply_hist(
_, xs, kw = self._parse_1d_args(
xs, autoreverse=False, orientation=orientation, **kwargs
)
- fill = _not_none(fill=fill, filled=filled)
- stack = _not_none(stack=stack, stacked=stacked)
if fill is not None:
histtype = _not_none(histtype, "stepfilled" if fill else "step")
- if stack is not None:
- histtype = _not_none(histtype, "barstacked" if stack else "bar")
+ if stacked is not None:
+ histtype = _not_none(histtype, "barstacked" if stacked else "bar")
kw["bins"] = bins
kw["label"] = kw.pop("labels", None) # multiple labels are natively supported
- kw["rwidth"] = _not_none(width=width, rwidth=rwidth) # latter is native
+ kw["rwidth"] = rwidth
kw["histtype"] = histtype = _not_none(histtype, "bar")
kw.update(_pop_props(kw, "patch"))
edgefix_kw = _pop_params(kw, self._fix_patch_edges)
diff --git a/ultraplot/axes/polar.py b/ultraplot/axes/polar.py
index c56b2bc98..caf091fa1 100644
--- a/ultraplot/axes/polar.py
+++ b/ultraplot/axes/polar.py
@@ -19,6 +19,7 @@
from .. import ticker as pticker
from ..config import rc
from ..internals import (
+ _alias_kwargs,
_not_none,
_pop_rc,
docstring,
@@ -72,15 +73,11 @@
Used to determine the azimuthal and radial gridline positions.
Passed to the `~ultraplot.constructor.Locator` constructor. Can be
float, list of float, string, or `matplotlib.ticker.Locator` instance.
-thetalines, rlines
- Aliases for `thetalocator`, `rlocator`.
thetalocator_kw, rlocator_kw : dict-like, optional
The azimuthal and radial locator settings. Passed to
`~ultraplot.constructor.Locator`.
thetaminorlocator, rminorlocator : optional
As for `thetalocator`, `rlocator`, but for the minor gridlines.
-thetaminorticks, rminorticks : optional
- Aliases for `thetaminorlocator`, `rminorlocator`.
thetaminorlocator_kw, rminorlocator_kw
As for `thetalocator_kw`, `rlocator_kw`, but for the minor locator.
rlabelpos : float, optional
@@ -92,8 +89,6 @@
Passed to the `~ultraplot.constructor.Formatter` constructor.
Can be string, list of string, or `matplotlib.ticker.Formatter`
instance. Use ``[]``, ``'null'``, or ``'none'`` for no labels.
-thetalabels, rlabels : optional
- Aliases for `thetaformatter`, `rformatter`.
thetaformatter_kw, rformatter_kw : dict-like, optional
The azimuthal and radial label formatter settings. Passed to
`~ultraplot.constructor.Formatter`.
@@ -500,6 +495,7 @@ def get_tightbbox(self, renderer, *args, **kwargs):
return super().get_tightbbox(renderer, *args, **kwargs)
@docstring._snippet_manager
+ @_alias_kwargs("polar.format")
def format(
self,
*,
@@ -523,20 +519,14 @@ def format(
rborder=None,
thetalocator=None,
rlocator=None,
- thetalines=None,
- rlines=None,
thetalocator_kw=None,
rlocator_kw=None,
thetaminorlocator=None,
rminorlocator=None,
- thetaminorlines=None,
- rminorlines=None, # noqa: E501
thetaminorlocator_kw=None,
rminorlocator_kw=None,
thetaformatter=None,
rformatter=None,
- thetalabels=None,
- rlabels=None,
thetaformatter_kw=None,
rformatter_kw=None,
labelpad=None,
@@ -611,20 +601,6 @@ def format(
rminorlocator_kw = rminorlocator_kw or {}
rformatter_kw = rformatter_kw or {}
- # Flexible input
- thetalocator = _not_none(thetalines=thetalines, thetalocator=thetalocator)
- thetaformatter = _not_none(
- thetalabels=thetalabels, thetaformatter=thetaformatter
- ) # noqa: E501
- thetaminorlocator = _not_none(
- thetaminorlines=thetaminorlines, thetaminorlocator=thetaminorlocator
- ) # noqa: E501
- rlocator = _not_none(rlines=rlines, rlocator=rlocator)
- rformatter = _not_none(rlabels=rlabels, rformatter=rformatter)
- rminorlocator = _not_none(
- rminorlines=rminorlines, rminorlocator=rminorlocator
- ) # noqa: E501
-
# Special radius settings
if r0 is not None:
self.set_rorigin(r0)
diff --git a/ultraplot/axes/taylor.py b/ultraplot/axes/taylor.py
index 02f8c93cb..b5020c242 100644
--- a/ultraplot/axes/taylor.py
+++ b/ultraplot/axes/taylor.py
@@ -11,7 +11,7 @@
import numpy as np
from ..config import rc
-from ..internals import _not_none, _pop_rc, docstring
+from ..internals import _alias_kwargs, _not_none, _pop_rc, docstring
from .polar import PolarAxes
__all__ = ["TaylorAxes"]
@@ -29,7 +29,7 @@
quadrant : {1, 2, 3, 4} or str, default: 1
The quadrant used for the Taylor diagram. Quadrants follow the Cartesian
convention: ``1`` is upper right and ``4`` is lower right.
-corrlocator, corrlines, corrticks : float or sequence of float, optional
+corrlocator : float or sequence of float, optional
Correlation coefficients used for the angular gridlines.
labelcolor, labelsize, labelweight : optional
Label text properties.
@@ -492,6 +492,7 @@ def draw(self, renderer=None, *args, **kwargs):
super().draw(renderer, *args, **kwargs)
@docstring._snippet_manager
+ @_alias_kwargs("taylor.format")
def format(
self,
*,
@@ -501,8 +502,6 @@ def format(
thetaunit=None,
quadrant=None,
corrlocator=None,
- corrlines=None,
- corrticks=None,
xlabel_kw=None,
ylabel_kw=None,
corrlabel_kw=None,
@@ -547,10 +546,7 @@ def format(
)
)
self._taylor_thetaunit = thetaunit
- corrs = _not_none(
- corrlocator=corrlocator, corrlines=corrlines, corrticks=corrticks
- )
- self._update_taylor_ticks(corrs)
+ self._update_taylor_ticks(corrlocator)
self._update_taylor_labels(
xlabel=xlabel,
ylabel=ylabel,
diff --git a/ultraplot/colorbar.py b/ultraplot/colorbar.py
index 165aac31c..bb1f3e8d2 100644
--- a/ultraplot/colorbar.py
+++ b/ultraplot/colorbar.py
@@ -17,7 +17,7 @@
from packaging import version
from . import constructor, colors as pcolors
-from .internals import _not_none, _pop_params, guides, warnings
+from .internals import _alias_kwargs, _not_none, _pop_params, guides, warnings
from .config import rc, _version_mpl
from .ultralayout import KIWI_AVAILABLE, ColorbarLayoutSolver
from . import ticker as pticker
@@ -41,6 +41,7 @@ class UltraColorbar:
def __init__(self, axes: maxes.Axes):
self.axes = axes
+ @_alias_kwargs("colorbar")
def add(
self,
mappable: Any,
@@ -51,55 +52,42 @@ def add(
space: Optional[Union[float, str]] = None,
pad: Optional[Union[float, str]] = None,
width: Optional[Union[float, str]] = None,
- length: Optional[Union[float, str]] = None,
span: Optional[Union[int, Tuple[int, int]]] = None,
row: Optional[int] = None,
col: Optional[int] = None,
rows: Optional[Union[int, Tuple[int, int]]] = None,
cols: Optional[Union[int, Tuple[int, int]]] = None,
- shrink: Optional[Union[float, str]] = None,
+ length: Optional[Union[float, str]] = None,
label: Optional[str] = None,
- title: Optional[str] = None,
reverse: bool = False,
rotation: Optional[float] = None,
- grid: Optional[bool] = None,
- edges: Optional[bool] = None,
drawedges: Optional[bool] = None,
extend: Optional[str] = None,
extendsize: Optional[Union[float, str]] = None,
extendfrac: Optional[float] = None,
ticks: Optional[Iterable[float]] = None,
- locator: Optional[Any] = None,
locator_kw: Optional[dict[str, Any]] = None,
format: Optional[str] = None,
- formatter: Optional[Any] = None,
- ticklabels: Optional[Iterable[str]] = None,
formatter_kw: Optional[dict[str, Any]] = None,
minorticks: Optional[bool] = None,
- minorlocator: Optional[Any] = None,
minorlocator_kw: Optional[dict[str, Any]] = None,
tickminor: Optional[bool] = None,
ticklen: Optional[Union[float, str]] = None,
ticklenratio: Optional[float] = None,
- tickdir: Optional[str] = None,
tickdirection: Optional[str] = None,
tickwidth: Optional[Union[float, str]] = None,
tickwidthratio: Optional[float] = None,
ticklabelsize: Optional[float] = None,
ticklabelweight: Optional[str] = None,
ticklabelcolor: Optional[str] = None,
- labelloc: Optional[str] = None,
labellocation: Optional[str] = None,
labelsize: Optional[float] = None,
labelweight: Optional[str] = None,
labelcolor: Optional[str] = None,
- c: Optional[str] = None,
color: Optional[str] = None,
- lw: Optional[Union[float, str]] = None,
linewidth: Optional[Union[float, str]] = None,
edgefix: Optional[bool] = None,
rasterized: Optional[bool] = None,
- frame: Optional[bool] = None,
frameon: Optional[bool] = None,
outline: Union[bool, None] = None,
labelrotation: Optional[Union[str, float]] = None,
@@ -113,19 +101,14 @@ def add(
# Parse input arguments and apply defaults
# TODO: Get the 'best' inset colorbar location using the legend algorithm
# and implement inset colorbars the same as inset legends.
- grid = _not_none(
- grid=grid, edges=edges, drawedges=drawedges, default=rc["colorbar.grid"]
- ) # noqa: E501
- length = _not_none(length=length, shrink=shrink)
- label = _not_none(title=title, label=label)
- labelloc = _not_none(labelloc=labelloc, labellocation=labellocation)
- locator = _not_none(ticks=ticks, locator=locator)
- formatter = _not_none(ticklabels=ticklabels, formatter=formatter, format=format)
- minorlocator = _not_none(minorticks=minorticks, minorlocator=minorlocator)
- color = _not_none(c=c, color=color, default=rc["axes.edgecolor"])
- linewidth = _not_none(lw=lw, linewidth=linewidth)
+ grid = _not_none(drawedges, rc["colorbar.grid"])
+ labelloc = labellocation
+ locator = ticks
+ formatter = format
+ minorlocator = minorticks
+ color = _not_none(color, rc["axes.edgecolor"])
ticklen = units(_not_none(ticklen, rc["tick.len"]), "pt")
- tickdir = _not_none(tickdir=tickdir, tickdirection=tickdirection)
+ tickdir = tickdirection
tickwidth = units(_not_none(tickwidth, linewidth, rc["tick.width"]), "pt")
linewidth = units(_not_none(linewidth, default=rc["axes.linewidth"]), "pt")
ticklenratio = _not_none(ticklenratio, rc["tick.lenratio"])
@@ -162,7 +145,7 @@ def add(
# Generate and prepare the colorbar axes
# NOTE: The inset axes function needs 'label' to know how to pad the box
# TODO: Use seperate keywords for frame properties vs. colorbar edge properties?
- frame = _not_none(frame=frame, frameon=frameon)
+ frame = frameon
bbox_to_anchor = kwargs.pop("bbox_to_anchor", None)
inset_side = loc in ("left", "right", "top", "bottom") and getattr(
ax, "_inset_parent", None
@@ -209,7 +192,7 @@ def add(
extendsize = _not_none(extendsize, rc["colorbar.insetextend"])
cax, kwargs = ax._parse_colorbar_inset(
loc=loc,
- frame=frame,
+ frameon=frame,
labelloc=labelloc,
labelrotation=labelrotation,
labelsize=labelsize,
diff --git a/ultraplot/constructor.py b/ultraplot/constructor.py
index eb8d7383f..48b651879 100644
--- a/ultraplot/constructor.py
+++ b/ultraplot/constructor.py
@@ -34,6 +34,7 @@
from . import ticker as pticker
from .config import rc
from .internals import (
+ _alias_kwargs,
_not_none,
_pop_props,
_version_cartopy,
@@ -880,8 +881,6 @@ class Cycle(cycler.Cycler):
If the last positional argument is numeric, it is used for the
`samples` keyword argument.
- N
- Shorthand for `samples`.
samples : float or sequence of float, optional
For :class:`~ultraplot.colors.DiscreteColormap`\\ s, this is the number of
colors to select. For example, ``Cycle('538', 4)`` returns the first 4
@@ -934,9 +933,9 @@ class Cycle(cycler.Cycler):
ultraplot.utils.get_colors
"""
- def __init__(self, *args, N=None, samples=None, name=None, **kwargs):
+ @_alias_kwargs("cycle")
+ def __init__(self, *args, samples=None, name=None, **kwargs):
cycler_props = self._parse_basic_properties(kwargs)
- samples = _not_none(samples=samples, N=N) # trigger Colormap default
if not args:
self._handle_empty_args(cycler_props, kwargs)
elif self._is_all_cyclers(args):
@@ -1495,13 +1494,12 @@ def _warn_basemap_deprecated():
)
+@_alias_kwargs("projection")
def Proj(
name,
backend=None,
lon0=None,
- lon_0=None,
lat0=None,
- lat_0=None,
lonlim=None,
latlim=None,
**kwargs,
@@ -1592,8 +1590,6 @@ def Proj(
lon0, lat0 : float, optional
The central projection longitude and latitude. These are translated to
`central_longitude`, `central_latitude` for cartopy projections.
- lon_0, lat_0 : float, optional
- Aliases for `lon0`, `lat0`.
lonlim : 2-tuple of float, optional
The longitude limits. Translated to `min_longitude` and `max_longitude` for
cartopy projections and `llcrnrlon` and `urcrnrlon` for basemap projections.
@@ -1667,8 +1663,6 @@ def Proj(
# Parse input arguments
# NOTE: Underscores are permitted for consistency with cartopy only here.
# In format() underscores are not allowed for constistency with reset of API.
- lon0 = _not_none(lon0=lon0, lon_0=lon_0)
- lat0 = _not_none(lat0=lat0, lat_0=lat_0)
lonlim = _not_none(lonlim, default=(None, None))
latlim = _not_none(latlim, default=(None, None))
is_crs = Projection is not object and isinstance(name, Projection)
diff --git a/ultraplot/figure.py b/ultraplot/figure.py
index 89c830bc1..4dd8d8342 100644
--- a/ultraplot/figure.py
+++ b/ultraplot/figure.py
@@ -32,6 +32,7 @@
from .config import rc, rc_matplotlib
from .internals import (
_alias_kwargs,
+ _canonicalize_kwargs,
_not_none,
_pop_params,
_pop_rc,
@@ -98,15 +99,10 @@ def _any_not_none(*values):
%(units.in)s
Ignored if `figwidth`, `figheight`, or `figsize` was passed. If you
specify just one, `refaspect` will be respected.
-ref, aspect, axwidth, axheight
- Aliases for `refnum`, `refaspect`, `refwidth`, `refheight`.
- *These may be deprecated in a future release.*
figwidth, figheight : unit-spec, optional
The figure width and height. Default behavior is to use `refwidth`.
%(units.in)s
If you specify just one, `refaspect` will be respected.
-width, height
- Aliases for `figwidth`, `figheight`.
figsize : 2-tuple, optional
Tuple specifying the figure ``(width, height)``.
sharex, sharey, share \
@@ -229,25 +225,25 @@ def _any_not_none(*values):
two options:
* Pass a *list* of projection specifications, one for each subplot.
- For example, ``uplt.subplots(ncols=2, proj=('cart', 'robin'))``.
+ For example, ``uplt.subplots(ncols=2, projection=('cart', 'robin'))``.
* Pass a *dictionary* of projection specifications, where the
keys are integers or tuples of integers that indicate the projection
to use for the corresponding subplot number(s). If a key is not
provided, the default projection ``'cartesian'`` is used. For example,
- ``uplt.subplots(ncols=4, proj={2: 'cyl', (3, 4): 'stere'})`` creates
+ ``uplt.subplots(ncols=4, projection={2: 'cyl', (3, 4): 'stere'})`` creates
a figure with a default Cartesian axes for the first subplot, a Mercator
projection for the second subplot, and a Stereographic projection
for the third and fourth subplots.
%(axes.proj_kw)s
If dictionary of properties, applies globally. If list or dictionary of
- dictionaries, applies to specific subplots, as with `proj`. For example,
- ``uplt.subplots(ncols=2, proj='cyl', proj_kw=({'lon_0': 0}, {'lon_0': 180})``
+ dictionaries, applies to specific subplots, as with `projection`. For example,
+ ``uplt.subplots(ncols=2, projection='cyl', projection_kw=({'lon0': 0}, {'lon0': 180})``
centers the projection in the left subplot on the prime meridian and in the
right subplot on the international dateline.
%(axes.backend)s
If string, applies to all subplots. If list or dict, applies to specific
- subplots, as with `proj`.
+ subplots, as with `projection`.
%(gridspec.shared)s
%(gridspec.vector)s
%(gridspec.tight)s
@@ -753,14 +749,7 @@ def __repr__(self):
@docstring._obfuscate_kwargs
@docstring._snippet_manager
- @_alias_kwargs(
- refnum=("ref",),
- refaspect=("aspect",),
- refwidth=("axwidth",),
- refheight=("axheight",),
- figwidth=("width",),
- figheight=("height",),
- )
+ @_alias_kwargs("figure.init")
def __init__(
self,
*,
@@ -1050,6 +1039,7 @@ def _init_figure_state(self, figwidth, figheight, kwargs):
self._skip_autolayout = False
self._includepanels = None
self._render_context = {}
+ kwargs = _canonicalize_kwargs("figure.format", kwargs)
rc_kw, rc_mode = _pop_rc(kwargs)
kw_format = _pop_params(kwargs, self._format_signature)
if figwidth is not None and figheight is not None:
@@ -3488,27 +3478,26 @@ def _align_content(): # noqa: E306
"0.10.0", mathtext_fallback="uplt.rc.mathtext_fallback = {}"
)
@docstring._snippet_manager
+ @_alias_kwargs("figure.format")
+ @_alias_kwargs("axes.format")
+ @_alias_kwargs("cartesian.format")
+ @_alias_kwargs("geo.format")
+ @_alias_kwargs("polar.format")
+ @_alias_kwargs("taylor.format")
def format(
self,
axs=None,
*,
- figtitle=None,
suptitle=None,
suptitle_kw=None,
- llabels=None,
leftlabels=None,
leftlabels_kw=None,
- rlabels=None,
rightlabels=None,
rightlabels_kw=None,
- blabels=None,
bottomlabels=None,
bottomlabels_kw=None,
- tlabels=None,
toplabels=None,
toplabels_kw=None,
- rowlabels=None,
- collabels=None, # aliases
includepanels=None,
**kwargs,
):
@@ -3568,23 +3557,16 @@ def format(
explicit_format_keys.update(generic_axis_kwargs)
rc_kw, rc_mode = _pop_rc(kwargs)
figure_layout_requested = _any_not_none(
- figtitle,
suptitle,
suptitle_kw,
- llabels,
leftlabels,
leftlabels_kw,
- rlabels,
rightlabels,
rightlabels_kw,
- blabels,
bottomlabels,
bottomlabels_kw,
- tlabels,
toplabels,
toplabels_kw,
- rowlabels,
- collabels,
includepanels,
)
if (
@@ -3620,30 +3602,11 @@ def format(
rightlabels_kw = rightlabels_kw or {}
bottomlabels_kw = bottomlabels_kw or {}
toplabels_kw = toplabels_kw or {}
- self._update_super_title(
- _not_none(figtitle=figtitle, suptitle=suptitle),
- **suptitle_kw,
- )
- self._update_super_labels(
- "left",
- _not_none(rowlabels=rowlabels, leftlabels=leftlabels, llabels=llabels),
- **leftlabels_kw,
- )
- self._update_super_labels(
- "right",
- _not_none(rightlabels=rightlabels, rlabels=rlabels),
- **rightlabels_kw,
- )
- self._update_super_labels(
- "bottom",
- _not_none(bottomlabels=bottomlabels, blabels=blabels),
- **bottomlabels_kw,
- )
- self._update_super_labels(
- "top",
- _not_none(collabels=collabels, toplabels=toplabels, tlabels=tlabels),
- **toplabels_kw,
- )
+ self._update_super_title(suptitle, **suptitle_kw)
+ self._update_super_labels("left", leftlabels, **leftlabels_kw)
+ self._update_super_labels("right", rightlabels, **rightlabels_kw)
+ self._update_super_labels("bottom", bottomlabels, **bottomlabels_kw)
+ self._update_super_labels("top", toplabels, **toplabels_kw)
# Update the main axes
if skip_axes: # avoid recursion
@@ -3717,12 +3680,12 @@ def _axis_has_label_text(ax, axis):
@docstring._concatenate_inherited
@docstring._snippet_manager
+ @_alias_kwargs("colorbar")
def colorbar(
self,
mappable,
values=None,
loc: Optional[str] = None,
- location: Optional[str] = None,
row: Optional[int] = None,
col: Optional[int] = None,
rows: Optional[Union[int, Tuple[int, int]]] = None,
@@ -3740,11 +3703,8 @@ def colorbar(
----------
%(axes.colorbar_args)s
length : float, default: :rc:`colorbar.length`
- The colorbar length. Units are relative to the span of the rows and
- columns of subplots.
- shrink : float, optional
- Alias for `length`. This is included for consistency with
- `matplotlib.figure.Figure.colorbar`.
+ The colorbar length (also accepted as ``shrink``). Units are relative
+ to the span of the rows and columns of subplots.
width : unit-spec, default: :rc:`colorbar.width`
The colorbar width.
%(units.in)s
@@ -3780,6 +3740,9 @@ def colorbar(
)
# Fill this axes
if cax is not None:
+ # Matplotlib uses its native spelling when given explicit axes.
+ if "length" in kwargs:
+ kwargs["shrink"] = kwargs.pop("length")
with context._state_context(cax, _internal_call=True): # do not wrap pcolor
cb = super().colorbar(mappable, cax=cax, **kwargs)
# Axes panel colorbar
@@ -3913,7 +3876,7 @@ def colorbar(
)
# Figure panel colorbar
else:
- loc = _not_none(loc=loc, location=location, default="r")
+ loc = _not_none(loc, "r")
ax = self._add_figure_panel(
loc,
row=row,
@@ -3930,12 +3893,12 @@ def colorbar(
@docstring._concatenate_inherited
@docstring._snippet_manager
+ @_alias_kwargs("legend")
def legend(
self,
handles=None,
labels=None,
loc=None,
- location=None,
row=None,
col=None,
rows=None,
@@ -4128,7 +4091,7 @@ def legend(
)
# Figure panel legend
else:
- loc = _not_none(loc=loc, location=location, default="r")
+ loc = _not_none(loc, "r")
ax = self._add_figure_panel(
loc,
row=row,
diff --git a/ultraplot/gridspec.py b/ultraplot/gridspec.py
index 89915645e..d91d140d0 100644
--- a/ultraplot/gridspec.py
+++ b/ultraplot/gridspec.py
@@ -20,6 +20,7 @@
from .axes._formatting import pop_axis_format_kwargs
from .config import rc
from .internals import (
+ _alias_kwargs,
_not_none,
_pop_rc,
docstring,
@@ -71,13 +72,10 @@
layout algorithm. For example, ``subplots(ncols=3, tight=True, wspace=(2, None))``
fixes the space between columns 1 and 2 but lets the tight layout algorithm
determine the space between columns 2 and 3.
-wratios, hratios : float or sequence, optional
- Passed to :class:`~ultraplot.gridspec.GridSpec`, denotes the width and height
- ratios for the subplot grid. Length of `wratios` must match the number
- of columns, and length of `hratios` must match the number of rows.
-width_ratios, height_ratios
- Aliases for `wratios`, `hratios`. Included for
- consistency with `matplotlib.gridspec.GridSpec`.
+width_ratios, height_ratios : float or sequence, optional
+ Passed to :class:`~ultraplot.gridspec.GridSpec`, and denote the width and
+ height ratios for the subplot grid. Length of `width_ratios` must match the
+ number of columns, and length of `height_ratios` must match the number of rows.
wpad, hpad, pad : unit-spec or sequence, optional
The tight layout padding between columns, rows, and both, respectively.
Unlike ``space``, these control the padding between subplot content
@@ -529,8 +527,8 @@ def _compute_ultra_positions(self):
right=right,
top=top,
bottom=bottom,
- wratios=self._wratios_total,
- hratios=self._hratios_total,
+ width_ratios=self._wratios_total,
+ height_ratios=self._hratios_total,
wpanels=[bool(val) for val in self._wpanels],
hpanels=[bool(val) for val in self._hpanels],
)
@@ -1377,6 +1375,7 @@ def _update_figsize(self):
else:
warnings._warn_ultraplot(f"Auto resize failed. Invalid figsize {figsize}.")
+ @_alias_kwargs("gridspec")
def _update_params(
self,
*,
@@ -1400,8 +1399,6 @@ def _update_params(
outerpad=None,
innerpad=None,
panelpad=None,
- hratios=None,
- wratios=None,
width_ratios=None,
height_ratios=None,
):
@@ -1484,8 +1481,8 @@ def _assign_vector(key, values, space):
wspace = _not_none(wspace, space)
hspace = units(hspace, "em", "in")
wspace = units(wspace, "em", "in")
- hratios = _not_none(hratios=hratios, height_ratios=height_ratios)
- wratios = _not_none(wratios=wratios, width_ratios=width_ratios)
+ hratios = height_ratios
+ wratios = width_ratios
_assign_vector("hpad", hpad, space=True)
_assign_vector("wpad", wpad, space=True)
_assign_vector("hspace", hspace, space=True)
diff --git a/ultraplot/internals/__init__.py b/ultraplot/internals/__init__.py
index a49943b07..c6d24c134 100644
--- a/ultraplot/internals/__init__.py
+++ b/ultraplot/internals/__init__.py
@@ -22,6 +22,11 @@
from .kwargs import ( # noqa: F401
_alias_kwargs,
_alias_maps,
+ _alias_registry,
+ _canonicalize_kwargs,
+ _format_alias_reference,
+ _figure_format_alias_scopes,
+ _format_alias_scopes,
_get_aliases,
_get_signature,
_kwargs_to_args,
diff --git a/ultraplot/internals/docstring.py b/ultraplot/internals/docstring.py
index 205e92bbf..7becad3ec 100644
--- a/ultraplot/internals/docstring.py
+++ b/ultraplot/internals/docstring.py
@@ -180,71 +180,62 @@ def __setitem__(self, key, value):
_snippet_manager["units.em"] = _units_docstring.format(units="em-widths")
-# Style docstrings
-# NOTE: These are needed in a few different places
-def _aliases_note(*names):
- """
- Render a compact ``Aliases: ...`` note for a style parameter. The canonical
- name leads the numpydoc field; the common documented synonyms go here so the
- parameter reads cleanly instead of opening with a pile of alias names.
- """
- return "Aliases: " + ", ".join(f"``{name}``" for name in names) + "."
-
-
-_line_docstring = f"""
+# Style docstrings. Compatibility spellings are intentionally kept out of these
+# primary parameter docs; the registry-generated alias reference is authoritative.
+_line_docstring = """
linewidth : unit-spec, default: :rc:`lines.linewidth`
- The width of the line(s). {_aliases_note("lw", "linewidths")}
+ The width of the line(s).
%(units.pt)s
linestyle : str, default: :rc:`lines.linestyle`
- The style of the line(s). {_aliases_note("ls", "linestyles")}
+ The style of the line(s).
color : color-spec, optional
- The color of the line(s). The property `cycle` is used by default. {_aliases_note("c", "colors")}
+ The color of the line(s). The property `cycle` is used by default.
alpha : float, optional
- The opacity of the line(s). Inferred from `color` by default. {_aliases_note("a", "alphas")}
+ The opacity of the line(s). Inferred from `color` by default.
"""
-_patch_docstring = f"""
+_patch_docstring = """
linewidth : unit-spec, default: :rc:`patch.linewidth`
- The edge width of the patch(es). {_aliases_note("lw", "linewidths")}
+ The edge width of the patch(es).
%(units.pt)s
linestyle : str, default: '-'
- The edge style of the patch(es). {_aliases_note("ls", "linestyles")}
-edgecolor : color-spec, default: '{{edgecolor}}'
- The edge color of the patch(es). {_aliases_note("ec", "edgecolors")}
+ The edge style of the patch(es).
+edgecolor : color-spec, default: '{edgecolor}'
+ The edge color of the patch(es).
facecolor : color-spec, optional
- The face color of the patch(es). The property `cycle` is used by default. {_aliases_note("fc", "facecolors", "fillcolor", "fillcolors")}
+ The face color of the patch(es). The property `cycle` is used by default.
alpha : float, optional
- The opacity of the patch(es). Inferred from `facecolor` and `edgecolor` by default. {_aliases_note("a", "alphas")}
+ The opacity of the patch(es). Inferred from `facecolor` and `edgecolor` by default.
"""
-_pcolor_collection_docstring = f"""
+_pcolor_collection_docstring = """
linewidths : unit-spec, default: 0.3
- The width of lines between grid boxes. {_aliases_note("lw", "linewidth")}
+ The width of lines between grid boxes.
%(units.pt)s
linestyles : str, default: '-'
- The style of lines between grid boxes. {_aliases_note("ls", "linestyle")}
+ The style of lines between grid boxes.
edgecolors : color-spec, default: 'k'
- The color of lines between grid boxes. {_aliases_note("ec", "edgecolor")}
+ The color of lines between grid boxes.
alpha : float, optional
- The opacity of the grid boxes. Inferred from `cmap` by default. {_aliases_note("a", "alphas")}
+ The opacity of the grid boxes. Inferred from `cmap` by default.
"""
-_contour_collection_docstring = f"""
+_contour_collection_docstring = """
linewidths : unit-spec, default: 0.3 or :rc:`lines.linewidth`
The width of the line contours. Default is ``0.3`` when adding to filled contours
- or :rc:`lines.linewidth` otherwise. {_aliases_note("lw", "linewidth")} %(units.pt)s
+ or :rc:`lines.linewidth` otherwise. %(units.pt)s
linestyles : str, default: '-' or :rc:`contour.negative_linestyle`
The style of the line contours. Default is ``'-'`` for positive contours and
- :rcraw:`contour.negative_linestyle` for negative contours. {_aliases_note("ls", "linestyle")}
+ :rcraw:`contour.negative_linestyle` for negative contours.
edgecolors : color-spec, default: 'k' or inferred
The color of the line contours. Default is ``'k'`` when adding to filled contours
- or inferred from `color` or `cmap` otherwise. {_aliases_note("ec", "edgecolor")}
+ or inferred from `color` or `cmap` otherwise.
alpha : float, optional
- The opacity of the contours. Inferred from `edgecolors` by default. {_aliases_note("a", "alphas")}
+ The opacity of the contours. Inferred from `edgecolors` by default.
"""
-_text_docstring = f"""
+_text_docstring = """
fontfamily : str, optional
The font typeface name (e.g., ``'Fira Math'``) or font family name (e.g.,
- ``'serif'``). Matplotlib falls back to the system default if not found. {_aliases_note("family", "name", "fontname")}
+ ``'serif'``). Matplotlib falls back to the system default if not found.
fontsize : unit-spec or str, optional
- The font size. {_aliases_note("size")} %(units.pt)s
+ The font size. %(units.pt)s
This can also be a string indicating some scaling relative to
:rcraw:`font.size`. The sizes and scalings are shown below. The
scalings ``'med'``, ``'med-small'``, and ``'med-large'`` are
diff --git a/ultraplot/internals/guides.py b/ultraplot/internals/guides.py
index 5b396791d..54a699017 100644
--- a/ultraplot/internals/guides.py
+++ b/ultraplot/internals/guides.py
@@ -11,6 +11,7 @@
from . import ic # noqa: F401
from . import warnings
+from .kwargs import _alias_registry
# Global constants
REMOVE_AFTER_FLUSH = (
@@ -22,10 +23,10 @@
"align",
"queue",
)
-GUIDE_ALIASES = (
- ("title", "label"),
- ("locator", "ticks"),
- ("format", "formatter", "ticklabels"),
+GUIDE_ALIASES = tuple(
+ (canonical, *aliases)
+ for scope in ("colorbar", "legend")
+ for canonical, aliases in _alias_registry[scope].items()
)
diff --git a/ultraplot/internals/inputs.py b/ultraplot/internals/inputs.py
index e3dd461b6..12eaef56e 100644
--- a/ultraplot/internals/inputs.py
+++ b/ultraplot/internals/inputs.py
@@ -11,6 +11,7 @@
from . import ic # noqa: F401
from . import _not_none, warnings
+from .kwargs import _alias_kwargs
try:
from cartopy.crs import PlateCarree
@@ -549,14 +550,13 @@ def _dist_clean(distribution):
raise ValueError("Input must be a numpy array or a list of lists")
-def _dist_reduce(data, *, mean=None, means=None, median=None, medians=None, **kwargs):
+@_alias_kwargs("plot.statistics")
+def _dist_reduce(data, *, means=None, medians=None, **kwargs):
"""
Reduce statistical distributions to means and medians. Tack on a
distribution keyword argument for processing down the line.
"""
# TODO: Permit 3D array with error dimension coming first
- means = _not_none(mean=mean, means=means)
- medians = _not_none(median=median, medians=medians)
if means and medians:
warnings._warn_ultraplot(
"Cannot have both means=True and medians=True. Using former."
diff --git a/ultraplot/internals/kwargs.py b/ultraplot/internals/kwargs.py
index 82396b837..160f2d75f 100644
--- a/ultraplot/internals/kwargs.py
+++ b/ultraplot/internals/kwargs.py
@@ -16,6 +16,11 @@
__all__ = [
"_not_none",
"_alias_kwargs",
+ "_alias_registry",
+ "_canonicalize_kwargs",
+ "_format_alias_reference",
+ "_figure_format_alias_scopes",
+ "_format_alias_scopes",
"_alias_maps",
"_get_aliases",
"_kwargs_to_args",
@@ -25,6 +30,325 @@
]
+# Compatibility aliases are deliberately kept outside function signatures. The
+# first-level keys describe the API context because the same shorthand can map to
+# different canonical Matplotlib names (for example ``lw`` for a Line2D versus a
+# Collection). This is data, rather than decorator arguments scattered throughout
+# the package, so it can also drive the migration reference and error messages.
+_alias_registry = {
+ "figure.init": {
+ "refnum": ("ref",),
+ "refaspect": ("aspect",),
+ "refwidth": ("axwidth",),
+ "refheight": ("axheight",),
+ "figwidth": ("width",),
+ "figheight": ("height",),
+ },
+ "axes.format": {
+ "lefttitle": ("ltitle",),
+ "centertitle": ("ctitle",),
+ "righttitle": ("rtitle",),
+ "upperlefttitle": ("ultitle",),
+ "uppercentertitle": ("uctitle",),
+ "upperrighttitle": ("urtitle",),
+ "lowerlefttitle": ("lltitle",),
+ "lowercentertitle": ("lctitle",),
+ "lowerrighttitle": ("lrtitle",),
+ },
+ "cartesian.format": {
+ "xspineloc": ("xloc",),
+ "yspineloc": ("yloc",),
+ "xformatter": ("xticklabels",),
+ "yformatter": ("yticklabels",),
+ "xlocator": ("xticks",),
+ "ylocator": ("yticks",),
+ "xminorlocator": ("xminorticks",),
+ "yminorlocator": ("yminorticks",),
+ },
+ "geo.format": {
+ "lonlocator": ("lonlines",),
+ "latlocator": ("latlines",),
+ "lonminorlocator": ("lonminorlines",),
+ "latminorlocator": ("latminorlines",),
+ "lonlocator_kw": ("lonlines_kw",),
+ "latlocator_kw": ("latlines_kw",),
+ "lonminorlocator_kw": ("lonminorlines_kw",),
+ "latminorlocator_kw": ("latminorlines_kw",),
+ },
+ "polar.format": {
+ "thetalocator": ("thetalines",),
+ "rlocator": ("rlines",),
+ "thetaminorlocator": ("thetaminorlines",),
+ "rminorlocator": ("rminorlines",),
+ "thetaformatter": ("thetalabels",),
+ "rformatter": ("rlabels",),
+ },
+ "taylor.format": {
+ "corrlocator": ("corrlines", "corrticks"),
+ },
+ "figure.format": {
+ "suptitle": ("figtitle",),
+ "leftlabels": ("llabels", "rowlabels"),
+ "rightlabels": ("rlabels",),
+ "bottomlabels": ("blabels",),
+ "toplabels": ("tlabels", "collabels"),
+ },
+ "colorbar": {
+ "loc": ("location",),
+ "drawedges": ("grid", "edges"),
+ "length": ("shrink",),
+ "label": ("title",),
+ "labellocation": ("labelloc",),
+ "ticks": ("locator",),
+ "format": ("formatter", "ticklabels"),
+ "minorticks": ("minorlocator",),
+ "color": ("c",),
+ "linewidth": ("lw",),
+ "tickdirection": ("tickdir",),
+ "frameon": ("frame",),
+ },
+ "legend": {
+ "loc": ("location",),
+ "ncols": ("ncol",),
+ "frameon": ("frame",),
+ },
+ "gridspec": {
+ "width_ratios": ("wratios",),
+ "height_ratios": ("hratios",),
+ },
+ "subplot": {
+ "projection": ("proj",),
+ "projection_kw": ("proj_kw",),
+ },
+ "inset": {
+ "projection": ("proj",),
+ },
+ "cycle": {
+ "samples": ("N",),
+ },
+ "projection": {
+ "lon0": ("lon_0",),
+ "lat0": ("lat_0",),
+ },
+ "scale.log": {
+ "base": ("basex", "basey"),
+ "nonpos": ("nonposx", "nonposy"),
+ "subs": ("subsx", "subsy"),
+ },
+ "scale.symlog": {
+ "base": ("basex", "basey"),
+ "linthresh": ("linthreshx", "linthreshy"),
+ "linscale": ("linscalex", "linscaley"),
+ "subs": ("subsx", "subsy"),
+ },
+ "plot.labels": {
+ "formatter": ("fmt",),
+ },
+ "plot.text": {
+ "color": ("c", "colors"),
+ "fontsize": ("size",),
+ },
+ "plot.contour_labels": {
+ "colors": ("c", "color"),
+ "fontsize": ("size",),
+ },
+ "plot.error_bars": {
+ "barstds": ("bars", "barstd"),
+ "barpctiles": ("barpctile",),
+ "boxstds": ("boxes", "boxstd"),
+ "boxpctiles": ("boxpctile",),
+ },
+ "plot.error_shading": {
+ "shadestds": ("shade", "shadestd"),
+ "shadepctiles": ("shadepctile",),
+ "fadestds": ("fade", "fadestd"),
+ "fadepctiles": ("fadepctile",),
+ },
+ "plot.colormap": {
+ "colors": ("c", "color"),
+ },
+ "plot.levels": {
+ "levels": ("N",),
+ },
+ "plot.stacked": {
+ "stacked": ("stack",),
+ },
+ "plot.statistics": {
+ "means": ("mean",),
+ "medians": ("median",),
+ },
+ "plot.boxplot": {
+ "means": ("showmeans",),
+ "fill": ("filled",),
+ },
+ "plot.violinplot": {
+ "means": ("showmeans",),
+ "medians": ("showmedians",),
+ },
+ "plot.hist": {
+ "rwidth": ("width",),
+ "stacked": ("stack",),
+ "fill": ("filled",),
+ },
+ "plot.pie": {
+ "labeldistance": ("labelpad",),
+ },
+}
+
+_format_alias_scopes = (
+ "axes.format",
+ "cartesian.format",
+ "geo.format",
+ "polar.format",
+ "taylor.format",
+)
+_figure_format_alias_scopes = ("figure.format", *_format_alias_scopes)
+
+
+def _get_alias_groups(scope=None, aliases=None):
+ """Return validated canonical-to-legacy alias groups."""
+ if scope is not None and aliases:
+ raise TypeError("Pass an alias registry scope or inline aliases, not both.")
+ if scope is None:
+ groups = aliases or {}
+ else:
+ try:
+ groups = _alias_registry[scope]
+ except KeyError:
+ raise KeyError(f"Unknown alias registry scope {scope!r}.") from None
+ return {
+ canonical: (legacy,) if isinstance(legacy, str) else tuple(legacy)
+ for canonical, legacy in groups.items()
+ }
+
+
+def _canonicalize_kwargs(scope, kwargs, *, aliases=None, provided=(), warn=False):
+ """
+ Return a copy of *kwargs* with legacy names translated to canonical names.
+
+ Only explicitly registered spellings are translated. Supplying two spellings
+ for one parameter raises ``TypeError``, matching Matplotlib's alias handling.
+ The input mapping is never mutated. Translation is intentionally silent during
+ the compatibility stage; a later deprecation can opt into warnings with
+ ``warn=True`` without changing call signatures or registry data.
+ """
+ if isinstance(scope, (tuple, list)):
+ if aliases:
+ raise TypeError("Inline aliases cannot be combined with multiple scopes.")
+ output = dict(kwargs)
+ for item in scope:
+ output = _canonicalize_kwargs(item, output, provided=provided, warn=warn)
+ return output
+ groups = _get_alias_groups(scope, aliases)
+ lookup = {
+ legacy: canonical
+ for canonical, legacy_names in groups.items()
+ for legacy in legacy_names
+ }
+ output = dict(kwargs)
+ explicit = output.get("_explicit_format_keys")
+ if explicit is not None:
+ output["_explicit_format_keys"] = {lookup.get(name, name) for name in explicit}
+ seen = {
+ canonical: canonical
+ for canonical in groups
+ if output.get(canonical) is not None
+ }
+ seen.update(
+ {
+ canonical: canonical
+ for canonical in provided
+ if canonical in groups and provided[canonical] is not None
+ }
+ )
+ # Validate the entire call before translating anything so invalid calls do
+ # not emit a partial sequence of migration warnings.
+ for legacy, value in output.items():
+ canonical = lookup.get(legacy)
+ if canonical is None or value is None:
+ continue
+ if canonical in seen:
+ raise TypeError(
+ f"Got both {seen[canonical]!r} and {legacy!r}, which are aliases "
+ f"for {canonical!r}."
+ )
+ seen[canonical] = legacy
+ for legacy in tuple(output):
+ canonical = lookup.get(legacy)
+ if canonical is None:
+ continue
+ value = output.pop(legacy)
+ # ``None`` is how pyplot and UltraPlot wrappers forward unspecified
+ # options. Treat it as absent so it neither warns nor shadows defaults.
+ if value is None:
+ continue
+ output[canonical] = value
+ if warn:
+ warnings._warn_ultraplot(
+ f"Keyword {legacy!r} is deprecated; use {canonical!r} instead."
+ )
+ return output
+
+
+def _format_alias_table(rows):
+ """Format alias rows as a simple RST table."""
+ rows = [("Context", "Accepted spelling", "Canonical spelling"), *rows]
+ widths = [max(len(row[idx]) for row in rows) for idx in range(3)]
+ rule = " ".join("=" * width for width in widths)
+ lines = [rule]
+ for idx, row in enumerate(rows):
+ lines.append(
+ " ".join(value.ljust(widths[col]) for col, value in enumerate(row)).rstrip()
+ )
+ if idx == 0:
+ lines.append(rule)
+ lines.append(rule)
+ return "\n".join(lines)
+
+
+def _format_alias_reference():
+ """Return the registered compatibility aliases as grouped RST tables."""
+ function_rows = []
+ style_rows = []
+ for scope, groups in _alias_registry.items():
+ target = style_rows if scope.startswith("style.") else function_rows
+ for canonical, aliases in groups.items():
+ target.extend((scope, legacy, canonical) for legacy in aliases)
+
+ # Dotted rc names cannot be Python identifiers, so UltraPlot historically
+ # accepted dotless spellings in ``format()`` kwargs. Generate these mappings
+ # from the rc registry instead of copying hundreds of entries by hand.
+ from . import rcsetup
+
+ rc_rows = [
+ ("rc (dotless)", legacy, canonical)
+ for legacy, canonical in sorted(rcsetup._rc_nodots.items())
+ if legacy != canonical
+ ]
+ sections = (
+ (
+ "Function keyword aliases",
+ "These mappings apply only in the listed call context.",
+ function_rows,
+ ),
+ (
+ "Artist property aliases",
+ "These are Matplotlib-style shorthand properties accepted while styling artists.",
+ style_rows,
+ ),
+ (
+ "Dotless rc aliases",
+ "Use the dotted canonical spelling through ``rc_kw`` when avoiding the accepted shorthand.",
+ rc_rows,
+ ),
+ )
+ rendered = [
+ f"{title}\n{'-' * len(title)}\n\n{description}\n\n{_format_alias_table(rows)}"
+ for title, description, rows in sections
+ ]
+ return "\n\n".join(rendered)
+
+
def _not_none(*args, default=None, **kwargs):
"""
Return the first non-``None`` value. This is used with keyword arg aliases and
@@ -52,45 +376,49 @@ def _not_none(*args, default=None, **kwargs):
return first
-def _alias_kwargs(**aliases):
+def _alias_kwargs(scope=None, **aliases):
"""
Fold keyword-argument aliases into their canonical names before a call.
- Each keyword maps a canonical parameter name to a tuple of accepted synonyms,
- e.g. ``@_alias_kwargs(figwidth=("width",), refnum=("ref",))``. A synonym passed
- by the caller is renamed to its canonical name. Passing a canonical together
- with a synonym (or two synonyms) warns and keeps the canonical / first value,
- matching the precedence and warning of `_not_none`. This replaces the repetitive
- ``x = _not_none(x=x, y=y)`` boilerplate at the top of aliased functions.
+ Pass a registry scope, e.g. ``@_alias_kwargs("figure.init")``. Inline mappings
+ remain available for small private helpers, but public compatibility aliases
+ should live in `_alias_registry` so they can be documented and audited.
- This handles keyword aliases only: a canonical argument passed *positionally*
- is not deduplicated against its synonyms, and a synonym must not shadow a
+ This handles keyword aliases only. Canonical arguments passed positionally
+ are included in duplicate detection, and a synonym must not shadow a
different real parameter of the wrapped function.
"""
- # Map each synonym to its canonical name; synonyms are tried in declared order
- # so the first non-``None`` one wins, exactly like `_not_none`.
- lookup = {syn: canon for canon, syns in aliases.items() for syn in syns}
+ if isinstance(scope, (tuple, list)):
+ if aliases:
+ raise TypeError("Inline aliases cannot be combined with multiple scopes.")
+ groups = {}
+ for item in scope:
+ groups.update(_get_alias_groups(item))
+ else:
+ groups = _get_alias_groups(scope, aliases)
def decorator(func):
+ signature = inspect.signature(func)
+
@functools.wraps(func)
def wrapper(*args, **kwargs):
- for syn, canon in lookup.items():
- if syn not in kwargs:
- continue
- value = kwargs.pop(syn)
- if value is None:
- continue
- if kwargs.get(canon) is None:
- kwargs[canon] = value
- else:
- # ``canon`` already holds an earlier value (from the canonical
- # keyword or a prior synonym); keep it and drop this synonym.
- warnings._warn_ultraplot(
- f"Got conflicting or duplicate values for {canon!r} "
- f"(ignoring alias {syn!r}). Using the first value."
- )
+ # Bind positional arguments separately so ``func(value, alias=value)``
+ # is diagnosed before the translated call reaches Python's binder.
+ provided = signature.bind_partial(*args).arguments
+ kwargs = _canonicalize_kwargs(
+ scope,
+ kwargs,
+ aliases=groups if scope is None else None,
+ provided=provided,
+ )
return func(*args, **kwargs)
+ wrapper._ultraplot_alias_scopes = (
+ tuple(scope)
+ if isinstance(scope, (tuple, list))
+ else (scope,) if scope is not None else ()
+ )
+ wrapper._ultraplot_aliases = dict(groups)
return wrapper
return decorator
@@ -200,6 +528,14 @@ def wrapper(*args, **kwargs):
},
}
+# Style aliases are consumed by ``_pop_props`` rather than decorators, but they
+# belong to the same context-aware compatibility registry. Keeping the legacy
+# ``_alias_maps`` name as a view avoids a broad internal migration while making
+# the complete public mapping discoverable and documentable in one place.
+_alias_registry.update(
+ {f"style.{category}": groups for category, groups in _alias_maps.items()}
+)
+
_INTERNAL_POP_PARAMS = frozenset(
{
diff --git a/ultraplot/legend.py b/ultraplot/legend.py
index e262cb405..b5c51dcde 100644
--- a/ultraplot/legend.py
+++ b/ultraplot/legend.py
@@ -16,7 +16,15 @@
from matplotlib.markers import MarkerStyle
from .config import rc
-from .internals import _not_none, _pop_props, docstring, guides, inputs, rcsetup
+from .internals import (
+ _alias_kwargs,
+ _not_none,
+ _pop_props,
+ docstring,
+ guides,
+ inputs,
+ rcsetup,
+)
from .utils import _fontsize_to_pt, units
try:
@@ -2247,6 +2255,7 @@ def _align_map() -> dict[Optional[str], dict[str, str]]:
"""
return ALIGN_OPTS
+ @_alias_kwargs("legend")
def _resolve_inputs(
self,
handles=None,
@@ -2257,9 +2266,7 @@ def _resolve_inputs(
width=None,
pad=None,
space=None,
- frame=None,
frameon=None,
- ncol=None,
ncols=None,
alphabetize=False,
center=None,
@@ -2284,9 +2291,9 @@ def _resolve_inputs(
"""
Normalize inputs, apply rc defaults, and convert units.
"""
- ncol = _not_none(ncols=ncols, ncol=ncol)
+ ncol = ncols
order = _not_none(order, "C")
- frameon = _not_none(frame=frame, frameon=frameon, default=rc["legend.frameon"])
+ frameon = _not_none(frameon, default=rc["legend.frameon"])
fontsize = _not_none(fontsize, rc["legend.fontsize"])
titlefontsize = _not_none(
title_fontsize=kwargs.pop("title_fontsize", None),
@@ -2468,6 +2475,7 @@ def _finalize(self, objs, *, loc, align):
ax._register_guide("legend", obj, (loc, align))
return obj
+ @_alias_kwargs("legend")
def add(
self,
handles=None,
@@ -2478,9 +2486,7 @@ def add(
width=None,
pad=None,
space=None,
- frame=None,
frameon=None,
- ncol=None,
ncols=None,
alphabetize=False,
center=None,
@@ -2513,9 +2519,7 @@ def add(
width=width,
pad=pad,
space=space,
- frame=frame,
frameon=frameon,
- ncol=ncol,
ncols=ncols,
alphabetize=alphabetize,
center=center,
diff --git a/ultraplot/scale.py b/ultraplot/scale.py
index 21161cda5..970473f5a 100644
--- a/ultraplot/scale.py
+++ b/ultraplot/scale.py
@@ -13,6 +13,7 @@
from . import ticker as pticker
from .internals import (
+ _canonicalize_kwargs,
_not_none,
_version_mpl,
ic, # noqa: F401
@@ -43,15 +44,11 @@ def _parse_logscale_args(*keys, **kwargs):
# NOTE: Scale classes ignore unused arguments with warnings, but matplotlib 3.3
# version changes the keyword args. Since we can't do a try except clause, only
# way to avoid warnings with 3.3 upgrade is to test version string.
+ scope = "scale.symlog" if "linthresh" in keys else "scale.log"
+ kwargs = _canonicalize_kwargs(scope, kwargs)
kwsuffix = "" if _version_mpl >= "3.3" else "x"
for key in keys:
- # Remove duplicates
- opts = {
- key: kwargs.pop(key, None),
- key + "x": kwargs.pop(key + "x", None),
- key + "y": kwargs.pop(key + "y", None),
- }
- value = _not_none(**opts) # issues warning if multiple values passed
+ value = kwargs.pop(key, None)
# Apply defaults and adjust
# NOTE: If linthresh is *exactly* on a power of the base, can end
@@ -207,10 +204,6 @@ def __init__(self, **kwargs):
Default *minor* tick locations are on these multiples of each power
of the base. For example, ``subs=(1, 2, 5)`` draws ticks on 1, 2,
5, 10, 20, 50, 100, 200, 500, etc.
- basex, basey, nonposx, nonposy, subsx, subsy
- Aliases for the above keywords. These used to be conditional
- on the *name* of the axis.
-
See also
--------
ultraplot.constructor.Scale
@@ -252,10 +245,6 @@ def __init__(self, **kwargs):
Default *minor* tick locations are on these multiples of each power
of the base. For example, ``subs=(1, 2, 5)`` draws ticks on 1, 2,
5, 10, 20, 50, 100, 200, 500, etc.
- basex, basey, linthreshx, linthreshy, linscalex, linscaley, subsx, subsy
- Aliases for the above keywords. These keywords used to be
- conditional on the name of the axis.
-
See also
--------
ultraplot.constructor.Scale
diff --git a/ultraplot/tests/test_alias_signatures.py b/ultraplot/tests/test_alias_signatures.py
new file mode 100644
index 000000000..2b99c488c
--- /dev/null
+++ b/ultraplot/tests/test_alias_signatures.py
@@ -0,0 +1,157 @@
+"""Regression tests for canonical public call signatures."""
+
+import inspect
+
+import numpy as np
+
+import ultraplot as uplt
+from ultraplot.axes.base import Axes
+from ultraplot.axes.cartesian import CartesianAxes
+from ultraplot.axes.geo import GeoAxes
+from ultraplot.axes.plot import PlotAxes
+from ultraplot.axes.polar import PolarAxes
+from ultraplot.axes.taylor import TaylorAxes
+from ultraplot._subplots import SubplotManager
+from ultraplot.colorbar import UltraColorbar
+from ultraplot.constructor import Cycle, Proj
+from ultraplot.figure import Figure
+from ultraplot.gridspec import GridSpec
+from ultraplot.legend import UltraLegend
+from ultraplot.ultralayout import (
+ UltraLayoutSolver,
+ compute_ultra_positions,
+ get_grid_positions_ultra,
+)
+
+
+def _parameter_names(signature):
+ return {
+ name
+ for name, parameter in signature.parameters.items()
+ if name != "self"
+ and parameter.kind not in (parameter.VAR_POSITIONAL, parameter.VAR_KEYWORD)
+ }
+
+
+def test_format_signatures_contain_only_canonical_names() -> None:
+ aliases = {
+ Axes: {"ltitle", "ctitle", "rtitle"},
+ CartesianAxes: {"xloc", "xticks", "xticklabels", "xminorticks"},
+ GeoAxes: {"lonlines", "latlines", "lonlines_kw", "latlines_kw"},
+ PolarAxes: {"thetalines", "rlines", "thetalabels", "rlabels"},
+ TaylorAxes: {"corrlines", "corrticks"},
+ }
+ for cls, legacy_names in aliases.items():
+ names = _parameter_names(Axes._format_signatures[cls])
+ assert names.isdisjoint(legacy_names)
+
+ names = _parameter_names(Figure._format_signature)
+ assert names.isdisjoint({"figtitle", "llabels", "rowlabels", "collabels"})
+
+
+def test_largest_migrated_signature_has_thirteen_fewer_parameters() -> None:
+ names = _parameter_names(inspect.signature(UltraColorbar.add))
+ assert "length" in names
+ assert len(names) == 47
+ assert names.isdisjoint(
+ {
+ "shrink",
+ "title",
+ "grid",
+ "edges",
+ "locator",
+ "formatter",
+ "ticklabels",
+ "minorlocator",
+ "tickdir",
+ "labelloc",
+ "c",
+ "lw",
+ "frame",
+ }
+ )
+
+
+def test_figure_init_signature_contains_only_canonical_names() -> None:
+ names = _parameter_names(inspect.signature(inspect.unwrap(Figure.__init__)))
+ assert len(names) == 36
+ assert names.isdisjoint({"ref", "aspect", "axwidth", "axheight", "width", "height"})
+
+
+def test_layout_and_projection_signatures_contain_only_canonical_names() -> None:
+ cases = (
+ (SubplotManager.parse_proj, {"proj", "proj_kw"}),
+ (SubplotManager.add_subplots, {"proj", "proj_kw"}),
+ (GridSpec._update_params, {"wratios", "hratios"}),
+ (UltraLayoutSolver.__init__, {"wratios", "hratios"}),
+ (compute_ultra_positions, {"wratios", "hratios"}),
+ (get_grid_positions_ultra, {"wratios", "hratios"}),
+ (Cycle.__init__, {"N"}),
+ (Proj, {"lon_0", "lat_0"}),
+ (Axes._add_inset_axes, {"proj"}),
+ )
+ for function, legacy_names in cases:
+ names = _parameter_names(inspect.signature(function))
+ assert names.isdisjoint(legacy_names)
+
+
+def test_guide_signatures_contain_only_canonical_names() -> None:
+ cases = (
+ (Axes.colorbar, {"location", "shrink"}),
+ (Axes._add_colorbar, {"shrink"}),
+ (Axes.legend, {"location"}),
+ (Figure.colorbar, {"location", "shrink"}),
+ (Figure.legend, {"location"}),
+ (Axes._add_legend, {"ncol", "frame"}),
+ (UltraLegend.add, {"location", "ncol", "frame"}),
+ (UltraLegend._resolve_inputs, {"ncol", "frame"}),
+ )
+ for function, legacy_names in cases:
+ signature = inspect.signature(inspect.unwrap(function))
+ assert _parameter_names(signature).isdisjoint(legacy_names)
+
+
+def test_plotting_helper_signatures_contain_only_canonical_names() -> None:
+ cases = (
+ (PlotAxes._add_auto_labels, {"fmt"}),
+ (PlotAxes._add_quadmesh_labels, {"c", "colors", "size"}),
+ (PlotAxes._add_collection_labels, {"c", "colors", "size"}),
+ (PlotAxes._add_contour_labels, {"c", "color", "size"}),
+ (
+ PlotAxes._add_error_bars,
+ {"bars", "barstd", "barpctile", "boxes", "boxstd", "boxpctile"},
+ ),
+ (
+ PlotAxes._add_error_shading,
+ {"shade", "shadestd", "shadepctile", "fade", "fadestd", "fadepctile"},
+ ),
+ (PlotAxes._parse_cmap, {"c", "color"}),
+ (PlotAxes._parse_level_vals, {"N"}),
+ (PlotAxes._apply_lines, {"stack"}),
+ (PlotAxes._apply_fill, {"stack"}),
+ (PlotAxes._apply_bar, {"stack"}),
+ (PlotAxes._apply_boxplot, {"mean", "showmeans", "filled"}),
+ (
+ PlotAxes._apply_violinplot,
+ {"mean", "median", "showmeans", "showmedians"},
+ ),
+ (PlotAxes._apply_hist, {"width", "stack", "filled"}),
+ (inspect.unwrap(PlotAxes.pie), {"labelpad"}),
+ )
+ for function, legacy_names in cases:
+ names = _parameter_names(inspect.signature(function))
+ assert names.isdisjoint(legacy_names)
+
+
+def test_level_alias_is_consumed_before_native_plot_call() -> None:
+ """The legacy ``N`` spelling must not leak into Matplotlib artist kwargs."""
+ _, ax = uplt.subplots()
+ mesh = ax.heatmap(np.arange(4).reshape(2, 2), N=5)
+ assert mesh is not None
+
+
+def test_format_alias_is_consumed_before_twin_axes_init() -> None:
+ """A twin's legacy spine location must override its canonical default."""
+ _, ax = uplt.subplots()
+ twin = ax.twiny(xloc="bottom", ticks=2.5)
+ assert twin is not None
diff --git a/ultraplot/tests/test_colorbar.py b/ultraplot/tests/test_colorbar.py
index 0eb7720ee..f7739b832 100644
--- a/ultraplot/tests/test_colorbar.py
+++ b/ultraplot/tests/test_colorbar.py
@@ -10,6 +10,40 @@
import ultraplot as uplt
+@pytest.mark.parametrize("placement", ["outer", "inset", "inset_side", "figure"])
+def test_colorbar_shrink_alias_matches_length(placement):
+ bounds = []
+ for keyword in ("length", "shrink"):
+ fig, ax = uplt.subplots()
+ if placement == "inset_side":
+ ax = ax.inset_axes([0.2, 0.2, 0.5, 0.5])
+ target = fig if placement == "figure" else ax
+ loc = "upper right" if placement == "inset" else "right"
+ length = "4em" if placement == "inset" else 0.4
+ cb = target.colorbar("magma", loc=loc, **{keyword: length})
+ fig.canvas.draw()
+ bounds.append(cb.ax.get_position().bounds)
+ np.testing.assert_allclose(bounds[0], bounds[1])
+
+
+@pytest.mark.parametrize("keyword", ["length", "shrink"])
+def test_colorbar_length_with_explicit_cax(keyword):
+ fig, ax = uplt.subplots()
+ mappable = ax.imshow(np.arange(4).reshape(2, 2))
+ cax = fig.add_axes([0.85, 0.2, 0.05, 0.6])
+ cb = fig.colorbar(mappable, cax=cax, **{keyword: 0.4})
+ assert cb.ax is cax
+ fig.canvas.draw()
+
+
+@pytest.mark.parametrize("target", ["axes", "figure"])
+def test_colorbar_length_and_shrink_conflict(target):
+ fig, ax = uplt.subplots()
+ target = fig if target == "figure" else ax
+ with pytest.raises(TypeError, match="aliases.*length"):
+ target.colorbar("magma", length=0.4, shrink=0.5)
+
+
def test_colorbar_defers_external_mode():
"""
External mode should defer on-the-fly colorbar creation until explicitly requested.
diff --git a/ultraplot/tests/test_docstring_helpers.py b/ultraplot/tests/test_docstring_helpers.py
index 17d826d49..83afd73a0 100644
--- a/ultraplot/tests/test_docstring_helpers.py
+++ b/ultraplot/tests/test_docstring_helpers.py
@@ -5,33 +5,29 @@
def test_style_snippets_lead_with_canonical_name() -> None:
- # The shared style fields should lead with the canonical parameter name and
- # relegate synonyms to a trailing "Aliases:" note, rather than opening the
- # numpydoc field with a pile of alias names.
+ # Shared style fields only show canonical names. Compatibility spellings are
+ # documented centrally in docs/aliases.rst.
line = docstring._snippet_manager["artist.line"]
assert line.lstrip().startswith("linewidth : unit-spec")
- assert "Aliases: ``lw``, ``linewidths``." in line
assert "The color of the line(s)" in line
- assert "Aliases: ``c``, ``colors``." in line
+ assert "Aliases:" not in line
# The old alias-pile header must be gone.
assert "lw, linewidth, linewidths :" not in line
-def test_collection_snippets_lead_with_registry_canonical_names() -> None:
+def test_collection_snippets_use_only_registry_canonical_names() -> None:
for name in ("artist.collection_pcolor", "artist.collection_contour"):
snippet = docstring._snippet_manager[name]
assert snippet.lstrip().startswith("linewidths : unit-spec")
assert "\nlinestyles : str" in snippet
assert "\nedgecolors : color-spec" in snippet
- assert "Aliases: ``lw``, ``linewidth``." in snippet
- assert "Aliases: ``ls``, ``linestyle``." in snippet
- assert "Aliases: ``ec``, ``edgecolor``." in snippet
+ assert "Aliases:" not in snippet
def test_contour_alpha_alias_typo_fixed() -> None:
# Previously the contour snippet listed ``a, alpha, alpha`` (duplicate typo).
contour = docstring._snippet_manager["artist.collection_contour"]
- assert "``a``, ``alphas``." in contour
+ assert "Aliases:" not in contour
assert "a, alpha, alpha" not in contour
@@ -47,17 +43,15 @@ def test_method_docstring_fully_substituted() -> None:
# leftover unfilled snippet markers.
doc = uplt.axes.PlotAxes.line.__doc__ or ""
assert "linewidth : unit-spec" in doc
- assert "Aliases: ``lw``" in doc
+ assert "Aliases:" not in doc
assert "%(artist" not in doc
-def test_geo_format_folds_alias_entries() -> None:
- # The geo format docstring folded its standalone "Aliases for ..." blocks
- # into trailing notes on the canonical locator entries.
+def test_geo_format_uses_only_canonical_entries() -> None:
+ # Compatibility spellings live in the generated alias reference instead of
+ # competing with canonical parameters in each function's primary docs.
geo = docstring._snippet_manager["geo.format"]
assert "Aliases for" not in geo
assert "lonlocator, latlocator : locator-spec" in geo
- assert "Aliases: ``lonlines`` and ``latlines``, respectively." in geo
- assert (
- "Aliases: ``lonminorlines_kw`` and ``latminorlines_kw``, respectively." in geo
- )
+ assert "lonlines" not in geo
+ assert "latlines" not in geo
diff --git a/ultraplot/tests/test_figure.py b/ultraplot/tests/test_figure.py
index 08257045d..a935cb7ee 100644
--- a/ultraplot/tests/test_figure.py
+++ b/ultraplot/tests/test_figure.py
@@ -943,13 +943,9 @@ def test_figure_keyword_aliases() -> None:
uplt.close("all")
-def test_figure_alias_conflict_warns() -> None:
- with warnings.catch_warnings(record=True) as record:
- warnings.simplefilter("always")
- fig = uplt.figure(refnum=1, ref=5)
- assert fig._refnum == 1 # canonical wins
- assert any("conflicting" in str(w.message).lower() for w in record)
- uplt.close(fig)
+def test_figure_alias_conflict_raises() -> None:
+ with pytest.raises(TypeError, match="aliases"):
+ uplt.figure(refnum=1, ref=5)
def test_clear_drops_subplot_state():
diff --git a/ultraplot/tests/test_kwargs_helpers.py b/ultraplot/tests/test_kwargs_helpers.py
index 853131d78..297d491c2 100644
--- a/ultraplot/tests/test_kwargs_helpers.py
+++ b/ultraplot/tests/test_kwargs_helpers.py
@@ -2,7 +2,10 @@
import warnings
+import pytest
+
from ultraplot import internals
+from ultraplot.internals import guides
from ultraplot.internals import kwargs as ikwargs
@@ -12,6 +15,11 @@ def test_kwargs_helpers_reexported_from_package() -> None:
for name in (
"_not_none",
"_alias_kwargs",
+ "_alias_registry",
+ "_canonicalize_kwargs",
+ "_format_alias_reference",
+ "_figure_format_alias_scopes",
+ "_format_alias_scopes",
"_alias_maps",
"_get_aliases",
"_kwargs_to_args",
@@ -56,25 +64,84 @@ def func(*, figwidth=42):
assert func(width=None) == 42
-def test_alias_kwargs_conflict_keeps_canonical_and_warns() -> None:
+def test_alias_kwargs_conflict_raises() -> None:
@ikwargs._alias_kwargs(figwidth=("width",))
def func(*, figwidth=None):
return figwidth
- with warnings.catch_warnings(record=True) as record:
- warnings.simplefilter("always")
- value = func(figwidth=1, width=2)
- assert value == 1 # canonical wins, matching _not_none precedence
- assert any("conflicting" in str(w.message).lower() for w in record)
+ with pytest.raises(TypeError, match="aliases"):
+ func(figwidth=1, width=2)
-def test_alias_kwargs_multiple_synonyms_first_wins() -> None:
+def test_alias_kwargs_multiple_synonyms_raise() -> None:
@ikwargs._alias_kwargs(saturation=("s", "c", "chroma"))
def func(*, saturation=None):
return saturation
assert func(chroma=0.5) == 0.5
- with warnings.catch_warnings(record=True):
- warnings.simplefilter("always")
- # Two synonyms: the first one encountered in declaration order wins.
- assert func(s=0.1, chroma=0.9) == 0.1
+ with pytest.raises(TypeError, match="aliases"):
+ func(s=0.1, chroma=0.9)
+
+
+def test_registry_scope_translates_without_mutating_input() -> None:
+ kwargs = {"xticks": [1, 2], "color": "red"}
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ result = ikwargs._canonicalize_kwargs("cartesian.format", kwargs)
+ assert kwargs == {"xticks": [1, 2], "color": "red"}
+ assert result == {"xlocator": [1, 2], "color": "red"}
+
+
+def test_registry_translates_explicit_format_keys() -> None:
+ kwargs = {"xticks": [1, 2], "_explicit_format_keys": {"xticks"}}
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ result = ikwargs._canonicalize_kwargs("cartesian.format", kwargs)
+ assert result["_explicit_format_keys"] == {"xlocator"}
+
+
+def test_ambiguous_alias_uses_call_context() -> None:
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ axes = ikwargs._canonicalize_kwargs(
+ ikwargs._format_alias_scopes, {"rlabels": 5}
+ )
+ figure = ikwargs._canonicalize_kwargs(
+ ikwargs._figure_format_alias_scopes, {"rlabels": ["right"]}
+ )
+ assert axes == {"rformatter": 5}
+ assert figure == {"rightlabels": ["right"]}
+
+
+def test_alias_kwargs_combines_registered_scopes() -> None:
+ @ikwargs._alias_kwargs(("plot.statistics", "plot.boxplot"))
+ def func(*, means=None, medians=None, fill=None):
+ return means, medians, fill
+
+ with warnings.catch_warnings():
+ warnings.simplefilter("error")
+ assert func(showmeans=True) == (True, None, None)
+ assert func(filled=True) == (None, None, True)
+ assert func._ultraplot_alias_scopes == ("plot.statistics", "plot.boxplot")
+ assert func._ultraplot_aliases["fill"] == ("filled",)
+
+
+def test_alias_kwargs_rejects_inline_aliases_with_registered_scopes() -> None:
+ with pytest.raises(TypeError, match="Inline aliases"):
+ ikwargs._alias_kwargs(("plot.statistics", "plot.boxplot"), old="new")
+
+
+def test_alias_reference_is_generated_from_registry() -> None:
+ reference = ikwargs._format_alias_reference()
+ assert "Function keyword aliases" in reference
+ assert "Artist property aliases" in reference
+ assert "Dotless rc aliases" in reference
+ assert "cartesian.format" in reference
+ assert "xticks" in reference
+ assert "xlocator" in reference
+
+
+def test_guide_defaults_do_not_duplicate_accepted_aliases() -> None:
+ kwargs = {"length": 0.5, "minorlocator": "minor"}
+ guides._update_kw(kwargs, overwrite=False, shrink=1.0, minorticks=True)
+ assert kwargs == {"length": 0.5, "minorlocator": "minor"}
diff --git a/ultraplot/tests/test_subplot_manager.py b/ultraplot/tests/test_subplot_manager.py
index 46e6bb06d..7a6d2c858 100644
--- a/ultraplot/tests/test_subplot_manager.py
+++ b/ultraplot/tests/test_subplot_manager.py
@@ -397,18 +397,13 @@ def test_ui_introspects_manager_not_figure_delegator():
silently routes ``proj`` to the figure, which then raises from ``Figure.set()``.
"""
proj_params = set(inspect.signature(SubplotManager.parse_proj).parameters)
- assert {
- "proj",
- "projection",
- "proj_kw",
- "projection_kw",
- "backend",
- "basemap",
- } <= proj_params
+ assert {"projection", "projection_kw", "backend", "basemap"} <= proj_params
+ assert {"proj", "proj_kw"}.isdisjoint(proj_params)
subplots_params = set(inspect.signature(SubplotManager.add_subplots).parameters)
assert {"array", "nrows", "ncols", "order"} <= subplots_params
- assert {"proj", "projection", "proj_kw", "projection_kw"} <= subplots_params
+ assert {"projection", "projection_kw"} <= subplots_params
+ assert {"proj", "proj_kw"}.isdisjoint(subplots_params)
def test_figure_delegator_signature_is_not_load_bearing():
diff --git a/ultraplot/ui.py b/ultraplot/ui.py
index f61b03840..c243ac1dd 100644
--- a/ultraplot/ui.py
+++ b/ultraplot/ui.py
@@ -10,6 +10,9 @@
from . import gridspec as pgridspec
from ._subplots import SubplotManager
from .internals import (
+ _canonicalize_kwargs,
+ _figure_format_alias_scopes,
+ _format_alias_scopes,
_not_none,
_pop_params,
_pop_props,
@@ -181,6 +184,9 @@ def subplot(**kwargs):
matplotlib.figure.Figure
"""
_parse_figsize(kwargs)
+ kwargs = _canonicalize_kwargs("subplot", kwargs)
+ kwargs = _canonicalize_kwargs("gridspec", kwargs)
+ kwargs = _canonicalize_kwargs(_format_alias_scopes, kwargs)
rc_kw, rc_mode = _pop_rc(kwargs)
kwsub = _pop_props(kwargs, "patch") # e.g. 'color'
# NOTE: Introspect the manager, which owns these parameters, rather than the
@@ -229,6 +235,9 @@ def subplots(*args, **kwargs):
matplotlib.figure.Figure
"""
_parse_figsize(kwargs)
+ kwargs = _canonicalize_kwargs("subplot", kwargs)
+ kwargs = _canonicalize_kwargs("gridspec", kwargs)
+ kwargs = _canonicalize_kwargs(_figure_format_alias_scopes, kwargs)
rc_kw, rc_mode = _pop_rc(kwargs)
kwsubs = _pop_props(kwargs, "patch") # e.g. 'color'
kwsubs.update(_pop_params(kwargs, SubplotManager.add_subplots))
diff --git a/ultraplot/ultralayout.py b/ultraplot/ultralayout.py
index 698900b4e..5aa2efdfa 100644
--- a/ultraplot/ultralayout.py
+++ b/ultraplot/ultralayout.py
@@ -11,6 +11,8 @@
import numpy as np
+from .internals import _alias_kwargs
+
try:
from kiwisolver import Solver, Variable
@@ -147,6 +149,7 @@ class UltraLayoutSolver:
a superior layout experience for complex subplot arrangements.
"""
+ @_alias_kwargs("gridspec")
def __init__(
self,
array: np.ndarray,
@@ -158,8 +161,8 @@ def __init__(
right: float = 0.125,
top: float = 0.125,
bottom: float = 0.125,
- wratios: Optional[List[float]] = None,
- hratios: Optional[List[float]] = None,
+ width_ratios: Optional[List[float]] = None,
+ height_ratios: Optional[List[float]] = None,
wpanels: Optional[List[bool]] = None,
hpanels: Optional[List[bool]] = None,
):
@@ -176,7 +179,7 @@ def __init__(
Spacing between columns and rows in inches
left, right, top, bottom : float
Margins in inches
- wratios, hratios : list of float, optional
+ width_ratios, height_ratios : list of float, optional
Width and height ratios for columns and rows
wpanels, hpanels : list of bool, optional
Flags indicating panel columns or rows with fixed widths/heights.
@@ -211,15 +214,15 @@ def __init__(
self.hspace = list(hspace)
# Set up ratios
- if wratios is None:
+ if width_ratios is None:
self.wratios = [1.0] * self.ncols
else:
- self.wratios = list(wratios)
+ self.wratios = list(width_ratios)
- if hratios is None:
+ if height_ratios is None:
self.hratios = [1.0] * self.nrows
else:
- self.hratios = list(hratios)
+ self.hratios = list(height_ratios)
# Set up panel flags (True for fixed-width panel slots).
if wpanels is None:
@@ -511,6 +514,7 @@ def solve(self) -> Dict[str, Tuple[float, float, float, float]]:
}
+@_alias_kwargs("gridspec")
def compute_ultra_positions(
array: np.ndarray,
figwidth: float = 10.0,
@@ -521,8 +525,8 @@ def compute_ultra_positions(
right: float = 0.125,
top: float = 0.125,
bottom: float = 0.125,
- wratios: Optional[List[float]] = None,
- hratios: Optional[List[float]] = None,
+ width_ratios: Optional[List[float]] = None,
+ height_ratios: Optional[List[float]] = None,
wpanels: Optional[List[bool]] = None,
hpanels: Optional[List[bool]] = None,
) -> Dict[int, Tuple[float, float, float, float]]:
@@ -539,7 +543,7 @@ def compute_ultra_positions(
Spacing between columns and rows in inches
left, right, top, bottom : float
Margins in inches
- wratios, hratios : list of float, optional
+ width_ratios, height_ratios : list of float, optional
Width and height ratios for columns and rows
wpanels, hpanels : list of bool, optional
Flags indicating panel columns or rows with fixed widths/heights.
@@ -567,14 +571,15 @@ def compute_ultra_positions(
right,
top,
bottom,
- wratios,
- hratios,
+ width_ratios,
+ height_ratios,
wpanels,
hpanels,
)
return solver.solve()
+@_alias_kwargs("gridspec")
def get_grid_positions_ultra(
array: np.ndarray,
figwidth: float,
@@ -585,8 +590,8 @@ def get_grid_positions_ultra(
right: float = 0.125,
top: float = 0.125,
bottom: float = 0.125,
- wratios: Optional[List[float]] = None,
- hratios: Optional[List[float]] = None,
+ width_ratios: Optional[List[float]] = None,
+ height_ratios: Optional[List[float]] = None,
wpanels: Optional[List[bool]] = None,
hpanels: Optional[List[bool]] = None,
) -> Tuple[np.ndarray, np.ndarray, np.ndarray, np.ndarray]:
@@ -606,7 +611,7 @@ def get_grid_positions_ultra(
Spacing between columns and rows in inches
left, right, top, bottom : float
Margins in inches
- wratios, hratios : list of float, optional
+ width_ratios, height_ratios : list of float, optional
Width and height ratios for columns and rows
wpanels, hpanels : list of bool, optional
Flags indicating panel columns or rows with fixed widths/heights.
@@ -626,8 +631,8 @@ def get_grid_positions_ultra(
right,
top,
bottom,
- wratios,
- hratios,
+ width_ratios,
+ height_ratios,
wpanels,
hpanels,
)