-
-
Save mattfullerton/9156556 to your computer and use it in GitHub Desktop.
This file contains hidden or 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
var chartHeight; | |
var chartWidth; | |
// ceiling value; needs to be set | |
var ceiling; | |
// Y scale will fit values from 0-10 within pixels 0 - height | |
var y; | |
var yneg; | |
/** | |
* Create an empty shell of a chart that bars can be added to | |
*/ | |
function displayStackedChart(chartId) { | |
// create an SVG element inside the div that fills 100% of the div | |
var vis = d3.select("#" + chartId).append("svg:svg"); | |
vis.attr("width", "100%").attr("height", chartHeight * 1) | |
// transform down to simulate making the origin bottom-left instead of top-left | |
// we will then need to always make Y values negative | |
.append("g").attr("class", "barChart").attr("transform", "translate(35, " + | |
(chartHeight - (chartHeight * 0.05)) + ")"); | |
var yAxis = d3.svg.axis().scale(yneg).orient("left").ticks(10).tickFormat( | |
d3.format("4.0d")); | |
/* var yAxis = d3.svg.axis() | |
.scale(y) | |
.orient("left"); */ | |
vis.append("g").attr("width", "10%").attr("height", chartHeight).append("g") | |
.attr("class", "axis baraxis").attr("fill", "black").attr("transform", | |
"translate(40,-" + chartHeight * 0.05 + ")").call(yAxis); | |
} | |
/** | |
* Add or update a bar of data in the given chart | |
* | |
* The data object expects to have an 'id' property to identify itself (id == a single bar) | |
* and have object properties with numerical values for each property in the 'propertyNames' array. | |
*/ | |
function addData(chartId, data, color) { | |
var transitionDuration = 50; | |
// if data already exists for this data ID, update it instead of adding it | |
var existingBarNode = document.querySelectorAll("#" + chartId + "_" + data.id); | |
if (existingBarNode.length > 0) { | |
var existingBar = d3.select(existingBarNode.item(0)); | |
// update the data on each data point defined by 'propertyNames' | |
var total = existingBar.selectAll("." + chartId + "_total"); | |
if (total[0].length < 1) | |
existingBar.append("text").attr("class", | |
chartId + "_total").attr("font-size", 15).attr("font-weight", | |
"bold").attr("font-family", "monospace").attr("fill", "black").attr( | |
"y", barY(data, propertyNames[+(propertyNames.length) - 1]) - 10).attr( | |
"x", (chartWidth / 2) - 25).text(runningTotal(data, propertyNames[+ | |
(propertyNames.length) - 1])); | |
//Add running totals | |
//d3.selectAll("." + chartId + "_total").remove(); | |
existingBar.select("." + chartId + "_total").transition().ease("linear") | |
.duration(transitionDuration).attr("y", barY(data, propertyNames[+( | |
propertyNames.length) - 1]) - 10).text(runningTotal(data, | |
propertyNames[+(propertyNames.length) - 1])); | |
; | |
for (index in propertyNames) { | |
existingBar.select("rect." + propertyNames[index]) | |
.transition().ease("linear").duration(transitionDuration) | |
.attr("y", barY(data, propertyNames[index])) | |
.attr("height", barHeight(data, propertyNames[index])); | |
} | |
} else { | |
// it's new data so add a bar | |
var barDimensions = updateBarWidthsAndPlacement(chartId); | |
// select the chart and add the new bar | |
var barGroup = d3.select("#" + chartId).selectAll("g.barChart") | |
.append("g") | |
.attr("class", "bar") | |
.attr("id", chartId + "_" + data.id) | |
.attr("style", "opacity:0.7"); | |
// now add each data point to the stack of this bar | |
for (index in propertyNames) { | |
barGroup.append("rect") | |
.attr("class", propertyNames[index]) | |
.attr("width", (barDimensions.barWidth - 1)) | |
.attr("x", function() { | |
return 10 + (barDimensions.numBars - 1) * barDimensions | |
.barWidth; | |
}) | |
.attr("y", barY(data, propertyNames[index])) | |
.attr("height", barHeight(data, propertyNames[index])) | |
.style("fill", color) | |
.style("stroke", "black"); | |
} | |
} | |
} | |
/** | |
* Remove a bar of data in the given chart | |
* | |
* The data object expects to have an 'id' property to identify itself (id == a single bar) | |
* and have object properties with numerical values for each property in the 'propertyNames' array. | |
*/ | |
function removeData(chartId, barId) { | |
var existingBarNode = document.querySelectorAll("#" + chartId + "_" + barId); | |
if (existingBarNode.length > 0) { | |
// bar exists so we'll remove it | |
var barGroup = d3.select(existingBarNode.item()); | |
barGroup | |
.transition().duration(200) | |
.remove(); | |
} | |
} | |
/** | |
* Update the bar widths and x positions based on the number of bars. | |
* @returns {barWidth: X, numBars:Y} | |
*/ | |
function updateBarWidthsAndPlacement(chartId) { | |
/** | |
* Since we dynamically add/remove bars we can't use data indexes but must determine how | |
* many bars we have already in the graph to calculate x-axis placement | |
*/ | |
var numBars = document.querySelectorAll("#" + chartId + " g.bar").length + 1; | |
// determine what the width of all bars should be | |
var barWidth = (chartWidth / numBars) - 50; //leave space for axis | |
if (barWidth > 50) { | |
//barWidth=50; | |
} | |
// reset the width and x position of each bar to fit | |
var barNodes = document.querySelectorAll(("#" + chartId + | |
" g.barChart g.bar")); | |
for (var i = 0; i < barNodes.length; i++) { | |
d3.select(barNodes.item(i)).selectAll("rect") | |
//.transition().duration(10) // animation makes the display choppy, so leaving it out | |
.attr("x", i * barWidth) | |
.attr("width", (barWidth - 1)); | |
} | |
return { | |
"barWidth": barWidth, | |
"numBars": numBars | |
}; | |
} | |
/* | |
* Function to calculate the Y position of a bar | |
*/ | |
function barY(data, propertyOfDataToDisplay) { | |
return -y(runningTotal(data, propertyOfDataToDisplay)); | |
} | |
function runningTotal(data, propertyOfDataToDisplay) { | |
/* | |
* Determine the baseline by summing the previous values in the data array. | |
* There may be a cleaner way of doing this with d3.layout.stack() but it | |
* wasn't obvious how to do so while playing with it. | |
*/ | |
var baseline = 0; | |
for (var j = 0; j < index; j++) { | |
baseline = baseline + data[propertyNames[j]]; | |
} | |
// make the y value negative 'height' instead of 0 due to origin moved to bottom-left | |
return (baseline + data[propertyOfDataToDisplay]); | |
} | |
/* | |
* Function to calculate height of a bar | |
*/ | |
function barHeight(data, propertyOfDataToDisplay) { | |
return y(data[propertyOfDataToDisplay]); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment