matplotlib/matplotlib · error · ValueError

total and sep cannot both be None when using layout mode 'eq

Error message

total and sep cannot both be None when using layout mode 'equal'

What it means

offsetbox._get_packed_offsets, the layout engine used by HPacker/VPacker (and everything built on them, like AnnotationBbox legends), distributes children according to mode. In mode='equal' every child gets an equal slice of the available space; the total slice size is derived either from the packer's fixed width/height ('total') or from the per-item gap 'sep'. When both total and sep are None the spacing is underdetermined, so it raises ValueError("total and sep cannot both be None when using layout mode 'equal'").

Source

Thrown at lib/matplotlib/offsetbox.py:142

    elif mode == "expand":
        # This is a bit of a hack to avoid a TypeError when *total*
        # is None and used in conjugation with tight layout.
        if total is None:
            total = 1
        if len(widths) > 1:
            sep = (total - sum(widths)) / (len(widths) - 1)
        else:
            sep = 0
        offsets_ = np.cumsum([0] + [w + sep for w in widths])
        offsets = offsets_[:-1]
        return total, offsets

    elif mode == "equal":
        maxh = max(widths)
        if total is None:
            if sep is None:
                raise ValueError("total and sep cannot both be None when "
                                 "using layout mode 'equal'")
            total = (maxh + sep) * len(widths)
        else:
            sep = total / len(widths) - maxh
        offsets = (maxh + sep) * np.arange(len(widths))
        return total, offsets


def _get_aligned_offsets(yspans, height, align="baseline"):
    """
    Align boxes each specified by their ``(y0, y1)`` spans.

    For simplicity of the description, the terminology used here assumes a
    horizontal layout (i.e., vertical alignment), but the function works
    equally for a vertical layout.

    Parameters
    ----------

View on GitHub (pinned to b379c1b69e)

Solutions

  1. Give the packer an explicit size: HPacker(..., width=300, mode='equal') (width/height are in pixels).
  2. Or give an explicit separator: HPacker(..., sep=5, mode='equal') (sep in points).
  3. If you want auto-sized tight packing, use the default mode='fixed' instead of 'equal'.
  4. Note the error fires at draw time — fix the constructor arguments in the traceback's packer creation frame, not where savefig appears.

Example fix

# before
box = offsetbox.HPacker(children=[a, b], mode='equal', sep=None, width=None)

# after
box = offsetbox.HPacker(children=[a, b], mode='equal', width=200)
Defensive patterns

Strategy: validation

Validate before calling

def make_hpacker(children, mode='equal', width=None, sep=None):
    if mode == 'equal' and width is None and sep is None:
        sep = 5  # or raise with a clear message naming the packer
    return offsetbox.HPacker(children=children, mode=mode, width=width, sep=sep)

Prevention

When it happens

Trigger: HPacker(children=[...], mode='equal', width=None, sep=None) or VPacker(mode='equal', height=None, sep=None); subclasses of PackerBase that forward None width/sep with mode='equal'; drawing (savefig/show) triggers _get_bbox_and_child_offsets, where the error surfaces at draw time.

Common situations: Migrating layouts that used mode='fixed' to 'equal' and passing sep=None to 'let it auto-size'; constructing custom OffsetBox toolbars/legends where the container size is only known later; the error appearing only at render time, confusing the stack trace.

Understand the failure class

Background: Missing required parameter errors: what 'X is required' and 'the required X param is missing' mean, and how to fix them — this error's family across 27 libraries.

Related errors


AI-assisted analysis of matplotlib/matplotlib@b379c1b69e (2026-08-21). Data as JSON: /api/errors/a0c4630116f5af7a. Report an issue: GitHub.