var margin = {top: 10, right: 0, bottom: 50, left: 40}, width = 900 - (margin.left + margin.right), width1 = width, width2 = width, height = 500 - 2 * (margin.top + margin.bottom), height1 = 300 - (margin.top + margin.bottom), height2 = height - height1, padding = 0.8; var speed = 1500; var reverseYAxis = true; var x1 = d3.scale.ordinal() .rangeBands([0, width1]); var x2 = d3.scale.ordinal(); var y1 = d3.scale.linear() .range([height1, 0]); var y2 = d3.scale.linear() .range([height2, 0]); if(reverseYAxis){ y1.range(y1.range().reverse()); y2.range(y2.range().reverse()); }; var x1Axis = d3.svg.axis() .scale(x1) .orient("bottom"); var y1Axis = d3.svg.axis() .scale(y1) .orient("left") .tickFormat(function(d) { return dollarFormatter(d); }); var x2Axis = d3.svg.axis() .scale(x2) .orient("bottom"); var y2Axis = d3.svg.axis() .scale(y2) .orient("left") .tickFormat(function(d) { return dollarFormatter(d); }); var svg = d3.select("body").append("svg") .attr("width", width + margin.left + margin.right) .attr("height", height + 2 * (margin.top + margin.bottom)); var chart = svg.append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")") .attr("class", "signed"); var signFunc = function(value){ if((value >= 0 && !reverseYAxis) || (value < 0 && reverseYAxis)){ return "positive"; } else { return "negative"; } } var tipText = function(d){ return d.year + " " + d.type + ":
" + dollarFormat(d.value) + "M"; } //var tooltips = d3.tip().html(tipText); // svg.call(tooltips); // Transform data (i.e., finding cumulative values and total) for easier charting var transformData = function(raw) { var cumulative = 0; var data = []; // Get list of change reasons var not_changes = ["Year", "Begin", "End"], changes = d3.keys(raw[0]).filter(function(d){ return not_changes.indexOf(d) < 0; }); // Add the initial value data.push({ year: raw[0].Year - 1 + "", type: "YearEnd", sign: "total", start: 0, end: +raw[0].Begin, value: +raw[0].Begin, }); cumulative = +raw[0].Begin; for (var i = 0; i < raw.length; i++) { // Add a bar for each of the different changes changes.forEach(function(c){ var d = {}; d.year = raw[i].Year, d.type = c; if(d.type != "Total"){ d.start = cumulative d.value = +raw[i][c] cumulative += d.value d.end = cumulative d.sign = signFunc(d.value) } else { d.start = +raw[i].Begin d.end = +raw[i].End d.value = d.end - d.start d.sign = signFunc(d.value) } data.push(d); }); // Add a bar for the year-end amount data.push({ year: raw[i].Year, type: "YearEnd", sign: 'total', start: 0, end: cumulative, value: cumulative }); } return data; }; d3.csv("texas_trs.csv", function(error, raw) { // parse the data var keys = d3.keys(raw[0]), amounts = keys.filter(function(k){ return k != "Year"; }), notChanges = ["Year", "Begin", "End"], changes = keys.filter(function(d){ return notChanges.indexOf(d) < 0; }), indivChanges = changes.filter(function(d){ return d != "Total"; }) raw.forEach(function(d){ amounts.forEach(function(k){ d[k] = +d[k]; }); }) // Transform data (i.e., finding cumulative values and total) for easier charting var data = transformData(raw); var nested = d3.nest() .key(function(d) {return d.type;}) .entries(data.filter(function(d) { return d.type != "YearEnd"; })); // Add a subchart for each reason for change width2 = (width + (margin.left + margin.right)) / changes.length - (margin.left + margin.right); // determine bounds of each chart var rangeByYear = raw.map(function(d) { var runTotal = [+d.Begin]; indivChanges.forEach(function(c, i) { runTotal.push(runTotal[i] + d[c]); }); return runTotal; }); var y1ext = d3.extent(rangeByYear.reduce(function(a, b) { return a.concat(b); })); y1max = d3.max(data, function(d){ return Math.max(d.start, d.end); }); y1min = d3.min(data, function(d){ return Math.min(d.start, d.end); }); var y2max = d3.max(raw, function(d) { return d3.max(changes.map(function(c){ return d[c]; })); }); var y2min = d3.min(raw, function(d) { return d3.min(changes.map(function(c){ return d[c]; })); }); var years = data.map(function(d){ return d.year; }), types = data.map(function(d){ return d.type; }); var typeColor = d3.scale.ordinal() .domain(types) .range(["rgb(77,77,77)"].concat(colorbrewer.Set2[8])); types.push(types.shift(1)); x1.domain(years); y1.domain([y1min, y1max]).nice(); x2.rangeBands([0, width2]) .domain(years.slice(1)); y2.domain([y2min, y2max]).nice(); var x1Type = d3.scale.ordinal() .rangeBands([0, x1.rangeBand()]) .domain(types.filter(function(t) { return t != "Total"; })); x2Axis.tickValues(x2.domain().filter(function(d, i) { return !(i % 2); })) var relEndWidth = 1.0, n = changes.length - 1, m = n + relEndWidth, barWidth = (x1.rangeBand() / (n + relEndWidth)); chart.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height1 + ")") .call(x1Axis); chart.append("g") .attr("class", "y axis") .call(y1Axis); chart.append("g") .append("line") .attr("stroke", "#222") .attr("stroke-width", 0.5) .attr("x1", 0) .attr("x2", width1) .attr("y1", y1(0)) .attr("y2", y1(0)); var mainHover = chart.selectAll("g.group") .data(data.filter(function(d) { return d.type != "Total"; })) .enter() .append("g") .attr("class", function(d){ return "group " + d.type + "-type " + d.sign; }) .attr("transform", function(d){ return "translate(" + x1(d.year) + ",0)"; }); mainHover.append("rect") // for capturing hover events .attr('width', x1.rangeBand()) .attr('height', height1) .attr('fill', 'none') //.attr('stroke', "#000") //.attr('stroke-width', 1) .attr('pointer-events', 'all') .on("mouseover", hoverMainOn) .on("mouseout", hoverOff); var mainTips = mainHover.selectAll("rect").append("g") var mainBars = mainHover.append("rect") .attr("class", function(d){ return "bar " + d.type + "-type " + d.sign; }) .attr("fill", function(d){ return typeColor(d.type); }) .attr("x", 0) .attr("width", function(d){ if(d.type == "YearEnd"){ return x1.rangeBand(); } else { return 0; }}) .attr("y", function(d){ return y1(d.start); }) .attr("height", 0) mainBars.transition() .duration(speed) .attr("y", function(d){ return Math.min(y1(d.start), y1(d.end)); }) .attr("height", function(d){ return Math.abs(y1(d.start) - y1(d.end)); }) mainBars.on("mouseover", hoverMainOn) .on("mouseout", hoverOff) mainBars.filter(function(d) { return d.type != "YearEnd" }).append("line") .attr("class", "connector") .attr("x1", function(d){ return x1(d.year) + x1Type(d.type) - (x1.rangeBand() - x1Type.rangeBand()) / 2; }) .attr("x2", function(d){ return x1(d.year) + x1Type(d.type) - (x1.rangeBand() + x1Type.rangeBand()) / 2; }) .attr("y1", function(d){ return y1(d.end); }) .attr("y2", function(d){ return y1(d.end); }); // Add small multiples - changes by type of change var subcharts = svg.selectAll("g.subchart") .data(nested); subcharts.enter().append("g") .attr("class", "subchart") .attr("transform", function(d, i){ return "translate(" + ((i + 1) * margin.left + i * (margin.right + width2)) + "," + (height1 + 2 * margin.top + margin.bottom) + ")"; }); // Add x-axes and rotate text subcharts.append("g") .attr("class", "x axis") .attr("transform", "translate(0," + height2 + ")") .call(x2Axis) .selectAll("text") .attr("y", 0) .attr("x", -9) .attr("dy", ".35em") .attr("transform", "rotate(270)") .style("text-anchor", "end"); // Add y-axes subcharts.append("g") .attr("class", "y axis") .call(y2Axis); // Add line at zero subcharts.append("g") .append("line") .attr("stroke", "#222") .attr("stroke-width", 0.5) .attr("x1", 0) .attr("x2", width2) .attr("y1", y2(0)) .attr("y2", y2(0)); // Add title for chart subcharts.append("text") .attr("y", 0) .attr("x", width2 / 2) .attr("text-anchor", "middle") .text(function(d) {return d.key;}) var subBars = subcharts.selectAll("g.bar") .data(function(d) {return d.values}); var subHover = subBars.enter() .append("g") .attr("class", function(d){ return "bar " + d.type + "-type " + d.sign; }) .attr("transform", function(d){ return "translate(" + x2(d.year) + ",0)"; }) subHover.append("rect") // for capturing hover events .attr('width', x2.rangeBand()) .attr('height', height2) .attr('fill', 'none') .attr('pointer-events', 'all') .on("mouseover", hoverSubOn) .on("mouseout", hoverOff) subHover.append("rect") .attr("fill", function(d){ return typeColor(d.type); }) .attr("x", 0) .attr("width", x2.rangeBand()) .attr("y", y2(0)) .attr("height", 0) .attr('pointer-events', 'none') .transition() .delay(speed * 3 / 2) .duration(speed) .attr("y", function(d){ return Math.min(y2(0), y2(d.value)); }) .attr("height", function(d){ return Math.abs(y2(d.end) - y2(d.start)); }); function hideChanges(delay){ mainHover // for capturing hover events .transition() .delay(delay) .duration(speed) .attr("transform", function(d){ return "translate(" + x1(d.year) + ",0)"; }) .selectAll("rect") .attr("width", x1.rangeBand()); mainBars.transition() .delay(delay) .duration(speed) .attr("width", function(d){ if(d.type == "YearEnd"){ return x1.rangeBand(); } else { return 0; }}); } function showChanges(delay){ mainHover.transition() .delay(delay) .duration(speed) .attr("transform", function(d){ return "translate(" + (x1(d.year) + x1Type(d.type) - (x1.rangeBand() - x1Type.rangeBand()) / 2) + ",0)"; }) .attr("width", x1Type.rangeBand()); mainBars.transition() .delay(delay) .duration(speed) //.attr("x", function(d){ return x1(d.year) + x1Type(d.type) - (x1.rangeBand() - x1Type.rangeBand()) / 2; }) .attr("width", x1Type.rangeBand()); } showChanges(speed * 3 / 2); function hoverMainOn(h) { mainBars .filter(function(d) { return (d.year != h.year) || (d.type != h.type); }) .attr("opacity", 0.4); subBars .filter(function(d){ return d.year != h.year; }) .attr("opacity", 0.4); //tooltips.show(h); } function hoverSubOn(h) { mainBars .filter(function(d) { return (d.year != h.year) || ((d.type != h.type) && (h.type != "Total")); }) .attr("opacity", 0.4); subBars .filter(function(d){ return d.year != h.year; }) .attr("opacity", 0.4); //tooltips.show(h); } function hoverOff(h) { mainBars.attr("opacity", 1.0); subBars.attr("opacity", 1.0); } d3.selectAll("input[name=mode]") .on("change", function() { console.log(this.value); if(this.value == "total"){ hideChanges(0); } else if(this.value == "sign") { showChanges(0); chart.classed("signed", true); subcharts.classed("signed", true); } else if(this.value == "type") { showChanges(0); chart.classed("signed", false); subcharts.classed("signed", false); } }); }); var dollarFormat = d3.format("$,.2f"); function dollarFormatter(n) { n = Math.round(n); var result = n; if (Math.abs(n) > 1000) { result = Math.round(n/1000) + 'K'; } return '$' + result; }