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) {