D3v4响应式图例适配问题:窗口缩小时调整行间距
Got it, let's solve that legend spacing issue you're facing! The problem here is that your current row spacing (i * 20) is hardcoded to a fixed pixel value, so it doesn't adjust when the window resizes. We need to make that spacing dynamic and update it whenever the window changes size.
Here's a step-by-step solution:
1. Wrap Legend Logic in a Reusable Update Function
First, we'll create a function that calculates the dynamic spacing and updates the legend's transform attribute. This way, we can call it both on initialization and whenever the window resizes. You can choose from a few flexible approaches:
Option 1: Threshold-Based Spacing
Switch between spacing values when the window crosses a height threshold (e.g., 600px):
function updateLegend() { // Adjust spacing based on window height const windowHeight = window.innerHeight; const rowSpacing = windowHeight < 600 ? 15 : 20; // Smaller spacing for compact windows // Update position of each legend group d3.select(".legend") .selectAll("g") .attr("transform", function(d, i) { return `translate(0, ${i * rowSpacing})`; }); }
Option 2: Viewport Unit Scaling
Use CSS viewport units for seamless, proportional scaling with window size:
function updateLegend() { // Spacing scales with viewport width (1.2% of window width) const rowSpacing = 1.2; d3.select(".legend") .selectAll("g") .attr("transform", function(d, i) { return `translate(0, ${i * rowSpacing}vw)`; }); }
Option 3: Fit to Container Height
Calculate spacing to make the legend perfectly fill its parent container:
function updateLegend() { const legendContainer = document.getElementById("legendecontainer"); const containerHeight = legendContainer.offsetHeight; const itemCount = color.domain().length; // Subtract padding (20px total) from container height const availableHeight = containerHeight - 20; // Spacing between items (n items have n-1 gaps) const rowSpacing = availableHeight / (itemCount - 1); d3.select(".legend") .selectAll("g") .attr("transform", function(d, i) { return `translate(0, ${i * rowSpacing})`; }); }
2. Initialize and Attach Resize Listener
Update your original code to call the updateLegend function on load, and add a listener to trigger it when the window resizes:
// Initialize legend container and groups var legend = d3.select("#legendecontainer").append("svg") .attr("class", "legend") .selectAll("g") .data(color.domain()) .enter().append("g"); // Add legend rectangles (your existing code) legend.append("rect") .attr("width", "1em") .attr("height", "1em") .style("fill", color); // Add legend text (your existing code) legend.append("text") .data(legendText) .attr("x", "1.8em") .attr("y", ".5em") .attr("dy", ".35em") .text(function(d) { return d; }); // Set initial spacing updateLegend(); // Update spacing automatically on window resize window.addEventListener("resize", updateLegend);
3. Bonus: Make SVG Container Responsive
To ensure the legend SVG fits its container fully, add this to the updateLegend function (adjust padding as needed):
function updateLegend() { // ... existing spacing calculation code ... // Update SVG dimensions to fit all legend items const svgHeight = (color.domain().length * rowSpacing) + 10; // Add 10px bottom padding d3.select(".legend") .attr("height", svgHeight) .attr("width", "100%"); // Fill container width }
The core fix here is moving from a one-time fixed spacing calculation to a dynamic, reusable function that updates whenever the window changes size. This keeps your legend properly aligned and adapted to available space.
内容的提问来源于stack exchange,提问作者Olivier F

