Related
I am trying to create an interactive line chart with mouseover function. When you place your mouse over the line you can see the value of the GDP and the year at that certain point. But for some reason,I am getting the below error but I am not sure why it is happening as i am passing the right format data-set into the function.
Uncaught TypeError: Cannot read property 'year' of undefined
My code is below:
HTML
<!DOCTYPE html>
<htmL>
<meta charset="utf-8">
<head>
<style>
.line-chart2{
margin-top:200px;
margin-left:100px;
border:1px solid lightgray;
}
circle {
fill: steelblue;
}
body {
font: 12px Arial;}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
div.tooltip {
position: absolute;
text-align: center;
width: 80px;
height: 64px;
padding: 2px;
font: 14px sans-serif;
color: black;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: #F1F3F3;
stroke: #6F257F;
stroke-width: 5px;
}
.hover-line {
stroke: #6F257F;
stroke-width: 2px;
stroke-dasharray: 3,3;
}
</style>
</head>
<body>
<svg class='line-chart2'></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="math.js" type="text/javascript"></script>
<script src="./regression.js"></script>
</body>
</htmL>
Javascript
var gdp=[ 387.65, 410.32, 415.73, 452.69, 462.14,
478.96, 508.06, 599.59, 699.68, 808.90,
920.31, 1201.11, 1186.95, 1323.94, 1656.61,
1823.04, 1827.63, 1856.72, 2039.12, 2102.39,
2274.22, 2600.81]; //y or GDP of India
var years=['1996','1997','1998','1999','2000','2001','2002','2003','2004','2005','2006','2007','2008','2009','2010','2011','2012','2013','2014','2015','2016','2017'];
var data_gdp=[]
for(i=0;i<forexp.length;i++){
data_gdp.push({year:years[i],value:gdp[i]})
}
function drawChart_gdp(data,class_name) {
var svgWidth = 1200, svgHeight = 400;
var margin = { top: 60, right: 60, bottom: 60, left: 60 };
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var svg = d3.select(class_name)
.attr("width", svgWidth)
.attr("height", svgHeight);
var bisectDate= d3.bisector(function(d) { return d.year; }).left;
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")"
);
var x = d3.scaleTime().range([0,width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var line = d3.line()
.x(function(d) { return x(new Date(parseInt(d.year),0))})
.y(function(d) { return y(d.value)})
x.domain(d3.extent(data, function (d) { return new Date(parseInt(d.year),0); }));
y.domain(d3.extent(data, function(d) { return d.value }));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.append("text")
.attr("fill", "#000")
.text("Year")
.attr("dy", "1.90em")
.attr("y", 5)
.attr("x",500)
.attr("font-size", "20px")
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", -80)
.attr("x",-55)
.attr("dy", "1.90em")
.attr("text-anchor", "center")
.attr("font-size", "20px")
.text("GDP ($)")
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
var focus = g.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", height);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("r", 7.5);
focus.append("text")
.attr("x", 15)
.attr("dy", ".31em");
svg.append("rect")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() { focus.style("display", null); })
.on("mouseout", function() { focus.style("display", "none"); })
.on("mousemove", function() { //problem in this function
var x0 = x.invert(d3.mouse(this)[0]),
i = bisectDate(data, x0, 1),
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");
focus.select("text").text(function() { return d.value; });
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
});
}
drawChart_gdp(data_gdp,'.line-chart2');
Since x is a time scale, the returned value of...
var x0 = x.invert(d3.mouse(this)[0])
... is a date object, like this:
Fri Nov 29 1996 19:56:00
However, in your data, you have strings:
[{year: "1996", value: 387.65}, {year: "1997", value: 410.32} etc...];
The solution is quite straightforward, just format the dates:
var x0 = d3.timeFormat("%Y")(x.invert(d3.mouse(this)[0]))
Here is your code with that change:
var gdp = [387.65, 410.32, 415.73, 452.69, 462.14,
478.96, 508.06, 599.59, 699.68, 808.90,
920.31, 1201.11, 1186.95, 1323.94, 1656.61,
1823.04, 1827.63, 1856.72, 2039.12, 2102.39,
2274.22, 2600.81
]; //y or GDP of India
var years = ['1996', '1997', '1998', '1999', '2000', '2001', '2002', '2003', '2004', '2005', '2006', '2007', '2008', '2009', '2010', '2011', '2012', '2013', '2014', '2015', '2016', '2017'];
var data_gdp = []
for (i = 0; i < years.length; i++) {
data_gdp.push({
year: years[i],
value: gdp[i]
})
}
function drawChart_gdp(data, class_name) {
var svgWidth = 1200,
svgHeight = 400;
var margin = {
top: 60,
right: 60,
bottom: 60,
left: 60
};
var width = svgWidth - margin.left - margin.right;
var height = svgHeight - margin.top - margin.bottom;
var svg = d3.select(class_name)
.attr("width", svgWidth)
.attr("height", svgHeight);
var bisectDate = d3.bisector(function(d) {
return d.year;
}).left;
var g = svg.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")"
);
var x = d3.scaleTime().range([0, width]);
var y = d3.scaleLinear().rangeRound([height, 0]);
var line = d3.line()
.x(function(d) {
return x(new Date(parseInt(d.year), 0))
})
.y(function(d) {
return y(d.value)
})
x.domain(d3.extent(data, function(d) {
return new Date(parseInt(d.year), 0);
}));
y.domain(d3.extent(data, function(d) {
return d.value
}));
g.append("g")
.attr("transform", "translate(0," + height + ")")
.call(d3.axisBottom(x))
.append("text")
.attr("fill", "#000")
.text("Year")
.attr("dy", "1.90em")
.attr("y", 5)
.attr("x", 500)
.attr("font-size", "20px")
.select(".domain")
.remove();
g.append("g")
.call(d3.axisLeft(y))
.append("text")
.attr("fill", "#000")
.attr("transform", "rotate(-90)")
.attr("y", -80)
.attr("x", -55)
.attr("dy", "1.90em")
.attr("text-anchor", "center")
.attr("font-size", "20px")
.text("GDP ($)")
g.append("path")
.datum(data)
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-linejoin", "round")
.attr("stroke-linecap", "round")
.attr("stroke-width", 1.5)
.attr("d", line);
var focus = g.append("g")
.attr("class", "focus")
.style("display", "none");
focus.append("line")
.attr("class", "x-hover-line hover-line")
.attr("y1", 0)
.attr("y2", height);
focus.append("line")
.attr("class", "y-hover-line hover-line")
.attr("x1", width)
.attr("x2", width);
focus.append("circle")
.attr("r", 7.5);
focus.append("text")
.attr("x", 15)
.attr("dy", ".31em");
svg.append("rect")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "overlay")
.attr("width", width)
.attr("height", height)
.on("mouseover", function() {
focus.style("display", null);
})
.on("mouseout", function() {
focus.style("display", "none");
})
.on("mousemove", function() { //problem in this function
var x0 = d3.timeFormat("%Y")(x.invert(d3.mouse(this)[0])),
i = bisectDate(data, x0, 1);
d0 = data[i - 1],
d1 = data[i],
d = x0 - d0.year > d1.year - x0 ? d1 : d0;
focus.attr("transform", "translate(" + x(d.year) + "," + y(d.value) + ")");
focus.select("text").text(function() {
return d.value;
});
focus.select(".x-hover-line").attr("y2", height - y(d.value));
focus.select(".y-hover-line").attr("x2", width + width);
});
}
drawChart_gdp(data_gdp, '.line-chart2');
<head>
<style>
circle {
fill: steelblue;
}
body {
font: 12px Arial;
}
path {
stroke: steelblue;
stroke-width: 2;
fill: none;
}
.axis path,
.axis line {
fill: none;
stroke: grey;
stroke-width: 1;
shape-rendering: crispEdges;
}
div.tooltip {
position: absolute;
text-align: center;
width: 80px;
height: 64px;
padding: 2px;
font: 14px sans-serif;
color: black;
background: lightsteelblue;
border: 0px;
border-radius: 8px;
pointer-events: none;
}
.overlay {
fill: none;
pointer-events: all;
}
.focus circle {
fill: #F1F3F3;
stroke: #6F257F;
stroke-width: 5px;
}
.hover-line {
stroke: #6F257F;
stroke-width: 2px;
stroke-dasharray: 3, 3;
}
</style>
</head>
<body>
<svg class='line-chart2'></svg>
<script src="https://d3js.org/d3.v4.min.js"></script>
</body>
I have a JSON query and I am presenting the data using a D3 chart. It seems that the grouping (Countries) does not work. We can some more than one line for each country (e.g. Canada). Part of the code:
data.forEach(function (a) {
groups[a.value] = groups[a.value] || [];
groups[a.value].push(a);
});
result = Object.keys(groups).reduce(function (r, k) {
return r.concat(groups[k]);
}, []);
Ideally, I want to make this work by changing the script. A second solution is to change the format of the JSON (manipulation). I am trying to focus on the first solution... The snippet is here:
var json_data= {"headers":["Dimension 1","Metric 1","Metric 2"],"rows":[["Australia",174,23],["Canada",502,17],["France",242,37],["Germany",102,42],["United Kingdom",126,44],["United States",1246,47],["Australia",680,80],["Canada",1241,66],["Canada",1241,66],["France",150,30],["Germany",244,22],["United Kingdom",501,9],["United States",4960,41],["Australia",9,8],["Canada",3655,70],["France",1654,95],["Germany",1190,36],["United Kingdom",1222,38],["United States",7941,53],["Australia",6829,56],["Canada",1664,75],["France",2995,88],["Germany",1487,100],["United Kingdom",9245,29],["United States",9008,66],["Australia",9376,7],["Canada",1531,31],["France",5421,22],["Germany",6975,41],["United Kingdom",4320,100],["United States",3200,41],["Australia",6688,41],["Canada",699,42],["France",5403,70],["Germany",6377,49],["United Kingdom",2471,14],["United States",6650,4],["Australia",865,70],["Canada",511,20],["France",981,36],["Germany",57,10],["United Kingdom",675,38],["United States",40,72],["Australia",400,63],["Canada",971,90],["France",357,93],["Germany",820,40],["United Kingdom",520,32],["United States",448,24],["Australia",513,40],["Canada",977,8],["France",118,84],["Germany",161,29],["United Kingdom",239,89],["United States",327,79]]};
var headers = json_data.headers;
var platform_data = json_data.rows;
var data = [];
var metric = 0;
for (var i in platform_data)
{
var dimension = platform_data[i][0];
metric = (platform_data[i][1]).toFixed(0);
object = { label: dimension, value: metric};
data.push(object);
}
//Sorting
var data = data,
groups = Object.create(null),
result = [];
data.forEach(function (a) {
groups[a.value] = groups[a.value] || [];
groups[a.value].push(a);
});
result = Object.keys(groups).reduce(function (r, k) {
return r.concat(groups[k]);
}, []);
//Descending - Reverse JSON order
var objAssetSelection = result.reverse();
data = objAssetSelection;
//D3 Code
var div = d3.select("body").append("div").attr("class", "toolTip");
var axisMargin = 20,
margin = 40,
valueMargin = 4,
width = parseInt(d3.select('body').style('width'), 10),
height = parseInt(d3.select('body').style('height'), 10),
barHeight = (height-axisMargin-margin*2)* 0.4/data.length,
barPadding = (height-axisMargin-margin*2)*0.6/data.length,
data, bar, svg, scale, xAxis, labelWidth = 0;
max = d3.max(data, function(d) { return d.value; });
svg = d3.select('body')
.append("svg")
.attr("width", width)
.attr("height", height);
bar = svg.selectAll("g")
.data(data)
.enter()
.append("g");
bar.attr("class", "bar")
.attr("cx",0)
.attr("transform", function(d, i) {
return "translate(" + margin + "," + (i * (barHeight + barPadding) + barPadding) + ")";
});
bar.append("text")
.attr("class", "label")
.attr("y", barHeight / 2)
.attr("dy", ".35em") //vertical align middle
.text(function(d){
return d.label;
}).each(function() {
labelWidth = Math.ceil(Math.max(labelWidth, this.getBBox().width));
});
scale = d3.scale.linear()
.domain([0, max])
.range([0, width - margin*2 - labelWidth]);
xAxis = d3.svg.axis()
.scale(scale)
.tickSize(-height + 2*margin + axisMargin)
.orient("bottom");
bar.append("rect")
.attr("transform", "translate("+labelWidth+", 0)")
.attr("height", barHeight)
.attr("width", function(d){
return scale(d.value);
});
bar.append("text")
.attr("class", "value")
.attr("y", barHeight / 2)
.attr("dx", -valueMargin + labelWidth) //margin right
.attr("dy", ".35em") //vertical align middle
.attr("text-anchor", "end")
.text(function(d){
return (d.value);
})
.attr("x", function(d){
var width = this.getBBox().width;
return Math.max(width + valueMargin, scale(d.value));
});
bar
.on("mousemove", function(d){
div.style("left", d3.event.pageX+10+"px");
div.style("top", d3.event.pageY-25+"px");
div.style("display", "inline-block");
div.html((d.label)+"<br>"+(d.value));
});
bar
.on("mouseout", function(d){
div.style("display", "none");
});
svg.insert("g",":first-child")
.attr("class", "axisHorizontal")
.attr("transform", "translate(" + (margin + labelWidth) + ","+ (height - axisMargin - margin)+")")
.call(xAxis);
#import url('https://fonts.googleapis.com/css?family=Roboto');
body {
font-family: "Roboto"!important;
width: 100%;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
position: center;
}
.toolTip {
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
text {
font: 10px sans-serif;
color: white;
}
text.value {
font-size: 100%;
fill: white;
}
.axisHorizontal path{
fill: none;
}
.axisHorizontal .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
The grouping code is incorrect. If you console log data after your grouping data, it's the same non-grouped data.
For better grouping, you can take a look at d3.nest. Here's the grouping code using d3.nest (grouping data based on label and summing up the values using d3.sum (d3.sum):
var nested_data = d3.nest()
.key(function(d) { return d.label; })
.rollup(function (d) {
return d3.sum(d, function(v) { return +v.value;})
}).entries(data);
And to not disturb your rest of the code, I'm mapping this nested_data to the original format by this:
data = nested_data.map(function(row) {
return {label: row.key, value: row.values};
});
EDIT: Adding sorting code
data.sort(function(a, b) { return a.value > b.value ? -1 : a.value === b.value ? 0 : 1; });
Using the above nesting/grouping, here's a code snippet:
var json_data= {"headers":["Dimension 1","Metric 1","Metric 2"],"rows":[["Australia",174,23],["Canada",502,17],["France",242,37],["Germany",102,42],["United Kingdom",126,44],["United States",1246,47],["Australia",680,80],["Canada",1241,66],["Canada",1241,66],["France",150,30],["Germany",244,22],["United Kingdom",501,9],["United States",4960,41],["Australia",9,8],["Canada",3655,70],["France",1654,95],["Germany",1190,36],["United Kingdom",1222,38],["United States",7941,53],["Australia",6829,56],["Canada",1664,75],["France",2995,88],["Germany",1487,100],["United Kingdom",9245,29],["United States",9008,66],["Australia",9376,7],["Canada",1531,31],["France",5421,22],["Germany",6975,41],["United Kingdom",4320,100],["United States",3200,41],["Australia",6688,41],["Canada",699,42],["France",5403,70],["Germany",6377,49],["United Kingdom",2471,14],["United States",6650,4],["Australia",865,70],["Canada",511,20],["France",981,36],["Germany",57,10],["United Kingdom",675,38],["United States",40,72],["Australia",400,63],["Canada",971,90],["France",357,93],["Germany",820,40],["United Kingdom",520,32],["United States",448,24],["Australia",513,40],["Canada",977,8],["France",118,84],["Germany",161,29],["United Kingdom",239,89],["United States",327,79]]};
var headers = json_data.headers;
var platform_data = json_data.rows;
var data = [];
var metric = 0;
for (var i in platform_data)
{
var dimension = platform_data[i][0];
metric = (platform_data[i][1]).toFixed(0);
object = { label: dimension, value: metric};
data.push(object);
}
//Sorting
var data = data,
groups = Object.create(null),
result = [];
/* data.forEach(function (a) {
groups[a.value] = groups[a.value] || [];
groups[a.value].push(a);
});
result = Object.keys(groups).reduce(function (r, k) {
return r.concat(groups[k]);
}, []); */
var nested_data = d3.nest()
.key(function(d) { return d.label; })
.rollup(function (d) {
return d3.sum(d, function(v) { return +v.value;})
}).entries(data);
data = nested_data.map(function(row) {
return {label: row.key, value: row.values};
}).sort(function(a, b) { return a.value > b.value ? -1 : a.value === b.value ? 0 : 1; });
//Descending - Reverse JSON order
/* var objAssetSelection = result.reverse();
data = objAssetSelection;
*/
//D3 Code
var div = d3.select("body").append("div").attr("class", "toolTip");
var axisMargin = 20,
margin = 40,
valueMargin = 4,
width = parseInt(d3.select('body').style('width'), 10),
height = parseInt(d3.select('body').style('height'), 10),
barHeight = (height-axisMargin-margin*2)* 0.4/data.length,
barPadding = (height-axisMargin-margin*2)*0.6/data.length,
data, bar, svg, scale, xAxis, labelWidth = 0;
max = d3.max(data, function(d) { return d.value; });
svg = d3.select('body')
.append("svg")
.attr("width", width)
.attr("height", height);
bar = svg.selectAll("g")
.data(data)
.enter()
.append("g");
bar.attr("class", "bar")
.attr("cx",0)
.attr("transform", function(d, i) {
return "translate(" + margin + "," + (i * (barHeight + barPadding) + barPadding) + ")";
});
bar.append("text")
.attr("class", "label")
.attr("y", barHeight / 2)
.attr("dy", ".35em") //vertical align middle
.text(function(d){
return d.label;
}).each(function() {
labelWidth = Math.ceil(Math.max(labelWidth, this.getBBox().width));
});
scale = d3.scale.linear()
.domain([0, max])
.range([0, width - margin*2 - labelWidth]);
xAxis = d3.svg.axis()
.scale(scale)
.tickSize(-height + 2*margin + axisMargin)
.orient("bottom");
bar.append("rect")
.attr("transform", "translate("+labelWidth+", 0)")
.attr("height", barHeight)
.attr("width", function(d){
return scale(d.value);
});
bar.append("text")
.attr("class", "value")
.attr("y", barHeight / 2)
.attr("dx", -valueMargin + labelWidth) //margin right
.attr("dy", ".35em") //vertical align middle
.attr("text-anchor", "end")
.text(function(d){
return (d.value);
})
.attr("x", function(d){
var width = this.getBBox().width;
return Math.max(width + valueMargin, scale(d.value));
});
bar
.on("mousemove", function(d){
div.style("left", d3.event.pageX+10+"px");
div.style("top", d3.event.pageY-25+"px");
div.style("display", "inline-block");
div.html((d.label)+"<br>"+(d.value));
});
bar
.on("mouseout", function(d){
div.style("display", "none");
});
svg.insert("g",":first-child")
.attr("class", "axisHorizontal")
.attr("transform", "translate(" + (margin + labelWidth) + ","+ (height - axisMargin - margin)+")")
.call(xAxis);
body {
font-family: "Roboto"!important;
width: 100%;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
position: center;
}
.toolTip {
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
text {
font: 10px sans-serif;
color: white;
}
text.value {
font-size: 100%;
fill: white;
}
.axisHorizontal path{
fill: none;
}
.axisHorizontal .tick line {
stroke-width: 1;
stroke: rgba(0, 0, 0, 0.2);
}
.bar {
fill: steelblue;
fill-opacity: .9;
}
<script src="https://d3js.org/d3.v3.min.js"></script>
To learn more about d3 nest, here's a good examples list: http://bl.ocks.org/phoebebright/raw/3176159/
Hope this helps.
I am working on a d3 application and I am interested in taking the following jsfiddle -- and onload or on an action -- revoking an animation where the bar charts animate one by one.
So the first bar animates to its height, then the second and so forth. Also a reversal of the animation would be good too -- so maybe something that is invoked automatically on scroll visibility?
http://jsfiddle.net/pg886/201/
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div
class="barchart"
data-role="barchart"
data-width=300
data-height=400
data-data="x"
data-configurations=""
>
</div>
<style>
.barchart{
/*width:100%;
border: 1px solid red;*/
}
.barchart svg{
width:100%;
/*border: 1px solid green;*/
}
.barchartg{
}
.barchart .axis path{
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.barchart .axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.barchart .x.axis path {
display: none;
}
.barchart .axis text{
fill: #005a70;
}
.barchart.dark .axis text{
fill: #ffffff;
}
.barchart.dark .axis path{
stroke: #ffffff;
}
.barchart .bar:hover {
fill: #e9168a;
}
</style>
<script>
$(document).ready(function() {
console.log("test")
var $this = $(".barchart");
var w = $this.data("width");
var h = $this.data("height");
var data = $this.data("data");
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}];
var configurations = $this.data("configurations");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#2b2d39", "#c12f39", "#f8dd2f", "#1b91dc"];
return colores_g[n % colores_g.length];
}
//asess the margin bottom for the chart based on the max char label
var charLabelCount = [];
data.map(function(d) {
var labelStr = d.label.toString();
charLabelCount.push(labelStr.length);
})
var maxChars = charLabelCount.reduce(function(a, b) {
return Math.max(a, b);
});
var bottomMarg = 60;
if(maxChars > 15){
bottomMarg = 170;
}
//bottom margin calculation
var margin = {top: 15, right: 20, bottom: bottomMarg, left: 40},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 "+w+" "+h)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "barchartg");
function sortBy(array,key){
var sorted = array.sort(function(a, b) {
return parseFloat(b[key]) - parseFloat(a[key]);
});
return sorted;
}
var sortedMax = 45;//45 as an initial value
//if there is a configuration file - it acts as an overide -- this is so there could be just one chart -- or a set of charts next to each other
if(configurations){
//if its a comparison chart -- use a max value that will be shared amongst a stack of sibling charts
if(configurations[0]["maxValue"]){
sortedMax = configurations[0]["maxValue"] + 5;//add 5 value buffer
}
}
else{
//if its a stand alone chart - adjust the max val by this chart's own values
sortedMax = sortBy(data, "value")[0]["value"] + 5;//add 5 value buffer
}
x.domain(data.map(function(d) {
return d.label;
}));
y.domain([0, sortedMax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll(".x.axis text")
.attr("transform", "rotate(-60) translate(-5,-5)")
.style("text-anchor", "end");
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("");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
});
});
</script>
Use .transition() to trigger animation to each rect.
But you have to start each rect with a height of zero and y of zero as well, so that you have something to work with in the animation.
$(document).ready(function() {
console.log("test")
var $this = $(".barchart");
var w = $this.data("width");
var h = $this.data("height");
var data = $this.data("data");
var data = [{
"label": "Apples",
"value": 100
},
{
"label": "Pears",
"value": 120
},
{
"label": "Bananas",
"value": 20
}
];
var configurations = $this.data("configurations");
function colores_google(n) {
var colores_g = ["#f7b363", "#448875", "#2b2d39", "#c12f39", "#f8dd2f", "#1b91dc"];
return colores_g[n % colores_g.length];
}
//asess the margin bottom for the chart based on the max char label
var charLabelCount = [];
data.map(function(d) {
var labelStr = d.label.toString();
charLabelCount.push(labelStr.length);
})
var maxChars = charLabelCount.reduce(function(a, b) {
return Math.max(a, b);
});
var bottomMarg = 60;
if (maxChars > 15) {
bottomMarg = 170;
}
//bottom margin calculation
var margin = {
top: 15,
right: 20,
bottom: bottomMarg,
left: 40
},
width = w - margin.left - margin.right,
height = h - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width]).padding(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var xAxis = d3.axisBottom(x);
var yAxis = d3.axisLeft(y);
var svg = d3.select($this[0])
.append("svg")
.attr("width", w)
.attr("height", h)
.attr("viewBox", "0 0 " + w + " " + h)
.attr("preserveAspectRatio", "xMidYMid meet")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")")
.attr("class", "barchartg");
function sortBy(array, key) {
var sorted = array.sort(function(a, b) {
return parseFloat(b[key]) - parseFloat(a[key]);
});
return sorted;
}
var sortedMax = 45;
if (configurations) {
if (configurations[0]["maxValue"]) {
sortedMax = configurations[0]["maxValue"] + 5;
}
} else {
sortedMax = sortBy(data, "value")[0]["value"] + 5;
}
x.domain(data.map(function(d) {
return d.label;
}));
y.domain([0, sortedMax]);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.selectAll(".x.axis text")
.attr("transform", "rotate(-60) translate(-5,-5)")
.style("text-anchor", "end");
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("");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(0);
})
.attr("height", function(d) {
return 0
});
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d,i){ return 500*i;})
.attr("height",function(d){ return height - y(d.value);})
.attr("y",function(d){return y(d.value);});
setTimeout(function(){
d3.selectAll("rect").transition()
.duration(500)
.delay(function(d,i){ return 600*(3-i);})
.attr("height",function(d){ return 0;})
.attr("y",function(d){return y(0);});
},2000);
});
.barchart {
/*width:100%;
border: 1px solid red;*/
}
.barchart svg {
width: 100%;
/*border: 1px solid green;*/
}
.barchartg {}
.barchart .axis path {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.barchart .axis line {
fill: none;
stroke: none;
shape-rendering: crispEdges;
}
.barchart .x.axis path {
display: none;
}
.barchart .axis text {
fill: #005a70;
}
.barchart.dark .axis text {
fill: #ffffff;
}
.barchart.dark .axis path {
stroke: #ffffff;
}
.barchart .bar:hover {
fill: #e9168a;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v4.min.js"></script>
<div class="barchart" data-role="barchart" data-width=300 data-height=400 data-data="x" data-configurations="">
</div>
you need to add transition for each bar while generating and delay the transion according to your wish.the code is as.
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
the updated code with your code block is as follows
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.transition()
.delay(function (d,i){ return i * 300;})
.duration(250)
.attr("fill", function(d, i) {
return colores_google(i);
})
.attr("x", function(d) {
return x(d.label);
})
.attr("width", x.bandwidth())
.attr("y", function(d) {
return y(d.value);
})
.attr("height", function(d) {
return height - y(d.value);
})
I found a vertical stack bar graph sample on google - http://bl.ocks.org/juan-cb/43f10523858abf6053ae
I want to convert it in horizontal stacked bar graph. I have done the changes but something is wrong. Graph is not correct.I think all the bars are overlapped.
Please help me to resolve this. Code is copied.
<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
position: center;
}
text{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.toolTip {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
text {
font: 10px sans-serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path{
fill: none;
stroke: #000;
}
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
</style>
<body>
<div class="barGraph" id='stacked-bar'></div>
<script src="http://d3js.org/d3.v4.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>
<script>
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
init();
function init(){
var dataset = [{
"goodRating": 27,
"avgRating": 21,
"badRating": 16,
"rooms": "0.01"
},
{
"goodRating": 26,
"avgRating": 22,
"badRating": 31,
"rooms": "0.02"
},
{
"goodRating": 100,
"avgRating": 0,
"badRating": 0,
"rooms": "1"
}];
var groupSpacing = 6;
var margin = {top: 10, right: 10, bottom: 60, left: 100},
width = 1000 - margin.left - margin.right,
height = 400 - margin.top - margin.bottom;
var y = d3.scaleBand()
.range([height, 0]);
var x = d3.scaleLinear()
.range([0, width], .1,.3);
// var colorRange = d3.scale.category20();
var color = d3.scaleOrdinal(d3.schemeCategory20);
var xAxis = d3.axisBottom(x).tickFormat(dataset.rooms),
yAxis = d3.axisLeft(y);
var svg = d3.select("#stacked-bar").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 divTooltip = d3.select("body").append("div").attr("class", "toolTip");
color.domain(d3.keys(dataset[0]).filter(function(key) { return key !== "rooms"; }));
dataset.forEach(function(d) {
var y0 = 0;
var y1 = 0;
d.values = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.values[d.values.length - 1].y1;
});
y.domain(dataset.map(function(d) { return d.rooms; }));
x.domain([0, d3.max(dataset, function(d) { return d.total; })]);
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", ".5em")
.attr("transform", "rotate(-65)");
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 9)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Satisfaction %");
var bar = svg.selectAll(".rooms")
.data(dataset)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + y(d.rooms) + ",0)"; });
svg.selectAll(".x.axis .tick text")
.call(wrap, y.bandwidth());
var bar_enter = bar.selectAll("rect")
.data(function(d) { return d.values; })
.enter();
bar_enter.append("rect")
.attr("height", y.bandwidth())
.attr("x", function(d) { return x(d.y1); })
.attr("width", function(d) { return x(d.y1) - x(d.y0) })
.style("fill", function(d) { return color(d.name); });
bar_enter.append("text")
.text(function(d) { return d3.format(".2s")(d.y1-d.y0)+"%"; })
.attr("x", function(d) { return x(d.y1)+(x(d.y0) - x(d.y1))/2; })
.attr("y", y.bandwidth()/3)
.style("fill", '#ffffff');
bar.on("mousemove", function(d){
divTooltip.style("left", d3.event.pageX+10+"px");
divTooltip.style("top", d3.event.pageY-25+"px");
divTooltip.style("display", "inline-block");
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l-1
element = elements[l].__data__
value = element.y1 - element.y0
divTooltip.html("Room No : "+(d.rooms)+"<br>"+element.name+" : "+value+"%");
});
bar.on("mouseout", function(d){
divTooltip.style("display", "none");
});
}
</script>
</body>
Regards,
Pinki Sharma
A few minor things were missing/incorrect:
The group (<g>) containing the bars was being transformed incorrectly (I'm guessing you missed this while changing from vertical stacked to horizontal) i.e. the following line
.attr("transform", function(d) { return "translate(" + y(d.rooms) + ",0)"; });
translates the bar groups from the left and the y position would be 0 and hence the overlap. I've changed that to this:
.attr("transform", function(d) { return "translate(0, " + y(d.rooms) + ")"; });
The rects' x value is changed from x(d.y1) to x(d.y0) (might be a typo)
bar_enter.append("rect")
.attr("height", y.bandwidth())
.attr("x", function(d) { return x(d.y0); })
Axis padding was missing for the scaleBand(). I've added that (check docs for more info)
var y = d3.scaleBand()
.rangeRound([height, 0]).padding(0.1);
Reset the margins to adapt to the SVG dimensions:
var margin = {top: 10, right: 60, bottom: 60, left: 50},
Combining all of the above, here's a fork of your codepen:
HORIZONTAL STACKED BAR CHART DEMO
Hope this helps.
I am trying to work on getting a d3.js bar chart similar to the one. I have in HTML & CSS as following :
Here is HTML Link : http://huntedhunter.com/venue_report/
Can I know if it would be possible to get this one similar like the above HTML & CSS one :
https://codepen.io/nicefellow1234/pen/aEZrma
function wrap(text, width) {
text.each(function() {
var text = d3.select(this),
words = text.text().split(/\s+/).reverse(),
word,
line = [],
lineNumber = 0,
lineHeight = 1.1, // ems
y = text.attr("y"),
dy = parseFloat(text.attr("dy")),
tspan = text.text(null).append("tspan").attr("x", 0).attr("y", y).attr("dy", dy + "em");
while (word = words.pop()) {
line.push(word);
tspan.text(line.join(" "));
if (tspan.node().getComputedTextLength() > width) {
line.pop();
tspan.text(line.join(" "));
line = [word];
tspan = text.append("tspan").attr("x", 0).attr("y", y).attr("dy", ++lineNumber * lineHeight + dy + "em").text(word);
}
}
});
}
dataset = [
{label:"Art and Humanities", "Not Satisfied":25, "Not Much Satisfied":40, "Satisfied": 15, "Very Satisfied":20},
{label:"Sciences", "Not Satisfied":5, "Not Much Satisfied":30, "Satisfied": 50, "Very Satisfied":15},
{label:"Health Sciences", "Not Satisfied":20, "Not Much Satisfied":30, "Satisfied": 40, "Very Satisfied":10},
{label:"Social Sciences", "Not Satisfied":10, "Not Much Satisfied":40, "Satisfied": 45, "Very Satisfied":5},
{label:"Architecture and Engineering", "Not Satisfied":5, "Not Much Satisfied":15, "Satisfied": 40, "Very Satisfied":40},
];
var margin = {top: (parseInt(d3.select('body').style('height'), 10)/20), right: (parseInt(d3.select('body').style('width'), 10)/20), bottom: (parseInt(d3.select('body').style('height'), 10)/6), left: (parseInt(d3.select('body').style('width'), 10)/20)},
width = parseInt(d3.select('body').style('width'), 10) - margin.left - margin.right,
height = parseInt(d3.select('body').style('height'), 10) - margin.top - margin.bottom;
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1,.3);
var y = d3.scale.linear()
.rangeRound([height, 0]);
var colorRange = d3.scale.category20();
var color = d3.scale.ordinal()
.range(colorRange.range());
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.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 + ")");
var divTooltip = d3.select("body").append("div").attr("class", "toolTip");
color.domain(d3.keys(dataset[0]).filter(function(key) { return key !== "label"; }));
dataset.forEach(function(d) {
var y0 = 0;
d.values = color.domain().map(function(name) { return {name: name, y0: y0, y1: y0 += +d[name]}; });
d.total = d.values[d.values.length - 1].y1;
});
x.domain(dataset.map(function(d) { return d.label; }));
y.domain([0, d3.max(dataset, function(d) { return d.total; })]);
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", 9)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Satisfaction %");
var bar = svg.selectAll(".label")
.data(dataset)
.enter().append("g")
.attr("class", "g")
.attr("transform", function(d) { return "translate(" + x(d.label) + ",0)"; });
svg.selectAll(".x.axis .tick text")
.call(wrap, x.rangeBand());
var bar_enter = bar.selectAll("rect")
.data(function(d) { return d.values; })
.enter();
bar_enter.append("rect")
.attr("width", x.rangeBand())
.attr("y", function(d) { return y(d.y1); })
.attr("height", function(d) { return y(d.y0) - y(d.y1); })
.style("fill", function(d) { return color(d.name); });
bar_enter.append("text")
.text(function(d) { return d3.format(".2s")(d.y1-d.y0)+"%"; })
.attr("y", function(d) { return y(d.y1)+(y(d.y0) - y(d.y1))/2; })
.attr("x", x.rangeBand()/3)
.style("fill", '#ffffff');
bar
.on("mousemove", function(d){
divTooltip.style("left", d3.event.pageX+10+"px");
divTooltip.style("top", d3.event.pageY-25+"px");
divTooltip.style("display", "inline-block");
var elements = document.querySelectorAll(':hover');
l = elements.length
l = l-1
element = elements[l].__data__
value = element.y1 - element.y0
divTooltip.html((d.label)+"<br>"+element.name+"<br>"+value+"%");
});
bar
.on("mouseout", function(d){
divTooltip.style("display", "none");
});
svg.append("g")
.attr("class", "legendLinear")
.attr("transform", "translate(0,"+(height+30)+")");
var legend = d3.legend.color()
.shapeWidth(height/4)
.shapePadding(10)
.orient('horizontal')
.scale(color);
svg.select(".legendLinear")
.call(legend);
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
width: 960px;
height: 500px;
position: relative;
}
svg {
width: 100%;
height: 100%;
position: center;
}
text{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
.toolTip {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
position: absolute;
display: none;
width: auto;
height: auto;
background: none repeat scroll 0 0 white;
border: 0 none;
border-radius: 8px 8px 8px 8px;
box-shadow: -3px 3px 15px #888888;
color: black;
font: 12px sans-serif;
padding: 5px;
text-align: center;
}
.legend {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
font-size: 60%;
}
text {
font: 10px sans-serif;
}
.axis text {
font: 10px sans-serif;
}
.axis path{
fill: none;
stroke: #000;
}
.axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.x.axis path {
display: none;
}
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-legend/1.7.0/d3-legend.min.js"></script>