{"record":{"id":"c061b0d867564a4f","repo":"apache/echarts","slug":"heatmap-on-cartesian-must-have-two-axes-with-bound","errorCode":null,"errorMessage":"Heatmap on cartesian must have two axes with boundaryGap true","messagePattern":"Heatmap on cartesian must have two axes with boundaryGap true","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src/chart/heatmap/HeatmapView.ts","lineNumber":188,"sourceCode":"    ) {\n        const coordSys = seriesModel.coordinateSystem as Cartesian2D | Calendar | Matrix;\n        const isCartesian2d = isCoordinateSystemType<Cartesian2D>(coordSys, 'cartesian2d');\n        const isMatrix = isCoordinateSystemType<Matrix>(coordSys, 'matrix');\n        let width;\n        let height;\n        let xAxisExtent;\n        let yAxisExtent;\n\n        if (isCartesian2d) {\n            const xAxis = coordSys.getAxis('x');\n            const yAxis = coordSys.getAxis('y');\n\n            if (__DEV__) {\n                if (!(xAxis.type === 'category' && yAxis.type === 'category')) {\n                    throw new Error('Heatmap on cartesian must have two category axes');\n                }\n                if (!(xAxis.onBand && yAxis.onBand)) {\n                    throw new Error('Heatmap on cartesian must have two axes with boundaryGap true');\n                }\n            }\n\n            // add 0.5px to avoid the gaps\n            width = calcBandWidth(xAxis).w + .5;\n            height = calcBandWidth(yAxis).w + .5;\n            xAxisExtent = xAxis.scale.getExtent();\n            yAxisExtent = yAxis.scale.getExtent();\n        }\n\n        const group = this.group;\n        const data = seriesModel.getData();\n\n        let emphasisStyle = seriesModel.getModel(['emphasis', 'itemStyle']).getItemStyle();\n        let blurStyle = seriesModel.getModel(['blur', 'itemStyle']).getItemStyle();\n        let selectStyle = seriesModel.getModel(['select', 'itemStyle']).getItemStyle();\n        let borderRadius = seriesModel.get(['itemStyle', 'borderRadius']);\n        let labelStatesModels = getLabelStatesModels(seriesModel);","sourceCodeStart":170,"sourceCodeEnd":206,"githubUrl":"https://github.com/apache/echarts/blob/30076aedcd7b7f65d8dd8e8d9ece46ce778133a3/src/chart/heatmap/HeatmapView.ts#L170-L206","documentation":"DEV-only guard immediately following [4]: for a cartesian heatmap, both category axes must have boundaryGap true (exposed by the helper as axis.onBand). boundaryGap centers categories into bands so heatmap cells align with band centers rather than tick lines. Stripped in production.","triggerScenarios":"Setting xAxis.boundaryGap=false (or a [false, ...] pair) on a cartesian heatmap axis; same for yAxis.","commonSituations":"Reusing an axis config tuned for a bar/candlestick chart with explicit boundaryGap:false; copying config from a K-line example into a heatmap.","solutions":["Set boundaryGap:true (or omit it so the category default applies) on both axes","Remove any explicit boundaryGap:false inherited from another chart's config"],"exampleFix":"// before\nxAxis: { type: 'category', boundaryGap: false },\nyAxis: { type: 'category', boundaryGap: false }\n\n// after\nxAxis: { type: 'category', boundaryGap: true },\nyAxis: { type: 'category', boundaryGap: true }","handlingStrategy":"validation","validationCode":"const axes = [...(option.xAxis || []), ...(option.yAxis || [])];\naxes.forEach((a: any) => {\n  if (a && a.type === 'category' && a.boundaryGap === false) {\n    console.warn('[heatmap] boundaryGap:false conflicts with cartesian heatmap');\n  }\n});","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Keep boundaryGap default (true) for category axes used by heatmaps","Audit axis config before setOption when reusing across chart types"],"tags":["heatmap","cartesian","boundary-gap","dev-only"],"backgroundTag":null,"analyzedSha":"30076aedcd7b7f65d8dd8e8d9ece46ce778133a3","analyzedAt":"2026-08-12T12:42:28.134Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}