{"record":{"id":"a60e0b9a7295ebcd","repo":"apache/echarts","slug":"circular-dependency-may-exists","errorCode":null,"errorMessage":"Circular dependency may exists: ","messagePattern":"Circular dependency may exists: ","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"src/util/component.ts","lineNumber":157,"sourceCode":"            const currComponentType = noEntryList.pop();\n            const currVertex = graph[currComponentType];\n            const isInTargetNameSet = !!targetNameSet[currComponentType];\n            if (isInTargetNameSet) {\n                callback.call(context, currComponentType, currVertex.originalDeps.slice());\n                delete targetNameSet[currComponentType];\n            }\n            zrUtil.each(\n                currVertex.successor,\n                isInTargetNameSet ? removeEdgeAndAdd : removeEdge\n            );\n        }\n\n        zrUtil.each(targetNameSet, function () {\n            let errMsg = '';\n            if (__DEV__) {\n                errMsg = makePrintable('Circular dependency may exists: ', targetNameSet, targetNameList, fullNameList);\n            }\n            throw new Error(errMsg);\n        });\n\n        function removeEdge(succComponentType: ComponentMainType): void {\n            graph[succComponentType].entryCount--;\n            if (graph[succComponentType].entryCount === 0) {\n                noEntryList.push(succComponentType);\n            }\n        }\n\n        // Consider this case: legend depends on series, and we call\n        // chart.setOption({series: [...]}), where only series is in option.\n        // If we do not have 'removeEdgeAndAdd', legendModel.mergeOption will\n        // not be called, but only sereis.mergeOption is called. Thus legend\n        // have no chance to update its local record about series (like which\n        // name of series is available in legend).\n        function removeEdgeAndAdd(succComponentType: ComponentMainType): void {\n            targetNameSet[succComponentType] = true;\n            removeEdge(succComponentType);","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/apache/echarts/blob/30076aedcd7b7f65d8dd8e8d9ece46ce778133a3/src/util/component.ts#L139-L175","documentation":"Thrown by `enableTopologicalTravel`'s `topologicalTravel` in component.ts:157. ECharts schedules component models in dependency order using Kahn's algorithm (activity-on-vertices) over the `dependencies` arrays declared on each Model (e.g. `static dependencies = ['grid', 'polar', ...]`). After draining the zero-entry queue, any main type still left in `targetNameSet` was never processed, which means it sits inside a dependency cycle. The throw is NOT guarded by `__DEV__`; only the human-readable message is — in a production build the thrown Error has an empty string as its message, so it surfaces as an Error with no text.","triggerScenarios":"A component main type's declared `dependencies` form a cycle (A depends on B and B depends on A, directly or transitively), reached during `GlobalModel` option merge / component scheduling (`topologicalTravel` at Global.ts:357 / :892). Built-in components are acyclic by design, so this requires a custom component model with cyclic `dependencies`, a buggy third-party extension, or an internal ECharts regression in dependency metadata.","commonSituations":"Authoring a custom component model whose `static dependencies` list loops back on itself or on another component that depends on it; installing a third-party ECharts plugin with incorrect dependency declarations; an ECharts version regression that introduced a cyclic dependency between built-ins; monkey-patching a built-in `dependencies` array.","solutions":["Inspect the `static dependencies` arrays of any custom or third-party component models and remove the edge that closes the cycle.","Update, fix, or uninstall the offending extension whose dependency metadata is cyclic.","If no custom components are involved, capture the full option + ECharts version and report it as an ECharts bug.","Bisect ECharts versions to identify where the cyclic dependency was introduced, then pin to the last good version while it is fixed upstream."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// topologicalTravel runs inside setOption; wrap it so a cyclic dependency\n// in a third-party component degrades gracefully instead of crashing render.\ntry {\n  chart.setOption(option);\n} catch (e) {\n  // In production builds the message is an empty string for this error.\n  if (e instanceof Error && /Circular dependency/i.test(e.message) || (e instanceof Error && e.message === '')) {\n    console.error('Component dependency cycle detected; option not applied.', option);\n    // fall back to a known-good baseline option\n    chart.setOption(baselineOption);\n  } else {\n    throw e;\n  }\n}","preventionTips":["When authoring a custom component model, list `dependencies` as only the components it truly reads from, and never let two models mutually depend on each other.","Keep a dependency diagram for custom components; a cycle is usually visible as a back-edge in that graph.","Test new/updated third-party ECharts extensions against your full option set in a dev build where the human-readable message is present."],"tags":["dependency-graph","topological-sort","custom-component","internal"],"backgroundTag":null,"analyzedSha":"30076aedcd7b7f65d8dd8e8d9ece46ce778133a3","analyzedAt":"2026-08-12T12:42:28.134Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}