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

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轴向上延伸。你的代码存在两个关键错误:

  1. Y轴比例尺的定义域顺序颠倒,导致数值映射的Y坐标方向完全相反
  2. 矩形高度直接使用原始数值,没有结合比例尺范围,位置计算错误

具体修复步骤

  1. 修正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);
}
  1. 修正矩形的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')
}
  1. 修正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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 01:50:16