block by micahstubbs a40254b6cb914018ff81

stacking with negative values

Full Screen

##stacking with negative values

A #d3js stacked bar chart that supports a mix of positive and negative values.
An iteration on the bl.ock barStack - stacking with negative values from ZJONSSON

The tooltips are adapted from the Stacked bar chart with tooltips bl.ock from mstanaland

index.html

<!DOCTYPE html>
<html>
<head>
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.6/d3.min.js"></script>
<title>stacked bar</title>
<style>
.axis text {
	font: 10px sans-serif;
}
.axis path, .axis line {
	fill: none;
	stroke: #000;
	shape-rendering: crispEdges;
}
</style>
</head>
<body>
<script>
/* 
	an iteration on this bl.ock
	//bl.ocks.org/ZJONSSON/2975320 
	barStack - stacking with negative values
*/

function barStack(seriesData) {
	var l = seriesData[0].length
	while (l--) {
		var posBase = 0; // positive base
		var negBase = 0; // negative base

		seriesData.forEach(function(d) {
			d = d[l]
			d.size = Math.abs(d.y)
			if (d.y < 0) {
				d.y0 = negBase
				negBase -= d.size
			} else
			{
				d.y0 = posBase = posBase + d.size
			}
		})
	}
	seriesData.extent = d3.extent(
		d3.merge(
			d3.merge(
				seriesData.map(function(e) { 
					return e.map(function(f) { return [f.y0,f.y0-f.size] }) 
				})
			)
		)
	)
}

/* Here is an example */

// each series is an array of objects
// our data is in turn an array of those series arrays
// which is to say
// an array of arrays of objects
var data = [
	[ {y:3},  {y:6},  {y:-3} ],
	[ {y:4},  {y:-2}, {y:-9} ],
	[ {y:10}, {y:-3}, {y:4}  ]
]

var h = 500;
var w = 500;
var margin = 20;
var color = d3.scale.category10();

var x = d3.scale.ordinal()
	.domain(['abc', 'abc2', 'abc3'])
	.rangeRoundBands([ margin, w - margin ], .1)

var y = d3.scale.linear()
	.range([h-margin,0+margin]);

var xAxis = d3.svg.axis()
	.scale(x)
	.orient("bottom")
	.tickSize(6, 0);

var yAxis = d3.svg.axis()
	.scale(y)
	.orient("left");

barStack(data);
y.domain(data.extent);

svg = d3.select("body")
	.append("svg")
	.attr("height", h)
	.attr("width", w)

svg.selectAll(".series")
	.data(data)
	.enter()
	.append("g")
	.classed("series", true)
	.style("fill", function(d,i) { return color(i) })
	.style("opacity", 0.8)
		.selectAll("rect")
		.data(Object)
		.enter()
		.append("rect")
			.attr("x", function(d, i) { return x(x.domain()[i]) })
			.attr("y", function(d) { return y(d.y0) })
			.attr("height", function(d) { return y(0) - y(d.size) })
			.attr("width", x.rangeBand())
			.on("mouseover", function() { tooltip.style("display", null); })
  		.on("mouseout", function() { tooltip.style("display", "none"); })
  		.on("mousemove", function(d) {
    		var xPosition = d3.mouse(this)[0] - 35;
    		var yPosition = d3.mouse(this)[1] - 5;
    		tooltip.attr("transform", "translate(" + xPosition + "," + yPosition + ")");
    		tooltip.select("text").text(d.y);
  		});

console.log("y(0)", y(0));
console.log("margin", margin); 

svg.append("g")
	.attr("class", "axis x")
	.attr("transform", "translate(0 " + y(0) + ")")
	.call(xAxis);

svg.append("g")
	.attr("class", "axis y")
	.attr("transform", "translate(" + margin + " 0)")
	.call(yAxis);

/* Here we add tooltips */

// Prep the tooltip bits, initial display is hidden
var tooltip = svg.append("g")
  .attr("class", "tooltip")
  .style("display", "none");
    
tooltip.append("rect")
  .attr("width", 30)
  .attr("height", 20)
  .attr("fill", "white")
  .style("opacity", 0.5);

tooltip.append("text")
  .attr("x", 15)
  .attr("dy", "1.2em")
  .style("text-anchor", "middle")
  .attr("font-size", "12px")
  .attr("font-weight", "bold");
	
</script>
</body>