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 @@ + + + + + + + + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 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 locations on an UltraPlot figure + An annotated chart with interactive labels for figure layout, side titles, axes, plot data, artist style, legend, and colorbar aliases. + + + + + + + + + + + + + + + + + + + + Figure &layout + + + + Side titles& labels + + + + Axes &projections + + + + Plot methods + + + + Legend + + + + Colorbar + + + + Artist styling + + +
+ +
+ + + + + + + +
+ +
+ +
+ + +
+

+
+
+ + +
+
+ +.. 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, )