I'm using d3 to build a horizontal bar chart.
When it is first rendered the values are displayed within the bar:
However, when further data is added something is preventing it and the new bars from showing:
This problem is related to my code to display the values within the bars.
When I remove this chunk of code, the new bars show (just without the values in them):
Where am I going wrong?
function renderBarChart(data, metric, countryID) {
data = sortByHighestValues(data, metric)
const width = 0.9 * screen.width
const height = 0.8 * screen.height
const margin = { top: 0, right: 0, bottom: 20, left: 30 }
const innerHeight = height - margin.top - margin.bottom
const xScale = d3
.scaleLinear()
.domain([0, d3.max(data, (d) => d[metric])])
.range([margin.left, width]);
const yScale = d3
.scaleBand()
.domain(data.map((d) => d[countryID]))
.range([0, innerHeight])
.padding(0.2);
const yAxis = d3.axisLeft(yScale);
const xAxis = d3.axisBottom(xScale).ticks(10);
if (!barChartAxisRendered) {
renderYAxis(width, height, margin, yAxis)
renderXAxis(width, height, margin, xAxis, innerHeight)
} else {
updateXAxis(width, height, xAxis)
updateYAxis(width, height, yAxis)
}
barChartAxisRendered = true
renderBars(data, yScale, xScale, margin, metric, countryID)
};
function renderBars(data, yScale, xScale, margin, metric, countryID) {
let selectDataForBarCharts = d3.select("svg")
.selectAll("rect")
.data(data)
selectDataForBarCharts
.enter()
.append("rect")
.merge(selectDataForBarCharts)
.attr("fill", d => setBarColor(d))
.attr("y", (d) => yScale(d[countryID]))
.attr("width", (d) => xScale(d[metric]))
.attr("height", yScale.bandwidth())
.attr("transform", `translate(${margin.left}, ${margin.top})`)
selectDataForBarCharts
.enter()
.append("text")
.merge(selectDataForBarCharts)
.attr("class", "casesPerCapitaValues")
.attr('text-anchor', 'middle')
.attr("x", d => xScale(d[metric])-10)
.attr("y", d => yScale(d[countryID]) + 13)
.attr("fill", "white")
.style("font-size", "10px")
.text(d => d.casesPerCapita)
}
Your code breaks because you overwrite the rectangles with text. Make a different selections for texts and rects, or make a group containing corresponding rect+text of one row.
I want to create a barchart displaying C02 emission.
The Problem (see picture below):
Why are the bars "pushed" to the right? Why are the years in the x-axis displayed without the first integer?
I am using Version 3 of d3.
Given some JSON data like this:
[
{
"Cement": 0.0,
"Gas Flaring": 0.0,
"Gas Fuel": 0.0,
"Liquid Fuel": 0.0,
"Per Capita": null,
"Solid Fuel": 3.0,
"Total": 3.0,
"Year": 1751
},
and so on…
]
To prepare for scaling I did:
var minDate = dataset[0].Year;
var maxDate = dataset[dataset.length - 1].Year;
var maxValue = d3.max(dataset, function(d) {
return d["Per Capita"];
});
I append the svg
var svg = d3
.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
I sacled the xAxis and the yAxis:
var xAxisScale = d3.time
.scale()
.domain([minDate, maxDate])
.range([0, w]);
var yAxisScale = d3.scale
.linear()
.domain([0, maxValue])
.range([h, 0]);
The I finally builded these axisses…
var xAxis = d3.svg
.axis()
.scale(xAxisScale)
.orient("bottom");
var yAxis = d3.svg
.axis()
.scale(yAxisScale)
.orient("left");
svg
.append("g")
.attr("class", "axis")
.attr("transform", "translate(92," + (h - padding) + ")")
.call(xAxis);
svg
.append("g")
.attr("class", "axis")
.attr("transform", "translate(" + padding + ",-90)")
.call(yAxis);
I also than addeded the rects…
svg
.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.style("fill", "teal")
.attr({
x: function(d, i) {
return i * (w / dataset.length);
},
y: function(d) {
return yAxisScale(d["Per Capita"]);
},
width: w / dataset.length,
height: function(d) {
return h - yAxisScale(d["Per Capita"]);
}
});
The result is not the intended one.
Could you please elaborate what went wrong?
Why are the bars "pushed" to the right?
Why are the years in the x-axis displayed without the first integer?
I am using Version 3 of d3.
Thank you very much!
The main problem here is that this...
"Year": 1751
... is not a date object. That's just a number. If you look at your axis you'll realise that.
So, you have to parse it. For instance:
const format = d3.time.format("%Y");
dataset.forEach(function(d){
d.Year = format.parse(d.Year);
});
Also, when you do this...
var minDate = dataset[0].Year;
var maxDate = dataset[dataset.length - 1].Year;
... you're blindly trusting that the array is sorted. Don't do that. Instead, do:
var minDate = d3.max(dataset, function(d){
return d.Year
});
var maxDate = d3.min(dataset, function(d){
return d.Year
});
Or, if you want to use destructuring:
var [minDate, maxDate] = d3.extent(dataset, d => d.Year);
Finally, now that you have a proper scale, don't use the indices for the x position. Use the scale:
x: function(d) {
return xAxisScale(d.Year);
},
This covers the problem regarding the x position. For fixing the y position, just set a proper margin.
Using d3.js I want to make a chart showing each item as pie chart displaying the quarterly sale of tickets for different programs
here is the sample picture of chart I want to make,each segment in the chart represents ticket for a program and its percentage of sales in each quarter. Now using this link enter link description here
I have made a graph but which is not exactly the one I needed.Is there any charts available in d3.js to show a graph as I mentioned in the picture or we need to customize it to get a graph like that.
Is there any charts available in d3.js to show a graph as I mentioned
in the picture or we need to customize it to get a graph like that?
No there isn't a ready made solution, d3 as the comment on the question notes is a collection of methods for manipulating the DOM, this allows a great deal of flexibility in creating custom visualizations (users aren't as limited as with many ready-made solutions that only allow defined modifications). Consequently, yes, you can make a chart like that in d3 taking elements and ideas from both scatter plot and pie chart implementations with d3 to make your chart.
This answer shows one approach that could be used in creating such a graph. Ideally it can provide ideas in crafting your own visualization that meets your need.
First, you need a mechanism to make variable sized pie charts and to place them - arguably this is the hardest part (after that you just have a scatter plot that's easier to manipulate). This requires some thought as to data structure, I've used a structure such as:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
You could add other properties as needed, all that this does is specify an x and y coordinate for the pie chart center, a radius for the pie chart, and the values of the wedges for each pie chart.
With that, you can append a group element (g) to your svg, one for each pie chart (or item in the data array) using a standard enter cycle in d3, positioning the groups as we go:
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
Because the data array used to append the wedges themselves will only include the wedge values, we can save the radius property as a property of the group and access that when appending the wedges:
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
A basic example might look like this:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:130,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:50,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
If you want to set each circle to have it's own color scheme, a few options might be available. If every pie has only two colors, you could assign a fill to the parent group and use the wedge increment to set transparency, creating lighter wedges such as in your image:
var data = [
{x:100,y:100,radius:20,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2]},
{x:180,y:130,radius:30,slices:[1,7]},
{x:50,y:50,radius:15,slices:[5,3]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = ["steelblue","orange","pink","crimson"]
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("fill",function(d,i) { return color[i] })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("opacity",function(d,i){
return 1-i*0.2;
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Other options are available, such as storing a local variable, storing the color as a property as we did for radius, or modifying our data structure to include a color for each wedge:
var data = [
{x:100,y:100,radius:20,
slices:[{value:1,color:"steelblue"},{value:5,color:"lightblue"} ]},
{x:150,y:180,radius:10,
slices:[{value:1,color:"crimson"},{value:2,color:"pink"}]},
{x:180,y:130,radius:30,
slices:[{value:1,color:"lawngreen"},{value:7,color:"darkgreen"}]}
]
var width = 500;
var height = 300;
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d.value; });
// Append a group for each pie chart, it will store the radius of each pie as a property
var pies = svg.selectAll("g")
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+d.x+","+d.y+")"; });
// draw each pie wedge, using the slices property of the data bound to the parent g
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
// remember that d3.pie creates it's own data array, thus using d.data.property:
.attr("fill",function(d){ return d.data.color; })
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Now we can adapt and implement characteristics of a scatter plot such as scales and axes. This would be the same for any other scatter plot essentially, we would scale the max and min (or a defined range) for the x and y scales, and add the axes. Altogether, that might look something like:
var data = [
{x:100,y:100,radius:10,slices:[1,5]},
{x:150,y:180,radius:10,slices:[1,2,3,4]},
{x:180,y:110,radius:30,slices:[1,2,3,4,5,6,7]},
{x:50,y:100,radius:15,slices:[5,3]},
{x:50,y:180,radius:40,slices:[6,3]}
]
var width = 500;
var height = 300;
var margin = {left:30,right:10,top:30,bottom:30}
var xScale = d3.scaleLinear()
.range([0,width-margin.left-margin.right])
.domain([0,d3.max(data,function(d) { return d.x + 20 }) ]);
var yScale = d3.scaleLinear()
.range([height-margin.top-margin.bottom,0])
.domain([0,d3.max(data,function(d) { return d.y + 20}) ]);
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate("+margin.left+","+margin.top+")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0,"+(height-margin.bottom-margin.top)+")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function(d) { return d; });
var color = d3.schemeCategory10;
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius",function(d) { return d.radius; })
.attr("transform",function(d) { return "translate("+xScale(d.x)+","+yScale(d.y)+")"; });
pies.selectAll()
.data(function(d){ return pie(d.slices); })
.enter()
.append("path")
.attr("d",function(d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d) })
.attr("fill",function(d,i){
return color[i];
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
Adding grid lines, legends, mouse over functionality, and other features should be relatively straightforward now - look at scatterplot examples with d3 to see how these and other features might be implemented, modifying a scatterplot of cirlces is about the same as modifying a scatterplot of pie charts.
From the sample provided by #Andrew Reid I have made it , the sample code for reference is posted here
<html>
<head>
<title>TODO supply a title</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
</head>
<body>
<script>
var data = [
{x: 170, y: 160, radius: 20, slices: [3, 4]},
{x: 180, y: 40, radius: 30, slices: [ 6, 7]},
{x: 50, y: 80, radius: 20, slices: [5, 3]},
{x: 50, y: 180, radius: 40, slices: [6, 3]}
]
var width = 500;
var height = 300;
var margin = {left: 30, right: 10, top: 30, bottom: 30}
var xScale = d3.scaleLinear()
.range([0, width - margin.left - margin.right])
.domain([0, d3.max(data, function (d) {
return d.x + 20
})]);
var yScale = d3.scaleLinear()
.range([height - margin.top - margin.bottom, 0])
.domain([0, d3.max(data, function (d) {
return d.y + 20
})]);
xMid=d3.max(xScale.domain())/2;
yMid=d3.max(yScale.domain())/2;
console.log(xMid,yMid)
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height);
var g = svg.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
var xAxis = d3.axisBottom(xScale);
g.append("g")
.attr("transform", "translate(0," + (height - margin.bottom - margin.top) + ")")
.call(xAxis);
var yAxis = d3.axisLeft(yScale);
g.append("g")
.call(yAxis);
var lineX= g.append("line")
.attr("x1", 0)
.attr("x2", 500)
.attr("y1", yMid+20)
.attr("y2", yMid+20)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var liney= g.append("line")
.attr("x1", xMid+130)
.attr("x2", xMid+130)
.attr("y1", -10)
.attr("y2", 245)
.attr("stroke-width", 1)
.attr("stroke", "black")
.attr("stroke-dasharray", "7,7");
var arc = d3.arc()
.innerRadius(0)
.outerRadius(50);
var pie = d3.pie()
.sort(null)
.value(function (d) {
return d;
});
var colors = d3.schemeCategory20;
var color = ["steelblue","orange","green","red"]
var pies = g.selectAll(null)
.data(data)
.enter()
.append("g")
.property("radius", function (d) {
return d.radius;
})
.attr("transform", function (d) {
return "translate(" + xScale(d.x) + "," + yScale(d.y) + ")";
})
.attr("fill", function (d, i) {
return color[i];
});
pies.selectAll()
.data(function (d) {
return pie(d.slices);
})
.enter()
.append("path")
.attr("d", function (d) {
var radius = d3.select(this.parentNode).property("radius");
arc.outerRadius(radius);
return arc(d)
})
.attr("opacity",function(d,i){ return 1-i*0.7; });
</script>
</body>
Okay, I'm starting to get a little more familiar with D3 but am still a little hazy on some things. I'm now trying to draw grid lines but am realizing that I may be hacking away versus doing it correctly. I tried to add some gridlines, using a tutorial, but ended up with a lot of code that I seem to be jimmy rigging in order to get it to line up properly. I was wondering if anyone could point me to a better way of writing this...
The original code is this.
<script type="text/javascript">
//Width and height
var w = 800;
var h = 400;
var padding = 20;
var border=1;
var bordercolor='black';
var dataset = [
[5, 20], [480, 90], [250, 50], [100, 33], [330, 95],[-50,-100],[50,-45],
[410, 12], [475, 44], [25, 67], [85, 21], [220, 88],[-480, -467], [3,-90],[468,481]
];
// create scale functions
var xScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[0]; })])
.range([padding, w - padding * 2]);
var yScale = d3.scale.linear()
.domain([d3.min(dataset, function(d) { return d[0]; }), d3.max(dataset, function(d) { return d[1]; })])
.range([h - padding, padding]);
var rScale = d3.scale.linear()
.domain( [-100, d3.max(dataset, function(d) { return d[1]; })] )
.range([2,5]);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("border",border)
;
//define X axis this is rly a function, remember, variables can hold functions in JS
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.tickSize(-h, 0, 0)
; //Set rough # of ticks
//Define Y axis
var yAxis = d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(1)
.tickSize(-w, 0, 0)
;
//create the circles
svg.selectAll("circle")
.data(dataset)
.enter()
.append("circle")
.attr("cx", function(d) {
return xScale(d[0]);
})
.attr("cy", function(d) {
return yScale(d[1]);
})
.attr("r", 3);
// draw axes here
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(0," + (h - padding) +")")
.call(xAxis);
svg.append("g")
.attr("class", "axis") //assign "axis" class
.attr("transform", "translate(" + padding + ",0)" )
.call(yAxis);
// end draw axes here
</script>
and the code I added in the second link is here
var vis = svg.append("svg:g")
.attr("transform", "translate(20,0)")
var rules = vis.append("svg:g").classed("rules", true)
rules.append("svg:g").classed("grid x_grid", true)
.attr("transform", "translate(-20,"+h+")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(-h,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("grid y_grid", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSize(-w,0,0)
.tickFormat("")
)
rules.append("svg:g").classed("labels x_labels", true)
.attr("transform", "translate(-20,"+ h +")")
.call(d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(4)
.tickSize(0)
.tickFormat("")
// .tickFormat(d3.time.format("%Y/%m"))
)
rules.append("svg:g").classed("labels y_labels", true)
.call(d3.svg.axis()
.scale(yScale)
.orient("left")
.ticks(5)
.tickSubdivide(1)
.tickSize(0, 0, 0)
.tickFormat("")
)
Again, really appreciate any help
Assuming that you have Mike Bostock's standard margins defined and you have defined a linear scale for the y-axis the following code will create horizontal gridlines without using tickSize().
svg.selectAll("line.horizontalGrid").data(yScale.ticks(4)).enter()
.append("line")
.attr(
{
"class":"horizontalGrid",
"x1" : margin.right,
"x2" : width,
"y1" : function(d){ return yScale(d);},
"y2" : function(d){ return yScale(d);},
"fill" : "none",
"shape-rendering" : "crispEdges",
"stroke" : "black",
"stroke-width" : "1px"
});
I would suggest to use d3.svg.axis().scale() to tie up the grid to your coordinates. I drew a quick example based on your code: http://jsfiddle.net/temirov/Rt65L/1/
The gist is to use the existing scales, x and y, and to use ticks as grid. Since yAxis and xAxis are already defined we can just re-use them. Here is the relevant code:
//Draw a grid
var numberOfTicks = 6;
var yAxisGrid = yAxis.ticks(numberOfTicks)
.tickSize(w, 0)
.tickFormat("")
.orient("right");
var xAxisGrid = xAxis.ticks(numberOfTicks)
.tickSize(-h, 0)
.tickFormat("")
.orient("top");
svg.append("g")
.classed('y', true)
.classed('grid', true)
.call(yAxisGrid);
svg.append("g")
.classed('x', true)
.classed('grid', true)
.call(xAxisGrid);
You could use the ticks() function of your scale to get the tick values and then use them in a data call to draw the lines.
var ticks = xScale.ticks(4);
rules.selectAll("path.xgrid").data(ticks).enter()
.append("path")
.attr("d", function(d) {
return "M" + xScale(d) + " " + padding + "L" + xScale(d) + " " + (h-padding);
});
You may prefer using a line generator for the grid lines instead of creating the path manually. This works similarly for y grid lines, only that the y coordinate is constant and ranges from 0 to width of graph. You may need to adjust the start and end values to make it look "nice".
In the d3fc project we have created a gridlines component that renders in exactly the same way as the D3(v4) axis.
Here's an example of the usage:
var width = 500, height = 250;
var container = d3.select("#gridlines")
.append("svg")
.attr("width", width)
.attr("height", height);
var xScale = d3.scaleLinear()
.range([0, 100]);
var yScale = d3.scaleLinear()
.range([0, 100]);
var gridline = fc.annotationSvgGridline()
.xScale(xScale)
.yScale(yScale);
container.append("g")
.call(gridline);
Which renders as follows:
The spacing of the gridlines is determined by the ticks supplied by the associated axes.
Disclosure: I am a core contributor to the d3fc project!
Following #arete's idea, you can use the following to avoid re-drawing unnecessarily the gridline:
function createsGrid(data) {
var grid = gridLine.selectAll("line.horizontalGrid").data(scaleY.ticks());
grid.enter()
.append("line")
.attr("class","horizontalGrid");
grid.exit().remove();
grid.attr({
"x1":0,
"x2": width,
"y1": function (d) { return scaleY(d); },
"y2": function (d) { return scaleY(d); }
});
}
and define the following in your CSS file
line.horizonalGrid{
fill : none;
shape-rendering : crispEdges;
stroke : black;
stroke-width : 1.5px;
}
You could just use innerTickSize, instead of tickSize:
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
.ticks(1)
.innerTickSize(-h);
Use tickSizeInner()
// x axis
var x = d3.scaleLinear().range([0, width]).domain([0, 100000]);
svg
.append("g")
.call(d3.axisBottom(x).ticks(10).tickSizeInner(-height))