d3js v5 x-axis bar chart - javascript
I tried a lot of datasets and I don't know why, I have a issue with data_histo.csv. The x axis seems reverse and then, bars can't be displayed. With data_histo2.csv or data_histo3.csv, all is good.
An explanation could be nice!
<!DOCTYPE html>
<html>
<head>
<script src="https://unpkg.com/d3#5.0.0/dist/d3.min.js"></script>
</head>
<body>
<svg class="histogramme" width="960" height="600"></svg>
<script>
//select
let svgHisto = d3.select(".histogramme")
//dimension
let margin = {top: 20, right: 10, bottom: 20, left: 80}
let width = +svgHisto.attr("width") - margin.left - margin.right
let height = +svgHisto.attr("height") - margin.top - margin.bottom;
let g1 = svgHisto.append("g")
.attr("transform",`translate(${margin.left}, ${margin.top})`);
//data
d3.csv("data_histo.csv").then(function(data) {
console.log(data);
//define x and y axis
let x = d3.scaleLinear();
let y = d3.scaleBand();
x.domain(d3.extent(data, function(d) { return d.value0; })).nice()
.rangeRound([0, width]);
y.domain(data.map(function(d) { return d.libreg; }))
.rangeRound([0, height]).padding(0.1);
//add x axis
g1.append("g")
.attr("class", "axis x_axis")
.attr("transform",`translate(0,${height})`)
.call(d3.axisBottom(x));
//add y axis
g1.append("g")
.attr("class", "axis y_axis")
.call(d3.axisLeft(y));
//bar chart
g1.selectAll(".bar1")
.data(data)
.enter().append("rect")
.attr("class", "bar bar1")
.attr("x", function(d) {return x(Math.min(0,d.value0)); })
.attr("y", function(d) { return y(d.libreg) + 10; })
.attr("width", 0)
.attr("height", y.bandwidth() - 20);
//animate
g1.selectAll(".bar1")
.transition()
.duration(1000)
.delay(function(d,i){return i*100})
.attr("width", function(d) { return x(d.value0); });
});
</script>
</body>
</html>
With data_histo.csv
"codreg","libreg","year0","value0","year1","value1"
"03","Guyane",2009,4,2014,4.6
"04","La Réunion",2009,8.2,2014,9.8
"11","Île-de-France",2009,12.6,2014,13.9
"01","Guadeloupe",2009,13.3,2014,15.8
"32","Hauts-de-France",2009,14.7,2014,16.1
"02","Martinique",2009,14.7,2014,17.6
"44","Grand Est",2009,16.5,2014,18
"84","Auvergne-Rhône-Alpes",2009,16.8,2014,18.3
"52","Pays de la Loire",2009,17.1,2014,18.6
"28","Normandie",2009,17.2,2014,19
"53","Bretagne",2009,18.5,2014,20.2
"24","Centre-Val de Loire",2009,18.7,2014,20.4
"27","Bourgogne-Franche-Comté",2009,18.8,2014,20.7
"76","Occitanie",2009,19.3,2014,20.8
"93","Provence-Alpes-Côte d''Azur",2009,19.5,2014,21.3
"94","Corse",2009,20.2,2014,21.5
"75","Nouvelle-Aquitaine",2009,20.2,2014,21.8
With data_histo2.csv
"codreg","libreg","year0","value0","year1","value1"
"84","Auvergne-Rhône-Alpes",2013,39.1,2017,41.7
"27","Bourgogne-Franche-Comté",2013,42.3,2017,44.4
"53","Bretagne",2013,39.6,2017,44.7
"24","Centre-Val de Loire",2013,40.5,2017,46.8
"94","Corse",2013,24.2,2017,30.8
"44","Grand Est",2013,41.3,2017,45.4
"01","Guadeloupe",2013,55.5,2017,56.5
"03","Guyane",2013,33.1,2017,33.2
"32","Hauts-de-France",2013,45.8,2017,47.3
"11","Île-de-France",2013,40.1,2017,42.6
"02","Martinique",2013,52.5,2017,50.2
"28","Normandie",2013,42.6,2017,46.2
"75","Nouvelle-Aquitaine",2013,40,2017,44.4
"76","Occitanie",2013,40.3,2017,43.7
"52","Pays de la Loire",2013,40.6,2017,45.8
"93","Provence-Alpes-Côte d''Azur",2013,38.5,2017,42.6
"04","La Réunion",2013,54.2,2017,54.6
"06","Mayotte",2013,,2017,
Here is my code : https://plnkr.co/edit/B8qEQ4dlUdRHhkQvzjZx?p=preview
There are two issues with your code:
D3 parses csv/tsv/dsv entries as text. So when you load your csv, you get rows that look like this:
{
"codreg": "03",
"libreg": "Guyane",
"year0": "2009",
"value0": "4",
"year1": "2014",
"value1": "4.6"
}
When you set your scale with extent, you aren't using the numerical extent. You could coerce your data to a number like so:
data.forEach(function(d) {
d.value0 = +d.value0;
})
Secondly, if you do this you'll notice some peculiar behavior in the placement of the bars:
You can see that the bars start to the left of the plot area. The reason is that you are using a linear scale, and plot the start of the bars like so:
.attr("x", function(d) {return x(Math.min(0,d.value0)); })
You want your bars to start at x(4) - which is where the x value that marks the interception with the y axis. Instead you are using x(0), which will naturally be to the left of where you want. This works in your second example, because x(0) also happens to be the x value that intercepts the y axis. Instead, you can simply use:
.attr("x",0)
This marks the left edge of the plot area, which is likely where you want all your bars to be anchored to.
Here's a forked plunkr.
One thing to note, is that your shortest bar will always be non-visible: the start and end points will be the same. This is because the extent of the scale goes from the smallest value to the largest value - and the smallest value, marking the left boundary of the plot area, is the value of the shortest bar. To change this you can modify the scale's domain, perhaps using 0 as the first value and then using d3.max to find the uppermost value.
Related
Adding HTML tags to axes labels using D3.JS
I'm working on simple horizontal bar chart using vue and d3. I wanna customize my axis labels along vertical axis. Now my code where I specify what to put in every label is looking like this: let yAxis = d3.axisLeft() .scale(this.yScale) .tickSize(0) .tickPadding(4) .tickFormat((d, i) => { return this.data[i].country + " " + this.data[i].value }) I'd like my labels to look this way: country value Country1 10.2 Country2 200.3 Country2 3000.4 i.e. country must be aligned left, while value must be right. Besides value must be in bold. The problem is though that it seems as if .tickFormat doesn't accept any html tags
Here is an example of styling the axis labels: const svg = d3.select("svg") .attr("width", 1000) // Create the scale const x = d3.scaleLinear() .domain([0, 100]) // This is what is written on the Axis: from 0 to 100 .range([100, 800]); // This is where the axis is placed: from 100px to 800px // Draw the axis svg .append("g") .classed('x-axis', true) .attr("transform", "translate(0,50)") // This controls the vertical position of the Axis .call(d3.axisBottom(x)); svg.select('.x-axis') .selectAll('text') .style('fill', 'red') .style('font-size', '32px') .attr('y', 20) <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script> <svg></svg>
D3 Categorical Area Chart - Scale Issue
I'm trying to create an area chart for statistics on each US state. I have a single number statistic for each state; an element of my data list looks like the following: {'state':'CA','count':4000} Currently, my area chart looks like this. The task is mainly complete, but you may notice how the very last category (in this case, UTAH) isn't filled. I'm not quite sure how to get around this. close_up I am using a scaleBand axis; this felt appropriate. Perhaps it is not the correct approach. Here is the JS behind the chart: var svg_area = d3.select("#area") .attr("width", width + margin.left + margin.right) .attr("height", height + margin.top + margin.bottom), g_area = svg_area.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); var x = d3.scaleBand().range([0, width]), y = d3.scaleLinear().range([height, 0]); var area = d3.area() .x(function(d) { return x(d.state); }) .y0(height) .y1(function(d) { return y(d.count); }); d3.csv('data/states.csv', function(data) { data.forEach(function(d) { d.count = +d.count; }); data.sort(function(a, b){ return b.count-a.count; }); data = data.slice(0,30); x.domain(data.map(function(d) { return d.state; })); y.domain([0, d3.max(data, function(d) { return d.count; })]); g_area.append('path') .datum(data) .attr('fill', solar[1]) .attr("class", "area") .attr('d', area); g_area.append("g") .attr("class", "x-axis") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)); g_area.append("g") .attr("class", "y-axis") .attr("transform", "translate(0," + 0 + ")") .call(d3.axisLeft(y)); }); Any suggestions on how I can fix this? Thanks for any feedback!
Contrary to your question's title (now edited), the area chart is not "leaving out the last data point". What you're seeing is the expected result, since you are using a band scale. Actually, that value just above the horizontal axis (just in the "edge" of the area chart) is Utah value! Try to understanding it with this explanation: Imagine a bar chart with your data. Each bar has, of course, a given width. Now, draw a path going from the top left corner of one bar to the top left corner of the next bar, starting at the first bar and, when reaching the last bar, going down from the top left corner to the axis. That's the area you have right now. There are two solutions here. The first one is using a point scale instead: var x = d3.scalePoint().range([0, width]) However, this will trim the "margins" of the area path, before the first state and after the last state (Utah). That means, the area chart will start right over California tick and end right over Utah tick. If you don't want that there is a second solution, which is hacky, but will keep those "margins": add the bandwidth() to the last state in the area generator: var area = d3.area() .x(function(d, i) { return i === data.length - 1 ? x(d.state) + x.bandwidth() : x(d.state) }) It may be worth noting that, using a band scale, your chart is technically incorrect: the values in the area for each state are not over the tick for that state.
D3.js Line Chart - Can't get x-axis to show up
I'm following this example by Mike himself. The timeFormat in the example is ("%d-%b-%y"), but using my own data uses just the year. I've made all the necessary changes (I think). The y-axis shows, but the x-axis doesn't. There are also no errors showing, so I'm not sure where to go. Below is my code. Thanks! <!DOCTYPE html> <meta charset="utf-8"> <p></p> <style> .axis--x path { display: none; } .line { fill: none; stroke: steelblue; stroke-width: 1.5px; } </style> <!--We immediately define the variables of our svg/chart--> <svg width="960" height="500"></svg> <script src="https://d3js.org/d3.v4.min.js"></script> <script> // Now we give our svg some attributes. We use conventional margins as set out by Mike Bostock himself. // Sets width and height minus the margins. var svg = d3.select("svg"), margin = {top: 20, right: 20, bottom: 30, left: 50}, width = +svg.attr("width") - margin.left - margin.right, height = +svg.attr("height") - margin.top - margin.bottom, g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")"); // Here we set out the time format: date-month-year. //var parseTime = d3.timeParse("%d-%b-%y"); var formatTime = d3.timeFormat("%Y"); formatTime(new Date); // "2015" // Now we set our axis. X is according to the time, and y is linear. // We use rangeRound to round all the values to the nearest whole number. // We don't use rangeBands or rangePoints as we're not creating a bar chart or scatter plot. var x = d3.scaleTime() .rangeRound([0, width]); var y = d3.scaleLinear() .rangeRound([height, 0]); // Now we tell what we want our line to do/represent. // x is the date, and y is the close price. var line = d3.line() .x(function(d) { return x(d.date); }) .y(function(d) { return y(d.close); }); // This is where we load our tsv file. d3.tsv("/LineCharts/Line Chart 2 - MO Capital Punishment/data/data.tsv", function(d) { d.date = formatTime(d.date); d.close = +d.close; return d; }, function(error, data) { if (error) throw error; // The .extent function returns the minimum and maximum value in the given array. // Then, function(d) { return d.date; } returns all the 'date' values in 'data'. // The .domain function which returns those maximum and minimum values to D3 as the range for the x axis. x.domain(d3.extent(data, function(d) { return d.date; })); //Same as above for the x domain. y.domain(d3.extent(data, function(d) { return d.close; })); // Note that we use attr() to apply transform as an attribute of g. // SVG transforms are quite powerful, and can accept several different kinds of transform definitions, including scales and rotations. // But we are keeping it simple here with only a translation transform, which simply pushes the whole g group over and down by some amount, each time a new value is loaded onto the page. g.append("g") .attr("class", "axis axis--x") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)); // Doing the same as above but for the y axis. g.append("g") .attr("class", "axis axis--y") .call(d3.axisLeft(y)) //This is where we append(add) text labels to our y axis. .append("text") .attr("fill", "#000") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", "0.71em") .style("text-anchor", "end") .text("Total"); g.append("path") .datum(data) .attr("class", "line") .attr("d", line); }); </script>
D3 line graph getting error and not getting plotted
I am making a line graph for a set of data regarding letter vs frequency. I have made proper code for x and y axis, but while plotting line I am getting error and not able to plot the line-graph. Can someone help fix the issue? SNIPPET: <html> <head> <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.12/angular.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.3.0/d3.min.js"></script> </head> <body ng-app="myApp" ng-controller="myCtrl"> <svg></svg> <script> //module declaration var app = angular.module('myApp',[]); //Controller declaration app.controller('myCtrl',function($scope){ $scope.svgWidth = 800;//svg Width $scope.svgHeight = 500;//svg Height //Data in proper format var data = [ {"letter": "A","frequency": "5.01"}, {"letter": "B","frequency": "7.80"}, {"letter": "C","frequency": "15.35"}, {"letter": "D","frequency": "22.70"}, {"letter": "E","frequency": "34.25"}, {"letter": "F","frequency": "10.21"}, {"letter": "G","frequency": "7.68"}, ]; //removing prior svg elements ie clean up svg d3.select('svg').selectAll("*").remove(); //resetting svg height and width in current svg d3.select("svg").attr("width", $scope.svgWidth).attr("height", $scope.svgHeight); //Setting up of our svg with proper calculations var svg = d3.select("svg"); var margin = {top: 20, right: 20, bottom: 30, left: 40}; var width = svg.attr("width") - margin.left - margin.right; var height = svg.attr("height") - margin.top - margin.bottom; //Plotting our base area in svg in which chart will be shown var g = svg.append("g"); //shifting the canvas area from left and top g.attr("transform", "translate(" + margin.left + "," + margin.top + ")"); //X and Y scaling var x = d3.scaleLinear().rangeRound([0, width]); var y = d3.scaleBand().rangeRound([height, 0]).padding(0.4); //Feeding data points on x and y axis x.domain([0, d3.max(data, function(d) { return +d.frequency; })]); y.domain(data.map(function(d) { return d.letter; })); //Final Plotting //for x axis g.append("g") .attr("transform", "translate(0," + height + ")") .call(d3.axisBottom(x)); //for y axis g.append("g") .call(d3.axisLeft(y)) .append("text") .attr("transform", "rotate(-90)") .attr("y", 6) .attr("dy", "0.71em") .attr("text-anchor", "end"); //the line function for path var lineFunction = d3.line() .x(function(d) {return xScale(d.x); }) .y(function(d) { return yScale(d.y); }) .curve(d3.curveLinear); //defining the lines var path = g.append("path"); //plotting lines path .attr("d", lineFunction(data)) .attr("stroke", "blue") .attr("stroke-width", 2) .attr("fill", "none"); }); </script> </body> </html> ERROR: NEW ERROR:
Look at the console: you don't have a xScale or a yScale. So, the line generator should be: var lineFunction = d3.line() .x(function(d) {return x(d.frequency); }) .y(function(d) { return y(d.letter); }) .curve(d3.curveLinear); Besides that, frequency is a string, not a number. So, it's a good idea turning it into a number. Write this right after your data variable: data.forEach(function(d){ d.frequency = +d.frequency; }); Note: it's a good practice defining your variable names properly, with descriptive names, like xScale, yAxis, chartLegend or formatNumber... Look at your line generator: you have two different x in a single line. If you don't take care, you'll mix them.
If you want to use xScale and yScale , you need to define these functions. Syntax is given below (ignore values): Below code is for d3 version 3 me.xscale = d3.scale.linear() // for horizontal distances if using for 2D .domain([0, 500]) .range([0, 700]); me.yscale = d3.scale.linear() // for vertical distances if using for 2D .domain([0, 600]) .range([0, 200]); These functions are used to define mapping of a values in one range to values in other range. e.g - Suppose you want draw a graph on your browser screen. And you want assume that width 500px on your browser screen should be counted as 500 on your graph. You need to define xScale as above . In this case , this function will map every value in domain (0-500) to unique value in range (0-700) and vice versa.
correctly sizing a D3 bar graph
I am attempting to add labels/axis/titles/etc to a D3 bar graph. I can get something close to the right size, however I end up clipping off part of the last bar (so the last bar is skinnier than the others). Here is the pertinent code: var x = d3.scale.linear() .domain([0, object.data.length]) .range([0, object.width]); var y = d3.scale.linear() .domain([object.min-(object.max-object.min)*.15, object.max (object.max-object.min)*.15]) .rangeRound([ object.height - 30, 0]); var vis = d3.select(object.ele) .append("svg:svg") .attr("width", object.width) .attr("height", object.height) .append("svg:g"); vis.selectAll("g") .data(object.data) .enter().append("rect") .attr("x", function(d, i) { return x(i); }) .attr("y", function(d) { return object.height - y(d.value); }) .attr("width", object.width/object.data.length - 1) .attr("height", function(d) { return y(d.value); }) .attr("transform", "translate(30,-30)"); At the moment everything (labels, axis, and so on) is 30px. How do I correctly alter the graph to make room for whatever else I need?
You are cutting off your last bar because you use translate the x coordinate but your the range of your x is only to the width without the extra 30 pixels. Also it may be easier to simplify your y domain to use .domain([object.min, object.max]) then have the "y" and "height" functions reversed. This way you start the rect at the y(d.value) and make it's height object.height - y(d.value). I would create three groups initially, one for your y axis, one for x axis, and then another for the bars. Draw your bars inside the last group and then translate the whole group itself instead of each individual bar. Increase the size of your object.width and object.height to match the total space you want.