Creating A Bar chart in d3.js from an JSON - javascript

I have this JSON data
{"1960":"133.55501327769","1961":"134.159118941963","1962":"134.857912280869","1963":"134.504575565342","1964":"134.105211273476","1965":"133.569625896451","1966":"132.675635192775","1967":"131.665502129354","1968":"129.190980115918","1969":"126.736756382819","1970":"124.382808900193","1971":"122.133431342027","1972":"120.020185557559","1973":"118.087531093609","1974":"116.132988067096","1975":"114.100918174437","1976":"111.980005447216","1977":"109.783821762662","1978":"106.033489239906","1979":"102.341720681455","1980":"98.7390023274647","1981":"95.2412508672801","1982":"91.7911923993222","1983":"88.0011769487606","1984":"84.2072557839419","1985":"80.3593225600132","1986":"76.4415956498419","1987":"72.5145803648751","1988":"71.1706639452677","1989":"69.8887679924858","1990":"69.0044133814268","1991":"67.7559924352118","1992":"66.9284506867798","1993":"64.9489678572737","1994":"62.9227777228154","1995":"60.7070695260477","1996":"58.5966308804751","1997":"56.4401276304142","1998":"55.5315395528949","1999":"54.6587808352011","2000":"53.8314102398679","2001":"52.9015276443892","2002":"51.9907926813042","2003":"51.5228563035101","2004":"51.1032496482833","2005":"50.7325902239383","2006":"50.3291352282938","2007":"49.9998514069402","2008":"49.8870459355469","2009":"49.7812066054555","2010":"49.6729747116906","2011":"49.5360469363113","2012":"49.3837446924523","2013":"48.7965576984378","2014":"48.1964180547578","2015":"47.5501940499984","Country Name":"Arab World","Country Code":"ARB","Indicator Name":"Adolescent fertility rate (births per 1,000 women ages 15-19)","Indicator Code":"SP.ADO.TFRT","":""}
which I convert from CSV to JS object.
How do I build a bar chart with d3.js where the key is the X axis and the values are the Y axis?
here's what I came up with
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// set the ranges
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
var y = d3.scale.linear().range([height, 0]);
// define the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
// add the SVG element
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 + ")");
d3.queue()
.defer(d3.csv, "http://localhost:8000/HNP_Data.csv")
.defer(d3.csv, "http://localhost:8000/HNP_Series.csv")
.await(analyze);
var dataToVis;
var array = []
function analyze(error,data,series) {
dataToVis = data[2];
console.log(JSON.stringify(dataToVis));
for (var key in dataToVis) {
if(!isNaN(key) && key.length >0) {
var p = new Object;
p.year = key;
p.frequency = p(dataToVis[key]);
array.push(p);
}
}
array.forEach(function(d){
d.year = d.year;
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.year; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
// add axis
svg.append("g")
.attr("class", "x axis")
.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")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 5)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
// Add bar chart
svg.selectAll("bar")
.data(array)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.year); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); });
}
but I end up with this error.
Error: <rect> attribute y: Expected length, "NaN".
Error: <rect> attribute height: Expected length, "NaN".

Related

d3 - how to add x-axis labels for only years on a monthly chart?

I'm creating a bar chart in d3 (using r2d3 in R) that shows data per month. I'm wondering how I can format the x-axis so that while there is a bar for each month, the labels only show the year markers (i.e. under each january bar.)
Data look like this:
letter,frequency
1/1/2017,144
2/1/2017,85
3/1/2017,59
4/1/2017,73
5/1/2017,68
6/1/2017,91
7/1/2017,107
8/1/2017,94
9/1/2017,79
10/1/2017,84
11/1/2017,70
12/1/2017,86
1/1/2018,72
2/1/2018,71
3/1/2018,82
4/1/2018,50
5/1/2018,86
6/1/2018,75
7/1/2018,62
8/1/2018,72
9/1/2018,65
10/1/2018,75
11/1/2018,63
12/1/2018,87
1/1/2019,59
2/1/2019,60
3/1/2019,99
4/1/2019,81
Data in console:
D3 Code:
var parseDate = d3.time.format("%Y-%m-%d").parse;
//coerce data to dates
data.forEach(function (d) {
d.letter = parseDate(d.letter);
console.log(d.letter)
});
//set margins
var margin = {top: 40, right: 20, bottom: 60, left: 40},
width = 1000 - margin.left - margin.right,
height = 200 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickFormat(d3.time.format("%Y-%m"));
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
var svg = div.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 + ")");
r2d3.onRender(function(data, s, w, h, options) {
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.attr("transform", "rotate(270)")
.attr('dx', '-1.9em');
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.frequency); })
.attr("height", function(d) { return height - y(d.frequency); })
.on('mouseover', tip.show)
.on('mouseout', tip.hide)
});
function type(d) {
d.frequency = +d.frequency;
console.log(d);
return d;
}
Current Chart:
What I want:
Here's how D3 suggests filtering ordinal scales. This should only show your January ticks
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
.tickValues(x.domain().filter(function(d, i) { return !(i % 12); }))

