I'd like to create a dot chart in d3.js, where it looks kind of like a bar chart, but there are stacked dots instead of bars.
This question is similar to what I want: D3.js (Wilkinson type) Dot Plot Example
I've used that code to try and accomplish what I want, but I'm stuck.
Here is an image showing what I want, based on the data for apples in my CSV file:
The code I have so far is below. When I console.log d3.range(d.apples), I see that I have an array for each day, which is what I want. However I can't figure out how to draw a circle for each spot in the array, where the x axis value would be the day # and the y axis value would be the position in the array + 1.
Any help very welcome! Thank you.
Code:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #999;
shape-rendering: crispEdges;
}
.dot {
stroke: none;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {top: 30, right: 20, bottom: 30, left: 50},
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.linear()
.range([0, width]);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10();
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 + ")");
var chart = svg.append("g")
.attr("id", "chart");
d3.csv("fruit.csv", function(error, data) {
if (error) throw error;
// console.log("data: ", data);
data.forEach(function(d) {
d.day = +d.day;
d.apples = +d.apples;
});
x.domain([0,4]);
y.domain([0,6]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Day");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("amount")
chart.selectAll("g")
.data(data)
.enter()
.append("g")
.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d, i) { return x(d.day); })
//.attr("cy", function(d,i){ return y(d.apples); })
.attr("cy", function(d,i,j){
var range = d3.range(d.apples);
console.log("apple range: ", range);
return y(range[i])
})
.style("fill", "blue")
.style("opacity", .5);
});
</script>
Data (fruit.csv):
"day","apples"
1,3
2,6
3,1
4,2
There are several ways to do what you want. I believe the most traditional approach is nesting the data and appending several groups, one for each fruit. But, for the sake of simplicity, I'd like to propose a different approach: let's change your data, flattening it, so, in each object, we have only one fruit:
originalData.forEach(function(d) {
data.push({
day: d.day,
fruit: "apples",
amount: +d.apples
});
data.push({
day: d.day,
fruit: "pears",
amount: +d.pears
});
data.push({
day: d.day,
fruit: "oranges",
amount: +d.oranges
});
});
Where originalData is the data array the way it is right now.
Doing that, your data will become something like this:
[{
"day": "1",
"fruit": "apples",
"amount": 3
}, {
"day": "1",
"fruit": "pears",
"amount": 6
}, {
"day": "1",
"fruit": "oranges",
"amount": 3
}, {
"day": "2",
"fruit": "apples",
"amount": 6
},//etc...
]
Then, we set a ordinal scale for the days:
var x = d3.scale.ordinal()
.rangePoints([0, width], 0.5);
.domain(data.map(d => d.day));
And, finally, we paint the circles:
var dots = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {
return x(d.day);
})
.attr("cy", function(d) {
return y(d.amount)
})
.style("fill", function(d) {
return color(d.fruit)
})
.style("opacity", .5);
Here is a demo (I put some titles, so you can hover over the dot to check the fruit and the amount):
var rawData = d3.csv.parse(d3.select("#csv").text());
var data = [];
rawData.forEach(function(d) {
data.push({
day: d.day,
fruit: "apples",
amount: +d.apples
});
data.push({
day: d.day,
fruit: "pears",
amount: +d.pears
});
data.push({
day: d.day,
fruit: "oranges",
amount: +d.oranges
});
});
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangePoints([0, width], 0.5);
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.category10()
.domain(["apples", "pears", "oranges"]);
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 + ")");
var chart = svg.append("g")
.attr("id", "chart");
x.domain(data.map(d => d.day));
y.domain([0, 10]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Day");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("amount");
var dots = svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cx", function(d) {
return x(d.day);
})
.attr("cy", function(d) {
return y(d.amount)
})
.style("fill", function(d) {
return color(d.fruit)
})
.style("opacity", .5)
.append("title")
.text(function(d){ return d.fruit + ": " + d.amount});
pre {
display: none;
}
body {
font: 10 px sans - serif;
}
.axis path,
.axis line {
fill: none;
stroke: #999;
shape-rendering: crispEdges;
}
.dot {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<pre id="csv">"day","apples","pears","oranges"
1,3,6,3
2,6,8,2
3,1,0,7
4,2,3,8</pre>
EDIT: After further clarifications in the comments, this is what OP wants:
Append the groups as you did, but translating them according to the day:
var groups = svg.selectAll(".groups")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + x(d.day) + ".0)";
});
And, in the dots, set the data using d3.range:
var dots = groups.selectAll("circle")
.data(function(d){ return d3.range(+d.apples + 1)})
That way, for each group, the data for the circles goes all the way up to the maximum. For example, if in a given day "apples" is 3, the data will be:
[0, 1, 2, 3]
Or, if "apples" is 6, the data will be:
[0, 1, 2, 3, 4, 5, 6]
If you don't want the leading zero, just do d3.range(1, +d.apples + 1).
Here is a demo:
var data = d3.csv.parse(d3.select("#csv").text());
var margin = {
top: 30,
right: 20,
bottom: 30,
left: 50
},
width = 360 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangePoints([0, width], 0.5);
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 + ")");
var chart = svg.append("g")
.attr("id", "chart");
x.domain(data.map(d => d.day));
y.domain([0, 7]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis)
.append("text")
.attr("class", "label")
.attr("x", width)
.attr("y", -6)
.style("text-anchor", "end")
.text("Day");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("class", "label")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("amount");
var groups = svg.selectAll(".groups")
.data(data)
.enter()
.append("g")
.attr("transform", function(d) {
return "translate(" + x(d.day) + ".0)";
});
var dots = groups.selectAll("circle")
.data(function(d) {
return d3.range(1, +d.apples + 1)
})
.enter().append("circle")
.attr("class", "dot")
.attr("r", 3.5)
.attr("cy", function(d) {
return y(d)
})
.style("fill", "blue")
.style("opacity", .5);
pre {
display: none;
}
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #999;
shape-rendering: crispEdges;
}
.dot {
stroke: none;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
<pre id="csv">"day","apples"
1,3
2,6
3,1
4,2</pre>
Related
I have created a D3 visualization which contains both a scatterplot and a line chart. They share an x-axis, but each have their own y-axis. My problem involves how to properly implement a brush and update both y-axes.
As you can see here the y-axes are correct initially and also correct again once the brush is turned off. However during 'brushing' both y-axes are set to the 'left' one. I see why this is happening when I setup my brush here:
brush = d3.svg.brush()
.x(brushFilterXScale)
.y(brushFilterTransactionsYScale)
.on('brush', brushed);
I also have a brushFilterBalanceYScale which is the scale for the axis on the right. My question is how do I pass BOTH of these scales to brush so that I can update each y-axis properly?
I don't know of a straight-forward way to do this. You can, however, reverse map it in your brush event:
function brushed() {
var extent = brush.extent(), //<-- the extent
yDomain = [extent[0][1], extent[1][1]]; //<-- the y domain of the extent
y2.domain(
[
y2Brush.invert(yBrush(yDomain[0])), //<-- take the yDomain start, get it's pixel position, then invert that back into the domain of the y2Brush
y2Brush.invert(yBrush(yDomain[1]))
]
);
Here's a working example:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
svg {
font: 10px sans-serif;
}
.line {
fill: none;
stroke: steelblue;
clip-path: url(#clip);
}
circle {
clip-path: url(#clip);
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.brush .extent {
stroke: #fff;
fill-opacity: .125;
shape-rendering: crispEdges;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var margin = {
top: 10,
right: 40,
bottom: 100,
left: 40
},
margin2 = {
top: 430,
right: 40,
bottom: 20,
left: 40
},
width = 500 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom,
height2 = 500 - margin2.top - margin2.bottom;
var x = d3.scale.linear().range([0, width])
y = d3.scale.linear().range([height, 0]),
y2 = d3.scale.linear().range([height, 0]),
xBrush = d3.scale.linear().range([0,width]),
yBrush = d3.scale.linear().range([height2, 0]),
y2Brush = d3.scale.linear().range([height2, 0]);
var xAxis = d3.svg.axis().scale(x).orient("bottom"),
yAxis = d3.svg.axis().scale(y).orient("left"),
yAxis2 = d3.svg.axis().scale(y2).orient("right"),
yAxisBrush = d3.svg.axis().scale(yBrush).orient("bottom");
xAxisBrush = d3.svg.axis().scale(xBrush).orient("bottom");
var brush = d3.svg.brush()
.x(xBrush)
.y(yBrush)
.on("brush", brushed);
var line = d3.svg.line()
.interpolate("monotone")
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return y(d.y);
});
var lineBrush = d3.svg.line()
.interpolate("monotone")
.x(function(d) {
return x(d.x);
})
.y(function(d) {
return yBrush(d.y);
});
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom);
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
var focus = svg.append("g")
.attr("class", "focus")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var context = svg.append("g")
.attr("class", "context")
.attr("transform", "translate(" + margin2.left + "," + margin2.top + ")");
var data1 = [],
data2 = [];
for (var i = 0; i < 100; i++) {
data1.push({
x: i,
y: Math.random() * 10
});
if (i % 3 === 0){
data2.push({
x: i,
y: Math.random() * 100
});
}
}
x.domain([0,100]);
y.domain([0, d3.max(data1.map(function(d) {
return d.y;
}))]);
y2.domain([0, d3.max(data2.map(function(d) {
return d.y;
}))]);
xBrush.domain(x.domain());
yBrush.domain(y.domain());
y2Brush.domain(y2.domain());
focus.append("path")
.datum(data1)
.attr("class", "line")
.attr("d", line);
var scatter = focus.append("g")
.selectAll("circle")
.data(data2);
scatter
.enter()
.append("circle")
.attr("cx", function(d){
return x(d.x);
})
.attr("cy", function(d){
return y2(d.y);
})
.attr("r", function(d){
d.r = Math.random() * 20;
return d.r;
})
.style("fill", "orange")
.style("opacity", "0.5");
focus.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
focus.append("g")
.attr("class", "y axis")
.call(yAxis);
focus.append("g")
.attr("transform", "translate(" + width + " ,0)")
.attr("class", "y2 axis")
.call(yAxis2);
context.append("path")
.datum(data1)
.attr("class", "line")
.attr("d", lineBrush);
context.append("g")
.selectAll("circle")
.data(data2)
.enter()
.append("circle")
.attr("cx", function(d){
return x(d.x);
})
.attr("cy", function(d){
return y2Brush(d.y);
})
.attr("r", function(d){
return d.r * 0.25;
})
.style("fill", "orange")
.style("opacity", "0.5");
context.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height2 + ")")
.call(xAxisBrush);
context.append("g")
.attr("class", "x brush")
.call(brush)
.selectAll("rect")
.attr("y", -6)
.attr("height", height2 + 7);
function brushed() {
var extent = brush.extent(),
yDomain = [extent[0][1], extent[1][1]];
y2.domain([y2Brush.invert(yBrush(yDomain[0])), y2Brush.invert(yBrush(yDomain[1]))]);
x.domain(brush.empty() ? xBrush.domain() : [extent[0][0], extent[1][0]]);
y.domain(brush.empty() ? yBrush.domain() : yDomain);
scatter
.attr("cx", function(d){
return x(d.x);
})
.attr("cy", function(d){
return y2(d.y);
})
focus.select(".line").attr("d", line);
focus.select(".x.axis").call(xAxis);
focus.select(".y.axis").call(yAxis);
focus.select(".y2.axis").call(yAxis2);
}
</script>
The problem:
I'm new to D3.js and I've created a bar chart that gets data from an array. I placed text on top of the bars and the text is wider than the bars themselves. I want to rotate it vertical so that the text goes through the middle of the bar.
I know .attr("transform", "rotate(180)") won't work because it rotates my text off-screen. I found an answer from Mike Bostock here, but it doesn't seem to work for me, I don't know what I'm doing wrong. He says I need to change the rotation point of origin, but I can't get it to work.
Any help is appreciated, you can find the code below:
var label = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d){
return d;
})
.attr("x", function(d, i){
xText = i*(w/data.length);
return xText;
})
.attr("y", function(d){
yText = h - yScale(d);
return yText;
})
.attr("transform", "translate(" + xText + "," + yText + ") rotate(90)")
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "11px");
Thanks!
You are setting both the x and y attributes and translated the text. With the rotate, you only want to use the translate:
.attr("transform", function(d,i){
var xText = i * (w / data.length);
var yText = h - yScale(d.v);
return "translate(" + xText + "," + yText + ") rotate(90)";
})
Also, I find your example code confusing, I was assuming that your data was an array of strings since you do:
.text(function(d) {
return d;
})
but that would make yScale(d), return NaN.
Here's an example that works nicely:
<html>
<head>
<script data-require="d3#3.5.3" data-semver="3.5.3" src="//cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.js"></script>
</head>
<body>
<script>
var w = 300,
h = 300,
data = [{
t: "Now",
v: 50
},{
t: "is",
v: 25
},{
t: "the",
v: 10
},{
t: "winter",
v: 30
}];
var svg = d3.select('body')
.append('svg')
.attr('width', w)
.attr('height', h);
var yScale = d3.scale.linear()
.range([0, h])
.domain([60, 0]);
var label = svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.t;
})
.attr("transform", function(d,i){
var xText = i * (w / data.length);
var yText = h - yScale(d.v);
return "translate(" + xText + "," + yText + ") rotate(90)";
})
.attr("fill", "black")
.attr("font-family", "sans-serif")
.attr("font-size", "11px");
</script>
</body>
</html>
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
fill: steelblue;
}
.bar:hover {
fill: brown;
}
.axis {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
var data = [{
"letter": "A",
"frequency": 0.08167
}, {
"letter": "B",
"frequency": 0.01492
}, {
"letter": "C",
"frequency": 0.02782
}, {
"letter": "D",
"frequency": 0.04253
}, {
"letter": "E",
"frequency": 0.12702
}];
var margin = {
top: 20,
right: 20,
bottom: 30,
left: 40
},
width = 500 - margin.left - margin.right,
height = 200 - 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")
.ticks(10, "%");
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.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);
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);
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
var text = svg.selectAll(".text").data(data);
text.enter()
.append("text")
.attr("class", "text")
.attr("x", function(d, i) {
return x(d.letter);
})
.attr("y", function(d) {
return y(d.frequency);
})
.text(function(d) {
return d.letter;
})
.attr("transform", function(d) {
return "rotate(90 " + x(d.letter) + "," + (y(d.frequency) - d.letter.length) + ")"
});
</script>
You can try out this, and as a base you can modify this as per your need. I hope it helps.
I'm a beginner with d3 javascript and I don't know how to change the y axis on this grouped bar chart:
http://bl.ocks.org/mbostock/3887051 Data + code can be found here
This is the code of the site & the data of the grouped bar chart:
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.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 x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis() //creating a generic axis function//
.scale(y)
.orient("left")
.tickFormat(d3.format(".2s"));
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("gender_ratio.csv", function(error, data) {
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "Perioden"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: +d[name]*1000}; });
});
x0.domain(data.map(function(d) { return d.Perioden; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.ages, function(d) { return d.value; }); })]);
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("Population");
var Perioden = svg.selectAll(".Perioden")
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.Perioden) + ",0)"; });
Perioden.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 20 + ")"; });
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
</script>
Data:
State,Under 5 Years,5 to 13 Years,14 to 17 Years,18 to 24 Years,25 to 44 Years,45 to 64 Years,65 Years and Over
CA,2704659,4499890,2159981,3853788,10604510,8819342,4114496
TX,2027307,3277946,1420518,2454721,7017731,5656528,2472223
NY,1208495,2141490,1058031,1999120,5355235,5120254,2607672
FL,1140516,1938695,925060,1607297,4782119,4746856,3187797
IL,894368,1558919,725973,1311479,3596343,3239173,1575308
PA,737462,1345341,679201,1203944,3157759,3414001,1910571
If your x-axis is regular old numeric data, you should be using a linear scale not an ordinal. Ordinal is meant for discrete values (think a, b, c or x, y, z or tom, dick, harry) while linear is meant for continuous data (think 1,2,3 or 50, 100, 150):
var x = d3.scale.linear()
.range([0, width])
.domain([1650, 1700]);
In d3 speak, range is the pixel span of your data (from min to max), while domain is the user-space span of your data (the min and max of your data values). The scale that's returned then maps your user space data to it's pixel space position.
Below is a heavily commented example of a simple d3 bar graph:
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: steelblue;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.5/d3.min.js"></script>
<script>
// sample data with x and y values
// d3 likes arrays of objects
var data = [
{
x: 1660,
y: 1
},{
x: 1670,
y: 2
},{
x: 1680,
y: 3
},{
x: 1690,
y: 4
}
];
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.linear()
.range([0,width]) // our pixel span
.domain([1650, 1700]); // our user space data span
var y = d3.scale.linear()
.range([height, 0]) // same thing as x, pixel span
.domain([0,5]); // user space space
// marry the scale to the axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
// set up our svg tag
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 + ")");
// draw x axis
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
// draw y axis
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// get a nice bar width
// it is the width of our axis divided by the number of ticks
var barWidth = (width / xAxis.ticks()[0]);
// draw the bars
var state = svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class","bar")
.attr("width", barWidth)
.attr("x", function(d) { return x(d.x) - (barWidth / 2); }) // center it on tick
.attr("y", function(d) { return y(d.y); }) // y is the top of the bar
.attr("height", function(d) { return height - y(d.y); }); // and height goes to axis
</script>
</body>
</html>
UPDATED
Problem
a) Only one of my bar charts is showing after I switched a variable var csvData in my scripts.js to the real data from the dummy data. The previous data referenced states and demographics, which has now been switched to food and their prices.
scripts.js (UPDATED, chart still not showing up)
$(function() {
$("#placeholder").remove();
var margin = {top: 60, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// X is the horizontal axis
var x0 = d3.scale.ordinal() // ordinal for non quantitative scales, like names, categories
.rangeRoundBands([0, width], .1); // Width of each individual bar?
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0]);
// Bar chart colors
var color = d3.scale.ordinal()
.range(["#001F4C", "#003D99", "#005CE6", "#0066FF", "#3385FF", "#80B2FF", "#CCE0FF", "#E6F0FF", "#E6EBFA"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left") // Where the Y axis goes, you'll want it on the left
.ticks(8)
.tickValues([0, 1, 2, 3, 4, 5, 6]);
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 + ")");
// Bar chart data
var csvData = [
{"Store":"Co-op","Broccoli":2.69,"Cauliflower":3.69,"Celery":1.89,"Strawberries":4.49,"Oranges":1.69,"Tomatoes":3.49,"Lemons":0.99, "Lettuce":0.01, "Cucumber":2},
{"Store":"Safeway","Broccoli":2.97,"Cauliflower":3.98,"Celery":1.77,"Strawberries":5.96,"Oranges":0.97,"Tomatoes":2.97,"Lemons":0.77, "Lettuce":4.97, "Cucumber":1.97},
{"Store":"Sobeys","Broccoli":3.49,"Cauliflower":3.99,"Celery":1.29,"Strawberries":3.99,"Oranges":1.99,"Tomatoes":4.99,"Lemons":1.29, "Lettuce":3.49, "Cucumber":1.99},
{"Store":"Superstore","Broccoli":2.69,"Cauliflower":2.49,"Celery":1.09,"Strawberries":2.99,"Oranges":0.99,"Tomatoes":3.99,"Lemons":0.99, "Lettuce":4.99, "Cucumber":2.49},
];
var data = csvData;
var foodNames = d3.keys(data[0]).filter(function(key) { return key !== "Store"; });
data.forEach(function(d) {
d.food = foodNames.map(function(name) { return {name: name, value: +d[name]}; });
});
x0.domain(data.map(function(d) { return d.Store; }));
x1.domain(foodNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) { return d3.max(d.food, function(d) { return d.value; }); })]);
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)") // Rotates the label on the Y axis
.attr("y", 8) // Label spacing from Y axis
.attr("dy", ".71em") // Label spacing from Y axis
.style("text-anchor", "end") // Anchor the label to the end of the Y axis
.text("Price (in dollars)"); // Changes the text on the Y or vertical axis
var store = svg.selectAll(".store") // Selects all of the data in column labelled store
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.store) + ",0)"; });
store.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("class", "stick")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 22.5 + ")"; }); // Determines spacing between items in legend
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Document</title>
<link rel="stylesheet" href="assets/css/style.css">
</head>
<body>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<script src="assets/js/scripts.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
</body>
</html>
style.css
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
/*.stick {
fill: steelblue;
}
.stick:hover {
fill: brown;
}*/
.x.axis path {
display: none;
}
$(function() {
$("#placeholder").remove();
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
// X is the horizontal axis
var x0 = d3.scale.ordinal() // ordinal for non quantitative scales, like names, categories
.rangeRoundBands([0, width], .1); // Width of each individual bar?
var x1 = d3.scale.ordinal();
var y = d3.scale.linear()
.range([height, 0])
// These are the colors
var color = d3.scale.ordinal()
// .range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
.range(["#001F4C", "#003D99", "#005CE6", "#0066FF", "#3385FF", "#80B2FF", "#CCE0FF", "#E6F0FF"]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left") // Where the Y axis goes, you'll want it on the left
// .tickFormat(d3.format(".1s"));
.ticks(6);
//.tickValues([0, 1, 2, 3, 4, 5]);
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 + ")");
// Bar chart data
var csvData = [
{"State":"Co-op","Broccoli":2027307,"Cauliflower":3277946,"Celery":1420518,"Strawberries":2454721,"Oranges":7017731,"Tomatoes":5656528,"Lemons":2472223, "Lettuce":2472223, "Cucumber":2472223},
{"State":"Safeway","Broccoli":2704659,"Cauliflower":4499890,"Celery":2159981,"Strawberries":3853788,"Oranges":10604510,"Tomatoes":8819342,"Lemons":4114496, "Lettuce":2472223, "Cucumber":2472223},
{"State":"Sobeys","Broccoli":1140516,"Cauliflower":1938695,"Celery":925060,"Strawberries":1607297,"Oranges":4782119,"Tomatoes":4746856,"Lemons":3187797, "Lettuce":2472223, "Cucumber":2472223},
{"State":"Superstore","Broccoli":1208495,"Cauliflower":2141490,"Celery":1058031,"Strawberries":1999120,"Oranges":5355235,"Tomatoes":5120254,"Lemons":2607672, "Lettuce":2472223, "Cucumber":2472223},
];
// Food Prices
// var csvData = [
// {"Store":"Sobey","Broccoli":2.69,"Cauliflower":3.69,"Celery":$1.89,"Strawberries":$4.49,"Oranges":"1.69,"Tomatoes":$3.49,"Lemons":$0.99,"Lettuce":$0.00,"Cucumber":2.00},
// {"Store":"Superstore","Broccoli":2.97,"Cauliflower":3.98,"Celery":1.77,"Strawberries":5.96,"Oranges":0.97,"Tomatoes":2.97,"Lemons":0.77,"Lettuce":4.97,"Cucumber":1.97},
// {"Store":"Safeway","Broccoli":3.49,"Cauliflower":3.99,"Celery":1.29,"Strawberries":3.99,"Oranges":1.99,"Tomatoes":4.99,"Lemons":1.29,"Lettuce":3.49,"Cucumber":1.99},
// {"Store":"Coop","Broccoli":2.69,"Cauliflower":"2.49","Celery":"1.09","Strawberries":"2.99","Oranges":"0.99","Tomatoes":"3.99","Lemons":"0.99","Lettuce":"4.99","Cucumber":"2.49"}
// ];
// AgeNames = FoodNames
// States = Stores
var data = csvData;
var ageNames = d3.keys(data[0]).filter(function(key) { return key !== "State"; });
data.forEach(function(d) {
d.ages = ageNames.map(function(name) { return {name: name, value: (+d[name])/2000000}; });
});
x0.domain(data.map(function(d) { return d.State; }));
x1.domain(ageNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.ages, function(c) {
return c.value;
});
})]);
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)") // Rotates the label on the Y axis
.attr("y", 8) // Label spacing from Y axis
.attr("dy", ".71em") // Label spacing from Y axis
.style("text-anchor", "end") // Anchor the label to the end of the Y axis
.text("Price (in dollars)"); // Changes the text on the Y or vertical axis
var state = svg.selectAll(".state") // Selects all of the data in column labelled State
.data(data)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x0(d.State) + ",0)"; });
state.selectAll("rect")
.data(function(d) { return d.ages; })
.enter().append("rect")
.attr("class", "stick")
.attr("width", x1.rangeBand())
.attr("x", function(d) { return x1(d.name); })
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.style("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(ageNames.slice().reverse())
.enter().append("g")
.attr("class", "legend")
.attr("transform", function(d, i) { return "translate(0," + i * 22.5 + ")"; });
// Number (20) determines spacing between items in legend
legend.append("rect")
.attr("x", width - 18)
.attr("width", 18)
.attr("height", 18)
.style("fill", color);
legend.append("text")
.attr("x", width - 24)
.attr("y", 9)
.attr("dy", ".35em")
.style("text-anchor", "end")
.text(function(d) { return d; });
});
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
/*.stick {
fill: steelblue;
}
.stick:hover {
fill: brown;
}*/
.x.axis path {
display: none;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
To achieve our requirement I've added one thing,i.e.
(+d[name])/2000000 while assigning the value I'm just dividing it to fit to our scale. y domain is set to very big number like near to one Crore.
In my d3.js bar chart i want the X-axis labels to be in "vertical". I'm getting the labels in "Horizontal" but the problem is some of the labels getting merged.
<!DOCTYPE html>
<style>
body {
font: 10px sans-serif;
}
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.bar {
fill: red;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="http://d3js.org/d3.v3.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 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("ClassRoom.csv",type,function(error,data){
x.domain(data.map(function(d) { return d.Name; }));
y.domain([0, d3.max(data, function(d) { return d.Marks; })]);
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("Marks");
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.Marks); })
.attr("height", function(d) { return height - y(d.Marks)});
});
function type(d) {
d.Marks = +d.Marks;
return d;
}
</script>
My CSV file is
Name,Marks
Sathesh,15
Somnath,45
Naresh,35
Venkat,25
Prabha,78
Dinesh,36
You will have to do that with the SVG-Text Labels. Assuming you create the X-Axis in this fashion:
var xAxis = svg.append("g")
.attr({
"class": "x axis",
transform: "translate(0," + h + ")"
})
.call(xAxis);
You select the Text, and apply a transformation:
xAxis.selectAll("text")
.attr({
transform: function (d) {
return "rotate(-60, 0, 0)";
}
});
You will have to adjust the 0,0 in the rotate transformation to suit your needs. Also i recommend looking into the text-anchor attribute. But this should get you started!