From 439c69af28e46617f3df84b53c4724bdd0ce537d Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 12:34:14 +1000 Subject: [PATCH 01/10] Reorganize documentation navigation and onboarding --- docs/index.rst | 161 ++++++++++++++++++++++++++++--------------------- docs/usage.rst | 28 +++++++-- 2 files changed, 114 insertions(+), 75 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 5b5ec248d..29678c2f2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,146 +3,166 @@ :align: center **UltraPlot** is a succinct wrapper around `matplotlib `__ -for creating **beautiful, publication-quality graphics** with ease. +for creating **publication-quality graphics** with a small, familiar API. -Key Features -############ -Build polished figures quickly with pragmatic defaults. -**Simplified Subplot Management** – Create multi-panel plots effortlessly. +Start with a finished figure +############################ + +The quickest way to get oriented is to make one complete figure, then explore +the concepts behind it. Follow the :doc:`first figure ` walkthrough or browse +the :doc:`recipes` for focused examples. + +.. admonition:: Coming from Matplotlib? + :class: tip -**Smart Aesthetics** – Optimized colormaps, fonts, and styles out of the box. + Compare the same figure side by side in the + :doc:`Matplotlib comparison ` to see where UltraPlot adds + convenience while keeping Matplotlib objects and conventions in view. -**Versatile Plot Types** – Cartesian plots, insets, colormaps, and more. + :doc:`Make your first figure ` · + :doc:`Compare with Matplotlib ` + +Key features +############ -**Get Started** → :doc:`Installation guide ` | :doc:`Why UltraPlot? ` | :doc:`Usage ` | :doc:`Gallery ` +Build polished figures with pragmatic defaults and familiar Matplotlib +objects. UltraPlot supports multi-panel layouts, Cartesian and geographic +plots, colorbars and legends, and data-aware plotting workflows. --------------------------------------- +**Get started** → :doc:`Installation guide ` | +:doc:`Why UltraPlot? ` | :doc:`Usage ` | +:doc:`Gallery ` -User Guide -########## -A preview of what UltraPlot can do. For more see the sidebar! +Topics +###### .. grid:: 1 2 3 3 :gutter: 2 .. grid-item-card:: - :link: subplots.html - :shadow: md - :class-card: card-with-bottom-text + :link: subplots.html + :shadow: md + :class-card: card-with-bottom-text - **Subplots & Layouts** - ^^^ + **Subplots & Layouts** + ^^^ - .. image:: _static/example_plots/subplot_example.svg - :align: center + .. image:: _static/example_plots/subplot_example.svg + :align: center - Create complex multi-panel layouts effortlessly. + Create multi-panel layouts with shared axes and automatic spacing. .. grid-item-card:: - :link: cartesian.html - :shadow: md - :class-card: card-with-bottom-text - - **Cartesian Plots** - ^^^ + :link: cartesian.html + :shadow: md + :class-card: card-with-bottom-text - .. image:: _static/example_plots/cartesian_example.svg - :align: center + **Cartesian Plots** + ^^^ - .. container:: bottom-aligned-text + .. image:: _static/example_plots/cartesian_example.svg + :align: center - Easily generate clean, well-formatted plots. + Format ordinary plots while retaining Matplotlib's plotting methods. .. grid-item-card:: - :link: projections.html - :shadow: md - :class-card: card-with-bottom-text + :link: colorbars_legends.html + :shadow: md + :class-card: card-with-bottom-text - **Projections & Maps** - ^^^ + **Colorbars & Legends** + ^^^ - .. image:: _static/example_plots/projection_example.svg - :align: center + .. image:: _static/example_plots/colorbars_legends_example.svg + :align: center - .. container:: bottom-aligned-text - Built-in support for projections and geographic plots. + Place and align guides across individual subplots or a whole figure. .. grid-item-card:: - :link: colorbars_legends.html - :shadow: md - :class-card: card-with-bottom-text - - **Colorbars & Legends** - ^^^ + :link: 1dplots.html + :shadow: md + :class-card: card-with-bottom-text - .. image:: _static/example_plots/colorbars_legends_example.svg - :align: center + **Data-aware plotting** + ^^^ - Customize legends and colorbars with ease. + Plot labelled pandas and xarray data with metadata-aware labels and + coordinates. .. grid-item-card:: - :link: insets_panels.html - :shadow: md - :class-card: card-with-bottom-text + :link: projections.html + :shadow: md + :class-card: card-with-bottom-text - **Insets & Panels** - ^^^ + **Projections & Maps** + ^^^ - .. image:: _static/example_plots/panels_example.svg - :align: center + .. image:: _static/example_plots/projection_example.svg + :align: center - Add inset plots and panel-based layouts. + Explore geographic plotting when you need projections and map features. .. grid-item-card:: :link: colormaps.html :shadow: md :class-card: card-with-bottom-text - **Colormaps & Cycles** + **Colormaps & Styles** ^^^ .. image:: _static/example_plots/colormaps_example.svg - :align: center + :align: center - Use prebuilt colormaps and define your own color cycles. + Choose and customize colormaps for clear, consistent visual encoding. -Reference & More +Reference & more ################ -For more details, check the full :doc:`User guide ` and :doc:`API Reference `. + +For details, see the full :doc:`User guide ` and +:doc:`API Reference `. * :ref:`genindex` * :ref:`modindex` * :ref:`glossary` + .. toctree:: :maxdepth: 1 - :caption: Getting Started + :caption: Getting started :hidden: install - why + why_ultraplot + first_figure usage + recipes gallery/index .. toctree:: :maxdepth: 1 - :caption: User Guide + :caption: Guides :hidden: basics subplots cartesian networks - projections colorbars_legends - insets_panels + colormaps 1dplots 2dplots + +.. toctree:: + :maxdepth: 1 + :caption: Advanced guides + :hidden: + + projections + insets_panels stats - colormaps + configuration + fonts cycles colors - fonts - configuration .. toctree:: :maxdepth: 1 @@ -152,13 +172,14 @@ For more details, check the full :doc:`User guide ` and :doc:`API Referen api lazy_loading external-links + faq whats_new contributing about .. toctree:: :maxdepth: 1 - :caption: Dev Zone + :caption: Development :hidden: plot_comparison_results diff --git a/docs/usage.rst b/docs/usage.rst index 298c12132..96a1d96fb 100644 --- a/docs/usage.rst +++ b/docs/usage.rst @@ -10,13 +10,31 @@ .. _usage: -============= +=============== Using UltraPlot -============= +=============== -This page offers a condensed overview of UltraPlot's features. It is populated -with links to the :ref:`API reference` and :ref:`User Guide `. -For a more in-depth discussion, see :ref:`why`. +This page is an orientation to UltraPlot's main concepts and building blocks. +For a hands-on start, make a :doc:`first figure ` and then use +the :doc:`recipes` and :ref:`User Guide ` to explore the features +that fit your workflow. The :ref:`API reference` is the detailed reference; +for design context and motivation, see :doc:`Why UltraPlot `. + +.. _usage_first_figure: + +Your first finished figure +========================== + +The recommended learning path is deliberately short: + +#. :doc:`Install UltraPlot ` and import it as ``uplt``. +#. Follow the :doc:`first figure ` walkthrough to create, format, and save a + complete figure. +#. Try a related :doc:`recipe `, then open the guide for the plot + type or feature you want to add. + +You can continue using familiar Matplotlib plotting methods and objects while +adopting UltraPlot's figure layout and formatting helpers incrementally. .. _usage_background: From 14d2245b98b48834d1698efad032ee153dea2020 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 12:37:53 +1000 Subject: [PATCH 02/10] Rewrite new-user documentation path --- docs/first_figure.py | 140 +++++++++++++++++++++++++++++++++++++++ docs/index.rst | 1 + docs/keyword_aliases.rst | 49 ++++++++++++++ docs/recipes.py | 86 ++++++++++++++++++++++++ docs/why_ultraplot.py | 123 ++++++++++++++++++++++++++++++++++ 5 files changed, 399 insertions(+) create mode 100644 docs/first_figure.py create mode 100644 docs/keyword_aliases.rst create mode 100644 docs/recipes.py create mode 100644 docs/why_ultraplot.py diff --git a/docs/first_figure.py b/docs/first_figure.py new file mode 100644 index 000000000..8ecc31f83 --- /dev/null +++ b/docs/first_figure.py @@ -0,0 +1,140 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.4 +# kernelspec: +# display_name: Python 3 +# language: python +# name: python3 +# --- + +# %% [raw] raw_mimetype="text/restructuredtext" +# .. _first_figure: +# +# Your first UltraPlot figure +# =========================== +# +# This short, runnable ladder builds one two-panel figure in five stages. Each +# stage is self-contained, so you can run it on its own while learning. The +# plotting methods are the familiar Matplotlib axes methods; UltraPlot's main +# contribution here is the layout and figure-wide formatting. + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 1: choose a layout +# ------------------------ +# +# Start by asking :func:`~ultraplot.ui.subplots` for two panels. ``axs`` is a +# convenient collection of axes that can be indexed from left to right. + +# %% +import ultraplot as uplt + +fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) +fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 2: add data +# ----------------- +# +# Add a line to the first panel and an image to the second. The data are +# deterministic and generated in memory, so this example has no network or +# heavyweight dependency. + +# %% +import numpy as np +import ultraplot as uplt + +rng = np.random.RandomState(2024) +x = np.linspace(0, 2 * np.pi, 100) +signal = np.sin(x) + 0.06 * rng.randn(x.size) +image = np.outer(np.sin(x / 2), np.cos(x / 3)) + +fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) +axs[0].plot(x, signal, label="signal") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 3: format the panels +# -------------------------- +# +# ``format`` groups common labels and titles in one call. You can still use +# any ordinary axes plotting method alongside it. + +# %% +import numpy as np +import ultraplot as uplt + +rng = np.random.RandomState(2024) +x = np.linspace(0, 2 * np.pi, 100) +signal = np.sin(x) + 0.06 * rng.randn(x.size) +image = np.outer(np.sin(x / 2), np.cos(x / 3)) + +fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) +axs[0].plot(x, signal, label="signal") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +axs[0].format(title="Signal", xlabel="angle", ylabel="value") +axs[1].format(title="Image", xlabel="column", ylabel="row") +fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 4: add guides and figure formatting +# ------------------------------------------- +# +# A legend describes the line, while a colorbar describes the image. Guides +# belong to the figure because they help the reader interpret the panels. +# ``fig.format`` adds the shared title without repeating it on each axes. + +# %% +import numpy as np +import ultraplot as uplt + +rng = np.random.RandomState(2024) +x = np.linspace(0, 2 * np.pi, 100) +signal = np.sin(x) + 0.06 * rng.randn(x.size) +image = np.outer(np.sin(x / 2), np.cos(x / 3)) + +fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) +axs[0].plot(x, signal, label="signal") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +axs[0].format(title="Signal", xlabel="angle", ylabel="value") +axs[1].format(title="Image", xlabel="column", ylabel="row") +fig.format(suptitle="A first UltraPlot figure") +fig.legend(loc="t") +fig.colorbar(mesh, loc="r", label="intensity") +fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 5: save the result +# ------------------------ +# +# Once the figure looks right, save it with :meth:`~ultraplot.figure.Figure.save`. +# PNG is convenient for a quick preview; choose another extension when your +# publication or workflow calls for a different format. + +# %% +import numpy as np +import ultraplot as uplt + +rng = np.random.RandomState(2024) +x = np.linspace(0, 2 * np.pi, 100) +signal = np.sin(x) + 0.06 * rng.randn(x.size) +image = np.outer(np.sin(x / 2), np.cos(x / 3)) + +fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) +axs[0].plot(x, signal, label="signal") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +axs[0].format(title="Signal", xlabel="angle", ylabel="value") +axs[1].format(title="Image", xlabel="column", ylabel="row") +fig.format(suptitle="A first UltraPlot figure") +fig.legend(loc="t") +fig.colorbar(mesh, loc="r", label="intensity") +fig.save("first_figure.png") diff --git a/docs/index.rst b/docs/index.rst index 29678c2f2..24798a42b 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -170,6 +170,7 @@ For details, see the full :doc:`User guide ` and :hidden: api + keyword_aliases lazy_loading external-links faq diff --git a/docs/keyword_aliases.rst b/docs/keyword_aliases.rst new file mode 100644 index 000000000..9a0f0188a --- /dev/null +++ b/docs/keyword_aliases.rst @@ -0,0 +1,49 @@ +Keyword vocabulary +================== + +UltraPlot accepts a broad set of keyword spellings so existing Matplotlib and +older UltraPlot code continues to work. New users only need a small canonical +front door. The table below is the vocabulary used throughout the beginner +recipes; aliases remain supported, but we do not teach every alias in every +example. + +Canonical names and supported aliases +------------------------------------- + +.. list-table:: + :header-rows: 1 + :widths: 22 34 44 + + * - Area + - Canonical + - Also supported + * - Layout + - ``refwidth``, ``refheight``, ``refaspect`` + - ``axwidth``, ``axheight``, ``aspect`` + * - Layout + - ``figwidth``, ``figheight``, ``wratios``, ``hratios`` + - ``width``, ``height``, ``width_ratios``, ``height_ratios`` + * - Plot styling + - ``linewidth``, ``color`` + - ``lw``, ``linewidths``, ``c``, ``colors`` + * - Panel geometry + - ``span`` + - ``row``, ``rows``, ``col``, ``cols`` + * - Shared axes + - ``sharex``, ``sharey`` + - ``share`` (sets both) + * - Figure + - ``suptitle`` + - ``figtitle`` + * - Guides + - ``loc``, ``ncols`` + - ``location``, ``ncol`` + * - Export + - ``fig.save(...)`` + - ``fig.savefig(...)`` + +Prefer the canonical column when starting new code. This is a documentation +choice only: this page does not change runtime behavior or deprecate aliases. + +See :doc:`recipes` for short, copyable figures using this vocabulary and the +full API reference for the complete keyword signatures. diff --git a/docs/recipes.py b/docs/recipes.py new file mode 100644 index 000000000..fdda3fcc4 --- /dev/null +++ b/docs/recipes.py @@ -0,0 +1,86 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# --- + +# %% [raw] raw_mimetype="text/restructuredtext" +# .. _ug_recipes: +# +# Common figure recipes +# ===================== +# +# These small, complete examples cover the patterns most figures need. Copy a +# recipe into a script or notebook and adapt the data and labels. + +# %% +import numpy as np +import ultraplot as uplt + + +# %% [markdown] +# Labelled line +# ------------- +# Use ``label`` on each artist and format the axes in one place. + +# %% +x = np.linspace(0, 2 * np.pi, 200) +fig, ax = uplt.subplots(refwidth=3.2) +ax.plot(x, np.sin(x), label="sine") +ax.plot(x, np.cos(x), label="cosine") +ax.format(xlabel="angle", ylabel="value", title="Two signals") +ax.legend(loc="ur") + + +# %% [markdown] +# Shared-axis grid +# ---------------- +# ``share="labels"`` keeps the grid readable while retaining tick labels. + +# %% +fig, axs = uplt.subplots(nrows=2, ncols=2, refwidth=2.0, share="labels") +for number, ax in enumerate(axs, start=1): + ax.plot(x, np.sin(x + number / 3), color=f"C{number - 1}") + ax.format(title=f"Panel {number}") +axs.format(xlabel="x", ylabel="y", suptitle="A shared-axis grid") + + +# %% [markdown] +# Image with a colorbar +# --------------------- +# Plot the returned mappable and request an outer colorbar with ``colorbar``. + +# %% +image = np.outer(np.sin(x[:60]), np.cos(x[:60])) +fig, ax = uplt.subplots(refwidth=3.0) +ax.imshow(image, cmap="viridis", colorbar="r") +ax.format(title="Image data", xformatter="none", yformatter="none") + + +# %% [markdown] +# Figure-wide legend +# ------------------ +# A figure legend collects labelled artists across the selected axes. + +# %% +fig, axs = uplt.subplots(ncols=2, refwidth=2.2) +for ax, phase in zip(axs, (0, np.pi / 2)): + ax.plot(x, np.sin(x + phase), label="signal") + ax.plot(x, np.cos(x + phase), label="reference") + ax.format(title=f"phase = {phase:.2g}") +fig.legend(loc="b", ncols=2) + + +# %% [markdown] +# Publication-sized export +# ------------------------ +# Set a physical figure width and write a vector file for a manuscript. + +# %% +fig, ax = uplt.subplots(figwidth="89mm", refaspect=1.6) +ax.plot(x, np.sin(x), color="C0") +ax.format(xlabel="angle", ylabel="value") +fig.save("figure.pdf") diff --git a/docs/why_ultraplot.py b/docs/why_ultraplot.py new file mode 100644 index 000000000..be7a21031 --- /dev/null +++ b/docs/why_ultraplot.py @@ -0,0 +1,123 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.4 +# kernelspec: +# display_name: Python 3 +# language: python +# name: python3 +# --- + +# %% [raw] raw_mimetype="text/restructuredtext" +# .. _why_ultraplot: +# +# Why UltraPlot? +# ============= +# +# Matplotlib is a powerful plotting library. UltraPlot keeps its familiar +# object-oriented plotting methods and adds a small layer for arranging and +# formatting complete figures. This page uses one deterministic, two-panel +# example to make that distinction concrete. +# +# .. note:: +# +# UltraPlot is most useful when a figure has several panels or needs +# repeated, publication-oriented formatting. A one-panel exploratory plot +# may not need UltraPlot at all; plain Matplotlib is an excellent choice. +# +# The comparison below is intentionally fair: both versions plot the same +# line and image, use the same labels, and add the same colorbar. The point is +# not to count lines of code. Matplotlib and UltraPlot have different +# responsibilities: Matplotlib supplies the plotting primitives, while +# UltraPlot makes figure-level layout and consistent formatting easier to +# express. + +# %% [raw] raw_mimetype="text/restructuredtext" +# The shared data +# --------------- +# +# The data are generated locally so the examples are reproducible and do not +# require a download. The line and image are deliberately simple enough to +# understand before looking at the plotting code. + +# %% +import numpy as np + +SEED = 51423 +rng = np.random.RandomState(SEED) +x = np.linspace(0, 10, 100) +line = np.sin(x) + 0.08 * rng.randn(x.size) +image = np.outer(np.sin(x / 2), np.cos(x / 3)) + + +# %% [raw] raw_mimetype="text/restructuredtext" +# Matplotlib +# ---------- +# +# Matplotlib's explicit axes and figure calls are a good fit when you want +# fine-grained control over each artist. The code below is a complete, +# conventional Matplotlib solution. +# +# See the `Matplotlib subplots tutorial +# `__ for the +# underlying interface. + +# %% +import matplotlib.pyplot as plt + +mpl_fig, mpl_axs = plt.subplots(1, 2, figsize=(7, 3), constrained_layout=True) +mpl_axs[0].plot(x, line, label="signal", color="tab:blue") +mpl_axs[0].set(xlabel="x", ylabel="value", title="Line") +mpl_axs[0].legend(loc="upper right") +mpl_mesh = mpl_axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +mpl_axs[1].set(xlabel="column", ylabel="row", title="Image") +mpl_fig.colorbar(mpl_mesh, ax=mpl_axs[1], label="intensity") +mpl_fig.suptitle("The same two-panel figure") +mpl_fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# UltraPlot +# --------- +# +# The plotting calls remain ordinary axes methods. The difference is that +# ``uplt.subplots`` and ``format`` let the figure layout and shared labels be +# described together. This is particularly handy when the number of panels +# grows: the intent of the figure stays in one place instead of being spread +# over many setter calls. +# +# See the :ref:`format command ` and the +# :func:`~ultraplot.ui.subplots` API reference for more options. + +# %% +import ultraplot as uplt + +uplt_fig, uplt_axs = uplt.subplots(ncols=2, share=False, refwidth=2.3) +uplt_axs[0].plot(x, line, label="signal", color="tab:blue") +uplt_mesh = uplt_axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +uplt_axs[0].format(title="Line", xlabel="x", ylabel="value") +uplt_axs[0].legend(loc="ur") +uplt_axs[1].format(title="Image", xlabel="column", ylabel="row") +uplt_fig.format(suptitle="The same two-panel figure") +uplt_fig.colorbar(uplt_mesh, loc="r", label="intensity") +uplt_fig.show() + + +# %% [raw] raw_mimetype="text/restructuredtext" +# What to take away +# ----------------- +# +# UltraPlot does not replace Matplotlib's plotting vocabulary. A useful mental +# model is: +# +# * use normal axes methods such as ``plot`` and ``imshow`` to draw data; +# * use ``axs.format`` for consistent panel-level labels and styling; and +# * use ``fig.format`` and figure guides when the whole figure must agree. +# +# For a quick one-panel question, start with Matplotlib. When layout and +# formatting become the recurring work, UltraPlot can remove that friction +# while keeping the Matplotlib object model. From 213e043f952859190c704006ac1885f03d978837 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 15:05:31 +1000 Subject: [PATCH 03/10] update figrst figure --- docs/first_figure.py | 258 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 226 insertions(+), 32 deletions(-) diff --git a/docs/first_figure.py b/docs/first_figure.py index 8ecc31f83..f695852ce 100644 --- a/docs/first_figure.py +++ b/docs/first_figure.py @@ -15,20 +15,24 @@ # %% [raw] raw_mimetype="text/restructuredtext" # .. _first_figure: # -# Your first UltraPlot figure +# Your First UltraPlot Figure # =========================== # -# This short, runnable ladder builds one two-panel figure in five stages. Each -# stage is self-contained, so you can run it on its own while learning. The -# plotting methods are the familiar Matplotlib axes methods; UltraPlot's main -# contribution here is the layout and figure-wide formatting. +# UltraPlot is designed to make creating complex Matplotlib figures simpler and +# more intuitive. This tutorial builds a two-panel figure in five stages. +# Because each stage is self-contained, you can run them individually while +# seeing exactly how UltraPlot removes standard Matplotlib boilerplate and +# automates layout generation. # %% [raw] raw_mimetype="text/restructuredtext" -# Stage 1: choose a layout +# Stage 1: Choose a layout # ------------------------ # -# Start by asking :func:`~ultraplot.ui.subplots` for two panels. ``axs`` is a -# convenient collection of axes that can be indexed from left to right. +# In standard Matplotlib, you generally have to guess a ``figsize`` tuple, +# which requires tedious trial and error anytime you add or remove subplots. +# UltraPlot calculates the figure size dynamically. By setting ``refwidth=2.4``, +# you simply define the width of a single reference panel, and UltraPlot +# automatically scales the entire figure to fit your rows and columns perfectly. # %% import ultraplot as uplt @@ -38,12 +42,13 @@ # %% [raw] raw_mimetype="text/restructuredtext" -# Stage 2: add data +# Stage 2: Add data # ----------------- # -# Add a line to the first panel and an image to the second. The data are -# deterministic and generated in memory, so this example has no network or -# heavyweight dependency. +# While UltraPlot revolutionizes the figure layout, the actual plotting +# commands are completely identical to Matplotlib. Because UltraPlot axes +# are directly built upon Matplotlib axes, you can use standard methods like +# ``plot()`` and ``imshow()`` with absolutely zero learning curve. # %% import numpy as np @@ -56,16 +61,19 @@ fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) axs[0].plot(x, signal, label="signal") -mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +mesh = axs[1].imshow(image, origin="lower", aspect="auto") fig.show() # %% [raw] raw_mimetype="text/restructuredtext" -# Stage 3: format the panels +# Stage 3: Format the panels # -------------------------- # -# ``format`` groups common labels and titles in one call. You can still use -# any ordinary axes plotting method alongside it. +# This is where UltraPlot drastically reduces boilerplate code. In Matplotlib, +# formatting these two axes would require six separate lines of code (using +# ``set_title()``, ``set_xlabel()``, and ``set_ylabel()``). UltraPlot introduces +# a unified ``format()`` method, allowing you to configure titles, labels, +# limits, and styling all in a single, readable function call per axis. # %% import numpy as np @@ -78,19 +86,22 @@ fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) axs[0].plot(x, signal, label="signal") -mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +mesh = axs[1].imshow(image, origin="lower", aspect="auto") +axs[1].colorbar(mesh, label="Intensity", loc="r") axs[0].format(title="Signal", xlabel="angle", ylabel="value") axs[1].format(title="Image", xlabel="column", ylabel="row") fig.show() # %% [raw] raw_mimetype="text/restructuredtext" -# Stage 4: add guides and figure formatting -# ------------------------------------------- +# Stage 4: Add guides and figure formatting +# ----------------------------------------- # -# A legend describes the line, while a colorbar describes the image. Guides -# belong to the figure because they help the reader interpret the panels. -# ``fig.format`` adds the shared title without repeating it on each axes. +# Adding colorbars and legends in Matplotlib is notorious for ruining layouts—they +# often overlap with data or require tedious ``GridSpec`` wrangling. UltraPlot +# solves this natively. By passing simple location strings like ``loc="t"`` (top) +# or ``loc="r"`` (right) to figure-level guide commands, UltraPlot allocates +# dedicated space *outside* the subplots without shrinking or distorting your axes. # %% import numpy as np @@ -103,22 +114,23 @@ fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) axs[0].plot(x, signal, label="signal") -mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", colorbar = "lr", colorbar_kw = dict(label = "Intensity")) axs[0].format(title="Signal", xlabel="angle", ylabel="value") axs[1].format(title="Image", xlabel="column", ylabel="row") +# fig.format(suptitle="A first UltraPlot figure") -fig.legend(loc="t") -fig.colorbar(mesh, loc="r", label="intensity") +fig.legend(loc="b") fig.show() # %% [raw] raw_mimetype="text/restructuredtext" -# Stage 5: save the result +# Stage 5: Save the result # ------------------------ # -# Once the figure looks right, save it with :meth:`~ultraplot.figure.Figure.save`. -# PNG is convenient for a quick preview; choose another extension when your -# publication or workflow calls for a different format. +# Saving the figure is just as straightforward. Unlike standard Matplotlib, where +# you frequently have to pass ``bbox_inches="tight"`` to prevent labels and guides +# from being cut off, UltraPlot's automated layout engine guarantees that your +# saved file will automatically have perfectly tight margins. # %% import numpy as np @@ -131,10 +143,192 @@ fig, axs = uplt.subplots(ncols=2, share=False, refwidth=2.4) axs[0].plot(x, signal, label="signal") -mesh = axs[1].imshow(image, origin="lower", aspect="auto", cmap="viridis") +mesh = axs[1].imshow(image, origin="lower", aspect="auto", colorbar = "ur", colorbar_kw = dict(label = "Intensity")) axs[0].format(title="Signal", xlabel="angle", ylabel="value") axs[1].format(title="Image", xlabel="column", ylabel="row") fig.format(suptitle="A first UltraPlot figure") -fig.legend(loc="t") -fig.colorbar(mesh, loc="r", label="intensity") +fig.legend(loc="b") fig.save("first_figure.png") + +# %% [raw] raw_mimetype="text/restructuredtext" +# Stage 6: The Grand Finale +# ------------------------- +# +# To truly see UltraPlot's power, let's create a complex, publication-ready +# figure. In standard Matplotlib, combining a custom mosaic layout, a geographic +# projection, an inset axes, and A-B-C panel labels usually results in hundreds +# of lines of fragile ``GridSpec`` and ``transform`` code. +# +# UltraPlot condenses all of this into a highly readable, declarative script. +# Notice how we assign a projection to just one panel using a dictionary, +# add an inset using a simple location string, and auto-generate our panel +# labels with a single ``abc=True`` argument. + +# %% +import numpy as np +import ultraplot as uplt + +# 1. Generate synthetic scientific data +rng = np.random.RandomState(2024) +lon, lat = np.linspace(-180, 180, 100), np.linspace(-90, 90, 100) +lon2d, lat2d = np.meshgrid(lon, lat) +# A pseudo-spatial anomaly pattern +geo_data = np.cos(np.radians(lat2d)) * np.sin(np.radians(lon2d * 2)) +time = np.linspace(0, 10, 200) +series1 = np.sin(time) * np.exp(-time / 5) +series2 = np.cos(time) * np.exp(-time / 5) + +# Use a Cartesian panel as the square reference. The map keeps its native +# Robinson aspect while the right-hand plots remain readable in a tall figure. +layout = [[1, 2], [1, 2], [1, 3], [1, 3]] + +# 3. Create the figure +# Apply a Robinson projection only to the first panel. +fig, axs = uplt.subplots( + layout, + proj={1: 'robin'}, + share=0, + refnum=2, + refwidth=1.5, + wratios=(3.25, 1), + # The two right-hand axes span rows (0, 1) and (2, 3), respectively. + # Only the middle boundary separates them. + hspace=('0pt', '13em', '0pt'), +) + +# 4. Geographic data +m = axs[0].contourf( + lon, lat, geo_data, + cmap='marine', + levels=15, +) + +axs[0].format( + land=True, + borders=True, + labels=True, + lonlines=120, + latlines=45, + labelsize=12, + title='Global Spatial Anomaly', + title_kw={'fontsize': 14}, +) + +# A geographic callout is useful for showing a local-scale pattern without +# sacrificing the global view. These deterministic points mimic city readings. +paris = (2.3522, 48.8566) +paris_rng = np.random.RandomState(99) +paris_lon = paris[0] + paris_rng.normal(scale=0.65, size=36) +paris_lat = paris[1] + paris_rng.normal(scale=0.45, size=36) +paris_value = np.hypot(paris_lon - paris[0], paris_lat - paris[1]) +paris_ax = axs[0].hawkeye( + (0.43, 0.68), + size=0.45, + anchor='ur', + proj='merc', + extent=(-0.8, 5.5, 46.8, 50.8), + shape='circle', + target='circle', + connector='line', + color='red7', + indicator_kw={'linewidth': 1.4}, +) +paris_ax.format(land=True, landcolor='gray8', borders=True) +paris_ax.scatter( + paris_lon, + paris_lat, + c=paris_value, + cmap='fire', + markersize=24, + edgecolor='white', + linewidth=0.35, + transform='cyl', + absolute_size = True, +) +paris_ax.plot( + *paris, + marker='*', + markersize=9, + color='red7', + markeredgecolor='white', + markeredgewidth=0.6, + transform='cyl', +) + +axs[0].colorbar( + m, + loc='b', + label='Anomaly magnitude', + length=0.8, + labelsize = 14 +) + +# 5. Scatter data with inset +x = rng.rand(100) +y = x + rng.randn(100) * 0.2 + +axs[1].scatter( + x, y, + c=x, + cmap='fire', + markersize=15, + alpha=0.7, +) + +axs[1].format( + title='Correlation Profile', + xlabel='Predictor', + ylabel='Response', + xlocator=(0, 0.5, 1), + ylocator=(0, 0.5, 1), + xtickminor=False, + ytickminor=False, + ticklabelsize=10, + labelsize=12, + title_kw={'fontsize': 12}, +) + +ax_ins = axs[1].inset([0.55, 0.55, 0.35, 0.35], zoom=False) +ax_ins.hist(x, bins=10, color='gray5', edgecolor='black') +ax_ins.format( + titleloc='uc', + grid=False, + xtickminor=False, + ytickminor=False, +) + +# 6. Time series +axs[2].plot(time, series1, label='Model Alpha') +axs[2].plot(time, series2, label='Model Beta') + +axs[2].format( + title='Temporal Decay', + xlabel='Time (s)', + ylabel='Amplitude', + xlocator=(0, 5, 10), + ylocator=(-0.5, 0, 0.5, 1), + xtickminor=False, + ytickminor=False, + ticklabelsize=10, + labelsize=12, + title_kw={'fontsize': 12}, +) + +axs[2].legend( + loc='ur', + frame=False, + fontsize=6, + ncols = 1, +) + +# 7. Figure-wide formatting +fig.format( + suptitle='Putting It All Together', + suptitle_kw={'fontsize': 15}, + abc=True, + abcloc='ul', + abcstyle='(a)', + abc_kw={'fontsize': 11}, +) + +fig.save('complex_figure.png', dpi=150) From 0e4417da84fcb7b4ce963641bd0a763d24702070 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 15:15:08 +1000 Subject: [PATCH 04/10] forgot to add latest change --- docs/why_ultraplot.py | 89 +++++++++++++++++++++---------------------- 1 file changed, 44 insertions(+), 45 deletions(-) diff --git a/docs/why_ultraplot.py b/docs/why_ultraplot.py index be7a21031..4d0d770d5 100644 --- a/docs/why_ultraplot.py +++ b/docs/why_ultraplot.py @@ -18,31 +18,30 @@ # Why UltraPlot? # ============= # -# Matplotlib is a powerful plotting library. UltraPlot keeps its familiar -# object-oriented plotting methods and adds a small layer for arranging and -# formatting complete figures. This page uses one deterministic, two-panel -# example to make that distinction concrete. +# Matplotlib is an incredibly powerful plotting engine, but creating multi-panel, +# publication-ready figures often requires repetitive boilerplate code. UltraPlot +# solves this by adding a concise, intuitive layer for layout and formatting, +# while letting you keep the familiar Matplotlib object-oriented methods you +# already know. # # .. note:: # -# UltraPlot is most useful when a figure has several panels or needs -# repeated, publication-oriented formatting. A one-panel exploratory plot -# may not need UltraPlot at all; plain Matplotlib is an excellent choice. +# If you just need a quick, exploratory plot, vanilla Matplotlib is perfect. +# UltraPlot truly shines when you are managing multi-panel figures or need +# consistent, publication-quality styling across your work. # -# The comparison below is intentionally fair: both versions plot the same -# line and image, use the same labels, and add the same colorbar. The point is -# not to count lines of code. Matplotlib and UltraPlot have different -# responsibilities: Matplotlib supplies the plotting primitives, while -# UltraPlot makes figure-level layout and consistent formatting easier to -# express. +# The comparison below uses a straightforward two-panel figure to illustrate the +# difference. This isn't about code golf or minimizing lines; it's about the +# separation of concerns. Matplotlib handles the plotting primitives, while +# UltraPlot gives you a streamlined syntax to orchestrate the broader figure layout. # %% [raw] raw_mimetype="text/restructuredtext" -# The shared data -# --------------- +# Setting up the data +# ------------------- # -# The data are generated locally so the examples are reproducible and do not -# require a download. The line and image are deliberately simple enough to -# understand before looking at the plotting code. +# To keep things fully reproducible without needing external downloads, we will +# generate some basic synthetic data locally. The data itself is intentionally +# simple so we can focus entirely on the plotting mechanics. # %% import numpy as np @@ -55,16 +54,15 @@ # %% [raw] raw_mimetype="text/restructuredtext" -# Matplotlib -# ---------- +# The Matplotlib approach +# ----------------------- # -# Matplotlib's explicit axes and figure calls are a good fit when you want -# fine-grained control over each artist. The code below is a complete, -# conventional Matplotlib solution. +# Matplotlib's explicit API is fantastic when you need surgical control over every +# individual artist on the canvas. Here is how you would conventionally build and +# format this two-panel figure using standard Matplotlib. # -# See the `Matplotlib subplots tutorial -# `__ for the -# underlying interface. +# For a deep dive into this approach, see the `Matplotlib subplots tutorial +# `__. # %% import matplotlib.pyplot as plt @@ -81,17 +79,19 @@ # %% [raw] raw_mimetype="text/restructuredtext" -# UltraPlot -# --------- +# The UltraPlot approach +# ---------------------- # -# The plotting calls remain ordinary axes methods. The difference is that -# ``uplt.subplots`` and ``format`` let the figure layout and shared labels be -# described together. This is particularly handy when the number of panels -# grows: the intent of the figure stays in one place instead of being spread -# over many setter calls. +# Notice that the actual drawing commands (``plot`` and ``imshow``) are identical +# to the Matplotlib version above. The magic happens with ``uplt.subplots`` and +# the ``format`` method. # -# See the :ref:`format command ` and the -# :func:`~ultraplot.ui.subplots` API reference for more options. +# Instead of scattering setter methods across your script, UltraPlot lets you +# define figure layouts and shared labels cohesively. As your figures grow in +# complexity, this centralized formatting keeps your code clean and readable. +# +# Discover more in the :ref:`format command ` guide and the +# :func:`~ultraplot.ui.subplots` API reference. # %% import ultraplot as uplt @@ -108,16 +108,15 @@ # %% [raw] raw_mimetype="text/restructuredtext" -# What to take away -# ----------------- +# The takeaway +# ------------ # -# UltraPlot does not replace Matplotlib's plotting vocabulary. A useful mental -# model is: +# UltraPlot does not reinvent the wheel—it just makes it easier to steer. A good +# mental model for your workflow looks like this: # -# * use normal axes methods such as ``plot`` and ``imshow`` to draw data; -# * use ``axs.format`` for consistent panel-level labels and styling; and -# * use ``fig.format`` and figure guides when the whole figure must agree. +# * Use standard Matplotlib axes methods (``plot``, ``imshow``, ``scatter``) to draw the data. +# * Use ``axs.format()`` to apply consistent labels, ticks, and styling at the panel level. +# * Use ``fig.format()`` for global aesthetics and unified figure guides (like shared colorbars). # -# For a quick one-panel question, start with Matplotlib. When layout and -# formatting become the recurring work, UltraPlot can remove that friction -# while keeping the Matplotlib object model. +# For a single, fast plot, stick with Matplotlib. When layout scaling and repetitive +# formatting become a chore, let UltraPlot handle the heavy lifting. From cf4c3ce671ebbb232f7b4c693652aaf428ca3a64 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 15:24:21 +1000 Subject: [PATCH 05/10] resolve links to api --- docs/first_figure.py | 13 ++++++------- docs/why_ultraplot.py | 11 ++++++++--- 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/docs/first_figure.py b/docs/first_figure.py index f695852ce..854936c94 100644 --- a/docs/first_figure.py +++ b/docs/first_figure.py @@ -178,9 +178,10 @@ series1 = np.sin(time) * np.exp(-time / 5) series2 = np.cos(time) * np.exp(-time / 5) -# Use a Cartesian panel as the square reference. The map keeps its native -# Robinson aspect while the right-hand plots remain readable in a tall figure. -layout = [[1, 2], [1, 2], [1, 3], [1, 3]] +# The map spans two rows while the right-hand panels stack beside it. With the +# 4:1 column ratio below, the Robinson map keeps its native 2:1 aspect and the +# right-hand panels receive approximately square plotting areas. +layout = [[1, 2], [1, 3]] # 3. Create the figure # Apply a Robinson projection only to the first panel. @@ -190,10 +191,8 @@ share=0, refnum=2, refwidth=1.5, - wratios=(3.25, 1), - # The two right-hand axes span rows (0, 1) and (2, 3), respectively. - # Only the middle boundary separates them. - hspace=('0pt', '13em', '0pt'), + wratios=(4, 1), + hspace='13em', ) # 4. Geographic data diff --git a/docs/why_ultraplot.py b/docs/why_ultraplot.py index 4d0d770d5..0c3a0fa47 100644 --- a/docs/why_ultraplot.py +++ b/docs/why_ultraplot.py @@ -114,9 +114,14 @@ # UltraPlot does not reinvent the wheel—it just makes it easier to steer. A good # mental model for your workflow looks like this: # -# * Use standard Matplotlib axes methods (``plot``, ``imshow``, ``scatter``) to draw the data. -# * Use ``axs.format()`` to apply consistent labels, ticks, and styling at the panel level. -# * Use ``fig.format()`` for global aesthetics and unified figure guides (like shared colorbars). +# * Use standard axes methods such as :meth:`~ultraplot.axes.PlotAxes.plot`, +# :meth:`~ultraplot.axes.PlotAxes.imshow`, and +# :meth:`~ultraplot.axes.PlotAxes.scatter` to draw data. +# * Use :meth:`~ultraplot.axes.Axes.format` through ``axs.format()`` to apply +# consistent labels, ticks, and styling at the panel level. +# * Use :meth:`~ultraplot.figure.Figure.format` through ``fig.format()`` for +# global aesthetics, and :meth:`~ultraplot.figure.Figure.colorbar` for a +# shared colorbar. # # For a single, fast plot, stick with Matplotlib. When layout scaling and repetitive # formatting become a chore, let UltraPlot handle the heavy lifting. From 4b6f519456897ba4e30bda144ef965cc5cd296c2 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 15:29:28 +1000 Subject: [PATCH 06/10] resolve links to api part 2 --- docs/first_figure.py | 19 ++++++++++++------- docs/recipes.py | 19 +++++++++++-------- docs/why_ultraplot.py | 8 +++++--- 3 files changed, 28 insertions(+), 18 deletions(-) diff --git a/docs/first_figure.py b/docs/first_figure.py index 854936c94..4aacda4f8 100644 --- a/docs/first_figure.py +++ b/docs/first_figure.py @@ -48,7 +48,8 @@ # While UltraPlot revolutionizes the figure layout, the actual plotting # commands are completely identical to Matplotlib. Because UltraPlot axes # are directly built upon Matplotlib axes, you can use standard methods like -# ``plot()`` and ``imshow()`` with absolutely zero learning curve. +# :meth:`~ultraplot.axes.PlotAxes.plot` and +# :meth:`~ultraplot.axes.PlotAxes.imshow` with absolutely zero learning curve. # %% import numpy as np @@ -71,8 +72,10 @@ # # This is where UltraPlot drastically reduces boilerplate code. In Matplotlib, # formatting these two axes would require six separate lines of code (using -# ``set_title()``, ``set_xlabel()``, and ``set_ylabel()``). UltraPlot introduces -# a unified ``format()`` method, allowing you to configure titles, labels, +# :meth:`~matplotlib.axes.Axes.set_title`, +# :meth:`~matplotlib.axes.Axes.set_xlabel`, and +# :meth:`~matplotlib.axes.Axes.set_ylabel`). UltraPlot introduces the unified +# :meth:`~ultraplot.axes.Axes.format` method, allowing you to configure titles, labels, # limits, and styling all in a single, readable function call per axis. # %% @@ -98,10 +101,12 @@ # ----------------------------------------- # # Adding colorbars and legends in Matplotlib is notorious for ruining layouts—they -# often overlap with data or require tedious ``GridSpec`` wrangling. UltraPlot +# often overlap with data or require tedious :class:`~matplotlib.gridspec.GridSpec` +# wrangling. UltraPlot # solves this natively. By passing simple location strings like ``loc="t"`` (top) -# or ``loc="r"`` (right) to figure-level guide commands, UltraPlot allocates -# dedicated space *outside* the subplots without shrinking or distorting your axes. +# or ``loc="r"`` (right) to :meth:`~ultraplot.figure.Figure.legend` and +# :meth:`~ultraplot.axes.Axes.colorbar`, UltraPlot allocates dedicated space +# *outside* the subplots without shrinking or distorting your axes. # %% import numpy as np @@ -157,7 +162,7 @@ # To truly see UltraPlot's power, let's create a complex, publication-ready # figure. In standard Matplotlib, combining a custom mosaic layout, a geographic # projection, an inset axes, and A-B-C panel labels usually results in hundreds -# of lines of fragile ``GridSpec`` and ``transform`` code. +# of lines of fragile :class:`~matplotlib.gridspec.GridSpec` and transform code. # # UltraPlot condenses all of this into a highly readable, declarative script. # Notice how we assign a projection to just one panel using a dictionary, diff --git a/docs/recipes.py b/docs/recipes.py index fdda3fcc4..c78e86028 100644 --- a/docs/recipes.py +++ b/docs/recipes.py @@ -21,7 +21,7 @@ import ultraplot as uplt -# %% [markdown] +# %% [raw] raw_mimetype="text/restructuredtext" # Labelled line # ------------- # Use ``label`` on each artist and format the axes in one place. @@ -35,7 +35,7 @@ ax.legend(loc="ur") -# %% [markdown] +# %% [raw] raw_mimetype="text/restructuredtext" # Shared-axis grid # ---------------- # ``share="labels"`` keeps the grid readable while retaining tick labels. @@ -48,10 +48,11 @@ axs.format(xlabel="x", ylabel="y", suptitle="A shared-axis grid") -# %% [markdown] +# %% [raw] raw_mimetype="text/restructuredtext" # Image with a colorbar # --------------------- -# Plot the returned mappable and request an outer colorbar with ``colorbar``. +# Plot the returned mappable and request an outer colorbar with +# :meth:`~ultraplot.axes.Axes.colorbar`. # %% image = np.outer(np.sin(x[:60]), np.cos(x[:60])) @@ -60,10 +61,11 @@ ax.format(title="Image data", xformatter="none", yformatter="none") -# %% [markdown] +# %% [raw] raw_mimetype="text/restructuredtext" # Figure-wide legend # ------------------ -# A figure legend collects labelled artists across the selected axes. +# A :meth:`~ultraplot.figure.Figure.legend` collects labelled artists across +# the selected axes. # %% fig, axs = uplt.subplots(ncols=2, refwidth=2.2) @@ -74,10 +76,11 @@ fig.legend(loc="b", ncols=2) -# %% [markdown] +# %% [raw] raw_mimetype="text/restructuredtext" # Publication-sized export # ------------------------ -# Set a physical figure width and write a vector file for a manuscript. +# Set a physical figure width and write a vector file for a manuscript with +# :meth:`~ultraplot.figure.Figure.save`. # %% fig, ax = uplt.subplots(figwidth="89mm", refaspect=1.6) diff --git a/docs/why_ultraplot.py b/docs/why_ultraplot.py index 0c3a0fa47..1bc751f23 100644 --- a/docs/why_ultraplot.py +++ b/docs/why_ultraplot.py @@ -82,9 +82,11 @@ # The UltraPlot approach # ---------------------- # -# Notice that the actual drawing commands (``plot`` and ``imshow``) are identical -# to the Matplotlib version above. The magic happens with ``uplt.subplots`` and -# the ``format`` method. +# Notice that the actual drawing commands, +# :meth:`~ultraplot.axes.PlotAxes.plot` and +# :meth:`~ultraplot.axes.PlotAxes.imshow`, are identical to the Matplotlib +# version above. UltraPlot adds :func:`~ultraplot.ui.subplots` for figure +# construction and :meth:`~ultraplot.axes.Axes.format` for panel formatting. # # Instead of scattering setter methods across your script, UltraPlot lets you # define figure layouts and shared labels cohesively. As your figures grow in From 8f92f5b64327cc8f5a8f3a312168a456c19140bf Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 15:49:03 +1000 Subject: [PATCH 07/10] center the img --- docs/index.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/index.rst b/docs/index.rst index 24798a42b..d45d58be5 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -86,6 +86,9 @@ Topics **Data-aware plotting** ^^^ + .. image:: _static/example_plots/cartesian_example.svg + :align: center + Plot labelled pandas and xarray data with metadata-aware labels and coordinates. From 9f048fea4195bd84e5eafda54a4079c8bc7d0ef5 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 16:36:31 +1000 Subject: [PATCH 08/10] Add data-aware plotting introduction --- .gitignore | 2 + .../example_plots/data_aware_example.svg | 1144 +++++++++++++++++ docs/data_aware.py | 63 + docs/index.rst | 5 +- 4 files changed, 1212 insertions(+), 2 deletions(-) create mode 100644 docs/_static/example_plots/data_aware_example.svg create mode 100644 docs/data_aware.py diff --git a/.gitignore b/.gitignore index fc0755a70..9d39a53cb 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,8 @@ docs/_static/ultraplotrc docs/_static/rctable.rst docs/_static/* !docs/_static/why_plots/ +!docs/_static/example_plots/ +!docs/_static/example_plots/data_aware_example.svg *.html docs/gallery/ docs/sg_execution_times.rst diff --git a/docs/_static/example_plots/data_aware_example.svg b/docs/_static/example_plots/data_aware_example.svg new file mode 100644 index 000000000..30e6781ae --- /dev/null +++ b/docs/_static/example_plots/data_aware_example.svg @@ -0,0 +1,1144 @@ + + + + + + + + image/svg+xml + + + Matplotlib v3.10.9, https://matplotlib.org/ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/docs/data_aware.py b/docs/data_aware.py new file mode 100644 index 000000000..09b40c98d --- /dev/null +++ b/docs/data_aware.py @@ -0,0 +1,63 @@ +# --- +# jupyter: +# jupytext: +# text_representation: +# extension: .py +# format_name: percent +# format_version: '1.3' +# jupytext_version: 1.11.4 +# kernelspec: +# display_name: Python 3 +# language: python +# name: python3 +# --- + +# %% [raw] raw_mimetype="text/restructuredtext" +# .. _ug_data_aware: +# +# Data-aware plotting +# =================== +# +# UltraPlot recognizes labelled data from pandas and xarray. Pass a pandas +# Series or DataFrame directly to an axes method and UltraPlot can use its +# index, column names, and axis metadata to choose coordinates and labels. +# This keeps the plotting call focused on the data instead of repeating its +# description in several formatting arguments. +# +# The example below starts with a small, labelled DataFrame. The date index +# becomes the horizontal coordinate, and the column names become legend labels. + +# %% +import numpy as np +import pandas as pd +import ultraplot as uplt + +dates = pd.date_range("2025-01-01", periods=24, freq="MS") +season = np.sin(np.linspace(0, 2 * np.pi, dates.size)) +data = pd.DataFrame( + { + "observed": 18 + 3 * season, + "smoothed": 18 + 2.5 * season, + }, + index=dates, +) +data.index.name = "Date" + +fig, ax = uplt.subplots(refwidth=3.4) +ax.plot(data) +ax.format(title="Monthly temperature", ylabel="Temperature (°C)") +ax.legend(loc="r") + + +# %% [raw] raw_mimetype="text/restructuredtext" +# What UltraPlot inferred +# ------------------------ +# +# The DataFrame index supplies the x coordinates, while its column labels are +# available to the legend. You can still override any inferred value with the +# usual plotting or :meth:`~ultraplot.axes.Axes.format` keyword arguments. +# +# For data arrays, coordinate inference works the same way with xarray. See the +# detailed :ref:`1D integration guide ` and +# :ref:`2D integration guide ` for MultiIndex data, +# DataArrays, and labelled two-dimensional plots. diff --git a/docs/index.rst b/docs/index.rst index d45d58be5..b99854b6e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -79,14 +79,14 @@ Topics Place and align guides across individual subplots or a whole figure. .. grid-item-card:: - :link: 1dplots.html + :link: data_aware.html :shadow: md :class-card: card-with-bottom-text **Data-aware plotting** ^^^ - .. image:: _static/example_plots/cartesian_example.svg + .. image:: _static/example_plots/data_aware_example.svg :align: center Plot labelled pandas and xarray data with metadata-aware labels and @@ -148,6 +148,7 @@ For details, see the full :doc:`User guide ` and basics subplots cartesian + data_aware networks colorbars_legends colormaps From ebb50886dccd935a9a7b4cc960d1bef75f69f134 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 16:53:05 +1000 Subject: [PATCH 09/10] Place data-aware legend in upper right --- .../example_plots/data_aware_example.svg | 281 +++++++++--------- docs/data_aware.py | 4 +- 2 files changed, 137 insertions(+), 148 deletions(-) diff --git a/docs/_static/example_plots/data_aware_example.svg b/docs/_static/example_plots/data_aware_example.svg index 30e6781ae..bdacb3543 100644 --- a/docs/_static/example_plots/data_aware_example.svg +++ b/docs/_static/example_plots/data_aware_example.svg @@ -1,7 +1,7 @@ - + @@ -21,8 +21,8 @@ @@ -30,8 +30,8 @@ z @@ -41,16 +41,16 @@ z +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - - + @@ -121,18 +121,18 @@ z - + - + - + @@ -142,53 +142,53 @@ L 138.615708 17.42 - - + - + - + - + - + - + - + @@ -200,17 +200,17 @@ L 0 2 +L 235.26 328.785618 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - - + @@ -254,12 +254,12 @@ z +L 235.26 279.302079 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -293,12 +293,12 @@ z +L 235.26 229.818539 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -324,12 +324,12 @@ z +L 235.26 180.335 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -368,12 +368,12 @@ z +L 235.26 130.851461 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -407,12 +407,12 @@ z +L 235.26 81.367921 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -458,12 +458,12 @@ z +L 235.26 31.884382 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - + @@ -477,187 +477,187 @@ L 235.06 31.884382 - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -670,57 +670,57 @@ L -2 0 +L 42.180286 140.283503 +L 50.273448 103.202441 +L 59.233734 71.841949 +L 67.904979 48.527891 +L 76.865265 34.989362 +L 85.536509 32.230455 +L 94.496795 40.455784 +L 103.457082 59.055314 +L 112.128326 86.649605 +L 121.088612 121.192112 +L 129.759857 160.120977 +L 138.720143 200.549023 +L 147.680429 239.477888 +L 155.773591 274.020395 +L 164.733877 301.614686 +L 173.405122 320.214216 +L 182.365408 328.439545 +L 191.036652 325.680638 +L 199.996938 312.142109 +L 208.957225 288.828051 +L 217.628469 257.467559 +L 226.588755 220.386497 +L 235.26 180.335 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #1f77b4; stroke-width: 1.5; stroke-linecap: square"/> +L 42.180286 146.958752 +L 50.273448 116.057868 +L 59.233734 89.924124 +L 67.904979 70.495742 +L 76.865265 59.213635 +L 85.536509 56.914545 +L 94.496795 63.768986 +L 103.457082 79.268595 +L 112.128326 102.263837 +L 121.088612 131.04926 +L 129.759857 163.489981 +L 138.720143 197.180019 +L 147.680429 229.62074 +L 155.773591 258.406163 +L 164.733877 281.401405 +L 173.405122 296.901014 +L 182.365408 303.755455 +L 191.036652 301.456365 +L 199.996938 290.174258 +L 208.957225 270.745876 +L 217.628469 244.612132 +L 226.588755 213.711248 +L 235.26 180.335 +" clip-path="url(#pe6b65707ee)" style="fill: none; stroke: #ff7f0e; stroke-width: 1.5; stroke-linecap: square"/> - - + - - - - - - - + - - + - + - + - + + diff --git a/docs/data_aware.py b/docs/data_aware.py index 09b40c98d..af0671359 100644 --- a/docs/data_aware.py +++ b/docs/data_aware.py @@ -43,10 +43,10 @@ ) data.index.name = "Date" -fig, ax = uplt.subplots(refwidth=3.4) +fig, ax = uplt.subplots() ax.plot(data) ax.format(title="Monthly temperature", ylabel="Temperature (°C)") -ax.legend(loc="r") +ax.legend(loc="ur", ncols=1) # %% [raw] raw_mimetype="text/restructuredtext" From 3fa69e52630bba6227d909db59579e457f3b62c5 Mon Sep 17 00:00:00 2001 From: cvanelteren Date: Fri, 4 Sep 2026 17:14:46 +1000 Subject: [PATCH 10/10] Use default sizing for data-aware preview --- .../example_plots/data_aware_example.svg | 676 +++++++++--------- 1 file changed, 351 insertions(+), 325 deletions(-) diff --git a/docs/_static/example_plots/data_aware_example.svg b/docs/_static/example_plots/data_aware_example.svg index bdacb3543..6d495e0f5 100644 --- a/docs/_static/example_plots/data_aware_example.svg +++ b/docs/_static/example_plots/data_aware_example.svg @@ -1,7 +1,7 @@ - + @@ -20,18 +20,18 @@ - - @@ -39,23 +39,23 @@ z - +" clip-path="url(#pd7713a8a22)" style="fill: none; stroke: #000000; stroke-opacity: 0.1; stroke-width: 0.6; stroke-linecap: square"/> - - + - + - + - + - + @@ -142,53 +142,53 @@ L 138.720143 17.42 - - + - + - + - + - + - + - + @@ -199,23 +199,23 @@ L 0 2 - + - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + @@ -477,300 +477,358 @@ L 235.26 31.884382 - - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - + - - - - - + + - - + + + + - - - + - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + - - + - - + @@ -928,53 +1026,14 @@ z - - - - - - + @@ -989,7 +1048,7 @@ z - + - + - - - + +