Related
I am a little new to D3 js.I need to align two graphs (bar and pie) side by side which will be displayed on a dashboard.When I use individual .html files for bar and pie charts,they work perfectly but the charts are getting overlapped when I combine them into a single .html file.
I have tried changing different parameters related to "svg" and corresponding x and y axes but to no avail.
There are two csv files(pie-data.csv,bar-data.csv) from where data will be picked.
Below is my code ::
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.arc text {
font: 10px sans-serif;
text-anchor: middle;
}
.arc path {
stroke: #fff;
}
</style>
<body>
<script src="//d3js.org/d3.v3.min.js"></script>
<script>
var width = 500,
height = 500,
radius = Math.min(width, height) / 2;
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var arc = d3.svg.arc()
.outerRadius(radius - 10)
.innerRadius(0);
var labelArc = d3.svg.arc()
.outerRadius(radius - 40)
.innerRadius(radius - 40);
var pie = d3.layout.pie()
.sort(null)
.value(function(d) { return d.ticket_count; });
var svg = d3.select("body").append("svg")
.attr("width", width)
.attr("height", height)
.append("g")
.attr("transform", "translate(" + width / 2 + "," + height / 2 + ")");
d3.csv("pie-data.csv", type, function(error, data) {
if (error) throw error;
var g = svg.selectAll(".arc")
.data(pie(data))
.enter().append("g")
.attr("class", "arc");
g.append("path")
.attr("d", arc)
.style("fill", function(d) { return color(d.data.ticket_meter); });
g.append("text")
.attr("transform", function(d) { return "translate(" + labelArc.centroid(d) + ")"; })
.attr("dy", ".35em")
.text(function(d) { return d.data.ticket_meter; });
});
function type(d) {
d.ticket_count = +d.ticket_count;
return d;
}
////////////////////////////////////////////////
var margin = {top: 20, right: 20, bottom: 70, left: 40},
width = 150 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
var x = d3.scale.ordinal().rangeRoundBands([0, width], .05);
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 + ")");
d3.csv("bar-data.csv", function(error, data) {
data.forEach(function(d) {
d.issue_status = d.issue_status;
d.issue_count = +d.issue_count;
});
x.domain(data.map(function(d) { return d.issue_status; }));
y.domain([0, d3.max(data, function(d) { return d.issue_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", "-.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")
svg.selectAll("bar")
.data(data)
.enter().append("rect")
.style("fill", "steelblue")
.attr("x", function(d) { return x(d.issue_status); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.issue_count); })
.attr("height", function(d) { return height - y(d.issue_count); });
});
</script>
</body>
</html>
Please suggest any pointers so that alignment can be done.Thanks in advance.
You are using the same variable twice:
var svg = d3.select("body").append("svg");
(and, besides that one, you are repeating some other variables that should be unique)
Appending the SVGs to the "body" is not the correct way, because when you append an SVG to the body the SVG is put at the end of the page, and you'll not be able to position them side by side.
A solution would be creating one div for each chart, with IDs #chart1 and #chart2 for instance, and then creating two variables:
var svg1 = d3.select("#chart1").append("svg");
And
var svg2 = d3.select("#chart2").append("svg");
Don't forget to change all other variables accordingly.
After the changes, use CSS to align the two divs side by side.
I am trying to create a grouped bar chart using D3.js. I have followed the examples provided in the D3 wiki at GitHub and have a semi working graph. However, it seems like all datapoints for a certain value get plotted at the same spot.
my data looks is a JSON array, which looks like this
[{"experiment":30385,"c":1,"ratio":0.022,"stdev":0.363,"median":0.032,"zscore":6.359},
{"experiment":30385,"c":2,"ratio":-0.02,"stdev":0.351,"median":-0.005,"zscore":-4.786},
{"experiment":30385,"c":3,"ratio":0.074,"stdev":0.339,"median":0.089,"zscore":29.036},
{"experiment":30385,"c":4,"ratio":-0.077,"stdev":0.361,"median":-0.065,"zscore":-25.704},
{"experiment":30385,"c":5,"ratio":-0.354,"stdev":0.569,"median":-0.223,"zscore":-145.625},
{"experiment":30385,"c":6,"ratio":-0.02,"stdev":0.352,"median":-0.007,"zscore":-2.545},
{"experiment":30385,"c":7,"ratio":0.018,"stdev":0.346,"median":0.036,"zscore":7.412},
{"experiment":30385,"c":8,"ratio":-0.11,"stdev":0.348,"median":-0.096,"zscore":-37.69},
{"experiment":30385,"c":9,"ratio":-0.012,"stdev":0.357,"median":0.008,"zscore":-4.394},
{"experiment":30385,"c":10,"ratio":-0.054,"stdev":0.366,"median":-0.036,"zscore":-14.158},
{"experiment":30385,"c":11,"ratio":-0.071,"stdev":0.344,"median":-0.044,"zscore":-21.4},
{"experiment":30385,"c":12,"ratio":-0.01,"stdev":0.352,"median":0.002,"zscore":-1.467},
{"experiment":30385,"c":13,"ratio":-0.03,"stdev":0.366,"median":-0.014,"zscore":-2.375},
{"experiment":30385,"c":14,"ratio":-0.039,"stdev":0.339,"median":-0.025,"zscore":-8.816},
{"experiment":30385,"c":15,"ratio":-0.02,"stdev":0.357,"median":0.0065,"zscore":-4.2},
{"experiment":30385,"c":16,"ratio":0.449,"stdev":0.439,"median":0.4215,"zscore":69.859},
{"experiment":30385,"c":17,"ratio":-0.028,"stdev":0.367,"median":-0.007,"zscore":-4.9},
{"experiment":30385,"c":18,"ratio":-0.071,"stdev":0.357,"median":-0.061,"zscore":-17.268},
{"experiment":30385,"c":19,"ratio":0.143,"stdev":0.356,"median":0.1415,"zscore":13.961},
{"experiment":30385,"c":20,"ratio":0.022,"stdev":0.349,"median":0.0405,"zscore":3.462},
{"experiment":30385,"c":21,"ratio":-0.076,"stdev":0.335,"median":-0.086,"zscore":-11.368},
{"experiment":30385,"c":22,"ratio":0.038,"stdev":0.355,"median":0.07,"zscore":3.152},
{"experiment":30385,"c":23,"ratio":0,"stdev":0,"median":0,"zscore":3.152},
{"experiment":30385,"c":24,"ratio":0,"stdev":0,"median":0,"zscore":3.152},
{"experiment":30384,"c":1,"ratio":-0.058,"stdev":0.403,"median":-0.042,"zscore":-14.154},
{"experiment":30384,"c":2,"ratio":-1.017,"stdev":0.418,"median":-0.982,"zscore":-360.857},
{"experiment":30384,"c":3,"ratio":-0.094,"stdev":0.417,"median":-0.074,"zscore":-30.964},
{"experiment":30384,"c":4,"ratio":-0.155,"stdev":0.397,"median":-0.157,"zscore":-54.593},
{"experiment":30384,"c":5,"ratio":-0.024,"stdev":0.381,"median":-0.001,"zscore":-8.125},
{"experiment":30384,"c":6,"ratio":0.013,"stdev":0.37,"median":0.0245,"zscore":7.455},
{"experiment":30384,"c":7,"ratio":-0.2,"stdev":0.434,"median":-0.171,"zscore":-56.706},
{"experiment":30384,"c":8,"ratio":-0.017,"stdev":0.367,"median":0.003,"zscore":-5.621},
{"experiment":30384,"c":9,"ratio":0.025,"stdev":0.365,"median":0.044,"zscore":6.818},
{"experiment":30384,"c":10,"ratio":-0.168,"stdev":0.422,"median":-0.121,"zscore":-44.158},
{"experiment":30384,"c":11,"ratio":-0.073,"stdev":0.382,"median":-0.056,"zscore":-22.067},
{"experiment":30384,"c":12,"ratio":0.002,"stdev":0.379,"median":0.019,"zscore":2.533},
{"experiment":30384,"c":13,"ratio":-0.054,"stdev":0.39,"median":-0.0295,"zscore":-8.375},
{"experiment":30384,"c":14,"ratio":0.019,"stdev":0.376,"median":0.025,"zscore":6.447},
{"experiment":30384,"c":15,"ratio":-0.054,"stdev":0.421,"median":-0.0265,"zscore":-11},
{"experiment":30384,"c":16,"ratio":0.055,"stdev":0.375,"median":0.0695,"zscore":8.297},
{"experiment":30384,"c":17,"ratio":0.024,"stdev":0.394,"median":0.054,"zscore":3.767},
{"experiment":30384,"c":18,"ratio":-0.049,"stdev":0.36,"median":-0.018,"zscore":-11.902},
{"experiment":30384,"c":19,"ratio":0.095,"stdev":0.37,"median":0.1135,"zscore":10.24},
{"experiment":30384,"c":20,"ratio":0.157,"stdev":0.343,"median":0.174,"zscore":29.423},
{"experiment":30384,"c":21,"ratio":-0.091,"stdev":0.407,"median":-0.067,"zscore":-14},
{"experiment":30384,"c":22,"ratio":0.071,"stdev":0.381,"median":0.104,"zscore":7.329},
{"experiment":30384,"c":23,"ratio":0,"stdev":0,"median":0,"zscore":7.329},
{"experiment":30384,"c":24,"ratio":0,"stdev":0,"median":0,"zscore":7.329}]
The data contains an experiment id, chromosome number, ratio and some satistics. The array can contain data from various experiments, which all have a different id.
my js code currently looks like this:
<script>
function unique(list) {
var result = [];
$.each(list, function(i, e) {
if ($.inArray(e, result) == -1) result.push(e);
});
return result;
}
var margin = {top: 50, right: 50, bottom: 50, left: 50};
var width = 1000 - margin.left - margin.right;
var height = 500 - margin.top - margin.bottom;
var threshold={upper:0.1,lower:-0.1};
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()
.scale(y)
.orient("left");
var svg = d3.select("#svg").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.json("{{settings.Base_url}}/templates/addons/data.json", function(error, data) {
if (error) throw error;
var expNames =unique(data.map(function(d) { return d.experiment; }));
x0.domain(data.map(function(d) { return d.c; }));
x1.domain(expNames).rangeRoundBands([0, x0.rangeBand()]);
y.domain([-1.5,1.5]);
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", -50)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Average Ratio/Chromosome");
var chr = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x0(d.c) + ",0)"; });
chr.selectAll("rect")
.data(data)
.enter().append("rect")
.attr("x", function(d) {return x1(d.experiment);})
.attr("y", function(d) { return y(Math.max(0, d.ratio)); })
.attr("height", function(d) { return Math.abs(y(d.ratio)-y(0)); })
.attr("width", x1.rangeBand())
.style("fill", function(d) { return color(d.experiment); })
.style({"opacity":0.6,"stroke-width":"2"})
.text("test");
var legend = svg.selectAll(".legend")
.data(expNames.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>
which results in a graph like this:
any ideas? I have a general idea where the error is, but cant seem to find a solution.
Note sure if I understand the question properly, but here's what I get:
You bind the data to the groups which you transform in x direction.
Afterwards you want to display two bars (for each experiment) in each group (c value)
If that's the case, you don't need to bind the data again for the bars, so it's just:
var chr = svg.selectAll(".bar")
.data(data)
.enter().append("g")
.attr("class", "bar")
.attr("transform", function (d) {
return "translate(" + x0(d.c) + ",0)";
});
chr.append("rect")
.attr("x", function (d) {
return x1(d.experiment);
})
...etc
See fiddle
Does that help?
The code below is working perfectly fine but I want to use another dataset. A dataset where the attributes are arrays themselves. They are structured likes this: [year, population, man, woman]. The year will be the X-axis and the other three will be displayed as bars next to each other. How can I make an triple bar chart? Where to start?
<script type="text/javascript">
//Width and height
var w = 500;
var h = 250;
var barPadding = 5;
//Dataset
var dataset = [10027, 10200, 10328, 10436, 10551, 10680, 10822, 10957, 11096, 11278,
11417, 11556, 11721, 11890 ];
//The other dataset
//[year, population, man, woman]
//var dataset = [[1950, 10027, 4998, 5029], [1950, 10027, 4998, 5029], [1950, 10027, 4998, 5029], etc];
//Scale
var x = d3.scale.ordinal().range([0, w])
var y = d3.scale.linear().range([h, 0]);
// Axis
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom")
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.ticks(10);
//Create SVG element
var svg = d3.select("body")
.append("svg")
.attr("width", w)
.attr("height", h);
svg.selectAll("rect")
.data(dataset)
.enter()
.append("rect")
.attr("x", function(d, i) {
return i * (w / dataset.length);
})
.attr("y", function(d) {
return h - (d / 50);
})
.attr("width", w / dataset.length - barPadding)
.attr("height", function(d) {
return d / 50;
})
.attr("fill", function(d) {
return "rgb(102, 0, 51)";
});
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + h + ")")
.call(xAxis)
.style("text-anchor", "end")
.text("Years");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", "0.5em")
.style("text-anchor", "end")
.text("Population in millions");
</script>
(little question: why doesn't my x-axis appear?)
Thanks in advance
Towards your little question: your x- and y-axis get rendered, but outside of the svg visible space. The common way for dealing with this is to add a margin inside the svg and render the chart with the width and height inside of the margin. This is described by the author of D3 as conventional margin
I have created a snippet with what you call a tripple bar chart.
This code is the grouped barchart of Bostock applied to your dataset.
I changed the dataset a little bit, so that each year is an object with the properties population, men, women and year. With this setup it is easer to process the values.
The idea behind the code for the grouped bar chart is to add a group element for each object in your dataset and render the bars inside this group.
Therefore you bind the dataset to the year groups.
Inside the years selection, each element is bound to the corresponding object from your dataset array.
If this all doesn't make that much sense to you, maybe you should read the Let's make a Bar Chart tutorials by Bostock. They give a great overview on the basic steps ;)
var dataset = [{year:1950, population:10000, men:4500, women:5500}, {year:1951, population:10200, men:5000, women:6200}, {year:1952, population:11000, men:6000, women:5000}, {year:1953, population:12000, men:5900, women:6100}];
// setup conventional margin, see http://bl.ocks.org/mbostock/3019563
var margin = {top: 20, right: 30, bottom: 30, left: 60},
width = 700 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
// accessor functions for the 4 values
var year = function(d) { return d.year; };
var population = function(d) { return d.population; };
var men = function(d) { return d.men; };
var women = function(d) { return d.women; };
var valueObject = function(d) { return [{name:"population", value:population(d)},{name:"men", value: men(d)}, {name: "women", value: women(d)}]; };
var color = d3.scale.ordinal()
.domain(["population","men","women"])
.range(["#31a354","#3182bd","#e6550d"]);
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .2)
.domain(dataset.map(year)); // use the year for the xAxis
var x1 = d3.scale.ordinal()
.domain(["population","men","women"])
.rangeRoundBands([0, x0.rangeBand()]);
var y = d3.scale.linear()
.range([height, 0])
.domain([0, d3.max(dataset.map(population))]); // use the population for calculating the maximum value
var xAxis = d3.svg.axis()
.scale(x0)
.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 + ")");
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 years = svg.selectAll(".year")
.data(dataset)
.enter()
.append("g")
.attr("class", "year")
.attr("transform", function(d) { return "translate(" + x0(year(d)) + ",0)"; });
years.selectAll("rect")
.data(valueObject)
.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); })
.attr("fill", function(d) { return color(d.name); });
var legend = svg.selectAll(".legend")
.data(["population","men","women"])
.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; });
.axis path,
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>
Fiddle Example
I have been following these two examples (1)(2) to create small multiple grouped bar charts on the same page. Here's a JSON data example:
var data = [
{"name":"AA","sales_price":20,"retail_price":25},
{"name":"BB","sales_price":30,"retail_price":45},
{"name":"CC","sales_price":10,"retail_price":55},
{"name":"DD","sales_price":10,"retail_price":25},
{"name":"EE","sales_price":13,"retail_price":20},
{"name":"GG","sales_price":13,"retail_price":15},
];
I've managed to get the bar values to show up correctly in each chart, but the X domain and Y domain values aren't right. I couldn't figure out how to bind each data row's sales_price and retail_price to the axises instead of the entire JSON data. I guess there's a problem with this block of code:
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {name: name, value: +d[name]};
});
});
x0.domain(data.map(function(d) { console.log(d); return d.name; }));
x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.compare, function(d) {
return d.value; });
})]);
How can I make the domains return each row's values for each grouped bar charts?
Full Code:
function multi_bars(el){
var margin = {top: 45, right:20, bottom: 20, left: 50},
width = 350 - margin.left - margin.right,
height = 250 - margin.top - margin.bottom;
var x0 = d3.scale.ordinal()
.rangeRoundBands([0, width], .1);
var x1 = d3.scale.ordinal();
var color = d3.scale.ordinal()
.range(["#98abc5", "#8a89a6", "#7b6888", "#6b486b", "#a05d56", "#d0743c", "#ff8c00"]);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x0)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left");
var field_name = ['retail_price','sales_price'];
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {name: name, value: +d[name]};
});
});
x0.domain(data.map(function(d) { console.log(d); return d.name; }));
x1.domain(field_name).rangeRoundBands([0, x0.rangeBand()]);
y.domain([0, d3.max(data, function(d) {
return d3.max(d.compare, function(d) {
return d.value; });
})]);
var svg = d3.select(el).selectAll("svg")
.data(data)
.enter().append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
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("Price");
// Accessing nested data: https://groups.google.com/forum/#!topic/d3-js/kummm9mS4EA
// data(function(d) {return d.values;})
// this will dereference the values for nested data for each group
svg.selectAll(".bar")
.data(function(d) {return d.compare;})
.enter()
.append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", color)
var legend = svg.selectAll(".legend")
.data(field_name.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; });
function type(d) {
d.percent = +d.percent;
return d;
}
}
multi_bars(".container");
Your setting up of x0, x1 and y is fine.
Later when you manipulate the DOM is where your references to the data don't work.
I have done two things: First I change your first block, so you create just one svg instead of
var svg = d3.select(el).selectAll("svg")
.data(data)
.enter().append("svg:svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
Later I just followed the example of http://bl.ocks.org/mbostock/3887051 and made the changes accordingly.
The result is here:
http://jsfiddle.net/ee2todev/g61f93gx/
If you want to have separate charts for each group as in your original fiddle you just have to translate each bar with the x0 scale. Just two adjustments have to be made:
a) you have to add the group name to the d.compare so it is accessible from the corresponding data in the bar selection
data.forEach(function(d) {
d.compare = field_name.map(function(name) {
return {group: d.name, name: name, value: +d[name]};
});
});
b) In the bar selection you have to translate each group accordingly:
svg.selectAll(".bar")
.data(function(d) {return d.compare;})
.enter()
.append("rect")
.attr("class", "bar")
.attr("transform", function(d) { return "translate(" + x0(d.group) + ",0)"; })
.attr("x", function(d) { console.log("x: "+d.value); return x1(d.name); })
.attr("width", x1.rangeBand())
.attr("y", function(d) { return y(d.value); })
.attr("height", function(d) { return height - y(d.value); })
.attr("fill", color);
The complete fiddle is here: http://jsfiddle.net/ee2todev/en8sr5m4/
Two more notes:
1) I just slightly changed your code. I highly recommend using meaningful and intuitive variable/object names. This is to me the most effective way to minimize errors. This might have been the reason you got confused. So I would rename the d.compare properties, e.g. {groupName: d.name, priceType: name, value: +d[name]}. As of now, you switched the meaning of name since name suddenly refers to the price type not the grouping name as in the original data!
2) This is a nice example of selection of selections. See also http://bost.ocks.org/mike/nest/
The first selectAll selection (the svg variable) contains an Array[6] with the objects. The second selection:
svg.selectAll(".bar").data(function(d) {return d.compare;})
iterates for each element of the svg data over an Array[2] containing an object with the price type and the value. There I added the group name.
I am creating a bar chart with d3.js from data stored in tsv file. I want to insert a text in each bar. How I can do?
I have tried even this solution, but doesn't work.
Here is the code of my function:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 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 + ")");
function visualize(file){
d3.tsv(file, function(error, data) {
data.forEach(function(d) {
d.weight = +d.weight;
});
x.domain(data.map(function(d) { return d.concept; }));
y.domain([0, d3.max(data, function(d) { return d.weight; })]);
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("weight");
svg.selectAll(".bar")
.data(data)
.enter().append("rect").style("fill", function (d){return d.color;})
.attr("class", "bar")
.attr("x", function(d) { return x(d.concept); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.weight); })
.attr("height", function(d) { return height - y(d.weight); });
svg.selectAll("text")
.data(data)
.enter()
.append("text")
.text(function(d) {
return d.concept;
})
.attr("text-anchor", "middle")
.attr("x", x)
.attr("y",y)
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white");
});
}
All my code with the files tsv are here: full code
AmeliaBR is right as usual, and I am only putting this answer because while I was working on it, I saw myself changing the code so that it really makes use of the Enter, Update, Exit selection paradigm. I have made quite a few changes in that regard. Here is the code, FWIW:
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 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 + ")");
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("weight");
var g = svg.append("g");
function update(file){
d3.tsv(file, function(error, data) {
data.forEach(function(d) {
d.weight = +d.weight;
});
x.domain(data.map(function(d) { return d.concept; }));
y.domain([0, d3.max(data, function(d) { return d.weight; })]);
var bar = g.selectAll(".bar")
.data(data);
bar.enter()
.append("rect")
.attr("class","bar");
bar
.style("fill", function (d){return d.color;})
.attr("class", "bar")
.attr("x", function(d) { return x(d.concept); })
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.weight); })
.attr("height", function(d) { return height - y(d.weight); });
bar.exit().remove();
var text = g.selectAll(".text")
.data(data);
text.enter()
.append("text")
.attr("class","text");
text
.attr("text-anchor", "right")
.attr("x", function(d) { return x(d.concept); })
.attr("y", function(d) { return y(d.weight) + 22;})
.attr("font-family", "sans-serif")
.attr("font-size", "11px")
.attr("fill", "white")
.text(function(d) {
return d.concept;
});
text.exit().remove();
});
}
And you call it like by doing update("data16.tsv") and then update("data15.tsv").
When you draw an axis, it creates separate <text> elements for each label inside the axis group inside the SVG. So if you then try to select all the <text> elements in the SVG, you're going to select all your axis labels. If you have more axis labels than data for text elements, your enter() selection will be empty and nothing will happen.
To be sure you're only selecting the correct <text> elements, give your text labels a class to distinguish them from the axis labels. And then use that class to narrow-down your selector:
svg.selectAll("text.bar-label")
.data(data)
.enter()
.append("text")
.attr("class", "bar-label")