如何在D3.js中截断坐标轴.domain类线条的多余部分
解决D3图表中
.domain轴线截断问题 我在使用D3构建图表时,需要截断带有.domain类的X轴和Y轴线条的多余部分,让轴线仅保留数据覆盖的范围(当前图表的多余区域已标记,预期效果是轴线两端对齐数据的首尾点)。
解决方案:手动绘制自定义轴线
D3默认生成的.domain轴线是贯穿整个轴的长度,无法直接截断,因此我们可以移除自动生成的domain线,手动计算数据范围对应的坐标来绘制轴线。
代码修改步骤:
移除自动生成的domain线:
先删除原有对自动生成.domain元素的样式设置,并移除默认生成的domain线:// 注释或删除原有样式设置 // yAxis.select('.domain').attr('class', 'domain stroke-neutral-600 stroke-[3px]'); // xAxis.select('.domain').attr('class', 'domain stroke-neutral-600 stroke-[3px]'); // 移除默认生成的domain线 xAxis.select('.domain').remove(); yAxis.select('.domain').remove();绘制自定义X轴domain线:
计算X轴首尾数据点的中心坐标,绘制仅覆盖数据范围的水平线条:const firstX = x(aapl[0].month) + x.bandwidth()/2; const lastX = x(aapl[aapl.length-1].month) + x.bandwidth()/2; svg.append('line') .attr('class', 'domain stroke-neutral-600 stroke-[3px]') .attr('x1', firstX) .attr('x2', lastX) .attr('y1', height - marginBottom) .attr('y2', height - marginBottom);绘制自定义Y轴domain线:
计算Y轴数据的最大和最小对应坐标,绘制仅覆盖数据范围的垂直线条:const yMax = y(d3.max(aapl, d => d.value)*1.05); const yMin = y(0); svg.append('line') .attr('class', 'domain stroke-neutral-600 stroke-[3px]') .attr('x1', marginLeft) .attr('x2', marginLeft) .attr('y1', yMax) .attr('y2', yMin);
修改后的完整代码
export const App = () => { const ref = useRef<SVGSVGElement | null>(null); const aapl: DataPoint[] = [ { month: 'Jan', value: Math.random() * 100 }, { month: 'Feb', value: Math.random() * 100 }, { month: 'Mar', value: Math.random() * 100 }, { month: 'Apr', value: Math.random() * 100 }, { month: 'May', value: Math.random() * 100 }, { month: 'Jun', value: Math.random() * 100 }, { month: 'Jul', value: Math.random() * 100 }, { month: 'Aug', value: Math.random() * 100 }, { month: 'Sep', value: Math.random() * 100 }, { month: 'Oct', value: Math.random() * 100 }, { month: 'Nov', value: Math.random() * 100 }, { month: 'Dec', value: Math.random() * 100 }, ]; // 图表尺寸与边距 const width = 928; const height = 500; const marginTop = 20; const marginRight = 30; const marginBottom = 30; const marginLeft = 40; useEffect(() => { if (ref.current) { // X轴比例尺 const x = d3 .scaleBand() .domain(aapl.map((d) => d.month)) .range([0, width - marginRight]) .padding(0.1); // Y轴比例尺 const y = d3 .scaleLinear() .domain([ 0, d3.max(aapl, (d) => d.value * 1.05), ] as Iterable<d3.NumberValue>) .range([height - marginBottom, marginTop]); // 面积生成器 const area = d3 .area<DataPoint>() .x((d) => { const xValue = x(d.month); return xValue !== undefined ? xValue + x.bandwidth() / 2 : 0; }) .y0(y(0)) .y1((d) => y(d.value)); // 线条生成器 const line = d3 .line<DataPoint>() .x((d) => { const xValue = x(d.month); return xValue !== undefined ? xValue + x.bandwidth() / 2 : 0; }) .y((d) => y(d.value)); // SVG容器 const svg = d3 .select(ref.current) .attr('width', width) .attr('height', height) .attr('viewBox', [0, 0, width, height]) .attr('style', 'max-width: 100%; height: auto;'); const areaData = area(aapl); const lineData = line(aapl); // 创建渐变填充 const lg = svg .append('defs') .append('linearGradient') .attr('id', 'graphGradient') .attr('x1', '0%') .attr('x2', '0%') .attr('y1', '0%') .attr('y2', '100%') .attr('gradientUnits', 'userSpaceOnUse'); lg.append('stop').style('stop-color', '#FBB979'); lg.append('stop').attr('offset', '46%').style('stop-color', '#FFEDD6'); lg.append('stop') .attr('offset', '100%') .style('stop-color', '#FFEDD6') .style('stop-opacity', '0'); // 添加面积路径 svg .append('path') .attr('fill', 'url(#graphGradient)') .attr('d', areaData); // 添加线条路径 svg .append('path') .attr('fill', 'none') .attr('stroke', '#FBB979') .attr('stroke-width', 2) .attr('d', lineData); // 添加X轴 const xAxis = svg .append('g') .attr('transform', `translate(0,${height - marginBottom} )`) .attr('class', 'x-axis') .call( d3 .axisBottom(x) .tickSizeInner(0) .tickSizeOuter(0) .ticks(width / 80) .tickPadding(12) ); // 添加Y轴 const yAxis = svg .append('g') .attr('transform', `translate(${marginLeft},0)`) .attr('class', 'y-axis') .call( d3 .axisLeft(y) .ticks(height / 40) .tickPadding(0) ); // 移除刻度线 xAxis.selectAll('.tick line').remove(); yAxis.selectAll('.tick line').remove(); // 设置刻度文字样式 yAxis.selectAll('.tick text').attr('class', 'Body Body-XS'); xAxis.selectAll('.tick text').attr('class', 'Body Body-Small'); // 移除默认生成的domain线 xAxis.select('.domain').remove(); yAxis.select('.domain').remove(); // 绘制自定义X轴domain线 const firstX = x(aapl[0].month) + x.bandwidth()/2; const lastX = x(aapl[aapl.length-1].month) + x.bandwidth()/2; svg.append('line') .attr('class', 'domain stroke-neutral-600 stroke-[3px]') .attr('x1', firstX) .attr('x2', lastX) .attr('y1', height - marginBottom) .attr('y2', height - marginBottom); // 绘制自定义Y轴domain线 const yMax = y(d3.max(aapl, d => d.value)*1.05); const yMin = y(0); svg.append('line') .attr('class', 'domain stroke-neutral-600 stroke-[3px]') .attr('x1', marginLeft) .attr('x2', marginLeft) .attr('y1', yMax) .attr('y2', yMin); } }, [ref]); return <svg ref={ref}></svg>; };
内容的提问来源于stack exchange,提问作者João Pedro
相关产品推荐
相关产品推荐

