Skip to content

Instantly share code, notes, and snippets.

@milroc
Last active August 29, 2015 13:57
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save milroc/9845999 to your computer and use it in GitHub Desktop.
Save milroc/9845999 to your computer and use it in GitHub Desktop.
Fitts's Bar, II

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 ensures that there is a minimum interactive plane, in order to allow the user to always interact with a data point, even if the data produced has no display. This combats Fitts's Law by ensuring that the area of the bar is above some bound, however the user still must adjust the cursor. See it in action on The Global Mail

Forked from Fitts's Bar, I which allows the user to interact with only the x dimension in order to invoke a tooltip.

<!DOCTYPE html>
<meta charset="utf-8">
<html>
<head>
<!-- CDN IMPORTS -->
<link href='http://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>
<!-- LOCAL IMPORTS -->
<!-- IN GIST -->
<link rel="stylesheet/less" type="text/css" href="style.less">
</head>
<body>
<label><input type="checkbox"> Sort values</label>
<!-- CDN IMPORTS -->
<!-- // <script src="http://cdnjs.cloudflare.com/ajax/libs/less.js/1.7.0/less.min.js"></script> -->
<!-- // <script src="http://d3js.org/d3.v3.min.js"></script> -->
<!-- LOCAL IMPORTS -->
<script src="/milroc/raw/9842284/d3.v3.min.js"></script>
<script src="http://labratrevenge.com/d3-tip/javascripts/d3.tip.min.js"></script>
<script src="/milroc/raw/9842284/less.min.js"></script>
<!-- IN GIST -->
<script src="src.js"></script>
</body>
</html>
var dataBlock = "/milroc/raw/9842179/";
var interactionMin = 40;
var margin = {top: 100, 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(dataBlock + "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 tip = d3.tip()
.attr('class', 'd3-tip')
.offset([-10, 0])
.html(function(d) {
return '<h5>' + d.letter + '</h5><p>' + tooltipPercent(d.frequency) + '</p>';
});
svg.call(tip);
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: function(d) {
var size = height - y(d.frequency);
return (size < interactionMin)?interactionMin:size;
},
y: function(d) {
var size = height - y(d.frequency);
return (size < interactionMin)?height - interactionMin:y(d.frequency);
},
'fill-opacity': 0.0,
})
.on('mouseover', tip.show)
.on('mouseout', tip.hide);
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);
}
});
@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-tip {
line-height: 1;
font-weight: bold;
padding: 5px 10px;
background: @bgColor;
opacity: .95;
border-radius: 2px;
text-align: center;
h5 {
border-bottom: 1px solid @axisColor;
}
border: 1px solid @axisColor;
}
@fernoftheandes
Copy link

Great work. One detail about the gist...currently, it is not including the data file. I had to go dig it from the other related gist.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment