2 // Chart design based on the recommendations of Stephen Few. Implementation
3 // based on the work of Clint Ivy, Jamie Love, and Jason Davies.
4 // http://projects.instantcognition.com/protovis/bulletchart/
6 nv.models.bullet = function() {
8 //============================================================
9 // Public Variables with Default Settings
10 //------------------------------------------------------------
12 var margin = {top: 0, right: 0, bottom: 0, left: 0}
13 , orient = 'left' // TODO top & bottom
15 , ranges = function(d) { return d.ranges }
16 , markers = function(d) { return d.markers }
17 , measures = function(d) { return d.measures }
18 , forceX = [0] // List of numbers to Force into the X scale (ie. 0, or a max / min, etc.)
22 , dispatch = d3.dispatch('elementMouseover', 'elementMouseout')
25 //============================================================
28 function chart(selection) {
29 selection.each(function(d, i) {
30 var availableWidth = width - margin.left - margin.right,
31 availableHeight = height - margin.top - margin.bottom,
32 container = d3.select(this),
33 mainGroup = nv.log(this.parentNode.parentNode).getAttribute('transform'),
34 heightFromTop = nv.log(parseInt(mainGroup.replace(/.*,(\d+)\)/,"$1"))); //TODO: There should be a smarter way to get this value
36 var rangez = ranges.call(this, d, i).slice().sort(d3.descending),
37 markerz = markers.call(this, d, i).slice().sort(d3.descending),
38 measurez = measures.call(this, d, i).slice().sort(d3.descending);
41 //------------------------------------------------------------
44 // Compute the new x-scale.
45 var MaxX = Math.max(rangez[0] ? rangez[0]:0 , markerz[0] ? markerz[0] : 0 , measurez[0] ? measurez[0] : 0)
46 var x1 = d3.scale.linear()
47 .domain([0, MaxX]).nice() // TODO: need to allow forceX and forceY, and xDomain, yDomain
48 .range(reverse ? [availableWidth, 0] : [0, availableWidth]);
50 // Retrieve the old x-scale, if this is an update.
51 var x0 = this.__chart__ || d3.scale.linear()
52 .domain([0, Infinity])
55 // Stash the new scale.
58 //------------------------------------------------------------
61 //------------------------------------------------------------
62 // Setup containers and skeleton of chart
64 var wrap = container.selectAll('g.nv-wrap.nv-bullet').data([d]);
65 var wrapEnter = wrap.enter().append('g').attr('class', 'nvd3 nv-wrap nv-bullet');
66 var gEnter = wrapEnter.append('g');
67 var g = wrap.select('g');
69 wrap.attr('transform', 'translate(' + margin.left + ',' + margin.top + ')');
71 //------------------------------------------------------------
75 var w0 = function(d) { return Math.abs(x0(d) - x0(0)) }, // TODO: could optimize by precalculating x0(0) and x1(0)
76 w1 = function(d) { return Math.abs(x1(d) - x1(0)) };
79 // Update the range rects.
80 var range = g.selectAll('rect.nv-range')
83 range.enter().append('rect')
84 .attr('class', function(d, i) { return 'nv-range nv-s' + i; })
86 .attr('height', availableHeight)
87 .attr('x', reverse ? x0 : 0)
88 .on('mouseover', function(d,i) {
89 dispatch.elementMouseover({
91 label: (i <= 0) ? 'Maximum' : (i > 1) ? 'Minimum' : 'Mean', //TODO: make these labels a variable
92 pos: [x1(d), heightFromTop]
95 .on('mouseout', function(d,i) {
96 dispatch.elementMouseout({
98 label: (i <= 0) ? 'Minimum' : (i >=1) ? 'Maximum' : 'Mean' //TODO: make these labels a variable
103 .attr('x', reverse ? x1 : 0)
105 .attr('height', availableHeight);
108 // Update the measure rects.
109 var measure = g.selectAll('rect.nv-measure')
112 measure.enter().append('rect')
113 .attr('class', function(d, i) { return 'nv-measure nv-s' + i; })
115 .attr('height', availableHeight / 3)
116 .attr('x', reverse ? x0 : 0)
117 .attr('y', availableHeight / 3)
118 .on('mouseover', function(d) {
119 dispatch.elementMouseover({
121 label: 'Current', //TODO: make these labels a variable
122 pos: [x1(d), heightFromTop]
125 .on('mouseout', function(d) {
126 dispatch.elementMouseout({
128 label: 'Current' //TODO: make these labels a variable
132 d3.transition(measure)
134 .attr('height', availableHeight / 3)
135 .attr('x', reverse ? x1 : 0)
136 .attr('y', availableHeight / 3);
140 // Update the marker lines.
141 var marker = g.selectAll('path.nv-markerTriangle')
144 var h3 = availableHeight / 6;
145 marker.enter().append('path')
146 .attr('class', 'nv-markerTriangle')
147 .attr('transform', function(d) { return 'translate(' + x0(d) + ',' + (availableHeight / 2) + ')' })
148 .attr('d', 'M0,' + h3 + 'L' + h3 + ',' + (-h3) + ' ' + (-h3) + ',' + (-h3) + 'Z')
149 .on('mouseover', function(d,i) {
150 dispatch.elementMouseover({
153 pos: [x1(d), heightFromTop]
156 .on('mouseout', function(d,i) {
157 dispatch.elementMouseout({
163 d3.transition(marker)
164 .attr('transform', function(d) { return 'translate(' + x1(d) + ',' + (availableHeight / 2) + ')' });
166 marker.exit().remove();
176 //============================================================
177 // Expose Public Variables
178 //------------------------------------------------------------
180 chart.dispatch = dispatch;
182 // left, right, top, bottom
183 chart.orient = function(_) {
184 if (!arguments.length) return orient;
186 reverse = orient == 'right' || orient == 'bottom';
190 // ranges (bad, satisfactory, good)
191 chart.ranges = function(_) {
192 if (!arguments.length) return ranges;
197 // markers (previous, goal)
198 chart.markers = function(_) {
199 if (!arguments.length) return markers;
204 // measures (actual, forecast)
205 chart.measures = function(_) {
206 if (!arguments.length) return measures;
211 chart.forceX = function(_) {
212 if (!arguments.length) return forceX;
217 chart.width = function(_) {
218 if (!arguments.length) return width;
223 chart.height = function(_) {
224 if (!arguments.length) return height;
229 chart.margin = function(_) {
230 if (!arguments.length) return margin;
231 margin.top = typeof _.top != 'undefined' ? _.top : margin.top;
232 margin.right = typeof _.right != 'undefined' ? _.right : margin.right;
233 margin.bottom = typeof _.bottom != 'undefined' ? _.bottom : margin.bottom;
234 margin.left = typeof _.left != 'undefined' ? _.left : margin.left;
238 chart.tickFormat = function(_) {
239 if (!arguments.length) return tickFormat;
244 //============================================================