diff --git a/bullet/README.md b/bullet/README.md
index 800be42..05b6a00 100644
--- a/bullet/README.md
+++ b/bullet/README.md
@@ -12,3 +12,75 @@ Implementation based on work by
[Clint Ivy](http://projects.instantcognition.com/protovis/bulletchart/),
Jamie Love of [N-Squared Software](http://www.nsquaredsoftware.com/) and
[Jason Davies](http://www.jasondavies.com/).
+
+##Documentation
+
+A bullet chart consists of _ranges_, _markers_, and _measures_. A range
+indicates the satisfaction of a measure and is shown as the background colors,
+typically shades of gray. A marker indicates a target or previous value and is
+shown as a vertical bar. A measure is the actual value being shown and is the
+central bar, typically blue. Bullet charts also have an
+[axis](https://github.com/mbostock/d3/wiki/SVG-Axes). Titles and subtitles are
+not automatically generated.
+
+Currently, the range of values must start at 0 and may not include negative numbers.
+
+# d3.bullet()
+Create a new bullet chart.
+
+# bullet(selection)
+Apply the axis to a selection or transition. The selection must contain an
+`svg` or `g` element. Typically passed as the argument to
+[selection.call](https://github.com/mbostock/d3/wiki/Selections#call).
+
+# bullet.orient([orientation])
+Get or set the orientation of the bullet chart. The supported orientations are:
+`"top"`, `"bottom"`, `"left"`, and `"right"`. The default orientation is `"left"`.
+
+# bullet.width([width])
+# bullet.height([height])
+Get or set the width or height of the bullet chart, not including the axis.
+Width and height are always horizontal and vertical, respectively, regardless
+of _orient_. The default width is 380 and default height is 30.
+
+# bullet.ranges([ranges])
+Get or set the ranges of the bullet chart. _ranges_ should be an array of
+thresholds between ranges (the first range implicitly starts at 0), or a
+function that returns the array. The function will be passed the current datum
+`d` and the current index `i`, with the `this` context as the current DOM
+element. The default is a function that returns `d.ranges`.
+
+Ranges are rendered as `rect` elements with the `range` class and two others.
+The index is given by `s0`, `s1`, and so on. `s0` is always the largest range
+and has the lowest z-order. If there are three ranges, all three will have the
+`t3` class, indicating the total number of ranges. This is useful for styling
+bullet charts with varying numbers of ranges with different gradients.
+
+Stephen Few recommends at most five ranges, and ideally three.
+
+# bullet.markers([markers])
+Get or set the markers of the bullet chart. _markers_ should be an array of
+marker values, or a function that returns the array. The function will be
+passed the current datum `d` and the current index `i`, with the `this` context
+as the current DOM element. The default is a function that returns `d.markers`.
+
+Markers are rendered as `line` elements with the `marker` class and another
+indicating the index in the original data: `s0`, `s1`, and so on.
+
+Stephen Few suggests that multiple markers can be used to indicate the target
+value and a prior comparable value, e.g. from last year. He advises against
+more than two markers, and that one is usually sufficient.
+
+# bullet.measures([measures])
+Get or set the measures of the bullet chart. _measures_ should be an array of
+values, or a function that returns the array. The function will be passed the
+current datum `d` and the current index `i`, with the `this` context as the
+current DOM element. The default is a function that returns `d.measures`.
+
+Measures are rendered as `rect` elements with the `measure` class and another
+indicating the index: `s0`, `s1`, and so on. `s0` always corresponds to the
+first measure in the original array, even though the bars will be z-ordered to
+put the largest on the bottom.
+
+The first measure is the primary datapoint displayed by the chart. Stephen Few
+recommends a second measure for a forecasted value, if appropriate.
diff --git a/bullet/bullet.js b/bullet/bullet.js
index 8288b0e..3c242f1 100644
--- a/bullet/bullet.js
+++ b/bullet/bullet.js
@@ -7,9 +7,9 @@ d3.bullet = function() {
var orient = "left",
reverse = false,
vertical = false,
- ranges = bulletRanges,
- markers = bulletMarkers,
- measures = bulletMeasures,
+ ranges = function(d){return d.ranges},
+ markers = function(d){return d.markers},
+ measures = function(d){return d.measures},
width = 380,
height = 30,
xAxis = d3.svg.axis();
@@ -17,13 +17,17 @@ d3.bullet = function() {
// For each small multiple…
function bullet(g) {
g.each(function(d, i) {
- var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
- markerz = markers.call(this, d, i).slice().sort(d3.descending),
- measurez = measures.call(this, d, i).slice().sort(d3.descending),
+ var rangez = typeof ranges === "function" ? ranges.call(this, d, i) : ranges,
+ markerz = typeof markers === "function" ? markers.call(this, d, i) : markers,
+ measurez = typeof measures === "function" ? measures.call(this, d, i) : measures,
g = d3.select(this),
extentX,
extentY;
+ rangez = rangez.slice().sort(d3.descending);
+ markerz = markerz.slice();
+ measurez = measurez.slice();
+
var wrap = g.select("g.wrap");
if (wrap.empty()) wrap = g.append("g").attr("class", "wrap");
@@ -32,12 +36,12 @@ d3.bullet = function() {
wrap.attr("transform", "rotate(90)translate(0," + -width + ")");
} else {
extentX = width, extentY = height;
- wrap.attr("transform", "translate(0)");
+ wrap.attr("transform", null);
}
// Compute the new x-scale.
var x1 = d3.scale.linear()
- .domain([0, Math.max(rangez[0], markerz[0], measurez[0])])
+ .domain([0, Math.max(rangez[0], d3.max(markerz), d3.max(measurez))])
.range(reverse ? [extentX, 0] : [0, extentX]);
// Retrieve the old x-scale, if this is an update.
@@ -57,7 +61,7 @@ d3.bullet = function() {
.data(rangez);
range.enter().append("rect")
- .attr("class", function(d, i) { return "range s" + i; })
+ .attr("class", function(d, i) { return "range s"+i+" t"+range.size(); })
.attr("width", w0)
.attr("height", extentY)
.attr("x", reverse ? x0 : 0)
@@ -67,29 +71,12 @@ d3.bullet = function() {
.attr("width", w1)
.attr("height", extentY);
- // Update the measure rects.
- var measure = wrap.selectAll("rect.measure")
- .data(measurez);
-
- measure.enter().append("rect")
- .attr("class", function(d, i) { return "measure s" + i; })
- .attr("width", w0)
- .attr("height", extentY / 3)
- .attr("x", reverse ? x0 : 0)
- .attr("y", extentY / 3);
-
- d3.transition(measure)
- .attr("width", w1)
- .attr("height", extentY / 3)
- .attr("x", reverse ? x1 : 0)
- .attr("y", extentY / 3);
-
// Update the marker lines.
var marker = wrap.selectAll("line.marker")
.data(markerz);
marker.enter().append("line")
- .attr("class", "marker")
+ .attr("class", function(d, i) { return "marker s"+i; })
.attr("x1", x0)
.attr("x2", x0)
.attr("y1", extentY / 6)
@@ -101,8 +88,27 @@ d3.bullet = function() {
.attr("y1", extentY / 6)
.attr("y2", extentY * 5 / 6);
+ // Update the measure rects.
+ var measure = wrap.selectAll("rect.measure")
+ .data(measurez);
+
+ measure.enter().append("rect")
+ .attr("class", function(d, i) { return "measure s"+i; })
+ .sort(d3.descending)
+ .attr("width", w0)
+ .attr("height", extentY / 3)
+ .attr("x", reverse ? x0 : 0)
+ .attr("y", extentY / 3);
+
+ d3.transition(measure)
+ .attr("width", w1)
+ .attr("height", extentY / 3)
+ .attr("x", reverse ? x1 : 0)
+ .attr("y", extentY / 3);
+
var axis = g.selectAll("g.axis").data([0]);
axis.enter().append("g").attr("class", "axis");
+ if (!vertical) axis.attr("transform", "translate(0,"+extentY+")")
axis.call(xAxis.scale(x1));
});
d3.timer.flush();
@@ -153,18 +159,6 @@ d3.bullet = function() {
return d3.rebind(bullet, xAxis, "tickFormat");
};
-function bulletRanges(d) {
- return d.ranges;
-}
-
-function bulletMarkers(d) {
- return d.markers;
-}
-
-function bulletMeasures(d) {
- return d.measures;
-}
-
function bulletWidth(x) {
var x0 = x(0);
return function(d) {