data bars on the xaxis should be aligned properly - d3.js

I am building my first bar chart using d3.js v5 what I want is that the bars should be aligned properly on the xaxis
I have almost done building the chart but can't figure out the problem
var headingOne;
var headingTwo;
var dataset;
var description;
var frequency;
var barPadding = 20;
document.addEventListener("DOMContentLoaded", function() {
var req = new XMLHttpRequest(); // Next time will use d3.json();
req.open(
"GET",
"https://raw.githubusercontent.com/freeCodeCamp/ProjectReferenceData/master/GDP-data.json",
true
);
req.send();
req.onload = function() {
let json = JSON.parse(req.responseText);
headingOne = json.source_name;
headingTwo = `From ${json.from_date.substring(0,4)} to ${json.to_date.substring(0,4)}`;
dataset = json.data;
descripton = json.description;
d3
.select("body")
.append("h1")
.text(headingOne)
.attr("class", "headings")
.attr("id", "title");
d3
.select("body")
.append("h2")
.text(headingTwo)
.attr("class", "headings");
var margin = { top: 20, right: 20, bottom: 50, left: 40 },
height = 600 - margin.top - margin.bottom,
width = 1100 - margin.left - margin.right;
var minDate = new Date(dataset[0][0]);
var maxDate = new Date(dataset[dataset.length - 1][0]);
var xScale = d3.scaleTime().domain([minDate, maxDate]).range([barPadding, width - barPadding]);
var yScale = d3.scaleLinear().domain([0, d3.max(dataset, d => d[1])]).range([height, barPadding]);
var xAxis = d3.axisBottom().scale(xScale);
var yAxis = d3.axisLeft().scale(yScale);
var svg = d3
.select("body")
.append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + ", " + margin.top + ")");
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", (d, i) => i * (width / dataset.length))
.attr("data-date", (d) => d[0])
.attr("y", (d) => yScale(d[1]))
.attr("data-gdp", (d) => d[1])
.attr("width", width / dataset.length)
.attr("height", d => height - yScale(d[1]))
svg.append("g").attr("transform", "translate("+barPadding+"," + (height) + ")").attr("id", "x-axis").call(xAxis);
svg.append("g").attr("transform", "translate("+margin.left+", 0)").attr("id", "y-axis").call(yAxis);
};
});
I expect the bars properly aligned on the xaxis.
Now the bars started before the xaxis (start of the xaxis towards left) starting point which is wrong but finished in the right position (end of the xaxis towards right)
the data is exceeding the limit.

Related

Center Pie Chart in SVG Element

