我正在使用 React 和 D3 (v7) 来制作堆积条形图。我添加了在 X 和 Y 方向上缩放和平移图表的功能。
缩放和平移时,元素将移动到图表边界之外。我添加了一个
clipPath
,它与条本身一起使用,因此当缩放/平移时,条不会被绘制到轴之外。但是,轴仍然超出范围(请参见下图的示例)。
此处正确位置 -
与缩放和平移后的移动方式相比
如何避免这种情况发生?我尝试过使用
rescaleY
,这会破坏缩放功能,并且 rescaleX
不适用于波段刻度。
这是我的缩写代码:
const BarChart = ({
data,
dimensions = {
chartOffset: {
left: 10,
},
margin: {
top: 50,
right: 50,
bottom: 50,
left: 50,
},
width: 800,
height: 500,
},
subgroups,
colors,
displayNames,
}) => {
const svgRef = React.useRef(null);
const {
width, height, margin, chartOffset,
} = dimensions;
const svgWidth = width + margin.left + margin.right;
const svgHeight = height + margin.top + margin.bottom;
const xScale = myChartUtils.getXScale(); //scaleBand
const yScale = myChartUtils.getYScale(); //scaleLinear
function zoom() {
const extent = [[0, 0], [width, height]];
const svgEl = d3.select(svgRef.current);
svgEl.call(d3.zoom()
.scaleExtent([1, 20])
.translateExtent(extent)
.extent(extent)
.on("zoom", zoomed));
function zoomed(e) {
const xAxis = d3.axisBottom(xScale);
const yAxis = d3.axisLeft(yScale).ticks(5).tickSize(5);
xScale.range([0, width - margin.right].map(d => e.transform.applyX(d)));
yScale.range([height, 0].map(d => e.transform.applyY(d)));
svgEl
.selectAll(".bars rect")
.attr('x', d => xScale(d.data.user))
.attr('width', xScale.bandwidth())
.attr('y', d => yScale(d[1]))
.attr('height', d => yScale(d[0]) - yScale(d[1]))
svgEl.selectAll('.x-axis').call(xAxis)
svgEl.selectAll('.y-axis').call(yAxis)
}
}
React.useEffect(() => {
// generate stacked bars
const stackedGroups = d3
.stack()
.keys(subgroups)
.value((obj, key) => obj[key].length)(data);
const svgEl = d3.select(svgRef.current);
const svg = svgEl
.append('g')
.attr("id", "plot")
.attr(
'transform',
`translate(${margin.left + chartOffset.left},${
margin.top + chartOffset.top
})`,
);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
const plotArea = svg.append("g")
.attr("clip-path", "url(#clip)");
myChartUtils.drawXAxis(xScale, svg, height, width, margin, 'title');
myChartUtils.drawYAxis(yScale, svg, height, 'title');
plotArea
.append('g')
.attr("class", "bars")
.selectAll('g')
.data(stackedGroups, (d) => d.user)
.enter()
.append('g')
.attr('fill', (d) => colors[d.key])
.selectAll('rect')
.data((d) => d)
.enter()
.append('rect')
.attr('x', (d) => xScale(d.data.user))
.attr('y', (d) => yScale(d[1]))
.attr('height', (d) => yScale(d[0]) - yScale(d[1]))
.attr('width', xScale.bandwidth())
.style('opacity', 0.5)
.call(zoom);
// Clear svg content before adding new elements
return function cleanup() {
svgEl.selectAll('rect').remove();
};
}, [data, margin, colors]);
const deviation = 0.001;
let k = event.transform.k;
xScale.range([0, width].map(d => event.transform.applyX(d)));
if (Math.abs(k - 1) < deviation) {
xScale.range([0, width]);
}
我写了这个问题的解决方案。