Back in the old days, when many data sets were still small, stem-and-leaf plots were a popular method of representing quantitative data. The example data shown in the text area comes from the cover of John Tukey's Exploratory Data Analysis. The stem-and-leaf plot updates as you change the data. Try adding fractions and negative values. Hover over the leaves to see the original values.
Last active
March 1, 2016 14:08
-
-
Save jeroenjanssens/6395842 to your computer and use it in GitHub Desktop.
Stem-and-Leaf Plot
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<meta charset="utf-8"> | |
<style> | |
body { | |
background: #fcfcfa; | |
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; | |
margin: auto; | |
position: relative; | |
width: 960px; | |
} | |
ul.stems { | |
list-style-type: none; | |
} | |
ul.stems > li > div { | |
display: inline-block; | |
padding: .1em .5em; | |
border-right: 2px solid #000; | |
text-align: right; | |
font-weight: bold; | |
width: 2em; | |
} | |
ul.leaves { | |
display: inline-block; | |
padding-left: .5em; | |
} | |
ul.leaves > li { | |
display: inline-block; | |
width: .7em; | |
text-align: center; | |
position: relative; | |
cursor: default; | |
padding: .1em 0; | |
} | |
ul.leaves > li:hover { | |
background: #000; | |
color: #fff; | |
} | |
ul.leaves > li:hover:after { | |
background: #ccc; | |
border: 1px solid #000; | |
color: #000; | |
content: attr(data-value); | |
position: absolute; | |
padding: .2em .5em; | |
top: -2em; | |
left: 1em; | |
pointer-events: none; | |
font-size: 80%; | |
} | |
textarea { | |
border: 1px solid #333; | |
width: 90%; | |
} | |
</style> | |
<div></div> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="stem.js"></script> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
stem_and_leaf = (value, base) -> | |
sign = if value < 0 then "-" else "" # String because "-0" != "0" | |
value = Math.abs(Math.round(value)) | |
stem = sign + Math.floor(value / base) | |
leaf = value % base | |
[stem, leaf] | |
stems_and_leaves = (data, base=10) -> | |
# Sort the data so that the leaves appear in the correct order | |
data.sort((a,b) -> a-b) | |
# Determine lowest and highest stem | |
min = data[0] | |
max = data[data.length - 1] | |
start = +stem_and_leaf(min, base)[0] | |
end = +stem_and_leaf(max, base)[0] | |
# Add all the necessary stems, including "-0" if necessary | |
stems = {} | |
for stem in [start..end] | |
if stem is 0 and min < 0 | |
stems["-0"] = [] | |
stems[""+stem] = [] | |
# Add the leaves to the stems | |
for value in data | |
[stem, leaf] = stem_and_leaf(value, base) | |
stems[stem].push {'leaf': leaf, 'value': value} | |
stemdata = [] | |
for stem, leaves of stems | |
stemdata.push({'stem': stem, 'leaves': leaves}) | |
stemdata | |
update = -> | |
# Get data from textarea | |
data = text.node().value.split(",").map (x) -> parseFloat(x) | |
data = (x for x in data when not isNaN(x)) | |
console.log data | |
stemdata = stems_and_leaves(data) | |
# Bind data to stems | |
stems = plot.selectAll("li.stem") | |
.data(stemdata, (d) -> | |
d.stem+(x.value for x in d.leaves).join(',') | |
) | |
# Stem enter | |
stem_enter = stems.enter() | |
.append("li") | |
.attr("class", "stem") | |
stem_enter.append("div") | |
# Leaves enter | |
stem_enter.append("ul") | |
.attr("class", "leaves") | |
.selectAll("li.leaf") | |
.data((d) -> d.leaves) | |
.enter() | |
.append("li") | |
.attr("class", "leaf") | |
.attr('data-value', (d) -> d.value) | |
.text((d) -> d.leaf) | |
# Re-order the stems | |
stems.sort((a, b) -> | |
x = a.stem | |
y = b.stem | |
if (x is "-0") and (y is "0") then return -1 | |
if (y is "-0") and (x is "0") then return 1 | |
if (+x < +y) then -1 else 1 | |
) | |
# Stem update | |
stems.select("div").text((d) -> d.stem) | |
# Stem exit | |
stems.exit().remove() | |
initial_data = [17, 32, 47, 53, 60, 61, 64, 67, 70, 70, 71, 72, 73, 73, 74, 76, 77, 79, 81, 82, 83, 83, 83, 83, 84, 85, 86, 87, 87, 88, 89, 90, 91, 91, 92, 94, 94, 95, 96, 97, 98, 98, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 102, 103, 103, 103, 103, 104, 106, 106, 106, 106, 107, 107, 107, 107, 108, 109, 109, 110, 111, 111, 111, 112, 112, 113, 114, 114, 114, 115, 116, 117, 117, 119, 120, 120, 120, 120, 121, 121, 122, 122, 122, 123, 124, 124, 125, 125, 126, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 132, 132, 132, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 138, 139, 140, 140, 142, 143, 144, 145, 145, 145, 145, 145, 147, 149, 152, 155, 157, 159] | |
div = d3.select("div") | |
text = div.append("textarea") | |
plot = div.append("ul").attr("class", "stems") | |
text.text(initial_data.join(",")) | |
text.on("keyup", -> update()) | |
update() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Generated by CoffeeScript 1.6.1 | |
(function() { | |
var div, initial_data, plot, stem_and_leaf, stems_and_leaves, text, update; | |
stem_and_leaf = function(value, base) { | |
var leaf, sign, stem; | |
sign = value < 0 ? "-" : ""; | |
value = Math.abs(Math.round(value)); | |
stem = sign + Math.floor(value / base); | |
leaf = value % base; | |
return [stem, leaf]; | |
}; | |
stems_and_leaves = function(data, base) { | |
var end, leaf, leaves, max, min, start, stem, stemdata, stems, value, _i, _j, _len, _ref; | |
if (base == null) { | |
base = 10; | |
} | |
data.sort(function(a, b) { | |
return a - b; | |
}); | |
min = data[0]; | |
max = data[data.length - 1]; | |
start = +stem_and_leaf(min, base)[0]; | |
end = +stem_and_leaf(max, base)[0]; | |
stems = {}; | |
for (stem = _i = start; start <= end ? _i <= end : _i >= end; stem = start <= end ? ++_i : --_i) { | |
if (stem === 0 && min < 0) { | |
stems["-0"] = []; | |
} | |
stems["" + stem] = []; | |
} | |
for (_j = 0, _len = data.length; _j < _len; _j++) { | |
value = data[_j]; | |
_ref = stem_and_leaf(value, base), stem = _ref[0], leaf = _ref[1]; | |
stems[stem].push({ | |
'leaf': leaf, | |
'value': value | |
}); | |
} | |
stemdata = []; | |
for (stem in stems) { | |
leaves = stems[stem]; | |
stemdata.push({ | |
'stem': stem, | |
'leaves': leaves | |
}); | |
} | |
return stemdata; | |
}; | |
update = function() { | |
var data, stem_enter, stemdata, stems, x; | |
data = text.node().value.split(",").map(function(x) { | |
return parseFloat(x); | |
}); | |
data = (function() { | |
var _i, _len, _results; | |
_results = []; | |
for (_i = 0, _len = data.length; _i < _len; _i++) { | |
x = data[_i]; | |
if (!isNaN(x)) { | |
_results.push(x); | |
} | |
} | |
return _results; | |
})(); | |
console.log(data); | |
stemdata = stems_and_leaves(data); | |
stems = plot.selectAll("li.stem").data(stemdata, function(d) { | |
return d.stem + ((function() { | |
var _i, _len, _ref, _results; | |
_ref = d.leaves; | |
_results = []; | |
for (_i = 0, _len = _ref.length; _i < _len; _i++) { | |
x = _ref[_i]; | |
_results.push(x.value); | |
} | |
return _results; | |
})()).join(','); | |
}); | |
stem_enter = stems.enter().append("li").attr("class", "stem"); | |
stem_enter.append("div"); | |
stem_enter.append("ul").attr("class", "leaves").selectAll("li.leaf").data(function(d) { | |
return d.leaves; | |
}).enter().append("li").attr("class", "leaf").attr('data-value', function(d) { | |
return d.value; | |
}).text(function(d) { | |
return d.leaf; | |
}); | |
stems.sort(function(a, b) { | |
var y; | |
x = a.stem; | |
y = b.stem; | |
if ((x === "-0") && (y === "0")) { | |
return -1; | |
} | |
if ((y === "-0") && (x === "0")) { | |
return 1; | |
} | |
if (+x < +y) { | |
return -1; | |
} else { | |
return 1; | |
} | |
}); | |
stems.select("div").text(function(d) { | |
return d.stem; | |
}); | |
return stems.exit().remove(); | |
d3.select(self.frameElement).style("height", div.style("height")); | |
}; | |
initial_data = [17, 32, 47, 53, 60, 61, 64, 67, 70, 70, 71, 72, 73, 73, 74, 76, 77, 79, 81, 82, 83, 83, 83, 83, 84, 85, 86, 87, 87, 88, 89, 90, 91, 91, 92, 94, 94, 95, 96, 97, 98, 98, 99, 99, 99, 99, 99, 100, 100, 100, 101, 101, 102, 103, 103, 103, 103, 104, 106, 106, 106, 106, 107, 107, 107, 107, 108, 109, 109, 110, 111, 111, 111, 112, 112, 113, 114, 114, 114, 115, 116, 117, 117, 119, 120, 120, 120, 120, 121, 121, 122, 122, 122, 123, 124, 124, 125, 125, 126, 126, 127, 128, 129, 130, 131, 131, 131, 131, 132, 132, 132, 132, 133, 133, 134, 134, 134, 135, 135, 135, 136, 136, 136, 137, 138, 139, 140, 140, 142, 143, 144, 145, 145, 145, 145, 145, 147, 149, 152, 155, 157, 159]; | |
div = d3.select("div"); | |
text = div.append("textarea"); | |
plot = div.append("ul").attr("class", "stems"); | |
text.text(initial_data.join(",")); | |
text.on("keyup", function() { | |
return update(); | |
}); | |
update(); | |
}).call(this); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment