plotly/plotly.js · warning

ignored <axOut._name>.scaleanchor: "<axIn.scaleanchor>" to a

Error message

ignored <axOut._name>.scaleanchor: "<axIn.scaleanchor>" to avoid either an infinite loop and possibly inconsistent scaleratios, or because this axis declares a *matches* constraint.

What it means

A scaleanchor on axOut is ignored when honoring it would either create a constraint cycle/inconsistent scaleratios or because the same axis already declares a matches constraint (matches and scaleanchor cannot coexist). The axis keeps its independent range and this warning states the reason. RAISED IN: handleOneAxDefaults during layout defaults.

Source

Thrown at src/plots/cartesian/constraints.js:297

        updateConstraintGroups(constraintGroups, thisID, matches, matchRatio);
    } else if(axIn.matches && axIds.indexOf(axIn.matches) !== -1) {
        Lib.warn('ignored ' + axOut._name + '.matches: "' +
            axIn.matches + '" to avoid an infinite loop');
    }

    if(scaleanchor) {
        var scaleratio = coerce('scaleratio');

        // TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero,
        // but that seems hacky. Better way to say "must be a positive number"?
        // Of course if you use several super-tiny values you could eventually
        // force a product of these to zero and all hell would break loose...
        // Likewise with super-huge values.
        if(!scaleratio) scaleratio = axOut.scaleratio = 1;

        updateConstraintGroups(constraintGroups, thisID, scaleanchor, scaleratio);
    } else if(axIn.scaleanchor && axIds.indexOf(axIn.scaleanchor) !== -1) {
        Lib.warn('ignored ' + axOut._name + '.scaleanchor: "' +
            axIn.scaleanchor + '" to avoid either an infinite loop ' +
            'and possibly inconsistent scaleratios, or because this axis ' +
            'declares a *matches* constraint.');
    }
}

function extent(layoutOut, ax) {
    var domain = ax.domain;
    if(!domain) {
        // at this point overlaying axes haven't yet inherited their main domains
        // TODO: constrain: domain with overlaying axes is likely a bug.
        domain = layoutOut[id2name(ax.overlaying)].domain;
    }
    return domain[1] - domain[0];
}

function getConstraintGroup(groups, thisID) {
    for(var i = 0; i < groups.length; i++) {

View on GitHub (pinned to 1d090e0b5f)

Solutions

  1. Declare scaleanchor on only one axis of each linked pair (remove it from the axis receiving the warning).
  2. Choose either matches or scaleanchor for a given axis — remove the one that is not needed.
  3. Flatten scaleanchor chains so every axis anchors to one base axis with no cycles.

Example fix

// before
layout.xaxis2 = {matches: 'x', scaleanchor: 'x'};
// after
layout.xaxis2 = {matches: 'x'}; // or {scaleanchor: 'x', scaleratio: 1}, not both
Defensive patterns

Strategy: validation

Validate before calling

function checkScaleanchorConflicts(layout) {
  for (const ax of Object.keys(layout).filter(k => k.endsWith('axis'))) {
    const cfg = layout[ax];
    if (cfg.scaleanchor && cfg.matches) throw new Error(`${ax}: cannot combine scaleanchor and matches`);
    if (cfg.scaleanchor && (layout[cfg.scaleanchor] || {}).scaleanchor === ax) throw new Error(`${ax}: mutual scaleanchor with ${cfg.scaleanchor}`);
  }
}
checkScaleanchorConflicts(layout);

Type guard

const hasValidScaleanchor = (layout, ax) => { const sa = (layout[ax] || {}).scaleanchor; return !sa || (!(layout[ax] || {}).matches && (layout[sa] || {}).scaleanchor !== ax); };

Prevention

When it happens

Trigger: Setting layout.xaxis2.scaleanchor = 'x' where x is already scaleanchored back to x2 (cycle), or where xaxis2 also has matches set (conflict), or chains of scaleanchors that would loop.

Common situations: Duplicating scaleanchor on both axes of a pair; combining matches and scaleanchor on the same axis expecting both effects; multi-panel figures where several scaleanchors were added incrementally forming a loop.

Related errors


AI-assisted analysis of plotly/plotly.js@1d090e0b5f (2026-09-02). Data as JSON: /api/errors/ef94333edf3fb0de. Report an issue: GitHub.