Reputation: 2886
I have created a D3 chart step by step. I have made some changes to make the SVG responsive. My target now is to make the bar graph more responsive in order to make it easier to read when the screen size is smaller (width). I have pasted the snippet below and at the bottom of the page, I focus on the part that I am thinking the solution is hidden.
var data = [
{"area": "one ", "value": 18000},
{"area": "Two ", "value": 17000},
{"area": "three ", "value": 80000},
{"area": "four ", "value": 55000},
{"area": "five ", "value": 100000},
{"area": "six", "value": 50000},
{"area": "seven", "value": 50000}
];
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = 1900 - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
//A fully-responsive chart area
var svg = d3.select("#chart-div")
.append("svg")
.attr("width","100%")
.attr("height","500px")
.attr("viewBox","0 0 "+
(width+margin.left+margin.right)+
" "+
(height+margin.top+margin.bottom) )
.append("g")
.attr("transform","translate("+
margin.left+","+margin.top+")");
var tooltip = d3.select("body").append("div").attr("class", "toolTip");
var x = d3.scaleLinear().range([0, width]);
var y = d3.scaleBand().range([height, 0]);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
data.sort(function(a, b) { return a.value - b.value; });
x.domain([0, d3.max(data, function(d) { return d.value; })]);
y.domain(data.map(function(d) { return d.area; })).padding(0.1);
g.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x).ticks(5).tickFormat(function(d) { return parseInt(d / 1000); }).tickSizeInner([-height]));
g.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y));
g.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", 0)
.attr("height", y.bandwidth())
.attr("y", function(d) { return y(d.area); })
.attr("width", function(d) { return x(d.value); })
.on("mousemove", function(d){
tooltip
.style("left", d3.event.pageX - 50 + "px")
.style("top", d3.event.pageY - 70 + "px")
.style("display", "inline-block")
.html((d.area) + "<br>" + "£" + (d.value));
})
.on("mouseout", function(d){ tooltip.style("display", "none");});
@import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 15px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #6F257F;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
.svg-container {
display: inline-block;
position: relative;
width: 100%;
padding-bottom: 100%; /* aspect ratio */
vertical-align: top;
overflow: hidden;
}
.svg-content-responsive {
display: inline-block;
position: absolute;
top: 10px;
left: 0;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="chart-div" style="width:100%;height:100%;"></div>
I changed a part of the code with the following:
var parentwidth = $("#chart-div").parent().width();
var margin = {top: 10, right: 10, bottom: 70, left: 30};
var width = parentwidth - margin.left - margin.right;
var height = 400 - margin.top - margin.bottom;
where I am actually getting the parentwidth
using jQuery.
I am actually thinking whether :
a) It is possible to avoid jQuery in this case.
b) Ideally, make the bar to scale differently so that everything will be easily read by the user (small text size is an issue):
I am testing the function below but I am possibly getting errors related to some chrome addons to avoid the cross-origin error. I can update the question if the below is the best solution:
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
Upvotes: 0
Views: 1869
Reputation: 2886
I have altered my script to make it work. Please see the snippet below:
var margin = {top: 20, right: 20, bottom: 50, left: 100},
width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
var yScale = d3.scale.ordinal()
.rangeRoundBands([height, 0], 0.1);
var xScale = d3.scale.linear()
.range([0, width]);
var dollarFormatter = d3.format(",.0f")
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left");
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.tickFormat(function(d) { return "$" + dollarFormatter(d);});
var svg = d3.select("#chart")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<div><span>Name:</span> <span style='color:white'>" + d.Name + "</span></div>" +
"<div><span>Sub-Category:</span> <span style='color:white'>" + d["Sub-Category"] + "</span></div>" +
"<div><span>Total Sales:</span> <span style='color:white'>" + "$"+ dollarFormatter(d.total) + "</span></div>";
})
svg.call(tip);
//Get CSV, JSON from URL
//var url = "http://bl.ocks.org/josiahdavis/raw/7d84b2f1837eab9c24d9/top.csv";
var data = [
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tom Stivers",
"total": 1889.8,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Keith Herrera",
"total": 2020.161,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Jack O'Briant",
"total": 2122.545,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Nora Paige",
"total": 2154.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Anna Gayman",
"total": 2396.2656,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Tracy Blumstein",
"total": 3083.43,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Maribeth Schnelling",
"total": 3406.664,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Greg Tran",
"total": 4007.84,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Quincy Jones",
"total": 4404.9,
"Type": "Customer"
},
{
"metric": "Sales",
"Category": "Furniture",
"Sub-Category": "Bookcases",
"Name": "Peter Fuller",
"total": 6232.624,
"Type": "Customer"
}];
//d3.csv(url, format, function(error, data){
//if (error) throw error;
// Filter to select a subset
var subset = data.filter(function(el){
return (el["metric"] === "Sales")
&& (el["Sub-Category"] === "Bookcases")
&& (el["Type"] === "Customer");
});
// Sort the data so bar chart is sorted in decreasing order
subset = subset.sort(function(a, b) { return a["total"] - b["total"]; });
console.log(JSON.stringify(subset, null, 2));
yScale.domain(subset.map(function(d) { return d["Name"]; }));
xScale.domain([0, d3.max(subset, function(d) { return d["total"]; })]);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.append("text")
.attr("class", "label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")")
.style("text-anchor", "middle")
.text("Sales");
svg.selectAll(".bar")
.data(subset)
.enter().append("rect")
.attr("class", "bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand())
.on('mouseover', tip.show)
.on('mouseout', tip.hide);;
//});
// Define responsive behavior
function resize() {
var width = parseInt(d3.select("#chart").style("width")) - margin.left - margin.right,
height = parseInt(d3.select("#chart").style("height")) - margin.top - margin.bottom;
// Update the range of the scale with new width/height
xScale.range([0, width]);
yScale.rangeRoundBands([height, 0], 0.1);
// Update the axis and text with the new scale
svg.select(".x.axis")
.call(xAxis)
.attr("transform", "translate(0," + height + ")")
.select(".label")
.attr("transform", "translate(" + width / 2 + "," + margin.bottom / 1.5 + ")");
svg.select(".y.axis")
.call(yAxis);
// Update the tick marks
xAxis.ticks(Math.max(width/75, 2), " $");
// Force D3 to recalculate and update the line
svg.selectAll(".bar")
.attr("width", function(d) { return xScale(d["total"]); })
.attr("y", function(d) { return yScale(d["Name"]); })
.attr("height", yScale.rangeBand());
};
// Call the resize function whenever a resize event occurs
d3.select(window).on('resize', resize);
// Call the resize function
resize();
// Define the format function
function format(d) {
d.total = +d.total;
return d;
}
@import url('https://fonts.googleapis.com/css?family=Roboto');
body {
margin: 5px;
background-color: #F1F3F3;
font-family: 'Roboto'!important;
}
.bar {
fill: #14405F;
}
.bar:hover {
fill: #33A1EE;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #D4D8DA;
stroke-width: 1px;
shape-rendering: crispEdges;
}
.x path {
display: none;
}
#chart {
width: 100%;
height: 100%;
position: absolute;
}
.d3-tip {
line-height: 1;
font: 14px sans-serif;
padding: 12px;
background: rgba(0, 0, 0, 0.8);
color: rgb(185, 185, 185);
border-radius: 2px;
}
/*
.toolTip {
position: absolute;
display: none;
min-width: 80px;
height: auto;
background: none repeat scroll 0 0 #ffffff;
border: 1px solid #6F257F;
padding: 14px;
text-align: center;
}
*/
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script src="https://labratrevenge.com/d3-tip/javascripts/d3.tip.v0.6.3.js"></script>
<svg id="chart"></svg>
Upvotes: 0
Reputation: 5061
Your question is basically the same as this one.
The TL;DR is: give your <svg>
a viewBox
attribute and a preserveAspectRatio
(e.g. xMinYMin meet
) attribute. Then wrap the <svg>
in a <div>
that has position: relative
.
This is not the only solution, but it's probably the easiest to implement and (I think) the most used.
For an overview and discussion of several other solutions, see this article by Amelia Bellamy-Royds.
Also, for an in-depth explanation of the SVG coordinate system, read this series of articles by Sara Soueidan.
As for your doubts about jQuery and the text being too small on mobile phones:
a) you can totally avoid jQuery
b) you can avoid having a text too small by "counter-scaling" the text, namely when your entire barchart (i.e. bars, axis, labels) scales down, your text scales up. This is sometime called sticky text. You can see an example here.
Upvotes: 1