Creating a bar chart using d3

So I'm trying to create a bar chart using d3 and here's my code, for some reason, the bar chart won't show up and I'm getting an error saying:
remote-work.js:14 Uncaught (in promise) TypeError: Cannot read property 'linear' of undefined
at drawBars (remote-work.js:14)
Can anyone advise me on where I'm going wrong it would be greatly appreciated.
async function drawBars() {
// 1. Access data
const data = await d3.csv("./../remote_work.csv")
console.log(data);
//chart = {
const margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 600 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom
const x = d => d.Benefits
const y = d3.scale.linear()
.range(height, 0);
const xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
const yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
const 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 + ")");
x.domain(data.map(function(d) { return d.Benefits; }));
y.domain([0, d3.max(data, function(d) { return d.Percentage; })]);
svg.append("g")
.attr("class", "x axis")
.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")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Percentage ($)");
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.Benefits); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.Percentage); })
.attr("height", function(d) { return height - y(d.Percentage); });
}
drawBars()

D3 Bar Chart Columns Overflowing

I am having an issue using d3 to create a bar chart where the column overflows the and the y axis labels are incorrect. Here is a fiddle http://jsfiddle.net/fajgvj9v/ that shows the issue. The data is parsing the <pre id="data"> tag to simulate a CSV I am using. If you change the value of 'a' to 5000 from 4000 it renders as expected.
<pre id="data">
name,count
a, 4000
b, 500
</pre>
yTitle = "Items";
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var y = d3.scale.linear()
.rangeRound([height, 0]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
var svg = d3.select("#malware_by_os").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 + ")");
var data = d3.csv.parse( d3.select("pre#data").text() );
console.log(data);
var xLbl = d3.keys(data[0])[0];
var yLbl = d3.keys(data[0])[1];
data.sort(function(a, b) {
return b[yLbl] - a[yLbl];
});
x.domain(data.map(function(d) {
return d[xLbl];
}));
y.domain([0, d3.max(data, function(d) {
return d[yLbl];
})]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text(yTitle);
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) {
return x(d[xLbl]);
})
.attr("width", x.rangeBand())
.attr("y", function(d) {
return y(d[yLbl]);
})
.attr("height", function(d) {
return height - y(d[yLbl]);
});
The max function needs to convert to a number using +
y.domain([0, d3.max(data, function(d) {
return d[yLbl];
})]);
should be
y.domain([0, d3.max(data, function(d) {
return +d[yLbl];
})]);

Rotated x-axis labels cut off in d3.js barchart

I have a bar chart in d3.js. The text of the x-axis labels were too long and overlapping each other so I rotated the x-axis text by adding the following 4 attributes to x axis text:
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)" );
The problem is that the height of the space allocated to the x-axis labels did not expand so now I am only seeing the last few characters of the labels. How can I expand the space allocated to the x-axis labels so that it extends to show all the rotated text?
Here's is the complete d3.js code:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
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 + ")");
d3.csv("segmentcount.csv", type, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.name; }));
y.domain([0, d3.max(data, function(d) { return d.count; })]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)" );
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.name); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.count); })
.attr("height", function(d) { return height - y(d.count); })
.style('fill', function(d) { return '#' + d.colour } );
});
Thanks very much!
Find the maximum label height, and translate the x axis accordingly. (Because the labels are rotated we compare the label widths).
Code snippet to illustrate:
<html>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var data = [
{name: "long name", count: 20},
{name: "name", count: 30},
{name: "even longer name", count: 10},
{name: "long name 4", count: 6},
{name: "long name 5", count: 17},
{name: "long name 6", count: 30},
{name: "long name 7", count: 45}];
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 + ")");
x.domain(data.map(function (d) {
return d.name;
}));
var gXAxis = svg.append("g")
.attr("class", "x axis")
.call(xAxis);
gXAxis.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", "rotate(-65)");
// Find the maxLabel height, adjust the height accordingly and transform the x axis.
var maxWidth = 0;
gXAxis.selectAll("text").each(function () {
var boxWidth = this.getBBox().width;
if (boxWidth > maxWidth) maxWidth = boxWidth;
});
height = height - maxWidth;
gXAxis.attr("transform", "translate(0," + height + ")");
var y = d3.scale.linear()
.range([height, 0]);
y.domain([0, d3.max(data, function (d) {
return d.count;
})]);
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Count");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function (d) {
return x(d.name);
})
.attr("width", x.rangeBand())
.attr("y", function (d) {
return y(d.count);
})
.attr("height", function (d) {
return height - y(d.count);
})
.style('fill', "blue");
</script>
</body>
</html>

D3 - ticks wrong positioned

I tried now couple of things but I can not figure out why my ticks are wrong positioned. I used different sources to make this stacked barchart.
Here is the fiddle of my code: http://jsfiddle.net/azj7guec/
And here is the code itself:
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 1000 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
x = d3.scale.ordinal().rangeRoundBands([0, width], .50);
y = d3.scale.linear().range([height, 0]);
z = d3.scale.ordinal().range(["darkblue", "blue", "lightblue"])
console.log("RAW MATRIX---------------------------");
// 4 columns: ID,c1,c2,c3
var matrix = [
[22,45,34,65],
[23,66,12,22],
[24,32,44,76],
[25,12,76,32],
[26, 67, 34, 56]
];
console.log(matrix)
var keys = matrix.map(function(item){return item[0]});
console.log("REMAP---------------------------");
var remapped =["c1","c2","c3"].map(function(dat,i){
return matrix.map(function(d,ii){
return {x: d[0], y: d[i+1] };
})
});
console.log(remapped)
console.log("LAYOUT---------------------------");
var stacked = d3.layout.stack()(remapped)
console.log(stacked)
//var yMax= d3.max(stacked)
x.domain(keys);
y.domain([0, d3.max(stacked[stacked.length - 1], function(d) { return d.y0 + d.y; })]);
// show the domains of the scales
console.log("x.domain(): " + x.domain())
console.log("y.domain(): " + y.domain())
console.log("------------------------------------------------------------------");
var yAxis = d3.svg.axis()
.scale(y)
.ticks(10)
.orient("left");
var xAxis = d3.svg.axis()
.scale(x)
.tickValues(keys)
.orient("bottom");
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("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.selectAll("text")
.style("text-anchor", "end")
.attr("dx", "-.8em")
.attr("dy", ".15em")
.attr("transform", function(d) {
return "rotate(-65)"
});
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Open Issues");
// Add a group for each column.
var valgroup = svg.selectAll("g.valgroup")
.data(stacked)
.enter().append("g")
.attr("class", "valgroup")
.style("fill", function(d, i) { return z(i); })
.style("stroke", function(d, i) { return d3.rgb(z(i)).darker(); });
// Add a rect for each date.
var rect = valgroup.selectAll("rect")
.data(function(d){return d;})
.enter().append("rect")
.attr("width", 20)
.attr("x", function(d) { return x(d.x); })
.attr("y", function(d) { return y(d.y0 + d.y); })
.attr("height", function(d) { return y(d.y0) - y(d.y0 + d.y); });
//.attr("width", x.rangeBand());
Hope someone can help me.
The ordinal scale divides its output range into intervals based on the input domain. Your current positioning puts the bars at the beginning of those intervals, whereas the ticks are in the center. To match up the positions, add half the interval minus half the bar width to the beginning of the interval:
.attr("x", function(d) { return x(d.x) + (x.rangeBand() - 20) / 2; })
Complete demo here.

Categories