Stacked Bar Chart with a Line Chart
Last active
February 11, 2017 16:19
-
-
Save arpitnarechania/ca3d1837c51776f12d9e7710f9b29822 to your computer and use it in GitHub Desktop.
Normalized Stack Bar Chart with Line
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.d3-tip { | |
line-height: 1; | |
font-weight: bold; | |
padding: 12px; | |
background: rgba(0, 0, 0, 0.8); | |
color: #fff; | |
border-radius: 2px; | |
} | |
/* Creates a small triangle extender for the tooltip */ | |
.d3-tip:after { | |
box-sizing: border-box; | |
display: inline; | |
font-size: 10px; | |
width: 100%; | |
line-height: 1; | |
color: rgba(0, 0, 0, 0.8); | |
content: "\25BC"; | |
position: absolute; | |
text-align: center; | |
} | |
/* Style northward tooltips differently */ | |
.d3-tip.n:after { | |
margin: -1px 0 0 0; | |
top: 100%; | |
left: 0; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE> | |
<html lang="en"> | |
<head> | |
<meta charset="utf-8"> | |
<title>Normalized Stacked Bar Chart with Line</title> | |
<!-- JavaScript Libraries //--> | |
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3-tip/0.7.1/d3-tip.min.js"></script> | |
<!-- CSS Style //--> | |
<link href="style.css" rel="stylesheet" type="text/css"> | |
<link href="d3-tip.css" rel="stylesheet" type="text/css"> | |
<script type="text/javascript" src="main.js"></script> | |
</head> | |
<body> | |
<div id="chart"></div> | |
</body> | |
<script> | |
var inputData = [ | |
{ | |
"1483209000000":50, | |
"1483223400000":60, | |
"1483237800000":70, | |
"1483252200000":80, | |
"1483266600000":90, | |
"1483281000000":100, | |
"1483338600000":110, | |
"1483353000000":120, | |
"1483367400000":130, | |
"1483381800000":140, | |
"1483396200000":150, | |
"1483410600000":160, | |
"1483425000000":170, | |
"1483439400000":180, | |
"1483468200000":190, | |
"CategoryNames":"Base" | |
}, | |
{ | |
"1483209000000":20, | |
"1483223400000":30, | |
"1483237800000":40, | |
"1483252200000":50, | |
"1483266600000":60, | |
"1483281000000":70, | |
"1483338600000":80, | |
"1483353000000":90, | |
"1483367400000":100, | |
"1483381800000":110, | |
"1483396200000":120, | |
"1483410600000":130, | |
"1483425000000":140, | |
"1483439400000":150, | |
"1483468200000":160, | |
"CategoryNames":"Category 1" | |
}, | |
{ | |
"1483209000000":30, | |
"1483223400000":30, | |
"1483237800000":30, | |
"1483252200000":30, | |
"1483266600000":30, | |
"1483281000000":30, | |
"1483338600000":30, | |
"1483353000000":30, | |
"1483367400000":30, | |
"1483381800000":30, | |
"1483396200000":30, | |
"1483410600000":30, | |
"1483425000000":30, | |
"1483439400000":30, | |
"1483468200000":30, | |
"CategoryNames":"Category 2" | |
} | |
]; | |
renderNormalizedStackBarChart(inputData,"#chart"); | |
</script> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
function renderNormalizedStackBarChart(inputData,dom_element_to_append_to) { | |
var mainMargin = { | |
top: 20, | |
right: 190, | |
bottom: 150, | |
left: 60 | |
}, | |
width = 900 - mainMargin.left - mainMargin.right, | |
height = 500 - mainMargin.top - mainMargin.bottom, | |
miniMargin = { | |
top: 50, | |
bottom: 150, | |
left: 40, | |
right: 190 | |
}, | |
miniHeight = 50; | |
var xscale = d3.time.scale() | |
.range([0, width]); | |
var miniXScale = d3.time.scale() | |
.range([0, width]); | |
var dateFormat = d3.time.format("%a %d"); | |
var yScaleLeft = d3.scale.linear() | |
.rangeRound([height, 0]); | |
var yScaleRight = d3.scale.linear() | |
.rangeRound([height, 0]); | |
var miniyScaleLeft = d3.scale.linear() | |
.rangeRound([miniHeight, 0]); | |
var miniyScaleRight = d3.scale.linear() | |
.rangeRound([miniHeight, 0]); | |
var colors = d3.scale.ordinal() | |
.range(["#63c172", "#ee9952", "#46d6c4", "#fee851", "#98bc9a"]); | |
console.log(inputData); | |
var xaxis = d3.svg.axis() | |
.scale(xscale) | |
.orient("bottom") | |
.tickFormat(function(d) { | |
return (new Date(+d) + "").substring(3,21); | |
}); | |
var miniXAxis = d3.svg.axis() | |
.scale(miniXScale) | |
.tickFormat(function(d) { | |
return (new Date(+d) + "").substring(0,16); | |
}) | |
.orient("bottom"); | |
var yAxisLeft = d3.svg.axis() | |
.scale(yScaleLeft) | |
.orient("left") | |
.tickFormat(d3.format(".0%")); | |
var yAxisRight = d3.svg.axis() | |
.scale(yScaleRight) | |
.orient("right") | |
.tickFormat(d3.format(".0%")); | |
var line = d3.svg.line() | |
.x(function(d, i) { | |
return xscale(d.response) ; | |
}) | |
.y(function(d) { | |
return yScaleRight(d.value); | |
}); | |
var miniLine = d3.svg.line() | |
.x(function(d, i) { | |
return miniXScale(d.response) + miniXScale.rangeBand()/2; | |
}) | |
.y(function(d) { | |
return miniyScaleRight(d.value); | |
}); | |
var svg = d3.select(dom_element_to_append_to).append("svg") | |
.attr("width", width + mainMargin.left + mainMargin.right ) | |
.attr("height", height + mainMargin.top + mainMargin.bottom); | |
svg.append("g") | |
.attr("class", "y axisLeft") | |
.attr("transform", function() { return "translate(" + (mainMargin.left ) + "," + mainMargin.top + ")"; }) | |
.call(yAxisLeft); | |
svg.append("g") | |
.attr("class", "y axisRight") | |
.attr("transform", function() { return "translate(" + (width + mainMargin.left ) + "," + mainMargin.top + ")"; }) | |
.call(yAxisRight); | |
var main = svg.append("svg") | |
.attr("width", width + mainMargin.left ) | |
.append("g") | |
.attr("transform", "translate(" + mainMargin.left + "," + mainMargin.top + ")"); | |
var mini = svg.append("svg") | |
.attr("width", width + miniMargin.left + 15 ) | |
.append("g") | |
.attr("transform", "translate(" + (miniMargin.left*1.5) + "," + (0) + ")"); | |
var tip = d3.tip() | |
.attr('class', 'd3-tip') | |
.offset([-10, 0]) | |
.html(function(d) { | |
var date = (new Date(+d.CategoryNames) + "" ).substring(0,25); | |
return "<div><span>Date:</span> <span style='color:white'>" + date + "</span></div>" + | |
"<div> <span style='color:white'><span>" + d.response+ " (" + (d.yp1*100).toFixed(0) +"%): " + "</span>" + d.y1 + " times</span></div>"; | |
}); | |
svg.call(tip); | |
var categories = d3.keys(inputData[0]).filter(function(key) { return key !== "CategoryNames"; }); | |
var parsedata = categories.map(function(name) { return { "CategoryNames": name }; }); | |
inputData.forEach(function(d) { | |
parsedata.forEach(function(pd) { | |
pd[d["CategoryNames"]] = d[pd["CategoryNames"]]; | |
}); | |
}); | |
colors.domain(d3.keys(parsedata[0]).filter(function(key) { return key !== "CategoryNames" && key !== "Base"; })); | |
parsedata.forEach(function(pd) { | |
var y0 = 0; | |
pd.responses = colors.domain().map(function(response) { | |
var responseobj = {response: response, y0: y0, yp0: y0}; | |
y0 += +pd[response]; | |
responseobj.y1 = y0; | |
responseobj.yp1 = y0; | |
responseobj.CategoryNames = pd.CategoryNames; | |
return responseobj; | |
}); | |
pd.responses.forEach(function(d) { d.yp0 /= y0; d.yp1 /= y0; }); | |
pd.totalresponses = pd.responses[pd.responses.length - 1].y1; | |
}); | |
xscale.domain(d3.extent(parsedata, function(d) { return +d.CategoryNames; }) ); | |
//mainX1.domain(xscale.domain()); | |
miniXScale.domain(xscale.domain()); | |
yScaleRight.domain([0, d3.max(parsedata, function(d) { return d.totalresponses; })]); | |
yAxisRight.tickFormat(d3.format(".2s")); | |
var brush = d3.svg.brush() | |
.x(miniXScale) | |
.on("brush", brushed); | |
xaxis.ticks(parsedata.length); | |
main.append("g") | |
.attr("class", "x axis") | |
.attr("transform", "translate(0," + height + ")") | |
.call(xaxis) | |
.selectAll("text") | |
.attr("y", 5) | |
.attr("x", 7) | |
.attr("dy", ".35em") | |
.attr("transform", "rotate(65)") | |
.style("text-anchor", "start"); | |
var category = main.selectAll(".category") | |
.data(parsedata) | |
.enter().append("g") | |
.attr("class", "category") | |
.attr("transform", function(d) { return "translate(" + xscale(+d.CategoryNames) + ",0)"; }); | |
console.log(parsedata); | |
category.selectAll("rect") | |
.data(function(d) { return d.responses; }) | |
.enter().append("rect") | |
.attr("width", width/(parsedata.length+10)) | |
.attr("y", function(d) { return yScaleLeft(d.yp1); }) | |
.attr("height", function(d) { return yScaleLeft(d.yp0) - yScaleLeft(d.yp1); }) | |
.attr("fill", function(d) { return colors(d.response); }) | |
.on('mouseover', tip.show) | |
.on('mouseout', tip.hide); | |
var convertedData = []; | |
parsedata.forEach(function(item) { | |
convertedData.push({response: item.CategoryNames, value: item.responses[0].y1}) | |
}); | |
main.append("path") | |
.data(convertedData) | |
.attr("class", "line") | |
.attr("d", line(convertedData)) | |
.attr("stroke", "blue") | |
.attr("stroke-width", 2) | |
.attr("fill", "none"); | |
var miniCategory = mini.selectAll(".mini_category") | |
.data(parsedata) | |
.enter().append("g") | |
.attr("class", "mini_category") | |
.attr("transform", function(d) { return "translate(" + (miniXScale(d.CategoryNames) ) + "," + (height+miniMargin.top + 50) + ")"; }); | |
miniCategory.selectAll("rect") | |
.data(function(d) { return d.responses; }) | |
.enter().append("rect") | |
.attr("width", width/(parsedata.length+10)) | |
.attr("y", function(d) { return miniyScaleLeft(d.yp1); }) | |
.attr("height", function(d) { return miniyScaleLeft(d.yp0) - miniyScaleLeft(d.yp1) ; }) | |
.style("fill", function(d) { return colors(d.response); }); | |
mini.append("g") | |
.attr("class", "x brush") | |
.call(brush) | |
.selectAll("rect") | |
.attr("y", -6) | |
.attr("height", miniHeight + 15) | |
.attr("transform", "translate(" + 0 + "," + (height+miniMargin.top + 50) + ")"); | |
var legend = svg.selectAll(".legend") | |
.data(colors.domain()) | |
.enter().append("g") | |
.attr("class", "legend") | |
.attr("transform", function(d, i) { return "translate(82," + (((height+miniMargin.top + 50)) + (i * 20)) + ")"; }); | |
legend.append("rect") | |
.attr("x", width - 18) | |
.attr("width", 18) | |
.attr("height", 18) | |
.style("fill", colors); | |
legend.append("text") | |
.attr("x", width + 10) | |
.attr("y", 9) | |
.attr("dy", ".35em") | |
.style("text-anchor", "start") | |
.text(function(d) { return d; }); | |
mini.append("g") | |
.attr("class", "x axis1") | |
.attr("transform", "translate(0," + (height+miniMargin.top + 50 + miniHeight) + ")") | |
.call(miniXAxis); | |
d3.selectAll("input").on("change", handleFormClick); | |
function handleFormClick() { | |
if (this.value === "bypercent") { | |
transitionPercent(); | |
} else { | |
transitionCount(); | |
} | |
} | |
function transitionPercent() { | |
yScaleLeft.domain([0, 1]); | |
miniyScaleLeft.domain([0, 1]); | |
var transMain = main.transition().duration(250); | |
var transMini = mini.transition().duration(250); | |
var categories = transMain.selectAll(".category"); | |
categories.selectAll("rect") | |
.attr("y", function(d) { return yScaleLeft(d.yp1); }) | |
.attr("height", function(d) { return yScaleLeft(d.yp0) - yScaleLeft(d.yp1); }); | |
var miniCategories = transMini.selectAll(".mini_category"); | |
miniCategories.selectAll("rect") | |
.attr("y", function(d) { return miniyScaleLeft(d.yp1); }) | |
.attr("height", function(d) { return miniyScaleLeft(d.yp0) - miniyScaleLeft(d.yp1); }); | |
yAxisLeft.tickFormat(d3.format(".0%")); | |
main.selectAll(".y.axisLeft").call(yAxisLeft); | |
} | |
function transitionCount() { | |
yScaleLeft.domain([0, d3.max(parsedata, function(d) { return d.totalresponses; })]); | |
miniyScaleLeft.domain([0, d3.max(parsedata, function(d) { return d.totalresponses; })]); | |
var transoneMain = main.transition().duration(100); | |
var transoneMini = mini.transition().duration(100); | |
var categoriesone = transoneMain.selectAll(".category"); | |
categoriesone.selectAll("rect") | |
.attr("y", function(d) { return this.getBBox().y + this.getBBox().height - (yScaleLeft(d.y0) - yScaleLeft(d.y1)) }) | |
.attr("height", function(d) { return yScaleLeft(d.y0) - yScaleLeft(d.y1); }); | |
var miniCategoriesone = transoneMini.selectAll(".mini_category"); | |
miniCategoriesone.selectAll("rect") | |
.attr("y", function(d) { return this.getBBox().y + this.getBBox().height - (miniyScaleLeft(d.y0) - miniyScaleLeft(d.y1)) }) | |
.attr("height", function(d) { return miniyScaleLeft(d.y0) - miniyScaleLeft(d.y1); }); | |
var transtwoMain = transoneMain.transition() | |
.delay(150) | |
.duration(200) | |
.ease("bounce"); | |
var transtwoMini = transoneMini.transition() | |
.delay(150) | |
.duration(200) | |
.ease("bounce"); | |
var categoriestwo = transtwoMain.selectAll(".category"); | |
categoriestwo.selectAll("rect") | |
.attr("y", function(d) { return yScaleLeft(d.y1); }); | |
var miniCategoriestwo = transtwoMini.selectAll(".mini_category"); | |
miniCategoriestwo.selectAll("rect") | |
.attr("y", function(d) { return miniyScaleLeft(d.y1); }); | |
yAxisLeft.tickFormat(d3.format(".2s")); | |
main.selectAll(".y.axisLeft").call(yAxisLeft); | |
} | |
function brushed() { | |
xscale.domain(brush.empty() ? miniXScale.domain() : brush.extent()); | |
main.selectAll(".category") | |
.attr("transform", function(d) { return "translate(" + xscale(+d.CategoryNames) + ",0)"; }); | |
main.selectAll(".line") | |
.data(convertedData) | |
.attr("d", function(d) { return line(convertedData); }) | |
main.select("g.x.axis") | |
.call(xaxis) | |
.selectAll("text") | |
.attr("y", 5) | |
.attr("x", 7) | |
.attr("dy", ".35em") | |
.attr("transform", "rotate(65)") | |
.style("text-anchor", "start"); | |
} | |
d3.select(self.frameElement).style("height", (height + mainMargin.top + mainMargin.bottom) + "px"); | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
rect.bordered { | |
stroke: #E6E6E6; | |
stroke-width: 2px; | |
} | |
g.axis text { | |
fill: #000; | |
font-size: 10px; | |
} | |
.axisLeft path, | |
.axisLeft line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.axisRight path, | |
.axisRight line { | |
fill: none; | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
.bar { | |
fill: steelblue; | |
} | |
.x.axis path { | |
display: block; | |
} | |
.x.axis1 path { | |
display: block; | |
} | |
.legend line { | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
path .line { | |
stroke: #000; | |
shape-rendering: crispEdges; | |
} | |
g.axis path, g.axis line { | |
fill: none; stroke:black; shape-rendering:crispEdges; | |
} | |
g.axis1 path, g.axis1 line { | |
fill: none; stroke:black; shape-rendering:crispEdges; | |
} | |
g.brush rect.extent { | |
fill-opacity:0.2; | |
position: relative; | |
z-index: 15; | |
shape-rendering: crispEdges; | |
} | |
g.axis1 text { | |
fill: none; | |
opacity: 0; | |
visibility: hidden; | |
} | |
.category:hover { | |
opacity: 0.75; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment