使用Chart.js实现分组饼图的技术求助
解决方案:用Chart.js实现分组嵌套饼图
Chart.js原生支持多数据集的环形图,通过调整cutout参数可实现外层大类、内层小类的分组效果,核心实现要点如下:
- 数据结构梳理:外层数据集对应大类的汇总值,内层数据集对应每个大类下的小类细分值,确保数据维度匹配
- 环形层级配置:外层设置较小的
cutout(如'30%'),内层设置较大的cutout(如'70%'),形成嵌套视觉效果 - 颜色关联设计:同一大类的外层和内层细分项使用同色系,外层用主色、内层用浅色调,方便用户识别关联关系
- 数据处理优化:读取CSV时按大类分组,避免零散数据存储导致的逻辑混乱
以下是修改后的完整实现代码:
修改后的HTML
<div class="chartMenu"> <p>分组饼图演示</p> </div> <div class="chartCard"> <div class="chartBox"> <canvas id="myChart"></canvas> </div> </div> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
修改后的CSS(保持原样式不变)
* { margin: 0; padding: 0; font-family: sans-serif; } .chartMenu { width: 100vw; height: 40px; background: #1a1a1a; color: rgba(255, 26, 104, 1); } .chartMenu p { padding: 10px; font-size: 20px; } .chartCard { width: 100vw; height: calc(100vh - 40px); background: rgba(255, 26, 104, 0.2); display: flex; align-items: center; justify-content: center; } .chartBox { width: 700px; padding: 20px; border-radius: 20px; border: solid 3px rgba(255, 26, 104, 1); background: white; }
修改后的JavaScript
// 按大类分组存储数据 const groupedData = {}; // 外层大类标签和数据 const outerLabels = []; const outerData = []; // 内层小类标签和数据 const innerLabels = []; const innerData = []; // 颜色映射:大类对应主色,小类对应同色系浅色调 const colorMap = { '大类1': '#619E98', '大类2': '#4164BE', '大类3': '#7D35CA', // 可根据实际CSV数据扩展更多颜色 }; showGroupedDoughnutChart(); async function showGroupedDoughnutChart() { await processCSVData(); const ctx = document.getElementById("myChart").getContext("2d"); new Chart(ctx, { type: "doughnut", data: { labels: [...outerLabels, ...innerLabels], datasets: [ // 外层:大类汇总数据 { label: "文档类型汇总", backgroundColor: outerLabels.map(label => colorMap[label]), borderWidth: 1, borderRadius: 5, cutout: '30%', // 外层环形,预留中间空间给内层 data: outerData, order: 1 // 控制绘制顺序,让外层显示在内层上方 }, // 内层:小类细分数据 { label: "文档版本细分", backgroundColor: innerLabels.map(label => { const parentType = label.split('-')[0]; // 假设小类标签格式为"大类-小类" const baseColor = colorMap[parentType]; // 将主色调浅,区分外层与内层 return lightenColor(baseColor, 20); }), borderWidth: 1, borderRadius: 5, cutout: '70%', // 内层环形,占用中间区域 data: innerData, order: 0 } ] }, options: { plugins: { legend: { display: true, position: "right", }, title: { display: true, text: "按类别划分的标签分布", color: "black", }, tooltip: { callbacks: { label: function(context) { const label = context.label || ''; const value = context.raw || 0; return `${label}: ${value}`; } } } }, layout: { padding: 10, }, maintainAspectRatio: true, responsive: true, }, }); } async function processCSVData() { const response = await fetch("stats_doc_type_Rev.csv"); const data = await response.text(); // 过滤空行,避免解析错误 const rows = data.split("\n").slice(1).filter(row => row.trim() !== ''); rows.forEach(row => { const columns = row.split(",").map(col => col.trim()); const docType = columns[0]; const docRevision = columns[1]; const revCount = parseInt(columns[2]); const totalCount = parseInt(columns[3]); // 初始化大类分组 if (!groupedData[docType]) { groupedData[docType] = { total: totalCount, revisions: [] }; outerLabels.push(docType); outerData.push(totalCount); } // 添加小类数据 const innerLabel = `${docType}-版本${docRevision}`; innerLabels.push(innerLabel); innerData.push(revCount); groupedData[docType].revisions.push({ label: innerLabel, count: revCount }); }); } // 辅助函数:将十六进制颜色调浅 function lightenColor(color, percent) { color = color.replace(/^#/, ''); const num = parseInt(color, 16); const amt = Math.round(2.55 * percent); const R = Math.min(255, Math.max(0, (num >> 16) + amt)); const G = Math.min(255, Math.max(0, (num >> 8 & 0x00FF) + amt)); const B = Math.min(255, Math.max(0, (num & 0x0000FF) + amt)); return '#' + (0x1000000 + R * 0x10000 + G * 0x100 + B).toString(16).slice(1); }
关键修改说明
- 数据分组管理:不再使用零散数组存储数据,而是按文档类型分组,清晰区分大类汇总与小类细分数据
- 环形层级控制:通过
cutout参数定义内外层环形的大小,order参数确保外层在视觉上层显示 - 颜色体系优化:小类使用对应大类的浅色调,保证视觉上的关联识别性
- 数据健壮性处理:读取CSV时过滤空行,避免无效数据导致的图表渲染错误
- 交互体验提升:优化tooltip显示逻辑,确保标签与数值对应准确
原提问及代码
我需要用Chart.js实现分组饼图(效果为外层大类、内层对应小类的嵌套结构),虽然知道D3.js可以实现,但没时间学习,只能用Chart.js。我自己尝试写了代码但没成功,不确定是问题本身复杂还是思路有误,希望得到技术帮助。
原HTML代码
<div class="chartMenu"> <p></p> </div> <div class="chartCard"> <div class="chartBox"> <canvas id="myChart"></canvas> </div> </div> <script type="text/javascript" src="https://cdn.jsdelivr.net/npm/chart.js" ></script>
原CSS代码
* { margin: 0; padding: 0; font-family: sans-serif; } .chartMenu { width: 100vw; height: 40px; background: #1a1a1a; color: rgba(255, 26, 104, 1); } .chartMenu p { padding: 10px; font-size: 20px; } .chartCard { width: 100vw; height: calc(100vh - 40px); background: rgba(255, 26, 104, 0.2); display: flex; align-items: center; justify-content: center; } .chartBox { width: 700px; padding: 20px; border-radius: 20px; border: solid 3px rgba(255, 26, 104, 1); background: white; }
原JavaScript代码
<script> const documentType = []; const documentRevision = []; const docTypeRev = []; const docTypeTotal = []; const percentage = []; showDoughnutChart(); ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // START OF DOUGHNUT CHART ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// async function showDoughnutChart() { await getDataDoughnutGraph(); const doughnutChart = document .getElementById("myChart") .getContext("2d"); let chart3a = new Chart(doughnutChart, { type: "doughnut", data: { labels: documentType, datasets: [ { label: "Some metric", backgroundColor: [ "#619E98", "#4164BE", "#7D35CA", "#B2A44D", "#946B76", "#6C9D62", "#C6B139", "#2688D9", "#D62963", "#CCBF33", "#adff2f", "#B6495D", ], borderWidth: 1, borderRadius: 5, offset: [0, 0, 0, 0, 0, 0, 0], cutout: "50%", data: docTypeRev, }, { label: "Some metric", backgroundColor: [ "#619E98", "#4164BE", "#7D35CA", "#B2A44D", "#946B76", "#6C9D62", "#C6B139", "#2688D9", "#D62963", "#CCBF33", "#adff2f", "#B6495D", ], borderWidth: 1, borderRadius: 5, offset: [0, 0, 0, 0, 0, 0, 0], cutout: "50%", data: [1, 1, 1], }, ], }, options: { plugins: { legend: { display: true, position: "right", }, title: { display: true, text: "Tag Distrubution by Class", color: "black", }, }, layout: { padding: 10, }, maintainAspectRatio: true, responsive: true, }, }); } async function getDataDoughnutGraph() { const responseLine = await fetch("stats_doc_type_Rev.csv"); const chart3el = document.getElementById("myChart").getContext("2d"); const data = await responseLine.text(); const table = data.split("\n").slice(1); table.forEach((row) => { //split by line const columns = row.split(","); // split by column const documentTypeEX = columns[0]; const documentRevisionEX = columns[1]; const docTypeRevEX = columns[2]; const docTypeTotalEX = columns[3]; const percentage_EX = columns[4]; //pushing into global variables documentType.push(documentTypeEX); documentRevision.push(documentRevisionEX); docTypeRev.push(docTypeRevEX); docTypeTotal.push(docTypeTotalEX); percentage.push(percentage_EX); // console.log(percentage_EX); }); } </script>
内容的提问来源于stack exchange,提问作者safalstha
相关产品推荐
相关产品推荐

