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

使用Chart.js实现国家-城市双层嵌套环形图的问题咨询

Chart.js 实现国家-城市双层嵌套环形图方案

你现有代码有5个直接导致效果异常的问题:

  • 外层城市数据统计逻辑错误:仅统计了每个国家下去重后的城市种类数,没有统计每个城市的实际出现次数,且外层数据集长度和内层逻辑不匹配,无法拆分扇区
  • 两个环形数据集未做半径/宽度区分,会完全重叠
  • 配置项circumference: 90会让图表只渲染四分之一圈,无法显示完整环形
  • 颜色随机生成,没有同国家色系关联,无法体现内外层扇区的归属关系
  • 预处理数据存在拼写错误:China被拼成Chine,Budapest被拼成Budapes,会导致同主体数据被错误拆分

正确实现步骤

1. 数据处理逻辑修正

核心原则:外层所有属于同一个国家的城市数值之和,必须等于内层对应国家的数值,Chart.js会自动按数值比例分配角度,实现内外层扇区严丝合缝的对应效果。

// 原始输入数据
const rawData = [
  {city: "Budapest", country: "Hungary"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Istanbul", country: "Turkey"},
  {city: "Ho Chi Minh", country: "Vietnam"},
  {city: "Shenzen", country: "China"},
  {city: "Budapes", country: "Hungary"},
  {city: "Budapest", country: "Hungary"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Istanbul", country: "Turkey"},
  {city: "Budapest", country: "Hungary"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Shenzen", country: "China"},
  {city: "Istanbul", country: "Turkey"},
]

// 按国家分组,同步统计每个国家下各城市的出现次数
const countryGroup = {}
rawData.forEach(item => {
  // 可在此处加名称标准化逻辑,修正拼写错误
  const countryName = item.country === 'Chine' ? 'China' : item.country
  const cityName = item.city === 'Budapes' ? 'Budapest' : item.city
  if (!countryGroup[countryName]) countryGroup[countryName] = {}
  if (!countryGroup[countryName][cityName]) countryGroup[countryName][cityName] = 0
  countryGroup[countryName][cityName] += 1
})

// 生成内层(国家层)数据
const innerLabels = Object.keys(countryGroup)
const innerData = innerLabels.map(country => {
  return Object.values(countryGroup[country]).reduce((sum, count) => sum + count, 0)
})
// 内层配置基础主题色
const innerColors = ['#e74c3c', '#3498db', '#2ecc71', '#f39c12']

// 生成外层(城市层)数据,顺序严格和内层国家对齐
const outerLabels = []
const outerData = []
const outerColors = []
innerLabels.forEach((country, countryIndex) => {
  const cityList = countryGroup[country]
  Object.entries(cityList).forEach(([city, count]) => {
    outerLabels.push(city)
    outerData.push(count)
    // 外层用同国家基础色的浅色调,体现归属关系,不想引入颜色库可以手动配置同色系色值
    outerColors.push(lightenColor(innerColors[countryIndex], 20))
  })
})

// 简易颜色明度调整函数,不需要依赖第三方库
function lightenColor(hex, percent) {
  const num = parseInt(hex.replace('#',''),16)
  const amt = Math.round(2.55 * percent)
  const R = Math.min(255, (num >> 16) + amt)
  const G = Math.min(255, (num >> 8 & 0x00FF) + amt)
  const B = Math.min(255, (num & 0x0000FF) + amt)
  return `#${(0x1000000 + R*0x10000 + G*0x100 + B).toString(16).slice(1)}`
}

2. 图表配置修正

this.locations_pie_data = {
  labels: [...innerLabels, ...outerLabels],
  datasets: [
    // 外层:城市数据,环形更靠外
    {
      type: 'doughnut',
      data: outerData,
      backgroundColor: outerColors,
      weight: 1,
      borderWidth: 1,
      borderColor: '#fff'
    },
    // 内层:国家数据,环形更靠内
    {
      type: 'doughnut',
      data: innerData,
      backgroundColor: innerColors,
      weight: 1,
      borderWidth: 1,
      borderColor: '#fff'
    }
  ],
  options: {
    cutout: '40%', // 控制中心空心区域大小
    plugins: {
      legend: {
        position: 'right',
        // 过滤图例,只显示国家层级,避免城市太多导致图例杂乱
        filter: (item) => innerLabels.includes(item.text)
      },
      tooltip: {
        callbacks: {
          label: (ctx) => {
            const total = ctx.dataset.data.reduce((a,b) => a+b, 0)
            const percentage = ((ctx.parsed / total)*100).toFixed(1)
            return `${ctx.label}: ${ctx.parsed} (${percentage}%)`
          }
        }
      }
    }
  }
}

最终效果

  • 内层4个扇区分别对应4个国家,扇区大小和该国总数据量成正比
  • 外层每个国家对应的扇区自动拆分为下属城市的小扇区,大小和城市出现次数成正比,同国家的外层扇区用同色系浅色,和内层扇区视觉关联
  • 鼠标悬浮可查看对应分类的数值和占比
  • 图例仅展示国家分类,信息层级清晰

双层环形图预期效果

内容的提问来源于stack exchange,提问作者Daniel H.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 11:45:34