BarChart动态键场景下默认Tooltip无数据显示问题
Nivo ResponsiveBar 工具提示为空问题
问题描述
使用Nivo的ResponsiveBar组件开发柱状图时,除默认Tooltip外其余功能均正常。鼠标悬停在柱状条上时,Tooltip无任何数据展示。图表采用动态生成的键作为图例,图例显示正常,但相同的键在Tooltip中无法正常匹配数据。
问题现象
柱状条通过动态生成的键能正常渲染,但Tooltip为空;同一组键对应的图例显示完全正常。
代码片段
图表配置
<ResponsiveBar data={barData} keys={chartKeys} indexBy="year" margin={{ top: 50, right: 130, bottom: 50, left: 60 }} padding={0.3} valueScale={{ type: "linear" }} indexScale={{ type: "band", round: true }} colors={{ scheme: "nivo" }} borderColor={{ from: "color", modifiers: [["darker", 1.6]], }} axisTop={null} axisRight={null} axisBottom={{ tickSize: 5, tickPadding: 5, tickRotation: 0, legend: "year", legendPosition: "middle", legendOffset: 32, }} axisLeft={{ tickSize: 5, tickPadding: 5, tickRotation: 0, legend: "credito_vigente", legendPosition: "middle", legendOffset: -40, }} enableTotals={true} labelSkipWidth={12} labelSkipHeight={12} labelTextColor={{ from: "color", modifiers: [["darker", 1.6]] }} legends={[ { dataFrom: "keys", anchor: "bottom-right", direction: "column", justify: false, translateX: 120, translateY: 0, itemsSpacing: 1, // 缩小项间距 itemWidth: 80, // 更小的宽度 itemHeight: 15, // 更小的高度 itemDirection: "left-to-right", itemOpacity: 0.85, symbolSize: 12, // 更小的符号尺寸 effects: [ { on: "hover", style: { itemOpacity: 1, }, }, ], }, ]} role="application" ariaLabel="Nivo bar chart demo" barAriaLabel={(e) => `${e.id}: ${e.formattedValue} in year: ${e.indexValue}` } />
数据处理逻辑
const [dataByYear, setDataByYear] = useState<AñoConSusActividades[]>([]); const [chartKeys, setChartKeys] = useState<string[]>([]); // 存储唯一键的状态 const transformAndSetData = useCallback( (data: PresupuestoPorProgramaUNI[]) => { const groupedData = data.reduce((acc, curr) => { // 在累积数据中查找已有的年份条目 const yearEntry = acc.find( (entry) => entry.year === curr.impacto_presupuestario_anio ); // 构造新的活动对象 const activity = { actividad_desc: curr.actividad_desc, credito_vigente: Number(curr.credito_vigente.toFixed(0)), }; if (yearEntry) { // 如果该年份的条目已存在,将新活动推入该年份的活动数组 yearEntry.activities.push(activity); } else { // 如果该年份的条目不存在,创建新条目并加入当前活动 acc.push({ year: curr.impacto_presupuestario_anio, activities: [activity], }); } return acc; }, [] as AñoConSusActividades[]); // 更新分组数据状态 setDataByYear(groupedData); // 根据新数据更新图表键 setChartKeys(extractUniqueKeys(groupedData)); }, [setDataByYear, setChartKeys] ); // 包含这些依赖项 useEffect(() => { async function fetchData() { try { const response = await fetch("/api/presupuestoUni"); const jsonData = await response.json(); const dataRes: PresupuestoPorProgramaUNI[] = jsonData.dataRes; console.log("获取的数据:", dataRes); // 打印原始获取的数据 transformAndSetData(dataRes); } catch (error) { console.error("获取数据失败:", error); } } fetchData(); }, [transformAndSetData]); // 仅当transformAndSetData变化时重新运行,由于useCallback,这种情况不会发生 const extractUniqueKeys = (data: AñoConSusActividades[]): string[] => { const allKeys = new Set( data.flatMap((yearGroup) => yearGroup.activities.map((activity) => activity.actividad_desc) ) ); return Array.from(allKeys); }; // 考虑是否可以直接在transformAndSetData中处理这部分逻辑 const barData = dataByYear.map((yearGroup) => { // 定义带有索引签名的累加器 // 告诉TypeScript该对象可以有任意数量的属性,属性键为字符串,属性值为数字 const activitiesAccumulator: Record<string, number> = {}; yearGroup.activities.forEach((activity) => { activitiesAccumulator[activity.actividad_desc] = activity.credito_vigente; }); return { year: yearGroup.year.toString(), // 与ResponsiveBar组件中的'indexBy'对应 ...activitiesAccumulator, }; });
问题演示
存在演示视频展示Tooltip为空的现象。
解决方案
1. 标准化键值匹配
动态生成的键可能存在空格、特殊字符或大小写不一致的问题,导致Tooltip无法匹配对应数据。对键进行标准化处理:
// 新增键标准化函数 const normalizeKey = (key: string) => key.trim().toLowerCase(); // 修改extractUniqueKeys const extractUniqueKeys = (data: AñoConSusActividades[]): string[] => { const allKeys = new Set( data.flatMap((yearGroup) => yearGroup.activities.map((activity) => normalizeKey(activity.actividad_desc)) ) ); return Array.from(allKeys); }; // 修改barData生成逻辑 const barData = dataByYear.map((yearGroup) => { const activitiesAccumulator: Record<string, number> = {}; yearGroup.activities.forEach((activity) => { const normalizedKey = normalizeKey(activity.actividad_desc); activitiesAccumulator[normalizedKey] = activity.credito_vigente; }); return { year: yearGroup.year.toString(), ...activitiesAccumulator, }; });
2. 校验数据有效性
确保barData中所有属性值都是有效数字,避免NaN或undefined导致Tooltip无数据:
// 修改活动对象构造逻辑 const activity = { actividad_desc: curr.actividad_desc, credito_vigente: isNaN(Number(curr.credito_vigente)) ? 0 : Number(curr.credito_vigente.toFixed(0)), };
3. 确保Tooltip功能启用
显式添加enableTooltip={true}到ResponsiveBar组件,避免默认配置被意外覆盖:
<ResponsiveBar // ...其他配置 enableTooltip={true} />
4. 同步状态更新时机
用useMemo包裹barData生成逻辑,确保数据与dataByYear同步更新:
const barData = useMemo(() => { return dataByYear.map((yearGroup) => { const activitiesAccumulator: Record<string, number> = {}; yearGroup.activities.forEach((activity) => { activitiesAccumulator[activity.actividad_desc] = activity.credito_vigente; }); return { year: yearGroup.year.toString(), ...activitiesAccumulator, }; }); }, [dataByYear]);
内容的提问来源于stack exchange,提问作者Vladimirzb
相关产品推荐
相关产品推荐

