Chart.js 3迁移报错:Class does not have id,标注线失效求助
Chart.js 3迁移后自定义标注线消失并报错
class does not have id的解决方法 问题原因分析
- 自定义插件缺少Chart.js 3强制要求的
id属性,导致注册时报错。 - Chart.js 3的API结构变更,原插件中部分属性访问路径已失效。
- 配置存在拼写错误(
cales应为scales),导致坐标轴配置不生效。 - 重复引入脚本文件引发潜在冲突。
- 标注文本的坐标参数顺序错误,导致文本无法正常显示。
解决方案步骤
1. 给自定义插件添加唯一ID
Chart.js 3要求所有注册的插件必须包含id字段用于标识,修改插件代码:
const annotationLinePlugin = { id: 'annotationLine', // 添加唯一ID renderAnnotationLine: function(chart, line) { // ... 后续代码 }, afterDatasetsDraw: function(chart, easing) { // ... 后续代码 } };
2. 适配Chart.js 3的API变化
- 将
chartInstance.chart.ctx改为chart.ctx(Chart.js 3中chart实例直接包含ctx属性)。 - 其他API如
getDatasetMeta和scales的访问方式基本兼容,确保上下文正确即可。
3. 修正配置中的拼写错误
将配置里的cales改为scales,同时适配Chart.js 3的坐标轴配置格式(从数组改为对象键值对):
scales: { // 原拼写为cales,修正为scales x: { // 替换xAxes数组为x对象 stacked: true, scaleLabel: { display: true, labelString: "Период", }, }, y: { // 替换yAxes数组为y对象 id: "L", type: "linear", position: "left", stacked: true, scaleLabel: { display: true, labelString: "Трудозатраты, чел-мес", }, }, },
4. 修正标注文本的坐标顺序
原代码中fillText的参数顺序错误(应为文本, x坐标, y坐标),修正后:
if (typeof line.label !== typeof undefined) { context.textAlign = 'left'; context.fillStyle = line.color; context.fillText(line.label, xEnd + 7, yCoordinate); // 交换x和y参数位置 }
5. 移除重复的脚本引入
保留一个版本的脚本文件即可,避免冲突:
<script src="./chartjs3/chart.min.js"></script> <script src="./chartjs3/chartjs-plugin-annotation.min.js"></script> <script src="./chartjs3/chartjs-plugin-annotationLine.js"></script> <script src="./chartjs3/chartjs-plugin-datalabels.min.js"></script> <script src="./chartjs3/hammer.min.js"></script>
完整修正后的代码
自定义插件代码
const annotationLinePlugin = { id: 'annotationLine', renderAnnotationLine: function(chart, line) { let datasetMeta = chart.getDatasetMeta(line.datasetIndex); let context = chart.ctx; // 适配Chart.js 3的ctx获取方式 let datasetModel = datasetMeta.data[line.dataIndex]._model; const xBarCenter = datasetModel.x; const barWidth = datasetModel.width; const xStart = xBarCenter - (barWidth / 2) - 4; const xEnd = xBarCenter + (barWidth / 2) + 4; const yAxisID = datasetMeta.yAxisID; const yCoordinate = chart.scales[yAxisID].getPixelForValue(line.yCoordinate); context.beginPath(); context.strokeStyle = line.color; context.lineWidth = line.width; context.moveTo(xStart, yCoordinate); context.lineTo(xEnd, yCoordinate); context.stroke(); if (typeof line.label !== typeof undefined) { context.textAlign = 'left'; context.fillStyle = line.color; context.fillText(line.label, xEnd + 7, yCoordinate); // 修正坐标顺序 } }, afterDatasetsDraw: function(chart, easing) { if (chart.data.datasets.length < 1) { return; } if (chart.config.options.lines) { // 从options读取lines配置 chart.config.options.lines.map(line => { this.renderAnnotationLine(chart, line); }); } } }; Chart.register(annotationLinePlugin);
index.html中的配置修正
const ctx = document.getElementById("сhart").getContext("2d"); const options = { type: "bar", responsive: true, maintainAspectRatio: false, title: { display: false }, tooltips: { mode: "index", intersect: true, filter: (tooltipItem, chartData) => { return ( !chartData.datasets[tooltipItem.datasetIndex].hideFromTooltip && (chartData.datasets[tooltipItem.datasetIndex].yAxisID == "R" || tooltipItem.yLabel > 0) ); }, callbacks: { label: function (tooltipItem, chartData) { return ( chartData.datasets[tooltipItem.datasetIndex].label + ": " + tooltipItem.yLabel.round(0) ); }, }, }, animation: { duration: 500, easing: "linear", }, plugins: { datalabels: { display: function (context) { const axisMax = context.chart.scales["L"] ? context.chart.scales["L"].max : 0; const datasetLabelsSettings = context.chart.data.datasets[context.datasetIndex].datalabels; const isLabelOutside = datasetLabelsSettings && datasetLabelsSettings.anchor === "end" && datasetLabelsSettings.align === "end"; // 处理未定义的全局变量,避免报错 return ( context.chart.isDatasetVisible(context.datasetIndex) && (typeof report !== 'undefined' ? report.chartSettings.showDataLabels : true) && ((isLabelOutside && context.dataset.data[context.dataIndex] > 0) || context.dataset.data[context.dataIndex] > axisMax * (typeof HIDE_DATALABELS_Y_LEVEL !== 'undefined' ? HIDE_DATALABELS_Y_LEVEL : 0.1)) && context.dataset.type !== "line" ); }, formatter: (x) => { return (x || 0).round(0); }, }, }, legend: { display: true, onClick: function (event, legendItem) { var index = legendItem.datasetIndex, meta = null; var tag = chart.data.datasets[index].tag; var meta = chart.getDatasetMeta(index); if (tag || !isNaN(tag)) { // 替换自定义isNumeric为原生判断 chart.data.datasets .filter((x) => x.tag == tag) .forEach((item) => { item.hidden = item.forcedHidden || !item.hidden; }); } else { meta.hidden = meta.hidden === null ? !meta.hidden : null; } chart.update(); }, position: "top", labels: { usePointStyle: true, filter: function (legendItem, chartData) { return ( chartData.datasets[legendItem.datasetIndex].displayInLegend || chartData.datasets[legendItem.datasetIndex].displayInLegend == undefined ); }, }, }, scales: { // 修正拼写错误cales为scales x: { // 替换xAxes数组为x对象 stacked: true, scaleLabel: { display: true, labelString: "Период", }, }, y: { // 替换yAxes数组为y对象 id: "L", type: "linear", position: "left", stacked: true, scaleLabel: { display: true, labelString: "Трудозатраты, чел-мес", }, }, }, lines: [ 5553, 5454, 5399, 5290, 5260, 4927, 4565, 4649, 5324, 5624, 5710, 5766, 5827, 5829, 5831, 5832, 5833, 5835, 5835, 5836, 5836, 5836, 5837, 583, ].map((x, i) => { return { datasetIndex: 0, dataIndex: i, yCoordinate: x, color: "salmon", width: 3, // 可添加label属性测试文本显示:label: x.toString() }; }), data: { labels: [ "2022-01", "2022-02", "2022-03", "2022-04", "2022-05", "2022-06", "2022-07", "2022-08", "2022-09", "2022-10", "2022-11", "2022-12", "2023-01", "2023-02", "2023-03", "2023-04", "2024-05", "2023-06", "2023-07", "2023-08", "2023-09", "2023-10", "2023-11", "2023-12", ], datasets: [ { label: "ОПИ (ресурсный профиль)", findId: "ОПИ", tag: "ОПИ", type: "bar", borderWidth: 1, stack: "cnt", groupType: "cnt", backgroundColor: "#2196f3", fill: false, pointStyle: "rect", detailMode: "point", data: [ 5356, 5802, 6105, 6258, 6524, 6690, 6697, 7348, 6578, 5742, 4626, 3678, 4809, 6038, 5891, 4526, 3637, 3601, 3471, 3618, 3548, 3428, 3330, 3448, 5031, ], displayInLegend: false, datalabels: { anchor: "end", align: "end", offset: 0, color: function (context) { return "#2196f3"; }, }, }, ], }, }; const сhart = new Chart(ctx, options);
内容的提问来源于stack exchange,提问作者Andrey
相关产品推荐
相关产品推荐

