# --- # 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_layout: # # Subplots # ======== # # This section documents a variety of features related to UltraPlot subplots, # including a-b-c subplot labels, axis sharing between subplots, automatic # "tight layout" spacing between subplots, and a unique feature where the figure # width and/or height are automatically adjusted based on the subplot geometry. # # .. note:: # # UltraPlot only supports one :class:`~ultraplot.gridspec.GridSpec` per figure # (see the section on :ref:`adding subplots `), and UltraPlot # does not officially support the "nested" matplotlib structures # :class:`~matplotlib.gridspec.GridSpecFromSubplotSpec` and :class:`~matplotlib.figure.SubFigure`. # These restrictions have the advantage of 1) considerably simplifying the # :ref:`tight layout ` and :ref:`figure size ` # algorithms and 2) reducing the ambiguity of :ref:`a-b-c label assignment ` # and :ref:`automatic axis sharing ` between subplots. If you need the # features associated with "nested" matplotlib structures, some are reproducible # with UltraPlot -- including :ref:`different spaces ` between distinct # subplot rows and columns and :ref:`different formatting ` for # distinct groups of subplots. # # # %% [raw] raw_mimetype="text/restructuredtext" # .. _ug_abc: # # A-b-c labels # ------------ # # UltraPlot can quickly add labels to subplots using the `abc` parameter. This parameter # can be a template (with a letter "a" or "A") used to format the subplot labels, such as "A.", which assigns # an alphabetic label based on the axis number. Alternatively, you can pass a list to # the `abc` parameter, where the list elements are mapped as labels for the subplots one by one. # If you add subplots one-by-one with :func:`~ultraplot.figure.Figure.add_subplot`, # you can manually specify the number with the `number` keyword. By default, the subplot # number is incremented by ``1`` each time you call :func:`~ultraplot.figure.Figure.add_subplot`. # If you draw all of your subplots at once with :func:`~ultraplot.figure.Figure.add_subplots`, # the numbers depend on the input arguments. If you :ref:`passed an array `, # the subplot numbers correspond to the numbers in the array. But if you used the `ncols` # and `nrows` keyword arguments, the number order is row-major by default and can be switched # to column-major by passing ``order='F'`` (note the number order also determines the list order # in the :class:`~ultraplot.gridspec.SubplotGrid` returned by :func:`~ultraplot.figure.Figure.add_subplots`). # # To turn on "a-b-c" labels, set :rcraw:`abc` to ``True`` or pass ``abc=True`` # to :func:`~ultraplot.axes.Axes.format` (see :ref:`the format command ` # for details). To change the label style, set :rcraw:`abc` to e.g. ``'A.'`` or # pass e.g. ``abc='A.'`` to :func:`~ultraplot.axes.Axes.format`. You can also modify # the "a-b-c" label location, weight, and size with the :rcraw:`abc.loc`, # :rcraw:`abc.weight`, and :rcraw:`abc.size` settings. Also note that if the # an "a-b-c" label and title are in the same position, they are automatically # offset away from each other. # # .. note:: # # "Inner" a-b-c labels and titles are surrounded with a white border when # :rcraw:`abc.border` and :rcraw:`title.border` are ``True`` (the default). # White boxes can be used instead by setting :rcraw:`abc.bbox` and # :rcraw:`title.bbox` to ``True``. These options help labels stand out # against plotted content. Any text can be given "borders" or "boxes" by # passing ``border=True`` or ``bbox=True`` to `ultraplot.axes.Axes.text`. # %% import ultraplot as uplt fig = uplt.figure(space=0, refwidth="10em") axs = fig.subplots(nrows=3, ncols=3) axs.format( abc="A.", abcloc="ul", xticks="null", yticks="null", facecolor="gray5", xlabel="x axis", ylabel="y axis", suptitle="A-b-c label offsetting, borders, and boxes", ) axs[:3].format(abcloc="l", titleloc="l", title="Title") axs[-3:].format(abcbbox=True) # also disables abcborder # axs[:-3].format(abcborder=True) # this is already the default # %% import ultraplot as uplt fig = uplt.figure(space=0, refwidth=0.7) axs = fig.subplots(nrows=8, ncols=8) axs.format( abc=True, abcloc="ur", xlabel="x axis", ylabel="y axis", xticks=[], yticks=[], suptitle="A-b-c label stress test", ) # %% [raw] raw_mimetype="text/restructuredtext" # .. _ug_autosize: # # Figure width and height # ----------------------- # # UltraPlot automatically adjusts the figure width and height by default to # respect the physical size of a "reference" subplot and the geometry of the # :func:`~ultraplot.figure.Figure.gridspec`. The "reference" subplot is the subplot whose # :func:`~ultraplot.axes.Axes.number` matches the `refnum` that was passed to # :class:`~ultraplot.figure.Figure` (the default `refnum` of ``1`` usually matches the subplot # in the upper-left corner -- see :ref:`this section ` for more on subplot # numbers). Alternatively, you can request a fixed figure width (height), and the # algorithm will automatically adjusts the figure height (width) to respect # the :func:`~ultraplot.figure.Figure.gridspec` geometry. # This algorithm is extremely powerful and generally produces more aesthetically # pleasing subplot grids out-of-the-box, especially when they contain images or map # projections (see below). It is constrained by the following :class:`~ultraplot.figure.Figure` # keyword arguments: # # * `refwidth` and `refheight` set the physical width and height of the reference # subplot (default is :rc:`subplots.refwidth`). If just the width (height) is # specified, then the height (width) is automatically adjusted to satisfy the # subplot spacing and the reference subplot aspect ratio `refaspect` (default # is ``1`` unless the data aspect ratio is fixed -- see below). If both the # width and height are specified, then `refaspect` is ignored. # * `figwidth` and `figheight` set the physical width and height of the figure. # As in matplotlib, you can use `figsize` to set both at once. If just the width # (height) is specified, then the height (width) is automatically adjusted, just # like with `refwidth` and `refheight`. If both the width and height are specified # (e.g., using `figsize`), then `refaspect` is ignored and the figure size is fixed. # Note that `figwidth` and `figheight` always override `refwidth` and `refheight`. # * `journal` sets the physical dimensions of the figure to meet requirements # for submission to an academic journal. For example, ``journal='nat1'`` sets # `figwidth` according to the `*Nature* standard for single-column figures # `__ and # ``journal='aaas2'`` sets `figwidth` according to the # `*Science* standard for dual-column figures # `__. # See :ref:`this table ` for the currently available journal # specifications (feel free to add to this list with a :ref:`pull request # `). # # The below examples demonstrate how different keyword arguments and # subplot arrangements influence the figure size algorithm. # # .. important:: # # * If the `data aspect ratio # `__ # of the reference subplot is fixed (either due to calling # :func:`~matplotlib.axes.Axes.set_aspect` or filling the subplot with a # :ref:`geographic projection `, :func:`~ultraplot.axes.PlotAxes.imshow` # plot, or :func:`~ultraplot.axes.PlotAxes.heatmap` plot), then this is used as # the default value for the reference aspect ratio `refaspect`. This helps # minimize excess space between grids of subplots with fixed aspect ratios. # * For the simplest subplot grids (e.g., those created by passing integers to # :func:`~ultraplot.figure.Figure.add_subplot` or passing `ncols` or `nrows` to # :func:`~ultraplot.figure.Figure.add_subplots`) the keyword arguments `refaspect`, # `refwidth`, and `refheight` effectively apply to every subplot in the # figure -- not just the reference subplot. # * The physical widths of UltraPlot :func:`~ultraplot.axes.Axes.colorbar`\ s and # :func:`~ultraplot.axes.Axes.panel`\ s are always independent of the figure size. # :class:`~ultraplot.gridspec.GridSpec` specifies their widths in physical units to help # users avoid drawing colorbars and panels that look "too skinny" or "too fat" # depending on the number of subplots in the figure. # %% import ultraplot as uplt import numpy as np # Grid of images (note the square pixels) state = np.random.RandomState(51423) colors = np.tile(state.rand(8, 12, 1), (1, 1, 3)) fig, axs = uplt.subplots(ncols=3, nrows=2, refwidth=1.7) fig.format(suptitle="Auto figure dimensions for grid of images") for ax in axs: ax.imshow(colors) # Grid of cartopy projections fig, axs = uplt.subplots(ncols=2, nrows=3, proj="robin", share=0) axs.format(land=True, landcolor="k") fig.format(suptitle="Auto figure dimensions for grid of cartopy projections") # %% import ultraplot as uplt uplt.rc.update(grid=False, titleloc="uc", titleweight="bold", titlecolor="red9") # Change the reference subplot width suptitle = "Effect of subplot width on figure size" for refwidth in ("3cm", "5cm"): fig, axs = uplt.subplots( ncols=2, refwidth=refwidth, ) axs[0].format(title=f"refwidth = {refwidth}", suptitle=suptitle) # Change the reference subplot aspect ratio suptitle = "Effect of subplot aspect ratio on figure size" for refaspect in (1, 2): fig, axs = uplt.subplots(ncols=2, refwidth=1.6, refaspect=refaspect) axs[0].format(title=f"refaspect = {refaspect}", suptitle=suptitle) # Change the reference subplot suptitle = "Effect of reference subplot on figure size" for ref in (1, 2): # with different width ratios fig, axs = uplt.subplots(ncols=3, wratios=(3, 2, 2), ref=ref, refwidth=1.1) axs[ref - 1].format(title="reference", suptitle=suptitle) for ref in (1, 2): # with complex subplot grid fig, axs = uplt.subplots([[1, 2], [1, 3]], refnum=ref, refwidth=1.8) axs[ref - 1].format(title="reference", suptitle=suptitle) uplt.rc.reset() # %% [raw] raw_mimetype="text/restructuredtext" # .. _ug_tight: # # Spacing and tight layout # ------------------------ # # UltraPlot automatically adjusts the spacing between subplots # by default to accomadate labels using its own `"tight layout" algorithm # `__. # In contrast to matplotlib's algorithm, UltraPlot's algorithm can :ref:`change the # figure size ` and permits variable spacing between each subplot # row and column (see :class:`ultraplot.gridspec.GridSpec` for details). # This algorithm can be disabled entirely by passing ``tight=False`` to # :class:`~ultraplot.figure.Figure` or by setting :rcraw:`subplots.tight` to ``False``, or # it can be partly overridden by passing any of the spacing arguments `left`, `right`, # `top`, `bottom`, `wspace`, or `hspace` to :class:`~ultraplot.figure.Figure` or # :class:`~ultraplot.gridspec.GridSpec`. For example: # # * ``left=2`` fixes the left margin at 2 em-widths, while the right, # bottom, and top margin widths are determined by the tight layout algorithm. # * ``wspace=1`` fixes the space between subplot columns at 1 em-width, while the # space between subplot rows is determined by the tight layout algorithm. # * ``wspace=(3, None)`` fixes the space between the first two columns of # a three-column plot at 3 em-widths, while the space between the second two # columns is determined by the tight layout algorithm. # # The padding between the tight layout extents (rather than the absolute spaces # between subplot edges) can also be changed by passing `outerpad`, `innerpad`, # or `panelpad` to :class:`~ultraplot.figure.Figure` or :class:`~ultraplot.gridspec.GridSpec`. # This padding can be set locally by passing an array of values to `wpad` # and `hpad` (analogous to `wspace` and `hspace`), or by passing the `pad` # keyword when creating :ref:`panel axes ` or :ref:`outer # colorbars or legends ` (analogous to `space`). # # All the subplot spacing arguments can be specified with a # :ref:`unit string ` interpreted by :func:`~ultraplot.utils.units`. # The default unit assumed for numeric arguments is an "em-width" (i.e., a # :rcraw:`font.size` width -- see the :ref:`units table ` for details). # # .. note:: # The core behavior of the tight layout algorithm can be modified with a few # keyword arguments and settings. Using ``wequal=True``, ``hequal=True``, or # ``equal=True`` (or setting :rcraw:`subplots.equalspace` to ``True``) constrains # the tight layout algorithm to produce equal spacing between main subplot columns # or rows (note that equal spacing is the default behavior when tight layout is # disabled). Similarly, using ``wgroup=False``, ``hgroup=False``, or ``group=False`` # (or setting :rcraw:`subplots.groupspace` to ``False``) disables the default # behavior of only comparing subplot extent between adjacent subplot "groups" # and instead compares subplot extents across entire columns and rows # (note the spacing between the first and second row in the below example). # %% import ultraplot as uplt # Stress test of the tight layout algorithm # This time override the algorithm between selected subplot rows/columns fig, axs = uplt.subplots( ncols=4, nrows=3, refwidth=1.1, span=False, bottom="5em", right="5em", # margin spacing overrides wspace=(0, 0, None), hspace=(0, None), # column and row spacing overrides ) axs.format( grid=False, xlocator=1, ylocator=1, tickdir="inout", xlim=(-1.5, 1.5), ylim=(-1.5, 1.5), suptitle="Tight layout with user overrides", toplabels=("Column 1", "Column 2", "Column 3", "Column 4"), leftlabels=("Row 1", "Row 2", "Row 3"), ) axs[0, :].format(xtickloc="top") axs[2, :].format(xtickloc="both") axs[:, 1].format(ytickloc="neither") axs[:, 2].format(ytickloc="right") axs[:, 3].format(ytickloc="both") axs[-1, :].format(xlabel="xlabel", title="Title\nTitle\nTitle") axs[:, 0].format(ylabel="ylabel") # %% import ultraplot as uplt # Stress test of the tight layout algorithm # Add large labels along the edge of one subplot equals = [("unequal", False), ("unequal", False), ("equal", True)] groups = [("grouped", True), ("ungrouped", False), ("grouped", True)] for (name1, equal), (name2, group) in zip(equals, groups): suffix = " (default)" if group and not equal else "" suptitle = f'Tight layout with "{name1}" and "{name2}" row-column spacing{suffix}' fig, axs = uplt.subplots( nrows=3, ncols=3, refwidth=1.1, share=False, equal=equal, group=group, ) axs[1].format(xlabel="xlabel\nxlabel", ylabel="ylabel\nylabel\nylabel\nylabel") axs[3:6:2].format( title="Title\nTitle", titlesize="med", ) axs.format( grid=False, toplabels=("Column 1", "Column 2", "Column 3"), leftlabels=("Row 1", "Row 2", "Row 3"), suptitle=suptitle, ) # %% [raw] raw_mimetype="text/restructuredtext" tags=[] # .. _ug_share: # # Axis label sharing # ------------------ # # Figures with lots of subplots often have :ref:`redundant labels `. # To help address this, the matplotlib command `matplotlib.pyplot.subplots` includes # `sharex` and `sharey` keywords that permit sharing axis limits and ticks between # like rows and columns of subplots. UltraPlot builds on this feature by: # # #. Automatically sharing axes between subplots and :ref:`panels ` # occupying the same rows or columns of the :class:`~ultraplot.gridspec.GridSpec`. This # works for :ref:`aribtrarily complex subplot grids `. It also works # for subplots generated one-by-one with :func:`~ultraplot.figure.Figure.add_subplot` # rather than :func:`~ultraplot.figure.Figure.subplots`. It is controlled by the `sharex` # and `sharey` :class:`~ultraplot.figure.Figure` keywords (default is :rc:`subplots.share`). # Use the `share` keyword as a shorthand to set both `sharex` and `sharey`. # #. Automatically sharing labels across subplots and :ref:`panels ` # with edges along the same row or column of the :class:`~ultraplot.gridspec.GridSpec`. # This also works for complex subplot grids and subplots generated one-by-one. # It is controlled by the `spanx` and `spany` :class:`~ultraplot.figure.Figure` # keywords (default is :rc:`subplots.span`). Use the `span` keyword # as a shorthand to set both `spanx` and `spany`. Note that unlike # `~matplotlib.figure.Figure.supxlabel` and `~matplotlib.figure.Figure.supylabel`, # these labels are aligned between gridspec edges rather than figure edges. # #. Supporting five sharing "levels". These values can be passed to `sharex`, # `sharey`, or `share`, or assigned to :rcraw:`subplots.share`. The levels # are defined as follows: # # * ``False`` or ``0``: Axis sharing is disabled. # * ``'labels'``, ``'labs'``, or ``1``: Axis labels are shared, but nothing else. # Labels will appear on the outermost plots. This implies that for left and bottom # labels (default), the labels will appear on the leftmost and bottommost subplots. # Note that labels will be shared only for plots that are immediately adjacent # in the same row or column of the :class:`~ultraplot.gridspec.GridSpec`; a space # or empty plot will add the labels, but not break the limit sharing. See below # for a more complex example. # # The below examples demonstrate the effect of various axis and label sharing # settings on the appearance of several subplot grids. # %% import ultraplot as uplt import numpy as np N = 50 M = 40 state = np.random.RandomState(51423) cycle = uplt.Cycle("grays_r", M, left=0.1, right=0.8) datas = [] for scale in (1, 3, 7, 0.2): data = scale * (state.rand(N, M) - 0.5).cumsum(axis=0)[N // 2 :, :] datas.append(data) # Plots with different sharing and spanning settings # Note that span=True and share=True are the defaults spans = (False, False, True, True) shares = (False, "labels", "limits", True) for i, (span, share) in enumerate(zip(spans, shares)): fig = uplt.figure(refaspect=1, refwidth=1.06, spanx=span, sharey=share) axs = fig.subplots(ncols=4) for ax, data in zip(axs, datas): on = ("off", "on")[int(span)] ax.plot(data, cycle=cycle) ax.format( grid=False, xlabel="spanning axis", ylabel="shared axis", suptitle=f"Sharing mode {share!r} (level {i}) with spanning labels {on}", ) # %% import ultraplot as uplt import numpy as np state = np.random.RandomState(51423) # Plots with minimum and maximum sharing settings # Note that all x and y axis limits and ticks are identical spans = (False, True) shares = (False, "all") titles = ("Minimum sharing", "Maximum sharing") for span, share, title in zip(spans, shares, titles): fig = uplt.figure(refwidth=1, span=span, share=share) axs = fig.subplots(nrows=4, ncols=4) for ax in axs: data = (state.rand(100, 20) - 0.4).cumsum(axis=0) ax.plot(data, cycle="Set3") axs.format( abc=True, abcloc="ul", suptitle=title, xlabel="xlabel", ylabel="ylabel", grid=False, xticks=25, yticks=5, ) # %% [raw] raw_mimetype="text/restructuredtext" # When subplots are arranged on a grid, UltraPlot will # automatically share axis labels where appropriate. For more # complex layouts, UltraPlot will add the labels when the subplot # is facing and "edge" which is defined as not immediately having a subplot next to it. For example: # %% import ultraplot as uplt, numpy as np layout = [[1, 0, 2], [0, 3, 0], [4, 0, 6]] fig, ax = uplt.subplots(layout) ax.format(xtickloc="top", ytickloc="right") # plot data to indicate that limits are still shared x = y = np.linspace(0, 1, 10) for axi in ax: axi.plot(axi.number * x, axi.number * y) # %% [raw] raw_mimetype="text/restructuredtext" # Notice how the top and right labels here are added since no # subplot is immediately adjacent to another, the limits however, are shared. # %% [raw] raw_mimetype="text/restructuredtext" # .. _ug_units: # # Physical units # -------------- # # UltraPlot supports arbitrary physical units for controlling the figure # `figwidth` and `figheight`; the reference subplot `refwidth` and `refheight`; # the gridspec spacing and tight layout padding keywords `left`, `right`, `bottom`, # `top`, `wspace`, `hspace`, `outerpad`, `innerpad`, `panelpad`, `wpad`, and `hpad`; # the :func:`~ultraplot.axes.Axes.colorbar` and :func:`~ultraplot.axes.Axes.panel` widths; # various :meth:`~ultraplot.axes.Axes.legend` spacing and padding arguments; various # :func:`~ultraplot.axes.Axes.format` font size and padding arguments; the line width and # marker size arguments passed to :class:`~ultraplot.axes.PlotAxes` commands; and all # applicable :func:`~ultraplot.config.rc` settings, e.g. :rcraw:`subplots.refwidth`, # :rcraw:`legend.columnspacing`, and :rcraw:`axes.labelpad`. This feature is # powered by the physical units engine :func:`~ultraplot.utils.units`. # # When one of these keyword arguments is numeric, a default physical unit is # used. For subplot and figure sizes, the defult unit is inches. For gridspec and # legend spaces, the default unit is `em-widths # `__. # For font sizes, text padding, and # line widths, the default unit is # `points `__. # See the relevant documentation in the :ref:`API reference ` for details. # A table of acceptable physical units is found :ref:`here ` # -- they include centimeters, millimeters, pixels, # `em-widths `__, # `en-heights `__, # and `points `__. # %% import ultraplot as uplt import numpy as np with uplt.rc.context(fontsize="12px"): # depends on rc['figure.dpi'] fig, axs = uplt.subplots( ncols=3, figwidth="15cm", figheight="3in", wspace=("10pt", "20pt"), right="10mm", ) cb = fig.colorbar( "Mono", loc="b", extend="both", label="colorbar", width="2em", extendsize="3em", shrink=0.8, ) pax = axs[2].panel_axes("r", width="5en") axs.format( suptitle="Arguments with arbitrary units", xlabel="x axis", ylabel="y axis", )