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

D3.js按年分组散点图x轴自定义刻度及数据映射优化问题

核心修改思路

针对你提出的三个问题,核心调整逻辑如下:

  1. 修正x轴刻度配置,传入正确的刻度值数组作为分割线位置
  2. 单独渲染年份文本标签,定位到对应分组的中点下方
  3. 替换手动输入x坐标的逻辑,通过D3比例尺自动计算同一年份内点的x偏移

完整修改后代码

JavaScript代码

import axios from "axios";

let meetings
let colors = { a: "brown", b: "orange", c: "red", d: "purple", e: "blue", f: "green" };
// 配置年份相关参数
const YEARS = [2014,2015,2016,2017,2018,2019,2020];
// 每个年份区间的宽度
const YEAR_BAND_WIDTH = 9;
// 年份之间的间隔宽度
const YEAR_GAP = 2;

window.addEventListener("load", () => {
    initUI();
});

function initUI() {
    axios
        .get("assets/example.txt")
        .then(async (res) => {
            var rawData = res.data
                .split("\n")
                .filter((row) => (row.indexOf("label") >= 0 ? false : true))
                .map((row) => {
                    var items = row.split(";");
                    return {
                        label: items[0],
                        year: parseInt(items[1]),
                        // 不再使用手动输入的xPosition
                    };
                });

            meetings = addXYPositionForAllOrganizations(rawData);
            scatterplot = await showScatterPlot(meetings);
        });
}

// 合并XY位置计算逻辑,自动生成x坐标
function addXYPositionForAllOrganizations(data) {
    let allMeetings = [];
    Object.keys(colors).forEach(org => {
        let orgData = data.filter(row => row.label == org);
        // 按年份排序
        orgData.sort((a, b) => a.year - b.year);
        // 按年份分组
        let yearGroup = d3.group(orgData, d => d.year);
        yearGroup.forEach((meetingsInYear, year) => {
            meetingsInYear.forEach((meeting, idx) => {
                // 自动计算x坐标:年份起始位置 + 同组内的索引偏移
                let yearIdx = YEARS.indexOf(year);
                meeting.xPosition = yearIdx * (YEAR_BAND_WIDTH + YEAR_GAP) + idx;
                meeting.yPosition = idx + 1;
            })
        })
        allMeetings = allMeetings.concat(orgData);
    })
    return allMeetings;
}

async function showScatterPlot(data) {
    let margin = { top: 10, right: 30, bottom: 100, left: 60 },
        width = 1000 - margin.left - margin.right,
        height = 400 - margin.top - margin.bottom;
    let svg = d3
        .select("#scatter-plot")
        .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 x = d3.scaleLinear().domain([-2, YEARS.length*(YEAR_BAND_WIDTH + YEAR_GAP)]).range([0, width]);
    // 生成分隔刻度:每个年份区间结束的位置
    let splitTicks = YEARS.slice(1).map((_, idx) => (idx+1)*(YEAR_BAND_WIDTH + YEAR_GAP) - YEAR_GAP/2);
    var xAxis = d3.axisBottom(x)
        .tickValues(splitTicks)
        .tickFormat("") // 隐藏默认刻度文本,我们单独加年份标签
        .tickSizeInner(-height); // 刻度线延伸到整个图表高度做分割线
    svg.append("g")
        .attr("class", "x axis")
        .attr("transform", "translate(0," + height + ")")
        .call(xAxis)
        .selectAll(".tick line")
        .style("stroke", "#eee"); // 分割线颜色调浅

    // 单独添加年份文本标签,放在对应分组下方
    YEARS.forEach((year, idx) => {
        let xPos = x(idx*(YEAR_BAND_WIDTH + YEAR_GAP) + YEAR_BAND_WIDTH/2);
        svg.append("text")
            .attr("x", xPos)
            .attr("y", height + 30)
            .attr("text-anchor", "middle")
            .style("font-family", "sans-serif")
            .text(year);
    })

    // x轴总标签
    svg.append("text")
        .attr("transform", "translate(" + (width / 2) + "," + (height + 60) + ")")
        .attr("text-anchor", "middle")
        .style("font-family", "sans-serif")
        .text("Years 2014 - 2020");

    var y = d3.scaleLinear().domain([0.5, 10]).range([height, 0]);
    svg.append("g").attr("class", "y axis").call(d3.axisLeft(y));
    svg.append("text")
        .attr("transform", "rotate(-90)")
        .attr("y", 0 - margin.left)
        .attr("x", 0 - (height / 2))
        .attr("dy", "1em")
        .style("text-anchor", "middle")
        .style("font-family", "sans-serif")
        .text("Amount");

    let meetings = svg.append('g')
        .selectAll("dot")
        .data(data)
        .enter()
        .append("circle")
        .attr("cx", function (d) { return x(d.xPosition); })
        .attr("cy", function (d) { return y(d.yPosition); })
        .attr("r", 5.5)
        .style("fill", getColorForMeeting);
    return { svg, x, y };    
}

function getColorForMeeting(data) {
    return colors[data.label];
}

HTML代码

<script src="https://d3js.org/d3.v4.js"></script>
<div id="scatter-plot"></div>

参数调整说明

如果需要调整年份间隔、同一年份内点的间距,直接修改YEAR_BAND_WIDTH和YEAR_GAP两个变量即可,不需要修改其他逻辑。


内容的提问来源于stack exchange,提问作者Dr.J

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.04 08:54:01