D3.js中rect元素沿X轴定位异常的排查与解决
D3.js柱状图定位问题:矩形跑到SVG底部而非X轴上
我正在学习使用D3.js,我的rect元素在Y轴上的高度与轴数值匹配,但它们却跑到了SVG的底部,而非定位在X轴上:

以下是我的代码片段:
let data = { "month": "Enero", "expenses": [{ "id": 1, "category": "Comida", "total": 1500 }, { "id": 2, "category": "Vicio", "total": 2000 }, { "id": 4, "category": "Ropa", "total": 1000 }, { "id": 3, "category": "Alquiler", "total": 2100 }] } let width = 800; let height = 1000; let padding = 50; let xScale; let yScale; let gastos = data.expenses; console.log(gastos) let generateScale = () => { yScale = d3.scaleLinear().domain([d3.max(gastos, (item) => { return item["total"] + 500 }), d3.min(gastos, (item) => { return item["total"] - 500 })]).range([padding, height - padding]); xScale = d3.scaleLinear().domain([d3.min(gastos, (item) => { return item["id"] - 1 }), d3.max(gastos, (item) => { return item["id"] + 1 })]).range([padding, width - padding]); } let svg = d3.select('#canvas') .attr('width', 800) .attr('height', 1000) let drawRect = () => { svg.selectAll('rect') .data(gastos) .enter() .append('rect') .attr('y', function (d) { return yScale(d['total']); }) .attr('height', function (d) { return d['total']; }) .attr('x', (item) => { return xScale(item['id']); }) .attr('width', 40) .attr('fill', 'black') .attr('transform', 'translate(0, 0)') } let generateAxes = () => { let xAxis = d3.axisBottom(xScale) let yAxis = d3.axisLeft(yScale) svg.append('g') .call(xAxis) .attr('id', 'x-axis') .attr('transform', 'translate(0, ' + (height - padding) + ')') svg.append('g') .call(yAxis) .attr('id', 'y-axis') .attr('transform', 'translate(' + padding + ', 0)') } generateScale() generateAxes() drawRect()
我尝试修改rect元素的'y'和'height'属性,包括去掉缩放、减去高度等操作,但均未得到预期结果。
问题分析与修复
核心问题
SVG的坐标原点在左上角,Y轴向下为正方向,而柱状图需要从底部X轴向上延伸。你的代码存在两个关键错误:
- Y轴比例尺的定义域顺序颠倒,导致数值映射的Y坐标方向完全相反
- 矩形高度直接使用原始数值,没有结合比例尺范围,位置计算错误
具体修复步骤
- 修正Y轴比例尺
把定义域改为从0到最大值(更符合柱状图逻辑),值域对应从SVG底部(height - padding)到顶部(padding),让大数值映射到更靠上的位置:
let generateScale = () => { // 修正Y轴比例尺 yScale = d3.scaleLinear() .domain([0, d3.max(gastos, item => item.total) + 500]) .range([height - padding, padding]); // 可选:改用序数比例尺优化X轴(解决ID不连续的间距问题) xScale = d3.scaleBand() .domain(gastos.map(item => item.category)) .range([padding, width - padding]) .padding(0.2); }
- 修正矩形的Y坐标和高度
矩形的y坐标是数值对应的Y位置,高度需要用底部Y坐标减去该位置的Y值(抵消SVG向下的正方向):
let drawRect = () => { svg.selectAll('rect') .data(gastos) .enter() .append('rect') .attr('y', d => yScale(d.total)) .attr('height', d => (height - padding) - yScale(d.total)) .attr('x', item => xScale(item.category)) // 配合序数比例尺修改 .attr('width', xScale.bandwidth()) // 自动计算柱子宽度 .attr('fill', 'black') }
- 修正X轴生成逻辑
如果改用了序数比例尺,X轴需要调整为基于类别的轴,可选旋转标签避免重叠:
let generateAxes = () => { let xAxis = d3.axisBottom(xScale) let yAxis = d3.axisLeft(yScale) svg.append('g') .call(xAxis) .attr('id', 'x-axis') .attr('transform', 'translate(0, ' + (height - padding) + ')') .selectAll('text') .attr('transform', 'rotate(-45)') .style('text-anchor', 'end'); svg.append('g') .call(yAxis) .attr('id', 'y-axis') .attr('transform', 'translate(' + padding + ', 0)') }
完整修复后代码
let data = { "month": "Enero", "expenses": [{ "id": 1, "category": "Comida", "total": 1500 }, { "id": 2, "category": "Vicio", "total": 2000 }, { "id": 4, "category": "Ropa", "total": 1000 }, { "id": 3, "category": "Alquiler", "total": 2100 }] } let width = 800; let height = 1000; let padding = 50; let xScale; let yScale; let gastos = data.expenses; let generateScale = () => { yScale = d3.scaleLinear() .domain([0, d3.max(gastos, item => item.total) + 500]) .range([height - padding, padding]); xScale = d3.scaleBand() .domain(gastos.map(item => item.category)) .range([padding, width - padding]) .padding(0.2); } let svg = d3.select('#canvas') .attr('width', width) .attr('height', height) let drawRect = () => { svg.selectAll('rect') .data(gastos) .enter() .append('rect') .attr('y', d => yScale(d.total)) .attr('height', d => (height - padding) - yScale(d.total)) .attr('x', item => xScale(item.category)) .attr('width', xScale.bandwidth()) .attr('fill', 'black') } let generateAxes = () => { let xAxis = d3.axisBottom(xScale) let yAxis = d3.axisLeft(yScale) svg.append('g') .call(xAxis) .attr('id', 'x-axis') .attr('transform', 'translate(0, ' + (height - padding) + ')') .selectAll('text') .attr('transform', 'rotate(-45)') .style('text-anchor', 'end'); svg.append('g') .call(yAxis) .attr('id', 'y-axis') .attr('transform', 'translate(' + padding + ', 0)') } generateScale() generateAxes() drawRect()
内容的提问来源于stack exchange,提问作者Gabriel Trcka
相关产品推荐
相关产品推荐

