如何使用d3.js实现Tableau同款多列水平条形图
D3.js 多列水平分组条形图实现方案
基础准备
你需要先引入D3.js v7版本资源,本地下载引入或者使用公共CDN都可以。
核心实现步骤
数据预处理
首先解析你提供的CSV格式数据,统一格式化字段名称,同时计算出所有数值指标的最大值,用来匹配x轴的展示范围:
// 以下是你提供的样本数据,也可以替换为外部加载的csv文件内容 const csvRaw = `id,country_name,value 1,value 2 actual,value 2 planned,value 3 actual,value 3 planned 1,Germany,3,6,9,12,12 1,China,5,9,14,6,18 1,India,4,14,19,14,15 1,Netherlands,5,7,1,11,7 1,Russia,3,7,11,7,11 1,Saudi Arabia,3,12,8,7,11 1,United States,3,2,20,8,2 2,Croatia,2,1,14,3,16 2,Brazil,4,2,20,6,4 2,Bulgaria,1,15,9,9,13 2,Germany,1,13,12,14,17 2,China,5,3,5,1,12 2,India,4,6,7,8,15 2,Singapore,1,13,20,1,6 3,Russia,5,14,18,4,17 3,Saudi Arabia,4,10,16,1,3 3,United States,1,3,4,8,3 3,Croatia,5,11,15,4,3 3,Brazil,1,1,13,4,8 3,Bulgaria,3,15,14,15,7 3,Germany,3,11,12,3,8 4,China,5,12,13,14,12 5,India,4,1,10,3,17 5,Singapore,3,15,14,5,4 5,Canada,3,10,7,12,18 5,Saudi Arabia,1,1,17,3,8 4,Canada,3,15,6,8,3 4,Qatar,1,8,16,14,20 4,Brazil,3,12,11,12,11 4,Bulgaria,2,5,4,13,18 4,Canada,3,11,20,13,13 5,China,4,9,7,9,8`; // 解析并格式化数据,把字符串类型的数值转为数字类型 const formattedData = d3.csvParse(csvRaw, item => { return { groupId: item.id, country: item.country_name, value1: +item['value 1'], value2Actual: +item['value 2 actual'], value2Planned: +item['value 2 planned'], value3Actual: +item['value 3 actual'], value3Planned: +item['value 3 planned'] } }); // 定义需要展示的指标列表、对应的显示名称和条形颜色,可按需调整 const metricConfig = [ {key: 'value1', name: '值1', color: '#2c7fb8'}, {key: 'value2Actual', name: '值2-实际', color: '#7fcdbb'}, {key: 'value2Planned', name: '值2-计划', color: '#edf8b1'}, {key: 'value3Actual', name: '值3-实际', color: '#fdae61'}, {key: 'value3Planned', name: '值3-计划', color: '#d7191c'} ]; // 计算所有指标的最大值,确定x轴的最大刻度 const maxVal = d3.max(formattedData, item => d3.max(metricConfig, m => item[m.key])); // 提取所有不重复的国家名称,作为y轴的分类项 const countryList = [...new Set(formattedData.map(item => item.country))];
画布与比例尺配置
先定义画布的宽高、边距,再配置三类比例尺:x轴数值比例尺、y轴国家分类比例尺、每个国家下多指标的子分类比例尺:
// 定义画布边距和尺寸 const margin = {top: 20, right: 30, bottom: 30, left: 120}, chartWidth = 900 - margin.left - margin.right, chartHeight = countryList.length * 40 - margin.top - margin.bottom; // 创建svg画布 const svg = d3.select("body") .append("svg") .attr("width", chartWidth + margin.left + margin.right) .attr("height", chartHeight + margin.top + margin.bottom) .append("g") .attr("transform", `translate(${margin.left},${margin.top})`); // x轴线性比例尺,把数值映射为条形的像素长度 const xScale = d3.scaleLinear() .domain([0, maxVal]) .range([0, chartWidth]); // y轴分类比例尺,把国家名称映射为对应的纵向位置 const yScale = d3.scaleBand() .domain(countryList) .range([0, chartHeight]) .padding(0.2);
绘制坐标轴和条形
// 绘制y轴,展示国家名称 svg.append("g") .call(d3.axisLeft(yScale).tickSize(0)) .select(".domain").remove(); // 绘制x轴,展示数值刻度 svg.append("g") .attr("transform", `translate(0, ${chartHeight})`) .call(d3.axisBottom(xScale)); // 给每个国家创建一个分组容器 const countryGroup = svg.selectAll(".country-item") .data(formattedData) .join("g") .attr("class", "country-item") .attr("transform", d => `translate(0, ${yScale(d.country)})`); // 绘制每个国家下的所有指标条形 countryGroup.selectAll(".metric-bar") .data(d => metricConfig.map(m => ({value: d[m.key], color: m.color, name: m.name}))) .join("rect") .attr("class", "metric-bar") .attr("x", 0) .attr("y", (d, i) => i * (yScale.bandwidth() / metricConfig.length)) .attr("width", d => xScale(d.value)) .attr("height", yScale.bandwidth() / metricConfig.length - 2) .attr("fill", d => d.color);
自定义调整说明
- 如果需要按样本数据里的
id字段分组展示,只需要先把数据按groupId分组,每个分组对应独立的图表区域即可,绘制逻辑和上面一致。 - 如果你需要添加图例、悬浮提示、条形上的数值标签,可以在现有代码基础上直接扩展对应的绘制逻辑即可。
内容的提问来源于stack exchange,提问作者iLoveYou3000
相关产品推荐
相关产品推荐

