Typical bar charts have issues with Fitts’s law for interactions. That is that the area of the bar prevents the user from easily moving from one bar to the other. Additionally when the bars are at some minimum area (or there is no area), the user may be unable to interact with the data.
This example fixes the available interactive dimension to be the x dimension only. Allowing the interaction area of all bars to be constant. See this in action on the New York Times
See Fitts’s Bar, II for another approach.
This is also an example of how to reference two blocks for:
It’s helpful in that data and common imports (e.g: less.js, topojson, or a specific version of d3) do not have to be redundantly copied per block that is created.
<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<!-- CDN IMPORTS -->
<link href='//fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<!-- LOCAL IMPORTS -->
<!-- IN GIST -->
<link rel="stylesheet/css" type="text/css" href="style.css">
</head>
<body>
<label><input type="checkbox"> Sort values</label>
<!-- CDN IMPORTS -->
<!-- LOCAL IMPORTS -->
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="less.min.js"></script>
<!-- IN GIST -->
<script src="src.js"></script>
</body>
</html>
letter frequency
A .08167
B .01492
C .02780
D .04253
E .12702
F .02288
G .02022
H .06094
I .06973
J .00153
K .00747
L .04025
M .02517
N .06749
O .07507
P .01929
Q .00098
R .05987
S .06333
T .09056
U .02758
V .01037
W .02465
X .00150
Y .01971
Z .00074
var margin = {top: 20, right: 20, bottom: 30, left: 40},
width = 960 - margin.left - margin.right,
height = 500 - margin.top - margin.bottom;
var formatPercent = d3.format(".0%"),
tooltipPercent = d3.format(".2%");
var x = d3.scale.ordinal()
.rangeRoundBands([0, width], .1, 1);
var y = d3.scale.linear()
.range([height, 0]);
var xAxis = d3.svg.axis()
.scale(x)
.orient("bottom");
var yAxis = d3.svg.axis()
.scale(y)
.orient("left")
.tickFormat(formatPercent);
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 + ")");
d3.tsv("letter.tsv", function(error, data) {
data.forEach(function(d) {
d.frequency = +d.frequency;
});
x.domain(data.map(function(d) { return d.letter; }));
y.domain([0, d3.max(data, function(d) { return d.frequency; })]);
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");
var bars = svg.selectAll(".bar")
.data(data),
barsEnter = bars.enter().append("g")
.classed('bar', true)
.attr({
transform: function(d) { return 'translate(' + x(d.letter) + ',' + 0 + ')'; },
}),
displayEnter = barsEnter.append('rect')
.classed('display', true)
.attr({
width: x.rangeBand(),
height: function(d) { return height - y(d.frequency); },
y: function(d) { return y(d.frequency); }
}),
interactEnter = barsEnter.append('rect')
.classed('interact', true)
.attr({
width: x.rangeBand(),
height: height,
'fill-opacity': 0.0,
})
.call(d3.helper.tooltip().attr({class: "d3-helper-tooltip" })
.html(function(d, i) { return '<h5>' + d.letter + '</h5><p>' + tooltipPercent(d.frequency) + '</p>'; }));
d3.select("input").on("change", change);
var sortTimeout = setTimeout(function() {
d3.select("input").property("checked", true).each(change);
}, 2000);
function change() {
clearTimeout(sortTimeout);
// Copy-on-write since tweens are evaluated after a delay.
var x0 = x.domain(data.sort(this.checked
? function(a, b) { return b.frequency - a.frequency; }
: function(a, b) { return d3.ascending(a.letter, b.letter); })
.map(function(d) { return d.letter; }))
.copy();
var transition = svg.transition().duration(750),
delay = function(d, i) { return i * 50; };
transition.selectAll(".bar")
.delay(delay)
.attr({
transform: function(d) { return 'translate(' + x0(d.letter) + ',' + 0 + ')'; },
});
transition.select(".x.axis")
.call(xAxis)
.selectAll("g")
.delay(delay);
}
});
d3.helper = {};
d3.helper.tooltip = function() {
var tooltipDiv;
var bodyNode = d3.select('body').node();
var attrs = {};
var html = d3.functor('');
var styles = {};
function tooltip(selection) {
selection.on('mouseenter.tooltip', function(pD, pI) {
d3.select(this).classed('selected-tooltip', true);
var name, value;
// Clean up lost tooltips
d3.select('body').selectAll('div.tooltip').remove();
// Append tooltip
tooltipDiv = d3.select('body').append('div');
tooltipDiv.attr(attrs);
tooltipDiv.style(styles);
var absoluteMousePos = d3.mouse(bodyNode);
tooltipDiv.style({
left: (absoluteMousePos[0] + 15)+'px',
top: (absoluteMousePos[1] - 10)+'px',
position: 'absolute',
'z-index': 1001
});
// Add text using the accessor function, Crop text arbitrarily
tooltipDiv.style('width', function(d, i) { return (html(pD, pI).length > 80) ? '300px' : null; })
.html(function(d, i) {return html(pD, pI);});
})
.on('mousemove.tooltip', function(pD, pI) {
d3.select(this).classed('selected-tooltip', true);
// Move tooltip
var absoluteMousePos = d3.mouse(bodyNode);
if (!tooltipDiv) {
tooltipDiv = d3.select('body').append('div');
tooltipDiv.attr(attrs);
tooltipDiv.style(styles);
}
tooltipDiv.style({
left: (absoluteMousePos[0] + 15)+'px',
top: (absoluteMousePos[1] - 10)+'px'
});
// Keep updating the text, it could change according to position
tooltipDiv.html(function(d, i) { return html(pD, pI); });
})
.on('mouseleave.tooltip', function(pD, pI) {
d3.select(this).classed('selected-tooltip', false);
// Remove tooltip
tooltipDiv.remove();
});
}
tooltip.attr = function(_x) {
if (!arguments.length) return attrs;
attrs = _x;
return this;
};
tooltip.style = function(_x) {
if (!arguments.length) return styles;
styles = _x;
return this;
};
tooltip.html = function(_x) {
if (!arguments.length) return html;
html = d3.functor(_x);
return this;
};
return tooltip;
};
body {
position: relative;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: #FFF0A5;
width: 960px;
color: #8E2800;
}
.axis text {
font: 10px Helvetica;
fill: #8E2800;
}
.axis path,
.axis line {
fill: none;
stroke: #FFB03B;
shape-rendering: crispEdges;
}
.bar .display {
fill: #468966;
}
.bar .interact {
stroke: #FFB03B;
}
.selected-tooltip {
fill: #FFF0A5;
fill-opacity: 0.3;
}
.x .axis path {
display: none;
}
label {
position: absolute;
top: 10px;
right: 10px;
}
.d3-helper-tooltip {
background: #FFF0A5;
opacity: .95;
padding: 5px 10px;
border-radius: 2px;
text-align: center;
}
.d3-helper-tooltip h5 {
border-bottom: 1px solid #FFB03B;
}
@bgColor: #FFF0A5;
@dataColor: #468966;
@axisColor: #FFB03B;
@textColor: #8E2800;
body {
position: relative;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
background-color: @bgColor;
width: 960px;
color: @textColor;
}
.axis {
text {
font: 10px Helvetica;
fill: @textColor;
}
path, line {
fill: none;
stroke: @axisColor;
shape-rendering: crispEdges;
}
}
.bar {
.display {
fill: @dataColor;
}
.interact {
stroke: @axisColor;
}
}
.selected-tooltip {
fill: @bgColor;
fill-opacity: 0.3;
}
.x {
.axis {
path {
display: none;
}
}
}
label {
position: absolute;
top: 10px;
right: 10px;
}
.d3-helper-tooltip {
background: @bgColor;
opacity: .95;
padding: 5px 10px;
border-radius: 2px;
text-align: center;
h5 {
border-bottom: 1px solid @axisColor;
}
}