如何在MUI-X Charts堆叠百分比柱状图中显示正确Tooltip?
解决MUI-X Charts堆叠柱状图Tooltip数值错误问题
问题描述
我正尝试用MUI-X Charts制作堆叠柱状图,数据源是百分比数据,想要把不同剂量强度的数值堆叠展示,不同颜色对应不同剂量强度。目前我通过减去前序值来实现堆叠效果,但这样会导致悬浮Tooltip显示的数值不正确。我可以切换为非堆叠图表,但想知道怎么在保留堆叠效果的同时让Tooltip显示正确数值?
当前代码
styles.module.scss
.root { margin-top: 20px; margin-bottom:20px; :global { .MuiChartsAxis-directionY .MuiChartsAxis-label { transform: translateX(-5px) !important; } } }
DrugEffectiveness.tsx
/** * 数据来源:https://www.bmj.com/content/326/7404/1423.long */ import React, { type ReactNode } from 'react' import { BarChart } from '@mui/x-charts/BarChart' import Typography from '@mui/material/Typography' import styles from './styles.module.scss' interface reductionType { strength: string reduction: number | null } interface drugType { name: string reduction: reductionType[] } interface seriesType { data: Array<number | null> label: string stack: string } const atorvastatin: drugType = { name: 'atorvastatin', reduction: [ { strength: '5mg', reduction: 31 }, { strength: '10mg', reduction: 37 }, { strength: '20mg', reduction: 43 }, { strength: '40mg', reduction: 49 }, { strength: '80mg', reduction: 55 } ] } const fluvastatin: drugType = { name: 'fluvastatin', reduction: [ { strength: '5mg', reduction: 10 }, { strength: '10mg', reduction: 15 }, { strength: '20mg', reduction: 21 }, { strength: '40mg', reduction: 27 }, { strength: '80mg', reduction: 33 } ] } const lovastatin: drugType = { name: 'lovastatin', reduction: [ { strength: '5mg', reduction: null }, { strength: '10mg', reduction: 21 }, { strength: '20mg', reduction: 29 }, { strength: '40mg', reduction: 37 }, { strength: '80mg', reduction: 45 } ] } const pravastatin: drugType = { name: 'pravastatin', reduction: [ { strength: '5mg', reduction: 15 }, { strength: '10mg', reduction: 20 }, { strength: '20mg', reduction: 24 }, { strength: '40mg', reduction: 29 }, { strength: '80mg', reduction: 33 } ] } const rosuvastatin: drugType = { name: 'rosuvastatin', reduction: [ { strength: '5mg', reduction: 38 }, { strength: '10mg', reduction: 43 }, { strength: '20mg', reduction: 48 }, { strength: '40mg', reduction: 53 }, { strength: '80mg', reduction: 58 } ] } const simvastatin: drugType = { name: 'simvastatin', reduction: [ { strength: '5mg', reduction: 23 }, { strength: '10mg', reduction: 27 }, { strength: '20mg', reduction: 32 }, { strength: '40mg', reduction: 37 }, { strength: '80mg', reduction: 42 } ] } const drugs: drugType[] = [atorvastatin, fluvastatin, lovastatin, pravastatin, rosuvastatin, simvastatin] const strengths: string[] = simvastatin.reduction.map(s => s.strength) const series5mg: seriesType = { data: [], stack: 'stack', label: '5mg' } const series10mg: seriesType = { data: [], stack: 'stack', label: '10mg' } const series20mg: seriesType = { data: [], stack: 'stack', label: '20mg' } const series40mg: seriesType = { data: [], stack: 'stack', label: '40mg' } const series80mg: seriesType = { data: [], stack: 'stack', label: '80mg' } const getReduction = ({ drug, strength }: { drug: drugType, strength: string }): number | null => { const filteredDrug = drug.reduction.filter((drugItem: reductionType) => drugItem.strength === strength) return filteredDrug.length > 0 ? filteredDrug[0].reduction : null } strengths.forEach((strength: string) => { drugs.forEach((drug: drugType) => { switch (strength) { case '5mg' : series5mg.data.push(getReduction({ drug, strength })) break case '10mg': series10mg.data.push(Number(getReduction({ drug, strength })) - Number(getReduction({ drug, strength: '5mg' }))) break case '20mg': series20mg.data.push(Number(getReduction({ drug, strength })) - Number(getReduction({ drug, strength: '10mg' }))) break case '40mg': series40mg.data.push(Number(getReduction({ drug, strength })) - Number(getReduction({ drug, strength: '20mg' }))) break case '80mg': series80mg.data.push(Number(getReduction({ drug, strength })) - Number(getReduction({ drug, strength: '40mg' }))) break } }) }) const valueFormatter = (val: number | null): string => { return val !== null ? val + '%' : '' } const DrugEffectiveness = (): ReactNode => ( <div className={`${styles.root} DrugEffectiveness`}> <Typography variant="h2">Percentage reduction in LDL cholesterol</Typography> <BarChart title="tester" width={800} slotProps={{ legend: { hidden: true } }} height={400} xAxis={[{ scaleType: 'band', data: drugs.map((d: drugType) => d.name) }]} series={[ { ...series5mg, valueFormatter }, { ...series10mg, valueFormatter }, { ...series20mg, valueFormatter }, { ...series40mg, valueFormatter }, { ...series80mg, valueFormatter } ]} yAxis={[{ label: 'Percentage reduction', valueFormatter: (v) => v + '%', labelStyle: { fontSize: 14 }, tickLabelStyle: { angle: -45, textAnchor: 'end', fontSize: 12 } }]} /> </div> ) export default DrugEffectiveness
解决方案:利用MUI-X内置堆叠+自定义Tooltip
你不需要手动计算差值实现堆叠,MUI-X Charts的BarChart本身支持堆叠功能,只要给每个series设置相同的stack属性即可,同时通过自定义Tooltip读取原始数据展示正确数值。
核心修改步骤
- 恢复原始数据:重构series生成逻辑,直接使用各剂量的原始百分比,不再计算差值。
- 自定义Tooltip:通过
slotProps.tooltip重写Tooltip渲染逻辑,从原始drug数据中获取对应药物和剂量的真实数值。
修改后的关键代码
// 重构series生成,直接使用原始数据 const seriesList: seriesType[] = strengths.map(strength => ({ data: drugs.map(drug => getReduction({ drug, strength })), label: strength, stack: 'stack' })) // 自定义Tooltip组件 const CustomTooltip = ({ active, payload }: any) => { if (active && payload && payload.length) { return ( <div style={{ backgroundColor: 'white', padding: '8px', border: '1px solid #ccc' }}> <p>{payload[0].xLabel}</p> {payload.map((entry: any) => { // 从原始数据中获取真实百分比 const targetDrug = drugs.find(d => d.name === entry.xLabel); const targetReduction = targetDrug?.reduction.find(r => r.strength === entry.series.label); const realValue = targetReduction?.reduction; return ( <p key={entry.series.label} style={{ color: entry.color }}> {entry.series.label}: {realValue !== null ? `${realValue}%` : 'N/A'} </p> ); })} </div> ); } return null; }; // 更新BarChart配置 const DrugEffectiveness = (): ReactNode => ( <div className={`${styles.root} DrugEffectiveness`}> <Typography variant="h2">Percentage reduction in LDL cholesterol</Typography> <BarChart title="tester" width={800} height={400} xAxis={[{ scaleType: 'band', data: drugs.map((d: drugType) => d.name) }]} series={seriesList.map(s => ({ ...s, valueFormatter }))} yAxis={[{ label: 'Percentage reduction', valueFormatter: (v) => v + '%', labelStyle: { fontSize: 14 }, tickLabelStyle: { angle: -45, textAnchor: 'end', fontSize: 12 } }]} slotProps={{ legend: { hidden: true }, tooltip: { content: <CustomTooltip /> } }} /> </div> )
效果说明
- MUI-X会自动处理堆叠逻辑,同stack的series数值会被累加,展示正确的堆叠高度。
- 自定义Tooltip直接读取原始数据源的百分比,避免了手动计算差值导致的Tooltip数值错误。
- 完美保留堆叠视觉效果的同时,Tooltip展示用户需要的真实数据。
内容的提问来源于stack exchange,提问作者pgee70
相关产品推荐
相关产品推荐

