在xx.ts控制器中实现HighCharts时出现Cannot read properties of undefined错误
问题说明
首次在项目中实现HighCharts,多系列图表原本运行正常,为实现单个tooltip显示多个系列值,添加shared:true配置后出现错误:Cannot read properties of undefined (reading 'color'),尝试split:true配置也无效。
错误信息
TChartController.ts:942 Uncaught TypeError: Cannot read properties of undefined (reading 'color')
at Object.formatter (TChartController.ts:942:81)
at f.refresh (highstock.js:238:71)
at highcharts-theme.js:60:25 formatter @ TChartController.ts:942 f.refresh @ highstock.js:238 (anonymous) @ highcharts-theme.js:60
setTimeout (async) (anonymous) @ highcharts-theme.js:57 b. @
highstock.js:21 t.runPointActions @ highstock.js:270
t.onContainerMouseMove @ highcharts.js:262
相关代码
tooltip: { getLabelOverride: this.allowAngularContentInTooltip, shared: true, useHTML: true, formatter: function () { const highchartsFormatterContext = this; const numItems = highchartsFormatterContext.y; const viewItemsFiltersForDataPoint = highchartsFormatterContext.point?.additionalData?.viewItemsFilters; self.setViewItemsFilters(numItems, viewItemsFiltersForDataPoint); if (self.viewItemsFilters?.seriesId && self.multipleSeriesFilters?.length) { const seriesFilters = self.multipleSeriesFilters.find(x => x.seriesId === self.viewItemsFilters.seriesId || x.SeriesId === self.viewItemsFilters.seriesId); if (seriesFilters) { const mergedFilters = { ...seriesFilters, ...self.viewItemsFilters }; self.viewItemsFilters = mergedFilters; } } let tooltipHeader: string; if (highchartsFormatterContext.point?.additionalData?.tooltip) { tooltipHeader = highchartsFormatterContext.point?.additionalData?.tooltip; } else { const dateFormat = self.intervalUnit ? self.intervalTooltipDateFormats[self.intervalUnit] : 'month'; tooltipHeader = Highcharts.dateFormat(dateFormat, highchartsFormatterContext.key); } let valueString = Highcharts.numberFormat(highchartsFormatterContext.y, 0); if (highchartsFormatterContext.series?.tooltipOptions?.valuePrefix) { valueString = `${highchartsFormatterContext.series.tooltipOptions.valuePrefix}${valueString}`; } if (highchartsFormatterContext.series?.tooltipOptions?.valueSuffix) { valueString = `${valueString}${highchartsFormatterContext.series.tooltipOptions.valueSuffix}`; } const headerHtml = `<span style="font-size:10px;">${tooltipHeader}</span><br />`; const pointHtml = `<span style="color:${highchartsFormatterContext.series.color}">●</span> ${highchartsFormatterContext.series.name}: <b>${valueString}</b><br/>`; const footerHtml = self.generateViewItemsButtonMarkup(); return `${headerHtml}${pointHtml}${footerHtml}`; }, stickOnContact: true, style: { pointerEvents: 'auto' } }, plotOptions: { series: { connectNulls: true, events: { click: (event) => { const dataPointLinkedWidgetFilters = event.point?.additionalData?.linkedWidgetFilters; if (!dataPointLinkedWidgetFilters) { return; } const filterType: FilterType = this.getFilterType(); if (!filterType) { return; } this.sendLinkedWidgetFiltersData( dataPointLinkedWidgetFilters, filterType); } } }
原因分析
开启shared: true后,HighCharts的tooltip formatter上下文结构发生变化:
- 非shared模式下,
this指向单个数据点,可直接访问series、y、point等属性 - shared模式下,
this指向包含所有系列数据点的集合对象,原有的单个点属性会变为undefined,需要通过this.points数组获取每个系列的点数据。代码中直接访问highchartsFormatterContext.series.color自然会报错。
修复方案
修改formatter函数,适配shared模式的上下文结构:
formatter: function () { const self = this; let tooltipHeader; // 获取tooltip头部标题 const firstPoint = self.points[0]; if (firstPoint.point?.additionalData?.tooltip) { tooltipHeader = firstPoint.point.additionalData.tooltip; } else { const dateFormat = self.intervalUnit ? self.intervalTooltipDateFormats[self.intervalUnit] : 'month'; tooltipHeader = Highcharts.dateFormat(dateFormat, self.key); } let headerHtml = `<span style="font-size:10px;">${tooltipHeader}</span><br />`; let pointsHtml = ''; // 遍历所有系列的点,生成每个系列的tooltip内容 self.points.forEach(point => { let valueString = Highcharts.numberFormat(point.y, 0); // 处理数值前缀后缀 if (point.series?.tooltipOptions?.valuePrefix) { valueString = `${point.series.tooltipOptions.valuePrefix}${valueString}`; } if (point.series?.tooltipOptions?.valueSuffix) { valueString = `${valueString}${point.series.tooltipOptions.valueSuffix}`; } // 处理视图筛选器逻辑 const viewItemsFiltersForDataPoint = point.point?.additionalData?.viewItemsFilters; if (viewItemsFiltersForDataPoint) { self.setViewItemsFilters(point.y, viewItemsFiltersForDataPoint); if (self.viewItemsFilters?.seriesId && self.multipleSeriesFilters?.length) { const seriesFilters = self.multipleSeriesFilters.find(x => x.seriesId === self.viewItemsFilters.seriesId || x.SeriesId === self.viewItemsFilters.seriesId); if (seriesFilters) { self.viewItemsFilters = { ...seriesFilters, ...self.viewItemsFilters }; } } } // 拼接单个系列的HTML内容 pointsHtml += `<span style="color:${point.series.color}">●</span> ${point.series.name}: <b>${valueString}</b><br/>`; }); const footerHtml = self.generateViewItemsButtonMarkup(); return `${headerHtml}${pointsHtml}${footerHtml}`; }
注意事项
- shared模式下必须通过
this.points数组访问各系列的点数据,原单个点的series、y等属性不再可用 - 头部标题可根据需求取第一个点的附加tooltip内容,或使用上下文的
key统一格式化 - 如果需要处理每个点的附加数据,需遍历
points数组逐个处理
内容的提问来源于stack exchange,提问作者MVC

