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
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:
1. Never use SVGs without a
viewBox
Add a viewbox to your SVG that indicates the actual size of your SVG’s contents:
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
460
s)2. Set Max Width
Set a maximum width on your SVG so it shrinks on mobile instead of just getting cut off:
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 attributepreserveAspectRatio 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:p.s. Thank you for the full code. It always makes it very easy to solve the issues!
Here’s a working version: