如何实现D3环形柱状图响应式,解决移动端显示异常问题?
问题描述
基于D3图表画廊的环形柱状图代码实现的可视化,在桌面端显示正常,但在移动端(iPhone13 mini的Safari和Chrome浏览器)出现不居中、显示不全且标签消失的问题。这是第一个D3.js项目,查阅文档后发现标准柱状图的响应式方案不适用于环形图场景,特此求助。
HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Page Title</title> <script src="https://cdn.jsdelivr.net/npm/chart.js"></script> <link href="https://fonts.cdnfonts.com/css/gotham-6" rel="stylesheet"> <link rel="stylesheet" href="{{ url_for('static', filename='css/styles.css') }}"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css"> <!-- D3 --> <script src="https://d3js.org/d3.v4.js"></script> <script src="https://cdn.jsdelivr.net/gh/holtzy/D3-graph-gallery@master/LIB/d3-scale-radial.js"></script> </head> <body> <header> <div class="main-title">Main Title</div> </header> <!-- Circular time distribution --> <h2 class="section-title" style="display: block; margin-bottom: 10px;">Circular BarPlot</h2> <div id="my_dataviz"></div> <!-- Circular time distribution histogram --> <script> // set the dimensions and margins of the graph var margin = {top: 0, right: 10, bottom: 30, left: 10}, width = 460 - margin.left - margin.right, height = 460 - margin.top - margin.bottom, innerRadius = 90, outerRadius = Math.min(width, height) / 2; // the outerRadius goes from the middle of the SVG area to the border // append the svg object var svg = d3.select("#my_dataviz") .append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom) .append("g") .attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")"); d3.csv("static/time_distribution.csv", function(data) { var maxCount = d3.max(data, function(d) { return +d.count; }); // Scales var x = d3.scaleBand() .range([0, 2 * Math.PI]) // X axis goes from 0 to 2pi = all around the circle. If I stop at 1Pi, it will be around a half circle .align(0) // This does nothing .domain(data.map(function(d) { return d.hour; })); // The domain of the X axis is the list of states. var y = d3.scaleRadial() .range([innerRadius, outerRadius]) // Domain will be define later. .domain([0, maxCount]); // Domain of Y is from 0 to the max seen in the data // Add the bars svg.append("g") .selectAll("path") .data(data) .enter() .append("path") .attr("fill", "#1DB954") .attr("stroke", "black") .attr("stroke-width", 3) // Set the border width .attr("d", d3.arc() // imagine your doing a part of a donut plot .innerRadius(innerRadius) .outerRadius(function(d) { return y(d['count']); }) .startAngle(function(d) { return x(d.hour); }) .endAngle(function(d) { return x(d.hour) + x.bandwidth(); }) .padAngle(0.01) .padRadius(innerRadius)) // Add the labels svg.append("g") .selectAll("g") .data(data) .enter() .append("g") .attr("text-anchor", function(d) { return (x(d.hour) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "end" : "start"; }) .attr("transform", function(d) { return "rotate(" + ((x(d.hour) + x.bandwidth() / 2) * 180 / Math.PI - 90) + ")"+"translate(" + (y(d['count'])+10) + ",0)"; }) .append("text") .text(function(d){return(d.hour)}) .attr("transform", function(d) { return (x(d.hour) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; }) .style("font-size", "15px") .style("font-family", "Gotham") .style("font-weight", "500") .attr("alignment-baseline", "middle") }); </script> </body> </html>
CSS样式
html { overflow-x: hidden; margin-right: calc(-1 * (100vw - 100%)); } body { display: flex; flex-direction: column; justify-content: center; min-height: 100vh; /* Set a minimum height for the page */ align-items: center; margin: 0; font-family: 'Circular', sans-serif; background-color: #f3f3f3; /* Set a light background color */ background-image: radial-gradient(circle at center center, #f5f5f5, #f5f5f5), repeating-radial-gradient(circle at center center, #f5f5f5, #f5f5f5 10px, transparent 20px, transparent 10px); background-blend-mode: multiply; background-attachment: fixed; /* Keep the background fixed while scrolling */ } .section-title { display: block; font-family: 'Gotham', sans-serif; margin-bottom: 0px; text-shadow: 1px 1px 1px rgba(0, 0, 0, 0.2); background: linear-gradient(45deg, #333, #000); /* Gradient background */ -webkit-background-clip: text; /* Clip text to background */ background-clip: text; -webkit-text-fill-color: transparent; /* Hide text fill to show gradient */ } @media (max-width: 500px) { /* 500 should be enough for mobile */ body { overflow-x: hidden; width: 100%; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box; } html { overflow-x: hidden; } }
(注:原CSS媒体查询缺少闭合大括号,已补充)
数据
hour,count 1,3 2,0 3,0 4,0 5,0 6,0 7,0 8,0 9,0 10,0 11,0 12,0 13,0 14,0 15,10 16,0 17,7 18,7 19,0 20,0 21,0 22,0 23,0 24,5
问题截图

解决方案
1. 改为响应式尺寸计算
原代码中width和height是固定值,导致移动端容器过大超出屏幕。改为基于父容器宽度动态计算:
// 替换原固定尺寸定义 var margin = {top: 20, right: 20, bottom: 20, left: 20}; // 获取父容器宽度,最小设为280保证移动端基础显示 var containerWidth = Math.min(document.getElementById('my_dataviz').offsetWidth, 460); var width = containerWidth - margin.left - margin.right; var height = width; // 保持正方形,适配环形图 var innerRadius = Math.min(width, height) / 4; // 按比例计算内半径,避免移动端过大 var outerRadius = Math.min(width, height) / 2 - 20; // 留出标签空间
2. 优化SVG的缩放与居中
给SVG添加viewBox属性实现自适应缩放,同时移除固定宽高,改用CSS控制:
// 替换原svg.append部分 var svg = d3.select("#my_dataviz") .append("svg") .attr("viewBox", `0 0 ${width + margin.left + margin.right} ${height + margin.top + margin.bottom}`) .attr("width", "100%") .attr("height", "auto") .append("g") .attr("transform", `translate(${(width + margin.left + margin.right)/2}, ${(height + margin.top + margin.bottom)/2})`);
3. 调整标签尺寸与位置
移动端屏幕小,需缩小字体并调整标签偏移量,避免超出容器:
// 修改标签样式部分 .append("text") .text(function(d){return(d.hour)}) .attr("transform", function(d) { return (x(d.hour) + x.bandwidth() / 2 + Math.PI) % (2 * Math.PI) < Math.PI ? "rotate(180)" : "rotate(0)"; }) .style("font-size", function() { return width < 350 ? "12px" : "15px"; }) // 自适应字体大小 .style("font-family", "Gotham") .style("font-weight", "500") .attr("alignment-baseline", "middle") .attr("dx", function() { return width < 350 ? "5px" : "10px"; }) // 调整标签与柱子的间距
4. 完善CSS响应式设置
确保容器占满可用宽度,同时避免溢出:
#my_dataviz { width: 100%; max-width: 460px; padding: 0 10px; box-sizing: border-box; } @media (max-width: 500px) { body { padding: 20px 0; overflow-x: hidden; } }
5. 添加窗口 resize 监听
实现窗口大小变化时自动重绘图表:
// 在CSV数据加载完成后添加监听 function drawChart(data) { // 把原有的绘图逻辑(比例尺、柱子、标签)全部移到这个函数里 // ... } d3.csv("static/time_distribution.csv", function(data) { drawChart(data); window.addEventListener('resize', function() { // 先清除旧图表 d3.select("#my_dataviz").select("svg").remove(); // 重新计算尺寸并绘制 drawChart(data); }); });
内容的提问来源于stack exchange,提问作者Physics_Student
相关产品推荐
相关产品推荐

