Skip to content

Instantly share code, notes, and snippets.

@llad
Created September 25, 2012 02:53
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 llad/3779709 to your computer and use it in GitHub Desktop.
Save llad/3779709 to your computer and use it in GitHub Desktop.
Reusable D3 column chart with transparency dimension

See it in action on bl.ocks.org

Created to look at returns within a portfolio of investments, this shows the allocation to a particular investment using transparency of the columns. The more opaque the column, the greater the allocation in the portfolio.

This also connects to this Google Spreadsheet for data.

This builds on my column chart gist

function densityChart() {
var margin = { top: 30, right: 10, bottom: 50, left: 50 },
width = 420,
height = 420,
xRoundBands = 0.2,
yTickFormat = ".1%",
xValue = function(d) { return d[0]; },
yValue = function(d) { return d[1]; },
allocationValue = function(d) { return d[2]; },
xScale = d3.scale.ordinal(),
yScale = d3.scale.linear(),
yAxis = d3.svg.axis().scale(yScale).orient("left"),
xAxis = d3.svg.axis().scale(xScale);
function chart(selection) {
selection.each(function(data) {
var total;
// Convert data to standard representation greedily;
// this is needed for nondeterministic accessors.
data = data.map(function(d, i) {
return [xValue.call(data, d, i), yValue.call(data, d, i), allocationValue.call(data, d, i)];
});
total = data.reduce(function(previous, current) {
return previous + current[1] * current[2];
},0);
// Update the x-scale.
xScale.domain(data.map(function(d) { return d[0]; }))
.rangeRoundBands([0, width - margin.left - margin.right], xRoundBands);
// Update the y-scale.
yScale.domain(d3.extent(data.map(function(d) { return d[1]; })))
.range([height - margin.top - margin.bottom, 0]).nice();
// Select the svg element, if it exists.
var svg = d3.select(this).selectAll("svg").data([data]);
// Otherwise, create the skeletal chart.
var gEnter = svg.enter().append("svg").append("g");
gEnter.append("g").attr("class", "bars");
gEnter.append("g").attr("class", "y axis");
gEnter.append("g").attr("class", "y axis zero");
gEnter.append("g").attr("class", "y axis total");
gEnter.append("g").attr("class", "x axis");
// Update the outer dimensions.
svg.attr("width", width).attr("height", height);
// Update the inner dimensions.
var g = svg.select("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
// Update the return line
var returnline = svg.select(".bars").selectAll(".returnline").data(data);
returnline.enter()
.append("line")
.attr("class", function(d, i) { return d[1] < total ? "return negative" : "return positive"; })
.attr("y1", function(d) { return Y(d); })
.attr("y2", function(d) { return Y(d); })
.attr("x1", function(d) { return X(d); })
.attr("x2", function(d) { return X(d) + xScale.rangeBand() + 1; }); // +1 to line up with box end
// Update the bars.
var bar = svg.select(".bars").selectAll(".bar").data(data);
bar.enter()
.append("rect")
.attr("class", function(d, i) { return d[1] < total ? "bar negative" : "bar positive"; })
.attr("x", function(d) { return X(d); })
.attr("y", function(d, i) { return d[1] < total ? yScale(total) : Y(d); })
.attr("width", xScale.rangeBand())
.attr("height", function(d, i) { return Math.abs(Y(d) - yScale(total)); })
.style("fill-opacity", function(d, i) { return d[2]; });
// Update the x-axis
g.select(".x.axis")
.attr("transform", "translate(0," + (height - margin.top - margin.bottom) + ")")
.call(xAxis
.orient("bottom"));
// Update the y-axis
g.select(".y.axis").call(yAxis
.tickFormat(d3.format(yTickFormat)));
// Update the y-axis zero line
g.select(".y.axis.zero").call(yAxis
.tickFormat("")
.tickValues([0])
.tickSize(-xScale.rangeExtent()[1],0));
// Update the y-axis total line
g.select(".y.axis.total").call(yAxis
.tickFormat("")
.tickValues([total])
.tickSize(-xScale.rangeExtent()[1],0));
});
}
// The x-accessor for the path generator; xScale ∘ xValue.
function X(d) {
return xScale(d[0]);
}
// The location of the zero line
function Y0() {
return yScale(0);
}
// The y-accessor for the path generator; yScale ∘ yValue.
function Y(d) {
return yScale(d[1]);
}
chart.margin = function(_) {
if (!arguments.length) return margin;
margin = _;
return chart;
};
chart.width = function(_) {
if (!arguments.length) return width;
width = _;
return chart;
};
chart.height = function(_) {
if (!arguments.length) return height;
height = _;
return chart;
};
chart.x = function(_) {
if (!arguments.length) return xValue;
xValue = _;
return chart;
};
chart.y = function(_) {
if (!arguments.length) return yValue;
yValue = _;
return chart;
};
chart.allocation = function(_) {
if (!arguments.length) return allocationValue;
allocationValue = _;
return chart;
};
chart.yTickFormat = function(_) {
if (!arguments.length) return yTickFormat;
yTickFormat = _;
return chart;
};
return chart;
}
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<script src="http://d3js.org/d3.v2.min.js?2.10.0"></script>
<!-- for c9.io dev --> <script type="text/javascript" src="../d3.v2.js" charset="utf-8" ></script>
<script type="text/javascript" src="density-column.js"></script>
<style>
.bars rect.positive{
fill: steelblue;
stroke: steelblue;
stroke-width: 1px;
stroke-opacity: .5;
shape-rendering: crispEdges;
}
.bars rect.negative {
fill: brown;
stroke: brown;
stroke-width: 1px;
stroke-opacity: .5;
shape-rendering: crispEdges;
}
line.return.positive {
stroke-width: 2px;
stroke-opacity: 1;
stroke: steelblue;
shape-rendering: crispEdges;
}
line.return.negative {
fill: none;
stroke-width: 2px;
stroke-opacity: 1;
stroke: brown;
shape-rendering: crispEdges;
}
.axis text {
font: 10px sans-serif;
}
.axis path, .axis line {
fill: none;
stroke: #000;
shape-rendering: crispEdges;
}
.axis.zero line.tick {
fill: none;
stroke: lightgray;
shape-rendering: crispEdges;
}
.axis.total line.tick {
fill: none;
stroke: #000;
stroke-width: 1.5px;
shape-rendering: crispEdges;
}
</style>
</head>
<body>
<div id="example"></div>
<script>
function myCallback(spreadsheetdata) {
// Callback from Google spreadsheet API for JSONP
// Gets the data from a published spreadsheet and then passes it to the chart
var data = [];
for (var i = 0; i < spreadsheetdata.feed.entry.length; i++) {
var entry = spreadsheetdata.feed.entry[i];
var row = {};
row.inv = entry.gsx$investment.$t;
row.ror = entry.gsx$return.$t;
row.allocation = entry.gsx$allocation.$t;
data[i] = row;
};
d3.select("#example")
.datum(data)
.call(densityChart()
.width(960)
.height(500)
.x(function(d) { return d.inv; })
.y(function(d) { return +d.ror; })
.allocation(function(d) { return +d.allocation; })
.yTickFormat(".1%"));
};
</script>
<script src="https://spreadsheets.google.com/feeds/list/0Agxrt1aeoXHOdFlWbl9tQm12cWRlRWdMVFY0bER4c2c/od6/public/values?alt=json-in-script&callback=myCallback"></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment