You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 05:52:33