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

如何基于D3.js将现有垂直柱状图转换为水平柱状图

将D3.js垂直分组柱状图转为水平分组柱状图

我已经帮你把垂直柱状图改成了水平版本,直接替换你的代码就能运行。下面是完整的修改后代码,之后我会解释关键的调整点:

修改后的完整代码

JavaScript 部分

$(document).ready(function() { render_chart(); });

function render_chart() {
    var stack = d3.stack;
    var dataset = {
        "categories": ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sunday'],
        "series": ["Group1", "Group2", "Group3"],
        "colors": ["#3498db", "#e74c3c", "#2ecc71"],
        "layers": [
            [{ "y": 1, "y0": 20, "month": "Monday" }, { "y": 2, "y0": 18, "month": "Tuesday" }, { "y": 5, "y0": 18, "month": "Wednesday" }, { "y": 10, "y0": 20, "month": "Thursday" }, { "y": 14, "y0": 23, "month": "Friday" }, { "y": 18, "y0": 24, "month": "Saturday" }, { "y": 20, "y0": 24, "month": "Sunday" }],
            [{ "y": 12, "y0": 24, "month": "Monday" }, { "y": 14, "y0": 24, "month": "Tuesday" }, { "y": 13, "y0": 23, "month": "Wednesday" }, { "y": 16, "y0": 21, "month": "Thursday" }, { "y": 18, "y0": 23, "month": "Friday" }, { "y": 19, "y0": 22, "month": "Saturday" }, { "y": 20, "y0": 22, "month": "Sunday" }],
            [{ "y": 8, "y0": 24, "month": "Monday" }, { "y": 14, "y0": 21, "month": "Tuesday" }, { "y": 12, "y0": 19, "month": "Wednesday" }, { "y": 15, "y0": 23, "month": "Thursday" }, { "y": 18, "y0": 21, "month": "Friday" }, { "y": 16, "y0": 23, "month": "Saturday" }, { "y": 17, "y0": 24, "month": "Sunday" }]
        ]
    };

    n = dataset["series"].length;
    m = dataset["layers"].length;
    yGroupMax = d3.max(dataset["layers"], function(layer) { return d3.max(layer, function(d) { return d.y0; }); });
    yGroupMin = d3.min(dataset["layers"], function(layer) { return d3.min(layer, function(d) { return d.y; }); });

    // 调整margin,适配水平布局(增加底部空间放图例,左侧保持足够宽度)
    var margin = { top: 50, right: 50, bottom: 80, left: 120 },
        width = 900 - margin.left - margin.right,
        height = 600 - margin.top - margin.bottom; // 增加高度适配垂直排列的日期

    // 交换比例尺:y轴用band对应日期,x轴用线性对应数值
    var yScale = d3.scaleBand()
        .domain(dataset["categories"])
        .rangeRound([0, height]) // y轴从上到下排列日期
        .padding(.08);

    var xScale = d3.scaleLinear()
        .domain([yGroupMin, yGroupMax])
        .range([0, width]); // x轴从左到右数值增大

    // 调整坐标轴方向
    var xAxis = d3.axisBottom(xScale)
        .tickSize(7)
        .tickPadding(6);

    var yAxis = d3.axisLeft(yScale).tickSize(0);

    var svg = d3.select("#groupchart").append("svg")
        .attr("width", width + margin.left + margin.right)
        .attr("height", height + margin.top + margin.bottom)
        .append("g")
        .attr("transform", "translate(" + margin.left + "," + margin.top + ")");

    var layer = svg.selectAll(".layer")
        .data(dataset["layers"])
        .enter().append("g")
        .attr("class", "layer");

    var rect = layer.selectAll("rect")
        .data(function(d, i) {
            d.map(function(b) { b.colorIndex = i; return b; });
            return d;
        })
        .enter()
        .append("rect")
        .transition()
        .duration(500)
        .delay(function(d, i) { return i * 10; })
        // 交换x/y属性:y对应日期分组,height对应每组柱子高度
        .attr("y", function(d, i, j) {
            var k = Array.prototype.indexOf.call(j[i].parentNode.parentNode.childNodes, j[i].parentNode);
            return yScale(d.month) + yScale.bandwidth() / n * k;
        })
        .attr("height", yScale.bandwidth() / n)
        .transition()
        // x对应数值起点,width对应数值范围长度
        .attr("x", function(d) { return xScale(d.y); })
        .attr("width", function(d) { return xScale(d.y0) - xScale(d.y); })
        .attr("class", "bar")
        .style("fill", function(d) { return dataset["colors"][d.colorIndex]; });

    // 绘制x轴(底部)
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis);

    // 绘制y轴(左侧)
    svg.append("g")
        .attr("class", "y axis")
        .call(yAxis);

    // 标题调整位置
    svg.append("text")
        .attr("x", width / 2)
        .attr("y", -20)
        .attr("text-anchor", "middle")
        .text("Horizontal Grouped Bar Chart Test");

    // 图例调整到底部
    var legend = svg.append("g")
        .attr("class", "legend")
        .attr("transform", "translate(" + (width/2 - 180) + "," + (height + 30) + ")"); // 居中放置

    legend.selectAll('rect')
        .data(dataset["colors"])
        .enter()
        .append("rect")
        .attr("x", function(d, i) { return i * 120; })
        .attr("y", 0)
        .attr("width", 10)
        .attr("height", 10)
        .style("fill", function(d) { return d; });

    legend.selectAll('text')
        .data(dataset["series"])
        .enter()
        .append("text")
        .attr("x", function(d, i) { return i * 120 + 12; })
        .attr("y", 9)
        .text(function(d) { return d; });

    // 调整tooltip位置逻辑
    var tooltip = d3.select("body")
        .append('div')
        .attr('class', 'tooltip');

    tooltip.append('div')
        .attr('class', 'month');
    tooltip.append('div')
        .attr('class', 'tempRange');

    svg.selectAll("rect")
        .on('mouseover', function(d) {
            if (!d.month) return null;
            tooltip.select('.month').html("<b>" + d.month + "</b>");
            tooltip.select('.tempRange').html(d.y + ":00 Hours to " + d.y0 + ":00 Hours");
            tooltip.style('display', 'block');
            tooltip.style('opacity', 1);
        })
        .on('mousemove', function(d) {
            if (!d.month) return null;
            tooltip.style('top', (d3.event.pageY - 30) + 'px')
                .style('left', (d3.event.pageX + 10) + 'px');
        })
        .on('mouseout', function() {
            tooltip.style('display', 'none');
            tooltip.style('opacity', 0);
        });
}

CSS 部分

.axis path, .axis line {
    fill: none;
    font: 10px sans-serif;
    stroke: #000;
    shape-rendering: crispEdges;
}

.bar {
    opacity: 0.8;
    transition: opacity 0.2s;
}

.bar:hover {
    opacity: 1;
}

.tooltip {
    position: absolute;
    background-color: rgba(0, 0, 0, 0.8);
    color: #fff;
    padding: 8px 12px;
    border-radius: 4px;
    font-size: 12px;
    pointer-events: none;
}

关键修改点说明

我主要做了以下核心调整来实现水平布局:

  • 比例尺互换:把原来对应日期的xScale(band类型)改成yScale,对应数值的yScale(线性)改成xScale,并调整它们的range适配水平方向
  • 矩形属性交换:将柱子的x/y属性和width/height属性互换,计算逻辑也对应调整——y对应日期分组位置,height对应每组柱子高度,x对应数值起点,width对应数值范围长度
  • 坐标轴调整:保持x轴在底部、y轴在左侧,但更新了坐标轴的比例尺关联,优化了y轴的tick样式
  • 布局适配:调整了margin值(增加底部空间放图例)、svg高度,重新定位了标题和图例的位置,让整体布局更协调
  • Tooltip优化:修改了鼠标移动时tooltip的定位逻辑,改用pageX/pageY避免布局偏移问题,同时调整了hover时的透明度效果

内容的提问来源于stack exchange,提问作者Rajkishan Swami

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:31:52