{"record":{"id":"ef94333edf3fb0de","repo":"plotly/plotly.js","slug":"ignored-axout-name-scaleanchor-axin-scaleanc","errorCode":null,"errorMessage":"ignored <axOut._name>.scaleanchor: \"<axIn.scaleanchor>\" to avoid either an infinite loop and possibly inconsistent scaleratios, or because this axis declares a *matches* constraint.","messagePattern":"ignored <axOut\\._name>\\.scaleanchor: \"<axIn\\.scaleanchor>\" to avoid either an infinite loop and possibly inconsistent scaleratios, or because this axis declares a \\*matches\\* constraint\\.","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"src/plots/cartesian/constraints.js","lineNumber":297,"sourceCode":"        updateConstraintGroups(constraintGroups, thisID, matches, matchRatio);\n    } else if(axIn.matches && axIds.indexOf(axIn.matches) !== -1) {\n        Lib.warn('ignored ' + axOut._name + '.matches: \"' +\n            axIn.matches + '\" to avoid an infinite loop');\n    }\n\n    if(scaleanchor) {\n        var scaleratio = coerce('scaleratio');\n\n        // TODO: I suppose I could do attribute.min: Number.MIN_VALUE to avoid zero,\n        // but that seems hacky. Better way to say \"must be a positive number\"?\n        // Of course if you use several super-tiny values you could eventually\n        // force a product of these to zero and all hell would break loose...\n        // Likewise with super-huge values.\n        if(!scaleratio) scaleratio = axOut.scaleratio = 1;\n\n        updateConstraintGroups(constraintGroups, thisID, scaleanchor, scaleratio);\n    } else if(axIn.scaleanchor && axIds.indexOf(axIn.scaleanchor) !== -1) {\n        Lib.warn('ignored ' + axOut._name + '.scaleanchor: \"' +\n            axIn.scaleanchor + '\" to avoid either an infinite loop ' +\n            'and possibly inconsistent scaleratios, or because this axis ' +\n            'declares a *matches* constraint.');\n    }\n}\n\nfunction extent(layoutOut, ax) {\n    var domain = ax.domain;\n    if(!domain) {\n        // at this point overlaying axes haven't yet inherited their main domains\n        // TODO: constrain: domain with overlaying axes is likely a bug.\n        domain = layoutOut[id2name(ax.overlaying)].domain;\n    }\n    return domain[1] - domain[0];\n}\n\nfunction getConstraintGroup(groups, thisID) {\n    for(var i = 0; i < groups.length; i++) {","sourceCodeStart":279,"sourceCodeEnd":315,"githubUrl":"https://github.com/plotly/plotly.js/blob/1d090e0b5ffb8d0fdf6e0e4ff51d3f3cf67f1314/src/plots/cartesian/constraints.js#L279-L315","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Declare scaleanchor on only one axis of each linked pair (remove it from the axis receiving the warning).","Choose either matches or scaleanchor for a given axis — remove the one that is not needed.","Flatten scaleanchor chains so every axis anchors to one base axis with no cycles."],"exampleFix":"// before\nlayout.xaxis2 = {matches: 'x', scaleanchor: 'x'};\n// after\nlayout.xaxis2 = {matches: 'x'}; // or {scaleanchor: 'x', scaleratio: 1}, not both","handlingStrategy":"validation","validationCode":"function checkScaleanchorConflicts(layout) {\n  for (const ax of Object.keys(layout).filter(k => k.endsWith('axis'))) {\n    const cfg = layout[ax];\n    if (cfg.scaleanchor && cfg.matches) throw new Error(`${ax}: cannot combine scaleanchor and matches`);\n    if (cfg.scaleanchor && (layout[cfg.scaleanchor] || {}).scaleanchor === ax) throw new Error(`${ax}: mutual scaleanchor with ${cfg.scaleanchor}`);\n  }\n}\ncheckScaleanchorConflicts(layout);","typeGuard":"const hasValidScaleanchor = (layout, ax) => { const sa = (layout[ax] || {}).scaleanchor; return !sa || (!(layout[ax] || {}).matches && (layout[sa] || {}).scaleanchor !== ax); };","tryCatchPattern":null,"preventionTips":["Anchor each pair from one side only.","Never combine matches and scaleanchor on the same axis.","Keep scaleanchor chains star-shaped (all anchor to one base).","Comment which axis owns each anchor in shared layout code."],"tags":["constraints","axes","scaleanchor","cyclic-reference"],"backgroundTag":"cyclic-axis-reference","analyzedSha":"1d090e0b5ffb8d0fdf6e0e4ff51d3f3cf67f1314","analyzedAt":"2026-09-02T22:03:39.906Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-10T02:17:09.455Z"}