Skip to content

Instantly share code, notes, and snippets.

@seemantk
Last active September 1, 2016 18:54
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 seemantk/2f8ff2b168ec2c408603345d0203caea to your computer and use it in GitHub Desktop.
Save seemantk/2f8ff2b168ec2c408603345d0203caea to your computer and use it in GitHub Desktop.
Bar Chart V - linearGradients
license: gpl-3.0

This simple bar chart evolved from my d3v4 fork of mbostock's Bar Chart.

Bar charts in d3 are usually presented as simpler to comprehend examples of using d3 to visualize data. Such bar charts suffer from two issues that are not straightforward to overcome.

  1. The math is confusing. Given SVG's "reversed" y-coordinate system, the top of each bar is at the y-coordinate of the data point, but the height is something like the total height minus that data point's y-coordinate. Probably...possibly...I don't honestly know, I'd have to look it up. If it wasn't for bl.ocks examples of bar charts, I'd be stuck every time, trying to figure out the correct y and height attributes for each rectangle in a bar chart.

  2. Clickability of the bars. From a usability perspective, bar charts generally have varying interaction characterstics. Tall bars are generally easier to click or hover the mouse. Shorter bars are trickier. A common method is to bind the data to a second set of <rect> objects. The second <rect>s are full height and width and invisible/transparent. Most importantly, they capture the mouse hover and click events for the bar.

These issues are obviously surmountable, and most people don't think too much about them. However, over time I've come to use a slightly different method of rendering bar charts. The data is still joined to two different DOM elements, but the overall math is much easier.

How it Works

What if we could think of our <rect>s as containers for color? In other words, what if we had full-height <rect> objects, that we could fill with paint up to a certain height?

Lucky for us, SVG lets us do exacly that with SVG linearGradients. We define a linearGradient in the SVG's <defs> section. Each bar will use its own unique linearGradient. Therefore, we'll define one linearGradient for each letter represented in our example dataset.

  1. Each gradient need an "id" attribute. We use this attribute to refer to the gradient when we need to paint a rectangle.
letter frequency
A .08167
B .01492
C .02782
D .04253
E .12702
F .02288
G .02015
H .06094
I .06966
J .00153
K .00772
L .04025
M .02406
N .06749
O .07507
P .01929
Q .00095
R .05987
S .06327
T .09056
U .02758
V .00978
W .02360
X .00150
Y .01974
Z .00074
<!DOCTYPE html>
<meta charset="utf-8">
<style>
.bar {
stop-color: steelblue;
}
.bar.hovered {
stop-color: brown;
}
.empty {
stop-opacity: 0;
}
.x.axis path {
display: none;
}
</style>
<body>
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var x = d3.scaleBand()
.rangeRound([0, width], .1)
.paddingInner(0.1);
var y = d3.scaleLinear()
.range([height, 0]);
var p = d3.scaleLinear()
.range([0, 100]);
var xAxis = d3.axisBottom()
.scale(x)
;
var yAxis = d3.axisLeft()
.scale(y)
.ticks(10, "%");
var svg = d3.select("body").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var defs = d3.select("body svg").append("defs");
d3.tsv("data.tsv", type, function(error, data) {
if (error) throw error;
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
p.domain(y.domain());
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis)
.append("text")
.attr("transform", "rotate(-90)")
.attr("y", 6)
.attr("dy", ".71em")
.style("text-anchor", "end")
.text("Frequency");
svg.selectAll(".bar")
.data(data)
.enter().append("rect")
.attr("class", "bar")
.attr("x", function(d) { return x(d.letter); })
.attr("width", x.bandwidth())
.attr("y", 0)
.attr("height", height)
.style("fill", function(d, i) { return "url(#gradient_" + i + ")"; })
.on("mouseover", function(d, i) {
defs.select("#gradient_" + i).selectAll(".bar")
.classed("hovered", true)
})
.on("mouseout", function(d, i) {
defs.select("#gradient_" + i).selectAll(".bar")
.classed("hovered", false)
})
;
defs.selectAll("linearGradient")
.data(data)
.enter().append("linearGradient")
.attr("id", function(d, i) { return "gradient_" + i; })
.attr("x1", 0)
.attr("x2", 0)
.attr("y1", 1)
.attr("y2", 0)
.each(function(d) {
var self = d3.select(this);
var stops = ["bar start", "bar end", "empty end"];
self.selectAll("stop")
.data(stops)
.enter().append("stop")
.attr("class", function(s, i) { return s; })
.attr("offset", function(s, i) {
return (i ? p(d.frequency) : "0") + "%";
})
;
})
;
});
function type(d) {
d.frequency = +d.frequency;
return d;
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment