Skip to content

Instantly share code, notes, and snippets.

@mattfullerton
Forked from benjchristensen/index.html
Last active August 29, 2015 13:56
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 mattfullerton/9156556 to your computer and use it in GitHub Desktop.
Save mattfullerton/9156556 to your computer and use it in GitHub Desktop.
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