You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React+Chart.js双X轴堆叠柱状图配置异常求助

双X轴堆叠柱状图实现问题求助

我无法排查当前代码问题,需要实现双X轴堆叠柱状图(参考示例图样式),但当前生成的图表显示异常。以下是接收的JSON数据、初始代码,寻求技术帮助。


接收的JSON数据

[
  {
    "label": "Others",
    "type": "bar",
    "stack": "Person 1",
    "data": [22,0,80]
  },
  {
    "label": "AWS",
    "type": "bar",
    "stack": "Person 1",
    "data": [2,0,21]
  },
  {
    "label": "Github Enterprise Cloud - Organization",
    "type": "bar",
    "stack": "Person 1",
    "data": [1,0,8]
  },
  {
    "label": "Okta Admin Console",
    "type": "bar",
    "stack": "Person 1",
    "data": [2,0,10]
  },
  {
    "label": "PacificTimeSheet",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,12]
  },
  {
    "label": "ArgoCD - Dev",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "kubero",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "Okta Dashboard",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "ArgoCD - Production IPV6",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "Okta Browser Plugin",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "Vercel",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,0]
  },
  {
    "label": "ONEDigital Portal Prod",
    "type": "bar",
    "stack": "Person 1",
    "data": [0,0,6]
  },
  {
    "label": "Others",
    "type": "bar",
    "stack": "Person 2",
    "data": [8,0,16]
  },
  {
    "label": "AWS",
    "type": "bar",
    "stack": "Person 2",
    "data": [2,0,1]
  },
  {
    "label": "Github Enterprise Cloud - Organization",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,2]
  },
  {
    "label": "Okta Admin Console",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "PacificTimeSheet",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,1]
  },
  {
    "label": "ArgoCD - Dev",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "kubero",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "Okta Dashboard",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "ArgoCD - Production IPV6",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "Okta Browser Plugin",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "Vercel",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "ONEDigital Portal Prod",
    "type": "bar",
    "stack": "Person 2",
    "data": [0,0,0]
  },
  {
    "label": "Others",
    "type": "bar",
    "stack": "Person 3",
    "data": [18,0,217]
  },
  {
    "label": "AWS",
    "type": "bar",
    "stack": "Person 3",
    "data": [3,0,17]
  },
  {
    "label": "Github Enterprise Cloud - Organization",
    "type": "bar",
    "stack": "Person 3",
    "data": [1,0,7]
  },
  {
    "label": "Okta Admin Console",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,11]
  },
  {
    "label": "PacificTimeSheet",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,2]
  },
  {
    "label": "ArgoCD - Dev",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,0]
  },
  {
    "label": "kubero",
    "type": "bar",
    "stack": "Person 3",
    "data": [4,0,0]
  },
  {
    "label": "Okta Dashboard",
    "type": "bar",
    "stack": "Person 3",
    "data": [2,0,4]
  },
  {
    "label": "ArgoCD - Production IPV6",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,0]
  },
  {
    "label": "Okta Browser Plugin",
    "type": "bar",
    "stack": "Person 3",
    "data": [3,0,3]
  },
  {
    "label": "Vercel",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,0]
  },
  {
    "label": "ONEDigital Portal Prod",
    "type": "bar",
    "stack": "Person 3",
    "data": [0,0,0]
  }
]

初始代码

const parsed = {
        labelsX2: ["May", "June", "July"],
        labelsX: ["Person 1", "Person 2", "Person 3", "Person 1", "Person 2", "Person 3", "Person 1", "Person 2", "Person 3"], // example names but should be according json stacks
        datasets: json.map((item) => {
            console.info("item: ", item);

            const { label, data, stack } = item;
            return {
                ...item,
                label,
                data,
                stack
            };
        }),
    };

    const [chartData, setChartData] = React.useState(parsed);

    const options = {
        plugins: {
            tooltip: {
                title: "Total",
            },
            subtitle: {
                display: true,
                text: "Number of events",
                position: 'left'
            },
            legend: {
                display: true,
                position: 'right',
            },
        },
        scales:{
            x: {
                type: 'category',
                stacked: true,
                labels: parsed.labelsX,
            },
            x2: {
                type: 'category',
                stacked: true,
                labels: parsed.labelsX2,
            },
            y: {
                stacked: true,
            },
        }
    };


// ...

<div className="chart-container">
    <h2 style={{textAlign: "center"}}>Bar Chart</h2>
    <Bar
        data={chartData}
        options={options}
    />
</div>

问题分析与修正方案

核心问题

  1. 数据结构不匹配:原代码未将人员数据与月份分组对应,导致双轴对齐错误
  2. 轴配置缺失:双X轴未设置位置、分组绑定关系,无法实现上层月份轴与下层人员轴的关联
  3. Tooltip逻辑错误:未根据数据位置匹配对应的月份和人员信息

修正后的完整代码

import React from 'react';
import { Bar } from 'react-chartjs-2';
import { Chart as ChartJS, CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, Subtitle } from 'chart.js';

ChartJS.register(CategoryScale, LinearScale, BarElement, Title, Tooltip, Legend, Subtitle);

// 此处插入你的JSON数据
const json = [/* 上面的JSON数据 */];

const DualXStackedBarChart = () => {
  // 定义轴标签
  const monthLabels = ["May", "June", "July"];
  const personLabels = ["Person 1", "Person 2", "Person 3"];
  // 生成底层X轴的完整标签:每个月份对应3个人员
  const baseXLabels = monthLabels.flatMap(() => personLabels);

  // 转换数据集:将每个人员的月份数据展开为按[May-P1, May-P2, May-P3, June-P1,...]排序的数组
  const datasets = json.map(item => {
    const { label, stack, data } = item;
    const personIndex = personLabels.indexOf(stack);
    // 按月份分组展开数据
    const transformedData = monthLabels.flatMap((_, monthIdx) => data[monthIdx]);
    return {
      label,
      type: 'bar',
      stack,
      data: transformedData,
      // 随机生成区分颜色(可自定义)
      backgroundColor: `rgba(${Math.floor(Math.random()*255)}, ${Math.floor(Math.random()*255)}, ${Math.floor(Math.random()*255)}, 0.7)`
    };
  });

  const chartData = {
    labels: baseXLabels,
    datasets
  };

  const options = {
    plugins: {
      tooltip: {
        callbacks: {
          // 自定义Tooltip标题,显示当前数据对应的月份和人员
          title: context => {
            const dataIdx = context[0].dataIndex;
            const monthIdx = Math.floor(dataIdx / personLabels.length);
            const personIdx = dataIdx % personLabels.length;
            return `${monthLabels[monthIdx]} - ${personLabels[personIdx]}`;
          },
          label: context => `${context.dataset.label}: ${context.raw}`
        }
      },
      subtitle: {
        display: true,
        text: "Number of events",
        position: 'left'
      },
      legend: {
        display: true,
        position: 'right'
      }
    },
    scales: {
      x: {
        type: 'category',
        stacked: true,
        ticks: {
          // 仅显示人员标签,每个月份下重复显示
          callback: value => personLabels[value % personLabels.length]
        },
        grid: {
          display: false,
          offset: true // 开启分组偏移,让每个月份的柱子成为一组
        }
      },
      x2: {
        type: 'category',
        stacked: true,
        position: 'top',
        offset: true,
        ticks: {
          // 仅在每个月份分组的起始位置显示月份标签
          callback: value => value % personLabels.length === 0 ? monthLabels[Math.floor(value / personLabels.length)] : ''
        },
        grid: { display: false },
        // 绑定到底层X轴,确保对齐
        axis: 'x',
        alignToPixels: true
      },
      y: {
        stacked: true,
        title: {
          display: true,
          text: '事件数量'
        }
      }
    }
  };

  return (
    <div className="chart-container">
      <h2 style={{ textAlign: "center" }}>双X轴堆叠柱状图</h2>
      <Bar
        data={chartData}
        options={options}
        height={400}
      />
    </div>
  );
};

export default DualXStackedBarChart;

关键修正点

  • 数据转换:将原数据集的[月份1数据, 月份2数据, 月份3数据]结构,转换为按[五月-P1, 五月-P2, 五月-P3, 六月-P1,...]顺序排列的数组,匹配双轴的分组逻辑
  • 轴配置优化:
    • 下层X轴显示人员标签,通过grid.offset实现月份分组
    • 上层X轴设置在顶部,仅在每个分组起始位置显示月份标签,并绑定到底层X轴确保对齐
  • Tooltip增强:自定义Tooltip内容,显示当前数据对应的具体月份和人员信息

内容的提问来源于stack exchange,提问作者Gabriel Angelo

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 13:50:56