Skip to content

Instantly share code, notes, and snippets.

@arpitnarechania
Last active February 11, 2017 16:19
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save arpitnarechania/ca3d1837c51776f12d9e7710f9b29822 to your computer and use it in GitHub Desktop.
Save arpitnarechania/ca3d1837c51776f12d9e7710f9b29822 to your computer and use it in GitHub Desktop.
Normalized Stack Bar Chart with Line

Stacked Bar Chart with a Line Chart

.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;
}
<!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>
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");
}
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