skip to Main Content

I have created the following circular barplot using the code here. It works fine on a laptop, but not on mobile and I am trying to figure out why. This is my first project in d3 and I tried to read the documentation but the examples of standard barplots don’t seem to apply to this case, so I am not sure how to go about it.

PS: By "does not work" I mean that the chart is not centered, it is not fully visible, and the labels have disappeared.

For reference, a version of this is here at the bottom of the page. ( I have edited the link, there was a generic link earlier)

This is my index.html

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

These are potentially relevant css styles

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;
    }

Data

Here’s the data I am using, although I doubt it makes a difference:

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

2

Answers


  1. Charts where the aspect ratio has to remain the same can be hard to make responsive. One thing you could do is just shrink the whole graph:

    .mygraphic {
        transform:scale(0.5);
    }
    
    Login or Signup to reply.
  2. 1. Never use SVGs without a viewBox

    Add a viewbox to your SVG that indicates the actual size of your SVG’s contents:

    var svg = d3.select("#my_dataviz")
          .append("svg")
             .attr("width", width + margin.left + margin.right)
             .attr("height", height + margin.top + margin.bottom)
             .attr("viewBox", "0 0 460 460") // ADD A VIEWBOX
    

    You generally should always use a viewBox. In my experience, SVGs without viewBoxes do weird, sometimes unexpected things.

    (p.s. You may want to use variable inserts vs. hardcoded 460s)

    2. Set Max Width

    Set a maximum width on your SVG so it shrinks on mobile instead of just getting cut off:

    var svg = d3.select("#my_dataviz")
          .append("svg")
             .attr("width", width + margin.left + margin.right)
             .attr("height", height + margin.top + margin.bottom)
             .attr("viewBox", "0 0 460 460") // ADD A VIEWBOX
             .style("max-width", "100%") // SET MAX WIDTH
    

    100% here probably works best since your SVG code has built-in margin. If it didn’t, you could instead use css like, display:block; max-width:90%; margin:auto;.

    3. Use the preserveAspectRatio SVG attribute

    preserveAspectRatio is the exact solution to your question, but the previous two steps are required for it to work.

    At this point, simply setting preserveAspectRatio to something like xMidYMid meet will make the SVG contents shrink proportionally on mobile:

    var svg = d3.select("#my_dataviz")
          .append("svg")
             .attr("width", width + margin.left + margin.right)
             .attr("height", height + margin.top + margin.bottom)
             .attr("viewBox", "0 0 460 460") // ADD A VIEWBOX
             .style("max-width", "100%") // SET MAX WIDTH
             .attr("preserveAspectRatio", "xMidYMid meet") // SHRINK ON MOBILE
    

    p.s. Thank you for the full code. It always makes it very easy to solve the issues!

    Here’s a working version:

    const dataSource = "data:text/csv;base64,aG91cixjb3VudAoxLDMKMiwwCjMsMAo0LDAKNSwwCjYsMAo3LDAKOCwwCjksMAoxMCwwCjExLDAKMTIsMAoxMywwCjE0LDAKMTUsMTAKMTYsMAoxNyw3CjE4LDcKMTksMAoyMCwwCjIxLDAKMjIsMAoyMywwCjI0LDUK";
    
    // 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)
         /// ----------------------------------------------------
         .attr("viewBox", "0 0 460 460")
         .attr("preserveAspectRatio", "xMidYMid meet")
         .style("max-width", "100%")
         /// ----------------------------------------------------
      .append("g")
        .attr("transform", "translate(" + (width / 2 + margin.left) + "," + (height / 2 + margin.top) + ")");
    
    d3.csv(dataSource, 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")
    
    });
    #my_dataviz {
        width: 250px; /* simulate mobile */
        outline: rgba(0,0,0,0.3) solid 1px;
    }
    
    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;
        }
    <!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>
    
    </body>
    </html>
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search