如何将D3折线图X轴从时间型改为分类型?
将D3时间轴折线图改为字符串分类X轴的修改方案
核心修改点
针对官方折线图示例,需要调整X轴的比例尺、定义域计算和轴生成逻辑,适配字符串类型的分类数据:
- 替换X轴比例尺类型:原默认使用
d3.scaleUtc(时间比例尺),分类字符串数据需改用d3.scaleBand(带比例尺),专门适配离散分类数据。 - 修正X轴定义域计算:时间轴用
d3.extent取极值,但分类轴需要直接使用所有X值的数组来保持数据原有顺序。 - 调整X轴生成逻辑:分类轴不需要
ticks()参数(该参数针对连续轴),直接移除即可确保每个分类标签都显示。 - 修正折线点位置:带比例尺下,折线点需要定位到每个分类的中间位置,避免偏移。
修改后的LineChart函数代码
LineChart = // Copyright 2021 Observable, Inc. // Released under the ISC license. function LineChart(data, { x = ([x]) => x, // 提取分类X值 y = ([, y]) => y, // 提取数值Y值 defined, // 处理数据间隙 curve = d3.curveLinear, // 插值方式 marginTop = 20, // 顶部边距(像素) marginRight = 30, // 右侧边距(像素) marginBottom = 30, // 底部边距(像素) marginLeft = 40, // 左侧边距(像素) width = 640, // 外部宽度(像素) height = 400, // 外部高度(像素) xType = d3.scaleBand, // 替换为分类带比例尺 xDomain, // X轴定义域(分类数组) xRange = [marginLeft, width - marginRight], // X轴范围 yType = d3.scaleLinear, // Y轴比例尺类型 yDomain, // Y轴定义域 yRange = [height - marginBottom, marginTop], // Y轴范围 yFormat, // Y轴格式字符串 yLabel, // Y轴标签 color = "currentColor", // 折线颜色 strokeLinecap = "round", // 折线端点样式 strokeLinejoin = "round", // 折线拐点样式 strokeWidth = 1.5, // 折线宽度(像素) strokeOpacity = 1, // 折线透明度 } = {}) { // 计算数据值 const X = d3.map(data, x); const Y = d3.map(data, y); const I = d3.range(X.length); if (defined === undefined) defined = (d, i) => !isNaN(Y[i]); // 分类X值无需判断NaN const D = d3.map(data, defined); // 计算默认定义域 if (xDomain === undefined) xDomain = X; // 直接用所有分类值作为X轴定义域 if (yDomain === undefined) yDomain = [0, d3.max(Y)]; // 构建比例尺与轴 const xScale = xType(xDomain, xRange).padding(0.1); // 添加padding让分类间有间隔 const yScale = yType(yDomain, yRange); const xAxis = d3.axisBottom(xScale).tickSizeOuter(0); // 移除ticks参数,显示所有分类标签 const yAxis = d3.axisLeft(yScale).ticks(height / 40, yFormat); // 构建折线生成器 const line = d3.line() .defined(i => D[i]) .curve(curve) .x(i => xScale(X[i]) + xScale.bandwidth() / 2) // 将折线点定位到分类中间 .y(i => yScale(Y[i])); const svg = d3.create("svg") .attr("width", width) .attr("height", height) .attr("viewBox", [0, 0, width, height]) .attr("style", "max-width: 100%; height: auto; height: intrinsic;"); svg.append("g") .attr("transform", `translate(0,${height - marginBottom})`) .call(xAxis); svg.append("g") .attr("transform", `translate(${marginLeft},0)`) .call(yAxis) .call(g => g.select(".domain").remove()) .call(g => g.selectAll(".tick line").clone() .attr("x2", width - marginLeft - marginRight) .attr("stroke-opacity", 0.1)) .call(g => g.append("text") .attr("x", -marginLeft) .attr("y", 10) .attr("fill", "currentColor") .attr("text-anchor", "start") .text(yLabel)); svg.append("path") .attr("fill", "none") .attr("stroke", color) .attr("stroke-width", strokeWidth) .attr("stroke-linecap", strokeLinecap) .attr("stroke-linejoin", strokeLinejoin) .attr("stroke-opacity", strokeOpacity) .attr("d", line(I)); return svg.node(); }
你的配置代码补充优化
注意给chartWidth赋值(避免宽度异常),修改后代码如下:
<script> document.getElementById('chart-title').innerHTML = 'Line Chart'; const chartColor = '#d13c4b'; const chartHeight = 500; const chartWidth = 800; // 补充宽度赋值 const yAxisLabel = "↑ Daily close ($)"; const statusValues = [ {status: 'PHA', value: phaCat}, {status: 'Local', value: localCat} ]; const chart = LineChart(statusValues, { x: d => d.status, y: d => d.value, width: chartWidth, height: chartHeight, color: chartColor, yLabel: yAxisLabel }); document.getElementById("d3_chart").append(chart); </script>
额外优化建议
如果分类名称较长导致标签重叠,可给X轴标签添加旋转:
// 在调用xAxis的代码块中添加以下内容 .call(g => g.selectAll(".tick text") .attr("transform", "rotate(-45)") .style("text-anchor", "end") .attr("dx", "-.8em") .attr("dy", ".15em"))
内容的提问来源于stack exchange,提问作者Dwayne Brissett
相关产品推荐
相关产品推荐

