D3.js按年分组散点图x轴自定义刻度及数据映射优化问题
核心修改思路
针对你提出的三个问题,核心调整逻辑如下:
- 修正x轴刻度配置,传入正确的刻度值数组作为分割线位置
- 单独渲染年份文本标签,定位到对应分组的中点下方
- 替换手动输入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
相关产品推荐
相关产品推荐