I've already read:
https://bl.ocks.org/mbostock/3887235
http://zeroviscosity.com/d3-js-step-by-step/step-1-a-basic-pie-chart
Center align a pie chart on svg
Consider the following:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>
I am trying to center the pie chart vertically and horizontally with respect to the entire svg element that it is in. I tried modifying my code to the examples above to no avail.
You just have to translate the parent g element at half width horizontally and at half height vertically:
Instead of:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
write:
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
Check the demo:
var dataAsCsv = `Col1,Col2
Type1,123456
Type2,789012
Type3,34567`;
var data = d3.csvParse(dataAsCsv);
var margin = {top: 50, right: 20, bottom: 50, left: 80},
width = 1400 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom;
var svgPie = d3.select('body').append('svg')
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
var gPie = svgPie.append("g")
.attr("transform", "translate(" + width/2 + "," + height/2 + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scaleOrdinal(d3.schemeCategory20b);
var label = d3.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var path = d3.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var pie = d3.pie()
.value(function(d) { return d.Col2; })
.sort(null);
var arc = gPie.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
arc.append("path")
.attr("d", path)
.attr("fill", function(d) { return color(d.data.Col1); });
arc.append("text")
.attr("transform", function(d) { return "translate(" + label.centroid(d) + ")"; })
.attr("dy", "0.35em")
.text(function(d) { return d.data.Col1; });
<script src="https://d3js.org/d3.v4.js"></script>

d3 v4 scaleBand ticks

I have data like the following
date,values
2016-10-01,10
2016-10-02,20
2016-10-03,30
2016-10-04,5
2016-10-05,50
2016-10-06,2
2016-10-07,7
2016-10-08,17
and am generating a bar chart using the following code
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 800 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var parseDate = d3.timeParse("%Y-%m-%d");
var x = d3.scaleBand().range([0, width]);
var y = d3.scaleLinear().range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return "<strong>Month of " + d.date + ":</strong> <span style='color:red'>" + d.value + " sales</span>";
})
var svg = d3.select("#barg").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.call(tip);
data = d3.csvParse(d3.select("pre#data2").text());
data.forEach(function(d) {
d.date = parseDate(d.date);
d.value = +d.value;
});
x.domain(data.map(function(d) { return d.date; }));
y.domain([0, d3.max(data, function(d) { return d.value; })]);
svg.append("g")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", "-.55em")
.attr("transform", "rotate(-90)" )
svg.append("g")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Value ($)");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.date); })
.attr("width", x.bandwidth() - 5)
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
So the problem I am having is that I have ordinal data, but for large cardinality (for instance, 120 data points) The x axis has way too many ticks. I have tried a few things like tickValues, but when I use this, my x axis tick points all show up on top of each other. Ideally I would like 10 tick points or so, when the cardinality is high. Any ideas?
This can be done using tickValues indeed. For instance, in this demo, we have 200 values, so the axis is absolutely crowded:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var data = d3.range(200);
var xScale = d3.scaleBand()
.domain(data.map(function(d){ return d}))
.range([10, 490]);
var xAxis = d3.axisBottom(xScale);
var gX = svg.append("g").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
Now, the same code using tickValues:
var svg = d3.select("body")
.append("svg")
.attr("width", 500)
.attr("height", 100);
var data = d3.range(200);
var xScale = d3.scaleBand()
.domain(data.map(function(d){ return d}))
.range([10, 490]);
var xAxis = d3.axisBottom(xScale)
.tickValues(xScale.domain().filter(function(d,i){ return !(i%10)}));
var gX = svg.append("g").call(xAxis);
<script src="https://d3js.org/d3.v4.min.js"></script>
In this last snippet, tickValues uses the remainder operator to show only 1 in every 10 ticks:
.tickValues(xScale.domain().filter(function(d,i){
return !(i%10)
}));
Here is a general solution to this problem using tickFormat(...). We can define a minimum acceptable width for our ticks, then skip every nth tick based on this minimum.
d3
.axisBottom(xScale)
.tickFormat((t, i) => {
const MIN_WIDTH = 30;
let skip = Math.round(MIN_WIDTH * data.length / chartWidth);
skip = Math.max(1, skip);
return (i % skip === 0) ? t : null;
});
let skip = ... is a rearrangement of the inequality ChartWidth / (NumTicks / N) > MinWidth. Here N represents the tick "step size", so we are asserting that the width of every nth tick is greater than the minimum acceptable width. If we rearrange the inequality to solve for N, we can determine how many ticks to skip to achieve our desired width.

D3 axes render on top of graph

The following code renders the axes on top of the graph and I can't seem to find where to add/subtract pixels to align the two.
I've spent weekend trying to solve this but I feel stuck. In my desperation, I've tried to add and subtract the padding in various places, add margins here and there to move things. It's like the graph and the axes are on two different scales but I can't see where I'm doing that either. This is a link to my codepen: http://codepen.io/piacoding/pen/amzoog?editors=0010
thank you,
var w = 780;
var h = 500;
var padding = 60;
var svg = d3.select("#graph")
.append("svg:svg")
.attr("width", w)
.attr("height", h )
.attr('class', 'gdp');
// define the x scale (horizontal)
var mindate = new Date(1947, 0, 1),
maxdate = new Date(2015, 6, 1);
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([padding, w - padding]);
var maxnumber = d3.max(dataset, function(d) {
return d[1]
});
var yScale = d3.scale.linear()
.domain([0, maxnumber])
.range([0, h]);
var y = d3.scale.linear()
.domain([0, maxnumber])
.range([h - padding, padding]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (yScale(d[1]));
})
.attr("width", w / dataset.length)
.attr("height", function(d) {
return yScale(d[1]);
})
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(y);
// define the y axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
// draw y axis
svg.append("g")
.attr("transform", "translate(" + padding + ",0)")
.attr('class', 'y axis')
.call(yAxis);
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + (h - padding) + ")")
.call(xAxis);
Take a look at Margin Convention which does exactly what you need. See http://codepen.io/anon/pen/JRovxV?editors=0010 for the updated version:
var margin = {top: 20, right: 10, bottom: 60, left: 60};
var width = 780 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var svg = d3.select("#graph")
.append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr('class', 'gdp');
// define the x scale (horizontal)
var mindate = new Date(1947, 0, 1),
maxdate = new Date(2015, 6, 1);
// var firstDate = dataset[0];
// var lastDate = dataset[dataset.length - 1][0];
var xScale = d3.time.scale()
.domain([mindate, maxdate])
.range([0, width]);
var maxnumber = d3.max(dataset, function(d) {
return d[1]
});
var yScale = d3.scale.linear()
.domain([0, maxnumber])
.range([0, height]);
var y = d3.scale.linear()
.domain([0, maxnumber])
.range([height, 0]);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (width / dataset.length);
})
.attr("y", function(d) {
return height - (yScale(d[1]));
})
.attr("width", width / dataset.length)
.attr("height", function(d) {
return yScale(d[1]);
})
// define the y axis
var yAxis = d3.svg.axis()
.orient("left")
.scale(y);
// define the y axis
var xAxis = d3.svg.axis()
.orient("bottom")
.scale(xScale);
// draw y axis
svg.append("g")
//.attr("transform", "translate(" + padding + ",0)")
.attr('class', 'y axis')
.call(yAxis);
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);

Animating D3 donut chart on load

I have a donut chart with five different arcs inside of it. The data will not be updated but I want to have a transition of the whole graph being drawn as a circle when the page loads, starting at the selected angle (in my case 1.1*PI). Here is the jsfiddle of the graph: http://jsfiddle.net/Nw62g/
var data = [
{name: "one", value: 10375},
{name: "two", value: 7615},
{name: "three", value: 832},
{name: "four", value: 516},
{name: "five", value: 491}
];
var margin = {top: 20, right: 20, bottom: 20, left: 20};
width = 400 - margin.left - margin.right;
height = width - margin.top - margin.bottom;
var chart = d3.select("body")
.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) + ")");
var radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#3399FF", "#5DAEF8", "#86C3FA", "#ADD6FB", "#D6EBFD"]);
var arc = d3.svg.arc()
.outerRadius(radius)
.innerRadius(radius - 20);
var pie = d3.layout.pie()
.sort(null)
.startAngle(1.1*Math.PI)
.endAngle(3.1*Math.PI)
.value(function(d) { return d.value; });
var g = chart.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.style("fill", function(d) { return color(d.data.name); })
.attr("d", arc);
How would I go about achieving this result?
You can do this with an attribute tween:
.attrTween('d', function(d) {
var i = d3.interpolate(d.startAngle+0.1, d.endAngle);
return function(t) {
d.endAngle = i(t);
return arc(d);
}
});
This animates the segment from start to end angle. To do this across the entire circle, you can use delayed transitions:
.transition().delay(function(d, i) { return i * 500; }).duration(500)
Complete example here. You can adjust start segment, duration, etc to your liking.

trouble with scales and hebin in d3js

I am trying to use the hexbin layout with data that is normally distributed around 0 - all the examples use data centered around the center of the screen, so the scales are the same as the screen scales (except for y inversion)
I've tried to modify the scale functions to account for possible negative values. It works for the y-scale, but the x-scale gives NaNs, and the hexagons are plotted off the screen upper left. That is not the only problem - I would like to programmatically determine the bin size for the hexbin function - in my data series, all of the values are 'binned' into only one to three hexagons, and I need them spread out over the available domain.. here is my code
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="http://d3js.org/d3.hexbin.v0.min.js?5c6e4f0"></script>
<script>
minMultArray =function(arr,index){
var min = arr[0][index];
for (i=0;i<arr.length;i++) {
min = (min>arr[i][index]?arr[i][index]:min);
}
return min;
};
maxMultArray =function(arr,index){
var max = arr[0][index];
for (i=0;i<arr.length;i++) {
max = (max< arr[i][index]?arr[i][index]:max);
}
return max;
};
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var randomX = d3.random.normal(0, 5),
randomY = d3.random.normal(0, 6),
points = d3.range(2000).map(function() { return [randomX(), randomY()]; });
var minX = minMultArray(points,0);
var minY = minMultArray(points,1);
//var minZ = minMultArray(points,2);
var maxX = maxMultArray(points,0);
var maxY = maxMultArray(points,1);
//var maxZ = maxMultArray(points,2);
var color = d3.scale.linear()
.domain([0, 20])
.range(["white", "steelblue"])
.interpolate(d3.interpolateLab);
var hexbin = d3.hexbin()
.size([width, height])
.radius(20);
alert('minX='+minX +' maxX='+maxX);
var x = d3.scale.linear()
.domain([minX, maxX])
.range(0,width);
alert('xScale(3)='+x(3));
var y = d3.scale.linear()
.domain([minY, maxY])
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickSize(6, -height);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickSize(6, -width);
console.log('hex = ' +hexbin(points));
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("class", "mesh")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("clip-path", "url(#clip)")
.selectAll(".hexagon")
.data(hexbin(points))
.enter().append("path")
.attr("class", "hexagon")
.attr("d", hexbin.hexagon())
.attr("transform", function(d) { return "translate(" + (d.x) + "," + (d.y) + ")"; })
.style("fill", function(d) { return color(d.length); });
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
</script>
After more debugging the hexbin functions, they are not compatible with negative and/or fractional domains- so I solved this by mapping my original data by linear scales up to the height and width of the hexagon plots. Then bin size is controlled by radius. I also modified the hexbin binning function to handle three element arrays, and can compute stats on the third element, using color or size to show mean/median/stddev/max/min. If interested, I can post the code on github...

Resources